Webapp security audit
Skill dustinkeeton/wafflestack/stacks/engineering-team/skills/webapp-security-audit
Security auditing for {{project.name}}, a browser-delivered web app. Covers dependency hygiene, XSS prevention, secret leakage in client bundles, validation of third-party data, CORS / mixed content, browser-storage safety, and CI workflow safety. Use when reviewing code for security vulnerabilities or setting up security guardrails.From its SKILL.md
npx -y skills add dustinkeeton/wafflestack --skill webapp-security-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.5 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Security Audit Standards
{{sec.threatModelIntro}}
Focus areas, in priority order:
Priority Areas
1. Dependency hygiene
- Run audit on every dep change:
{{sec.auditCmd}}(production deps; run the dev-inclusive variant as a separate report). - Vet new deps: for any package added in the diff, check on npmjs.com:
- Weekly downloads (≥ 10k unless niche)
- Last publish date (within 12 months)
- Maintainer count (single-maintainer = higher risk)
- GitHub repo activity (open issues, recent commits)
- Transitive bloat (
pnpm why <pkg>/npm ls <pkg>)
- Flag typo-squats — packages with names that are off-by-one from popular packages.
- Lockfile is the source of truth. Reject PRs that modify
package.jsonwithout updating{{project.lockfile}}.
2. XSS prevention
- Forbidden in JSX/templates unless explicitly justified:
dangerouslySetInnerHTML(or the framework's raw-HTML equivalent) — must have a comment justifying it AND show input is sanitized (DOMPurify or equivalent)<svg>constructed from variable stringsinnerHTML/outerHTMLassignments via refs
- URL injection:
href={...}andsrc={...}must validate that the value ishttps:or a relative path. Never accept arbitrary strings from user input or third-party data. target="_blank"must includerel="noopener noreferrer".- Markdown rendering: if any markdown is rendered, use a sanitizing renderer (e.g.,
react-markdownwithrehype-sanitize) — never raw HTML.
3. Secrets in the client bundle
- The bundle is public. Anything exposed via the bundler's public env mechanism (e.g.
import.meta.env.VITE_*) ships to the browser. Treat all such env vars as public. - Grep the source and the build output for likely secret patterns:
sk-,Bearer,API_KEY,SECRET,TOKEN,Authorization,AKIA[A-Z0-9]{16},AIza[A-Za-z0-9_-]{35}. - No private keys, tokens, or credentials should appear in the build output after
{{project.buildCmd}}. Inspect the production build output, not just source. - If you need a real secret (e.g., a paid API key), it MUST live behind a server proxy — never in the browser.
4. Validation of third-party data
{{sec.dataSource}} is untrusted external input. Treat its responses as adversarial:
- Every field used downstream must be type-narrowed at the boundary (schema library or hand-written guards).
- Never pass response strings into
dangerouslySetInnerHTML,eval,new Function(), or as a URL. - Reject malformed records rather than crashing — log and skip.
- No string concatenation into selectors or DOM APIs (e.g.,
document.querySelector("#" + record.name)— vulnerable ifnamecontains a]/'/etc.).
5. CORS / mixed content
- All external endpoints MUST be
https://. Search forhttp://in source — it shouldn't appear except in localhost dev URLs. - An endpoint that reflects
Access-Control-Allow-Originis usable by any origin — do not assume responses are unique to your app. - Do not use
<script src="http://...">or<img src="http://...">. - Any external
<script>from a CDN must carry Subresource Integrity (integrity="sha384-..." crossorigin="anonymous") — without it a CDN compromise ships arbitrary code to your users. Prefer bundling over CDN scripts entirely.
6. localStorage / sessionStorage / IndexedDB safety
- Never store secrets (the storage is readable by any script on the same origin).
- Validate on read — storage can be tampered with. Re-narrow the type when reading; reject if the shape is wrong.
- Use a versioned key prefix (e.g.,
app:v1:records) so a future schema change doesn't crash users with stale data. - Query-cache persistence (e.g., TanStack Query storage persisters) is fine, but ensure the persister has a key prefix and doesn't persist queries that include sensitive data.
7. CI workflow safety
- No
pull_request_targetwith secrets unless every code path is read-only (this trigger runs in the context of the base branch but with PR code — a known supply-chain footgun). - Pin actions by SHA, not tag (
uses: actions/checkout@<sha>) for any action that has access to secrets or write permissions. - Limit
GITHUB_TOKENpermissions to the minimum needed (permissions:block at the workflow or job level). - Don't echo secrets in CI logs.
Audit Checklist
Run through this list on every review:
{{sec.auditCmd}}— note critical/high findings{{project.lockfile}}matchespackage.json(no untracked dependency changes)git grep -E "(sk-|Bearer |API_KEY|SECRET|TOKEN|AKIA|AIza)"— no secrets in source- After
{{project.buildCmd}},grep -rE "(sk-|API_KEY|SECRET)" <build output dir>/— no secrets in built output git grep dangerouslySetInnerHTML— every hit has a justifying comment + sanitizationgit grep -E "target=\"_blank\""— every hit also hasrel="noopener noreferrer"git grep -E "http://"— only acceptable inlocalhostdev URLs- The data-client module validates every external response field before returning
- localStorage / sessionStorage usage validates types on read
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.