CLI Reference
xgrep flags and subcommands.
CLI Reference
xgrep [flags] -f <rules> <targets...>
Flags:
-f, --rules string path to rule file or directory
-c, --config string path to rule file or directory (alias for --rules)
--json output results as JSON
--stream with --json: emit findings as newline-delimited JSON (NDJSON) during the scan
--sarif output results as SARIF
--gitlab output results as a GitLab SAST report (gl-sast-report.json)
--project-root string repository root for report file paths (default: auto-detected git root)
--sarif-category string SARIF automationDetails.id / GitHub Code Scanning category (default: xgrep)
--error exit with code 1 if findings are found (default: exit 0 even with findings)
--max-findings int exit 1 only when active findings exceed N (gradual rollout); takes precedence over --error; -1 disables (default)
--disable-nosemgrep ignore inline nosemgrep/nogrep suppression comments and report all findings
-j, --jobs int number of parallel workers (default: NumCPU)
--severity string minimum severity to report (INFO, WARNING, ERROR)
--category string only run rules in this category (e.g. security, correctness); default: security,secrets for built-in rules
--subcategory string only run rules in this subcategory tier (e.g. vuln for exploitable-only)
--exclude-subcategory string skip rules in this subcategory tier (e.g. audit to drop hardening/advisory rules)
--with-builtin string with -f/--rules, also run the built-in rules from the given categories (comma-separated)
--include-opt-in also run rules marked metadata.opt-in: true (off by default)
--include-tests keep security findings in test/spec/fixture/example paths (dropped by default; secrets are always kept)
--all-files also scan untracked files (default: git-tracked only)
--include string include only files matching glob pattern
--exclude string exclude files matching glob pattern
--max-target-bytes int skip files larger than N bytes
-o, --output string write output to file instead of stdout
--rule-id string only run rules with matching IDs (comma-separated)
--skip-rule string skip rules with matching IDs (comma-separated)
--no-cache do not write the .xgrep/findings.json cache that `xgrep fix` reads
--no-dep-scan skip dependency vulnerability scanning; report code (SAST) findings only
--sbom string scan an existing SBOM file (CycloneDX/SPDX/Mondoo JSON) for dependency vulnerabilities instead of walking a code tree
--incognito local-only scan: do not report findings to Mondoo Platform (reporting is otherwise automatic when a service account is configured)
--scope-mrn string target Mondoo space/org MRN for reporting (defaults to the scope in the service-account config)
--mondoo-config string path to the Mondoo service-account config (default: MONDOO_CONFIG_PATH or ~/.config/mondoo/mondoo.yml)
--verbose enable debug outputFlags shown with a type (string, int) take an argument; the rest
(--include-opt-in, --include-tests, --no-cache, …) are booleans that take none.
For the complete, current flag list (including --baseline-commit, --history,
--decode, --timeout, --max-memory, --lang, --metrics,
--stdin, and profiling flags), run xgrep --help.
Rule selection
The built-in corpus is filtered by category and tier before it runs:
--category(defaultsecurity,secrets) picks the top-level rule sets:security,secrets,correctness, … Secrets scanning is on by default, so a committed credential is reported without any extra flag.--subcategory/--exclude-subcategorynarrow by exploitability tier (vulnvsaudit, below).--rule-id/--skip-rule(and the Semgrep-compatible--exclude-rulealias) include or exclude individual rule IDs.--include-opt-inadditionally runs rules markedmetadata.opt-in: true— higher-noise or situational rules that are off by default.--skip-rulestill excludes them.
To run your own rules alongside the built-ins, pass -f/--rules together
with --with-builtin <categories>; a custom rule overrides a built-in rule with
the same ID, and an explicit --category still filters the whole merged set:
xgrep scan -f rules/ --with-builtin security,secrets src/Exploitability tier (--subcategory)
Every built-in security rule carries a metadata.subcategory tier:
vuln— exploitable / attacker-reachable impact: injection, eval/dynamic exec, deserialization, SSRF, path traversal, auth bypass, open redirect, hardcoded credentials/secret exposure, and insecure-TLS / disabled cert validation.audit— hardening / best-practice / advisory with no direct exploit: missing security headers, cookie flags, weak hashing/ciphers, timing attacks on constants, info disclosure (stacktraces, cleartext logging), availability DoS (ReDoS), and regex/validation smells.
Filter on it to get an exploitable-only scan (high signal, fewer advisories):
xgrep scan --category security --subcategory vuln <target>--exclude-subcategory audit is the inverse and yields the same set when every
rule is tiered. Combine with --xgrepignore to also drop
non-source trees — the recommended setup for scanning a focused executable
surface (e.g. an AI-agent skill):
xgrep scan --category security --subcategory vuln --xgrepignore <target>Both flags accept a comma-separated list and compose with --category,
--severity, and --rule-id/--skip-rule.
Subcommands
scan scan targets (default when -f is provided)
sbom generate a Software Bill of Materials (deps), or a CBOM with --cbom
ci CI-optimized diff-aware scan (auto-detects the CI environment)
login authenticate with Mondoo Platform using a registration token
inspect code intelligence: search symbols, navigate definitions, assess impact
graph build and query the code graph
mcp run as an MCP server over stdio (for AI agents)
skill list and install the Claude Code skills bundled in the binary
test <path> run tests on rule files in a directory
validate <path> validate rule files without scanning
lsp start an LSP server over stdio
version print version and exitloginand reporting to the platform (automatic when a service account is configured;--incognitoto disable) are documented under Mondoo Platform.inspectandgraphare documented under Code intelligence.lspis documented under IDE integration.mcpis documented under Integrations.skillis documented under AI agents → Skills.testis documented under Testing rules.sbomgenerates a dependency SBOM, or a Cryptography Bill of Materials withsbom --cbom— see CBOM.
Fixing findings (xgrep fix)

