diff --git a/CLAUDE.md b/CLAUDE.md index 00740d27..86c411af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,7 +68,7 @@ npx @keygraph/shannon setup # Dashboard: http://localhost:8233 # Stop -./shannon stop # Stop one scan (confirms first; --yes/-y to skip) +./shannon stop [] # Stop one scan (default: the single running scan; confirms first; --yes/-y to skip) ./shannon stop --all # Stop all running scans (Temporal stays up; confirms first) ./shannon reset # Stop everything and wipe all Temporal data + volumes (type 'confirm' to proceed; cannot be skipped) @@ -101,8 +101,8 @@ apps/worker/ — @shannon/worker (private, Temporal worker + pipeline logic) ### CLI Package (`apps/cli/`) Published as `@keygraph/shannon` on npm. Contains Docker orchestration logic plus a read-only `@temporalio/client` reader (for `status`); no worker/pipeline business logic or prompts. Bundled with tsdown for single-file ESM output (deps stay external). -- `apps/cli/src/index.ts` — CLI dispatcher (`setup`, `start`, `stop`, `reset`, `logs`, `status`, `build`, `version`) -- `apps/cli/src/temporal-client.ts` — `@temporalio/client` reader for `status`: connects to the frontend on `127.0.0.1:7233` (published by compose), `describeScan` (status + `pendingActivities` → running agents), `queryProgress` (live `getProgress` query → `PipelineState`), `getTerminalOutcome` (workflow `result()`). No worker of its own; scans are visible only within Temporal's ~24h retention (namespace default, unset in compose) +- `apps/cli/src/index.ts` — CLI dispatcher (`setup`, `start`, `stop`, `reset`, `logs`, `status`, `scans`, `build`, `version`) +- `apps/cli/src/temporal-client.ts` — `@temporalio/client` reader for `status`: connects to the frontend on `127.0.0.1:7233` (published by compose), `describeScan` (status + `pendingActivities` → running agents), `queryProgress` (live `getProgress` query → `PipelineState`), `getTerminalOutcome` (workflow `result()`). No worker of its own; scans are visible within Temporal's retention window, which `ensureInfra` (`apps/cli/src/docker.ts`) converges to `168h` (7 days) on every successful `shannon start` — override with `SHANNON_TEMPORAL_RETENTION` (a positive whole-hour value like `72h`) - `apps/cli/src/scan/` — `status` rendering: `pipeline.ts` (static phase/agent plan + `run*Agent` activity-type→agent map + mirrored `PipelineState`/`AgentMetrics` types; keep in sync with the worker), `render.ts` (one renderer for both the live query state and the terminal result) - `apps/cli/src/mode.ts` — Auto-detection: local mode if `SHANNON_LOCAL=1` env var is set - `apps/cli/src/docker.ts` — Compose lifecycle, image pull/build, ephemeral `docker run` worker spawning @@ -158,15 +158,15 @@ Around those phases: - Optional agentic static analysis runs before the pentest when `agentic_sast.enabled` is `"true"`, as a child workflow. - After each class's analysis, reconciliation groups its findings into exploitation tasks. -- Findings outside the five classes form an internal `other` class with its own exploitation agent (`other-exploit`). +- Findings outside the five classes form an internal `miscellaneous` class with its own exploitation agent (`miscellaneous-exploit`). - A scan can finish `completed`, `partial`, `failed`, or `cancelled`; `partial` carries an ordered set of reasons. ### Supporting Systems -- **Configuration** — YAML configs in `apps/worker/configs/` use the closed JSON Schema in `config-schema.json`. Every fresh scan runs the fixed five analysis classes; there is no public class selector. `agentic_sast.enabled` is the only public agentic-SAST setting. Finding reconciliation runs on every scan and has no public setting of its own. Config also supports authentication (MFA/TOTP), URL/code rule scoping (`rules.avoid`/`rules.focus`), `exploit`, free-form `rules_of_engagement`, and post-hoc `report` options (`min_severity`, `min_confidence`, `guidance`, and exploit-only `sarif` output via `apps/worker/src/services/sarif-renderer.ts`). `code_path` avoid rules are enforced via the `@gotgenes/pi-permission-system` extension: `apps/worker/src/temporal/activities.ts:syncCodePathDenyRules` writes a global `path` deny config once per workflow (`apps/worker/src/ai/pi/permission-system.ts:syncPermissionSystemConfig`), and the executor loads the extension when that config is present (`apps/worker/src/ai/pi/pi-executor.ts`), so denies fire across every tool and child `task` session. Credential resolution — local mode: env vars → `./.env`; npx mode: env vars → `~/.shannon/config.toml` (via `npx @keygraph/shannon setup`) +- **Configuration** — YAML configs in `apps/worker/configs/` use the closed JSON Schema in `config-schema.json`. Every fresh scan runs the fixed five analysis classes; there is no public class selector. `agentic_sast.enabled` is the only public agentic-SAST setting. Finding reconciliation runs on every scan and has no public setting of its own. Config also supports authentication (MFA/TOTP), URL/code rule scoping (`rules.avoid`/`rules.focus`), `exploit`, free-form `rules_of_engagement`, and post-hoc `report` options (`min_severity`, `min_confidence`, `guidance`, and exploit-only `sarif` output via `apps/worker/src/services/sarif-renderer.ts`, on by default for exploit runs and opt out with `report.sarif: "false"`). `code_path` avoid rules are enforced via the `@gotgenes/pi-permission-system` extension: `apps/worker/src/temporal/activities.ts:syncCodePathDenyRules` writes a global `path` deny config once per workflow (`apps/worker/src/ai/pi/permission-system.ts:syncPermissionSystemConfig`), and the executor loads the extension when that config is present (`apps/worker/src/ai/pi/pi-executor.ts`), so denies fire across every tool and child `task` session. Credential resolution — local mode: env vars → `./.env`; npx mode: env vars → `~/.shannon/config.toml` (via `npx @keygraph/shannon setup`) - **Prompts** — Per-phase templates in `apps/worker/prompts/` with variable substitution (`{{TARGET_URL}}`, `{{CONFIG_CONTEXT}}`). Shared partials in `apps/worker/prompts/shared/` via `apps/worker/src/services/prompt-manager.ts`, including `_code-path-rules.txt` (focus/avoid `[FILE]`/`[GLOB]` routing) and `_rules-of-engagement.txt` (free-text engagement rules). When `exploit: false`, `apps/worker/src/services/findings-renderer.ts` deterministically converts each `*_exploitation_queue.json` into a `*_findings.md` for report assembly — no LLM in the loop - **Agent Harness (pi)** — Uses the **pi harness** (`@earendil-works/pi-coding-agent`, requires Node ≥ 22.19) via `apps/worker/src/ai/pi/pi-executor.ts` (`runPiPrompt` → `createAgentSession`). Retry is split in `apps/worker/src/ai/pi/retry-settings.ts`: pi's agent-level loop is off so Temporal owns agent restarts, while `provider.maxRetries` stays on — pi reads the `provider` block independently of the `enabled` flag — so transport faults are absorbed in-session rather than costing a full agent re-run. `maxRetryDelayMs` is left at pi's 60s default. One model runs every phase, named by `SHANNON_AI_MODEL=:` (default `anthropic:claude-sonnet-4-6`). `apps/worker/src/ai/models.ts` parses the spec — splitting on the **first** colon only, so Bedrock IDs keep theirs — and resolves it through pi's `ModelRuntime`. pi ships the `CredentialStore` interface but no in-memory implementation (its own reads `auth.json` from disk), so `RuntimeCredentialStore` in that file supplies one: credentials arrive as env vars in an ephemeral container and must never touch disk. `createModelRuntime(providerId, apiKey)` builds the runtime; `allowModelNetwork` stays at its default `false` so a scan never blocks on a catalog refresh. `resolveModelSelection()` is **async** because `ModelRuntime.create()` is. Any pi-ai provider id is accepted — `parseModelSpec` no longer rejects against a hardcoded list, so pi's registry is the authority (an unknown provider/model surfaces as a clear "not found in pi registry" error at preflight, which points to the browsable catalogue at `pi.dev/models` — `PI_CATALOG_URL` in `apps/worker/src/ai/models.ts`, appended to the not-found errors and shown in the setup wizard's "Other provider" hint). Four providers are **curated** (`CURATED_PROVIDERS`: `anthropic`, `openai`, `xai`, `amazon-bedrock`) with their own credential variables, config sections, and setup flows; each provider's API key env var is declared once in `PROVIDER_API_KEY_ENV` — Shannon uses each vendor's own variable name (`OPENAI_API_KEY`, `XAI_API_KEY`, …), never an invented one; Bedrock's entry is `AWS_BEARER_TOKEN_BEDROCK`, paired with `AWS_REGION`, which preflight requires separately as provider config rather than a credential. Any other provider uses the **generic** credential path: `SHANNON_AI_API_KEY` (`GENERIC_API_KEY_ENV`) supplies the key for any provider whose credential is a plain API key. Curated providers' own variables take precedence over it, and it also works as a fallback for them — Bedrock is the sole exception (it authenticates through its AWS_ variables, so the generic key never stands in for it). The CLI forwards `SHANNON_AI_API_KEY` in `COMMON_FORWARD_VARS` (it is provider-neutral, binding to whatever `SHANNON_AI_MODEL` names, so the "only one provider configured" guard counts only named credentials), and stores it under a generic `[provider]` config.toml section (`provider.api_key`). `npx @keygraph/shannon setup` exposes this as the "Other provider" option: free-text provider id + model id + key (a curated provider id is rejected there, since it has its own option). `SHANNON_AI_BASE_URL` overrides the endpoint for any provider (proxies/gateways); the credential is unchanged. `pointAtGateway` (`apps/worker/src/ai/models.ts`) applies the one dialect change: behind a base URL, `openai` follows `SHANNON_AI_OPENAI_FORMAT` (`chat-completions` default, or `responses`). On `chat-completions` it switches the API to `openai-completions` and drops the catalogue's Responses-shaped `compat` block so pi's `detectCompat` derives completions settings; on `responses` the descriptor is unchanged but for the endpoint. `resolveGatewayFormat` rejects the variable when the provider is not `openai` or no base URL is set, since it cannot take effect there. All other providers keep their API. The CLI mirrors the accepted values in `apps/cli/src/model-spec.ts`, forwards the variable in `COMMON_FORWARD_VARS`, and maps it to `openai.format` in config.toml. `buildEnvFlags` forwards only the selected provider's credential into the worker container. The CLI mirrors the parse rule and the provider/credential tables in `apps/cli/src/model-spec.ts` (it cannot import from the worker package); the two must stay in sync. pi ships no JSON-schema output or `Task`/`TodoWrite` built-ins, so structured queues are captured via a `submit_exploitation_queue` custom tool (`apps/worker/src/ai/queue-schemas.ts`), and `task` (child sessions scoped to `read`, `grep`, `find`, `ls`, `write`, and `bash` — no nested `task` or collector tools; `CHILD_TOOLS` in `apps/worker/src/ai/pi/task-tool.ts`) + `todo_write` (`apps/worker/src/ai/pi/session-tools.ts`) are provided as custom tools; the per-phase collectors are pi custom tools (TypeBox `defineTool` in `apps/worker/src/collectors/`). Shannon sets no thinking configuration at all — no `thinkingLevel` is passed to any `createAgentSession` call, so pi's own default applies. There is no adaptive-thinking support and no `CLAUDE_ADAPTIVE_THINKING` / `core.adaptive_thinking` setting. Browser automation via `playwright-cli` with session isolation (`-s=`). TOTP generation via `generate-totp` CLI tool. Login flow template at `apps/worker/prompts/shared/login-instructions.txt` supports form, SSO, API, and basic auth. On authenticated whitebox scans, the `validate-authentication` preflight performs the single real login and saves the browser session to `auth-state.json` in the per-session audit directory (path from `authStateFile()` in `apps/worker/src/audit/utils.ts`, derived from `generateAuditPath()`). The validation activity (`apps/worker/src/services/validate-authentication.ts`) removes any stale file from a prior run before the agent runs and verifies the file parses and contains cookies or storage before the preflight is marked complete; `logWorkflowComplete` deletes it when the workflow ends so authenticated cookies don't sit on disk between scans. Agent prompts opt in to session reuse by `@include(shared/_shared-session.txt)` before their `` block — the partial restores the session and falls through to the full login flow if verification fails. `vuln-auth`/`exploit-auth` omit the include and own their own login - **Pi Credential Reuse** — `SHANNON_USE_PI_AUTH=1` opts into reusing the host's Pi login, including an `openai-codex` ChatGPT Plus/Pro subscription selected with `SHANNON_AI_MODEL=openai-codex:`. `apps/cli/src/env.ts` requires `~/.pi/agent/auth.json`; `start.ts` passes its path to `spawnWorker`, which mounts only that file read-write at `/tmp/.pi/agent/auth.json`. The flag itself is not forwarded: the worker detects the file with `piAuthPresent()` and passes its path to `ModelRuntime.create`. CLI and worker API-key presence checks are skipped on this path, but the normal preflight model probe still validates the credential. The image and UID-remapping entrypoint keep `/tmp/.pi/agent` owned by `pentest` so adjacent Pi/Shannon configuration remains writable. Refreshed OAuth state is persisted to the host for subsequent scans. -- **Audit System** — Crash-safe append-only logging in `workspaces/{hostname}_{sessionId}/`. The run directory's top level holds the human-facing report in both formats (`Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`, `FINAL_REPORT_PDF_FILENAME`/`FINAL_REPORT_MD_FILENAME` in `apps/worker/src/paths.ts`); everything else — deliverables, per-agent logs, prompts, `session.json`, `workflow.log`, and browser artifacts — is nested under a hidden `.shannon/` internals dir (`INTERNAL_DIR`) so a customer sees only the report. Audit path helpers route through `generateInternalPath` (`apps/worker/src/audit/utils.ts`); the CLI nests the overlay backing dirs under the same `.shannon/` (`apps/cli/src/docker.ts`, `start.ts`). `session.json`/`workflow.log` reads use dual-read resolvers (`resolveSessionJsonPath`, `resolveRunFile`) that prefer `.shannon/` and fall back to the legacy run-root layout, so pre-restructure workspaces stay listable (`scans`/`logs`) without migration. Resuming a pre-restructure workspace upgrades it in place first: `migrateLegacyWorkspaceLayout` (`apps/cli/src/commands/start.ts`) renames the flat deliverables/logs/session entries into `.shannon/` (carrying the deliverables `.git` along) before the overlay dirs are mounted, so resume finds the old checkpoints instead of re-running every agent. The report agent writes structured findings to `report.json`, from which `report-renderer.ts` renders the assembled markdown and `report-json-adapter.ts` produces the Typst-shaped JSON that `pdf-renderer.ts` compiles into `comprehensive_security_assessment_report.pdf` using the bundled `apps/worker/templates/typst/report.typ` template (the `typst` binary is installed in the worker image). `copyReportToRunRoot` (`apps/worker/src/services/reporting.ts`) surfaces both the PDF and the markdown to the run root as `Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`; the deliverables-dir copies remain as the git-checkpointed sources. PDF compilation is best-effort — a failure is logged and the run still completes. WorkflowLogger (`apps/worker/src/audit/workflow-logger.ts`) provides unified human-readable per-workflow logs, backed by LogStream (`apps/worker/src/audit/log-stream.ts`) shared stream primitive. Every combined-log line is also projected into a per-agent file under `.shannon/agents/.log` (one per pipeline agent, one per Capella stage; subagents fold into the parent's file, and a stage's concurrent sessions share its file with an inline session label). The projection boundary is `apps/worker/src/audit/actor-projection.ts` (`projectActor` maps a `TraceActor` to its combined prefix and owning file slug — slugs come only from closed fields); fan-out is best-effort and never blocks the canonical combined log. A lifecycle owner holds a `LogStream` lease per agent file (the pipeline agent's `logAgent` span, or a Capella stage activity's `try/finally`) so per-line writes ride the reference count; `CapellaStageTrace.drain()` flushes a stage's trace queue before its activity returns. The CLI tails one file with `shannon logs --agent ` (`--list-agents` to enumerate); the default `shannon logs` path is unchanged +- **Audit System** — Crash-safe append-only logging in `workspaces/{hostname}_{sessionId}/`. The run directory's top level holds the human-facing report in both formats (`Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`, `FINAL_REPORT_PDF_FILENAME`/`FINAL_REPORT_MD_FILENAME` in `apps/worker/src/paths.ts`); everything else — deliverables, per-agent logs, prompts, `session.json`, `workflow.log`, and browser artifacts — is nested under a hidden `.shannon/` internals dir (`INTERNAL_DIR`) so a customer sees only the report. Audit path helpers route through `generateInternalPath` (`apps/worker/src/audit/utils.ts`); the CLI nests the overlay backing dirs under the same `.shannon/` (`apps/cli/src/docker.ts`, `start.ts`). `session.json`/`workflow.log` reads use dual-read resolvers (`resolveSessionJsonPath`, `resolveRunFile`) that prefer `.shannon/` and fall back to the legacy run-root layout, so pre-restructure workspaces stay listable (`scans`/`logs`) without migration. A pre-restructure workspace cannot be resumed: `classifyWorkspaceLaunch` (`apps/cli/src/commands/start.ts`) requires `.shannon/launch.json`, and its absence fails the launch as "created by an earlier version of Shannon" before anything on disk is touched. There is no in-place migration — the workspace's files and report are left untouched, and the operator starts a new scan under a different `-w` name. The report agent writes structured findings to `report.json`, from which `report-renderer.ts` renders the assembled markdown and `report-json-adapter.ts` produces the Typst-shaped JSON that `pdf-renderer.ts` compiles into `comprehensive_security_assessment_report.pdf` using the bundled `apps/worker/templates/typst/report.typ` template (the `typst` binary is installed in the worker image). `copyReportToRunRoot` (`apps/worker/src/services/reporting.ts`) surfaces both the PDF and the markdown to the run root as `Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`; the deliverables-dir copies remain as the git-checkpointed sources. PDF compilation is best-effort — a failure is logged and the run still completes. WorkflowLogger (`apps/worker/src/audit/workflow-logger.ts`) provides unified human-readable per-workflow logs, backed by LogStream (`apps/worker/src/audit/log-stream.ts`) shared stream primitive. Every combined-log line is also projected into a per-agent file under `.shannon/agents/.log` (one per pipeline agent, one per Capella stage; subagents fold into the parent's file, and a stage's concurrent sessions share its file with an inline session label). The projection boundary is `apps/worker/src/audit/actor-projection.ts` (`projectActor` maps a `TraceActor` to its combined prefix and owning file slug — slugs come only from closed fields); fan-out is best-effort and never blocks the canonical combined log. A lifecycle owner holds a `LogStream` lease per agent file (the pipeline agent's `logAgent` span, or a Capella stage activity's `try/finally`) so per-line writes ride the reference count; `CapellaStageTrace.drain()` flushes a stage's trace queue before its activity returns. The CLI tails one file with `shannon logs --agent ` (`--list-agents` to enumerate); the default `shannon logs` path is unchanged - **Deliverables** — Saved to `.shannon/deliverables/` in the target repo via the `save-deliverable` CLI script (`apps/worker/src/scripts/save-deliverable.ts`) - **Workspaces & Resume** — Named workspaces via `-w ` or auto-named from URL+timestamp. Resume detects completed agents via `session.json`. `loadResumeState()` in `apps/worker/src/temporal/activities.ts` validates deliverable existence, restores git checkpoints, and cleans up incomplete deliverables diff --git a/LICENSES/MIT-Pi.txt b/LICENSES/MIT-Pi.txt new file mode 100644 index 00000000..4864295e --- /dev/null +++ b/LICENSES/MIT-Pi.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Mario Zechner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index aae9eeef..f18e84eb 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ It analyzes your source code, identifies attack paths, and executes real exploit - [Documentation](#documentation) - [Safety, Scope, and Limitations](#safety-scope-and-limitations) - [License](#license) +- [Acknowledgements](#acknowledgements) - [About Keygraph](#about-keygraph) - [Community and Support](#community-and-support) - [Common Questions](#common-questions) @@ -230,6 +231,14 @@ Commercial and enterprise licensing is available for organizations that need dif For commercial licensing, contact [shannon@keygraph.io](mailto:shannon@keygraph.io). +## Acknowledgements + +Thanks to [Pi](https://github.com/earendil-works/pi), +[Playwright CLI](https://github.com/microsoft/playwright-cli), +and [Mantis](https://github.com/google/mantis). + +See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for licensing and attribution details. + ## About Keygraph **Keygraph** is the company behind Shannon. It also builds the **Keygraph platform**, the commercial agentic pentesting product that closes the full AppSec lifecycle and runs an enhanced build of Shannon as its pentesting engine. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 3718d31f..9e1bfd87 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -6,6 +6,17 @@ Shannon as a whole is distributed under the GNU Affero General Public License, version 3.0 (see LICENSE). Third-party material incorporated into Shannon remains subject to the attribution and notice requirements of its own license. +## Pi + +Shannon uses Pi as part of its agent framework. + +Project: https://github.com/earendil-works/pi +License: MIT + +Copyright (c) 2025 Mario Zechner + +The applicable license is reproduced at `LICENSES/MIT-Pi.txt`. + ## Mantis Portions of Shannon's Capella agentic SAST implementation, specifically the @@ -24,13 +35,12 @@ reside under: - apps/worker/prompts/partials/ (capella-*.hbs prompt partials) - apps/worker/prompts/sast/capella/ (prompt templates) -The pinned upstream tree contains no NOTICE file, so no upstream NOTICE text is -reproduced here. The upstream LICENSE carries no copyright notice of its own, so -none is reproduced. +The Mantis-derived material has been substantially modified by Keygraph +for use within Shannon, including adaptation to Shannon's agent +architecture and the Pi agent framework. -The Mantis-derived material has been substantially modified by Keygraph for -Shannon. Material changes include adaptation to Shannon's agent architecture, -enforcing repository-relative paths and complete verdict sets, and providing -separate production and pipeline-testing prompt variants. +Copyright and attribution notices from the original Mantis material +remain the property of their respective copyright holders. -Modifications: Copyright © 2026 Keygraph, Inc. +Modifications: +Copyright © 2026 Keygraph, Inc. diff --git a/apps/cli/src/commands/logs.ts b/apps/cli/src/commands/logs.ts index 42632549..57f93aa5 100644 --- a/apps/cli/src/commands/logs.ts +++ b/apps/cli/src/commands/logs.ts @@ -148,10 +148,10 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom return new Promise((resolve) => { let position = 0; const completion = new LogCompletionState(); + const completionDecoder = new StringDecoder('utf8'); let done = false; const controller = new AbortController(); let watcher: ReturnType | undefined; - const completionDecoder = new StringDecoder('utf8'); /** Output any new content appended since the last read. */ function flush(): boolean { diff --git a/apps/cli/src/docker.ts b/apps/cli/src/docker.ts index f7645159..3875fb45 100644 --- a/apps/cli/src/docker.ts +++ b/apps/cli/src/docker.ts @@ -448,8 +448,9 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess { args.push(...opts.envFlags); // Container settings. Chromium's own sandbox needs syscalls Docker's default seccomp - // profile blocks, so it is loosened for the in-container browser automation; the - // worker process itself is not granted any extra privilege by this. + // profile blocks, which is why the profile is dropped. `seccomp=unconfined` is a + // container-wide setting, not a per-process one: every process here runs unfiltered, + // the worker included — not just the browser automation that motivates it. args.push('--shm-size', '2gb', '--security-opt', 'seccomp=unconfined'); // Image diff --git a/apps/worker/src/ai/audit-logger.ts b/apps/worker/src/ai/audit-logger.ts index 2590997b..12b3afe5 100644 --- a/apps/worker/src/ai/audit-logger.ts +++ b/apps/worker/src/ai/audit-logger.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/models.ts b/apps/worker/src/ai/models.ts index f92a5313..823f7f64 100644 --- a/apps/worker/src/ai/models.ts +++ b/apps/worker/src/ai/models.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -19,6 +19,13 @@ * * Resolution returns a pi `Model` plus the `ModelRuntime` that owns its auth, * built over an in-memory credential store primed from the environment. + * + * The CLI cannot import this module (it ships as a separate bundle), so + * `apps/cli/src/model-spec.ts` mirrors the parse rule and the provider/credential + * tables by hand for its own `status` rendering and setup wizard. The two copies + * have no shared compile-time link: a provider added or renamed on one side and + * not the other does not fail to build, it just makes the CLI's guidance or + * guard rails disagree with what the worker actually accepts at runtime. */ import { existsSync } from 'node:fs'; @@ -30,6 +37,11 @@ import { getAgentDir, ModelRuntime } from '@earendil-works/pi-coding-agent'; * Providers Shannon curates with their own credential variables, config sections, * and setup flows. Each is a pi-ai provider id; any other pi provider is still * reachable through the generic credential path below. + * + * Kept identical to the CLI's own copy of this list (`apps/cli/src/model-spec.ts`), + * which the CLI uses to decide whether "only one provider is configured" and to + * gate its "Other provider" setup option. A curated provider missing from one + * copy is silently treated as generic on that side. */ export const CURATED_PROVIDERS = ['anthropic', 'openai', 'xai', 'amazon-bedrock'] as const; @@ -47,6 +59,11 @@ export const GENERIC_API_KEY_ENV = 'SHANNON_AI_API_KEY'; * does not invent credential names — these are the variables each provider's own * tooling uses. Bedrock pairs its bearer token with AWS_REGION, which is provider * config rather than a credential. + * + * Mirrored by the CLI's own table of the same name, used there to decide which + * env vars to forward into the worker container. A variable added here without + * its CLI counterpart never reaches the container: the worker looks for a + * credential the CLI never forwarded, and preflight reports it as absent. */ export const PROVIDER_API_KEY_ENV: Readonly> = { anthropic: ['ANTHROPIC_API_KEY', 'CLAUDE_CODE_OAUTH_TOKEN'], @@ -335,11 +352,18 @@ export async function resolveModelSelection(): Promise { ); } + let credentialSource: ModelSelection['credentialSource'] = 'ambient'; + if (mountedPiAuth) { + credentialSource = 'pi-auth'; + } else if (credentials.apiKey) { + credentialSource = 'api-key'; + } + return { model, modelRuntime, modelId, providerId, - credentialSource: mountedPiAuth ? 'pi-auth' : credentials.apiKey ? 'api-key' : 'ambient', + credentialSource, }; } diff --git a/apps/worker/src/ai/output-formatters.ts b/apps/worker/src/ai/output-formatters.ts index cc604582..e75e1b75 100644 --- a/apps/worker/src/ai/output-formatters.ts +++ b/apps/worker/src/ai/output-formatters.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/pi/capella-agent-executor.ts b/apps/worker/src/ai/pi/capella-agent-executor.ts index 02cb0697..464a9a07 100644 --- a/apps/worker/src/ai/pi/capella-agent-executor.ts +++ b/apps/worker/src/ai/pi/capella-agent-executor.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -38,6 +38,9 @@ const MAX_TOOLS_PER_SESSION = 32; const MAX_TURNS_PER_SESSION = 1_000; const MAX_TIMEOUT_MS = 24 * 60 * 60 * 1_000; +// The closed set of stage-specific tools a caller is allowed to hand in alongside the confined +// repository tools. Anything not on this list, and not a repository tool, is rejected as unknown +// by validateCallerTools below. const CAPELLA_COLLECTOR_TOOL_NAMES = new Set([ 'report_finding', 'record_duplicates', @@ -47,6 +50,12 @@ const CAPELLA_COLLECTOR_TOOL_NAMES = new Set([ 'record_calibration', ]); +// A Capella stage reasons over a read-only, confined view of the repository; none of these may +// ever be offered to it. `bash`/`shell`/`network`/`browser`/`web_search` would give it an escape +// hatch out of the confined tool set entirely; `edit`/`write` would let a review agent change the +// code it is meant to only analyze; `task` would let it spawn further sessions outside this +// executor's bounded turn/timeout accounting; `glob`/`ls`/`todo`/`todo_write` duplicate tools the +// stage already gets from the confined factory or has no use for. const FORBIDDEN_TOOL_NAMES = new Set([ 'bash', 'browser', diff --git a/apps/worker/src/ai/pi/capella-agent-types.ts b/apps/worker/src/ai/pi/capella-agent-types.ts index 2f013317..aa2bc7fb 100644 --- a/apps/worker/src/ai/pi/capella-agent-types.ts +++ b/apps/worker/src/ai/pi/capella-agent-types.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/pi/permission-system.ts b/apps/worker/src/ai/pi/permission-system.ts index a1febac0..6c10e9b2 100644 --- a/apps/worker/src/ai/pi/permission-system.ts +++ b/apps/worker/src/ai/pi/permission-system.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/pi/pi-executor.ts b/apps/worker/src/ai/pi/pi-executor.ts index 483f7049..ffb9bf5d 100644 --- a/apps/worker/src/ai/pi/pi-executor.ts +++ b/apps/worker/src/ai/pi/pi-executor.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/pi/retry-settings.ts b/apps/worker/src/ai/pi/retry-settings.ts index 77ceb89e..f88de7e0 100644 --- a/apps/worker/src/ai/pi/retry-settings.ts +++ b/apps/worker/src/ai/pi/retry-settings.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/pi/session-tools.ts b/apps/worker/src/ai/pi/session-tools.ts index b5f640f5..24b97b11 100644 --- a/apps/worker/src/ai/pi/session-tools.ts +++ b/apps/worker/src/ai/pi/session-tools.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/pi/source-jail.ts b/apps/worker/src/ai/pi/source-jail.ts index e7892086..9b27219d 100644 --- a/apps/worker/src/ai/pi/source-jail.ts +++ b/apps/worker/src/ai/pi/source-jail.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -13,6 +13,9 @@ import path from 'node:path'; import { ArtifactIntegrityError, ReconciliationIoError } from '../reconciliation/artifact-store.js'; const JAIL_PREFIX = 'shannon-task-formation-'; +// Never copied into the model-readable jail: `.git` carries deliverables history, `.shannon` holds +// scan internals, and `.pi` holds provider credentials. Any of these reaching the jail would expose +// them to the tools the model drives. The post-copy verification re-checks their absence by name. const ALWAYS_EXCLUDED_NAMES = Object.freeze(['.git', '.shannon', '.pi'] as const); export interface SourceJailOptions { @@ -44,6 +47,8 @@ function checkCancellation(signal: AbortSignal | undefined): void { if (signal?.aborted === true) throw cancellationError(signal); } +// Path-confinement predicate: true only when `candidate` is `root` itself or lies beneath it. +// A relative path that escapes upward (`..`) or is absolute means the candidate is outside the root. function isWithin(root: string, candidate: string): boolean { const relativePath = path.relative(root, candidate); return ( @@ -76,6 +81,8 @@ async function relativeExclusion( if (relativePath === undefined) return undefined; if (relativePath === '') { + // An exclusion that resolves to the whole root would empty the jail. Fail closed rather than + // copy nothing and hand the model an empty tree. throw new ArtifactIntegrityError('A task-formation exclusion resolves to the complete source root'); } return relativePath; @@ -119,11 +126,16 @@ async function copySourceTree( throw new ReconciliationIoError('Unable to enumerate the task-formation source tree'); } + // Cancellation is checked before every top-level entry and inside the copy filter so an aborted + // scan stops promptly instead of copying a whole large tree first. for (const entry of entries) { checkCancellation(signal); const source = path.join(sourceRoot, entry.name); const destinationEntry = path.join(destination, entry.name); try { + // verbatimSymlinks copies links as links rather than following them, so a link pointing + // outside the tree cannot pull external content in; the filter then drops any path that + // resolves outside the root, plus the always- and dynamically-excluded paths. await cp(source, destinationEntry, { recursive: true, verbatimSymlinks: true, @@ -183,6 +195,9 @@ async function assertDynamicExclusionsAbsent( } } +// Re-verify the copied tree independently of the copy filter: the jail root must be a real +// directory (not a symlink), and no excluded name or protected workspace path may survive. This +// catches a filter gap or a race during the copy before the model is allowed to read the tree. async function verifyJail( directory: string, dynamicExclusions: readonly string[], @@ -247,6 +262,7 @@ export async function materializeSourceJail(options: SourceJailOptions): Promise } catch { throw new ReconciliationIoError('Unable to resolve the task-formation temporary root'); } + // A temp root inside the source tree would make the copy try to copy the jail into itself. if (isWithin(sourceRoot, tempRoot)) { throw new ArtifactIntegrityError('The task-formation temporary root cannot be inside the source tree'); } diff --git a/apps/worker/src/ai/pi/structured-generation.ts b/apps/worker/src/ai/pi/structured-generation.ts index 38670fbe..a3ae25f0 100644 --- a/apps/worker/src/ai/pi/structured-generation.ts +++ b/apps/worker/src/ai/pi/structured-generation.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -17,6 +17,10 @@ import { type CapturedSubmitTool, createGenericSubmitTool } from '../submit-tool const ZERO_USAGE = { inputTokens: 0, outputTokens: 0, costUsd: 0 } as const; +// True only when this caller's own signal aborted and the error traces back to it. Walk a bounded, +// cycle-guarded cause chain so a cancellation wrapped several layers deep is still recognized as a +// cancellation and not misreported as a provider error. Without the `signal.aborted` gate an +// unrelated AbortError from the provider could be mistaken for our cancellation. function isSignalCancellation(error: unknown, signal: AbortSignal | undefined): boolean { if (signal?.aborted !== true) return false; @@ -109,6 +113,8 @@ async function generate(host: ModelHost, request: StructuredGenerationRequest): }; } if (response.stopReason === 'aborted') { + // An abort with our signal set is a real cancellation. An abort without it is a provider-side + // stop we did not ask for, so classify it as an error the caller can retry on. if (request.signal?.aborted === true) { return { stopReason: 'aborted', toolCalls: [], usage: responseUsage(response) }; } diff --git a/apps/worker/src/ai/pi/task-tool.ts b/apps/worker/src/ai/pi/task-tool.ts index eec3b56e..0257b5e4 100644 --- a/apps/worker/src/ai/pi/task-tool.ts +++ b/apps/worker/src/ai/pi/task-tool.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/pi/turn-error.ts b/apps/worker/src/ai/pi/turn-error.ts index f5079034..6a17ca29 100644 --- a/apps/worker/src/ai/pi/turn-error.ts +++ b/apps/worker/src/ai/pi/turn-error.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/playwright-config-writer.ts b/apps/worker/src/ai/playwright-config-writer.ts index e17e6fdd..a06f511d 100644 --- a/apps/worker/src/ai/playwright-config-writer.ts +++ b/apps/worker/src/ai/playwright-config-writer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/progress-manager.ts b/apps/worker/src/ai/progress-manager.ts index 0d4e63eb..2980eab9 100644 --- a/apps/worker/src/ai/progress-manager.ts +++ b/apps/worker/src/ai/progress-manager.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -9,6 +9,13 @@ import { ProgressIndicator } from '../progress-indicator.js'; import { extractAgentType } from '../utils/formatting.js'; +/** + * `useCleanOutput` marks the phases that use the friendly "Running X..." + * spinner plus a one-line completion message (pre-recon, recon, report, and + * the vuln/exploit agents) as opposed to the verbose turn-by-turn fallback + * formatting used elsewhere. `createProgressManager` reads it to decide + * between a real spinner and the silent null one. + */ export interface ProgressContext { description: string; useCleanOutput: boolean; @@ -62,7 +69,8 @@ class NullProgressManager implements ProgressManager { } } -// Returns no-op when disabled +// Returns no-op when disabled. `disableLoader` lets a caller force the silent manager regardless +// of useCleanOutput, for a context where an animated spinner would be unwanted no matter the phase. export function createProgressManager(context: ProgressContext, disableLoader: boolean): ProgressManager { if (!context.useCleanOutput || disableLoader) { return new NullProgressManager(); diff --git a/apps/worker/src/ai/queue-schemas.ts b/apps/worker/src/ai/queue-schemas.ts index 062c2e25..81a8bcaa 100644 --- a/apps/worker/src/ai/queue-schemas.ts +++ b/apps/worker/src/ai/queue-schemas.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/sast/capella/stages/verdicts.ts b/apps/worker/src/ai/sast/capella/stages/verdicts.ts index bcf99820..c7290220 100644 --- a/apps/worker/src/ai/sast/capella/stages/verdicts.ts +++ b/apps/worker/src/ai/sast/capella/stages/verdicts.ts @@ -267,6 +267,9 @@ export async function runReviewStage( ); let usage = primary.usage; let salvagedTurnLimitCount = primary.salvagedTurnLimit ? 1 : 0; + // One bounded repair pass, scoped to only the findings the primary session skipped: this + // recovers a session that ran out of turns or omitted a few findings without re-running the + // full finding set, which would double the cost of every unaffected verdict alongside it. const missing = missingFindings(source.findings, collector.getAcceptedIds()); if (missing.length > 0) { const repair = await runCollectorSession( diff --git a/apps/worker/src/ai/sast/types.ts b/apps/worker/src/ai/sast/types.ts index 30a6291d..2853102f 100644 --- a/apps/worker/src/ai/sast/types.ts +++ b/apps/worker/src/ai/sast/types.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/structured-generation.ts b/apps/worker/src/ai/structured-generation.ts index 0554e5a6..569610c5 100644 --- a/apps/worker/src/ai/structured-generation.ts +++ b/apps/worker/src/ai/structured-generation.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/submit-tool.ts b/apps/worker/src/ai/submit-tool.ts index 847e0381..b9ed1cfd 100644 --- a/apps/worker/src/ai/submit-tool.ts +++ b/apps/worker/src/ai/submit-tool.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/ai/types.ts b/apps/worker/src/ai/types.ts index 99321f7b..e12d28cd 100644 --- a/apps/worker/src/ai/types.ts +++ b/apps/worker/src/ai/types.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/audit/audit-session.ts b/apps/worker/src/audit/audit-session.ts index 0c626f9b..ee90d0b0 100644 --- a/apps/worker/src/audit/audit-session.ts +++ b/apps/worker/src/audit/audit-session.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/audit/index.ts b/apps/worker/src/audit/index.ts index 97d3b7d2..cd2f38c3 100644 --- a/apps/worker/src/audit/index.ts +++ b/apps/worker/src/audit/index.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/audit/log-stream.ts b/apps/worker/src/audit/log-stream.ts index 179a3c9d..4af01493 100644 --- a/apps/worker/src/audit/log-stream.ts +++ b/apps/worker/src/audit/log-stream.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/audit/metrics-tracker.ts b/apps/worker/src/audit/metrics-tracker.ts index bbaf4774..6d64ba95 100644 --- a/apps/worker/src/audit/metrics-tracker.ts +++ b/apps/worker/src/audit/metrics-tracker.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/audit/utils.ts b/apps/worker/src/audit/utils.ts index ee0737c6..346eb5c6 100644 --- a/apps/worker/src/audit/utils.ts +++ b/apps/worker/src/audit/utils.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/audit/workflow-logger.ts b/apps/worker/src/audit/workflow-logger.ts index e2bea653..172037d0 100644 --- a/apps/worker/src/audit/workflow-logger.ts +++ b/apps/worker/src/audit/workflow-logger.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/collectors/exploit-collector.ts b/apps/worker/src/collectors/exploit-collector.ts index aa78453c..f4cde6d3 100644 --- a/apps/worker/src/collectors/exploit-collector.ts +++ b/apps/worker/src/collectors/exploit-collector.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/collectors/finding-collector.ts b/apps/worker/src/collectors/finding-collector.ts index 8df92bd6..2d5b448e 100644 --- a/apps/worker/src/collectors/finding-collector.ts +++ b/apps/worker/src/collectors/finding-collector.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/collectors/pre-recon-collector.ts b/apps/worker/src/collectors/pre-recon-collector.ts index 2c0ee910..0ce0eb94 100644 --- a/apps/worker/src/collectors/pre-recon-collector.ts +++ b/apps/worker/src/collectors/pre-recon-collector.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/collectors/recon-collector.ts b/apps/worker/src/collectors/recon-collector.ts index f48a3385..53261864 100644 --- a/apps/worker/src/collectors/recon-collector.ts +++ b/apps/worker/src/collectors/recon-collector.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -717,6 +717,9 @@ export function createReconCollector(): ReconCollector { 'The renderer sorts by (path, method) before rendering, so emission order does not affect output.', parameters: AddEndpointsInputSchema, async execute(_toolCallId, input) { + // Unlike the one-shot set_* tools, repeated calls here are expected (the agent splits a + // large inventory across several), so a repeated (method, path) pair is silently skipped + // as a no-op rather than rejected as a DuplicateError. addEndpointsCalls += 1; const added: string[] = []; const skipped: string[] = []; diff --git a/apps/worker/src/collectors/schema.ts b/apps/worker/src/collectors/schema.ts index 8b9f2400..0132a1fe 100644 --- a/apps/worker/src/collectors/schema.ts +++ b/apps/worker/src/collectors/schema.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/collectors/vuln-collector.ts b/apps/worker/src/collectors/vuln-collector.ts index deb08da3..15a7e67c 100644 --- a/apps/worker/src/collectors/vuln-collector.ts +++ b/apps/worker/src/collectors/vuln-collector.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -404,6 +404,8 @@ export function createVulnCollector(vulnClass: VulnClass): VulnCollector { parameters: intelSchema, async execute(_toolCallId, input) { if (state.strategic_intelligence) return alreadyCalled('set_strategic_intelligence'); + // Safe: intelSchema was selected from STRATEGIC_INTEL_SCHEMAS by this collector's own + // vulnClass, so cleanInput's output shape always matches one arm of the union below. state.strategic_intelligence = cleanInput(intelSchema, input) as unknown as StrategicIntelligenceInput; return successResult({ set: 'set_strategic_intelligence' }); }, diff --git a/apps/worker/src/config-parser.ts b/apps/worker/src/config-parser.ts index d229a69d..0a543178 100644 --- a/apps/worker/src/config-parser.ts +++ b/apps/worker/src/config-parser.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/paths.ts b/apps/worker/src/paths.ts index b52ea0d3..1cca61f4 100644 --- a/apps/worker/src/paths.ts +++ b/apps/worker/src/paths.ts @@ -93,4 +93,6 @@ function findRepoRoot(): string { } const REPO_ROOT = findRepoRoot(); + +/** Default root for named scan workspaces; each session's audit directory nests under here. */ export const WORKSPACES_DIR = path.join(REPO_ROOT, 'workspaces'); diff --git a/apps/worker/src/progress-indicator.ts b/apps/worker/src/progress-indicator.ts index 961f7e85..c2ffa0d0 100644 --- a/apps/worker/src/progress-indicator.ts +++ b/apps/worker/src/progress-indicator.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -36,7 +36,8 @@ export class ProgressIndicator { this.interval = null; } - // Clear the spinner line + // Clear the spinner line: overwrite with spaces at least as wide as the last frame + // written (message plus the spinner glyph and separator), then return the cursor home. process.stdout.write(`\r${' '.repeat(this.message.length + 5)}\r`); this.isRunning = false; } diff --git a/apps/worker/src/scripts/generate-totp.ts b/apps/worker/src/scripts/generate-totp.ts index 72140617..b00dcf1f 100644 --- a/apps/worker/src/scripts/generate-totp.ts +++ b/apps/worker/src/scripts/generate-totp.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -12,6 +12,11 @@ * Generates a TOTP code for the target's MFA. * Based on RFC 6238 (TOTP) and RFC 4226 (HOTP). * + * The login flow prompt has the agent run this via the `bash` tool with the TOTP secret + * substituted in, rather than asking the model to work out HOTP/TOTP arithmetic itself. + * The secret is only ever held in memory here; nothing is written to disk, and the + * result is emitted as JSON on stdout for the caller to parse. + * * Usage: * generate-totp --secret JBSWY3DPEHPK3PXP */ @@ -64,7 +69,10 @@ function generateHOTP(secret: string, counter: number, digits: number = 6): stri hmac.update(counterBuffer); const hash = hmac.digest(); - // Dynamic truncation (SHA-1 always produces 20 bytes) + // Dynamic truncation (SHA-1 always produces 20 bytes). The low nibble of the last byte + // picks a 4-byte window anywhere in the hash; masking the top bit of that window's first + // byte (0x7f) keeps the result a positive 31-bit int per RFC 4226, regardless of JS's + // signed 32-bit bitwise operators. const lastByte = hash[hash.length - 1] ?? 0; const offset = lastByte & 0x0f; const code = diff --git a/apps/worker/src/scripts/save-deliverable.ts b/apps/worker/src/scripts/save-deliverable.ts index 31e19198..4b1772f9 100644 --- a/apps/worker/src/scripts/save-deliverable.ts +++ b/apps/worker/src/scripts/save-deliverable.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -107,6 +107,10 @@ function main(): void { process.exit(1); } + // The --type argument only ever resolves to one of the fixed filenames in + // DELIVERABLE_FILENAMES; there is no path taking an agent-supplied filename directly + // to disk. This keeps the deliverables directory contents fully predictable regardless + // of what an agent passes for --type. const deliverableType = args.type as DeliverableType; const filename = DELIVERABLE_FILENAMES[deliverableType]; diff --git a/apps/worker/src/scripts/set-report-meta.ts b/apps/worker/src/scripts/set-report-meta.ts index 2fd4005a..eac30495 100644 --- a/apps/worker/src/scripts/set-report-meta.ts +++ b/apps/worker/src/scripts/set-report-meta.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -58,6 +58,8 @@ function getFlag(argv: string[], flag: string): string | undefined { return undefined; } +// Reads the existing report.json (if any) so report_meta can be merged in without +// disturbing the findings array that other invocations of the report agent's tools append to. function readReportFile(filePath: string): ReportFile { if (!existsSync(filePath)) { return { findings: [] }; @@ -66,6 +68,8 @@ function readReportFile(filePath: string): ReportFile { return JSON.parse(raw) as ReportFile; } +// Temp file + rename so a crash mid-write never leaves report.json truncated; the +// finding-collector's own writes to this file rely on the same guarantee. function writeReportFile(filePath: string, data: ReportFile): void { const tmpPath = `${filePath}.tmp`; const payload = JSON.stringify(data, null, 2); diff --git a/apps/worker/src/services/agent-execution.ts b/apps/worker/src/services/agent-execution.ts index caaf3969..0da6eff1 100644 --- a/apps/worker/src/services/agent-execution.ts +++ b/apps/worker/src/services/agent-execution.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -90,6 +90,9 @@ function errorCodeFromResult(result: PiPromptResult): ErrorCode { return ErrorCode.AGENT_EXECUTION_FAILED; } +// Only the codes this service can itself raise from execution failure get a specific category; +// every other code (including provider-classified ones) falls through to 'validation' because +// this path only runs for a failed agent attempt, never for a config or preflight error. function categoryForErrorCode(code: ErrorCode): PentestErrorType { switch (code) { case ErrorCode.GIT_CHECKPOINT_FAILED: @@ -109,6 +112,8 @@ function gitFailureForAgent( error: Error | undefined, code: ErrorCode = ErrorCode.GIT_CHECKPOINT_FAILED, ): PentestError { + // An unclassified git failure is assumed transient (filesystem contention, not a permanent + // fault), so it does not cost the class its one shot at succeeding on retry. const retryable = error instanceof PentestError ? error.retryable : true; const message = error?.message ?? 'unknown git failure'; return new PentestError( diff --git a/apps/worker/src/services/agent-git-paths.ts b/apps/worker/src/services/agent-git-paths.ts index ceb7bc5f..a1db0077 100644 --- a/apps/worker/src/services/agent-git-paths.ts +++ b/apps/worker/src/services/agent-git-paths.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/code-location-join.ts b/apps/worker/src/services/code-location-join.ts index 41156529..a29f5b97 100644 --- a/apps/worker/src/services/code-location-join.ts +++ b/apps/worker/src/services/code-location-join.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/config-loader.ts b/apps/worker/src/services/config-loader.ts index 5bd63496..cd2a050a 100644 --- a/apps/worker/src/services/config-loader.ts +++ b/apps/worker/src/services/config-loader.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/container.ts b/apps/worker/src/services/container.ts index 7f0de9c9..e14bf322 100644 --- a/apps/worker/src/services/container.ts +++ b/apps/worker/src/services/container.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/error-handling.ts b/apps/worker/src/services/error-handling.ts index de89d5a6..29921b62 100644 --- a/apps/worker/src/services/error-handling.ts +++ b/apps/worker/src/services/error-handling.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/exact-output-commit.ts b/apps/worker/src/services/exact-output-commit.ts index f6c6f9a5..42c8f1a1 100644 --- a/apps/worker/src/services/exact-output-commit.ts +++ b/apps/worker/src/services/exact-output-commit.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -33,6 +33,11 @@ export interface RenumberErrorDetails { readonly [key: string]: unknown; } +/** + * Integrity failure shared by exact-path publication and the renumber/compaction transforms. + * `retryable` drives the Temporal wrapper's retry decision, and `details.checkCode` is a stable + * machine-readable identifier; both are part of the durable error contract. + */ export class RenumberError extends Error { readonly retryable: boolean; readonly type: RenumberErrorType; @@ -56,6 +61,7 @@ export interface ExactOutputFile { export interface ExactOutputCommit { readonly commitHash: string; readonly changedPaths: readonly string[]; + /** True when HEAD already held every declared byte and the existing commit was adopted. */ readonly alreadyCommitted: boolean; } @@ -71,6 +77,9 @@ function isErrno(error: unknown, code: string): boolean { return error instanceof Error && (error as NodeJS.ErrnoException).code === code; } +// WARNING: a symlink at a declared output path would redirect the atomic write outside the +// deliverables repo, so publication refuses to write through one. ENOENT is fine: the path +// simply has not been written yet. async function rejectSymlinks(dir: string, relPaths: readonly string[]): Promise { for (const relPath of relPaths) { try { @@ -107,6 +116,11 @@ async function executeExactGitCommand( } } +/** + * Realign only the declared paths with HEAD, in both the worktree and the index: paths present + * at HEAD are restored, paths absent at HEAD are unstaged and deleted. Sibling files are never + * touched, so a failed publication cannot discard a concurrent agent's staged or dirty work. + */ async function repairExactPathsFromHead(dir: string, relPaths: readonly string[]): Promise { const presentPaths: string[] = []; const absentPaths: string[] = []; @@ -141,6 +155,13 @@ async function repairExactPathsFromHead(dir: string, relPaths: readonly string[] } } +/** + * Commit the declared files through a scratch index seeded from HEAD's tree, so entries staged + * in the real index can never leak into the commit and a failed attempt leaves the real index + * untouched. Publication routes here whenever the file set declares a deletion. + * `update-ref HEAD ` is a compare-and-swap: it fails instead of clobbering HEAD if + * anything else advanced the branch after the tree was read. + */ async function commitExactFilesWithTemporaryIndex( dir: string, files: readonly ExactOutputFile[], @@ -178,6 +199,9 @@ async function commitExactFilesWithTemporaryIndex( dir, 'advancing HEAD to the exact-output commit', ); + // The commit was built in the scratch index, so the real index still reflects the old HEAD. + // Re-sync just the declared paths there; otherwise later status and commit calls would see + // phantom changes for files this commit already settled. for (const file of files) { if (file.contents === null) { const listed = await executeExactGitCommand( @@ -207,7 +231,16 @@ async function commitExactFilesWithTemporaryIndex( } } -/** Exact-path, lost-acknowledgement-safe publication used by both transforms. */ +/** + * Publish an exact file set as one commit whose changed paths equal exactly the declared + * deltas, leaving every sibling path alone. + * + * The call is safe to re-drive after a lost acknowledgement: when HEAD already holds every + * declared byte it repairs worktree drift and adopts the existing state (`alreadyCommitted`) + * instead of creating a second commit. Committed bytes are re-read and verified before the + * result is returned, so a caller never acknowledges a publication the repo does not hold. + * On any failure the declared paths are rolled back to HEAD. + */ export async function writeAndCommitExactFiles( dir: string, files: readonly ExactOutputFile[], @@ -278,6 +311,8 @@ export async function writeAndCommitExactFiles( throw new RenumberError('key-set-divergence', false, { checkCode: 'changed-path-set-mismatch' }); } await options.afterCommit?.(committed); + // Verify the committed bytes before returning: acknowledgement must follow proof, and a + // mismatch here rolls back and surfaces as terminal rather than as a lying success. for (const file of files) { const verified = await readCommittedFile(dir, file.relPath); const matches = diff --git a/apps/worker/src/services/exploit-renderer.ts b/apps/worker/src/services/exploit-renderer.ts index 47b2e174..7df77b85 100644 --- a/apps/worker/src/services/exploit-renderer.ts +++ b/apps/worker/src/services/exploit-renderer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/exploitation-checker.ts b/apps/worker/src/services/exploitation-checker.ts index 79238ccf..8854f72c 100644 --- a/apps/worker/src/services/exploitation-checker.ts +++ b/apps/worker/src/services/exploitation-checker.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/findings-renderer.ts b/apps/worker/src/services/findings-renderer.ts index 33fda53f..07057a10 100644 --- a/apps/worker/src/services/findings-renderer.ts +++ b/apps/worker/src/services/findings-renderer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/git-manager.ts b/apps/worker/src/services/git-manager.ts index 6d2b5213..286313d7 100644 --- a/apps/worker/src/services/git-manager.ts +++ b/apps/worker/src/services/git-manager.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -180,6 +180,9 @@ export async function withGitRepoLock(operation: () => Promise): Promise { diff --git a/apps/worker/src/services/prompt-manager.ts b/apps/worker/src/services/prompt-manager.ts index 89b7040d..ae127a48 100644 --- a/apps/worker/src/services/prompt-manager.ts +++ b/apps/worker/src/services/prompt-manager.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -170,6 +170,10 @@ interface IncludeReplacement { } // Pure function: Build complete login instructions from config +// +// Username, password, TOTP secret, and email-login credentials are substituted directly into +// the returned string, which only ever lives in process memory on its way into the prompt sent +// to the model. Nothing in this function writes credentials to a file. async function buildLoginInstructions( authentication: Authentication, logger: ActivityLogger, @@ -306,6 +310,10 @@ function replaceLiteral(input: string, pattern: RegExp | string, replacement: st return input.replace(pattern, () => replacement); } +// Deliberately omits password, TOTP secret, and email-login credentials: this block is +// background context for the agent's prompt header, not the login mechanism itself. The +// actual secret values are only ever interpolated into {{LOGIN_INSTRUCTIONS}} via +// buildLoginInstructions, so a secret is never duplicated into this second location. function buildAuthContext(config: DistributedConfig | null): string { if (!config?.authentication) { return 'No authentication configured - unauthenticated testing only'; diff --git a/apps/worker/src/services/queue-validation.ts b/apps/worker/src/services/queue-validation.ts index 67ac1ab6..ea717c9a 100644 --- a/apps/worker/src/services/queue-validation.ts +++ b/apps/worker/src/services/queue-validation.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/recon-renderer.ts b/apps/worker/src/services/recon-renderer.ts index 9b00be9e..e5506342 100644 --- a/apps/worker/src/services/recon-renderer.ts +++ b/apps/worker/src/services/recon-renderer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/report-json-adapter.ts b/apps/worker/src/services/report-json-adapter.ts index 8a97133a..cd92c42c 100644 --- a/apps/worker/src/services/report-json-adapter.ts +++ b/apps/worker/src/services/report-json-adapter.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/report-output-schema.ts b/apps/worker/src/services/report-output-schema.ts index 8d4555a9..9c778607 100644 --- a/apps/worker/src/services/report-output-schema.ts +++ b/apps/worker/src/services/report-output-schema.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/report-renderer.ts b/apps/worker/src/services/report-renderer.ts index 2d8d6139..fff05d49 100644 --- a/apps/worker/src/services/report-renderer.ts +++ b/apps/worker/src/services/report-renderer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/reporting.ts b/apps/worker/src/services/reporting.ts index 18f115fa..e5258868 100644 --- a/apps/worker/src/services/reporting.ts +++ b/apps/worker/src/services/reporting.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/services/sarif-renderer.ts b/apps/worker/src/services/sarif-renderer.ts index 9ce0216f..2869be85 100644 --- a/apps/worker/src/services/sarif-renderer.ts +++ b/apps/worker/src/services/sarif-renderer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -176,6 +176,9 @@ function buildMessageMarkdown(finding: AddFindingInput): string { parts.push('', '**Remediation**', '', finding.remediation); // Exploitation steps and proof of impact are deliberately absent: SARIF has no structural home // for them, and flattening them into prose would imply this file carries the evidence. + // NOTE: the filename below is not imported from paths.ts (FINAL_REPORT_PDF_FILENAME) because + // it is customer-facing prose, not a path this module reads or writes. It must be kept in sync + // by hand if that constant's value ever changes. parts.push('', 'Full exploitation evidence: `Security-Assessment-Report.pdf`'); return parts.join('\n'); } diff --git a/apps/worker/src/services/validate-authentication.ts b/apps/worker/src/services/validate-authentication.ts index 3d5536b7..54ecf6f0 100644 --- a/apps/worker/src/services/validate-authentication.ts +++ b/apps/worker/src/services/validate-authentication.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -125,6 +125,12 @@ export async function validateAuthentication( loginType: authentication.login_type, }); + // This is the one place in the pipeline that performs a real login and persists the resulting + // browser session (cookies/storage) to disk, so downstream agents can reuse it instead of + // logging in again. Remove any file left by a prior attempt first: verifySavedAuthState below + // trusts the file's mere presence as proof this run's login succeeded, so a stale leftover + // would let a failed attempt look like a success. The file itself is deleted again when the + // workflow ends, so an authenticated session never survives between scans. const stateFile = authStateFile(auditSession.sessionMetadata); await rm(stateFile, { force: true }); diff --git a/apps/worker/src/services/vuln-renderer.ts b/apps/worker/src/services/vuln-renderer.ts index 70daa153..010b5302 100644 --- a/apps/worker/src/services/vuln-renderer.ts +++ b/apps/worker/src/services/vuln-renderer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/session-manager.ts b/apps/worker/src/session-manager.ts index 3e728d07..0a4b1e31 100644 --- a/apps/worker/src/session-manager.ts +++ b/apps/worker/src/session-manager.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/temporal/activities.ts b/apps/worker/src/temporal/activities.ts index a2fd7d80..9bf33e4f 100644 --- a/apps/worker/src/temporal/activities.ts +++ b/apps/worker/src/temporal/activities.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/temporal/activity-logger.ts b/apps/worker/src/temporal/activity-logger.ts index bcfe2b99..c34ca113 100644 --- a/apps/worker/src/temporal/activity-logger.ts +++ b/apps/worker/src/temporal/activity-logger.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/temporal/summary-mapper.ts b/apps/worker/src/temporal/summary-mapper.ts index ae428585..8fc7fc06 100644 --- a/apps/worker/src/temporal/summary-mapper.ts +++ b/apps/worker/src/temporal/summary-mapper.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/temporal/worker.ts b/apps/worker/src/temporal/worker.ts index d3f5a574..f8f057d9 100644 --- a/apps/worker/src/temporal/worker.ts +++ b/apps/worker/src/temporal/worker.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/temporal/workflow-errors.ts b/apps/worker/src/temporal/workflow-errors.ts index d7db4f8d..b567725a 100644 --- a/apps/worker/src/temporal/workflow-errors.ts +++ b/apps/worker/src/temporal/workflow-errors.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -62,6 +62,11 @@ const REMEDIATION_HINTS: Record = { PipelineFailedError: 're-run the same -w to retry from the last checkpoint.', }; +/** + * Every message a terminal scan failure can show. Closed on purpose: an ApplicationFailure's + * own `.message` can carry raw activity or provider detail, so it is never surfaced directly. + * A type absent from this record falls back to one generic sentence instead. + */ const SAFE_WORKFLOW_FAILURE_MESSAGES: Readonly> = { AuthenticationError: 'Provider authentication failed.', ConfigurationError: 'The scan configuration is invalid.', diff --git a/apps/worker/src/temporal/workflows.ts b/apps/worker/src/temporal/workflows.ts index c1371de6..b8c93605 100644 --- a/apps/worker/src/temporal/workflows.ts +++ b/apps/worker/src/temporal/workflows.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/types/activity-logger.ts b/apps/worker/src/types/activity-logger.ts index ff2ae3f8..c6254d0c 100644 --- a/apps/worker/src/types/activity-logger.ts +++ b/apps/worker/src/types/activity-logger.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/types/agents.ts b/apps/worker/src/types/agents.ts index 9044f16f..ceff633b 100644 --- a/apps/worker/src/types/agents.ts +++ b/apps/worker/src/types/agents.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -61,7 +61,9 @@ export type VulnType = VulnClass; * Decision returned by queue validation for exploitation phase. */ export interface ExploitationDecision { + /** True when the class's exploitation queue has at least one vulnerability to process. */ shouldExploit: boolean; + /** Currently always false; queue validation failures are surfaced as thrown errors instead. */ shouldRetry: boolean; vulnerabilityCount: number; vulnType: VulnType; diff --git a/apps/worker/src/types/audit.ts b/apps/worker/src/types/audit.ts index 25d64b22..362deeef 100644 --- a/apps/worker/src/types/audit.ts +++ b/apps/worker/src/types/audit.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/types/config.ts b/apps/worker/src/types/config.ts index acbd7f46..e3b206a0 100644 --- a/apps/worker/src/types/config.ts +++ b/apps/worker/src/types/config.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -8,6 +8,9 @@ * Configuration type definitions */ +// Every variant but `code_path` scopes network requests (URL/method/header/parameter matching). +// `code_path` is enforced by a different mechanism entirely: it becomes a permission-system deny +// rule so an avoided path is blocked from every tool and child session, not just outbound traffic. export type RuleType = 'url_path' | 'subdomain' | 'domain' | 'method' | 'header' | 'parameter' | 'code_path'; export interface Rule { diff --git a/apps/worker/src/types/deliverables.ts b/apps/worker/src/types/deliverables.ts index ea9c0c31..3f020092 100644 --- a/apps/worker/src/types/deliverables.ts +++ b/apps/worker/src/types/deliverables.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -8,6 +8,10 @@ * Deliverable Type Definitions * * Maps deliverable types to their filenames for the save-deliverable CLI. + * + * This covers only the phases whose pipeline-testing prompts write a markdown deliverable + * by invoking that CLI directly. Exploitation and reporting have no entries here because their + * structured output goes through the collector tool contract instead, in every prompt mode. */ export enum DeliverableType { diff --git a/apps/worker/src/types/errors.ts b/apps/worker/src/types/errors.ts index 5478684e..9d45992c 100644 --- a/apps/worker/src/types/errors.ts +++ b/apps/worker/src/types/errors.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -63,6 +63,8 @@ export function isProviderFailureCategory(value: unknown): value is ProviderFail } export interface ProviderFailure { + // The exhaustive set of shapes a model-provider failure can take at this boundary: bad + // credentials, bad provider/model configuration, or everything else the agent run raised. readonly type: 'AuthenticationError' | 'ConfigurationError' | 'AgentExecutionError'; readonly category: ProviderFailureCategory; readonly retryable: boolean; diff --git a/apps/worker/src/types/index.ts b/apps/worker/src/types/index.ts index ba166100..4bd98d8b 100644 --- a/apps/worker/src/types/index.ts +++ b/apps/worker/src/types/index.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/types/metrics.ts b/apps/worker/src/types/metrics.ts index 2e445761..e9e32f62 100644 --- a/apps/worker/src/types/metrics.ts +++ b/apps/worker/src/types/metrics.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/types/result.ts b/apps/worker/src/types/result.ts index 9f79b718..e80274de 100644 --- a/apps/worker/src/types/result.ts +++ b/apps/worker/src/types/result.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/utils/browser-agents.ts b/apps/worker/src/utils/browser-agents.ts index aa18544c..0d46e824 100644 --- a/apps/worker/src/utils/browser-agents.ts +++ b/apps/worker/src/utils/browser-agents.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 diff --git a/apps/worker/src/utils/concurrency.ts b/apps/worker/src/utils/concurrency.ts index 4c3c6ce2..7f617e90 100644 --- a/apps/worker/src/utils/concurrency.ts +++ b/apps/worker/src/utils/concurrency.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -51,6 +51,9 @@ export class SessionMutex { // 4. Return unlock that releases the next waiter in the chain return () => { + // Only delete the map entry if it still points at this call's own promise. If a later + // lock() has already replaced it, this unlock is not the current tail, so leave the + // newer entry alone; deleting unconditionally would drop a still-pending waiter. if (this.locks.get(sessionId) === promise) { this.locks.delete(sessionId); } diff --git a/apps/worker/src/utils/file-io.ts b/apps/worker/src/utils/file-io.ts index f77459a2..1f4e3a8f 100644 --- a/apps/worker/src/utils/file-io.ts +++ b/apps/worker/src/utils/file-io.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -32,6 +32,8 @@ export async function ensureDirectory(dirPath: string): Promise { * Guarantees no partial writes or corruption on crash */ export async function atomicWrite(filePath: string, data: object | string): Promise { + // The temp file must sit next to filePath so the rename below stays on one filesystem; + // POSIX only guarantees rename() is atomic within a single filesystem, not across mounts. const tempPath = `${filePath}.tmp`; const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2); @@ -39,10 +41,12 @@ export async function atomicWrite(filePath: string, data: object | string): Prom // Write to temp file await fs.writeFile(tempPath, content, 'utf8'); - // Atomic rename (POSIX guarantee: atomic on same filesystem) + // Atomic rename (POSIX guarantee: atomic on same filesystem). A reader can only ever + // observe the old complete file or the new complete file, never a truncated write. await fs.rename(tempPath, filePath); } catch (error) { - // Clean up temp file on failure + // Clean up temp file on failure. Errors here are swallowed so the original + // write/rename failure is what propagates, not a secondary cleanup failure. try { await fs.unlink(tempPath); } catch { diff --git a/apps/worker/src/utils/formatting.ts b/apps/worker/src/utils/formatting.ts index 3f60d20a..3049d53f 100644 --- a/apps/worker/src/utils/formatting.ts +++ b/apps/worker/src/utils/formatting.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -44,7 +44,10 @@ export function calculatePercentage(part: number, total: number): number { } /** - * Extract agent type from description string for display purposes + * Extract agent type from description string for display purposes. + * Matches on literal substrings from agent display names, so it is cosmetic only: + * a miss just falls through to the generic 'analysis' label instead of affecting + * any pipeline behavior. */ export function extractAgentType(description: string): string { if (description.includes('Pre-recon')) { diff --git a/apps/worker/src/utils/functional.ts b/apps/worker/src/utils/functional.ts index 261f2ec2..54e9c6e1 100644 --- a/apps/worker/src/utils/functional.ts +++ b/apps/worker/src/utils/functional.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -16,6 +16,10 @@ type PipelineFunction = (x: any) => any | Promise; /** * Async pipeline that passes result through a series of functions. * Clearer than reduce-based pipe and easier to debug. + * + * There is no per-step try/catch: a thrown error or rejected promise from any function + * stops the pipeline immediately and propagates to the caller, skipping the remaining + * functions. Callers that need partial-failure handling must do it inside a step. */ export async function asyncPipe(initial: unknown, ...fns: PipelineFunction[]): Promise { let result = initial; diff --git a/apps/worker/src/utils/glob.ts b/apps/worker/src/utils/glob.ts index b27b2104..5a1d2d2e 100644 --- a/apps/worker/src/utils/glob.ts +++ b/apps/worker/src/utils/glob.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 @@ -6,6 +6,12 @@ import { glob } from 'zx'; +/** + * Whether a code_path rule value contains wildcard syntax (e.g. `*`, `**`, `{a,b}`) rather + * than naming a single literal path. Used to tag rules as `[GLOB]` vs `[FILE]` when rendering + * them into agent prompts and permission-system deny config, since the two need different + * matching behavior downstream. + */ export function isGlobPattern(value: string): boolean { return glob.isDynamicPattern(value); } diff --git a/apps/worker/src/utils/metrics.ts b/apps/worker/src/utils/metrics.ts index 55033db2..1dc96763 100644 --- a/apps/worker/src/utils/metrics.ts +++ b/apps/worker/src/utils/metrics.ts @@ -1,9 +1,10 @@ -// Copyright (C) 2025 Keygraph, Inc. +// Copyright (C) 2026 Keygraph, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License version 3 // as published by the Free Software Foundation. +/** Wall-clock timer. Safe to call duration() before stop() to read elapsed time so far. */ export class Timer { name: string; startTime: number; @@ -19,6 +20,8 @@ export class Timer { return this.duration(); } + // Falls back to the current time when the timer hasn't been stopped yet, so callers + // can poll an in-flight timer without needing to stop it first. duration(): number { const end = this.endTime || Date.now(); return end - this.startTime; diff --git a/docs/configuration.md b/docs/configuration.md index 37afb970..01e80514 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -144,7 +144,7 @@ report: sarif: "false" ``` -Each finding becomes one SARIF result, filed under a rule per vulnerability class (`shannon/injection`, `shannon/xss`, `shannon/auth`, `shannon/authz`, `shannon/ssrf`, and `shannon/other` for findings outside those classes) and tagged with its OWASP Top Ten 2025 category. Results are anchored to the code location the analysis phase recorded, falling back to the HTTP entry point when the finding names no file. Severity maps onto SARIF's three levels: `critical` and `high` become `error`, `medium` becomes `warning`, everything else becomes `note`. +Each finding becomes one SARIF result, filed under a rule per vulnerability class (`shannon/injection`, `shannon/xss`, `shannon/auth`, `shannon/authz`, `shannon/ssrf`, and `shannon/miscellaneous` for findings outside those classes) and tagged with its OWASP Top Ten 2025 category. Results are anchored to the code location the analysis phase recorded, falling back to the HTTP entry point when the finding names no file. Severity maps onto SARIF's three levels: `critical` and `high` become `error`, `medium` becomes `warning`, everything else becomes `note`. If the SARIF log cannot be written, the JSON and Markdown reports are still produced and the scan finishes as "partial". diff --git a/docs/development.md b/docs/development.md index a4a975ae..08ec8b1f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -57,12 +57,14 @@ The target repository is mounted read-only inside the worker container. Monitor progress: ```bash -npx @keygraph/shannon logs -npx @keygraph/shannon status +npx @keygraph/shannon logs [] # defaults to the single running scan, else the most recent +npx @keygraph/shannon status [] # same default target; add --json for a machine-readable snapshot npx @keygraph/shannon scans npx @keygraph/shannon version ``` +With no workspace, `logs` and `status` follow the single running scan; when several are running, name one. + Source-build equivalents: ```bash @@ -89,7 +91,7 @@ open http://localhost:8233 Stop Shannon: ```bash -npx @keygraph/shannon stop # stop one scan (confirms first; add --yes/-y to skip) +npx @keygraph/shannon stop [] # stop one scan (defaults to the single running scan; confirms first; add --yes/-y to skip) npx @keygraph/shannon stop --all # stop all scans (Temporal stays up) npx @keygraph/shannon reset # stop everything and wipe all Temporal data (type 'confirm' to proceed; cannot be skipped) ``` @@ -97,7 +99,7 @@ npx @keygraph/shannon reset # stop everything and wipe all Temporal Source-build equivalents: ```bash -./shannon stop # stop one scan (confirms first; add --yes/-y to skip) +./shannon stop [] # stop one scan (defaults to the single running scan; confirms first; add --yes/-y to skip) ./shannon stop --all # stop all scans (Temporal stays up) ./shannon reset # stop everything and wipe all Temporal data (type 'confirm' to proceed; cannot be skipped) ``` @@ -120,7 +122,7 @@ npx @keygraph/shannon start -u https://example.com -r /path/to/repo -w q1-audit # Stream the log until the scan finishes, then exit on its outcome (useful in CI). npx @keygraph/shannon start -u https://example.com -r /path/to/repo --follow -# List completed scans. +# List running and completed scans. npx @keygraph/shannon scans ``` diff --git a/docs/workspaces.md b/docs/workspaces.md index d9b5682e..5d555cd3 100644 --- a/docs/workspaces.md +++ b/docs/workspaces.md @@ -36,7 +36,7 @@ Resume an auto-named workspace: npx @keygraph/shannon start -u https://example.com -r /path/to/repo -w example-com_shannon-1771007534808 ``` -List completed scans: +List running and completed scans: ```bash npx @keygraph/shannon scans diff --git a/llms-full.txt b/llms-full.txt index 71237989..8acae559 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # Shannon Full LLM Context -> Combined README and documentation for AI agents and LLMs. Generated from repository Markdown files. For the concise index, see [llms.txt](llms.txt). +> Combined README and documentation for AI agents and LLMs. This file is a hand-maintained copy of the repository Markdown files, updated by hand whenever those files change. For the concise index, see [llms.txt](llms.txt). --- @@ -48,6 +48,7 @@ It analyzes your source code, identifies attack paths, and executes real exploit - [Documentation](#documentation) - [Safety, Scope, and Limitations](#safety-scope-and-limitations) - [License](#license) +- [Acknowledgements](#acknowledgements) - [About Keygraph](#about-keygraph) - [Community and Support](#community-and-support) - [Common Questions](#common-questions) @@ -239,6 +240,14 @@ Commercial and enterprise licensing is available for organizations that need dif For commercial licensing, contact [shannon@keygraph.io](mailto:shannon@keygraph.io). +## Acknowledgements + +Thanks to [Pi](https://github.com/earendil-works/pi), +[Playwright CLI](https://github.com/microsoft/playwright-cli), +and [Mantis](https://github.com/google/mantis). + +See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for licensing and attribution details. + ## About Keygraph **Keygraph** is the company behind Shannon. It also builds the **Keygraph platform**, the commercial agentic pentesting product that closes the full AppSec lifecycle and runs an enhanced build of Shannon as its pentesting engine. @@ -266,10 +275,6 @@ Stay connected: ## Common Questions -### Is Shannon free? - -Yes. Shannon Open Source is free and licensed under AGPL-3.0. You run it yourself from the command line. Your only cost is the AI provider credits you supply. - ### Can I self-host Shannon? Yes. Shannon Open Source runs entirely on your own infrastructure in an ephemeral Docker container. Your source code is mounted read-only and never leaves your environment. @@ -294,10 +299,6 @@ Technically yes, but it is not recommended. Shannon works with local models serv Shannon executes real exploits. It reports a finding only when it has produced a working proof-of-concept, and discards hypotheses it cannot prove. It is a pentester, not a scanner. -### Is Shannon free for startups and nonprofits? - -Shannon Open Source is free for everyone. In addition, the Keygraph Community Program gives eligible nonprofits and early-stage startups free access to the commercial Keygraph platform. See [keygraph.io](https://keygraph.io). -

