Command line (synapse-cli)¶
Documentation home · Previous: Configuration · Next: Architecture
synapse-cli runs the same SCA pipeline as the server, from the command line. It is built for
CI gating. It creates an ephemeral, scope-checked engagement covering the target path, so scope
enforcement is exercised, not bypassed. Nothing is persisted.
Build it with make build. The binary lands at ./bin/synapse-cli.
The benchmark-only operator binaries synapse-sca-bench, synapse-sca-cycle, and
synapse-sca-prepare are intentionally outside product composition. The cycle
binary runs the fixed same-SBOM accuracy workflow; the prepare binary validates
pinned inputs before capture. Their input and artifact contracts are documented
in SCA accuracy benchmark.
synapse-reachability-cycle current-go-binary-scorecard assembles the hosted
Go-binary regression result from the API and worker binding reports. It requires
Linux/amd64 and SYNAPSE_GOBIN_BINDING_REPORT_DIR pointing to a directory with
api.json and worker.json from the binding tests. It writes a sanitized scorecard
under the process temp directory; missing, mismatched, or stale report identities
and a failed ratchet exit nonzero. The fixture uses the root go.mod and go.sum
pins, so prepare dependencies with go mod download before an offline local run.
The hosted workflow uploads the scorecard as an artifact when generated and fails
the gate unless it passes.
Assessment lifecycle administration¶
These are separate operator binaries, not synapse-cli subcommands. make build
places them in bin/; the production image includes them in /opt/synapse/.
Follow Assessment lifecycle rollout operations
for migration order, dry-run examples, checkpoints, approval and rollback gates.
| Binary | Purpose |
|---|---|
synapse-assessment-backfill |
Create historical singleton Cycles with durable checkpoints. |
synapse-assessment-snapshot-backfill |
Append immutable legacy Snapshots after Cycle backfill. |
synapse-finding-lineage-backfill |
Project source Findings into Identities, Observations, review candidates or explicit skips. |
synapse-assessment-integrity |
Verify membership, boundaries and source reconciliation without repairing source data. |
synapse-assessment-comparison-backfill |
Queue root-to-selected-head comparisons and optionally repair failed artifacts. |
synapse-assessment-rollout-gate |
Evaluate supplied JSON rollout evidence; it does not enable feature flags or collect metrics. |
The first five commands require SYNAPSE_DB_DSN and a non-superuser role that
cannot bypass RLS. They do not run schema migrations. Integrity verification
persists its run/checkpoints/findings but never changes the assessed source data.
| Flag | Applies to / behavior |
|---|---|
--tenants |
All database commands; required comma-separated list of one to four unique tenant IDs. |
--actor |
All database commands; audit actor, 1–256 characters, defaults to the command-specific service actor. |
--dry-run |
All backfills; default false, so pass it explicitly to preview. Integrity defaults to true and rejects false. |
--batch-size |
All database commands; 1–2000 rows/items per batch. Default 500, except comparison uses SYNAPSE_ASSESSMENT_MIGRATION_BATCH_SIZE. |
--timeout |
All database commands; positive duration. Default 30m, or 2h for comparison backfill. |
--lease-duration |
Cycle/Snapshot/Lineage backfill and integrity; positive per-tenant lease, default 10m. |
--resume-after |
Cycle/Snapshot backfill: initial Assessment ID; Lineage: initial Finding ID. Exactly one tenant required. Durable existing runs resume their own checkpoint. |
--producers |
Lineage only; optional comma-separated producer filter. See the operations guide for supported names and deferred matcher skips. |
--repair-failed |
Comparison only; default false. Repair one deterministic failed batch before queueing missing comparisons. |
--backlog-warning |
Comparison only; default from configuration (500), allowed 1–500. |
--backlog-hard-limit |
Comparison only; default from configuration (1000), between the warning threshold and 1000. |
--oldest-active-limit |
Comparison only; positive age gate for queued/generating artifacts, default 15m. |
--after-updated-at, --after-cycle-id |
Comparison only; paired RFC3339 timestamp / Cycle ID checkpoint, exactly one tenant required. |
--phase |
Rollout gate only; required internal_canary, opt_in_canary, read_cutover, ui_default or rollback_drill. |
--input |
Rollout gate only; JSON file or - for stdin (default), at most 1 MiB, exactly one object with no unknown fields. |
Successful runs exit 0; invalid input, runtime errors, integrity findings or a
rejected rollout gate exit 1 and explain the failure on stderr. The rollout gate
also emits the evaluated decision JSON on stdout when policy rejects valid input.
Do not use process success alone as production approval: the operations guide
requires retained evidence and explicit operator/security sign-off.
RulePack release commands¶
The rulepack command group verifies signed detection-content artifacts, replays their deterministic
fixtures, and evaluates attested release evidence. The RulePack content-signing key and gate-evidence
producer key are supplied independently as standard-base64 text containing raw 32-byte Ed25519 public
keys; neither artifact is allowed to self-authorize an embedded trust key.
synapse-cli rulepack verify \
--artifact rulepack.signed.json \
--public-key rulepack-release.pub
synapse-cli rulepack replay \
--artifact rulepack.signed.json \
--public-key rulepack-release.pub
synapse-cli rulepack gate \
--artifact rulepack.signed.json \
--public-key rulepack-release.pub \
--evidence rulepack-gate-evidence.json \
--evidence-public-key rulepack-evidence.pub \
--phase promotion
| Subcommand / flag | Description |
|---|---|
rulepack verify |
Recompute the canonical digest and verify the RulePack signature against --public-key. Prints the verified pack identity as JSON. |
rulepack replay |
Verify the artifact, then run positive fixtures before negative fixtures in deterministic order. Prints replay results as JSON and exits non-zero on any mismatch. |
rulepack gate |
Verify both the RulePack and the attested gate-evidence envelope, emit the deterministic gate report, and exit non-zero when the selected lifecycle phase is not eligible. |
--artifact <file> |
Signed RulePack JSON. Required by all three RulePack subcommands. |
--public-key <file> |
Externally trusted RulePack Ed25519 public key. Required by all three subcommands. |
--evidence <file> |
Attested SignedGateEvidence JSON. Required by rulepack gate. Raw/unattested gate input is rejected. |
--evidence-public-key <file> |
Externally trusted Ed25519 key for the evidence producer. Required by rulepack gate and intentionally separate from the RulePack content key. |
--phase pre-canary\|canary\|promotion |
Which lifecycle threshold to enforce. Default: promotion. pre-canary requires deployment state candidate; canary and promotion require state canary. |
Successful verification/replay/gating exits 0; a signature, provenance, replay, lifecycle-state, or gate
failure exits non-zero and prints the reason on stderr. rulepack gate writes its deterministic report to
stdout after provenance verification even when the requested gate subsequently fails, so CI can retain the
failure evidence. See RulePack release lifecycle for the signed artifact schema, retro-hunt
selector provenance, release-stage ordering, metrics, and rollback semantics.
Doctor¶
doctor is an offline pre-scan readiness check. It does not run a scan, install tools, or
call the network. It reports optional toolchain availability, dependency markers found in the
target tree, and whether SCA, SAST, secret, misconfig, and code-quality coverage is full,
partial, or unavailable.
# preview what Synapse can analyze before scanning the current tree
synapse-cli doctor .
# emit structured output for CI or wrapper scripts
synapse-cli doctor . --json
Scan¶
| Flag | Description |
|---|---|
--mode full\|vulnerabilities\|licenses |
What to scan. Default is full. |
--fail-on critical\|high\|medium\|low\|info |
Exit non-zero if a finding at or above this severity is present. Default is high. |
--image |
Treat the argument as a container image reference, pulled daemonlessly in-process, instead of a local path. |
--offline |
Make the scan run without network egress. Detection uses the local sources only, the owned advisory store, plus Grype's pre-synced database when SYNAPSE_DETECTION_SOURCES names it (Grype is not in the default set), and every network-capable resolver and enricher is switched off: npm, composer, poetry, Bundler, Maven, Gradle, the Maven Central JAR SHA-1 lookup, KEV/EPSS, online NVD CVSS backfill, deps.dev and PyPI license metadata, and AI false-positive triage. SYNAPSE_OFFLINE=true does the same. Recall drops in exchange; the run makes no outbound request. Target acquisition is the one step outside the flag: a registry --image reference or a remote git URL is still fetched, so on an air-gapped runner point the scan at a local path or a local OCI layout. |
--ignore-unfixed |
Ignore vulnerabilities that have no fix available. |
--min-confidence low\|medium\|high\|very_high |
Drop findings below this confidence. Findings that carry no confidence (SAST/misconfig) are kept. Useful to cut lower-signal secret matches. |
--base <ref> |
Scope line-anchored findings (SAST, secret, misconfig) to code changed vs this git ref (Clean-as-You-Code), so a repo with a backlog gates the pipeline only on what a change introduces. Dependency/license findings are not line-attributable and are kept, baseline those with .synapseignore. Local git repos only (not --image). |
--detection-priority comprehensive\|precise |
comprehensive (default) reports every match. precise moves single-source, non-KEV findings into a needs-verify queue that does not trip --fail-on. |
--include-test |
Also gate on findings in test, fixture, and example paths. They are reported but gate-exempt by default. |
--verify-secrets |
Actively confirm each detected credential is live by making one minimal read-only API call to its provider's public host (GitHub, GitLab, OpenAI), stamping the finding verified/unverified/unknown. Opt-in and off by default, and IGNORED under --offline (which forbids network egress). It sends the raw leaked secret over the network to the public provider host, so use it only against credentials you are authorized to test; a self-managed-instance token is still sent to the public host and yields unknown. The secret is never logged or written to output; a verified live credential is raised to very_high confidence, and an unverified or unknown verdict never removes a finding. |
--json |
Print the full scan result as JSON to stdout, for machine consumption in CI. |
--sarif |
Print a SARIF 2.1.0 report to stdout, ready to upload to GitHub code scanning. Covers every finding kind; SAST, secret and misconfig findings carry a file and line so the platform annotates the exact source line. Findings exempted from the CI gate by verified AI consensus remain present and carry an external suppression with the policy version and reason. --fail-on still sets the exit code. |
--sarif-out <file> |
Write the same SARIF report to <file> and keep the human report on stdout. Prefer this in a pipeline: --sarif owns stdout, so redirecting it to a file leaves the job log with nothing but the exit code. |
--sbom |
Print the generated CycloneDX SBOM to stdout instead of a findings report. |
--server <url> --project <key> |
Record the result on a Synapse server as that project's next analysis. The token comes from SYNAPSE_API_TOKEN. https is required unless the host is loopback. See Push results to the console. |
--engagement <id> |
Record the scan's security findings on that engagement, through the server's own SARIF ingest, so a pipeline scan reaches the console the way a code-quality analysis does. Independent of --project: give either, or both to record one run in both places. The server deduplicates against existing first-party findings and reports what it refused. |
--asset <id> |
Bind the findings ingested by --engagement to a business asset. Needs --engagement. |
--coverage <file> |
Record test coverage with the project analysis. lcov, cobertura and jacoco are auto-detected, by the same parser synapse-cli coverage and gate --coverage use. The analysis payload has always carried a line_coverage field and the scan had no way to fill it, so a pushed analysis showed no coverage and a managed gate could not evaluate a coverage condition. Needs --project. |
--push-sbom |
Import the generated CycloneDX SBOM into the engagement, which is how an image scan's component inventory becomes visible on the console beside its findings. The server keeps one active imported SBOM per engagement, so this replaces any SBOM imported by hand. Needs --engagement. |
--push-source |
Upload the scanned tree for the analysis --project creates, so the console's Code view can show the source. Without it a CI-pushed analysis reports source as unavailable with the reason not_retained, because the CLI pushes results and not files. Best-effort: a failed upload warns and names the publish-source command that retries it, and never fails the build. Needs --project. |
--insecure-http |
Accept a plain-http --server that is not loopback. The API token then travels in the clear; use it only on a network you trust. |
--branch <ref>, --run-url <url>, --ci-provider <name> |
What the pipeline says about itself, shown on the analysis in the console. On GitHub Actions, GitLab CI and Jenkins these are read from the provider's variables when not given. |
--json, --sarif, and --sbom each take over stdout completely, so they are mutually exclusive.
Passing more than one exits 2 rather than silently honoring the last flag.
Suppressing findings with .synapseignore¶
Drop known/accepted findings by committing a .synapseignore file at the scan root. Every suppression
must carry a reason and an expires date (YYYY-MM-DD), after the expiry the suppression stops
applying and the finding reappears (with a warning), so suppressions are periodically re-justified instead
of rotting. Each entry matches by rule (a rule key or advisory id), by path (a file glob), or both:
suppress:
- rule: generic-secret
path: "testdata/**"
reason: "test fixtures, not real credentials"
expires: "2026-12-31"
- rule: CVE-2024-1234
reason: "not exploitable in our configuration; tracked in JIRA-123"
expires: "2026-09-30"
A malformed entry (missing reason/expiry, or no matcher) fails the scan rather than silently ignoring nothing. Suppressed counts and any expired entries are printed to stderr.
Examples¶
# fail a build on any high-or-critical vulnerability
synapse-cli scan . --fail-on high
# licenses only
synapse-cli scan . --mode licenses
# scan a container image, offline
synapse-cli scan alpine:3.19 --image --offline
The exit code is 0 when no finding meets the --fail-on threshold. See
Exit codes for the full contract, which distinguishes a gate result from a usage error.
Push results to the console¶
Without --server, synapse-cli scan is a self-contained gate: it prints a report, sets the exit
code, and nothing is persisted. With --server, the same scan is also recorded on a Synapse server
as the named project's next analysis:
export SYNAPSE_API_TOKEN="$CI_SECRET_SYNAPSE_TOKEN"
synapse-cli scan . --fail-on high --server https://synapse.example.com --project payments-api
What happens on the server is exactly what happens for an analysis the server runs itself. The
result goes through the same recorder, so it takes its place in the project's history, moves the
trend on the Activity page, is evaluated against the project's managed quality gate, and carries
ratings, issues and hotspots. The analysis is marked origin: ci and shows the branch, the run and
the actor the pipeline reported, with a link back to the run.
Two things are different from a server-run analysis, and the console says so. The branch and commit
are the pipeline's own account, since the server did not clone anything. And the gate that decides
is the server's: the CLI's --fail-on threshold still sets this process's exit code for the
pipeline, and the server's managed gate verdict is printed alongside it. If they disagree, both are
right about what they measure, and the server's is the one the console records.
A push that fails is an error whatever the local gate says. A pipeline that asked for its result to be recorded must not go green because the record silently did not happen.
--project records a full source analysis, so it cannot be combined with --image or with a --mode
other than full: measures, ratings and a code-quality report have no meaning for an image. --engagement
has no such limit, because an ingest is findings and an image scan produces those as well as a source scan
does. An image pipeline therefore records with --engagement, optionally adding --push-sbom so the
image's package inventory lands on the console too, and the findings appear under the engagement's
Imported tab with the tool and rule that produced each one.
Recording a result needs the operate permission. Give the pipeline its own user with that role rather than the bootstrap operator token.
CI pull / merge-request identity¶
When a scan is pushed with --server, Synapse also captures provider-neutral pull/merge-request identity for later PR decoration. Explicit CLI CI fields still win; the variables below fill only missing values. The forge head SHA is the source commit for the change, not GitHub's synthetic merge commit.
| Provider | Variables used |
|---|---|
| GitHub Actions | GITHUB_HEAD_REF, GITHUB_BASE_REF, GITHUB_REPOSITORY, GITHUB_REF, and the pull_request payload in GITHUB_EVENT_PATH (including head.sha). |
| GitLab CI | CI_MERGE_REQUEST_IID, CI_MERGE_REQUEST_TARGET_BRANCH_NAME, CI_PROJECT_PATH, CI_MERGE_REQUEST_SOURCE_BRANCH_SHA (falling back to CI_COMMIT_SHA). |
| Bitbucket Pipelines | BITBUCKET_PR_ID, BITBUCKET_PR_DESTINATION_BRANCH, BITBUCKET_REPO_FULL_NAME, BITBUCKET_COMMIT. |
| Jenkins multibranch | CHANGE_ID, CHANGE_TARGET, GIT_COMMIT; set SYNAPSE_REPO_SLUG when Jenkins cannot infer the repository slug. |
Provider-independent overrides are SYNAPSE_PR_NUMBER, SYNAPSE_PR_TARGET_BRANCH, SYNAPSE_REPO_SLUG, and SYNAPSE_PR_HEAD_SHA. Decoration is skipped unless all four identity fields are present.
False-positive gate¶
A scan of a real repository surfaces findings in test files and deliberately-insecure fixtures. Synapse
handles this in two layers, and neither ever deletes a finding, both are retain-and-mark (the finding
stays in the report, it is only held back from the --fail-on gate).
-
Deterministic test scope. Findings in test/fixture/example/benchmark/docs paths, including the
foo_test.go,test_foo.py,foo.test.ts,foo_spec.rbfile conventions where the test sits beside its source, are classified as background scope and are exempt from the gate by default. Pass--include-testto gate on them too. This alone removes the bulk of the noise. -
AI critique (opt-in). Set
SYNAPSE_FP_TRIAGE_ENABLED=truewith an LLM endpoint configured (SYNAPSE_LLM_BASE_URL,SYNAPSE_LLM_API_KEY, andSYNAPSE_FP_TRIAGE_MODELorSYNAPSE_LLM_MODEL). After the deterministic pass, the model adjudicates the remaining production-scope first-party source findings (SAST/misconfig; secret findings are never sent to the LLM) and returns a typed verdict,refuted(suspected false positive),sound, oruncertain, with a confidence. The proposer only advises: single-model output can never change the gate. SetSYNAPSE_VERIFIER_MODELto a different model family to enable consensus. The verifier may use its ownSYNAPSE_VERIFIER_BASE_URL,SYNAPSE_VERIFIER_API_KEY, and explicitSYNAPSE_VERIFIER_PROVIDER; the proposer provider isSYNAPSE_FP_TRIAGE_PROVIDER(defaulting toSYNAPSE_LLM_PROVIDER). The verifier runs first with only the finding and source context; it never sees the proposer verdict. Provider prefixes, dated aliases, and Amazon Bedrock geographic/global inference-profile IDs are canonicalized fail-closed so one model family cannot verify itself under two names. SetSYNAPSE_FP_TRIAGE_INDEPENDENCE=providerto require both a different provider and a different model family; missing/unknown identity metadata leaves triage advisory-only. Provider/model-family/policy metadata is retained in the scan evidence. The rollout mode defaults toSYNAPSE_FP_TRIAGE_MODE=shadow: Synapse storeswould_gate_exemptfor measurement, always forcesgate_exempt=false, and keeps the finding gating. Set the mode explicitly toenforceonly after the evaluation threshold is approved. In enforced mode, a finding is gate-exempt only when both models independently refute it at/above the bar and the deterministic human-review floor permits it. High/critical findings, secrets, and dangerous injection/auth/access- control/SSRF/traversal/upload/deserialization CWEs always stay gating.
Every finding remains in JSON/SARIF/compliance. The ai_triage JSON separates suspected_fp,
verified, gate_exempt, and review_required, and carries model/prompt/policy metadata. These
fields are sealed into the scan evidence hash-chain when a ledger is configured. The standalone CLI
currently has no evidence vault, so AI triage there is advisory-only and never exempts the gate. Model,
verifier, or evidence availability failure leaves the gate unchanged.
Per scan, Synapse attempts at most SYNAPSE_FP_TRIAGE_MAX_FINDINGS=100 eligible findings with
SYNAPSE_FP_TRIAGE_CONCURRENCY=6 simultaneous assessments by default. A distinct verifier can make
at most two provider calls per attempted finding. If the cap is reached, selection is deterministic,
every skipped finding remains reported and gating, and ai_triage_budget plus the CLI warning expose
eligible, attempted, and skipped counts. Accepted ranges are 1..1000 findings and 1..32 concurrent
assessments; zero, negative, malformed, and over-limit values restore the safe finite defaults.
A second reservation guard defaults to SYNAPSE_FP_TRIAGE_MAX_TOKENS=1000000. Optional micro-USD
pricing and SYNAPSE_FP_TRIAGE_MAX_COST_MICRO_USD add a strict cost ceiling. Reservations happen
before either model is contacted, so a finding that does not fit receives no partial verifier call
and remains gating. Repeated provider or invalid-output failures open a bounded circuit and keep the
remaining findings advisory-only until its cooldown probe succeeds. API deployments expose the
resulting request, latency, timeout, parse, token/cost, disagreement, exemption and alert views at
/api/v1/ai-triage/observability. The same response carries normalized language/CWE/project
distributions for offline drift checks:
go run ./cmd/synapse-fptriage-drift \
--baseline ai-triage-drift-baseline.json \
--observed ai-triage-observability.json \
--output ai-triage-drift-report.json
The baseline owns its human approval, minimum sample size, and maximum total-variation distance. The command writes deterministic evidence before returning a non-zero drift alert; it never changes runtime gate behavior. See AI triage evaluation.
export SYNAPSE_LLM_BASE_URL=http://localhost:8081/v1
export SYNAPSE_LLM_API_KEY=…
SYNAPSE_FP_TRIAGE_ENABLED=true SYNAPSE_FP_TRIAGE_MODE=shadow SYNAPSE_FP_TRIAGE_MODEL=<model> \
synapse-cli scan . --fail-on high --json
To evaluate a model/prompt/policy combination against the repository's versioned non-production golden
dataset, run synapse-fptriage-eval. It emits deterministic JSON with precision, recall, false-negative
escape rate, disagreement, coverage, language/kind/CWE/severity/framework/adversarial breakdowns, and pairwise
adversarial invariance evidence. The bundled v2 dataset pairs a clean control with a semantically
equivalent prompt-injection challenge; the v3 report records proposer, verifier, consensus, and policy
flips without copying source into the robustness summary:
SYNAPSE_FP_TRIAGE_MODEL=<proposer> SYNAPSE_VERIFIER_MODEL=<verifier> \
go run ./cmd/synapse-fptriage-eval --output ai-triage-eval.json
The evaluator always invokes the server policy in shadow mode. A report containing gate_exempt=true is
rejected, so an evaluation run can never authorize a production quality gate.
Before reviewing a new model or prompt for promotion, compare its shadow report with the approved baseline on the same dataset and policy:
go run ./cmd/synapse-fptriage-compare \
--baseline ai-triage-baseline.json \
--candidate ai-triage-candidate.json \
--output ai-triage-comparison.json
The command exits non-zero on a quality regression or adversarial flip but writes the deterministic
comparison evidence first. The default policy requires complete counterfactual coverage, complete
verifier coverage whenever a pair reaches the refuted branch, and zero proposer/verifier/consensus/policy
flips. A passing result is still review_required; it never changes runtime AI configuration.
After that result, use synapse-fptriage-release to bind the baseline, candidate, comparison, unique
release version, and independent PM/Security approvals into a hash-chained ledger. Rollback appends
another approved decision targeting initial or a previous decision. The command writes a new ledger
file for every event and never changes live AI-triage or gate configuration. See
AI triage evaluation.
# First print the exact digest PM and Security must approve.
go run ./cmd/synapse-fptriage-release \
--manifest ai-triage-release.json \
--comparison ai-triage-comparison.json \
--baseline ai-triage-baseline.json \
--candidate ai-triage-candidate.json \
--print-review-digest
# After both approvals are added to the manifest, create a new ledger artifact.
go run ./cmd/synapse-fptriage-release \
--manifest ai-triage-release-approved.json \
--comparison ai-triage-comparison.json \
--baseline ai-triage-baseline.json \
--candidate ai-triage-candidate.json \
--output ai-triage-release-ledger.json
The AI critique reads the target's own source into the prompt, so an untrusted PR can still try prompt injection through comments or strings. Distinct consensus and the human-review floor bound the risk, and the finding always remains in SARIF/JSON, but treat AI triage as advisory for untrusted contributor code.
Exit codes¶
The exit code is a CI contract. A pipeline that branches on it can distinguish a real finding from a misconfigured invocation:
| Code | Meaning |
|---|---|
0 |
The command succeeded and no gate threshold was crossed. |
1 |
A runtime failure, or a gate result: a finding at or above --fail-on, a failed quality gate, coverage below its threshold, or a rating below --fail-below. |
2 |
Invalid usage: an unknown or incomplete flag, a missing required argument, or an invalid enum value such as --fail-on none. Nothing was analyzed. |
Treat 2 as "fix the pipeline definition" and 1 as "inspect the findings". Retrying an exit 2
unchanged will always fail again.
Container image (Docker)¶
The current release workflow does not publish a container image. Build one locally when a containerized CLI is needed:
docker build -t synapse:full --target full -f deploy/Dockerfile .
docker run --rm -v "$PWD:/scan:ro" synapse:full synapse-cli scan /scan --fail-on high
The full target bundles pinned Syft and Grype and covers the pure-Go scan path: SBOM, OSV/Grype
vulnerabilities, licenses, SAST, secrets, and IaC misconfiguration. Sandboxed execution and
JVM-from-source resolution need a Linux host with bubblewrap and a JDK/Maven/Gradle, so run those on a
host install or the full Compose stack.
Advisory sync (optional owned store)¶
For detection independence you can maintain an owned advisory store and ingest feeds into it.
This requires a database via SYNAPSE_DB_DSN.
# ingest a local OSV dump directory
synapse-cli sync-advisories <dir>
# fetch and ingest application ecosystems from the OSV bulk source
synapse-cli sync-advisories --remote
# fetch and ingest OS-package advisories (large)
synapse-cli sync-advisories --remote-distros
# ingest a local CSAF 2.0 advisory dump
synapse-cli sync-advisories --csaf <dir>
# ingest a local yum/dnf updateinfo dump (Amazon Linux ALAS and Fedora: repodata updateinfo.xml[.gz/.bz2/.zst])
synapse-cli sync-advisories --updateinfo <dir>
# ingest a local RESF/Apollo OSV list dump (Rocky Linux RLSA: apollo.build.resf.org OSV JSON pages)
synapse-cli sync-advisories --rocky <dir>
# ingest a local apk secdb dump (Alpine secdb.alpinelinux.org, Wolfi packages.wolfi.dev, Chainguard)
synapse-cli sync-advisories --secdb <dir>
Unsigned local OVAL is not accepted by sync-advisories because it cannot safely enter durable
advisory storage. Configure an authenticated API-managed OVAL source instead, with either a pinned
OpenPGP key or trusted provider metadata. The only exception is the exact SLES 15 SP6 SUSE HTTPS-origin
source documented in Vulnerability intelligence: it is
not a local import, generic unsigned OVAL support, or OpenPGP verification. Local OVAL data remains
suitable for ownadvisory parser and scabench benchmark fixtures, which do not import it into durable
advisory storage.
Enable the store at scan time with SYNAPSE_OWNED_ADVISORY=true, then it runs alongside the
live and offline sources.
GitHub Action¶
The reusable action installs the released synapse-cli and runs the gate, so a whole scan step is three
lines. v1 is the action's INTERFACE version and moves with every release; pin a release tag such as
@v0.2.4 instead when you want the action itself frozen.
- uses: KKloudTarus/synapse-ce@v1
with:
fail-on: high # critical | high | medium | low | info (default: high)
path: . # what to scan (default: .)
version: latest # a released tag like v0.1.0, or latest (default)
Emit SARIF and upload it to the Security tab, while still failing the build on high findings:
- id: synapse
uses: KKloudTarus/synapse-ce@v1
with:
fail-on: high
sarif: true
continue-on-error: true # let the upload run even when the gate fails
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.synapse.outputs.sarif-file }}
Set offline: true to run against the bundled offline databases only (no network egress).
From source¶
Without the action you can install the tools and build the CLI yourself:
Or emit SARIF and upload it to the GitHub Security tab, while still failing the build on high findings:
- name: Synapse scan
run: ./bin/synapse-cli scan . --sarif --fail-on high > synapse.sarif
continue-on-error: true # let the upload run even when the gate fails the step
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: synapse.sarif
The report lands in the repository's Code scanning alerts, with each SAST, secret and misconfig finding annotated on its exact source line.
GitLab CI¶
The same gate as a GitLab job. make tools installs syft and grype, make build produces
./bin/synapse-cli, and a non-zero exit from the scan fails the pipeline:
synapse-scan:
stage: test
image: golang:1.26
script:
- make tools
- make build
- ./bin/synapse-cli scan . --fail-on high
Keep the SARIF report as a downloadable build artifact (works on every GitLab tier):
synapse-scan:
stage: test
image: golang:1.26
script:
- make tools
- make build
- ./bin/synapse-cli scan . --sarif --fail-on high > synapse.sarif
artifacts:
when: always
paths:
- synapse.sarif
Note on native GitLab ingestion.
artifacts:reports:sastdoes not accept SARIF; it takes GitLab's own report schema (agl-sast-report.json), so pointingreports: sast:at a SARIF file does nothing. GitLab ingests SARIF only throughartifacts:reports:sarif, and that (with the merge-request security widget and the Vulnerability Report) is a GitLab Ultimate feature. On Free/Premium, download the artifact above or upload the SARIF to your own tooling.
Jenkins¶
A declarative pipeline stage. The scan's exit code fails the stage on a finding at or above the threshold:
pipeline {
agent { docker { image 'golang:1.26' } }
stages {
stage('Synapse scan') {
steps {
sh 'make tools'
sh 'make build'
sh './bin/synapse-cli scan . --fail-on high'
}
}
}
}
To keep the SARIF report as a build artifact (for a platform or plugin that ingests SARIF), let the scan step record its exit code, archive the report, then fail the build explicitly:
stage('Synapse scan') {
steps {
sh 'make tools && make build'
script {
def rc = sh(returnStatus: true, script: './bin/synapse-cli scan . --sarif --fail-on high > synapse.sarif')
archiveArtifacts artifacts: 'synapse.sarif', allowEmptyArchive: true
if (rc != 0) { error("Synapse found a finding at or above the fail-on threshold") }
}
}
}
Code quality gate (Clean as You Code)¶
Beyond security, synapse-cli measures code health and gates on it. The quality gate can score the
whole codebase or, with --new-code-only, just the lines a branch changed, so a legacy repo can adopt
the gate without fixing all pre-existing debt first.
# fail the build if new code introduces a critical/high issue, a new secret, or drops below A ratings
synapse-cli gate . --new-code-only --base origin/main
# feed a coverage report (lcov / Cobertura / JaCoCo / Go -coverprofile, auto-detected); a .synapse-gate.yaml can then
# require e.g. `coverage >= 80` on new code
synapse-cli gate . --new-code-only --base origin/main --coverage coverage.info
With --new-code-only the gate also measures new_coverage (line coverage over the added lines the
report knows about) and new_duplication (the share of added lines inside a duplicated block). Each is
written only when it could be measured: a condition on coverage, new_coverage, or new_duplication
with no measurement fails as no data rather than being judged against a 0 nobody computed, so a
new_duplication condition without --new-code-only, or with a diff no report line matches, fails
rather than silently passing.
The gate also builds a first-party dependency graph for Go and JavaScript/TypeScript source. Managed or
local gates can cap max_efferent_coupling and max_instability; if graph collection is incomplete,
those conditions are reported as no data and fail closed. synapse-cli quality prints the same maxima
for architectural feedback without executing project code or package managers.
synapse-cli scan --server also records a commit-pinned Behavioral Hotspots snapshot when the source is
a clean Git worktree and enough first-parent history is already present. It scores each file as
cyclomatic complexity multiplied by the number of commits that touched its current path. Collection is
read-only and performs no network fetch; unavailable or shallow evidence stays explicit and does not
create findings or affect the gate.
When the server records successive analyses, its Project Measures response also includes complexity
rollups and signed per-path deltas. These are server-side history facts, not a CLI quality-gate input:
the current cyclomatic/cognitive totals are summed from parsed functions, while Δ preserves both
increases and reductions against a compatible baseline. The response includes measured/eligible file
coverage and the baseline analysis provenance; unsupported languages, parser failures, legacy snapshots,
and unknown branches are reported as unavailable rather than as zeroes.
| Flag | Default | Description |
|---|---|---|
--new-code-only |
off | Score only lines changed against --base instead of the whole tree. |
--base <ref> |
origin/main |
Git reference the new-code diff is computed against. |
--gate <file> |
<path>/.synapse-gate.yaml |
Gate definition to apply. |
--rules <file> |
<path>/.synapse-rules.yaml |
Rule enable/disable and severity overrides. |
--coverage <file> |
none | Coverage report (lcov, Cobertura, JaCoCo, or a Go -coverprofile, auto-detected) so gate conditions can require a coverage floor. A Go profile names files by import path; the CLI reads the module directive from <dir>/go.mod and strips it so lines key on repo-relative paths. Without a go.mod at the scan root the import paths are kept as-is and will not match the tree. |
--format text\|markdown |
text |
Output format. markdown prints a ready-to-post PR summary. |
--decorate |
off | Post the gate result back to the pull/merge request on the CI's forge (commit status, check/report, and a PR/MR comment). The forge is the CI provider (SYNAPSE_CI_PROVIDER, auto-detected on GitHub/GitLab/Bitbucket); the token is SYNAPSE_DECORATION_TOKEN. A decoration error never fails the gate. |
--dry-run |
off | With --decorate, print the resolved decoration target and gate verdict without any forge write, and without needing a token. Use it to confirm decoration will land before provisioning a credential. |
A .synapse-gate.yaml overrides the built-in gate, and a .synapse-rules.yaml enables/disables rules
or overrides severities. Use --gate and --rules to point at files outside the scanned tree:
# .synapse-gate.yaml
conditions:
- metric: new_critical
op: "<="
threshold: 0
- metric: coverage
op: ">="
threshold: 80
- metric: max_efferent_coupling
op: "<="
threshold: 12
- metric: max_instability
op: "<="
threshold: 0.8
Inspect coverage on its own:
Code-health commands¶
These commands run the same analyzers the gate composes, but each reports one dimension on its own. None
of them needs a database or a server; all are safe in CI. Complexity and structural analysis use the
synapse-ast sidecar, and degrade to Go-only counts when it is unavailable rather than failing.
synapse-cli inventory <path>
synapse-cli metrics <path> [--fail-on-complexity N] [--top N]
synapse-cli duplication <path> [--min-tokens N] [--fail-on-duplication PCT] [--top N]
synapse-cli quality <path> [--fail-on SEV] [--min-complexity N] [--include-test-smells] [--sarif]
synapse-cli rating <path> [--json] [--fail-below GRADE]
| Command | Reports | Gate flag |
|---|---|---|
inventory |
Languages, files, and lines of code | none |
metrics |
Per-function cyclomatic and cognitive complexity | --fail-on-complexity N exits 1 when any function exceeds N |
duplication |
Duplicated blocks, lines, and density | --fail-on-duplication PCT exits 1 when density exceeds PCT |
quality |
Maintainability and reliability findings, duplication and complexity bridges, and module coupling maxima | --fail-on SEV accepts critical\|high\|medium\|low\|info |
rating |
A–E security, reliability, and maintainability grades with technical debt | --fail-below GRADE exits 1 when any grade falls below it |
--top N limits how many entries are printed. quality --sarif writes a SARIF report, and
quality --include-test-smells adds test-code smells that are otherwise suppressed.
# gate a build on complexity and duplication without a server
synapse-cli metrics . --fail-on-complexity 15
synapse-cli duplication . --fail-on-duplication 3
# publish code-quality findings to GitHub code scanning
synapse-cli quality . --sarif > quality.sarif
Validate a third-party SARIF report¶
validate-sarif runs a third-party report through the same use case as the server ingest endpoint and
reports what would be accepted or refused. It writes nothing: the JSON output carries
"persisted": false, and no audit entry is recorded. To actually ingest a report, post it to
/api/v1/engagements/{id}/sarif, where the ingesting actor comes from the authenticated principal.
import-sarif remains an alias for the same command and the same non-persisting contract. Pass - to
read the report from stdin. --actor is a local label only. Exit 1 covers both a report whose results
were all refused and, with --fail-on-refusal, any partial refusal, so a pipeline can insist every
result be attributable.
Publish analysis source¶
Uploads the source files a completed server-side analysis listed as retainable, so the dashboard can show
annotated code. It requires SYNAPSE_API_TOKEN, and --server defaults to SYNAPSE_API_URL. Only files
in the analysis inventory are sent; the server returns a digest-verified source manifest.
Build an offline CVSS database¶
Converts NVD JSON feeds into a compact local database. Point SYNAPSE_NVD_CVSS_DB at the output to
backfill CVSS scores with no network access and no API rate limit, which suits air-gapped CI.
PR decoration¶
--decorate posts the gate result natively to the pull/merge request on the CI's forge: a commit
status, a check run (GitHub) or Code Insights report (Bitbucket) or Code Quality report (GitLab), and
one summary comment. It is idempotent (a rerun updates the same status and comment in place) and
fail-soft (a forge error never fails the gate). The forge is chosen from the CI provider, which is
auto-detected on GitHub Actions, GitLab CI, and Bitbucket Pipelines (override with SYNAPSE_CI_PROVIDER),
and the PR identity comes from the CI environment. Confirm the wiring first with --dry-run, which
prints the resolved target without any network write:
Token scopes. SYNAPSE_DECORATION_TOKEN must be able to write back to the change. Grant only what
decoration needs and prefer a short-lived CI-provided token:
- GitHub: a token with
statuses: write,checks: write, andpull-requests: write. - GitLab: a project access token with the
apiscope (commit status + MR note), presented asPRIVATE-TOKEN. - Bitbucket: an access token or app password with
repository:writeandpullrequest:write, over Basic auth (SYNAPSE_DECORATION_USERNAMEpairs with an app password; access tokens default tox-token-auth).
- name: Synapse quality gate
run: |
make tools && make build
./bin/synapse-cli gate . --new-code-only --base "origin/${{ github.base_ref }}" \
--coverage coverage.info --decorate
env:
SYNAPSE_DECORATION_TOKEN: ${{ secrets.GITHUB_TOKEN }}
The server decorates automatically too: a project that opts in
(PUT /api/v1/projects/{key}/decoration {"enabled": true}) has every PR-ref analysis decorated using
the tenant's configured SCM connector, so a CI push through the import route needs no --decorate flag.
Decoration is off for every project by default, so no project performs an outward forge write until it
opts in.
Next: Architecture