feat: add client-secret-exposure-audit skill - #1422
Conversation
Defensive, read-only workflow to audit a deployed web app for secrets and sensitive files exposed to the browser: hardcoded API keys/tokens in JS, secrets in HTML meta/data-*/comments, publicly reachable source/config/deploy files, and header/CORS misconfiguration. Maps to OWASP A02/A05 and CWE-798/200/540. Includes a worked example-report.md.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings affect request scope, redaction, bundle coverage, file verification, and CORS guidance.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a defensive, read-only skill for auditing browser-exposed secrets and sensitive deployment files.
Changes:
- Adds a six-phase GET-only audit workflow.
- Documents secret patterns, severity, safety, and remediation guidance.
- Adds a worked example report.
File summaries
| File | Description |
|---|---|
skills/client-secret-exposure-audit/SKILL.md |
Defines the audit workflow, commands, and security guidance. |
skills/client-secret-exposure-audit/example-report.md |
Demonstrates findings and remediation reporting. |
Review details
Suppressed comments (5)
skills/client-secret-exposure-audit/SKILL.md:98
- The workflow says to scan sourcemaps, but the preceding commands only discover and download
.jsURLs; they never inspectsourceMappingURLcomments or fetch*.js.mapfiles. An exposed map can therefore contain the very server-side source/secrets this audit is meant to find without being scanned.
Also scan any sourcemaps (`*.js.map`) — they can rebuild original source with
comments intact.
skills/client-secret-exposure-audit/SKILL.md:154
- This copy-paste example sends a request to a real third-party host even though the skill requires explicit authorization for exact hostnames. A site being public or intentionally vulnerable does not establish permission; use a reserved placeholder/local target here and label the output as illustrative.
BASE="https://demo-for-opensource.vercel.app"; curl -s "$BASE/" -o body.html
grep -inE "recaptcha-secret|data-aws-secret|data-webhook-secret|mongodb\+srv" body.html
# -> meta recaptcha-secret=..., data-aws-access=AKIA…/data-aws-secret=…,
# data-webhook-secret=whsec_…, and a hidden JSON blob with a mongodb+srv URI
# (username:password@cluster). All reachable with a single unauthenticated GET.
skills/client-secret-exposure-audit/SKILL.md:94
- This is the only JavaScript grep, but the severity table and worked report also claim findings for
Math.random(),X-Rate-Limit-Bypass, and secret logging. Following the documented commands cannot discover those findings; add explicit patterns here or remove those out-of-scope findings from the workflow/report.
grep -rinE "secret|password|api[_-]?key|token|bearer|sk_(live|test)|pk_(live|test)|\
whsec_|akia|aiza|jwt|signing[_-]?key|admin[_-]?token|mongodb|redis://" js_* 2>/dev/null
skills/client-secret-exposure-audit/example-report.md:1
- The repository's approval-safe path policy only recognizes
skills/<id>/SKILL.mdand support files underassets/,references/, orresources/(tools/lib/workflow-contract.js:28-31). A siblingexample-report.mdis therefore classified as an unapproved path, so this source-only skill change cannot use the normal approval-safe flow; move the report underreferences/(and update the links).
# Example Audit Report — Client-Side Secret & Sensitive-File Exposure
skills/client-secret-exposure-audit/example-report.md:69
- This evidence does not confirm that the paths are real files: a custom error page can have a different size, and a real file can share the fallback size. Present size differences as candidates and verify the response before claiming the deployment exposes these files.
- **Evidence:** Each returns a distinct body size from the SPA catch-all page and
contains real source/config (route definitions, internal service tokens such as
- Files reviewed: 2/2 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| grep -inE "secret|passwd|password|api[_-]?key|apikey|token|bearer|authorization|\ | ||
| akia|sk_live|sk_test|pk_live|whsec_|ghp_|aiza|private[_-]?key|mongodb(\+srv)?://|\ | ||
| data-[a-z-]*(secret|token|key|access)" body.html |
| # extract script srcs, resolve relative paths against $BASE, fetch and scan | ||
| grep -ioE 'src="[^"]+\.js"' body.html | sed -E 's/^src="//; s/"$//' \ | ||
| | while read -r p; do | ||
| u="$p"; case "$p" in http*) ;; /*) u="$BASE$p";; *) u="$BASE/$p";; esac |
| grep -ioE 'src="[^"]+\.js"' body.html | sed -E 's/^src="//; s/"$//' \ | ||
| | while read -r p; do |
| read -r code size < <(curl -s -o /dev/null -w "%{http_code} %{size_download}" "$BASE$p") | ||
| [ "$code" = "200" ] && [ "$size" != "$FALLBACK" ] && echo "REAL FILE $code $size $p" | ||
| done |
| |---|---| | ||
| | **Critical** | Live provider secret keys (`sk_live_`, cloud `AKIA…`+secret, DB URI with password, private signing/JWT secret, admin bearer token) reachable anonymously | | ||
| | **High** | Server-side source/config/deploy files exposed; test-mode secret keys; internal service tokens; webhook signing secrets | | ||
| | **Medium** | CORS `*`, missing CSP/security headers, verbose banners, weak randomness for security values (`Math.random()` for tokens/refs) | |
| - **Impact:** Any origin can read responses; dangerous if any endpoint returns | ||
| user/authenticated data. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e728c51e05
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ### Step 1: Fetch the page and inspect response headers | ||
|
|
||
| ```bash | ||
| curl -s -D headers.txt -o body.html "$BASE/" |
There was a problem hiding this comment.
Follow redirects before scanning the target
When an authorized hostname redirects, such as an apex domain redirecting to www, Step 1 saves the redirect response rather than the application, and the later bundle and file probes continue using the original host. curl --help all documents -L, --location as “Follow redirects”; without it, the HTML scan can be empty and Step 4 discards redirect responses because they are not 200, yielding a false-clean audit. Follow redirects consistently and evaluate the final URL/status.
AGENTS.md reference: AGENTS.md:L54-L54
Useful? React with 👍 / 👎.
|
|
||
| ```bash | ||
| # extract script srcs, resolve relative paths against $BASE, fetch and scan | ||
| grep -ioE 'src="[^"]+\.js"' body.html | sed -E 's/^src="//; s/"$//' \ |
There was a problem hiding this comment.
Traverse JavaScript beyond bare double-quoted URLs
On common builds where script URLs contain cache-busting queries such as app.js?v=1, use single-quoted attributes, or load route-split chunks through dynamic imports, this expression omits browser-loaded JavaScript because it only accepts src="...js" from the initial HTML. Secrets in those omitted bundles therefore produce a false-clean result despite the section claiming to pull every bundle; include query/quote variants and recursively discover preload, manifest, and imported chunks.
AGENTS.md reference: AGENTS.md:L54-L54
Useful? React with 👍 / 👎.
| read -r code size < <(curl -s -o /dev/null -w "%{http_code} %{size_download}" "$BASE$p") | ||
| [ "$code" = "200" ] && [ "$size" != "$FALLBACK" ] && echo "REAL FILE $code $size $p" |
There was a problem hiding this comment.
Verify fallback content before calling a path real
When a 200 catch-all embeds the requested path, a request ID, a nonce, or other dynamic content, its size differs between requests, so this condition labels every probed path as a real exposed file even though each is still the fallback; a real file with the same byte count is missed conversely. A length mismatch is not proof of file identity, so fetch candidates and validate their content/type or compare normalized bodies before reporting exposure.
AGENTS.md reference: AGENTS.md:L54-L54
Useful? React with 👍 / 👎.
| - `access-control-allow-origin: *` — permissive CORS (worse when paired with | ||
| credentials). |
There was a problem hiding this comment.
Require exploitable context before flagging wildcard CORS
For the public page inspected in Step 1, Access-Control-Allow-Origin: * only permits cross-origin reading of data already anonymously available, while browsers reject wildcard origins for credentialed requests. Flagging the header by itself—and later rating it Medium—therefore creates a false vulnerability finding; require a sensitive response plus an exploitable origin policy, such as reflected or weakly matched origins with credentials, consistent with skills/cloudflare-security-audit/references/CLIENT-SIDE.md:36-37.
AGENTS.md reference: AGENTS.md:L54-L54
Useful? React with 👍 / 👎.
What
Adds a new
client-secret-exposure-auditskill underskills/client-secret-exposure-audit/— a defensive, read-only workflow forfinding secrets and sensitive files that a deployed web app exposes to the
browser.
Why
The catalog has
web-security-testingfor the broad OWASP Top 10, but nothingfocused on the very common, high-impact case of credentials leaking into the
client surface: hardcoded keys/tokens in JS, secrets in HTML
meta/data-*/comments, and server-side source/config/deploy files leftpublicly reachable behind an SPA catch-all. This skill fills that gap.
Maps to OWASP A02:2021 (Cryptographic Failures / sensitive data exposure) and
A05:2021 (Security Misconfiguration); CWE-798, CWE-200, CWE-540, CWE-942,
CWE-338.
Contents
SKILL.md— 6-phase workflow (headers → HTML → JS bundles → source/config/deployfile probing with SPA-fallback size baselining → triage/severity → reporting),
a secret-pattern grep set, a severity table, and generalized
curl/grepcommands driven by a single
$BASEvariable so it works on any authorizedtarget.
example-report.md— a full worked report format, produced by running the skillagainst a public, intentionally-vulnerable teaching demo
(
demo-for-opensource.vercel.app, whose own source states its secrets are fake).Safety
risk: safe— the workflow performs only unauthenticated GET requests forresources the server already serves publicly; no exploitation, brute force, or
mutation.
in the Security & Safety Notes section.
Validation
python3 tools/scripts/validate_skills.py→ all skills passpython3 tools/scripts/validate_skills.py --strict→ all skills passnode tools/scripts/tests/docs_security_content.test.js→ exit 0Source-only PR: no generated registry artifacts (
CATALOG.md,skills_index.json,data/*.json) included.🤖 Generated with Claude Code
https://claude.ai/code/session_015PmSa2TM63N7R5mGCzVriS