scan discovers and never mutates source; the separate fix command is the
only one that writes. scan --json emits each fixable finding's edits
(extra.fix_info.edits) and contracts; fix consumes those and applies them
through the verification harness. Every fix carries a confidence kind,
surfaced in --json as extra.fix_info:
deterministic— exactly one correct edit, computed from the match.fixapplies these directly.assisted— context-dependent; emitted as a contract for an external agent (not auto-applied). The contract isextra.fix_info.contract: theunsafe_regionto change (byte span + enclosingfunctionwhen a code graph is available), thestrategy(the rule'sfix-hint), an optionalcanonical_fix("from → to"), therecognized_sanitizersthe re-scan gate accepts, and theacceptance_criteriathe harness enforces.advisory— natural-language guidance only (the rule'sfix-hint).
A deterministic fix carries its edits as extra.fix_info.edits — one or more
byte-span replacements (start_byte/end_byte/replacement), each annotated
with its 1-based line/column span so renderers can place a multi-edit fix without
re-reading the file. The same edits drive the SARIF fixes (one replacement per
edit) and the LSP quickfix (one TextEdit per edit).
Every edit passes a verification harness before being written:
- Parse-clean gate — the patched file is re-parsed; a fix that would introduce a syntax error into a previously-clean file is rejected. xgrep never writes code it would refuse to report.
- Atomic write — a temp file in the same directory is renamed over the target, preserving mode; there is no partial-write window.
- Re-scan gate (on by default;
--no-rescanskips it) — the patched file is re-scanned and the fix is rejected unless it actually cleared the finding without introducing an equal-or-higher-severity finding in the region it touched.
fix has two verbs: fix verify never writes (returns a verdict + diff
preview), fix apply writes the accepted edit atomically. Input precedence:
a scan --json report or candidate piped on stdin wins; otherwise the
.xgrep/findings.json cache scan writes is used. fix emits a FixOutcome per
finding — applied / verified / rejected / needs-agent / advisory /
out-of-scope / stale-cache. Scope which findings are fixed with --rule
(rule-ID allowlist), --min-confidence, --max-severity, and --confirmed
(triage-reviewed true-positives only — see Fixing only confirmed
findings below); out-of-scope fixes are
reported, never silently dropped. Pass --strict to exit non-zero on any
rejection, so a CI pipeline can gate on fix failures.
# Preview every deterministic fix as a diff (no write).
xgrep scan --json ./src | xgrep fix verify
# Apply only high-confidence fixes for one rule.
xgrep scan --json ./src | xgrep fix apply --rule js-incorrect-suffix-check
# Or use the cache scan writes: discover once, then fix.
xgrep scan ./src && xgrep fix applyInteractive review + agent fixing (xgrep fix)
Run bare xgrep fix in a terminal (no pipe) and it becomes an interactive review
session: walk the findings, triage each, and apply or delegate the fixes — a TUI over
the same harness.
xgrep fix # review the .xgrep cache interactively
xgrep fix ./src # scan ./src first if there's no cache, then reviewKeys: ↑/↓ move (or click a finding) · t/f/s mark true-positive /
false-positive / needs-review (written to extra.triage) · u reset the verdict to
unreviewed · x fix this finding (xgrep applies the deterministic edit or
delegates the assisted one to the agent automatically) · space add to the batch,
* select all, enter fix the selected set · g show xgrep graph context ·
q quit. The mouse wheel (or PgUp/PgDn) scrolls the detail pane.
The detail pane previews the fix for the selected finding — a real before→after
diff for deterministic fixes, or the plan/contract for assisted ones (the agent
authors those at run time). To fix several at once, space toggles a finding into
the batch (* selects all fixable), then enter shows a plan summary you confirm
(enter) or cancel (esc); the confirmed set is fixed in one pass — deterministic
edits applied, assisted findings delegated — each proven by the same re-scan.
Delegation shells out to your installed coding-agent CLI — claude by default, or
codex, or a custom command. The agent investigates with xgrep graph/inspect and
lands its edit through xgrep fix verify/apply; xgrep re-scans afterward and
only reports applied if the finding actually cleared. xgrep never calls a model
itself — it orchestrates the agent you already run.
Pick the agent with --agent (or XGREP_AGENT, or agent.default in the config):
xgrep fix --agent claude
xgrep fix --agent codex
xgrep fix --agent "my-agent --flag {prompt}" # custom; {prompt} marks the promptThe same orchestration runs non-interactively for CI — no terminal, driven by flags:
# Delegate triage of un-reviewed findings to the agent, then fix the confirmed ones.
xgrep fix --agent claude --triage-agent --yes--yes (or any non-terminal invocation) skips the TUI; piped input
(scan --json | fix) stays the batch path.
Fixing only confirmed findings
Between discovery and fixing, a finding is usually triaged — reviewed as a true
positive, a false positive, or needs-review. xgrep carries that verdict on the
finding itself, in an optional extra.triage object, so the whole
scan → triage → fix loop round-trips through one findings.json:
"triage": {
"reviewed": true,
"status": "true-positive", // | "false-positive" | "needs-review"
"rationale": "uid flows unsanitized into execute()",
"reviewed_by": "alice", // a reviewer or a tool name
"reviewed_at": "2026-07-01T10:30:00Z"
}xgrep never sets triage during a scan — it is written after the scan by a
reviewer or an agent (the xgrep-triage skill writes
it back, joined to findings by fingerprint). xgrep fix then reads it:
- By default, a finding marked
status: false-positiveis never auto-fixed —fixwill not undo a verdict a reviewer made (the triage analog of an inlinenosemgrep). Everything else fixes as usual. - With
--confirmed, only findings markedstatus: true-positiveare fixed; un-reviewed findings (notriage, orneeds-review) are reportedout-of-scope.
# Fix only what review confirmed — a one-flag CI gate after triage.
xgrep fix apply --confirmed < triaged-findings.json--confirmed scopes the deterministic auto-apply batch; for assisted fixes, the
agent driving the contract applies the same true-positive filter.
Agent round-trip (assisted fixes)
assisted findings aren't auto-applied — xgrep emits a fix contract and an
external agent authors the edit, which fix then verifies through the same
harness. A candidate is a JSON object on stdin (path, rule_id, edits):
# Verify a candidate without writing; returns a verdict + diff preview.
echo '{"path":"src/db.py","rule_id":"py-sql-injection","edits":[
{"start_byte":420,"end_byte":480,"replacement":"..."}]}' \
| xgrep fix verify -f rules.yaml
# Apply it (fix apply always writes the accepted edit, gated by the same harness).
echo '{…candidate…}' | xgrep fix apply -f rules.yaml
# Fix many findings in one process with NDJSON streaming.
cat candidates.ndjson | xgrep fix apply --batch -f rules.yamlA fix that spans more than one file — e.g. add an import in one file and change
the call site in another — is expressed as a changeset: a top-level
files array of {path, edits}, verified and applied as one all-or-nothing unit
(no file is written unless every file passes the harness). fix autodetects it on
stdin and reports one FixOutcome whose paths lists every file touched.
echo '{"rule_id":"py-sql-injection","files":[
{"path":"src/db.py", "edits":[{"start_byte":420,"end_byte":480,"replacement":"…"}]},
{"path":"src/util.py", "edits":[{"start_byte":0,"end_byte":0,"replacement":"import shlex\n"}]}
]}' | xgrep fix apply -f rules.yamlA rejected candidate returns a machine-readable reason — span-conflict,
parse-error, finding-not-cleared, new-finding-introduced, or no-change —
to iterate on. The same operations are available over MCP as fix_verify and
fix_apply. See the AI agent guide for the full loop.
In the LSP, each diagnostic's server-defined data payload carries fixKind
(deterministic / assisted / advisory, omitted when the rule offers no
remediation) alongside hasFix. Editor clients read it to decide which
affordance to surface — e.g. an "Apply xgrep fix" quickfix for deterministic,
or an AI-assisted fix action for assisted (which then fetches the contract via
scan --json and drives the xgrep fix verify/apply round-trip).
Scan targets
A scan target is more than a single file. xgrep accepts any of these as
<targets...>, and you can pass several at once:
- A file —
xgrep scan app.pyscans just that file. - A directory —
xgrep scan .scans it recursively. By default xgrep scans the git-tracked files under the directory (respecting.gitignore), falling back to a filesystem walk when the target isn't a git repository. See File filtering for what is and isn't included. - Multiple targets —
xgrep scan src/ lib/ config.yamlscans the union. - A remote git repository —
xgrep scan github.com/expressjs/expressclones and scans it, no manual checkout needed. See Scanning a remote repository below. - stdin — pipe a single source with
--stdin, or a multi-file JSON manifest with--stdin-files. See Scanning from stdin below.
With no target and no stdin, xgrep prints a welcome screen instead of scanning.
Scanning from stdin
When the code to scan isn't on disk — an editor buffer, a generated snippet, a file pulled from an API — feed it on stdin instead of a path:
# A single source. --lang is required (there's no filename to detect from).
cat app.py | xgrep scan --stdin --lang python
# A set of files in one call, as a JSON manifest [{path, content}, ...].
# Cross-file taint is preserved across the set.
xgrep scan --stdin-files < files.json--stdinreads one source from stdin and requires--langto set the language. Findings are reported against a synthetic path.--stdin-filesreads a JSON array of{path, content}objects and scans them together, preserving the relative paths (so cross-file dataflow works).- stdin input is mutually exclusive with
--historyand--baseline-commit.
Scanning a remote repository
A scan target can be a remote git repository instead of a local path; xgrep
clones it (shallowly, default branch) into a temp directory, scans it, and
reports repo-relative paths. Cloning uses a built-in git client — no
git binary required.
xgrep scan github.com/expressjs/express # host/owner/repo shorthand
xgrep scan https://github.com/expressjs/express # explicit https
xgrep scan git@github.com:expressjs/express.git # SSH
xgrep scan github.com/expressjs/express --ref 4.18.2 # a tag, branch, or commit
xgrep scan github.com/expressjs/express --full-clone # full history instead of shallow- A target is treated as remote only when it isn't an existing local path, so a local directory always scans in place.
--ref <branch|tag|commit>checks out a specific ref before scanning (default: the repo's default branch). A commit SHA implies a full clone.--depth <n>sets the shallow clone depth (default1);--full-cloneclones full history. The two are mutually exclusive.- Private repositories use your usual git credentials: SSH targets via the
ssh-agent; HTTPS targets via a token in the environment —
GITHUB_TOKEN,GITLAB_TOKEN, or the genericXGREP_GIT_TOKEN. - Diff-aware scanning of a remote works: combine
--baseline-commitwith a remote target and xgrep clones it with full history automatically (a shallow clone has none to diff a range against), then diffs and reports only changed lines, repo-relative — the same behavior as a local diff-aware scan. Only a single remote target is supported in this mode.xgrep scan github.com/acme/app --baseline-commit v1.0.0..v1.1.0 - Remote scanning needs outbound network access (governed by your environment's network policy in hosted/CI setups).
Suppressing findings (nosemgrep)
Add a nosemgrep (or nogrep) comment on the matched line or the line directly
above it to suppress a finding. Scope it to specific rules with nosemgrep: <id>;
a bare nosemgrep suppresses every rule on that line.
dangerous(user_input) # nosemgrep
dangerous(user_input) # nosemgrep: python-command-injectionSuppressed findings are retained, not deleted, and surface differently per output so CI behaves predictably:
| Output | Suppressed finding |
|---|---|
| Console text | hidden |
| JSON | included, extra.is_ignored: true |
| SARIF | included with suppressions[].kind: "inSource" → GitHub shows it dismissed |
| GitLab SAST | omitted |
Exit code / --error | not counted (a file with only suppressed findings exits 0) |
Pass --disable-nosemgrep to ignore all suppression comments and report every
finding as active — useful for auditing what suppressions are hiding.
Diff-aware scanning (--baseline-commit)
For pull-request / CI checks, --baseline-commit scopes the scan to the files
changed since a baseline so xgrep only parses and runs rules over what changed,
and only reports findings on changed lines. This matches
Semgrep/Opengrep, which expose diff-aware scanning under the same flag.
xgrep --baseline-commit HEAD # changes in the working tree vs HEAD
xgrep --baseline-commit origin/main # changes since origin/main (use the merge-base in CI)
xgrep --baseline-commit origin/main..HEAD # changes in a commit range- The spec is a single ref (diffed against the working tree) or a
<base>..<head>/<base>...<head>range. - Only changed files are scanned; findings on unchanged lines of changed files
are dropped.
--errorexits 1 only when a changed line has a finding. - Content scanned is the working tree, and paths are repository-root
relative — run xgrep from the repo root (the standard CI setup). For an
accurate range, the head should be the working tree /
HEAD. - Because unchanged files are not scanned, interfile (cross-file) analysis sees
only the changed files; omit
--baseline-commitfor a full-context scan.
Scanning git history for secrets (--history)
A secret that was committed and later deleted still lives in the repository's
git history, so it is still compromised. A normal scan only sees the working
tree, and even a diff-aware scan (--baseline-commit) misses an add-then-remove.
--history walks the full commit history and scans the content each commit
introduced, so it catches secrets that no longer exist in the current tree.
# Scan a repo's whole history for secrets (pass the repo path)
xgrep --history --category secrets .
# Bound the walk for speed on large repos
xgrep --history --category secrets --since 2024-01-01 .
xgrep --history --category secrets --max-commits 5000 .Each finding carries commit provenance — who introduced the secret and when —
so you can rotate the credential and purge the history. In JSON/SARIF output this
appears as a commit object:
{
"check_id": "aws-access-key-id",
"path": "config/app.yaml",
"commit": {
"sha": "b719f81a0c…",
"author": "Jane Dev",
"email": "jane@example.com",
"date": "2024-03-02T11:07:14Z"
}
}Notes:
- Pair it with
--category secrets. History scanning runs whatever rules are selected; secrets are the use case it exists for. (The built-in default category issecurity, so without--category secretsyou would scan history with the code-vulnerability rules instead.) - Additions only. Each commit is compared to its first parent and only the lines it added are attributed to it, so a secret is reported at the commit that introduced it — with its true line number in that commit's file.
- Reported once. The same secret touched by many commits is de-duplicated to its earliest introducing commit.
--since <date>acceptsYYYY-MM-DDor an RFC3339 timestamp;--max-commits <n>caps how many commits are walked (0= no limit). Both require--history.- Hermetic. It reads only the local
.gitobject store — no network. To scan a remote repository's history, clone it with full history first (xgrep scan <url> --full-clone) or check it out locally, then run--history. - Mutually exclusive with
--baseline-commitand stdin input.
Decoding encoded payloads (--decode)
Secrets are often committed one encoding layer deep — a base64-wrapped .env, a
gzip'd Kubernetes secret, a token inside a percent-encoded URL. To a normal scan
that outer blob is opaque, so the credential inside it is missed. --decode
decodes encoded payloads and re-runs the secret/generic rules over the decoded
content, so the hidden token is found and reported at the encoded span in the
original file.
# Find secrets hidden inside base64 / hex / url / gzip payloads
xgrep --decode --category secrets .
# Combine with history to sweep encoded secrets across deleted code too
xgrep --history --decode --category secrets .Each decoded finding records the decode chain in metadata.decoded-from, and its
position points at the encoded blob you can see in the file:
{
"check_id": "aws-access-key-id",
"path": "config/app.yaml",
"start": { "line": 12, "col": 14 },
"extra": {
"metadata": { "decoded-from": "base64 > gzip" }
}
}Notes:
- Opt-in. Off by default; a scan without
--decodeis byte-for-byte unchanged. Decoders supported today:base64(standard + URL-safe),hex,url(percent-encoding), andgzip/zlib(peeled when nested). - Bounded. Nesting depth, per-payload input/output size, and per-file budgets are all capped, and an over-sized inflation is abandoned — so a decompression bomb cannot blow up a scan.
- Precise. Decoded bytes that are not printable text are dropped before matching, so decoding never manufactures false positives; it only adds findings on content that really decodes to a credential.
- Hermetic. Decoding is a local, deterministic transform — no network, no temp files. Decoded content is scanned as text, never parsed back into the language/AST engine.
- Pair it with
--category secretsfor the intended use case (the built-in default category issecurity).
Validating secrets (--validate)
Detection finds strings that look like credentials; --validate confirms
whether one is actually live by probing its provider. It turns a
finding's validation_state into:
confirmed— the provider accepted the secret; it is live (act now).unconfirmed— the provider rejected it; revoked/invalid (lower urgency).error— could not determine (network issue / unexpected response).unvalidated— not probed (the default; no--validate, or the rule has no validation endpoint).
xgrep --validate --category secrets <target>{ "check_id": "github-personal-access-token", "extra": { "validation_state": "confirmed" } }This is opt-in and off by default — it is the one mode that makes outbound network calls, so the hermetic default is preserved unless you ask for it:
- The candidate secret is sent only to that rule's fixed provider endpoint (built into the rule, not anything from the scanned content), and is never logged or written to disk — it is held in memory only for the probe.
- Validation runs after detection; it never changes which findings are reported,
only their
validation_state. Probes are bounded by a small concurrency limit and short timeouts to stay gentle on provider APIs. - Only rules with a provider introspection endpoint are validated; the rest stay
unvalidated. Validators currently ship for GitHub, GitLab, Slack, and Stripe tokens (more to follow). - Severity follows the result. A validatable secret is reported at its
reduced
unvalidated-severityuntil proven live; a confirmed finding is raised to the rule's full severity, while unconfirmed/error keep the reduced one. So--validatesharpens the signal — live keys rise to the top, dead ones stay low — rather than just adding a field.
Validating GitHub Action pins (--validate)
The same --validate flag also verifies GitHub Action SHA pins. Pinning an
action to a commit SHA with a version comment is the recommended practice:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1The static rules (in the default scan, no flag needed) check the shape of this
line — that a SHA pin has a version comment, uses a full-length SHA, and isn't
annotated with a mutable ref like # latest. What they cannot check offline
is whether the comment tells the truth: that b4ffde6… really is the commit the
v4.1.1 tag points to. A comment that lies — through drift or a malicious PR —
slips a wrong or hostile commit past a reviewer who trusts the # v4.1.1 they
see.
You must pass --validate to get this check — like secret validation, it is
the one mode that makes outbound calls (it resolves the tag through the GitHub
REST API), so it is off by default. Without --validate, the
actions-sha-pin-version-mismatch rule stays silent: a version-commented pin is
the good state, so it is only ever surfaced as a finding when the API proves the
comment is wrong.
xgrep --validate <target>When enabled, each pin's comment is checked and a mismatch is reported at a severity that reflects how wrong it is:
CRITICAL— the pinned SHA matches no published release tag of the action: an off-release (arbitrary, fork, or unreleased) commit posing as a trusted version.HIGH— the version named in the comment does not exist (deleted/yanked or fabricated), though the SHA is some other real release.MEDIUM— the comment names a real but different release than the SHA.- Honest pins, and pins that can't be checked (network/rate-limit), produce no finding.
Notes:
- Set
GITHUB_TOKENto raise the GitHub API rate limit (the same token pinact uses). - For GitHub Enterprise Server, point the resolver at your instance with
XGREP_GITHUB_API_BASE=https://ghe.example.com/api/v3. - Honest pins cost a single tag lookup; the heavier release-tag listing runs only when a comment doesn't match, so validation stays cheap on a clean repo.
- The version comment must be
v-prefixed (# v4.1.1) — the form pinact, Dependabot, and Renovate emit. Bare-semver comments (# 4.0.2) are left to the static shape checks.
Overview
Run xgrep's SAST (static application security testing) engine against your code — taint and dataflow analysis that traces untrusted input to dangerous sinks, plus output formats, supported languages, Semgrep parity, and custom rules.
Output formats
xgrep emits human-readable text, Semgrep-compatible JSON, SARIF 2.1.0, a GitLab SAST report, or a CycloneDX 1.6 CBOM.