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. |
|
I re-checked this against main at 25b90b0 — it still rebases clean, same two files, +26/-6, and nothing upstream has overtaken it. The Dockerfile still has chmod 777 on /app and the three /tmp cache dirs at 136-139, and entrypoint.sh still passes SHANNON_HOST_UID and GID straight into groupadd and useradd with no validation. The one thing worth spelling out is why it's two files and not two separate one-line patches. That 777 is the only reason /tmp/.cache, /tmp/.config and /tmp/.npm stay writable once the uid is remapped, because the chown on line 15 only ever covered sessions, workspaces, .claude and .pi. Tighten the mode without extending that chown and every SHANNON_HOST_UID run breaks, so the two halves have to land together. I know the README says you're not taking external code, and I'm not going to argue the policy. If that's still firm, just say so and I'll close this and refile the same two hunks as an issue — I'd rather you had the fix than have this sitting on your queue. |
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.