Skip to main content
Back to blog

Critiq team

  • guides

What critiq audit secrets catches (and what it doesn't)

An honest look at Critiq's pattern-based secret scanner: what it finds, what it skips, and when to use audit secrets vs check.

What critiq audit secrets catches (and what it doesn't) content

Accidental credential commits are one of the fastest ways to turn a small PR into an incident. Critiq ships a built-in text scanner in the open source CLI so you can catch common secret shapes before they land on main, without signing up for a hosted vault product or wiring a separate SaaS into every repo.

The dedicated command is critiq audit secrets. It runs the same pattern engine that critiq check uses for its advisory secret summary, but with its own exit code, richer terminal output (including code frames), and JSON output shaped for CI gates. This post is an honest scope guide: what the scanner is good at, what it deliberately is not, and how to choose between audit secrets and check in your workflow.

Pattern-based detection, not live validation

Critiq's secret scanner is a deterministic, text-based pass over file contents. It looks for credential-shaped literals using a fixed set of regex detectors, PEM private key blocks, AWS access key prefixes, GitHub and Slack token shapes, Stripe keys, database URLs with embedded passwords, and high-entropy literals assigned to sensitive variable names.

That design choice matters. The scanner does not call cloud provider APIs to check whether a key is still active. It does not connect to HashiCorp Vault, AWS Secrets Manager, or your internal secret store. It does not rank findings by "realness" using a black-box ML model. If a string in your repo matches a known secret shape, you get a finding with a stable fingerprint, file location, and detector id, the same way catalog rules stay inspectable.

Pattern matching has trade-offs. You get fast, local, reproducible results on every machine. You also accept that some matches will be test fixtures, commented examples, or revoked keys that still look like secrets on disk. Critiq gives you config hooks to tune that noise, but the baseline posture is: surface likely leaks early and let humans (or your review process) decide what to rotate.

What critiq audit secrets catches

Today's OSS scanner ships thirteen detectors. Each has a stable detectorId you can disable or reference in config:

  • secrets.pem-private-key-block, PEM-encoded private key material (RSA, EC, and similar BEGIN/END blocks)
  • secrets.aws-access-key-id, AKIA-prefixed AWS access key ids
  • secrets.aws-session-access-key-id, ASIA-prefixed temporary session key ids
  • secrets.github-classic-pat, GitHub classic personal access tokens (ghp_)
  • secrets.github-oauth, GitHub OAuth access tokens (gho_)
  • secrets.github-fine-grained, GitHub fine-grained PATs (github_pat_)
  • secrets.google-api-key, Google API keys (AIza…)
  • secrets.openai-api-key, OpenAI secret keys (sk-proj-…)
  • secrets.slack-token, Slack bot, user, and app tokens (xoxb-, xoxp-, etc.)
  • secrets.stripe-secret, Stripe live secret keys (sk_live_)
  • secrets.stripe-test-secret, Stripe test secret keys (sk_test_)
  • secrets.database-url-with-credentials, postgres, mysql, and mariadb URLs with username:password@ host segments
  • secrets.high-entropy-assignment, long random-looking literals next to names like apiKey, clientSecret, accessToken, or password

The high-entropy detector is intentionally bounded. It fires when a sensitive key name appears near a 32–120 character literal, not when any long random string appears anywhere in the file. That reduces false positives from UUIDs and hashes while still catching the classic const apiKey = "…" mistake in application code.

Secret scans also respect the same path eligibility rules as critiq check, with one important difference for diff mode: changed files are enumerated without filtering to source-adapter extensions. That means .env files, YAML config, and other non-code paths in a PR diff are scanned. Rule evaluation still uses adapter-filtered scope; secrets use the broader file list so env leaks do not slip through because they are not TypeScript.

What it does not catch

Being clear about limits is as important as listing detectors. critiq audit secrets is not a replacement for a full secret-management platform or a provider-validated leak scanner. Here is what is out of scope today:

  • Vault and secret-store scanning, Critiq reads files in your working tree or git scope. It does not inventory AWS Secrets Manager, GCP Secret Manager, 1Password, or Vault paths.
  • Live credential validation, Findings are shape-based. A revoked AWS key that still matches AKIA… will report the same as an active one. Rotation and provider-side revocation are your follow-up, not the CLI's job.
  • Entropy-only magic, There is no standalone "find anything that looks random" mode. High-entropy detection requires a sensitive variable name nearby.
  • Exhaustive provider coverage, Thirteen detectors cover common cloud and SaaS token shapes, not the hundreds of vendor-specific patterns commercial scanners maintain.
  • Historical org-wide leak analytics, The OSS CLI scans the target you point at (repo, diff, or staged index). It does not crawl every branch in your organization or correlate findings across repos.
  • SARIF export for secret findings, critiq check supports SARIF for catalog rules; audit secrets supports pretty and JSON output only.

Hardcoded credentials inside application logic can also surface through catalog rules such as ts.security.hardcoded-credentials, an AST-driven check scoped to auth-related misuse in TypeScript and JavaScript. That rule and the text scanner complement each other: one catches literal assignment patterns in code structure, the other catches token-shaped strings in any text file including configs and env templates.

critiq audit secrets vs critiq check

Both commands run the same underlying secret engine. The difference is packaging, exit behavior, and what else runs in the same invocation.

  • critiq check runs the full public rule catalog (435+ OSS rules today) plus an advisory secret scan. Secret findings appear in pretty output as a summary block and in JSON under secretsScan. The exit code stays catalog-driven, secret matches alone do not fail the run.
  • critiq audit secrets runs only the secret scanner. It exits non-zero when secret findings exist (exit code 1, subject to the same diagnostic conventions as other CLI commands). Use it when you want CI or git hooks to hard-fail on credential-shaped literals.
  • Output depth, audit secrets prints code frames around matches in pretty mode. check prints a compact count and one line per finding so the rule results stay primary.
  • Scope flags, both support --base / --head for diff-scoped scans and --staged for index-only pre-commit scans. check also supports SARIF and HTML for catalog findings; audit secrets does not.

The GitHub Action (critiq-action) runs critiq check on pull requests. If you need secret findings to block merges, add a separate workflow step for critiq audit secrets, do not rely on the check exit code alone.

Try it locally

Start with a full-repo scan to see what the engine reports in your tree:

npx @critiq/cli audit secrets .
critiq audit secrets . --format json

JSON output is a single document with command, target, scope, scannedFileCount, findingCount, findings (each with detectorId, summary, fingerprint, and locations), diagnostics, and exitCode. Pretty output adds terminal code frames so you can jump straight to the line.

For pull-request or pre-push workflows, scope to changed files only:

critiq audit secrets . --base origin/main --head HEAD
critiq audit secrets . --base origin/main --head HEAD --format json

For pre-commit hooks, scan staged index content without touching unstaged working-tree files:

critiq audit secrets . --staged

Compare with critiq check on the same tree. You will see catalog rule findings plus the advisory secret block at the bottom of pretty output:

critiq check .
critiq check . --format json | jq '.secretsScan'

The JSON envelope includes secretsScan alongside rule results, useful for dashboards, but remember the check exit code ignores secret findingCount:

{
  "command": "check",
  "format": "json",
  "secretsScan": {
    "scannedFileCount": 12,
    "findingCount": 1,
    "findings": [
      {
        "detectorId": "secrets.aws-access-key-id",
        "summary": "Possible AWS access key id (AKIA…)",
        "fingerprint": "<64-char hex>",
        "locations": { "primary": { "path": "config/deploy.env", "line": 4, "column": 1 } }
      }
    ],
    "diagnostics": []
  }
}

When to use each command

Use critiq check when you want deterministic rule coverage, security, correctness, performance, and quality findings from the public catalog, and you are fine treating secret matches as informational for now. That is the default local dev loop and what critiq-action runs on PRs.

Use critiq audit secrets when secret leakage should fail the command, pre-commit hooks, pre-push guards, dedicated CI jobs, or any automation where a non-zero exit must mean "possible credential in scope." If your policy says "no AKIA strings in git, period," wire audit secrets, not check alone.

Many teams run both: check on every PR for rule signal, audit secrets as a parallel or preceding step when secret gating matters. The commands share secretsScan config, so ignore paths and disabled detectors stay consistent.

CI and git hook tips

Keep secret gating fast by scoping scans to what changed. Full-repo scans are fine for nightly jobs; PR and hook paths should prefer diff or staged mode so feedback stays under a few seconds on typical changesets.

Sample hook scripts ship in @critiq/cli under scripts/hooks/. The pre-commit sample runs audit secrets with --staged; the pre-push sample diffs against origin/main (override with CRITIQ_PRE_PUSH_BASE). Copy them into .git/hooks/ or translate the same commands into Husky, lefthook, or pre-commit framework configs.

#!/usr/bin/env sh
# Pre-commit: scan staged blobs only
set -eu
cd "$(git rev-parse --show-toplevel)"
exec npx --no-install @critiq/cli audit secrets . --staged

In GitHub Actions, pin the CLI version and run audit secrets on the PR diff before or alongside critiq-action:

- name: Secret scan (gate)
  run: npx @critiq/cli audit secrets . --base origin/main --head HEAD --format json

- name: Rule scan (critiq-action)
  uses: critiq-dev/critiq-action@v1
  with:
    fail-on-severity: high

Parse JSON in CI when you need fingerprints for allowlists or when you post findings to a dashboard. Each finding's fingerprint is a stable 64-character hex hash derived from detector id, path, and span, use it with suppressFingerprints in config only for reviewed, intentional fixtures.

Tuning with secretsScan config

Both check and audit secrets honor an optional secretsScan block in .critiq/config.yaml:

secretsScan:
  ignorePaths:
    - "**/test/fixtures/**"
    - "**/*.snap"
  disabledDetectors:
    - "secrets.stripe-test-secret"
  suppressFingerprints:
    - "<64-char hex from audit secrets --format json>"

ignorePaths adds globs on top of catalog-level ignorePaths. disabledDetectors matches detectorId values from findings, useful when test suites intentionally include Stripe test keys. suppressFingerprints is for fixtures you have already reviewed; treat it as a narrow escape hatch, not a way to silence production leaks.

What comes next

The OSS scanner will grow more detectors and richer output over time, SARIF for secret findings and broader provider coverage are on the roadmap, but the contract stays the same: local, inspectable, pattern-based detection with explicit limits. Hosted org-wide secret operations and provider-validated scanning live in the product backlog, not in today's CLI.

For step-by-step hook install, JSON field reference, and exit code tables, see the audit secrets guide at https://docs.critiq.dev/guides/audit-secrets and the CLI reference at https://docs.critiq.dev/oss/audit-secrets. Run the commands on your repo, read the findings, and decide from the output, that is the loop Critiq is built for.