diff --git a/.env.example b/.env.example index cd22ef8..d7dc6e2 100644 --- a/.env.example +++ b/.env.example @@ -48,3 +48,8 @@ SHANNON_AI_MODEL=anthropic:claude-sonnet-4-6 # --- Misc -------------------------------------------------------------------- # Forward /etc/hosts entries into the worker container. # SHANNON_FORWARD_HOSTS=false + +# See the guide below to use an OpenAI subscription +# https://github.com/KeygraphHQ/shannon/blob/main/docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription +# SHANNON_USE_PI_AUTH=1 +# SHANNON_AI_MODEL=openai-codex:gpt-5.5 diff --git a/CLAUDE.md b/CLAUDE.md index 681061c..df6f7e3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -154,6 +154,7 @@ Durable workflow orchestration with crash recovery, queryable progress, intellig - **Configuration** — YAML configs in `apps/worker/configs/` with JSON Schema validation (`config-schema.json`). Supports auth settings (MFA/TOTP), URL/code rule scoping (`rules.avoid`/`rules.focus`), run-scope steering (`vuln_classes`, `exploit`), free-form `rules_of_engagement`, and post-hoc `report` options (`min_severity`, `min_confidence`, `guidance`, and `sarif` to emit a SARIF 2.1.0 log via `apps/worker/src/services/sarif-renderer.ts`; exploit-only). `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. `vuln_classes`/`exploit` scope is locked into `session.json` on first run; resumes with a different scope fail fast (`persistOrValidateRunScope`). Credential resolution — local mode: env vars → `./.env`; npx mode: env vars → `~/.shannon/config.toml` (via `npx @keygraph/shannon setup`) - **Prompts** — Per-phase templates in `apps/worker/prompts/` with variable substitution (`{{TARGET_URL}}`, `{{CONFIG_CONTEXT}}`). Shared partials in `apps/worker/prompts/shared/` via `apps/worker/src/services/prompt-manager.ts`, including `_code-path-rules.txt` (focus/avoid `[FILE]`/`[GLOB]` routing) and `_rules-of-engagement.txt` (free-text engagement rules). When `exploit: false`, `apps/worker/src/services/findings-renderer.ts` deterministically converts each `*_exploitation_queue.json` into a `*_findings.md` for report assembly — no LLM in the loop - **Agent Harness (pi)** — Uses the **pi harness** (`@earendil-works/pi-coding-agent`, requires Node ≥ 22.19) via `apps/worker/src/ai/pi/pi-executor.ts` (`runPiPrompt` → `createAgentSession`). Retry is split in `apps/worker/src/ai/pi/retry-settings.ts`: pi's agent-level loop is off so Temporal owns agent restarts, while `provider.maxRetries` stays on — pi reads the `provider` block independently of the `enabled` flag — so transport faults are absorbed in-session rather than costing a full agent re-run. `maxRetryDelayMs` is left at pi's 60s default. One model runs every phase, named by `SHANNON_AI_MODEL=:` (default `anthropic:claude-sonnet-4-6`). `apps/worker/src/ai/models.ts` parses the spec — splitting on the **first** colon only, so Bedrock IDs keep theirs — and resolves it through pi's `ModelRuntime`. pi ships the `CredentialStore` interface but no in-memory implementation (its own reads `auth.json` from disk), so `RuntimeCredentialStore` in that file supplies one: credentials arrive as env vars in an ephemeral container and must never touch disk. `createModelRuntime(providerId, apiKey)` builds the runtime; `allowModelNetwork` stays at its default `false` so a scan never blocks on a catalog refresh. `resolveModelSelection()` is **async** because `ModelRuntime.create()` is. Any pi-ai provider id is accepted — `parseModelSpec` no longer rejects against a hardcoded list, so pi's registry is the authority (an unknown provider/model surfaces as a clear "not found in pi registry" error at preflight, which points to the browsable catalogue at `pi.dev/models` — `PI_CATALOG_URL` in `apps/worker/src/ai/models.ts`, appended to the not-found errors and shown in the setup wizard's "Other provider" hint). Four providers are **curated** (`CURATED_PROVIDERS`: `anthropic`, `openai`, `xai`, `amazon-bedrock`) with their own credential variables, config sections, and setup flows; each provider's API key env var is declared once in `PROVIDER_API_KEY_ENV` — Shannon uses each vendor's own variable name (`OPENAI_API_KEY`, `XAI_API_KEY`, …), never an invented one; Bedrock's entry is `AWS_BEARER_TOKEN_BEDROCK`, paired with `AWS_REGION`, which preflight requires separately as provider config rather than a credential. Any other provider uses the **generic** credential path: `SHANNON_AI_API_KEY` (`GENERIC_API_KEY_ENV`) supplies the key for any provider whose credential is a plain API key. Curated providers' own variables take precedence over it, and it also works as a fallback for them — Bedrock is the sole exception (it authenticates through its AWS_ variables, so the generic key never stands in for it). The CLI forwards `SHANNON_AI_API_KEY` in `COMMON_FORWARD_VARS` (it is provider-neutral, binding to whatever `SHANNON_AI_MODEL` names, so the "only one provider configured" guard counts only named credentials), and stores it under a generic `[provider]` config.toml section (`provider.api_key`). `npx @keygraph/shannon setup` exposes this as the "Other provider" option: free-text provider id + model id + key (a curated provider id is rejected there, since it has its own option). `SHANNON_AI_BASE_URL` overrides the endpoint for any provider (proxies/gateways); the credential is unchanged. `pointAtGateway` (`apps/worker/src/ai/models.ts`) applies the one dialect change: behind a base URL, `openai` follows `SHANNON_AI_OPENAI_FORMAT` (`chat-completions` default, or `responses`). On `chat-completions` it switches the API to `openai-completions` and drops the catalogue's Responses-shaped `compat` block so pi's `detectCompat` derives completions settings; on `responses` the descriptor is unchanged but for the endpoint. `resolveGatewayFormat` rejects the variable when the provider is not `openai` or no base URL is set, since it cannot take effect there. All other providers keep their API. The CLI mirrors the accepted values in `apps/cli/src/model-spec.ts`, forwards the variable in `COMMON_FORWARD_VARS`, and maps it to `openai.format` in config.toml. `buildEnvFlags` forwards only the selected provider's credential into the worker container. The CLI mirrors the parse rule and the provider/credential tables in `apps/cli/src/model-spec.ts` (it cannot import from the worker package); the two must stay in sync. pi ships no JSON-schema output or `Task`/`TodoWrite` built-ins, so structured queues are captured via a `submit_exploitation_queue` custom tool (`apps/worker/src/ai/queue-schemas.ts`), and `task` (child sessions scoped to `read`, `grep`, `find`, `ls`, `write`, and `bash` — no nested `task` or collector tools; `CHILD_TOOLS` in `apps/worker/src/ai/pi/task-tool.ts`) + `todo_write` (`apps/worker/src/ai/pi/session-tools.ts`) are provided as custom tools; the per-phase collectors are pi custom tools (TypeBox `defineTool` in `apps/worker/src/collectors/`). Shannon sets no thinking configuration at all — no `thinkingLevel` is passed to any `createAgentSession` call, so pi's own default applies. There is no adaptive-thinking support and no `CLAUDE_ADAPTIVE_THINKING` / `core.adaptive_thinking` setting. Browser automation via `playwright-cli` with session isolation (`-s=`). TOTP generation via `generate-totp` CLI tool. Login flow template at `apps/worker/prompts/shared/login-instructions.txt` supports form, SSO, API, and basic auth. On authenticated whitebox scans, the `validate-authentication` preflight performs the single real login and saves the browser session to `auth-state.json` in the per-session audit directory (path from `authStateFile()` in `apps/worker/src/audit/utils.ts`, derived from `generateAuditPath()`). The validation activity (`apps/worker/src/services/validate-authentication.ts`) removes any stale file from a prior run before the agent runs and verifies the file parses and contains cookies or storage before the preflight is marked complete; `logWorkflowComplete` deletes it when the workflow ends so authenticated cookies don't sit on disk between scans. Agent prompts opt in to session reuse by `@include(shared/_shared-session.txt)` before their `` block — the partial restores the session and falls through to the full login flow if verification fails. `vuln-auth`/`exploit-auth` omit the include and own their own login +- **Pi Credential Reuse** — `SHANNON_USE_PI_AUTH=1` opts into reusing the host's Pi login, including an `openai-codex` ChatGPT Plus/Pro subscription selected with `SHANNON_AI_MODEL=openai-codex:`. `apps/cli/src/env.ts` requires `~/.pi/agent/auth.json`; `start.ts` passes its path to `spawnWorker`, which mounts only that file read-write at `/tmp/.pi/agent/auth.json`. The flag itself is not forwarded: the worker detects the file with `piAuthPresent()` and passes its path to `ModelRuntime.create`. CLI and worker API-key presence checks are skipped on this path, but the normal preflight model probe still validates the credential. The image and UID-remapping entrypoint keep `/tmp/.pi/agent` owned by `pentest` so adjacent Pi/Shannon configuration remains writable. Refreshed OAuth state is persisted to the host for subsequent scans. - **Audit System** — Crash-safe append-only logging in `workspaces/{hostname}_{sessionId}/`. The run directory's top level holds only the human-facing report (`Security-Assessment-Report.md`, `FINAL_REPORT_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 (`workspaces`/`logs`) without migration. Resuming a pre-restructure workspace upgrades it in place first: `migrateLegacyWorkspaceLayout` (`apps/cli/src/commands/start.ts`) renames the flat deliverables/logs/session entries into `.shannon/` (carrying the deliverables `.git` along) before the overlay dirs are mounted, so resume finds the old checkpoints instead of re-running every agent. The report is surfaced by copying the assembled `comprehensive_security_assessment_report.md` from the deliverables dir to the run root (`copyReportToRunRoot` in `apps/worker/src/services/reporting.ts`). 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 - **Deliverables** — Saved to `.shannon/deliverables/` in the target repo via the `save-deliverable` CLI script (`apps/worker/src/scripts/save-deliverable.ts`) - **Workspaces & Resume** — Named workspaces via `-w ` or auto-named from URL+timestamp. Resume detects completed agents via `session.json`. `loadResumeState()` in `apps/worker/src/temporal/activities.ts` validates deliverable existence, restores git checkpoints, and cleans up incomplete deliverables. Workspace listing via `apps/worker/src/temporal/workspaces.ts` diff --git a/Dockerfile b/Dockerfile index 9a888ff..cca3fef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -107,12 +107,12 @@ RUN ln -s /app/apps/worker/dist/scripts/save-deliverable.js /usr/local/bin/save- # Create directories for session data and ensure proper permissions RUN mkdir -p /app/sessions /app/repos /app/workspaces && \ - mkdir -p /tmp/.cache /tmp/.config /tmp/.npm && \ + mkdir -p /tmp/.cache /tmp/.config /tmp/.npm /tmp/.pi/agent && \ chmod 777 /app && \ chmod 777 /tmp/.cache && \ chmod 777 /tmp/.config && \ chmod 777 /tmp/.npm && \ - chown -R pentest:pentest /app /tmp/.claude + chown -R pentest:pentest /app /tmp/.claude /tmp/.pi COPY entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh diff --git a/README.md b/README.md index 16a4da8..23928f0 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,10 @@ Shannon pulls the worker image from Docker Hub, starts the required local infras For source builds, authenticated scans, provider-specific setup, and platform notes, see [Documentation](#documentation). > [!TIP] -> **Prefer to run on your Claude Code subscription instead of API credits?** The [`shannon-v1`](https://github.com/KeygraphHQ/shannon/tree/shannon-v1) branch is the last release built on the Claude Agent SDK, so it accepts a Claude Code OAuth token. Generate one with `claude setup-token`, then run `npx @keygraph/shannon@1.9.0 setup` and pick **OAuth Token**. Pentests then cost nothing beyond your existing subscription. +> **Prefer to use a subscription instead of API credits?** +> +> - **OpenAI Codex:** The latest version of Shannon supports ChatGPT Plus and Pro subscriptions. Follow the [OpenAI Codex subscription setup guide](docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription) to get started. +> - **Claude Code:** The latest version of Shannon does not support Claude Code subscriptions. Follow the [Claude Code subscription setup guide](docs/ai-providers.md#claude-code-subscription) to use version `1.9.0`, which is the final release built on the Claude Agent SDK. ## Key Capabilities diff --git a/apps/cli/src/commands/start.ts b/apps/cli/src/commands/start.ts index e5413ef..eac3665 100644 --- a/apps/cli/src/commands/start.ts +++ b/apps/cli/src/commands/start.ts @@ -9,7 +9,7 @@ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import { ensureImage, ensureInfra, randomSuffix, spawnWorker } from '../docker.js'; -import { buildEnvFlags, loadEnv, validateCredentials } from '../env.js'; +import { buildEnvFlags, loadEnv, resolveHostPiAuthPath, shouldUsePiAuth, validateCredentials } from '../env.js'; import { getWorkspacesDir, initHome } from '../home.js'; import { isLocal } from '../mode.js'; import { resolveModelSpec } from '../model-spec.js'; @@ -135,6 +135,7 @@ export async function start(args: StartArgs): Promise { workspace, ...(args.pipelineTesting && { pipelineTesting: true }), ...(args.debug && { debug: true }), + ...(shouldUsePiAuth() && { piAuthHostPath: resolveHostPiAuthPath() }), }); // 14. Bail if `docker run -d` itself fails (mount error, image missing, etc.) diff --git a/apps/cli/src/docker.ts b/apps/cli/src/docker.ts index d13e308..fbb6d80 100644 --- a/apps/cli/src/docker.ts +++ b/apps/cli/src/docker.ts @@ -12,6 +12,7 @@ import os from 'node:os'; import path from 'node:path'; import { setTimeout as sleep } from 'node:timers/promises'; import { fileURLToPath } from 'node:url'; +import { envBool, PI_AUTH_CONTAINER_PATH } from './env.js'; import { getMode, isDevMode } from './mode.js'; import { INTERNAL_DIR } from './paths.js'; @@ -203,7 +204,7 @@ function shouldSkipHostsName(name: string, hostname: string): boolean { * `host-gateway` so they target the host's loopback instead of the container's. */ function forwardEtcHostsFlags(): string[] { - if (process.env.SHANNON_FORWARD_HOSTS === 'false') return []; + if (!envBool('SHANNON_FORWARD_HOSTS', true)) return []; if (os.platform() === 'win32') return []; let content: string; @@ -255,6 +256,7 @@ export interface WorkerOptions { workspace: string; pipelineTesting?: boolean; debug?: boolean; + piAuthHostPath?: string; } /** @@ -305,6 +307,11 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess { args.push('-v', `${opts.outputDir}:/app/output`); } + // Reuse the host's pi credentials: mount only the auth file, allowing token refreshes to persist. + if (opts.piAuthHostPath) { + args.push('-v', `${opts.piAuthHostPath}:${PI_AUTH_CONTAINER_PATH}`); + } + // Environment args.push(...opts.envFlags); diff --git a/apps/cli/src/env.ts b/apps/cli/src/env.ts index 75ef6ab..3071d59 100644 --- a/apps/cli/src/env.ts +++ b/apps/cli/src/env.ts @@ -5,6 +5,9 @@ * NPX mode: fills gaps from ~/.shannon/config.toml (no .env). */ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; import dotenv from 'dotenv'; import { resolveConfig } from './config/resolver.js'; import { getMode } from './mode.js'; @@ -41,6 +44,34 @@ function providerForwardVars(providerId: string): readonly string[] { return [...PROVIDER_API_KEY_ENV[providerId], ...PROVIDER_EXTRA_ENV[providerId]]; } +/** Parse a user-facing boolean env var: `1`/`true` (any case) true, `0`/`false`/empty false, else the default. */ +export function envBool(name: string, defaultValue: boolean): boolean { + const raw = process.env[name]?.trim().toLowerCase(); + if (raw === undefined || raw === '') return defaultValue; + if (raw === '1' || raw === 'true') return true; + if (raw === '0' || raw === 'false') return false; + return defaultValue; +} + +const USE_PI_AUTH_ENV = 'SHANNON_USE_PI_AUTH'; + +/** Where the host's auth.json is mounted: pi's standard location (worker HOME is /tmp), read natively. */ +export const PI_AUTH_CONTAINER_PATH = '/tmp/.pi/agent/auth.json'; + +/** Host path to pi's credential file. */ +export function resolveHostPiAuthPath(): string { + return path.join(os.homedir(), '.pi', 'agent', 'auth.json'); +} + +export function piAuthFlagEnabled(): boolean { + return envBool(USE_PI_AUTH_ENV, false); +} + +/** Opted into pi auth via the flag, and the auth file exists to mount. */ +export function shouldUsePiAuth(): boolean { + return piAuthFlagEnabled() && fs.existsSync(resolveHostPiAuthPath()); +} + /** * Load credentials into process.env. * Local mode: loads ./.env via dotenv. @@ -110,6 +141,18 @@ export function validateCredentials(): CredentialValidation { return { valid: false, error: spec }; } + // Pi-auth: skip the API-key checks, but the host auth file must exist to mount. + if (piAuthFlagEnabled()) { + const authPath = resolveHostPiAuthPath(); + if (!fs.existsSync(authPath)) { + return { + valid: false, + error: `${USE_PI_AUTH_ENV} is set but no pi credentials were found at ${authPath}. Authenticate with pi first.`, + }; + } + return { valid: true }; + } + // 2. The selected provider must have a credential if (!hasCredential(spec.providerId)) { const requirement = isCuratedProvider(spec.providerId) diff --git a/apps/worker/src/ai/models.ts b/apps/worker/src/ai/models.ts index e9ddec8..3dabce6 100644 --- a/apps/worker/src/ai/models.ts +++ b/apps/worker/src/ai/models.ts @@ -21,8 +21,10 @@ * built over an in-memory credential store primed from the environment. */ +import { existsSync } from 'node:fs'; +import path from 'node:path'; import type { Api, Credential, CredentialInfo, CredentialStore, Model } from '@earendil-works/pi-ai'; -import { ModelRuntime } from '@earendil-works/pi-coding-agent'; +import { getAgentDir, ModelRuntime } from '@earendil-works/pi-coding-agent'; /** * Providers Shannon curates with their own credential variables, config sections, @@ -203,12 +205,29 @@ class RuntimeCredentialStore implements CredentialStore { } } +/** The file pi reads credentials from: the agent dir's auth.json. */ +function piAuthPath(): string { + return path.join(getAgentDir(), 'auth.json'); +} + +/** Whether the host's pi credentials are mounted (auth.json present in the agent dir). */ +export function piAuthPresent(): boolean { + return existsSync(piAuthPath()); +} + /** * Build a ModelRuntime whose only credential is the one supplied. Model catalogs * stay offline (`allowModelNetwork` defaults to false) so a scan never blocks on * a catalog refresh. + * + * 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 + * refreshes persist to the host for subsequent runs. */ export async function createModelRuntime(providerId: string, apiKey: string | undefined): Promise { + if (piAuthPresent()) { + return ModelRuntime.create({ authPath: piAuthPath() }); + } return ModelRuntime.create({ credentials: new RuntimeCredentialStore(providerId, apiKey) }); } diff --git a/apps/worker/src/services/preflight.ts b/apps/worker/src/services/preflight.ts index b687ccc..f0e4e5f 100644 --- a/apps/worker/src/services/preflight.ts +++ b/apps/worker/src/services/preflight.ts @@ -42,6 +42,7 @@ import { type ModelSpec, type OpenAiFormat, PI_CATALOG_URL, + piAuthPresent, resolveGatewayFormat, resolveModel, resolveModelSpec, @@ -296,6 +297,7 @@ function credentialHint(providerId: string): string { /** Human-readable label for which credential path a run is using. */ function describeAuth(providerId: string, baseUrl: string | undefined): string { if (baseUrl) return `custom endpoint (${baseUrl})`; + if (piAuthPresent()) return `${providerId} credentials from pi auth.json`; if (providerId === 'amazon-bedrock') return 'Bedrock bearer token'; return `${providerId} API key`; } @@ -341,9 +343,11 @@ async function validateCredentials(logger: ActivityLogger): Promise !process.env[n]) : []; - if (missing.length > 0 || (!isBedrock && !credentials.apiKey)) { + const missing = + isBedrock && !piAuthPresent() ? ['AWS_REGION', 'AWS_BEARER_TOKEN_BEDROCK'].filter((n) => !process.env[n]) : []; + if (!piAuthPresent() && (missing.length > 0 || (!isBedrock && !credentials.apiKey))) { return err( new PentestError( `No credentials found for provider "${spec.providerId}". Set ${credentialHint(spec.providerId)} in .env.`, diff --git a/docs/ai-providers.md b/docs/ai-providers.md index 5c1abe8..96f2b34 100644 --- a/docs/ai-providers.md +++ b/docs/ai-providers.md @@ -144,12 +144,55 @@ The variable is rejected in preflight where it cannot take effect: with a non-`o `npx @keygraph/shannon setup` covers this under **Custom Base URL**, which asks which API your gateway serves and configures the matching provider for you. +## OpenAI Codex (ChatGPT Plus/Pro subscription) + +A ChatGPT Plus or Pro Codex subscription can run Shannon. Shannon reuses a login created by Pi. + +Before running a pentest, review the [cyber safeguards requirements](#cyber-safeguards-do-this-before-your-first-scan). + +1. Install Pi by following the instructions at [pi.dev](https://pi.dev). +2. Log in with your subscription using Pi's [subscription authentication guide](https://pi.dev/docs/latest/providers#subscriptions). This creates `~/.pi/agent/auth.json` with an `openai-codex` entry. + +3. Select a Codex model and enable Pi authentication: + + ```bash + export SHANNON_USE_PI_AUTH=1 + export SHANNON_AI_MODEL=openai-codex:gpt-5.5 + ``` + +4. In npx mode, run `npx @keygraph/shannon start ...` from the same shell. In source-build mode, add the two variables to `.env` and run `./shannon start ...`. + +Supported Codex models are `gpt-5.6-sol`, `gpt-5.5`, and `gpt-5.4`. + +## Claude Code subscription + +The latest version of Shannon does not support Claude Code subscriptions. The [`shannon-v1`](https://github.com/KeygraphHQ/shannon/tree/shannon-v1) branch is the final release built on the Claude Agent SDK and supports Claude Code OAuth. + +Before running a pentest, review the [cyber safeguards requirements](#cyber-safeguards-do-this-before-your-first-scan). + +1. Generate a Claude Code OAuth token: + + ```bash + claude setup-token + ``` + +2. Run the setup flow for the final `shannon-v1` release: + + ```bash + npx @keygraph/shannon@1.9.0 setup + ``` + +3. Select **OAuth Token** and enter the token generated by Claude Code. +4. Start the pentest with `npx @keygraph/shannon@1.9.0 start ...`. + +These instructions apply only to `shannon-v1`. + ## Validation 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. -- **Credential presence** — always validated for the selected provider. +- **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. ## Migrating from the three-tier configuration diff --git a/entrypoint.sh b/entrypoint.sh index 3173743..2661ba0 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -12,7 +12,7 @@ if [ -n "$TARGET_UID" ] && [ "$TARGET_UID" != "$CURRENT_UID" ]; then groupadd -g "$TARGET_GID" pentest useradd -u "$TARGET_UID" -g pentest -s /bin/bash -M pentest - chown -R pentest:pentest /app/sessions /app/workspaces /tmp/.claude + chown -R pentest:pentest /app/sessions /app/workspaces /tmp/.claude /tmp/.pi fi exec su -m pentest -c "exec $*"