mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-10-03 14:56:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a14c7944d8 | ||
|
|
57c511ff8e | ||
|
|
327c10fd90 | ||
|
|
22b093aac5 |
No files matched your search
@@ -126,7 +126,7 @@ Infra (Temporal) runs via `docker-compose.yml`. Workers are ephemeral `docker ru
|
|||||||
- `docker-compose.yml` — Infra only: `shannon-temporal` (port 7233/8233). Network: `shannon-net`
|
- `docker-compose.yml` — Infra only: `shannon-temporal` (port 7233/8233). Network: `shannon-net`
|
||||||
- `Dockerfile` — 2-stage build (builder + Chainguard Wolfi runtime). Uses pnpm. Entrypoint: `CMD ["node", "apps/worker/dist/temporal/worker.js"]`
|
- `Dockerfile` — 2-stage build (builder + Chainguard Wolfi runtime). Uses pnpm. Entrypoint: `CMD ["node", "apps/worker/dist/temporal/worker.js"]`
|
||||||
- No `docker-compose.docker.yml` — host gateway handled via `--add-host` flag in CLI
|
- No `docker-compose.docker.yml` — host gateway handled via `--add-host` flag in CLI
|
||||||
- `/etc/hosts` forwarding — at worker spawn, `forwardEtcHostsFlags` in `apps/cli/src/docker.ts` reads the host's `/etc/hosts` and emits one `--add-host` flag per valid user-added entry. Loopback IPs (`127.x`, `::1`) are rewritten to `host-gateway`; IPv6 addresses are bracketed. Disable per-scan via `SHANNON_FORWARD_HOSTS=false`. No-op on Windows native (WSL2 reads its own `/etc/hosts` via the Linux path).
|
- `/etc/hosts` forwarding — at worker spawn, `forwardEtcHostsFlags` in `apps/cli/src/docker.ts` reads the host's `/etc/hosts` and emits one `--add-host` flag per valid user-added entry. Loopback IPs (`127.x`, `::1`) are rewritten to `host-gateway`; IPv6 addresses are bracketed. Disable per-scan via `SHANNON_FORWARD_HOSTS=false`. Native Windows is refused at startup (`blockNativeWindows` in `apps/cli/src/index.ts`, pointing to the WSL2 guide in `docs/platforms.md`); WSL2 reads its own `/etc/hosts` via the Linux path.
|
||||||
|
|
||||||
### Worker Package (`apps/worker/`)
|
### Worker Package (`apps/worker/`)
|
||||||
- `apps/worker/src/paths.ts` — Centralized path constants (`PROMPTS_DIR`, `CONFIGS_DIR`, `WORKSPACES_DIR`)
|
- `apps/worker/src/paths.ts` — Centralized path constants (`PROMPTS_DIR`, `CONFIGS_DIR`, `WORKSPACES_DIR`)
|
||||||
@@ -165,7 +165,7 @@ Around those phases:
|
|||||||
- **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`)
|
- **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`)
|
||||||
- **Agentic SAST progress** — Capella runs as a child workflow, so its activities are absent from the parent's `pendingActivities` and invisible to the CLI. The child signals each stage boundary up via `capellaStageProgress` (`apps/worker/src/temporal/shared.ts`); the parent's handler validates the payload and writes the child-supplied `startedAt` and `durationMs` directly to `operationalStages['agentic-sast:<stage>']`, so both the live `getProgress` query and the terminal result carry per-stage rows. Signalling is best-effort and every failure is swallowed — a closed or unreachable parent must never fail a SAST run. `CAPELLA_STAGE_LABELS` in `apps/worker/src/ai/sast/types.ts` is the one label table, shared by the scan log and the status tree; `CAPELLA_PROGRESS_STAGES` omits `export`, which runs no model and so never becomes a row. Scans predating the signal keep the aggregate `agentic-sast` span and render as a bare phase line
|
- **Agentic SAST progress** — Capella runs as a child workflow, so its activities are absent from the parent's `pendingActivities` and invisible to the CLI. The child signals each stage boundary up via `capellaStageProgress` (`apps/worker/src/temporal/shared.ts`); the parent's handler validates the payload and writes the child-supplied `startedAt` and `durationMs` directly to `operationalStages['agentic-sast:<stage>']`, so both the live `getProgress` query and the terminal result carry per-stage rows. Signalling is best-effort and every failure is swallowed — a closed or unreachable parent must never fail a SAST run. `CAPELLA_STAGE_LABELS` in `apps/worker/src/ai/sast/types.ts` is the one label table, shared by the scan log and the status tree; `CAPELLA_PROGRESS_STAGES` omits `export`, which runs no model and so never becomes a row. Scans predating the signal keep the aggregate `agentic-sast` span and render as a bare phase line
|
||||||
- **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
|
- **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=<provider>:<model-id>` (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). A model too new for the pinned pi release does not require an SDK bump: `--models-config <file>` mounts a pi `models.json` read-only at `/app/models.json`. The mount is the entire CLI→worker protocol: nothing is forwarded through the environment, and `modelsConfigPath()` detects the file at that fixed path, exactly as `piAuthPresent()` detects the pi auth mount whose flag is likewise not forwarded (`MODELS_CONFIG_CONTAINER_PATH` in the CLI and `MODELS_CONFIG_PATH` in `apps/worker/src/paths.ts` must stay in sync). `createModelRuntime` always names `modelsPath` explicitly — the mounted path, or **`null` when no config was supplied**, which switches models.json off outright. It is never left to pi's default of `<agent dir>/models.json`, because that dir is shared with the pi auth mount, so a file landing there must not silently contribute model definitions to a scan that did not ask for one. `modelsStorePath` is pinned to the agent dir alongside it, since pi otherwise derives it from `dirname(modelsPath)` and would try to write beside a read-only mount. Custom definitions merge over the built-in catalogue: a matching model id replaces the built-in entry, a new id is added alongside, and `modelOverrides` adjusts a built-in without replacing the provider's list. Omitted fields take pi's defaults (`contextWindow` 128000, `maxTokens` 16384), so a large-context model needs them stated. Credentials are unaffected: `RuntimeCredentialStore` outranks any `apiKey` the file carries, so a snippet pasted from `pi.dev/models` can keep its placeholder key while the real secret stays in the environment — and pi's `!command` config-value form is never reached through `apiKey`. Preflight reports `modelRuntime.getError()` **before** the "model not found" check, because a file that fails to parse, fails schema validation, or composeLine truncated
|
- **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=<provider>:<model-id>` (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 with `allowModelNetwork: true`, so `ModelRuntime.create()` refreshes the model catalogue over the network at scan start and a freshly released model resolves without a `--models-config` file. The fetch is bounded (10s) and falls back to the static catalogue on timeout, so an unreachable catalogue endpoint cannot hang the scan. The refresh does not override a `--models-config`: pi reloads and re-applies that file as a config overlay on every refresh (it reloads `this.config` at the top of `refresh()`), so custom definitions still win over the fetched catalogue; the merge semantics below are unchanged, just layered over a fresher base. `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). A model pi's catalogue does not carry, such as a self-hosted model, is reachable without an SDK bump: `--models-config <file>` mounts a pi `models.json` read-only at `/app/models.json`. The mount is the entire CLI→worker protocol: nothing is forwarded through the environment, and `modelsConfigPath()` detects the file at that fixed path, exactly as `piAuthPresent()` detects the pi auth mount whose flag is likewise not forwarded (`MODELS_CONFIG_CONTAINER_PATH` in the CLI and `MODELS_CONFIG_PATH` in `apps/worker/src/paths.ts` must stay in sync). `createModelRuntime` always names `modelsPath` explicitly — the mounted path, or **`null` when no config was supplied**, which switches models.json off outright. It is never left to pi's default of `<agent dir>/models.json`, because that dir is shared with the pi auth mount, so a file landing there must not silently contribute model definitions to a scan that did not ask for one. `modelsStorePath` is pinned to the agent dir alongside it, since pi otherwise derives it from `dirname(modelsPath)` and would try to write beside a read-only mount. Custom definitions merge over the built-in catalogue: a matching model id replaces the built-in entry, a new id is added alongside, and `modelOverrides` adjusts a built-in without replacing the proviLine truncated
|
||||||
- **Pi Credential Reuse** — `SHANNON_USE_PI_AUTH=1` opts into reusing the host's Pi login, including an `openai-codex` ChatGPT Plus/Pro subscription (`SHANNON_AI_MODEL=openai-codex:<model-id>`) or an `xai` Grok subscription (`SHANNON_AI_MODEL=xai:<model-id>`); the mechanism is provider-agnostic and works for any Pi login. `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.
|
- **Pi Credential Reuse** — `SHANNON_USE_PI_AUTH=1` opts into reusing the host's Pi login, including an `openai-codex` ChatGPT Plus/Pro subscription (`SHANNON_AI_MODEL=openai-codex:<model-id>`) or an `xai` Grok subscription (`SHANNON_AI_MODEL=xai:<model-id>`); the mechanism is provider-agnostic and works for any Pi login. `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. 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/<slug>.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 <name>` (`--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/<slug>.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 <name>` (`--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`)
|
- **Deliverables** — Saved to `.shannon/deliverables/` in the target repo via the `save-deliverable` CLI script (`apps/worker/src/scripts/save-deliverable.ts`)
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ These reports are from Shannon Open Source scans of Photoview 2.4.0, one of the
|
|||||||
|
|
||||||
- **Docker**: required for the worker container.
|
- **Docker**: required for the worker container.
|
||||||
- **Node.js 18+**: required for the recommended `npx` workflow.
|
- **Node.js 18+**: required for the recommended `npx` workflow.
|
||||||
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url), and a model the catalogue does not yet carry can be described with a [custom model configuration](docs/ai-providers.md#custom-model-configuration). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
|
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url), and a model the catalogue does not carry can be described with a [custom model configuration](docs/ai-providers.md#custom-model-configuration). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
|
||||||
- **Cyber safeguards cleared with your provider**: Anthropic and OpenAI apply real-time safeguards to cyber-security workloads, which can interrupt a scan mid-run. Complete their guidance for legitimate security testers before your first run - see [AI providers](docs/ai-providers.md#cyber-safeguards-do-this-before-your-first-scan).
|
- **Cyber safeguards cleared with your provider**: Anthropic and OpenAI apply real-time safeguards to cyber-security workloads, which can interrupt a scan mid-run. Complete their guidance for legitimate security testers before your first run - see [AI providers](docs/ai-providers.md#cyber-safeguards-do-this-before-your-first-scan).
|
||||||
|
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ Yes. Shannon emits SARIF 2.1.0, the OASIS standard format for static analysis re
|
|||||||
|
|
||||||
### Which AI providers does Shannon support?
|
### Which AI providers does Shannon support?
|
||||||
|
|
||||||
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. A model the catalogue does not yet carry, such as one released after Shannon's pinned harness version, runs without waiting for a Shannon release. Describe it in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file and pass it with `--models-config`. Shannon uses a single unified model setting throughout a pentest.
|
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. A model the catalogue does not carry, such as one a router or gateway serves under its own ID, or a self-hosted model, is described in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file and passed with `--models-config`. Shannon uses a single unified model setting throughout a pentest.
|
||||||
|
|
||||||
### Can I run Shannon on a local or self-hosted model?
|
### Can I run Shannon on a local or self-hosted model?
|
||||||
|
|
||||||
|
|||||||
@@ -96,8 +96,6 @@ function loadTOML(): TOMLConfig | null {
|
|||||||
if (!fs.existsSync(configPath)) return null;
|
if (!fs.existsSync(configPath)) return null;
|
||||||
|
|
||||||
// Config contains secrets — refuse to read if group or others have any access.
|
// Config contains secrets — refuse to read if group or others have any access.
|
||||||
// Skip on Windows where POSIX permissions are not supported.
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
const mode = fs.statSync(configPath).mode;
|
const mode = fs.statSync(configPath).mode;
|
||||||
if (mode & 0o077) {
|
if (mode & 0o077) {
|
||||||
const actual = (mode & 0o777).toString(8).padStart(3, '0');
|
const actual = (mode & 0o777).toString(8).padStart(3, '0');
|
||||||
@@ -105,7 +103,6 @@ function loadTOML(): TOMLConfig | null {
|
|||||||
`Your config file is readable by other users on this machine (${actual}). Lock it down: chmod 600 ${configPath}`,
|
`Your config file is readable by other users on this machine (${actual}). Lock it down: chmod 600 ${configPath}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(configPath, 'utf-8');
|
const content = fs.readFileSync(configPath, 'utf-8');
|
||||||
|
|||||||
@@ -360,7 +360,6 @@ function shouldSkipHostsName(name: string, hostname: string): boolean {
|
|||||||
*/
|
*/
|
||||||
function forwardEtcHostsFlags(): string[] {
|
function forwardEtcHostsFlags(): string[] {
|
||||||
if (!envBool('SHANNON_FORWARD_HOSTS', true)) return [];
|
if (!envBool('SHANNON_FORWARD_HOSTS', true)) return [];
|
||||||
if (os.platform() === 'win32') return [];
|
|
||||||
|
|
||||||
let content: string;
|
let content: string;
|
||||||
try {
|
try {
|
||||||
@@ -517,8 +516,6 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
|
|||||||
// ignore stdin/stdout (the container ID is noise).
|
// ignore stdin/stdout (the container ID is noise).
|
||||||
return spawn('docker', args, {
|
return spawn('docker', args, {
|
||||||
stdio: ['ignore', 'ignore', 'inherit'],
|
stdio: ['ignore', 'ignore', 'inherit'],
|
||||||
// Prevent MSYS/Git Bash from converting Unix paths on Windows
|
|
||||||
...(os.platform() === 'win32' && { env: { ...process.env, MSYS_NO_PATHCONV: '1' } }),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,18 @@ function blockSudo(): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Refuse to run on native Windows. WSL2 reports `linux`, so it is unaffected. */
|
||||||
|
function blockNativeWindows(): void {
|
||||||
|
if (process.platform !== 'win32') return;
|
||||||
|
|
||||||
|
failWith(
|
||||||
|
'CLI_PRECONDITION_FAILED',
|
||||||
|
'Shannon does not run on native Windows.',
|
||||||
|
'Run Shannon inside WSL2. Setup instructions:',
|
||||||
|
'https://github.com/KeygraphHQ/shannon/blob/main/docs/platforms.md',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Commands whose `--json` output contract extends to failures. */
|
/** Commands whose `--json` output contract extends to failures. */
|
||||||
const JSON_CAPABLE_COMMANDS = new Set(['status', 'scans', 'version', '--version', '-v']);
|
const JSON_CAPABLE_COMMANDS = new Set(['status', 'scans', 'version', '--version', '-v']);
|
||||||
|
|
||||||
@@ -262,6 +274,7 @@ async function main(): Promise<void> {
|
|||||||
enableJsonErrors();
|
enableJsonErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockNativeWindows();
|
||||||
blockSudo();
|
blockSudo();
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|||||||
@@ -20,7 +20,9 @@
|
|||||||
* Resolution returns a pi `Model` plus the `ModelRuntime` that owns its auth,
|
* Resolution returns a pi `Model` plus the `ModelRuntime` that owns its auth,
|
||||||
* built over an in-memory credential store primed from the environment.
|
* built over an in-memory credential store primed from the environment.
|
||||||
*
|
*
|
||||||
* A model too new for the pinned pi release is reachable by passing its descriptor in a
|
* The catalogue is refreshed over the network at scan start, so a newly released model
|
||||||
|
* on a catalogue provider resolves on its own. A model the catalogue does not carry, such
|
||||||
|
* as a router model under its own id, or a self-hosted server, is described in a
|
||||||
* pi `models.json` (the CLI's `--models-config`), which merges over the catalogue. The
|
* pi `models.json` (the CLI's `--models-config`), which merges over the catalogue. The
|
||||||
* credential store below outranks any `apiKey` that file carries, so it describes the
|
* credential store below outranks any `apiKey` that file carries, so it describes the
|
||||||
* model while the environment still supplies the secret.
|
* model while the environment still supplies the secret.
|
||||||
@@ -216,13 +218,18 @@ function modelsStorePath(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a ModelRuntime whose only credential is the one supplied. Model catalogs
|
* Build a ModelRuntime whose only credential is the one supplied. `allowModelNetwork`
|
||||||
* stay offline (`allowModelNetwork` defaults to false) so a scan never blocks on
|
* refreshes the model catalogue over the network at scan start, so the registry reflects
|
||||||
* a catalog refresh.
|
* models the pinned pi build predates. The fetch is bounded and falls back to the static
|
||||||
|
* catalogue on timeout, so an unreachable endpoint cannot hang the scan. A mounted
|
||||||
|
* `--models-config` overlays the catalogue and is reloaded on every refresh, so its
|
||||||
|
* definitions take precedence.
|
||||||
*
|
*
|
||||||
* `modelsPath` is always explicit, never pi's default of `<agent dir>/models.json`: with no
|
* `modelsPath` is always explicit, never pi's default of `<agent dir>/models.json`: with no
|
||||||
* `--models-config` it is null, which switches models.json off outright, so a stray file in
|
* `--models-config` it is null, which switches models.json off outright, so a stray file in
|
||||||
* that shared dir cannot feed model definitions to a run that did not ask for them.
|
* that shared dir cannot feed model definitions to a run that did not ask for them.
|
||||||
|
* `modelsStorePath` is pinned to the writable agent dir, replacing pi's default
|
||||||
|
* `dirname(modelsPath)` (a read-only mount) as the fetched catalogue's store.
|
||||||
*
|
*
|
||||||
* When the host's pi auth.json is present, the runtime reads it instead: pi's
|
* When the host's pi auth.json is present, the runtime reads it instead: pi's
|
||||||
* disk-backed store resolves the credential. The mount is writable so OAuth
|
* disk-backed store resolves the credential. The mount is writable so OAuth
|
||||||
@@ -233,6 +240,8 @@ export async function createModelRuntime(providerId: string, apiKey: string | un
|
|||||||
const modelSources = {
|
const modelSources = {
|
||||||
modelsPath: modelsPath ?? null,
|
modelsPath: modelsPath ?? null,
|
||||||
...(modelsPath ? { modelsStorePath: modelsStorePath() } : {}),
|
...(modelsPath ? { modelsStorePath: modelsStorePath() } : {}),
|
||||||
|
allowModelNetwork: true,
|
||||||
|
modelRefreshTimeoutMs: 10_000,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (piAuthPresent()) {
|
if (piAuthPresent()) {
|
||||||
@@ -254,9 +263,8 @@ export interface ModelSelection {
|
|||||||
*
|
*
|
||||||
* The model must exist in the runtime's registry, whether or not an endpoint override
|
* The model must exist in the runtime's registry, whether or not an endpoint override
|
||||||
* is in play — a base URL changes the address and nothing else. A gateway serving a
|
* is in play — a base URL changes the address and nothing else. A gateway serving a
|
||||||
* model under its own name, or one newer than the pinned pi release, is described in a
|
* model under its own name is described in a `--models-config` file, which puts a real
|
||||||
* `--models-config` file, which puts a real descriptor in the registry rather than
|
* descriptor in the registry rather than guessing one from an unrelated model.
|
||||||
* guessing one from an unrelated model.
|
|
||||||
*/
|
*/
|
||||||
export function resolveModel(
|
export function resolveModel(
|
||||||
modelRuntime: ModelRuntime,
|
modelRuntime: ModelRuntime,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import type {
|
|||||||
CapellaTool,
|
CapellaTool,
|
||||||
} from './capella-agent-types.js';
|
} from './capella-agent-types.js';
|
||||||
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
||||||
|
import { PI_THINKING_LEVEL } from './thinking-level.js';
|
||||||
|
|
||||||
const MAX_ERROR_LENGTH = 2_000;
|
const MAX_ERROR_LENGTH = 2_000;
|
||||||
const MAX_TOOLS_PER_SESSION = 32;
|
const MAX_TOOLS_PER_SESSION = 32;
|
||||||
@@ -393,6 +394,7 @@ class StandaloneCapellaAgentExecutor implements CapellaAgentExecutor {
|
|||||||
cwd: request.cwd,
|
cwd: request.cwd,
|
||||||
agentDir,
|
agentDir,
|
||||||
model: selection.model,
|
model: selection.model,
|
||||||
|
thinkingLevel: PI_THINKING_LEVEL,
|
||||||
modelRuntime: selection.modelRuntime,
|
modelRuntime: selection.modelRuntime,
|
||||||
noTools: 'all',
|
noTools: 'all',
|
||||||
tools: toolNames,
|
tools: toolNames,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { permissionSystemConfigExists, permissionSystemPackageDir } from './perm
|
|||||||
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
||||||
import { createGlobTool, createTodoWriteTool } from './session-tools.js';
|
import { createGlobTool, createTodoWriteTool } from './session-tools.js';
|
||||||
import { createTaskTool } from './task-tool.js';
|
import { createTaskTool } from './task-tool.js';
|
||||||
|
import { PI_THINKING_LEVEL } from './thinking-level.js';
|
||||||
import { TraceEmitter } from './trace-emitter.js';
|
import { TraceEmitter } from './trace-emitter.js';
|
||||||
import { providerTurnError, type SafeProviderTurnDetails, safeProviderTurnDetails } from './turn-error.js';
|
import { providerTurnError, type SafeProviderTurnDetails, safeProviderTurnDetails } from './turn-error.js';
|
||||||
|
|
||||||
@@ -332,6 +333,7 @@ export async function runPiPrompt(
|
|||||||
({ session } = await createAgentSession({
|
({ session } = await createAgentSession({
|
||||||
cwd: sourceDir,
|
cwd: sourceDir,
|
||||||
model: selection.model,
|
model: selection.model,
|
||||||
|
thinkingLevel: PI_THINKING_LEVEL,
|
||||||
tools,
|
tools,
|
||||||
customTools,
|
customTools,
|
||||||
modelRuntime: selection.modelRuntime,
|
modelRuntime: selection.modelRuntime,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import type { ValidatingSubmitTool } from '../reconciliation/submit-validation.j
|
|||||||
import { ConfinementError, compileRepositoryGlob, RepositoryConfinement } from '../sast/capella/tools/confinement.js';
|
import { ConfinementError, compileRepositoryGlob, RepositoryConfinement } from '../sast/capella/tools/confinement.js';
|
||||||
import { createCapellaRepositoryTools } from '../sast/capella/tools/repository-tools.js';
|
import { createCapellaRepositoryTools } from '../sast/capella/tools/repository-tools.js';
|
||||||
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
||||||
|
import { PI_THINKING_LEVEL } from './thinking-level.js';
|
||||||
|
|
||||||
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1_000;
|
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1_000;
|
||||||
const DEFAULT_MAX_TURNS = 64;
|
const DEFAULT_MAX_TURNS = 64;
|
||||||
@@ -527,6 +528,7 @@ class StandaloneTaskFormationExecutor implements TaskFormationExecutor {
|
|||||||
cwd: request.cwd,
|
cwd: request.cwd,
|
||||||
agentDir,
|
agentDir,
|
||||||
model: selection.model,
|
model: selection.model,
|
||||||
|
thinkingLevel: PI_THINKING_LEVEL,
|
||||||
modelRuntime: selection.modelRuntime,
|
modelRuntime: selection.modelRuntime,
|
||||||
noTools: 'all',
|
noTools: 'all',
|
||||||
tools: toolNames,
|
tools: toolNames,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '@earendil-works/pi-coding-agent';
|
} from '@earendil-works/pi-coding-agent';
|
||||||
import { type LoggableAgentName, normalizeSemanticLabel } from '../../audit/safe-fields.js';
|
import { type LoggableAgentName, normalizeSemanticLabel } from '../../audit/safe-fields.js';
|
||||||
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
import { PI_RETRY_SETTINGS } from './retry-settings.js';
|
||||||
|
import { PI_THINKING_LEVEL } from './thinking-level.js';
|
||||||
import { TraceEmitter } from './trace-emitter.js';
|
import { TraceEmitter } from './trace-emitter.js';
|
||||||
|
|
||||||
export interface TaskToolContext {
|
export interface TaskToolContext {
|
||||||
@@ -135,6 +136,7 @@ export function createTaskTool(config: TaskToolContext): ToolDefinition {
|
|||||||
agentDir,
|
agentDir,
|
||||||
resourceLoader,
|
resourceLoader,
|
||||||
model: config.model,
|
model: config.model,
|
||||||
|
thinkingLevel: PI_THINKING_LEVEL,
|
||||||
tools: CHILD_TOOLS,
|
tools: CHILD_TOOLS,
|
||||||
modelRuntime: config.modelRuntime,
|
modelRuntime: config.modelRuntime,
|
||||||
sessionManager: SessionManager.inMemory(config.cwd),
|
sessionManager: SessionManager.inMemory(config.cwd),
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
|
||||||
|
|
||||||
|
/** Thinking level for every pi agent session, raised above pi's default for deeper analysis. */
|
||||||
|
export const PI_THINKING_LEVEL: ThinkingLevel = 'high';
|
||||||
@@ -374,7 +374,7 @@ async function validateCredentials(logger: ActivityLogger): Promise<Result<void,
|
|||||||
if (!baseModel) {
|
if (!baseModel) {
|
||||||
return err(
|
return err(
|
||||||
new PentestError(
|
new PentestError(
|
||||||
`Model not found in pi registry: provider="${spec.providerId}" model="${spec.modelId}". Check SHANNON_AI_MODEL — browse valid providers and models at ${PI_CATALOG_URL}. A model too new for this pi release can be defined in a model config passed with --models-config.`,
|
`Model not found in pi registry: provider="${spec.providerId}" model="${spec.modelId}". Check SHANNON_AI_MODEL — browse valid providers and models at ${PI_CATALOG_URL}. A model the catalogue does not carry can be defined in a model config passed with --models-config.`,
|
||||||
'config',
|
'config',
|
||||||
false,
|
false,
|
||||||
{ providerId: spec.providerId, modelId: spec.modelId },
|
{ providerId: spec.providerId, modelId: spec.modelId },
|
||||||
|
|||||||
@@ -128,6 +128,9 @@
|
|||||||
text(fill: white, weight: "bold", size: 7.5pt, tracking: 0.3pt, upper(label)),
|
text(fill: white, weight: "bold", size: 7.5pt, tracking: 0.3pt, upper(label)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#let finding-anchor(id) = label("finding-" + id)
|
||||||
|
#let finding-link(id) = link(finding-anchor(id), text(weight: "semibold")[#id])
|
||||||
|
|
||||||
#let categories-in-order = if mode == "exploits" {
|
#let categories-in-order = if mode == "exploits" {
|
||||||
data.exploitedByType.map(entry => entry.category)
|
data.exploitedByType.map(entry => entry.category)
|
||||||
} else {
|
} else {
|
||||||
@@ -338,7 +341,7 @@
|
|||||||
#if "bullets" in entry and entry.bullets != none [
|
#if "bullets" in entry and entry.bullets != none [
|
||||||
#list(
|
#list(
|
||||||
..entry.bullets.map(b => [
|
..entry.bullets.map(b => [
|
||||||
#text(weight: "semibold")[#b.id] — #inline-code(b.description)
|
#finding-link(b.id) — #inline-code(b.description)
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@@ -474,7 +477,7 @@
|
|||||||
..(if show-confidence-col { (text(size: 9.5pt, weight: "semibold")[Confidence],) } else { () }),
|
..(if show-confidence-col { (text(size: 9.5pt, weight: "semibold")[Confidence],) } else { () }),
|
||||||
),
|
),
|
||||||
..data.findings.map(f => (
|
..data.findings.map(f => (
|
||||||
text(weight: "semibold")[#f.id],
|
finding-link(f.id),
|
||||||
inline-code(f.title),
|
inline-code(f.title),
|
||||||
text(size: 9.5pt)[#f.category],
|
text(size: 9.5pt)[#f.category],
|
||||||
sev-chip(f.severity),
|
sev-chip(f.severity),
|
||||||
@@ -531,7 +534,7 @@
|
|||||||
|
|
||||||
#let render-exploit(f) = {
|
#let render-exploit(f) = {
|
||||||
block(breakable: false)[
|
block(breakable: false)[
|
||||||
#heading(level: 2)[#f.id: #inline-code(f.title)]
|
#heading(level: 2)[#f.id: #inline-code(f.title)]#finding-anchor(f.id)
|
||||||
#sev-chip(f.severity)
|
#sev-chip(f.severity)
|
||||||
#v(8pt)
|
#v(8pt)
|
||||||
#render-finding-owasp(f)
|
#render-finding-owasp(f)
|
||||||
@@ -553,7 +556,7 @@
|
|||||||
|
|
||||||
#let render-analysis(f) = {
|
#let render-analysis(f) = {
|
||||||
block(breakable: false)[
|
block(breakable: false)[
|
||||||
#heading(level: 2)[#f.id: #inline-code(f.title)]
|
#heading(level: 2)[#f.id: #inline-code(f.title)]#finding-anchor(f.id)
|
||||||
#sev-chip(f.severity)
|
#sev-chip(f.severity)
|
||||||
#h(4pt)
|
#h(4pt)
|
||||||
#confidence-chip(f.confidence)
|
#confidence-chip(f.confidence)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com # optional: route thr
|
|||||||
|
|
||||||
This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported.
|
This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported.
|
||||||
|
|
||||||
A model the catalogue does not yet carry, such as one released after Shannon's pinned Pi version, is reachable by describing it yourself. See [Custom model configuration](#custom-model-configuration).
|
A model the catalogue does not carry is reachable by describing it yourself. See [Custom model configuration](#custom-model-configuration).
|
||||||
|
|
||||||
`npx @keygraph/shannon setup` exposes this as the **Other provider** option.
|
`npx @keygraph/shannon setup` exposes this as the **Other provider** option.
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com/v1
|
|||||||
|
|
||||||
## Custom model configuration
|
## Custom model configuration
|
||||||
|
|
||||||
A model released after Shannon's pinned Pi version is not in the harness catalogue yet, so `SHANNON_AI_MODEL` alone cannot reach it. Rather than wait for a Shannon release, describe the model yourself and pass the file with `--models-config`:
|
A custom model configuration is a Pi `models.json` file that describes a model the harness catalogue does not carry: one a router or gateway serves under its own ID, or a local server (see [Local and self-hosted models](#local-and-self-hosted-models)). You pass it with `--models-config`, and Shannon merges its definitions over the catalogue so `SHANNON_AI_MODEL` can then name the model like any other:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @keygraph/shannon start -u https://example.com -r /path/to/repo --models-config ./models.json
|
npx @keygraph/shannon start -u https://example.com -r /path/to/repo --models-config ./models.json
|
||||||
|
|||||||
+4
-4
@@ -139,7 +139,7 @@ These reports are from Shannon Open Source scans of Photoview 2.4.0, one of the
|
|||||||
|
|
||||||
- **Docker**: required for the worker container.
|
- **Docker**: required for the worker container.
|
||||||
- **Node.js 18+**: required for the recommended `npx` workflow.
|
- **Node.js 18+**: required for the recommended `npx` workflow.
|
||||||
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url), and a model the catalogue does not yet carry can be described with a [custom model configuration](docs/ai-providers.md#custom-model-configuration). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
|
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url), and a model the catalogue does not carry can be described with a [custom model configuration](docs/ai-providers.md#custom-model-configuration). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
|
||||||
- **Cyber safeguards cleared with your provider**: Anthropic and OpenAI apply real-time safeguards to cyber-security workloads, which can interrupt a scan mid-run. Complete their guidance for legitimate security testers before your first run - see [AI providers](docs/ai-providers.md#cyber-safeguards-do-this-before-your-first-scan).
|
- **Cyber safeguards cleared with your provider**: Anthropic and OpenAI apply real-time safeguards to cyber-security workloads, which can interrupt a scan mid-run. Complete their guidance for legitimate security testers before your first run - see [AI providers](docs/ai-providers.md#cyber-safeguards-do-this-before-your-first-scan).
|
||||||
|
|
||||||
|
|
||||||
@@ -383,7 +383,7 @@ Yes. Shannon emits SARIF 2.1.0, the OASIS standard format for static analysis re
|
|||||||
|
|
||||||
### Which AI providers does Shannon support?
|
### Which AI providers does Shannon support?
|
||||||
|
|
||||||
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. A model the catalogue does not yet carry, such as one released after Shannon's pinned harness version, runs without waiting for a Shannon release. Describe it in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file and pass it with `--models-config`. Shannon uses a single unified model setting throughout a pentest.
|
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. A model the catalogue does not carry, such as one a router or gateway serves under its own ID, or a self-hosted model, is described in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file and passed with `--models-config`. Shannon uses a single unified model setting throughout a pentest.
|
||||||
|
|
||||||
### Can I run Shannon on a local or self-hosted model?
|
### Can I run Shannon on a local or self-hosted model?
|
||||||
|
|
||||||
@@ -792,7 +792,7 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com # optional: route thr
|
|||||||
|
|
||||||
This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported.
|
This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported.
|
||||||
|
|
||||||
A model the catalogue does not yet carry, such as one released after Shannon's pinned Pi version, is reachable by describing it yourself. See [Custom model configuration](#custom-model-configuration).
|
A model the catalogue does not carry is reachable by describing it yourself. See [Custom model configuration](#custom-model-configuration).
|
||||||
|
|
||||||
`npx @keygraph/shannon setup` exposes this as the **Other provider** option.
|
`npx @keygraph/shannon setup` exposes this as the **Other provider** option.
|
||||||
|
|
||||||
@@ -895,7 +895,7 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com/v1
|
|||||||
|
|
||||||
## Custom model configuration
|
## Custom model configuration
|
||||||
|
|
||||||
A model released after Shannon's pinned Pi version is not in the harness catalogue yet, so `SHANNON_AI_MODEL` alone cannot reach it. Rather than wait for a Shannon release, describe the model yourself and pass the file with `--models-config`:
|
A custom model configuration is a Pi `models.json` file that describes a model the harness catalogue does not carry: one a router or gateway serves under its own ID, or a local server (see [Local and self-hosted models](#local-and-self-hosted-models)). You pass it with `--models-config`, and Shannon merges its definitions over the catalogue so `SHANNON_AI_MODEL` can then name the model like any other:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @keygraph/shannon start -u https://example.com -r /path/to/repo --models-config ./models.json
|
npx @keygraph/shannon start -u https://example.com -r /path/to/repo --models-config ./models.json
|
||||||
|
|||||||
Reference in new issue
Block a user