diff --git a/.detect-secrets.cfg b/.detect-secrets.cfg new file mode 100644 index 000000000..66ed5236e --- /dev/null +++ b/.detect-secrets.cfg @@ -0,0 +1,26 @@ +# detect-secrets exclusion patterns (regex) +# +# Note: detect-secrets does not read this file by default. If you want these +# applied, wire them into your scan command (e.g. translate to --exclude-files +# / --exclude-lines) or into a baseline's filters_used. + +[exclude-files] +# pnpm lockfiles contain lots of high-entropy package integrity blobs. +pattern = (^|/)pnpm-lock\.yaml$ + +[exclude-lines] +# Fastlane checks for private key marker; not a real key. +pattern = key_content\.include\?\("BEGIN PRIVATE KEY"\) +# UI label string for Anthropic auth mode. +pattern = case \.apiKeyEnv: "API key \(env var\)" +# CodingKeys mapping uses apiKey literal. +pattern = case apikey = "apiKey" +# Schema labels referencing password fields (not actual secrets). +pattern = "gateway\.remote\.password" +pattern = "gateway\.auth\.password" +# Schema label for talk API key (label text only). +pattern = "talk\.apiKey" +# checking for typeof is not something we care about. +pattern = === "string" +# specific optional-chaining password check that didn't match the line above. +pattern = typeof remote\?\.password === "string" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e741444b8..be0d8926f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,6 +141,31 @@ jobs: - name: Run ${{ matrix.task }} (${{ matrix.runtime }}) run: ${{ matrix.command }} + secrets: + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: false + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install detect-secrets + run: | + python -m pip install --upgrade pip + python -m pip install detect-secrets==1.5.0 + + - name: Detect secrets + run: | + if ! detect-secrets scan --baseline .secrets.baseline; then + echo "::error::Secret scanning failed. See docs/gateway/security.md#secret-scanning-detect-secrets" + exit 1 + fi + checks-windows: runs-on: blacksmith-4vcpu-windows-2025 defaults: diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 000000000..4c0ca50a4 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,518 @@ +{ + "version": "1.5.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "GitLabTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "IPPublicDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "OpenAIDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "PypiTokenDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TelegramBotTokenDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + }, + { + "path": "detect_secrets.filters.regex.should_exclude_file", + "pattern": [ + "(^|/)pnpm-lock\\.yaml$" + ] + }, + { + "path": "detect_secrets.filters.regex.should_exclude_line", + "pattern": [ + "key_content\\.include\\?\\(\"BEGIN PRIVATE KEY\"\\)", + "case \\.apiKeyEnv: \"API key \\(env var\\)\"", + "case apikey = \"apiKey\"", + "\"gateway\\.remote\\.password\"", + "\"gateway\\.auth\\.password\"", + "\"talk\\.apiKey\"", + "=== \"string\"", + "typeof remote\\?\\.password === \"string\"" + ] + } + ], + "results": { + ".env.example": [ + { + "type": "Twilio API Key", + "filename": ".env.example", + "hashed_secret": "3c7206eff845bc69cf12d904d0f95f9aec15535e", + "is_verified": false, + "line_number": 2 + } + ], + "appcast.xml": [ + { + "type": "Base64 High Entropy String", + "filename": "appcast.xml", + "hashed_secret": "1b1c2b73eca84e441a823c37a06c71c9fadcfe24", + "is_verified": false, + "line_number": 19 + }, + { + "type": "Base64 High Entropy String", + "filename": "appcast.xml", + "hashed_secret": "5c47736fee5151b26b3bb61bb38955da0e8937c6", + "is_verified": false, + "line_number": 35 + }, + { + "type": "Base64 High Entropy String", + "filename": "appcast.xml", + "hashed_secret": "bbbca47179268f154c63affa0ca441c6e49e650f", + "is_verified": false, + "line_number": 52 + } + ], + "apps/macos/Tests/ClawdbotIPCTests/AnthropicAuthResolverTests.swift": [ + { + "type": "Secret Keyword", + "filename": "apps/macos/Tests/ClawdbotIPCTests/AnthropicAuthResolverTests.swift", + "hashed_secret": "e761624445731fcb8b15da94343c6b92e507d190", + "is_verified": false, + "line_number": 26 + }, + { + "type": "Secret Keyword", + "filename": "apps/macos/Tests/ClawdbotIPCTests/AnthropicAuthResolverTests.swift", + "hashed_secret": "a23c8630c8a5fbaa21f095e0269c135c20d21689", + "is_verified": false, + "line_number": 42 + } + ], + "apps/macos/Tests/ClawdbotIPCTests/ConnectionsSettingsSmokeTests.swift": [ + { + "type": "Secret Keyword", + "filename": "apps/macos/Tests/ClawdbotIPCTests/ConnectionsSettingsSmokeTests.swift", + "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", + "is_verified": false, + "line_number": 83 + } + ], + "apps/macos/Tests/ClawdbotIPCTests/TailscaleIntegrationSectionTests.swift": [ + { + "type": "Secret Keyword", + "filename": "apps/macos/Tests/ClawdbotIPCTests/TailscaleIntegrationSectionTests.swift", + "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", + "is_verified": false, + "line_number": 27 + } + ], + "docs/configuration.md": [ + { + "type": "Secret Keyword", + "filename": "docs/configuration.md", + "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", + "is_verified": false, + "line_number": 268 + }, + { + "type": "Secret Keyword", + "filename": "docs/configuration.md", + "hashed_secret": "1188d5a8ed7edcff5144a9472af960243eacf12e", + "is_verified": false, + "line_number": 465 + }, + { + "type": "Secret Keyword", + "filename": "docs/configuration.md", + "hashed_secret": "22af290a1a3d5e941193a41a3d3a9e4ca8da5e27", + "is_verified": false, + "line_number": 718 + }, + { + "type": "Secret Keyword", + "filename": "docs/configuration.md", + "hashed_secret": "16c249e04e2be318050cb883c40137361c0c7209", + "is_verified": false, + "line_number": 760 + }, + { + "type": "Secret Keyword", + "filename": "docs/configuration.md", + "hashed_secret": "c1e6ee547fd492df1441ac492e8bb294974712bd", + "is_verified": false, + "line_number": 859 + }, + { + "type": "Secret Keyword", + "filename": "docs/configuration.md", + "hashed_secret": "45d676e7c6ab44cf4b8fa366ef2d8fccd3e6d6e6", + "is_verified": false, + "line_number": 982 + } + ], + "docs/faq.md": [ + { + "type": "Secret Keyword", + "filename": "docs/faq.md", + "hashed_secret": "a219d7693c25cd2d93313512e200ff3eb374d281", + "is_verified": false, + "line_number": 593 + }, + { + "type": "Secret Keyword", + "filename": "docs/faq.md", + "hashed_secret": "ec3810e10fb78db55ce38b9c18d1c3eb1db739e0", + "is_verified": false, + "line_number": 650 + } + ], + "docs/skills-config.md": [ + { + "type": "Secret Keyword", + "filename": "docs/skills-config.md", + "hashed_secret": "c1e6ee547fd492df1441ac492e8bb294974712bd", + "is_verified": false, + "line_number": 28 + } + ], + "docs/skills.md": [ + { + "type": "Secret Keyword", + "filename": "docs/skills.md", + "hashed_secret": "c1e6ee547fd492df1441ac492e8bb294974712bd", + "is_verified": false, + "line_number": 97 + } + ], + "docs/tailscale.md": [ + { + "type": "Secret Keyword", + "filename": "docs/tailscale.md", + "hashed_secret": "9cb0dc5383312aa15b9dc6745645bde18ff5ade9", + "is_verified": false, + "line_number": 52 + } + ], + "docs/talk.md": [ + { + "type": "Secret Keyword", + "filename": "docs/talk.md", + "hashed_secret": "1188d5a8ed7edcff5144a9472af960243eacf12e", + "is_verified": false, + "line_number": 50 + } + ], + "docs/telegram.md": [ + { + "type": "Secret Keyword", + "filename": "docs/telegram.md", + "hashed_secret": "e9fe51f94eadabf54dbf2fbbd57188b9abee436e", + "is_verified": false, + "line_number": 57 + } + ], + "skills/local-places/SERVER_README.md": [ + { + "type": "Secret Keyword", + "filename": "skills/local-places/SERVER_README.md", + "hashed_secret": "6d9c68c603e465077bdd49c62347fe54717f83a3", + "is_verified": false, + "line_number": 28 + } + ], + "skills/openai-whisper-api/SKILL.md": [ + { + "type": "Secret Keyword", + "filename": "skills/openai-whisper-api/SKILL.md", + "hashed_secret": "1077361f94d70e1ddcc7c6dc581a489532a81d03", + "is_verified": false, + "line_number": 39 + } + ], + "skills/trello/SKILL.md": [ + { + "type": "Secret Keyword", + "filename": "skills/trello/SKILL.md", + "hashed_secret": "11fa7c37d697f30e6aee828b4426a10f83ab2380", + "is_verified": false, + "line_number": 18 + } + ], + "src/agents/models-config.test.ts": [ + { + "type": "Secret Keyword", + "filename": "src/agents/models-config.test.ts", + "hashed_secret": "7cf31e8b6cda49f70c31f1f25af05d46f924142d", + "is_verified": false, + "line_number": 25 + }, + { + "type": "Secret Keyword", + "filename": "src/agents/models-config.test.ts", + "hashed_secret": "3a81eb091f80c845232225be5663d270e90dacb7", + "is_verified": false, + "line_number": 90 + } + ], + "src/agents/skills.test.ts": [ + { + "type": "Secret Keyword", + "filename": "src/agents/skills.test.ts", + "hashed_secret": "3acfb2c2b433c0ea7ff107e33df91b18e52f960f", + "is_verified": false, + "line_number": 158 + }, + { + "type": "Secret Keyword", + "filename": "src/agents/skills.test.ts", + "hashed_secret": "7a85f4764bbd6daf1c3545efbbf0f279a6dc0beb", + "is_verified": false, + "line_number": 265 + }, + { + "type": "Secret Keyword", + "filename": "src/agents/skills.test.ts", + "hashed_secret": "5df3a673d724e8a1eb673a8baf623e183940804d", + "is_verified": false, + "line_number": 462 + }, + { + "type": "Secret Keyword", + "filename": "src/agents/skills.test.ts", + "hashed_secret": "8921daaa546693e52bc1f9c40bdcf15e816e0448", + "is_verified": false, + "line_number": 490 + } + ], + "src/browser/target-id.test.ts": [ + { + "type": "Hex High Entropy String", + "filename": "src/browser/target-id.test.ts", + "hashed_secret": "4e126c049580d66ca1549fa534d95a7263f27f46", + "is_verified": false, + "line_number": 16 + } + ], + "src/commands/antigravity-oauth.ts": [ + { + "type": "Base64 High Entropy String", + "filename": "src/commands/antigravity-oauth.ts", + "hashed_secret": "709d0f232b6ac4f8d24dec3e4fabfdb14257174f", + "is_verified": false, + "line_number": 17 + }, + { + "type": "Base64 High Entropy String", + "filename": "src/commands/antigravity-oauth.ts", + "hashed_secret": "3848603b8e866f62d07c206ff622279b9dcb0238", + "is_verified": false, + "line_number": 20 + } + ], + "src/commands/onboard-auth.ts": [ + { + "type": "Secret Keyword", + "filename": "src/commands/onboard-auth.ts", + "hashed_secret": "16c249e04e2be318050cb883c40137361c0c7209", + "is_verified": false, + "line_number": 50 + } + ], + "src/config/config.test.ts": [ + { + "type": "Secret Keyword", + "filename": "src/config/config.test.ts", + "hashed_secret": "bea2f7b64fab8d1d414d0449530b1e088d36d5b1", + "is_verified": false, + "line_number": 520 + } + ], + "src/gateway/server.auth.test.ts": [ + { + "type": "Secret Keyword", + "filename": "src/gateway/server.auth.test.ts", + "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", + "is_verified": false, + "line_number": 89 + }, + { + "type": "Secret Keyword", + "filename": "src/gateway/server.auth.test.ts", + "hashed_secret": "a4b48a81cdab1e1a5dd37907d6c85ca1c61ddc7c", + "is_verified": false, + "line_number": 109 + } + ], + "src/infra/env.test.ts": [ + { + "type": "Secret Keyword", + "filename": "src/infra/env.test.ts", + "hashed_secret": "df98a117ddabf85991b9fe0e268214dc0e1254dc", + "is_verified": false, + "line_number": 10 + }, + { + "type": "Secret Keyword", + "filename": "src/infra/env.test.ts", + "hashed_secret": "6d811dc1f59a55ca1a3d38b5042a062b9f79e8ec", + "is_verified": false, + "line_number": 25 + } + ], + "src/infra/shell-env.test.ts": [ + { + "type": "Secret Keyword", + "filename": "src/infra/shell-env.test.ts", + "hashed_secret": "65c10dc3549fe07424148a8a4790a3341ecbc253", + "is_verified": false, + "line_number": 35 + }, + { + "type": "Base64 High Entropy String", + "filename": "src/infra/shell-env.test.ts", + "hashed_secret": "64db6bf7f0e5a0491df4419f0eb1bbcc402989e8", + "is_verified": false, + "line_number": 56 + }, + { + "type": "Secret Keyword", + "filename": "src/infra/shell-env.test.ts", + "hashed_secret": "e013ffda590d2178607c16d11b1ea42f75ceb0e7", + "is_verified": false, + "line_number": 73 + }, + { + "type": "Base64 High Entropy String", + "filename": "src/infra/shell-env.test.ts", + "hashed_secret": "be6ee9a6bf9f2dad84a5a67d6c0576a5bacc391e", + "is_verified": false, + "line_number": 75 + } + ], + "src/web/qr-image.test.ts": [ + { + "type": "Hex High Entropy String", + "filename": "src/web/qr-image.test.ts", + "hashed_secret": "564666dc1ca6e7318b2d5feeb1ce7b5bf717411e", + "is_verified": false, + "line_number": 12 + } + ], + "vendor/a2ui/README.md": [ + { + "type": "Secret Keyword", + "filename": "vendor/a2ui/README.md", + "hashed_secret": "2619a5397a5d054dab3fe24e6a8da1fbd76ec3a6", + "is_verified": false, + "line_number": 123 + } + ] + }, + "generated_at": "2026-01-05T13:01:00Z" +} diff --git a/docs/gateway/security.md b/docs/gateway/security.md index 847590fae..82d534654 100644 --- a/docs/gateway/security.md +++ b/docs/gateway/security.md @@ -439,6 +439,32 @@ If your AI does something bad: - What the attacker sent + what the agent did - Whether the Gateway was exposed beyond loopback (LAN/Tailscale Funnel/Serve) +## Secret Scanning (detect-secrets) + +CI runs `detect-secrets scan --baseline .secrets.baseline` in the `secrets` job. +If it fails, there are new candidates not yet in the baseline. + +### If CI fails + +1. Reproduce locally: + ```bash + detect-secrets scan --baseline .secrets.baseline + ``` +2. Understand the tools: + - `detect-secrets scan` finds candidates and compares them to the baseline. + - `detect-secrets audit` opens an interactive review to mark each baseline + item as real or false positive. +3. For real secrets: rotate/remove them, then re-run the scan to update the baseline. +4. For false positives: run the interactive audit and mark them as false: + ```bash + detect-secrets audit .secrets.baseline + ``` +5. If you need new excludes, add them to `.detect-secrets.cfg` and regenerate the + baseline with matching `--exclude-files` / `--exclude-lines` flags (the config + file is reference-only; detect-secrets doesn’t read it automatically). + +Commit the updated `.secrets.baseline` once it reflects the intended state. + ## The Trust Hierarchy ``` diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md index 6e076782f..6be90c4cd 100644 --- a/docs/gateway/troubleshooting.md +++ b/docs/gateway/troubleshooting.md @@ -31,6 +31,11 @@ See also: [Health checks](/gateway/health) and [Logging](/logging). ## Common Issues +### CI Secrets Scan Failed + +This means `detect-secrets` found new candidates not yet in the baseline. +Follow [Secret scanning](/gateway/security#secret-scanning-detect-secrets). + ### Service Installed but Nothing is Running If the gateway service is installed but the process exits immediately, the daemon