Frontend render security
Skill event4u-app/agent-config/src/skills/frontend-render-security
Universal AI Agent OS — audited skills, governance rules, replayable state. One contract, every host agent.
npx -y skills add event4u-app/agent-config --skill frontend-render-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 7 stars7 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.
What its author says it does
Copied from the file, not written here
Writing/reviewing client-side UI (React/Vue/vanilla) — insecure-render + client-trust gaps AI ships: XSS via innerHTML, client secrets, client-only auth, CORS wildcard, token in localStorage
SKILL.md
6.2 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it
frontend-render-security
AI optimizes for the shortest code that produces the requested visible behavior and omits the invisible defensive layer — the sanitizer, the origin check, the server-side gate, the security header. Large samples put XSS in a majority of AI frontend code and ~2.74× more XSS than human code; every agent in the Tenzai benchmark shipped client-side flaws. These are high-precision, grep-catchable patterns — stop them at authoring time.
When to use
- Writing or modifying a component, template, or client-side script that renders data, calls an API, handles auth, or reads the URL /
postMessage. - Reviewing an AI-authored frontend diff.
- Editing
.tsx/.jsx/.vue/.svelte/.htmlor client-side.js/.ts.
Do NOT use when: the change is server-only, CLI, or non-UI — route to security / security-sensitive-stop.
The Iron Law
NEVER RENDER NON-CONSTANT INPUT INTO AN HTML/JS SINK WITHOUT ENCODING OR SANITIZING.
THE CLIENT IS UNTRUSTED — EVERY CLIENT GATE NEEDS A SERVER-SIDE TWIN.
NO SECRET, PRIVILEGED KEY, OR SESSION TOKEN LIVES IN CLIENT CODE OR localStorage.
Procedure
- Identify the sinks in the diff: HTML/JS render points, API calls, auth/role checks, URL /
postMessagereads, secret/token usage. - Run the backstop greps below; for each hit, apply the matching fix from the patterns table.
- For every client-side gate, confirm a server-side twin exists (the client is never the security boundary).
- Emit the per-diff verdict (Output format); surface any unresolved hit to the user.
The patterns AI ships by default
| Pattern | Why it's dangerous | Do instead |
|---|---|---|
dangerouslySetInnerHTML / v-html / .innerHTML = on non-constant value | Stored/DOM XSS (CWE-79) | render as text, or sanitize (DOMPurify) if HTML is required |
Secret / API key inline or in NEXT_PUBLIC_* / VITE_* | Bundler inlines it into shipped JS (CWE-798) | call the third party via a backend-for-frontend proxy; only truly-public values get a public prefix |
| Client-side-only auth / role / validation | Bypassable with dev tools or a proxy (CWE-602) | client gate is UX only; enforce the same check server-side |
location.* / query param → HTML or JS sink | DOM XSS (CWE-79) | textContent; never pass URL data into an HTML/exec sink |
CORS origin: '*' (esp. with credentials) | Any site reads authenticated responses (CWE-942) | explicit origin allow-list; never reflect Origin on credentialed routes |
Token / JWT in localStorage / sessionStorage | XSS-exfiltratable (CWE-522) | HttpOnly; Secure; SameSite cookie |
addEventListener('message', …) without event.origin check | Any embedder drives the handler (CWE-346) | strict-equality check event.origin against an allow-list first |
redirect / next param → location / router.push | Open redirect / phishing (CWE-601) | relative-path or allow-listed-host only; reject external / non-http schemes |
eval / new Function / string-setTimeout on input | Eval injection / RCE (CWE-95) | a parser or explicit allow-list |
target="_blank" without rel="noopener" | Reverse tabnabbing (CWE-1022) | add rel="noopener noreferrer" |
| Missing CSP; missing alt/label/contrast | No XSS second line of defense; a11y failures | strict CSP (no unsafe-inline); alt text, labels, AA contrast |
Backstop greps
Run before committing frontend changes; each should return zero (or every hit is read and justified):
# Insecure render + eval sinks
rg -n 'dangerouslySetInnerHTML|v-html|\.innerHTML\s*=|document\.write\(|\beval\(|new Function\('
# Client secrets / token storage
rg -n 'NEXT_PUBLIC_.*(SECRET|KEY|TOKEN|PASSWORD)|VITE_.*(SECRET|KEY)|localStorage\.setItem\([^)]*[Tt]oken'
# Wildcard CORS + unchecked postMessage + open redirect
rg -n "origin:\s*['\"]\*['\"]|Access-Control-Allow-Origin.*\*"
rg -n "addEventListener\(\s*['\"]message['\"]" # then confirm each checks event.origin
# Unhardened external links
rg -n 'target=["'\'']_blank["'\'']' # then confirm rel="noopener" present
Output format
- A per-diff verdict listing each pattern class checked and its result (
clean/hit at file:line → fixed by <change>). - The backstop greps run, with results.
- For any client gate added, the file:line of its server-side twin (or an explicit note that the server check already exists and where).
Gotcha
- Modern browsers default
_blanktonoopener, but legacy/embedded webviews do not — keep therelfor portability. NEXT_PUBLIC_/VITE_on a genuinely public value (a publishable analytics ID) is fine; the violation is a sensitive name behind that prefix. Read the name, don't blanket-block the prefix.- A sanitizer (DOMPurify) is the fix only when HTML output is actually required; if plain text suffices, render text and skip the dependency.
- Client-side validation is not wrong — it is wrong as the only enforcement. Keep it for UX; add the server twin.
Do NOT
- Do NOT render user/DB HTML through
dangerouslySetInnerHTML/v-html/innerHTMLwithout a sanitizer. - Do NOT put a secret, privileged key, or session token in client code or web storage.
- Do NOT treat a client-side role/auth check as a security boundary.
- Do NOT set
origin: '*'on a credentialed endpoint. - Do NOT auto-strip a grep hit without reading it — some innerHTML uses are on constant, trusted markup.
Auto-trigger keywords
- frontend security
- XSS
- dangerouslySetInnerHTML
- client-side secret
- insecure render
See also
ai-code-blindspots— the surface→controls checklist that routes here.senior-engineering-discipline— anchor rule.security,defense-in-depth,accessibility-auditor,secrets-management.
What ships with it: 1 file
2.1 KB alongside SKILL.md
evals/
- triggers.json2.1 KB
Gives 0 of the 12 instructions most design frontend skills give in ~1.6k tokens
Counted across 1,169 of the 1,878 authors here whose files we hold, read 2026-08-07
- Use CSS variables for color consistencyin 72 of 1169, across 23 files
- Commit to one bold aesthetic direction before codingin 72 of 1169, across 27 files
- Match implementation complexity to the aesthetic visionin 70 of 1169, across 20 files
- Add atmospheric background effects and texturesin 57 of 1169, across 9 files
- Use unexpected spatial compositions and layoutsin 56 of 1169, across 8 files
- Implement real working codein 55 of 1169, across 7 files
- Vary themes and aesthetics across different designsin 48 of 1169, across 7 files
- Launch chromium in headless modein 47 of 1169, across 4 files
- Close the browser when donein 47 of 1169, across 4 files
- Run provided scripts with help flag firstin 47 of 1169, across 4 files
- Wait for network idle statein 47 of 1169, across 4 files
- Use descriptive selectors for elementsin 47 of 1169, across 4 files
Said here and by no other author read
- enforce every client gate with a server-side twin
- exclude secrets and tokens from client code
- validate postMessage event origin against an allow-list
- restrict CORS origins to an explicit allow-list
- reject external redirect parameters
- add rel noopener noreferrer to external links
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.