Built by Keygraph

@@ -365,21 +366,31 @@ The target repository is mounted read-only inside the worker container. Monitor progress: ```bash -npx @keygraph/shannon logs -npx @keygraph/shannon status +npx @keygraph/shannon logs [] # defaults to the single running scan, else the most recent +npx @keygraph/shannon status [] # same default target; add --json for a machine-readable snapshot npx @keygraph/shannon scans npx @keygraph/shannon version ``` +With no workspace, `logs` and `status` follow the single running scan; when several are running, name one. + Source-build equivalents: ```bash -./shannon logs -./shannon status +./shannon logs [] # the combined live log (unchanged default) +./shannon logs [] --agent # tail one agent's own log +./shannon logs [] --list-agents # list the agents with their own log +./shannon status [] ./shannon scans ./shannon version ``` +Every scan writes one combined `.shannon/workflow.log` and a per-agent projection of it under +`.shannon/agents/`: one file per pipeline agent (`recon.log`, `xss-vuln.log`, …) and one per Capella +stage (`agentic-sast-research.log`, …). Delegated subagents fold into their parent's file, and a +Capella stage's concurrent sessions share its file with an inline session label. The combined log +stays canonical; the per-agent files are best-effort projections. + Open the Temporal Web UI for detailed monitoring: ```bash @@ -389,7 +400,7 @@ open http://localhost:8233 Stop Shannon: ```bash -npx @keygraph/shannon stop # stop one scan (confirms first; add --yes/-y to skip) +npx @keygraph/shannon stop [] # stop one scan (defaults to the single running scan; confirms first; add --yes/-y to skip) npx @keygraph/shannon stop --all # stop all scans (Temporal stays up) npx @keygraph/shannon reset # stop everything and wipe all Temporal data (type 'confirm' to proceed; cannot be skipped) ``` @@ -397,7 +408,7 @@ npx @keygraph/shannon reset # stop everything and wipe all Temporal Source-build equivalents: ```bash -./shannon stop # stop one scan (confirms first; add --yes/-y to skip) +./shannon stop [] # stop one scan (defaults to the single running scan; confirms first; add --yes/-y to skip) ./shannon stop --all # stop all scans (Temporal stays up) ./shannon reset # stop everything and wipe all Temporal data (type 'confirm' to proceed; cannot be skipped) ``` @@ -420,7 +431,7 @@ npx @keygraph/shannon start -u https://example.com -r /path/to/repo -w q1-audit # Stream the log until the scan finishes, then exit on its outcome (useful in CI). npx @keygraph/shannon start -u https://example.com -r /path/to/repo --follow -# List completed scans. +# List running and completed scans. npx @keygraph/shannon scans ``` @@ -455,7 +466,7 @@ workspaces/{hostname}_{sessionId}/ |-- Security-Assessment-Report.md # the final report (Markdown) `-- .shannon/ # internals |-- deliverables/ # report source, per-phase analysis, queues - |-- agents/ # per-agent logs + |-- agents/ # per-agent log projections, one file per agent/Capella stage |-- prompts/ # rendered prompts |-- scratchpad/ # screenshots, scripts |-- session.json # resume state @@ -612,7 +623,9 @@ report: sarif: "false" ``` -Each finding becomes one SARIF result, filed under a rule per vulnerability class (`shannon/injection`, `shannon/xss`, `shannon/auth`, `shannon/authz`, `shannon/ssrf`) and tagged with its OWASP Top Ten 2025 category. Results are anchored to the code location the analysis phase recorded, falling back to the HTTP entry point when the finding names no file. Severity maps onto SARIF's three levels: `critical` and `high` become `error`, `medium` becomes `warning`, everything else becomes `note`. +Each finding becomes one SARIF result, filed under a rule per vulnerability class (`shannon/injection`, `shannon/xss`, `shannon/auth`, `shannon/authz`, `shannon/ssrf`, and `shannon/miscellaneous` for findings outside those classes) and tagged with its OWASP Top Ten 2025 category. Results are anchored to the code location the analysis phase recorded, falling back to the HTTP entry point when the finding names no file. Severity maps onto SARIF's three levels: `critical` and `high` become `error`, `medium` becomes `warning`, everything else becomes `note`. + +If the SARIF log cannot be written, the JSON and Markdown reports are still produced and the scan finishes as "partial". The log is written only for exploitative runs. `sarif` is ignored when `exploit` is `"false"`. @@ -1001,7 +1014,7 @@ Resume an auto-named workspace: npx @keygraph/shannon start -u https://example.com -r /path/to/repo -w example-com_shannon-1771007534808 ``` -List completed scans: +List running and completed scans: ```bash npx @keygraph/shannon scans @@ -1070,6 +1083,7 @@ For broader coverage, the Keygraph platform adds black-box and white-box agentic A full test run typically takes roughly 1 to 1.5 hours. LLM API costs vary by model pricing, target complexity, selected provider, and concurrency. + --- # File: docs/coverage-roadmap.md