An audit orchestrator. It runs existing analyzers, records what each one actually covered, refuses to report "clean" over checks that never executed, and stops for a human before anything is called a vulnerability.
It does not detect vulnerabilities. It is the thing that keeps detection honest.
| Product | Purpose | When to use |
|---|---|---|
| Sentinel CLI | Local-first SAST, supply chain audit, build intelligence, skills/MCP for AI agents | Run locally, in CI, or as part of agent workflows. No account required for core scans. |
| Sentinel Audit | Focused audit workflows (deps/PRs/CI) and reporting | Structured audits with tighter reporting/automation. |
| Sentinel Cloud | Web dashboard, team workflows, full-engine scans with pulses | When you need shared intelligence, collaboration, or the full engine. |
| Sentinel Oracle | Merge authorization/enforcement server (gate PR merges) | When you need to enforce "authorize before merge" with isolated enforcement. |
Tip: If Sentinel Cloud is temporarily unreachable or you hit auth/connection issues, use the CLI directly (
sntnl scan,sntnl audit-deps) — it works standalone without Cloud.
Running seven security tools and concatenating their output produces a number, and the number is meaningless without knowing what was checked. A report that says "0 findings" is indistinguishable from a report where the analyzer crashed, was skipped, or silently parsed nothing. Both look clean.
This runner exists so those two things can never look the same. Every finding count travels with its coverage, and the verdict names what was not checked.
preflight -> run tools -> validate health -> normalize
-> correlate -> adjudicate (human) -> report
preflight answers, before spending a byte: is there a disclosure channel,
is the language analyzable by something installed, how big is the real
analyzable surface, and what will it cost.
Running sentinel-audit opens a colored terminal menu and reports the active
GitHub CLI identity when available. GitHub identity is checked through the
authenticated gh api user endpoint. The PR workflow uses the current Git
remote automatically and begins listing open pull requests without requiring
an extra confirmation keystroke. When no remote is available, choose a
repository from the authenticated account's repository list.
The menu, repository and PR selectors, engine choice, findings, and saved
reports render in bordered panels. Findings use severity-colored panels and
show a concise snippet; the full evidence remains in the execution report.
The stage map uses a slower live spinner and a bordered status view so progress
is readable during longer specialist runs. Set NO_COLOR=1 for plain output.
Menu navigation is numbered throughout: 0 or Enter backs out of selection
screens, and Enter at the main menu exits. Options 4 and 5 let you choose a
repository and recent commit from GitHub, then run an audit or preflight on an
isolated temporary checkout. Option 6 lists saved local executions so reports
can be rendered without typing filesystem paths. After key management or an
audit, the CLI returns to the main menu. The responsibility notice is available
from option 7. Option 8 shows a bounded, token-redacted log of the current CLI
session; it is discarded when the process exits. Option 9 exits. The startup
banner also shows the local dashboard URL and its start command; the dashboard
starts only when requested with sentinel-audit web.
The guided PR workflow labels the deterministic diff scan as Sentinel Audit Lite. It then pins the PR head SHA, creates a temporary checkout, passes the Lite signals into the Sentinel Audit routing pipeline, runs justified local specialists that are installed, correlates their findings, and prints a stage map. The execution directory contains the full evidence and report. Missing specialists, unrouted signals, failed stages, and unknown coverage are shown as gaps with reasons; they are never represented as a clean scan.
Saved Sentinel Cloud keys do not mean a pulse was used. A saved key enables the
Activar Pulse action: it opens the local Pulse settings, prefilled with the
public production endpoint (https://sentinel-psi-nine.vercel.app) unless
SENTINEL_CLOUD_URL or a saved endpoint overrides it, and starts the selected
PR scan after activation. A hosted pulse is offered only when the cloud client,
an endpoint, hosted policy, and a key are available. Selecting that option
explicitly sends the pinned PR checkout to Sentinel Cloud; the local Lite diff
result remains labeled separately. Otherwise, the menu lists the missing
prerequisites and uses the local path. The responsibility notice is available
from menu option 7 rather than printed on every startup.
The repository includes sentinel-audit.cmd, a dependency-free launcher for
PowerShell and Command Prompt. Add the checkout to the user PATH once, then
open a new terminal:
$auditRoot = 'C:\Users\sleyt\sentinel-audit'
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -notlike "*$auditRoot*") {
[Environment]::SetEnvironmentVariable('Path', "$userPath;$auditRoot", 'User')
}Verify with sentinel-audit --help. The launcher requires Node.js 20 or
newer and does not elevate privileges or install anything globally.
The terminal workflow remains available through sentinel-audit or
sentinel-audit interactive. Use sentinel-audit doctor for a machine-readable
readiness report. The menu helps select a workflow but does not replace reading
the report or human adjudication.
Run sentinel-audit web to start the browser console at
http://127.0.0.1:4317. It binds to loopback only and does not open a browser
automatically. The dashboard requires Node.js 22.5 or newer for the built-in
SQLite module; the terminal commands continue to support Node.js 20 or newer.
Set SENTINEL_AUDIT_WEB_PORT to change the local port.
The dashboard enumerates repositories and open PRs through the active GitHub CLI session. Repository cards show the open PR count when it can be read, and the PR inventory summarizes severity composition in a donut chart with counts and percentages. Each PR offers a local Lite scan and, when the cloud client, endpoint, hosted policy, and a saved key are ready, an explicit Sentinel Cloud Pulse; with a key but no activation, Activar Pulse opens the prefilled settings and chains the scan after activation. Both paths pin the PR head to its full SHA and run Sentinel Audit's own pipeline; Pulse sends the pinned checkout to the configured Sentinel Cloud endpoint (the public production endpoint by default). Specialists and their coverage states appear independently. The local evidence map distinguishes completed, partial, failed, unavailable, and unrun stages. An optional retry is limited to specialists already justified by the saved signal route; tools that the route did not select are not silently forced.
Each completed scan is stored in the Audit-only SQLite database at
~/.sentinel-audit/audit.sqlite (on Windows, under the user's home directory).
The interface provides scan, finding, and stage tables, a long-cell modal,
severity/source/file/date summaries, and JSON export. The report records a
SHA-256 digest for the scan body and a separate digest for the exact PR diff;
the body digest is computed over canonical serialized JSON before its
integrity field is added. These are content references, not signatures or
attestations from GitHub.
The API-key panel uses the existing Sentinel Audit local key store, masks tokens in every listing, and never returns a token to browser code. Removing a key here deletes its local copy only; it does not revoke it server-side in Sentinel Cloud. Store and revoke credentials through the Sentinel Cloud account when permanent invalidation is required. The local dashboard has no remote login or multi-user protection and should remain bound to the current device.
Tool inventory states distinguish an executable found on the host, an executable
blocked by a host runtime guard, and a binary not found on PATH or configured
paths. “Not found” is not a claim that the package cannot exist in an unsearched
location. Analytics use a donut for severity composition, ranked horizontal bars
for engine/source counts, a ranked list for affected files, tiles for categories,
and an SVG timeline for scan dates.
Every finding and observation keeps its evidence reachable: cards link to the pinned file and line on GitHub when the path is safe, and observations that were not promoted to candidates still expose their signals and normalized JSON. When a scan produces no findings, the dashboard shows an explicit zero state that names the engines that ran and any coverage still missing instead of implying the change is clean.
The evidence view separates indicadores de comportamiento malicioso from defectos de seguridad with a heuristic over the finding text and category, and labels anything else as unclassified. This is a triage aid, not an intent judgment: it never claims that a developer acted deliberately, and the limitation is stated next to the counts.
The local web console provides complete transparency over evidence, specialist execution, and repository inventory:
Coding agents can follow the optional sentinel-audit skill
for health checks, pinned scope, coverage reporting and human-led adjudication.
node audit-runner.js interactive
node audit-runner.js doctor
node audit-runner.js preflight <repo> [<repo> ...] --commit <sha>
node audit-runner.js run <repo> --commit <sha> --routing file|directory|repo
node audit-runner.js route <executionDir> --routing file|directory|repo
node audit-runner.js specialists <executionDir> [--only codeql,semgrep]
node audit-runner.js report <executionDir>
node audit-runner.js cleanup <executionDir>
node audit-runner.js adjudicate <executionDir> --candidate SAR-0001 \
--disposition <DISPOSITION> --priority <P0|P1|P2|P3> --rationale "..."Los comandos run y scan muestran un mapa en vivo en stderr: preflight,
checkout fijado al commit, motor seleccionado, enrutamiento, trabajos especialistas,
correlación, triage y limpieza. Los estados y herramientas provienen del pipeline;
no se estiman porcentajes. Los analizadores paralelos muestran su estado de cola,
ejecución y finalización individual. Al terminar queda una vista estática del
resultado junto al resumen de salida.
El mapa usa color y redibujado solo en una terminal interactiva. NO_COLOR,
TERM=dumb, CI, --json y --quiet evitan secuencias ANSI o animación; JSON
permanece limpio en stdout y el progreso interactivo va por stderr. En modo
--provider cloud se distingue el motor alojado; la API no ofrece progreso de
sus componentes internos, así que el mapa solo informa el inicio y el resultado
agregado del motor.
Consulte la guía del mapa de ejecución para leer los estados y el alcance de cada etapa.
audit and scan remain aliases for run. Each new execution receives a
unique directory under out/<repo>/<execution-id>/; raw evidence is written
before normalization and is not reused across runs. identity.json pins the
target commit/tree and records worktree state, engine revision/hash, audit
source hashes, specialist versions, and routing/config hashes. A dirty target
checkout or a requested SHA that differs from HEAD fails preflight.
Routing policy is in config/policies.json. file is the current default;
directory groups promoted files by parent directory, and repo selects the
whole checkout. Repository-wide tools such as CodeQL/Trivy/OSV are explicitly
recorded as repo-level jobs and are not represented as scoped by Cloud.
--only and --skip can narrow a justified specialist set; skipped jobs remain
coverage gaps. Unpromoted files mean “not selected for this stage,” never
“safe.”
Coverage counters are only numeric when an analyzer provides a real denominator.
Otherwise the artifact carries coverageKnown=false, coverageUnknown=true,
and ENGINE_COVERAGE_UNMEASURED; an execution-complete flag is not a coverage
claim. Candidate records retain signal, promotion, job, specialist-finding and
correlation IDs. CodeQL's memory setting is recorded as a requested budget, not
as measured peak RAM.
Exit codes: 0 complete with no blocking candidate; 1 candidate(s) under the
configured policy; 2 incomplete/degraded execution; 3 preflight or
configuration failure; 4 unrecoverable infrastructure/tool failure.
The primary sensor is the local Sentinel Cloud worker engine configured in
config/tools.json. The runner calls its scanner module directly in local mode;
it does not call the hosted service or Sentinel Purple. Scanner parse coverage
is currently unmeasured, so a zero-signal result is never a clean claim.
Two fields on a candidate are workflow bookkeeping. Neither is read by any
verdict, neither changes reportable, and neither can close or promote a
finding. They are stated here because a field that changes nothing is easy to
mistake for one that decides something.
| Field | Values | Meaning |
|---|---|---|
investigationPriority |
P0–P3, unset |
how soon a human should look |
reviewState |
UNREVIEWED, REVIEWED |
whether anyone has adjudicated it |
PLAUSIBLE + UNREVIEWED means the tool produced it and nobody has looked.
PLAUSIBLE + REVIEWED means an analyst looked and kept the hypothesis. Without
that distinction an untouched backlog is indistinguishable from a triaged one.
| Tool | Role | Authority |
|---|---|---|
| Sentinel Cloud worker engine | primary behavioral signals | signal only; coverage currently unmeasured |
| CodeQL | dataflow / reachability | corroborating |
| Semgrep | pattern semantics | corroborating |
| Bandit | Python sink semantics | corroborating |
| ShellCheck | shell lint | lint only, never a taint claim |
| Trivy | SCA + secrets | contextual |
| OSV-Scanner | SCA | contextual |
| Zizmor | GitHub Actions hardening | corroborating for CI/CD workflows |
A tool is one of three things, and the distinction is load-bearing:
- corroborating — can promote a signal to a candidate
- signal only — can never promote anything on its own
- contextual — informs a human, promotes nothing
Sentinel Audit orchestrates these projects; it does not vendor, fork, or relicense them. Each remains under its own license and terms, and credit for the analysis belongs to its maintainers.
| Project | Upstream | License / terms |
|---|---|---|
| CodeQL | https://github.com/github/codeql | Queries MIT; CLI subject to GitHub CodeQL Terms |
| Semgrep | https://github.com/semgrep/semgrep | LGPL-2.1 |
| Bandit | https://github.com/PyCQA/bandit | Apache-2.0 |
| ShellCheck | https://github.com/koalaman/shellcheck | GPL-3.0 |
| Trivy | https://github.com/aquasecurity/trivy | Apache-2.0 |
| OSV-Scanner | https://github.com/google/osv-scanner | Apache-2.0 |
| Zizmor | https://github.com/zizmorcore/zizmor | MIT |
| GitHub Actions | https://github.com/features/actions | GitHub Terms of Service |
| GitHub CLI | https://github.com/cli/cli | MIT |
| Node.js | https://github.com/nodejs/node | MIT |
Sentinel Cloud and the rest of the Sentinel platform are maintained in the Sentinel repositories.
These are the reasons the code is shaped the way it is. Each one exists because its absence produced a wrong answer.
-
A finding count is never reported without coverage.
tool error != 0 findings,partial != clean,unsupported != clean,skipped != clean,scope exclusion != silent discard. -
SENTINEL_SIGNAL != CANDIDATE != CONFIRMED_ISSUE. Tool confidence never decides exploitability. Only
adjudicatedoes, and only a human calls it. -
A skipped tool is a coverage gap, not a clean result. A tool that never ran cannot support a clean claim.
NOT_APPLICABLEis different: Bandit on a JavaScript repo is not a gap, OSV without a lockfile is. See v1.1 below, where this was wrong. -
Git is the source of truth for
SECURITY.md,LICENSE, commit and tree. A filesystem heuristic cannot grant or deny a disclosure channel. -
A retired channel is not a channel. A policy that closes a program does not thereby offer it. A CNA escalation address is a fallback, not the maintainer's route. See v1.2 below, where this was wrong.
-
Every candidate preserves its evidence: repo, commit, tree, tool, rule, file, line, snippet, source, sink, path, controls and corroborating signals. A reviewer must never need to re-clone the target to judge it.
-
Publication is unconditionally forbidden. No push, no PR, no issue, no email, no automatic disclosure. Fixes and drafts stay local.
-
Target code is never executed, built, tested, or installed.
-
The runner orchestrates. It does not re-implement detection.
| Verdict | Meaning |
|---|---|
CLEAN_WITH_FULL_COVERAGE |
every applicable tool completed, nothing open |
CLEAN_WITH_LIMITATIONS |
nothing open, but some tool had reduced coverage |
CANDIDATES_FOUND |
something needs human adjudication |
PARTIAL_ANALYSIS |
at least one applicable tool did not complete |
Per tool: SUCCESS, PARTIAL, ERROR, UNSUPPORTED, SKIPPED,
NOT_APPLICABLE.
verdict and analysisState are separate fields on purpose. Collapsing them
is how a run with dead tools gets called clean, or how a run with twelve real
candidates gets filed under "degraded" and buried.
CONFIRMED_SECURITY_ISSUE, STRONG_SECURITY_CANDIDATE,
PLAUSIBLE_SECURITY_ISSUE, BENIGN, TOOLING_INTENT, FALSE_POSITIVE,
DUPLICATE, ALREADY_MITIGATED, OUT_OF_SCOPE, UNRESOLVED,
STATIC_ONLY_LIMITATION.
tools/determinism.js asserts that two runs over the same pinned commit
produce identical evidence once timestamps, paths and wall-clock are removed.
node audit-runner.js audit <repo> --name runA
node audit-runner.js audit <repo> --name runB
node tools/determinism.js out/runA out/runBA nonzero exit means real nondeterminism, which must be explained rather than tolerated.
| Tag | Commit | What it froze, or what was wrong with it |
|---|---|---|
audit-runner-v1-freeze |
3819cc0 |
Initial freeze. Known wrong, kept for the record. |
audit-runner-v1.1-freeze |
ab60f15 |
A skipped tool no longer counts as clean. |
audit-runner-v1.2-freeze |
16b1dab |
Retired channels and CNA fallbacks are not vetted channels. |
audit-runner-v1.3-freeze |
6d09016 |
A finding closed by an analyst no longer counted as open, so a triaged target could be reported clean. |
audit-runner-v1.4-freeze |
4e0c267 |
Investigation priority, as a field no verdict reads. |
audit-runner-v1.5-freeze |
this commit | Historical freeze; its Purple opt-out behavior was removed when wiring the direct Cloud worker engine. |
v1 and v1.1 are both wrong in ways that produced a false clean claim. They are not deleted. A freeze that gets quietly rewritten is worse than one that keeps its mistakes visible, because the mistakes are the argument for the invariants above.
Both defects were found by running the campaign, not by testing. The first
audit target (fastify/fastify) hit both. The v1.3 defect was found the same
way: after adjudicating all three of axios/axios's candidates, the runner
still reported them as open, and a target with nothing left to do looked like a
target with work outstanding.
| Target | Commit | Verdict | Candidates | Coverage gap |
|---|---|---|---|---|
fastify/fastify |
c3051e5b60c0 |
PARTIAL_ANALYSIS |
0 | OSV skipped, no lockfile |
expressjs/express |
9a34acf03cb8 |
PARTIAL_ANALYSIS |
0 | OSV skipped, no lockfile |
nestjs/nest |
b58554ea5857 |
CLEAN_WITH_FULL_COVERAGE |
1 adjudicated TOOLING_INTENT |
none |
Zero confirmed vulnerabilities. PARTIAL_ANALYSIS on the first two is
correct, not a failure: neither ships a lockfile, so the SCA class of checks
could not run, and the runner declines to call that clean.
- Sentinel emits no line numbers. Its
commensalmode reports findings at file granularity only. Every signal is preserved (zero loss), but Sentinel-only observations are not triageable to a line. This is a Sentinel limitation; the runner does not modify Sentinel. - SCA needs a lockfile. Targets without one are
PARTIAL_ANALYSISby construction, even when everything else is clean. - ShellCheck is lint. A clean ShellCheck run says nothing about injection.
- Static analysis only. No dynamic testing, no exploit execution.
- Correlating a Python finding with JavaScript tooling is not attempted; the pipeline treats cross-language corroboration as unproven.
audit-runner.js CLI
config/tools.json tool paths, query suites, memory
config/policies.json budgets, coverage rules, disclosure policy
lib/core.js envelopes, verdicts, git helpers, coverage
workflow/preflight.js gates, disclosure and license detection, cost
workflow/audit.js the pipeline
adapters/index.js one adapter per tool
correlate/index.js dedup, candidates, adjudication, audit verdict
reports/expediente.js Markdown report
tools/determinism.js the determinism assertion
tools/verdict-matrix.js which dispositions may claim clean
out/ run output, gitignored
The interactive CLI needs Node.js 20 or newer and GitHub CLI (gh) for GitHub account and repository selection. The local web dashboard needs Node.js 22.5 or newer for built-in SQLite. Specialist tools are optional: Sentinel Audit can run its own Lite analysis without them, and each unavailable or unrouted specialist remains explicit in the evidence route.
On Windows, the dashboard can install CodeQL CLI, Semgrep, Trivy, OSV-Scanner, Zizmor, Bandit, and ShellCheck into %USERPROFILE%\\.sentinel-audit\\tools without administrator privileges or modifying system PATH. GitHub release binaries are accepted only from allowlisted official release assets with a published SHA-256 digest; if that digest is missing or does not match, installation stops and the file is removed. Semgrep, Zizmor, and Bandit use isolated Python virtual environments and binary wheels from PyPI, requiring Python 3 and the Windows py launcher. Their package manager resolves transitive dependencies from the configured official index; use your organization's package policy if stricter dependency pinning is required. Installed executable paths are recorded in %USERPROFILE%\\.sentinel-audit\\tool-paths.json and discovered on the next state refresh. The UI shows download/install progress and confirms when the tool is registered. It does not execute a downloaded installer binary during download.
CodeQL requires checking its usage terms in the UI before downloading. Its CLI is restricted by GitHub's terms to eligible open-source codebases or academic research; review the linked current terms before use. The integrated installer is Windows-only. On other systems, install from the upstream projects and configure executable paths in config/tools.json or the documented SPECIALIST_*_PATH environment variables. A tool reported as not found means it was not resolved from PATH/configuration, not proof it is absent elsewhere.









