Security hardening, CLI bug fixes, and SARIF report output - #322
Security hardening, CLI bug fixes, and SARIF report output#322DevamShah wants to merge 1 commit into
Conversation
Adds an opt-in SARIF emission path so Vedha findings can be consumed directly by GitHub Code Scanning, GitLab, Defect Dojo, and any other SARIF-aware scanner UI. Also rewrites the README to credit upstream Shannon clearly and enumerate exactly what Vedha layers on top. Behaviour: ./vedha start -u ... -r ... --report-format sarif writes <repo>/.shannon/deliverables/comprehensive_security_assessment_report.sarif alongside the existing markdown report. The default (`--report-format md`) is unchanged byte-for-byte. Wiring: - apps/cli: new `--report-format md|sarif` flag on `start`. Validated up front. Help text describes the two values. - apps/cli/docker.ts: forwards VEDHA_REPORT_FORMAT and VEDHA_VERSION to the worker container as env. Env is the right channel because the worker reads them inside `assembleReportActivity` to gate optional emission, and env survives Temporal serialisation without needing pipeline-input plumbing. - apps/worker/temporal/activities.ts: `assembleReportActivity` now invokes SarifReportOutputProvider after the markdown assembly when VEDHA_REPORT_FORMAT=sarif. SARIF emission is best-effort — a failure there never blocks the markdown path. - apps/worker/services/sarif-output-provider.ts: new provider. Walks the five `*_exploitation_evidence.md` deliverables that `assembleFinalReport` already consumes; emits one SARIF result per non-empty evidence file with the body as the result message (truncated at 16 KiB). - apps/worker/services/index.ts: re-exports SarifReportOutputProvider. - apps/worker/tsconfig.json: excludes __tests__/** from production compile so test code doesn't end up in dist/. Tool driver advertises five rules tagged with their CWE IDs: vedha.injection (CWE-74), vedha.xss (CWE-79), vedha.auth (CWE-287), vedha.ssrf (CWE-918), vedha.authz (CWE-285). Test infrastructure: - vitest dev dep + `test` script on @shannon/worker - vitest.config.ts in apps/worker - turbo `test` task wired up - 5 new SARIF tests covering envelope shape, one-result-per-evidence, empty/missing handling, oversized truncation, output path - Total: 29/29 pass (24 existing + 5 new) - pnpm check + build clean README: - New "Credit & lineage" section: Vedha is a fork of Shannon by Keygraph. Architecture is theirs; Vedha exists to carry security hardening, propose improvements upstream, and integrate with the Archeon stack. - New "What Vedha adds over upstream Shannon" section enumerating all 8 security fixes (S-1..S-8) and the SARIF feature. - Versioning policy table linking Vedha versions to Shannon base. - Cross-link to KeygraphHQ/shannon#322 (the upstream PR carrying the security hardening for review). Out of scope for v1.1.0 (deferred follow-ups): - --max-cost USD kill switch - --dry-run / read-only mode - Per-finding line/column SARIF locations (needs structured findings from agents)
Two container-runtime issues, both on the path that remaps the in-container pentest user to the host's uid. entrypoint.sh: SHANNON_HOST_UID and SHANNON_HOST_GID reach groupadd/useradd unvalidated. An empty GID (set UID without GID) fails with an opaque "groupadd: invalid group ID ''"; a UID of 0 silently maps the agent user onto root, which defeats running the agents unprivileged in the first place. Both are now checked against ^[0-9]+$ and a 1..2000000 range before use, with an explicit error. Validation moved inside the remap branch so the no-env path is byte-for-byte unchanged. Dockerfile: /app, /tmp/.cache, /tmp/.config and /tmp/.npm are chmod 777. Only the pentest user (or a remapped uid running as pentest) ever writes them, so world-writable adds blast radius with no functional benefit. Dropped to 770. The 777 was, however, load-bearing for the remap path: the entrypoint chowns /app/sessions, /app/workspaces, /tmp/.claude and /tmp/.pi after remapping but never /tmp/.cache, /tmp/.config or /tmp/.npm, which stayed writable only because they were world-writable. Tightening to 770 without also extending that chown would break `SHANNON_HOST_UID` runs, so the entrypoint's chown list now covers those three directories too. Signed-off-by: devamshah <devamshah91@gmail.com>
dc82513 to
a650708
Compare
|
@ezl-keygraph — this sat for four months at +1677/-40 because I bundled three unrelated things into one PR. That was my mistake, so I've cut it down rather than ask you to wade through it. Dropped, and why:
What's left is rebased onto Worth flagging, because it's the non-obvious half: the Verified: Two things I pulled out and deliberately did not open PRs for — say the word if either is wanted:
The title and description still describe the old three-part scope; treat this comment as the description until I correct them. |
Summary
Three logically distinct commits, one PR for review convenience:
security:hardening — prompt-injection defences inprompt-manager.ts, UID/GID validation inentrypoint.sh, dropchmod 777tochmod 770on container temp dirs.fix(cli):— URL try/catch + scheme allowlist onstart,distinguish
ENOENTfrom real I/O errors in the session-poll loop,locale-aware splash with ASCII fallback for terminals that don't
render Unicode block art.
feat:SARIF 2.1.0 report output — opt-in--report-format sarifflag wires a new
SarifReportOutputProviderthrough the existingReportOutputProviderDI seam. Default behaviour (md only) isbyte-for-byte unchanged.
Why bundled
These were found while reading the codebase to evaluate Shannon for
internal use. Each commit stands alone — squash, cherry-pick, or close
any one without affecting the others.
Commit 1 — security
config.descriptionapps/worker/src/services/prompt-manager.tsconfig.avoid/config.focusrule descriptions injected rawSHANNON_HOST_UID/SHANNON_HOST_GIDconsumed bygroupadd/useraddwithout numeric or range validationentrypoint.shchmod 777on/app,/tmp/.cache,/tmp/.config,/tmp/.npmDockerfileThreat model. Anyone who can write a Shannon config (a CI secret
leak, a compromised target repo) can today embed
{{AUTH_CONTEXT}}or
@include(/etc/passwd)in a description and have the agent treatit as orchestrator-level instruction. After this change those payloads
are inert text — the new `sanitizePromptValue()` breaks
{{...}}placeholder syntax and
@include(...)directives. Newlines arepreserved. Applied uniformly to every user-controlled interpolation
site.
`entrypoint.sh` now rejects non-numeric / out-of-range / zero values
for `SHANNON_HOST_UID` and `SHANNON_HOST_GID` with a clear error,
preventing a malicious env from mapping the pentest user to root or
feeding crafted input into a system command.
`chmod 770` is sufficient: only the pentest user (or a UID remapped
into the pentest group) ever runs in the container — world-write adds
blast radius without functional benefit.
Commit 2 — CLI bug fixes
handling; a malformed input crashed mid-setup with a raw `TypeError`.
Now wrapped in try/catch with an explicit `http` / `https` scheme
allowlist (the worker assumes web semantics).
`EACCES` / `EIO` / `ENOTDIR` alike, so a permissions issue
manifested as an indefinite "Waiting for workflow to start...".
Now distinguishes `ENOENT` (steady-state) and `SyntaxError`
(worker mid-write) from real I/O errors.
doesn't advertise UTF-8. Detection uses `LANG` / `LC_ALL` /
`LC_CTYPE` plus the well-known `WT_SESSION` and
`TERM_PROGRAM=vscode` signals. The Unicode visual is preserved on
every modern terminal and only degrades on raw cmd.exe / locale-less
SSH / some CI log streams.
Commit 3 — SARIF report output
`shannon start ... --report-format sarif` writes
`/.shannon/deliverables/comprehensive_security_assessment_report.sarif`
alongside the markdown report. The default (`md`) is unchanged.
Wiring goes:
`CLI flag → SHANNON_REPORT_FORMAT env → worker.ts:configureReportOutputProvider() → setContainerFactory(SarifReportOutputProvider)`
The provider plugs into the existing `ReportOutputProvider` interface
that's already invoked from `generateReportOutputActivity` —
zero changes to the activity / workflow layer.
Tool driver advertises five rules tagged with their CWE IDs and
the canonical OWASP help URI:
`shannon.injection` (CWE-74), `shannon.xss` (CWE-79),
`shannon.auth` (CWE-287), `shannon.ssrf` (CWE-918),
`shannon.authz` (CWE-285). Each non-empty
`*_exploitation_evidence.md` produces one SARIF `result` with the
evidence body as the message (truncated at 16 KiB to stay under
GitHub's per-result cap).
Out of scope (explicitly v0.1). Per-finding line/column locations
inside source files. The agents don't currently emit structured
per-finding metadata, so the artefact location is the deliverable file
itself. The envelope and consumer wiring shipped here unblock that
follow-up — the result mapping is then a one-function change.
Test plan
`sanitizePromptValue` and URL validation contracts
contract that `entrypoint.sh` enforces, so the bash and TS
sides can never silently drift
shape, one-result-per-evidence, empty/missing handling,
oversized truncation, and the output path
bundle 53.21 kB / 14.48 kB gzipped (no growth from main)
`LANG=C node apps/cli/dist/index.mjs info` renders the ASCII
fallback splash; `pnpm check` / `pnpm build` / `pnpm test` all
cached on second run
`workflows.ts` etc.) are not touched. All files added or modified
in this PR pass `biome check` cleanly.
Things I deliberately did not do
from Docker `CMD`, not user input — the practical injection risk is
near zero, and the fix is fiddly enough that I'd rather not bundle
it.
shortlist but each needs cost-tracker / fs-write-audit infrastructure
I don't yet have a clean read on. Happy to follow up with separate
PRs if there's interest.
agents emit structured findings (see Commit 3 description).
Built against `main @ 79caada` (post-v1.1.0). Author: Devam Shah —
no commercial affiliation, contributing personally.