diff --git a/.env.example b/.env.example index 54f0d43e..6e20880f 100644 --- a/.env.example +++ b/.env.example @@ -22,21 +22,15 @@ SHANNON_AI_MODEL=anthropic:claude-sonnet-4-6 # SHANNON_AI_MODEL=amazon-bedrock:us.anthropic.claude-opus-4-8 # --- Custom Base URL --------------------------------------------------------- -# Route through a proxy or gateway (LiteLLM, an internal endpoint). -# Pick the block matching the API dialect your gateway speaks, and uncomment all -# three lines. The provider prefix picks the dialect; the model id is whatever -# name your gateway serves it under. - -# Anthropic compatible - Anthropic Messages: +# Anthropic Messages API: # SHANNON_AI_API_KEY=your-gateway-key-here # SHANNON_AI_BASE_URL=https://llm-gateway.example.com # SHANNON_AI_MODEL=anthropic:claude-sonnet-4-6 -# OpenAI compatible - Chat Completions (default) or Responses: +# OpenAI Responses API: # SHANNON_AI_API_KEY=your-gateway-key-here # SHANNON_AI_BASE_URL=https://llm-gateway.example.com/v1 # SHANNON_AI_MODEL=openai:gpt-5.5 -# SHANNON_AI_OPENAI_FORMAT=responses # --- Other provider ---------------------------------------------------------- # Any other provider the Pi harness supports. Name it in SHANNON_AI_MODEL and @@ -44,6 +38,8 @@ SHANNON_AI_MODEL=anthropic:claude-sonnet-4-6 # and model at preflight. # SHANNON_AI_MODEL=openrouter:moonshotai/kimi-k3 # SHANNON_AI_API_KEY=your-api-key-here +# Optional: point that provider at a proxy or LLM gateway. +# SHANNON_AI_BASE_URL=https://llm-gateway.example.com # --- Misc -------------------------------------------------------------------- # Forward /etc/hosts entries into the worker container. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5032abd1..ac34c39d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -121,8 +121,8 @@ body: - "xAI" - "AWS Bedrock" - "Custom base URL - Anthropic Messages" - - "Custom base URL - OpenAI Chat Completions" - "Custom base URL - OpenAI Responses" + - "Other provider (Pi catalogue)" validations: required: true diff --git a/CLAUDE.md b/CLAUDE.md index 45f268f1..3782e48e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`) - **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:']`, 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 -- **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 +- **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 and API dialect are unchanged. A base URL only changes the address — `resolveModel` (`apps/worker/src/ai/models.ts`) carries it onto the model descriptor and nothing else. pi's builtin `openai` provider serves a single API (Responses) and dispatches on the provider rather than `model.api`, so an `openai:` gateway run always speaks Responses; a Chat-Completions-only gateway must be reached through a completions-native provider such as `openrouter` instead. `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 (`SHANNON_AI_MODEL=openai-codex:`) or an `xai` Grok subscription (`SHANNON_AI_MODEL=xai:`); 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/.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`) diff --git a/README.md b/README.md index 76bb4ba3..c9bf9a97 100644 --- a/README.md +++ b/README.md @@ -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. - **Node.js 18+**: required for the recommended `npx` workflow. -- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue, and any endpoint that speaks the Anthropic Messages API or the OpenAI Chat Completions or Responses API through a [custom base URL](docs/ai-providers.md#custom-base-url). 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). 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). @@ -175,7 +175,7 @@ For source builds, authenticated scans, provider-specific setup, and platform no - **Resumable workspaces**: Resumes interrupted scans without repeating completed work. - **Native CI/CD integrations**: Runs through the official GitHub Action or GitLab CI/CD component, preserves artifacts, publishes findings, and gates releases on proven vulnerabilities. - **Multi-format reports**: Produces evidence-rich PDF and Markdown reports plus JSON and SARIF 2.1.0. SARIF is enabled by default for exploit-mode scans. -- **Provider agnostic and BYOK**: Supports Anthropic, OpenAI, xAI, AWS Bedrock, compatible APIs and gateways, and local models served through Ollama, vLLM, or LM Studio. +- **Provider agnostic and BYOK**: Supports Anthropic, OpenAI, xAI, AWS Bedrock, compatible APIs and LLM gateways, and local models served through Ollama, vLLM, or LM Studio. - **Private by design**: Runs in your infrastructure, stores results locally, and sends model requests directly to your chosen endpoint. A local endpoint keeps data inside your environment. @@ -286,7 +286,7 @@ Use these guides for operational detail: | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Source build and CLI commands](docs/development.md) | Cloning, building, common commands, output paths, and local development. | | [Configuration](docs/configuration.md) | Authenticated testing, login flows, rules of engagement, and report filters. | -| [AI providers](docs/ai-providers.md) | Selecting the model, the supported providers (Anthropic, OpenAI, xAI, AWS Bedrock, and any other Pi-supported provider), and custom gateways. | +| [AI providers](docs/ai-providers.md) | Selecting the model, the supported providers (Anthropic, OpenAI, xAI, AWS Bedrock, and any other Pi-supported provider), and custom LLM gateways. | | [Platforms and networking](docs/platforms.md) | Windows/WSL2, Linux, macOS, Docker networking, local apps, and custom hostnames. | | [Workspaces and resuming](docs/workspaces.md) | Naming workspaces, resuming interrupted scans, and workspace storage. | | [Safety and limitations](docs/safety.md) | Authorized-use requirements, non-production guidance, mutative effects, cost, and model caveats. | @@ -306,7 +306,7 @@ Important limitations: - Shannon Open Source is tuned for fast, code-informed pentesting in everyday development and CI/CD. Exhaustive agentic SAST, broader scanner coverage, centralized governance, and full-lifecycle vulnerability management are delivered through the Keygraph Enterprise Platform. - Findings still require human review. LLM-generated reports can contain weakly supported or incorrect details. -- Anthropic, OpenAI, xAI, and AWS Bedrock are built-in providers, and any Anthropic Messages API or OpenAI Chat Completions or Responses API endpoint works through a custom base URL. Model capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker results. +- Anthropic, OpenAI, xAI, and AWS Bedrock are built-in providers, and any other provider in the harness catalogue works too — each reachable through a custom base URL that points it at a proxy or LLM gateway. Model capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker results. - A full run can take roughly 1 to 1.5 hours and may incur LLM API costs depending on model pricing and application complexity. - Do not scan untrusted or adversarial codebases. AI-powered tools that read source code can be exposed to prompt injection. @@ -375,11 +375,11 @@ Yes. Shannon emits SARIF 2.1.0, the OASIS standard format for static analysis re ### 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 endpoint that implements the Anthropic Messages API or the OpenAI Chat Completions or Responses API, reached through a custom base URL. The rule is the API format, not the vendor. 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 `:` 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. Shannon uses a single unified model setting throughout a pentest. ### Can I run Shannon on a local or self-hosted model? -Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and gateways such as LiteLLM. Point Shannon at the endpoint with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [AI providers](docs/ai-providers.md#custom-base-url). +Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and LLM gateways such as LiteLLM. Point Shannon at the endpoint with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [AI providers](docs/ai-providers.md#custom-base-url). ### Does Shannon actually exploit vulnerabilities, or just scan? diff --git a/apps/cli/README.md b/apps/cli/README.md index 541103a5..12728970 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -22,7 +22,7 @@ It analyzes your source code, identifies attack paths, and executes real exploit - **Docker**: required for the worker container. - **Node.js 18+**: required for the recommended `npx` workflow. -- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, any other provider in the harness catalogue, and any endpoint that speaks the Anthropic Messages API or the OpenAI Chat Completions or Responses API through a custom base URL. You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. +- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and any other provider in the harness catalogue — each of which you can point at a proxy or LLM gateway through a custom base URL. You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. - **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. ### Run Shannon diff --git a/apps/cli/src/commands/setup.ts b/apps/cli/src/commands/setup.ts index 6555880a..3208c184 100644 --- a/apps/cli/src/commands/setup.ts +++ b/apps/cli/src/commands/setup.ts @@ -10,7 +10,7 @@ import os from 'node:os'; import path from 'node:path'; import * as p from '@clack/prompts'; import { type ShannonConfig, saveConfig } from '../config/writer.js'; -import { CURATED_PROVIDERS, type CuratedProviderId, isCuratedProvider, type OpenAiFormat } from '../model-spec.js'; +import { CURATED_PROVIDERS, type CuratedProviderId, isCuratedProvider } from '../model-spec.js'; import { displaySplash } from '../splash.js'; import { requireInteractive } from '../tty.js'; import { getVersion } from '../version.js'; @@ -22,24 +22,16 @@ const CUSTOM_BASE_URL = '__custom_base_url__'; const OTHER_PROVIDER = '__other_provider__'; /** - * Wire formats reachable through the gateway route. The format picks the provider - * that supplies the credential, and for OpenAI it also picks which of the two - * OpenAI APIs Shannon calls. + * API dialects reachable through the gateway route. The dialect picks the provider + * that supplies the credential and names the wire protocol the endpoint must speak. */ const GATEWAY_DIALECTS: readonly { value: string; label: string; provider: 'anthropic' | 'openai'; - format?: OpenAiFormat; }[] = [ { value: 'anthropic', label: 'Anthropic Messages', provider: 'anthropic' }, - { - value: 'openai-chat-completions', - label: 'OpenAI Chat Completions', - provider: 'openai', - format: 'chat-completions', - }, - { value: 'openai-responses', label: 'OpenAI Responses', provider: 'openai', format: 'responses' }, + { value: 'openai', label: 'OpenAI Responses', provider: 'openai' }, ]; /** Suggested models per curated provider, best-first. Free-text entry accepts any model in the provider's catalogue. */ @@ -78,7 +70,11 @@ export async function setup(): Promise { { value: 'openai' as const, label: 'OpenAI', hint: 'GPT models' }, { value: 'xai' as const, label: 'xAI', hint: 'Grok models' }, { value: 'amazon-bedrock' as const, label: 'AWS Bedrock', hint: 'Claude models via AWS' }, - { value: CUSTOM_BASE_URL as typeof CUSTOM_BASE_URL, label: 'Custom Base URL', hint: 'your own proxy or gateway' }, + { + value: CUSTOM_BASE_URL as typeof CUSTOM_BASE_URL, + label: 'Custom Base URL', + hint: 'route through a proxy or LLM gateway', + }, { value: OTHER_PROVIDER as typeof OTHER_PROVIDER, label: 'Other provider', @@ -88,20 +84,21 @@ export async function setup(): Promise { }); if (p.isCancel(selected)) return cancelAndExit(); - // 2. Credentials — and, on the gateway route, the endpoint and its dialect. - const { provider, config, gateway } = await setupSelection(selected); + // 2. Credentials, and any endpoint override. A base URL overrides the endpoint + // for whichever provider is chosen — the curated gateway route names it via + // the dialect, the "Other provider" route asks for it directly. + const { provider, config, baseUrl } = await setupSelection(selected); // 3. The model that runs every phase. const modelId = await promptModel(provider); config.core = { ...config.core, model: `${provider}:${modelId}` }; - if (gateway) config.core = { ...config.core, base_url: gateway.baseUrl }; + if (baseUrl) config.core = { ...config.core, base_url: baseUrl }; saveConfig(config); const configPath = path.join(SHANNON_HOME, 'config.toml'); const summary = [`Provider ${provider}`, `Model ${modelId}`]; - if (gateway) summary.push(`Endpoint ${gateway.baseUrl}`); - if (gateway?.format) summary.push(`API ${gateway.format}`); + if (baseUrl) summary.push(`Endpoint ${baseUrl}`); p.log.success(`Configuration saved to ${configPath}`); p.log.info(summary.join('\n')); @@ -111,7 +108,7 @@ export async function setup(): Promise { interface Selection { provider: string; config: ShannonConfig; - gateway?: GatewaySetup; + baseUrl?: string; } /** Resolve the provider selection into a provider id and its credential config. */ @@ -120,7 +117,7 @@ async function setupSelection( ): Promise { if (selected === CUSTOM_BASE_URL) { const gateway = await setupGateway(); - return { provider: gateway.provider, config: gateway.config, gateway }; + return { provider: gateway.provider, config: gateway.config, baseUrl: gateway.baseUrl }; } if (selected === OTHER_PROVIDER) { return setupOtherProvider(); @@ -144,6 +141,8 @@ async function setupProvider(provider: CuratedProviderId): Promise { p.log.info('Browse supported providers and models at https://pi.dev/models'); @@ -159,7 +158,13 @@ async function setupOtherProvider(): Promise { if (p.isCancel(provider)) return cancelAndExit(); const apiKey = await promptSecret('Enter the API key'); - return { provider: provider.trim(), config: { provider: { api_key: apiKey } } }; + const baseUrl = await promptOptionalBaseUrl(); + + return { + provider: provider.trim(), + config: { provider: { api_key: apiKey } }, + ...(baseUrl && { baseUrl }), + }; } // === Provider Setup Flows === @@ -200,11 +205,10 @@ interface GatewaySetup { provider: CuratedProviderId; config: ShannonConfig; baseUrl: string; - format?: OpenAiFormat; } /** - * Gateway route: the endpoint decides where requests go, but the format still + * Gateway route: the endpoint decides where requests go, but the dialect still * picks a real provider, because that is what supplies the credential and the * wire protocol. */ @@ -236,11 +240,9 @@ async function setupGateway(): Promise { const authToken = await promptSecret('Enter the auth token for the endpoint'); const config: ShannonConfig = - provider === 'anthropic' - ? { anthropic: { api_key: authToken } } - : { openai: { api_key: authToken, ...(dialect.format && { format: dialect.format }) } }; + provider === 'anthropic' ? { anthropic: { api_key: authToken } } : { openai: { api_key: authToken } }; - return { provider, config, baseUrl, ...(dialect.format && { format: dialect.format }) }; + return { provider, config, baseUrl }; } // === Model Selection === @@ -308,6 +310,31 @@ async function promptModelId(provider: string, placeholder?: string): Promise { + const baseUrl = await p.text({ + message: 'Custom base URL (optional, leave blank for the provider default)', + placeholder: 'https://llm-gateway.example.com', + validate: (value) => { + const trimmed = value?.trim(); + if (!trimmed) return undefined; + try { + new URL(trimmed); + } catch { + return 'Must be a valid URL'; + } + return undefined; + }, + }); + if (p.isCancel(baseUrl)) return cancelAndExit(); + + const trimmed = baseUrl?.trim(); + return trimmed ? trimmed : undefined; +} + async function promptSecret(message: string): Promise { const value = await p.password({ message, diff --git a/apps/cli/src/config/resolver.ts b/apps/cli/src/config/resolver.ts index 97b173c2..aac9e66b 100644 --- a/apps/cli/src/config/resolver.ts +++ b/apps/cli/src/config/resolver.ts @@ -40,9 +40,8 @@ const CONFIG_MAP: readonly ConfigMapping[] = [ { env: 'ANTHROPIC_API_KEY', toml: 'anthropic.api_key', type: 'string' }, { env: 'CLAUDE_CODE_OAUTH_TOKEN', toml: 'anthropic.oauth_token', type: 'string' }, - // OpenAI — format picks the wire API a gateway serves + // OpenAI { env: 'OPENAI_API_KEY', toml: 'openai.api_key', type: 'string' }, - { env: 'SHANNON_AI_OPENAI_FORMAT', toml: 'openai.format', type: 'string' }, // xAI { env: 'XAI_API_KEY', toml: 'xai.api_key', type: 'string' }, diff --git a/apps/cli/src/config/writer.ts b/apps/cli/src/config/writer.ts index 44e893c8..497d3e0b 100644 --- a/apps/cli/src/config/writer.ts +++ b/apps/cli/src/config/writer.ts @@ -10,7 +10,7 @@ import { getConfigFile } from '../home.js'; export interface ShannonConfig { core?: { model?: string; base_url?: string }; anthropic?: { api_key?: string; oauth_token?: string }; - openai?: { api_key?: string; format?: string }; + openai?: { api_key?: string }; xai?: { api_key?: string }; bedrock?: { region?: string; token?: string }; /** Generic credential for any provider Shannon does not curate. Maps to SHANNON_AI_API_KEY. */ diff --git a/apps/cli/src/env.ts b/apps/cli/src/env.ts index 032bcbc9..658bbfdd 100644 --- a/apps/cli/src/env.ts +++ b/apps/cli/src/env.ts @@ -30,7 +30,6 @@ import { const COMMON_FORWARD_VARS = [ 'SHANNON_AI_MODEL', 'SHANNON_AI_BASE_URL', - 'SHANNON_AI_OPENAI_FORMAT', // Opt-in debug flag: when set, the worker persists a bounded, sanitized snippet of a failed // provider turn's raw error message to error.log. Off by default; provider prose stays out of // durable state unless an operator deliberately enables it for a diagnosis. diff --git a/apps/cli/src/model-spec.ts b/apps/cli/src/model-spec.ts index 2173a781..4cc58af4 100644 --- a/apps/cli/src/model-spec.ts +++ b/apps/cli/src/model-spec.ts @@ -52,15 +52,6 @@ export const PROVIDER_CREDENTIAL_HINT: Readonly:` ids. */ export const PI_CATALOG_URL = 'https://pi.dev/models'; -/** - * Wire formats an OpenAI-compatible gateway may serve, named by - * SHANNON_AI_OPENAI_FORMAT. Only `openai` offers a choice: every other supported - * provider has exactly one API in pi's registry. - */ -export const OPENAI_FORMATS = { - 'chat-completions': 'openai-completions', - responses: 'openai-responses', -} as const; - -export type OpenAiFormat = keyof typeof OPENAI_FORMATS; - -/** Format assumed when a gateway is configured but no format is named. */ -export const DEFAULT_OPENAI_FORMAT: OpenAiFormat = 'chat-completions'; - -function isOpenAiFormat(value: string): value is OpenAiFormat { - return value in OPENAI_FORMATS; -} - -/** - * Read SHANNON_AI_OPENAI_FORMAT. Unset returns undefined, which lets the caller - * distinguish "not configured" from an explicit choice and reject the variable - * where it has no effect. - */ -export function resolveOpenAiFormat(): OpenAiFormat | undefined { - const raw = process.env.SHANNON_AI_OPENAI_FORMAT?.trim(); - if (!raw) return undefined; - - if (!isOpenAiFormat(raw)) { - throw new Error( - `SHANNON_AI_OPENAI_FORMAT must be one of: ${Object.keys(OPENAI_FORMATS).join(', ')}. Got "${raw}".`, - ); - } - return raw; -} - export interface ModelSpec { providerId: string; modelId: string; @@ -256,26 +220,6 @@ export interface ModelSelection { readonly credentialSource: 'api-key' | 'pi-auth' | 'ambient'; } -/** - * Point a model descriptor at a gateway. - * - * An OpenAI gateway may serve either wire format, named by - * SHANNON_AI_OPENAI_FORMAT and defaulting to chat completions, which is what - * most gateway software exposes. Switching to completions also drops the stored - * `compat` block: the catalogue's block describes Responses, and an explicit - * entry outranks pi's `detectCompat`, so leaving it would apply Responses - * settings to a completions request. Staying on Responses keeps it, since it - * then describes the format in use. Every other provider has one API and only - * changes address. - */ -function pointAtGateway(model: Model, providerId: string, baseUrl: string, format: OpenAiFormat): Model { - if (providerId !== 'openai') return { ...model, baseUrl }; - if (format === 'responses') return { ...model, baseUrl, api: OPENAI_FORMATS.responses }; - - const { compat: _responsesCompat, ...withoutCompat } = model; - return { ...withoutCompat, baseUrl, api: OPENAI_FORMATS['chat-completions'] }; -} - /** * Resolve a model against a runtime. * @@ -293,44 +237,17 @@ export function resolveModel( providerId: string, modelId: string, baseUrl: string | undefined, - format: OpenAiFormat = DEFAULT_OPENAI_FORMAT, ): Model | undefined { const found = modelRuntime.getModel(providerId, modelId); if (found) { - return baseUrl ? pointAtGateway(found, providerId, baseUrl, format) : found; + return baseUrl ? { ...found, baseUrl } : found; } if (!baseUrl) return undefined; const reference = modelRuntime.getModels(providerId)[0]; if (!reference) return undefined; - return pointAtGateway({ ...reference, id: modelId, name: modelId }, providerId, baseUrl, format); -} - -/** - * Validate SHANNON_AI_OPENAI_FORMAT against the rest of the configuration and - * return the format a gateway run should use. - * - * The variable only reaches a request when both an OpenAI model and a gateway - * are configured, so it is rejected outside that combination rather than - * silently ignored. - */ -export function resolveGatewayFormat(providerId: string, baseUrl: string | undefined): OpenAiFormat { - const configured = resolveOpenAiFormat(); - if (!configured) return DEFAULT_OPENAI_FORMAT; - - if (providerId !== 'openai') { - throw new Error( - `SHANNON_AI_OPENAI_FORMAT applies to openai models only, but SHANNON_AI_MODEL selects "${providerId}". ` + - `${providerId} serves a single API, so there is no format to choose.`, - ); - } - if (!baseUrl) { - throw new Error( - 'SHANNON_AI_OPENAI_FORMAT applies to gateway runs only. Set SHANNON_AI_BASE_URL, or unset the format to call OpenAI directly.', - ); - } - return configured; + return { ...reference, id: modelId, name: modelId, baseUrl }; } /** @@ -340,12 +257,11 @@ export function resolveGatewayFormat(providerId: string, baseUrl: string | undef export async function resolveModelSelection(): Promise { const { providerId, modelId } = resolveModelSpec(); const credentials = resolveProviderCredentials(providerId); - const format = resolveGatewayFormat(providerId, credentials.baseUrl); const mountedPiAuth = piAuthPresent(); const modelRuntime = await createModelRuntime(providerId, credentials.apiKey); - const model = resolveModel(modelRuntime, providerId, modelId, credentials.baseUrl, format); + const model = resolveModel(modelRuntime, providerId, modelId, credentials.baseUrl); if (!model) { throw new Error( `Model not found in pi registry: provider="${providerId}" model="${modelId}". Browse valid providers and models at ${PI_CATALOG_URL}.`, diff --git a/apps/worker/src/services/preflight.ts b/apps/worker/src/services/preflight.ts index c3328aef..3b98dfe4 100644 --- a/apps/worker/src/services/preflight.ts +++ b/apps/worker/src/services/preflight.ts @@ -40,10 +40,8 @@ import { createModelRuntime, GENERIC_API_KEY_ENV, type ModelSpec, - type OpenAiFormat, PI_CATALOG_URL, piAuthPresent, - resolveGatewayFormat, resolveModel, resolveModelSpec, resolveProviderCredentials, @@ -329,24 +327,7 @@ async function validateCredentials(logger: ActivityLogger): Promise !process.env[n]) : []; @@ -362,12 +343,12 @@ async function validateCredentials(logger: ActivityLogger): Promise: +export SHANNON_AI_API_KEY=your-api-key # the provider's key — or the gateway's when a base URL is set +export SHANNON_AI_MODEL=openrouter:moonshotai/kimi-k3 # : +export SHANNON_AI_BASE_URL=https://llm-gateway.example.com # optional: route through a proxy or LLM gateway ``` This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported. @@ -48,7 +49,7 @@ Review each vendor's guidance and complete the verification or enrollment they a - Anthropic - [Real-time cyber safeguards on Claude Opus and Sonnet](https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude-opus-and-sonnet) - OpenAI - [Cyber](https://chatgpt.com/cyber) -This applies to the Anthropic and OpenAI providers, including when either is reached through a gateway. Bedrock serves Claude models and is subject to Anthropic's safeguards as well. +This applies to the Anthropic and OpenAI providers, including when either is reached through an LLM gateway. Bedrock serves Claude models and is subject to Anthropic's safeguards as well. ## Suggested models @@ -104,17 +105,16 @@ Bedrock uses bearer-token authentication only. IAM access keys, session tokens, ## Custom base URL -To route model traffic through your own infrastructure — a corporate proxy, an LLM gateway such as LiteLLM, or a regional endpoint — set a base URL alongside your normal model selection. The provider half of `SHANNON_AI_MODEL` decides which key is sent and which API Shannon speaks, so pick the one your gateway serves: +`SHANNON_AI_BASE_URL` routes model traffic through a proxy or LLM gateway instead of the provider's default endpoint — an LLM gateway such as LiteLLM, a regional endpoint, or any other host you choose. It is a plain endpoint override: it changes only *where* requests go. The provider half of `SHANNON_AI_MODEL` still decides which credential is sent and which API dialect is spoken, and that is unchanged by the base URL. -| Gateway serves | Model prefix | API key | -| --- | --- | --- | -| Anthropic Messages | `anthropic:` | `SHANNON_AI_API_KEY` | -| OpenAI Chat Completions | `openai:` | `SHANNON_AI_API_KEY` | -| OpenAI Responses | `openai:` + `SHANNON_AI_OPENAI_FORMAT=responses` | `SHANNON_AI_API_KEY` | +This works for **any** provider, curated or not. The one rule is that a provider's dialect is fixed, so the endpoint you point at must speak that provider's dialect: -The model ID is whatever name your gateway serves it under; it does not have to exist in Shannon's catalogue. +| Provider prefix | Dialect the endpoint must speak | +| --- | --- | +| `anthropic:` | Anthropic Messages | +| `openai:` | OpenAI Responses | -Anthropic Messages: +Anthropic Messages LLM gateway: ```bash export SHANNON_AI_API_KEY=sk-ant-... @@ -122,7 +122,7 @@ export SHANNON_AI_MODEL=anthropic:claude-sonnet-4-6 export SHANNON_AI_BASE_URL=https://llm-gateway.example.com ``` -OpenAI Chat Completions: +OpenAI Responses LLM gateway: ```bash export SHANNON_AI_API_KEY=sk-... @@ -130,19 +130,7 @@ export SHANNON_AI_MODEL=openai:gpt-5.6-sol export SHANNON_AI_BASE_URL=https://llm-gateway.example.com/v1 ``` -`SHANNON_AI_MODEL` is always `:`, gateway or not. - -OpenAI is the one provider serving two APIs, so a gateway run picks one: - -```bash -export SHANNON_AI_OPENAI_FORMAT=responses # default: chat-completions -``` - -Chat Completions is the default because that is what most gateway software exposes. Set `responses` for a gateway that passes the Responses API through — it preserves reasoning state between turns, which Chat Completions cannot. `openai:gpt-5` with no base URL always calls OpenAI's Responses API directly. - -The variable is rejected in preflight where it cannot take effect: with a non-`openai` model, since Anthropic, xAI, and Bedrock each serve one API, and with no `SHANNON_AI_BASE_URL`, since a direct OpenAI run is always Responses. - -`npx @keygraph/shannon setup` covers this under **Custom Base URL**, which asks which API your gateway serves and configures the matching provider for you. +`npx @keygraph/shannon setup` configures a base URL two ways: **Custom Base URL** covers the common Anthropic Messages and OpenAI Responses LLM gateways, and **Other provider** takes any provider ID plus an optional base URL of its own. ## OpenAI Codex (ChatGPT Plus/Pro subscription) @@ -209,7 +197,7 @@ These instructions apply only to `shannon-v1`. Checks run before a scan starts, so mistakes fail immediately rather than partway through a run: -- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). A custom base URL exempts the model ID, since a gateway may serve its own names. +- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). A custom base URL exempts the model ID, since an LLM gateway may serve its own names. - **Credential presence** — validated for the selected provider, or read from Pi when `SHANNON_USE_PI_AUTH=1`. - **Credential validity** — one minimal request against the model the scan will use, so a rejected key, an exhausted quota, or a model the account cannot reach fails before any agent runs. Bedrock included: its bearer token and region go through the same probe. diff --git a/llms-full.txt b/llms-full.txt index 6f5d831a..fd69321b 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -97,7 +97,7 @@ Sample penetration test reports from intentionally vulnerable applications, prod - **Docker**: required for the worker container. - **Node.js 18+**: required for the recommended `npx` workflow. -- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue, and any endpoint that speaks the Anthropic Messages API or the OpenAI Chat Completions or Responses API through a [custom base URL](docs/ai-providers.md#custom-base-url). 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). 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). @@ -141,7 +141,7 @@ For source builds, authenticated scans, provider-specific setup, and platform no - **Resumable workspaces**: Shannon can resume interrupted runs without re-running completed agents. - **Native CI/CD integrations**: Run Shannon through the official GitHub Action or reusable GitLab CI/CD component. Preserve reports, SARIF, and logs as pipeline artifacts; publish findings into native security workflows; and gate releases only on vulnerabilities Shannon actually demonstrates. - **Professional and machine-readable reports**: Shannon generates evidence-rich PDF and Markdown reports plus structured JSON and SARIF 2.1.0. SARIF is enabled by default on exploit-mode scans and can be disabled with `report.sarif: "false"`. -- **Bring your own key, provider-agnostic**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and any endpoint speaking the Anthropic Messages API or the OpenAI Chat Completions or Responses API, including self-hosted models served through Ollama, vLLM, or LM Studio and gateways such as OpenRouter and LiteLLM. You supply the credentials and choose exactly where model traffic goes. Local and self-hosted models are supported. +- **Bring your own key, provider-agnostic**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and any other provider in the Pi harness catalogue, each of which you can point at a proxy or LLM gateway with a custom base URL — including self-hosted models served through Ollama, vLLM, or LM Studio and LLM gateways such as OpenRouter and LiteLLM. You supply the credentials and choose exactly where model traffic goes. Local and self-hosted models are supported. - **Private by design**: Shannon runs inside your infrastructure and writes results to a local workspace. Model requests go straight to the provider or endpoint you configure, and they carry source and application context with them, so choose that endpoint deliberately. Point Shannon at a local model endpoint and nothing leaves your environment. @@ -265,7 +265,7 @@ Use these guides for operational detail: | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Source build and CLI commands](docs/development.md) | Cloning, building, common commands, output paths, and local development. | | [Configuration](docs/configuration.md) | Authenticated testing, login flows, rules of engagement, and report filters. | -| [AI providers](docs/ai-providers.md) | Selecting the model, the supported providers (Anthropic, OpenAI, xAI, AWS Bedrock, and any other Pi-supported provider), and custom gateways. | +| [AI providers](docs/ai-providers.md) | Selecting the model, the supported providers (Anthropic, OpenAI, xAI, AWS Bedrock, and any other Pi-supported provider), and custom LLM gateways. | | [Platforms and networking](docs/platforms.md) | Windows/WSL2, Linux, macOS, Docker networking, local apps, and custom hostnames. | | [Workspaces and resuming](docs/workspaces.md) | Naming workspaces, resuming interrupted scans, and workspace storage. | | [Safety and limitations](docs/safety.md) | Authorized-use requirements, non-production guidance, mutative effects, cost, and model caveats. | @@ -285,7 +285,7 @@ Important limitations: - Shannon Open Source is tuned for fast, code-informed pentesting in everyday development and CI/CD. Exhaustive agentic SAST, broader scanner coverage, centralized governance, and full-lifecycle vulnerability management are delivered through the Keygraph Enterprise Platform. - Findings still require human review. LLM-generated reports can contain weakly supported or incorrect details. -- Anthropic, OpenAI, xAI, and AWS Bedrock are built-in providers, and any Anthropic Messages API or OpenAI Chat Completions or Responses API endpoint works through a custom base URL. Model capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker results. +- Anthropic, OpenAI, xAI, and AWS Bedrock are built-in providers, and any other provider in the harness catalogue works too — each reachable through a custom base URL that points it at a proxy or LLM gateway. Model capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker results. - A full run can take roughly 1 to 1.5 hours and may incur LLM API costs depending on model pricing and application complexity. - Do not scan untrusted or adversarial codebases. AI-powered tools that read source code can be exposed to prompt injection. @@ -346,11 +346,11 @@ Yes. Shannon emits SARIF 2.1.0, the OASIS standard format for static analysis re ### 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 endpoint that implements the Anthropic Messages API or the OpenAI Chat Completions or Responses API, reached through a custom base URL. The rule is the API format, not the vendor. 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 `:` 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. Shannon uses a single unified model setting throughout a pentest. ### Can I run Shannon on a local or self-hosted model? -Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and gateways such as LiteLLM. Point Shannon at the endpoint with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [AI providers](docs/ai-providers.md#custom-base-url). +Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and LLM gateways such as LiteLLM. Point Shannon at the endpoint with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [AI providers](docs/ai-providers.md#custom-base-url). ### Does Shannon actually exploit vulnerabilities, or just scan? @@ -747,8 +747,9 @@ Shannon forwards only the selected provider's credential into the scan container Shannon accepts any provider and model present in the Pi harness catalogue. Browse them at [pi.dev/models](https://pi.dev/models). ```bash -export SHANNON_AI_API_KEY=your-api-key # the provider's API key -export SHANNON_AI_MODEL=openrouter:moonshotai/kimi-k3 # : +export SHANNON_AI_API_KEY=your-api-key # the provider's key — or the gateway's when a base URL is set +export SHANNON_AI_MODEL=openrouter:moonshotai/kimi-k3 # : +export SHANNON_AI_BASE_URL=https://llm-gateway.example.com # optional: route through a proxy or LLM gateway ``` This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported. @@ -767,7 +768,7 @@ Review each vendor's guidance and complete the verification or enrollment they a - Anthropic - [Real-time cyber safeguards on Claude Opus and Sonnet](https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude-opus-and-sonnet) - OpenAI - [Cyber](https://chatgpt.com/cyber) -This applies to the Anthropic and OpenAI providers, including when either is reached through a gateway. Bedrock serves Claude models and is subject to Anthropic's safeguards as well. +This applies to the Anthropic and OpenAI providers, including when either is reached through an LLM gateway. Bedrock serves Claude models and is subject to Anthropic's safeguards as well. ## Suggested models @@ -823,17 +824,16 @@ Bedrock uses bearer-token authentication only. IAM access keys, session tokens, ## Custom base URL -To route model traffic through your own infrastructure — a corporate proxy, an LLM gateway such as LiteLLM, or a regional endpoint — set a base URL alongside your normal model selection. The provider half of `SHANNON_AI_MODEL` decides which key is sent and which API Shannon speaks, so pick the one your gateway serves: +`SHANNON_AI_BASE_URL` routes model traffic through a proxy or LLM gateway instead of the provider's default endpoint — an LLM gateway such as LiteLLM, a regional endpoint, or any other host you choose. It is a plain endpoint override: it changes only *where* requests go. The provider half of `SHANNON_AI_MODEL` still decides which credential is sent and which API dialect is spoken, and that is unchanged by the base URL. -| Gateway serves | Model prefix | API key | -| --- | --- | --- | -| Anthropic Messages | `anthropic:` | `SHANNON_AI_API_KEY` | -| OpenAI Chat Completions | `openai:` | `SHANNON_AI_API_KEY` | -| OpenAI Responses | `openai:` + `SHANNON_AI_OPENAI_FORMAT=responses` | `SHANNON_AI_API_KEY` | +This works for **any** provider, curated or not. The one rule is that a provider's dialect is fixed, so the endpoint you point at must speak that provider's dialect: -The model ID is whatever name your gateway serves it under; it does not have to exist in Shannon's catalogue. +| Provider prefix | Dialect the endpoint must speak | +| --- | --- | +| `anthropic:` | Anthropic Messages | +| `openai:` | OpenAI Responses | -Anthropic Messages: +Anthropic Messages LLM gateway: ```bash export SHANNON_AI_API_KEY=sk-ant-... @@ -841,7 +841,7 @@ export SHANNON_AI_MODEL=anthropic:claude-sonnet-4-6 export SHANNON_AI_BASE_URL=https://llm-gateway.example.com ``` -OpenAI Chat Completions: +OpenAI Responses LLM gateway: ```bash export SHANNON_AI_API_KEY=sk-... @@ -849,19 +849,7 @@ export SHANNON_AI_MODEL=openai:gpt-5.6-sol export SHANNON_AI_BASE_URL=https://llm-gateway.example.com/v1 ``` -`SHANNON_AI_MODEL` is always `:`, gateway or not. - -OpenAI is the one provider serving two APIs, so a gateway run picks one: - -```bash -export SHANNON_AI_OPENAI_FORMAT=responses # default: chat-completions -``` - -Chat Completions is the default because that is what most gateway software exposes. Set `responses` for a gateway that passes the Responses API through — it preserves reasoning state between turns, which Chat Completions cannot. `openai:gpt-5` with no base URL always calls OpenAI's Responses API directly. - -The variable is rejected in preflight where it cannot take effect: with a non-`openai` model, since Anthropic, xAI, and Bedrock each serve one API, and with no `SHANNON_AI_BASE_URL`, since a direct OpenAI run is always Responses. - -`npx @keygraph/shannon setup` covers this under **Custom Base URL**, which asks which API your gateway serves and configures the matching provider for you. +`npx @keygraph/shannon setup` configures a base URL two ways: **Custom Base URL** covers the common Anthropic Messages and OpenAI Responses LLM gateways, and **Other provider** takes any provider ID plus an optional base URL of its own. ## OpenAI Codex (ChatGPT Plus/Pro subscription) @@ -928,7 +916,7 @@ These instructions apply only to `shannon-v1`. Checks run before a scan starts, so mistakes fail immediately rather than partway through a run: -- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). A custom base URL exempts the model ID, since a gateway may serve its own names. +- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). A custom base URL exempts the model ID, since an LLM gateway may serve its own names. - **Credential presence** — validated for the selected provider, or read from Pi when `SHANNON_USE_PI_AUTH=1`. - **Credential validity** — one minimal request against the model the scan will use, so a rejected key, an exhausted quota, or a model the account cannot reach fails before any agent runs. Bedrock included: its bearer token and region go through the same probe. diff --git a/llms.txt b/llms.txt index 40fd8d23..5aabdabe 100644 --- a/llms.txt +++ b/llms.txt @@ -13,7 +13,7 @@ Use this file as the concise entry point for AI agents and LLMs reading this rep - [Development](docs/development.md): Source-build workflow, common CLI commands, repository paths, and output locations. - [Configuration](docs/configuration.md): Authenticated testing, login flows, rules of engagement, report filters, credential precedence, and rate-limit settings. -- [AI Providers](docs/ai-providers.md): Anthropic, OpenAI, xAI, AWS Bedrock, any other Pi-supported provider, and custom gateway setup. +- [AI Providers](docs/ai-providers.md): Anthropic, OpenAI, xAI, AWS Bedrock, any other Pi-supported provider, and custom LLM gateway setup. - [Platforms and Networking](docs/platforms.md): Windows/WSL2, Linux, macOS, Docker networking, local applications, and custom hostnames. - [Workspaces and Resuming](docs/workspaces.md): Workspace storage, naming, resuming interrupted scans, and examples. - [Safety and Limitations](docs/safety.md): Authorized-use requirements, non-production guidance, mutative effects, model caveats, scope limits, cost, and performance.