Skip to content

fix(rules): add np.stripe.3 for publishable API keys (pk_live_/pk_test_)#215

Merged
michaelweber merged 1 commit intomainfrom
fix/publishable-key-false-positive
Apr 28, 2026
Merged

fix(rules): add np.stripe.3 for publishable API keys (pk_live_/pk_test_)#215
michaelweber merged 1 commit intomainfrom
fix/publishable-key-false-positive

Conversation

@michaelweber
Copy link
Copy Markdown
Collaborator

Fixes #214

Problem

Stripe (and Moonpay) publishable keys (pk_live_*, pk_test_*) were flagged by np.generic.2 (Generic API Key) when found in contexts like apiKey=pk_live_.... These keys are intentionally public — designed to be embedded in browser/mobile code. They cannot initiate charges or access user data. Treating them as secrets generates noise.

stripe.yml already had a FIXME acknowledging this gap.

Approach

Add explicit np.stripe.3 (base_score: 5 — info tier) rather than suppressing the pattern in the generic rule. Explicit beats implicit.

The rule:

  • Requires the pk_live_/pk_test_ prefix explicitly
  • Includes the same keyword context (api_key, apikey, etc.) as np.generic.2 so both rules fire in the same scenario
  • Has a longer, more specific pattern → cross-rule dedup's patternLen tiebreaker selects this rule, labelling the finding "Stripe Publishable API Key" rather than "Generic API Key"
  • Named capture group key captures the same bytes as np.generic.2's positional capture → they're clustered for dedup

Result

Rule: Stripe Publishable API Key (np.stripe.3)
Score: 5/100 — info

The finding description tells operators this is expected and normally not worth remediating unless found alongside a sk_live_* key.

Test plan

  • go test ./pkg/rule/... — 131 tests pass
  • make test — all packages green
  • titus-score-lint — all rules valid

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@github-actions
Copy link
Copy Markdown

Claude Review

Critical issues

None.

Security

No security concerns flagged. Rule correctly classifies pk_live_*/pk_test_* as informational (base_score: 5); negative example confirms sk_live_* is excluded by the pk_ prefix in the named capture.

Test coverage

YAML rule change is covered by the existing rule loader/lint tests (go test ./pkg/rule/..., titus-score-lint) per the PR description; no production Go code changed.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5573562d-8bdb-4b53-8338-bea8051a4bc6

📥 Commits

Reviewing files that changed from the base of the PR and between 4a8f24e and acf6be3.

📒 Files selected for processing (2)
  • pkg/rule/rules/stripe.yml
  • pkg/rule/rulesets/default.yml
✅ Files skipped from review due to trivial changes (1)
  • pkg/rule/rulesets/default.yml

Walkthrough

Adds a new Stripe detection rule np.stripe.3 to the security rules configuration to detect publishable API keys by matching pk_live_ and pk_test_ patterns with contextual keywords (e.g., publishable_key, api_key). The rule uses regex with minimum length checks, classifies findings as type api and severity informational, and includes references, positive examples, and negative test cases. Changes add ~49 lines to pkg/rule/rules/stripe.yml and update the default ruleset to include the new rule.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/publishable-key-false-positive

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

Gemini Review

The PR correctly adds an informational rule for Stripe publishable keys to reduce false positives from generic API key rules. The regex and rule configuration are well-formed and effectively address the issue.

Critical Issues

None.

Security

No security concerns flagged.

Suggestions

  • The (?i) flag makes the pk_live_/pk_test_ prefix case-insensitive. While harmless, Stripe prefixes are strictly lowercase. You could restrict case-insensitivity to the keyword prefix and use [a-zA-Z0-9] for the key payload for stricter matching. (pkg/rule/rules/stripe.yml)
  • The .{0,3} and [ \t]* tokens overlap in what they can match (e.g., spaces). While the backtracking is bounded and likely won't cause performance issues, it's generally best practice to avoid overlapping adjacent quantifiers in regex. (pkg/rule/rules/stripe.yml)

Reviewed by Gemini (gemini-3.1-pro-preview)

@github-actions
Copy link
Copy Markdown

Codex Review

Critical Issues

  • pkg/rule/rules/stripe.yml: np.stripe.3 depends on cross-rule dedupe with np.generic.2, but the new rule was not added to any built-in ruleset. In default, np.generic.2 is included while np.stripe.3 is not (pkg/rule/rulesets/default.yml); np.assets also omits it (pkg/rule/rulesets/np.assets.yml). On any ruleset-driven scan, publishable Stripe keys will still surface as generic secret findings instead of the intended low-severity informational Stripe finding.
  • pkg/rule/rules/stripe.yml: the keyword context only allows publishable_key|api_key|access_key, so common Stripe frontend forms like stripe_key are still missed. The repo already contains that exact shape in extension/test/test-secrets.html, and neither this new rule nor np.generic.2 will match it.

