Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/hooks/block_inline_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
(r"xox[baprs]-[A-Za-z0-9-]{10,}", "Slack token"),
(r"[sr]k_(live|test)_[A-Za-z0-9]{24,}", "Stripe key"),
(r"lin_api_[A-Za-z0-9]{43}", "Linear API key"),
(r"AIza[0-9A-Za-z_-]{35}", "Google API key"),
(r"glpat-[A-Za-z0-9_-]{20}", "GitLab personal access token"),
(r"dp\.(pt|st|sa|ct|scim|audit)\.[A-Za-z0-9]{40,}", "Doppler token"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match inline Doppler service tokens' config segment

For inline command blocking, this has the same service-token gap: Doppler service tokens are shaped like dp.st.<config>.<secret> (for example the docs use dp.st.prd.xxxx), but the regex requires the secret material immediately after dp.st.. Inline commands such as export DOPPLER_TOKEN='dp.st.prd....' can therefore be approved and persisted to settings even though service tokens are the documented production path.

Useful? React with 👍 / 👎.

(r"-----BEGIN[A-Z ]*PRIVATE KEY-----", "private key"),
]

Expand Down
4 changes: 4 additions & 0 deletions script/security-credential-scan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ PATTERNS["Supabase Key"]="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]+\
# Note: Vercel Token pattern removed (too broad, causes false positives)
PATTERNS["Linear API Key"]="lin_api_[a-zA-Z0-9]{43}"
PATTERNS["Sentry DSN"]="https://[a-f0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io/[0-9]+"
PATTERNS["GitLab PAT"]="glpat-[a-zA-Z0-9_-]{20}"
PATTERNS["Doppler Token"]="dp\\.(pt|st|sa|ct|scim|audit)\\.[a-zA-Z0-9]{40,}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match Doppler service tokens' config segment

Doppler service tokens include the config name between dp.st and the random token material (the official service-token docs show values shaped like dp.st.prd.xxxx), so this pattern only catches personal-style dp.pt.<secret> tokens and misses the documented production/CI form dp.st.<config>.<secret>. That leaves leaked service tokens in .claude/settings*.json or other scanned files unreported even though this change marks Doppler tokens as critical.

Useful? React with 👍 / 👎.


# Generic Patterns
PATTERNS["Private Key"]="-----BEGIN.*PRIVATE KEY-----"
Expand Down Expand Up @@ -222,6 +224,8 @@ record_finding() {
[[ "$pattern_name" == *"Stripe"* ]] || \
[[ "$pattern_name" == *"Supabase"* ]] || \
[[ "$pattern_name" == *"Slack Token"* ]] || \
[[ "$pattern_name" == *"GitLab"* ]] || \
[[ "$pattern_name" == *"Doppler"* ]] || \
[[ "$pattern_name" == *"Database URL"* ]]; then
severity="CRITICAL"
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
Expand Down
12 changes: 12 additions & 0 deletions test/hooks-command-safety.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ describe('Claude Code command-safety hooks', () => {
expect(content).toContain('sk-ant-');
});

test('should detect Google API keys', () => {
expect(content).toContain('AIza');
});

test('should detect GitLab personal access tokens', () => {
expect(content).toContain('glpat-');
});

test('should detect Doppler tokens', () => {
expect(content).toContain('Doppler token');
});
Comment on lines +153 to +155

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert the Doppler regex marker, not only the label text.

Line 154 validates "Doppler token" only. If the regex is accidentally weakened but the label stays, this test still passes.

💡 Suggested test hardening
 test('should detect Doppler tokens', () => {
-  expect(content).toContain('Doppler token');
+  expect(content).toContain('dp\\.(pt|st|sa|ct|scim|audit)\\.');
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/hooks-command-safety.test.js` around lines 153 - 155, The test 'should
detect Doppler tokens' currently only asserts the label text via
expect(content).toContain('Doppler token'); update it to assert the actual
Doppler regex marker instead of just the label by changing the assertion on the
content variable to match the regex pattern (use expect(content).toMatch(...)
with the Doppler detection regex or the exact marker string used in the
scanner). Locate the test named "should detect Doppler tokens" and replace the
toContain check on content with a toMatch against the canonical Doppler
regex/marker used by the hook scanner so the test fails if the regex is
weakened.


test('should detect private key blocks', () => {
expect(content).toContain('PRIVATE KEY');
});
Expand Down
24 changes: 24 additions & 0 deletions test/integration/security-scripts.bats
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,30 @@ JS
assert_output --partial "Azure Service Principal"
}

@test "security-credential-scan.sh detects GitLab and Doppler tokens" {
mkdir -p "$TEST_TEMP_DIR/.claude"
# Build tokens at runtime so no literal secret pattern lives in the source
# (GitHub push protection blocks pushes that contain valid token shapes).
gl="glpat-$(printf 'x%.0s' $(seq 20))"
dp="dp.pt.$(printf 'x%.0s' $(seq 40))"
cat > "$TEST_TEMP_DIR/.claude/settings.local.json" <<JSON
{
"permissions": {
"allow": [
"Bash(export GITLAB_TOKEN=\"$gl\")",
"Bash(export DOPPLER_TOKEN=\"$dp\")"
]
}
}
JSON

run "$REPO_ROOT/script/security-credential-scan.sh" --path "$TEST_TEMP_DIR" --json
assert_success
echo "$output" | jq -e '.critical_count >= 2' >/dev/null
assert_output --partial "GitLab PAT"
assert_output --partial "Doppler Token"
}

@test "security-credential-scan.sh ignores Nix flake.lock rev hashes" {
mkdir -p "$TEST_TEMP_DIR/nix"
cat > "$TEST_TEMP_DIR/nix/flake.lock" <<'JSON'
Expand Down
Loading