A collaborative session between a developer and Claude — diagnosing a false positive in a security scanner's output, then designing and agreeing on a feature to fix it systematically. The human drives all decisions; Claude researches, proposes, and structures.
Please read the PRISM Scout report and see the critical warning about API. I tried independently verifying if this is an actual exposure or not. I tried using developer tools to locate the file, but I can't find that either. Can you independently verify the finding is true, or a false positive?
cdn.<redacted>/agent/static/…/<redacted>.js 533.7 KB · 200 OK
Fetch https://app.<redacted> 3.7 KB · 200 OK
The critical finding is in <redacted>.js from cdn.<redacted>. Let me fetch that file to verify what's actually there.
The <redacted>.js file itself has no hardcoded credentials — that's expected. But the scanner found the apiKey somewhere in it. The real question is whether this is a subscription/agent key (which is intentionally public) or something else. Let me check how it's initialized on the actual site.
This is a false positive. Here's the full picture:
The "exposed credential" is the <redacted> subscription key (agent key). It's called in the site's JS as:
})('<redacted-uuid>');
This is intentionally public by design:
<script> tag.The [REDACTED] in the report is PRISM Scout masking the value for display, not something the site is hiding. The value is fully visible on the page.
apiKey variable name pattern in the vendor's initialization snippet and classifying it as a generic secret. It should be tuned to recognize these subscription key patterns (UUID-format values passed to third-party loader functions) and downgrade or suppress them. Worth filing as a false-positive reduction task.
Can you research ways for the tool to verify false positives for something like apiKey? Maybe it gets sent to the LLM to independently verify before deciding if it is concern-worthy?
Good context gathered. I now understand the full pipeline well.
The Generic Secret Var pattern is the culprit — it's a broad catch-all (apiKey = "...") that inevitably hits public/intentional keys. The more specific patterns (AWS AKIA prefix, OpenAI sk-, Stripe sk_live_) are rarely false positives.
Should false-positive verification apply broadly to all secret patterns, or only to the noisy catch-all (Generic Secret Var)?
My instinct is to scope it narrowly — patterns like AWS Access Key, OpenAI API Key, GitHub Token are almost never wrong, while Generic Secret Var is inherently fuzzy. Targeting just the fuzzy pattern keeps the change focused.
Targeting the fuzzy patterns sounds like the smart move in this case.
Should false-positive verification work even when no LLM is configured?
Right now, if LLM_BACKEND isn't set, Phase 5 scoring is skipped entirely. We could build a purely deterministic layer (CDN allowlists, UUID detection, variable name analysis) that always runs, and optionally layer LLM verification on top. Or we could make it LLM-only and just skip verification when no LLM is available.
For now, LLM only. No unnecessary complexity, and I'll always tie in a local LLM.
Where in the pipeline should verification happen? There are two natural spots:
secret_detector.run()) — each fuzzy match gets verified before it becomes a Finding. The scanner itself becomes "smart" and self-filters.Let's add a verification step. As I continue to test, I will find more things that could benefit from it.
What should happen to a finding the LLM determines is a false positive — should it be dropped entirely from the report, or kept but downgraded/flagged?
If the verification concludes it's purely intentional, it could maybe include it as part of a 'tech stack' finding, but should not count towards an increased vulnerability score.
Should the LLM verify fuzzy findings one at a time, or in a single batch call?
Batching is faster and cheaper, but per-finding calls give the LLM more focused context and makes it easier to add per-finding reasoning to the output.
Let's go 'per-finding'. We want to be good, not fast.
Perfect. I have everything I need. Here are three approaches:
My recommendation is Option A. It keeps the LLM agent layer cohesive, the verification logic is isolated and testable, and FUZZY_PATTERNS gives you a clean extension point as you find more patterns that need this treatment.
Option A, I agree with you here. The separate verification layer will be useful later, I just know it.
Design — Section 1: Architecture & Data Flow
The pipeline gains a new Phase 4.5: Secret Verification between subscan deduplication and LLM scoring:
secret_detector.py exports a FUZZY_PATTERNS set — initially just {"Generic Secret Var"}. Any finding whose title contains one of these pattern names is a candidate for verification.
The new prism_scout/agent/verifier.py exposes one public coroutine:
async def verify_secret_finding(finding: Finding, llm: LLMClient) -> Finding
It sends a focused prompt to the LLM with the finding's pattern name, masked value, file URL, and context snippet. The LLM returns structured JSON:
{
"is_intentional_public_key": true,
"service_name": "<redacted> Analytics",
"reasoning": "..."
}
If is_intentional_public_key is true, the verifier reclassifies the finding in-place: category → TECH, severity → INFO, title becomes "Third-Party Tracking: <service>". If false, the finding is returned unchanged (remains CRITICAL).
The orchestrator loops through fuzzy findings sequentially, skips verification if llm.is_available is False, and logs each decision.
This approach looks perfect.
Design — Section 2: Verification Prompt
A new SECRET_VERIFICATION_PROMPT in prompts.py. The LLM receives all the context needed to make a confident judgment:
You are reviewing a potential credential exposure found in
client-side JavaScript.
Finding details:
- Pattern matched: {pattern_name}
- Code context: {context}
- File URL: {file_path}
- Masked value: {masked_value}
Determine whether this is an intentionally public client-side
key or a genuinely sensitive credential that should not be
exposed.
Intentionally public keys are designed to be in client-side
code — examples: [vendor] agent keys, Google Analytics IDs,
Mixpanel project tokens, Segment write keys, Intercom app IDs.
Genuinely sensitive credentials include: secret API keys,
service account tokens, database passwords, private keys.
Output JSON only, no prose:
{
"is_intentional_public_key": <true|false>,
"service_name": "<service name, or null>",
"reasoning": "<one sentence>"
}
The context field passes the code snippet that secret_detector already captures — giving the LLM the variable name and surrounding code.
This will be fine. I'll be using a local LLM, so exposure isn't really a concern. I just need the LLM to know where to look and what to verify.
Noted — and since it's a local LLM we can pass the unmasked context snippet for better accuracy. The prompt just needs to be clear and directive, not defensive about exposure.
Design — Section 3: Extension Point
FUZZY_PATTERNS in secret_detector.py is the single control surface for routing findings to verification:
# Any pattern name listed here is routed through Phase 4.5.
FUZZY_PATTERNS: set[str] = {
"Generic Secret Var",
}
Any future pattern added here automatically gets the verification treatment in Phase 4.5 — no orchestrator changes needed.
That covers the full design. Ready to write the spec doc and move to implementation planning?
Let's do it.
docs/superpowers/specs/2026-03-26-secret-verification-phase-design.md
git commit aad752f
Spec written and committed to docs/superpowers/specs/2026-03-26-secret-verification-phase-design.md. Please review it and let me know if you want to make any changes before we start writing out the implementation plan.