Security

No security concerns flagged.

Suggestions

  • Add np.stripe.3 to pkg/rule/rulesets/np.assets.yml, not default.yml, so ruleset-based scans can report it without reclassifying public keys as secrets.
  • Add a regression test for both STRIPE_PUBLISHABLE_KEY=... and stripe_key: "pk_live_..." so future rule edits don’t silently lose either form.

Reviewed by Codex (gpt-5.4)

Stripe (and Moonpay) publishable keys are intentionally designed to be
embedded in client-side code — they identify a merchant account for
payment-form display only and cannot initiate charges or access user
data. Flagging them as secrets produces false positives (issue #214).

Add np.stripe.3 (base_score: 5, informational) with an explicit pattern
that requires the pk_live_ or pk_test_ prefix. The rule includes the
same keyword context (api_key, apikey, etc.) as np.generic.2 so that
cross-rule deduplication clusters both matches and selects this more
specific rule (longer pattern, named capture group) as the winner,
correctly labelling the finding "Stripe Publishable API Key" rather
than "Generic API Key".

Also resolves the long-standing FIXME comment in stripe.yml that
acknowledged this gap.

Fixes #214
@michaelweber michaelweber force-pushed the fix/publishable-key-false-positive branch from 4a8f24e to acf6be3 Compare April 28, 2026 15:14
@michaelweber
Copy link
Copy Markdown
Collaborator Author

Root cause found and fixed — default ruleset was the missing piece

The Codex feedback that np.generic.2 would still fire was correct. Here's the full picture:

Why the initial approach didn't work:
The default ruleset (pkg/rule/rulesets/default.yml) is an allowlist of the ~466 rules that actually run during a scan. np.stripe.3 wasn't in it, so the scan never evaluated it — leaving np.generic.2 as the only matching rule.

How the cross-rule dedup mechanism works (and why it's the right approach):
When both np.stripe.3 and np.generic.2 are active in the same scan, the cross-rule deduplicator clusters them (they both capture the same pk_live_xxx bytes as their group value) and picks one winner. The winner is selected by: hasValidatorgroupCountgroupsLenpatternLenruleID. Since np.stripe.3's pattern (203 chars) is longer than np.generic.2's (167 chars), np.stripe.3 wins — and the finding is labeled "Stripe Publishable API Key" with score 5 (info) instead of "Generic API Key" with score 50 (medium).

Verified empirically:

[scan] Starting scan with 467 rules
→ np.stripe.3 | Stripe Publishable API Key  ← wins over np.generic.2

This commit adds:

  1. np.stripe.3 rule in stripe.yml (pattern + examples + negative_examples + description)
  2. np.stripe.3 entry in pkg/rule/rulesets/default.yml
  3. Resolves the long-standing FIXME comment in stripe.yml

1133 tests pass.

@michaelweber
Copy link
Copy Markdown
Collaborator Author

Codex Review

Critical Issues

  • pkg/rule/rules/stripe.yml: np.stripe.3 depends on cross-rule dedupe with np.generic.2, but the new rule was not added to any built-in ruleset. In default, np.generic.2 is included while np.stripe.3 is not (pkg/rule/rulesets/default.yml); np.assets also omits it (pkg/rule/rulesets/np.assets.yml). On any ruleset-driven scan, publishable Stripe keys will still surface as generic secret findings instead of the intended low-severity informational Stripe finding.
  • pkg/rule/rules/stripe.yml: the keyword context only allows publishable_key|api_key|access_key, so common Stripe frontend forms like stripe_key are still missed. The repo already contains that exact shape in extension/test/test-secrets.html, and neither this new rule nor np.generic.2 will match it.

Security

No security concerns flagged.

Suggestions

  • Add np.stripe.3 to pkg/rule/rulesets/np.assets.yml, not default.yml, so ruleset-based scans can report it without reclassifying public keys as secrets.
  • Add a regression test for both STRIPE_PUBLISHABLE_KEY=... and stripe_key: "pk_live_..." so future rule edits don’t silently lose either form.

Reviewed by Codex (gpt-5.4)

I needed to add np.stripe.3 to the default ruleset to make sure it wouldn't ALSO be triggered with the np.generic.2 rule. Now it's appropriately identified.

@michaelweber michaelweber merged commit 712cb77 into main Apr 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reduce false positive on publishable API keys with format pk_live_*

1 participant