Always-on browser-security rules for client-side code in any frontend stack — bundle secrets, XSS sinks, URL and redirect handling, postMessage, CSP, CSRF with cookie sessions, token storage and third-party scripts.
What it isAlways-on coding rules. GitHub Copilot applies them to files matching **/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.mjs,**/*.vue,**/*.svelte,**/src/**/*.html; the Claude Code version is a path-scoped rule generated from the same file. Why use instructions →
Install
This repository (recommended)
Adds .github/instructions/frontend-security.instructions.md — commit it so everyone's Copilot follows the same rules.
Nothing to invoke — edit a matching file and Copilot picks the rules up. Adjust applyTo if your repository layout differs.
This repository (recommended)
Adds .claude/rules/frontend-security.md — commit it. Claude loads it when it reads a matching file.
npx -y github:AGCO-Global/org-skills add instructions frontend-security --tool claude
Just for me, every repository
Save it in your user profile instead.
npx -y github:AGCO-Global/org-skills add instructions frontend-security --tool claude --user
Use it
Nothing to invoke. Adjust the paths globs if your repository layout differs.
Commands run in your repository folder and need Node.js 20+. They use your normal git sign-in to GitHub, so they work while the repository is private. Run npx -y github:AGCO-Global/org-skills list to see everything available.
Settings
name: "Frontend security"
description: "Always-on browser-security rules for client-side code in any frontend stack — bundle secrets, XSS sinks, URL and redirect handling, postMessage, CSP, CSRF with cookie sessions, token storage and third-party scripts."
applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.mjs,**/*.vue,**/*.svelte,**/src/**/*.html"
Instructions Copilot follows
Frontend security rules
Everything in a browser bundle is public, and every value that reaches the DOM is attacker-influenced until proven otherwise. These are the browser-specific rules; secure-coding.instructions.md holds the stack-agnostic ones and is not repeated here.
Secrets and configuration
A browser bundle cannot hold a secret. API keys, client secrets, signing keys and connection strings belong behind your own backend; if the browser needs the capability, proxy the call.
A key that a vendor calls "publishable" is still only safe if it is scoped and rate-limited server side. Check what it can do before treating it as public.
Do not ship internal hostnames, private endpoints or tenant identifiers in client code, comments or source maps published to the public.
Rendering untrusted values
Never pass user- or API-supplied strings to an HTML sink: dangerouslySetInnerHTML, [innerHTML], innerHTML, insertAdjacentHTML, outerHTML, document.write, v-html, lwc:dom="manual". Render text, or render structured data as elements.
Never call a sanitizer bypass on data you did not author: Angular's bypassSecurityTrustHtml/Url/ResourceUrl/Script/Style disable the framework's only XSS protection.
If formatted user content genuinely must render, sanitise it server side with a maintained library and store the sanitised form — client-side sanitisation is a second line of defence, not the first.
Never eval, new Function, setTimeout('string'), or inject a <script> built from data.
URLs, redirects and links
Validate any URL that reaches href, src, action, formaction or window.open against a scheme allow-list of https: (and mailto:/tel: where needed). javascript: and data: URLs execute.
Build redirects from an allow-list of known paths, never from a raw query parameter — an open redirect on your own domain is a working phishing page.
Put rel="noopener noreferrer" on target="_blank" links to third-party sites. Modern browsers imply noopener; noreferrer is what stops your internal URLs leaking in the referrer.
Cross-origin messaging and embedding
In every message listener, check event.origin against an exact expected origin before reading event.data, and treat the payload as untrusted input after that.
Never call postMessage(data, '*') with anything sensitive; name the target origin.
Sandbox embedded third-party content (<iframe sandbox="...">) and grant only the capabilities it needs.
Sessions, tokens and authorisation
With cookie sessions, cookies must be set server side with HttpOnly; Secure; SameSite=Lax (or Strict), and every state-changing request must carry the framework's CSRF token.
Prefer keeping access tokens in memory over localStorage, which any injected script can read and which survives the tab. If a refresh flow is needed, the refresh token belongs in an HttpOnly cookie.
Client-side permission checks are user experience only. Hiding a button is not access control; the server must enforce every rule the UI implies.
Use crypto.randomUUID() or crypto.getRandomValues() for anything id- or nonce-like. Math.random() is predictable.
Headers and third-party code
Ship a Content-Security-Policy and keep it strict: no unsafe-inline, no unsafe-eval, explicit origins for scripts, connections and frames. Enable Trusted Types where the browsers you support allow it.
Adding a third-party script is a security decision: check what it loads at runtime, give it Subresource Integrity where the URL is versioned, add its origins to the CSP, and record what data it collects.
Do not disable CSP, CORS checks or certificate validation to make something work locally; use a local proxy or a development-only configuration that cannot ship.
Data handling in the client
Keep personal data out of console output, analytics events, error reports, breadcrumbs and URL query strings — all four leave the browser and are retained by someone.
Do not persist personal or financial data in localStorage, sessionStorage or IndexedDB unless the feature requires it, and clear it on sign-out.
Scrub tokens, cookies and request bodies before sending an error to monitoring.
Go deeper: frontend-code-review for the review checklist, and the stack's development skill for the framework-specific form of each rule.
---
name: "Frontend security"
description: "Always-on browser-security rules for client-side code in any frontend stack — bundle secrets, XSS sinks, URL and redirect handling, postMessage, CSP, CSRF with cookie sessions, token storage and third-party scripts."
applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.mjs,**/*.vue,**/*.svelte,**/src/**/*.html"
---
# Frontend security rules
Everything in a browser bundle is public, and every value that reaches the DOM is attacker-influenced until proven otherwise. These are the browser-specific rules; `secure-coding.instructions.md` holds the stack-agnostic ones and is not repeated here.
## Secrets and configuration
- A browser bundle cannot hold a secret. API keys, client secrets, signing keys and connection strings belong behind your own backend; if the browser needs the capability, proxy the call.
- A key that a vendor calls "publishable" is still only safe if it is scoped and rate-limited server side. Check what it can do before treating it as public.
- Do not ship internal hostnames, private endpoints or tenant identifiers in client code, comments or source maps published to the public.
## Rendering untrusted values
- Never pass user- or API-supplied strings to an HTML sink: `dangerouslySetInnerHTML`, `[innerHTML]`, `innerHTML`, `insertAdjacentHTML`, `outerHTML`, `document.write`, `v-html`, `lwc:dom="manual"`. Render text, or render structured data as elements.
- Never call a sanitizer bypass on data you did not author: Angular's `bypassSecurityTrustHtml`/`Url`/`ResourceUrl`/`Script`/`Style` disable the framework's only XSS protection.
- If formatted user content genuinely must render, sanitise it server side with a maintained library and store the sanitised form — client-side sanitisation is a second line of defence, not the first.
- Never `eval`, `new Function`, `setTimeout('string')`, or inject a `<script>` built from data.
## URLs, redirects and links
- Validate any URL that reaches `href`, `src`, `action`, `formaction` or `window.open` against a scheme allow-list of `https:` (and `mailto:`/`tel:` where needed). `javascript:` and `data:` URLs execute.
- Build redirects from an allow-list of known paths, never from a raw query parameter — an open redirect on your own domain is a working phishing page.
- Put `rel="noopener noreferrer"` on `target="_blank"` links to third-party sites. Modern browsers imply `noopener`; `noreferrer` is what stops your internal URLs leaking in the referrer.
## Cross-origin messaging and embedding
- In every `message` listener, check `event.origin` against an exact expected origin before reading `event.data`, and treat the payload as untrusted input after that.
- Never call `postMessage(data, '*')` with anything sensitive; name the target origin.
- Sandbox embedded third-party content (`<iframe sandbox="...">`) and grant only the capabilities it needs.
## Sessions, tokens and authorisation
- With cookie sessions, cookies must be set server side with `HttpOnly; Secure; SameSite=Lax` (or `Strict`), and every state-changing request must carry the framework's CSRF token.
- Prefer keeping access tokens in memory over `localStorage`, which any injected script can read and which survives the tab. If a refresh flow is needed, the refresh token belongs in an `HttpOnly` cookie.
- Client-side permission checks are user experience only. Hiding a button is not access control; the server must enforce every rule the UI implies.
- Use `crypto.randomUUID()` or `crypto.getRandomValues()` for anything id- or nonce-like. `Math.random()` is predictable.
## Headers and third-party code
- Ship a Content-Security-Policy and keep it strict: no `unsafe-inline`, no `unsafe-eval`, explicit origins for scripts, connections and frames. Enable Trusted Types where the browsers you support allow it.
- Adding a third-party script is a security decision: check what it loads at runtime, give it Subresource Integrity where the URL is versioned, add its origins to the CSP, and record what data it collects.
- Do not disable CSP, CORS checks or certificate validation to make something work locally; use a local proxy or a development-only configuration that cannot ship.
## Data handling in the client
- Keep personal data out of `console` output, analytics events, error reports, breadcrumbs and URL query strings — all four leave the browser and are retained by someone.
- Do not persist personal or financial data in `localStorage`, `sessionStorage` or IndexedDB unless the feature requires it, and clear it on sign-out.
- Scrub tokens, cookies and request bodies before sending an error to monitoring.
Go deeper: `frontend-code-review` for the review checklist, and the stack's development skill for the framework-specific form of each rule.
---
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.js"
- "**/*.jsx"
- "**/*.mjs"
- "**/*.vue"
- "**/*.svelte"
- "**/src/**/*.html"
---
<!-- Frontend security — generated from instructions/frontend/frontend-security.instructions.md for Claude Code. Edit that file, not this one. -->
# Frontend security rules
Everything in a browser bundle is public, and every value that reaches the DOM is attacker-influenced until proven otherwise. These are the browser-specific rules; `secure-coding.instructions.md` holds the stack-agnostic ones and is not repeated here.
## Secrets and configuration
- A browser bundle cannot hold a secret. API keys, client secrets, signing keys and connection strings belong behind your own backend; if the browser needs the capability, proxy the call.
- A key that a vendor calls "publishable" is still only safe if it is scoped and rate-limited server side. Check what it can do before treating it as public.
- Do not ship internal hostnames, private endpoints or tenant identifiers in client code, comments or source maps published to the public.
## Rendering untrusted values
- Never pass user- or API-supplied strings to an HTML sink: `dangerouslySetInnerHTML`, `[innerHTML]`, `innerHTML`, `insertAdjacentHTML`, `outerHTML`, `document.write`, `v-html`, `lwc:dom="manual"`. Render text, or render structured data as elements.
- Never call a sanitizer bypass on data you did not author: Angular's `bypassSecurityTrustHtml`/`Url`/`ResourceUrl`/`Script`/`Style` disable the framework's only XSS protection.
- If formatted user content genuinely must render, sanitise it server side with a maintained library and store the sanitised form — client-side sanitisation is a second line of defence, not the first.
- Never `eval`, `new Function`, `setTimeout('string')`, or inject a `<script>` built from data.
## URLs, redirects and links
- Validate any URL that reaches `href`, `src`, `action`, `formaction` or `window.open` against a scheme allow-list of `https:` (and `mailto:`/`tel:` where needed). `javascript:` and `data:` URLs execute.
- Build redirects from an allow-list of known paths, never from a raw query parameter — an open redirect on your own domain is a working phishing page.
- Put `rel="noopener noreferrer"` on `target="_blank"` links to third-party sites. Modern browsers imply `noopener`; `noreferrer` is what stops your internal URLs leaking in the referrer.
## Cross-origin messaging and embedding
- In every `message` listener, check `event.origin` against an exact expected origin before reading `event.data`, and treat the payload as untrusted input after that.
- Never call `postMessage(data, '*')` with anything sensitive; name the target origin.
- Sandbox embedded third-party content (`<iframe sandbox="...">`) and grant only the capabilities it needs.
## Sessions, tokens and authorisation
- With cookie sessions, cookies must be set server side with `HttpOnly; Secure; SameSite=Lax` (or `Strict`), and every state-changing request must carry the framework's CSRF token.
- Prefer keeping access tokens in memory over `localStorage`, which any injected script can read and which survives the tab. If a refresh flow is needed, the refresh token belongs in an `HttpOnly` cookie.
- Client-side permission checks are user experience only. Hiding a button is not access control; the server must enforce every rule the UI implies.
- Use `crypto.randomUUID()` or `crypto.getRandomValues()` for anything id- or nonce-like. `Math.random()` is predictable.
## Headers and third-party code
- Ship a Content-Security-Policy and keep it strict: no `unsafe-inline`, no `unsafe-eval`, explicit origins for scripts, connections and frames. Enable Trusted Types where the browsers you support allow it.
- Adding a third-party script is a security decision: check what it loads at runtime, give it Subresource Integrity where the URL is versioned, add its origins to the CSP, and record what data it collects.
- Do not disable CSP, CORS checks or certificate validation to make something work locally; use a local proxy or a development-only configuration that cannot ship.
## Data handling in the client
- Keep personal data out of `console` output, analytics events, error reports, breadcrumbs and URL query strings — all four leave the browser and are retained by someone.
- Do not persist personal or financial data in `localStorage`, `sessionStorage` or IndexedDB unless the feature requires it, and clear it on sign-out.
- Scrub tokens, cookies and request bodies before sending an error to monitoring.
Go deeper: `frontend-code-review` for the review checklist, and the stack's development skill for the framework-specific form of each rule.