@schalkneethling/calavera-skill-frontend-security 0.2.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/calavera-artifact.json +11 -0
- package/package.json +24 -0
- package/payload/frontend-security/SKILL.md +57 -0
- package/payload/frontend-security/agents/openai.yaml +4 -0
- package/payload/frontend-security/references/audit-workflow.md +57 -0
- package/payload/frontend-security/references/csp-configuration.md +262 -0
- package/payload/frontend-security/references/csrf-protection.md +383 -0
- package/payload/frontend-security/references/dom-security.md +263 -0
- package/payload/frontend-security/references/file-upload-security.md +662 -0
- package/payload/frontend-security/references/framework-patterns.md +328 -0
- package/payload/frontend-security/references/input-validation.md +360 -0
- package/payload/frontend-security/references/jwt-security.md +352 -0
- package/payload/frontend-security/references/nodejs-npm-security.md +325 -0
- package/payload/frontend-security/references/xss-prevention.md +212 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Schalk Neethling
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://calavera.schalkneethling.com/schemas/calavera-artifact.schema.json",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"id": "skill-frontend-security",
|
|
5
|
+
"type": "skill",
|
|
6
|
+
"displayName": "Frontend security",
|
|
7
|
+
"payload": "payload/frontend-security",
|
|
8
|
+
"compatibility": {
|
|
9
|
+
"calavera": ">=2.2.0 <3"
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@schalkneethling/calavera-skill-frontend-security",
|
|
3
|
+
"version": "0.2.0-next.2",
|
|
4
|
+
"description": "Frontend security artifact for Calavera.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+ssh://git@github.com/schalkneethling/create-project-calavera.git",
|
|
9
|
+
"directory": "packages/artifacts/skill-frontend-security"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"calavera-artifact.json",
|
|
13
|
+
"payload"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./calavera-artifact.json",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"provenance": true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-security
|
|
3
|
+
description: Audit frontend codebases for security vulnerabilities and bad practices. Use when performing security reviews, auditing code for XSS/CSRF/DOM vulnerabilities, checking Content Security Policy configurations, validating input handling, reviewing file upload security, or examining Node.js/NPM dependencies. Target frameworks include web platform (vanilla HTML/CSS/JS), React, Astro, Twig templates, Node.js, and Bun. Based on OWASP security guidelines.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Frontend Security Audit Skill
|
|
7
|
+
|
|
8
|
+
Audit frontend-adjacent code by mapping trust boundaries, tracing untrusted data into dangerous
|
|
9
|
+
sinks, and using searches as supporting evidence. Cover browser/frontend concerns first, then route
|
|
10
|
+
related server, package, authentication, and upload findings to the relevant reference.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Map routes, rendering models, APIs, authentication/session boundaries, third-party scripts,
|
|
15
|
+
uploads, package-manager entry points, and deployment assumptions.
|
|
16
|
+
2. List untrusted sources such as URL parameters, forms, storage, `postMessage`, responses, files,
|
|
17
|
+
cookies, headers, and package metadata. Trace them to DOM, navigation, command, filesystem,
|
|
18
|
+
database, network, and log sinks.
|
|
19
|
+
3. Load `references/audit-workflow.md` for focused evidence searches. Treat matches as leads, not
|
|
20
|
+
proof; use AST-aware tooling where syntax or multiline structure matters.
|
|
21
|
+
4. Review output encoding, sanitization, CSP, CSRF, token storage, input validation, uploads,
|
|
22
|
+
dependencies, and framework escape hatches.
|
|
23
|
+
5. Rate the confirmed finding from exploitability, impact, reachability, required privileges, user
|
|
24
|
+
interaction, data sensitivity, and compensating controls.
|
|
25
|
+
6. Report the affected trust boundary, evidence, exploit scenario, severity rationale, remediation,
|
|
26
|
+
and authoritative reference using the template in `references/audit-workflow.md`.
|
|
27
|
+
|
|
28
|
+
## Reference Routing
|
|
29
|
+
|
|
30
|
+
| Finding area | Load |
|
|
31
|
+
| ---------------------------------------------------------- | ------------------------------------ |
|
|
32
|
+
| Browser XSS, unsafe URLs, tabnabbing, HTML injection | `references/xss-prevention.md` |
|
|
33
|
+
| DOM sinks, clobbering, postMessage, client storage | `references/dom-security.md` |
|
|
34
|
+
| CSP headers, nonces, hashes, Trusted Types | `references/csp-configuration.md` |
|
|
35
|
+
| Cookie auth, forms, state-changing requests | `references/csrf-protection.md` |
|
|
36
|
+
| URL, number, date, schema, and path validation | `references/input-validation.md` |
|
|
37
|
+
| React, Astro, Twig, SSR, hydration, templates | `references/framework-patterns.md` |
|
|
38
|
+
| Uploads, downloads, archives, quarantine | `references/file-upload-security.md` |
|
|
39
|
+
| JWT lifecycle, storage, fingerprints, refresh flows | `references/jwt-security.md` |
|
|
40
|
+
| Node runtime, npm supply chain, scripts, command execution | `references/nodejs-npm-security.md` |
|
|
41
|
+
|
|
42
|
+
Name boundaries precisely, for example “browser-to-API CSRF,” “frontend-triggered upload
|
|
43
|
+
processing,” or “package-script supply-chain risk.” State scope limits: route deep backend
|
|
44
|
+
authorization, database, infrastructure, malware, and incident-response work to specialists.
|
|
45
|
+
|
|
46
|
+
## Severity
|
|
47
|
+
|
|
48
|
+
- **Critical**: likely high-impact exploit such as unauthenticated account takeover, privileged
|
|
49
|
+
stored XSS, production credential exposure, or reachable remote code execution.
|
|
50
|
+
- **High**: plausible exploit with meaningful user, data, integrity, or supply-chain impact.
|
|
51
|
+
- **Medium**: real weakness with limited reach, prerequisites, lower sensitivity, or meaningful
|
|
52
|
+
compensating controls.
|
|
53
|
+
- **Low**: hard-to-exploit or defense-in-depth gap, deprecated pattern, or hygiene issue.
|
|
54
|
+
|
|
55
|
+
Always record why the project-specific evidence supports the chosen severity. Consider attacker
|
|
56
|
+
capability, interaction, privileges, production reachability, persistence, blast radius, and
|
|
57
|
+
existing controls. Do not assign severity from a search result alone.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Security Audit Evidence and Reporting
|
|
2
|
+
|
|
3
|
+
## Evidence Searches
|
|
4
|
+
|
|
5
|
+
Start broad, then narrow to the project's languages and trust boundaries. Prefer AST-aware tools or
|
|
6
|
+
Semgrep when syntax or multiline structure matters.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
FRONTEND_GLOBS='*.{js,jsx,ts,tsx,mjs,cjs,mts,cts,astro,vue,svelte,html,htm,twig,njk,ejs,hbs,mdx}'
|
|
10
|
+
rg -n "dangerouslySetInnerHTML|innerHTML|outerHTML|insertAdjacentHTML|document\\.write|DOMParser" --glob "$FRONTEND_GLOBS"
|
|
11
|
+
rg -n "location\\.(href|assign|replace)|window\\.open|javascript:|data:text/html" --glob "$FRONTEND_GLOBS"
|
|
12
|
+
rg -n "\\beval\\s*\\(|new Function\\s*\\(|set(?:Timeout|Interval)\\s*\\(\\s*['\"]" --glob "$FRONTEND_GLOBS"
|
|
13
|
+
rg -n "<form|method=[\"']post|fetch\\(|axios\\.|Authorization|Bearer|refresh[_-]?token|jwt" --glob "$FRONTEND_GLOBS"
|
|
14
|
+
rg -n "localStorage|sessionStorage|indexedDB|document\\.cookie" --glob "$FRONTEND_GLOBS"
|
|
15
|
+
rg -n -i "api[_-]?key|client[_-]?secret|password|private[_-]?key" --glob "$FRONTEND_GLOBS" --glob "*.env*"
|
|
16
|
+
rg -n "multer|busboy|formidable|Content-Disposition|extractAllTo|adm-zip|yauzl" --glob "$FRONTEND_GLOBS" --glob "package.json"
|
|
17
|
+
rg -n '"(preinstall|install|postinstall|prepare)"|child_process|exec\\(|execFile\\(|spawn\\(' --glob "package.json" --glob "*.{js,ts,mjs,cjs,mts,cts,sh,yml,yaml,toml,json}"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Follow representative matches from an untrusted source to a sensitive sink before reporting them.
|
|
21
|
+
|
|
22
|
+
## Report Template
|
|
23
|
+
|
|
24
|
+
```markdown
|
|
25
|
+
## Security Audit Report
|
|
26
|
+
|
|
27
|
+
### Summary
|
|
28
|
+
|
|
29
|
+
- Critical: X
|
|
30
|
+
- High: X
|
|
31
|
+
- Medium: X
|
|
32
|
+
- Low: X
|
|
33
|
+
|
|
34
|
+
#### [SEVERITY-001] Finding title
|
|
35
|
+
|
|
36
|
+
- **Location**: file:line
|
|
37
|
+
- **Trust boundary**: source to sink
|
|
38
|
+
- **Evidence**: confirmed code path
|
|
39
|
+
- **Exploit scenario**: attacker capability and path
|
|
40
|
+
- **Risk**: concrete impact
|
|
41
|
+
- **Severity rationale**: exploitability, reachability, impact, and controls
|
|
42
|
+
- **Remediation**: focused fix
|
|
43
|
+
- **Reference**: authoritative guidance
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## References
|
|
47
|
+
|
|
48
|
+
- [XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
|
|
49
|
+
- [DOM XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html)
|
|
50
|
+
- [CSRF Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
|
|
51
|
+
- [Content Security Policy](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html)
|
|
52
|
+
- [Input Validation](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html)
|
|
53
|
+
- [DOM Clobbering Prevention](https://cheatsheetseries.owasp.org/cheatsheets/DOM_Clobbering_Prevention_Cheat_Sheet.html)
|
|
54
|
+
- [Node.js Security](https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html)
|
|
55
|
+
- [File Upload](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html)
|
|
56
|
+
- [Error Handling](https://cheatsheetseries.owasp.org/cheatsheets/Error_Handling_Cheat_Sheet.html)
|
|
57
|
+
- [JWT Security](https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html)
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Content Security Policy Reference
|
|
2
|
+
|
|
3
|
+
## Strict CSP Configuration
|
|
4
|
+
|
|
5
|
+
### Nonce-Based (Recommended)
|
|
6
|
+
|
|
7
|
+
```http
|
|
8
|
+
Content-Security-Policy:
|
|
9
|
+
default-src 'self';
|
|
10
|
+
script-src 'nonce-{per_request_nonce}' 'strict-dynamic';
|
|
11
|
+
object-src 'none';
|
|
12
|
+
base-uri 'none';
|
|
13
|
+
form-action 'self';
|
|
14
|
+
frame-ancestors 'none';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Implementation:
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
// Generate unique nonce per request
|
|
21
|
+
const crypto = require("crypto");
|
|
22
|
+
const nonce = crypto.randomBytes(16).toString("base64");
|
|
23
|
+
|
|
24
|
+
// Set header
|
|
25
|
+
res.setHeader(
|
|
26
|
+
"Content-Security-Policy",
|
|
27
|
+
[
|
|
28
|
+
"default-src 'self'",
|
|
29
|
+
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
|
|
30
|
+
"object-src 'none'",
|
|
31
|
+
"base-uri 'none'",
|
|
32
|
+
"form-action 'self'",
|
|
33
|
+
"frame-ancestors 'none'",
|
|
34
|
+
].join("; "),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Include nonce in script tags
|
|
38
|
+
res.send(`<script nonce="${nonce}">/* safe code */</script>`);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Hash-Based (For Static Scripts)
|
|
42
|
+
|
|
43
|
+
```http
|
|
44
|
+
Content-Security-Policy:
|
|
45
|
+
default-src 'self';
|
|
46
|
+
script-src 'sha256-{hash}' 'strict-dynamic';
|
|
47
|
+
object-src 'none';
|
|
48
|
+
base-uri 'none';
|
|
49
|
+
form-action 'self';
|
|
50
|
+
frame-ancestors 'none';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Generate hash:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
echo -n 'console.log("hello");' | openssl sha256 -binary | openssl base64
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Essential Directives
|
|
60
|
+
|
|
61
|
+
| Directive | Purpose | Recommended Value |
|
|
62
|
+
| ----------------- | ----------------------------- | ---------------------------------------------- |
|
|
63
|
+
| `default-src` | Fallback for other directives | `'self'` |
|
|
64
|
+
| `script-src` | JavaScript sources | `'nonce-{per_request_nonce}' 'strict-dynamic'` |
|
|
65
|
+
| `style-src` | CSS sources | `'self'` or nonce/hash-based styles |
|
|
66
|
+
| `img-src` | Image sources | `'self' data: https:` |
|
|
67
|
+
| `font-src` | Font sources | `'self'` |
|
|
68
|
+
| `connect-src` | AJAX/WebSocket/Fetch | `'self' https://api.example.com` |
|
|
69
|
+
| `frame-src` | iframe sources | `'none'` or specific origins |
|
|
70
|
+
| `object-src` | Plugin content | `'none'` |
|
|
71
|
+
| `base-uri` | Base URL restrictions | `'none'` |
|
|
72
|
+
| `form-action` | Form submission targets | `'self'` |
|
|
73
|
+
| `frame-ancestors` | Who can embed page | `'self'` or `'none'` |
|
|
74
|
+
|
|
75
|
+
## Framework Integration
|
|
76
|
+
|
|
77
|
+
### Express.js
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
const crypto = require("crypto");
|
|
81
|
+
const helmet = require("helmet");
|
|
82
|
+
|
|
83
|
+
// Nonce middleware
|
|
84
|
+
app.use((req, res, next) => {
|
|
85
|
+
res.locals.nonce = crypto.randomBytes(16).toString("base64");
|
|
86
|
+
next();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.use(
|
|
90
|
+
helmet.contentSecurityPolicy({
|
|
91
|
+
directives: {
|
|
92
|
+
defaultSrc: ["'self'"],
|
|
93
|
+
scriptSrc: [(req, res) => `'nonce-${res.locals.nonce}'`, "'strict-dynamic'"],
|
|
94
|
+
styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
|
|
95
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
96
|
+
objectSrc: ["'none'"],
|
|
97
|
+
baseUri: ["'none'"],
|
|
98
|
+
formAction: ["'self'"],
|
|
99
|
+
frameAncestors: ["'self'"],
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Astro
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// astro.config.mjs
|
|
109
|
+
import { defineConfig } from "astro/config";
|
|
110
|
+
|
|
111
|
+
// Development-only fallback. Prefer production headers from your hosting edge,
|
|
112
|
+
// adapter middleware, or server response so each request can receive a fresh
|
|
113
|
+
// nonce and avoid unsafe-inline.
|
|
114
|
+
export default defineConfig({
|
|
115
|
+
vite: {
|
|
116
|
+
plugins: [
|
|
117
|
+
{
|
|
118
|
+
name: "dev-csp-plugin",
|
|
119
|
+
apply: "serve",
|
|
120
|
+
configureServer(server) {
|
|
121
|
+
server.middlewares.use((req, res, next) => {
|
|
122
|
+
res.setHeader(
|
|
123
|
+
"Content-Security-Policy",
|
|
124
|
+
"default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none';",
|
|
125
|
+
);
|
|
126
|
+
next();
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Meta Tag (Fallback)
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<meta
|
|
139
|
+
http-equiv="Content-Security-Policy"
|
|
140
|
+
content="default-src 'self'; script-src 'self' 'sha256-<base64-script-hash>'; object-src 'none'; base-uri 'none';"
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Note**: Meta tag CSP cannot set `frame-ancestors`, `report-uri`, or `sandbox`.
|
|
145
|
+
Use hashes for static fallback pages. Use a response header when you need a
|
|
146
|
+
per-request nonce.
|
|
147
|
+
|
|
148
|
+
## Report-Only Mode
|
|
149
|
+
|
|
150
|
+
Test policies without enforcement:
|
|
151
|
+
|
|
152
|
+
```http
|
|
153
|
+
Content-Security-Policy-Report-Only:
|
|
154
|
+
default-src 'self';
|
|
155
|
+
script-src 'nonce-{per_request_nonce}' 'strict-dynamic';
|
|
156
|
+
object-src 'none';
|
|
157
|
+
base-uri 'none';
|
|
158
|
+
form-action 'self';
|
|
159
|
+
frame-ancestors 'none';
|
|
160
|
+
report-uri /csp-report;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Report endpoint (apply a rate limiter before parsing and logging public reports):
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
function sanitizeReportUrl(value) {
|
|
167
|
+
if (typeof value !== "string") return undefined;
|
|
168
|
+
try {
|
|
169
|
+
const url = new URL(value);
|
|
170
|
+
url.search = "";
|
|
171
|
+
url.hash = "";
|
|
172
|
+
return url.href.slice(0, 512);
|
|
173
|
+
} catch {
|
|
174
|
+
return value.slice(0, 128);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
app.post(
|
|
179
|
+
"/csp-report",
|
|
180
|
+
cspReportLimiter,
|
|
181
|
+
express.json({ type: "application/csp-report", limit: "16kb" }),
|
|
182
|
+
(req, res) => {
|
|
183
|
+
const report = req.body?.["csp-report"];
|
|
184
|
+
if (!report || typeof report !== "object" || Array.isArray(report)) {
|
|
185
|
+
return res.status(204).end();
|
|
186
|
+
}
|
|
187
|
+
const directive = report["violated-directive"];
|
|
188
|
+
console.warn("CSP Violation:", {
|
|
189
|
+
blockedUri: sanitizeReportUrl(report["blocked-uri"]),
|
|
190
|
+
violatedDirective: typeof directive === "string" ? directive.slice(0, 128) : undefined,
|
|
191
|
+
documentUri: sanitizeReportUrl(report["document-uri"]),
|
|
192
|
+
});
|
|
193
|
+
res.status(204).end();
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Common Violations and Fixes
|
|
199
|
+
|
|
200
|
+
### Inline Scripts
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<!-- Violation: inline script -->
|
|
204
|
+
<script>
|
|
205
|
+
alert("hello");
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<!-- Fix: add a fresh per-response nonce generated by the server -->
|
|
209
|
+
<script nonce="{per_request_nonce}">
|
|
210
|
+
alert("hello");
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<!-- Or: move to external file -->
|
|
214
|
+
<script src="/js/app.js"></script>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Inline Event Handlers
|
|
218
|
+
|
|
219
|
+
```html
|
|
220
|
+
<!-- Violation: inline event handler -->
|
|
221
|
+
<button onclick="handleClick()">Click</button>
|
|
222
|
+
|
|
223
|
+
<!-- Fix: use addEventListener -->
|
|
224
|
+
<button id="myBtn">Click</button>
|
|
225
|
+
<script nonce="{per_request_nonce}">
|
|
226
|
+
document.getElementById("myBtn").addEventListener("click", handleClick);
|
|
227
|
+
</script>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Inline Styles
|
|
231
|
+
|
|
232
|
+
```html
|
|
233
|
+
<!-- Violation: style attribute -->
|
|
234
|
+
<div style="color: red;">Text</div>
|
|
235
|
+
|
|
236
|
+
<!-- Fix: use classes -->
|
|
237
|
+
<div class="text-red">Text</div>
|
|
238
|
+
|
|
239
|
+
<!-- Or: add a fresh per-response nonce to style tags -->
|
|
240
|
+
<style nonce="{per_request_nonce}">
|
|
241
|
+
.text-red {
|
|
242
|
+
color: red;
|
|
243
|
+
}
|
|
244
|
+
</style>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Never copy a literal nonce value. A nonce must be unpredictable and unique for
|
|
248
|
+
each response that contains nonce-authorized inline script or style.
|
|
249
|
+
|
|
250
|
+
## Security Headers Companion
|
|
251
|
+
|
|
252
|
+
Deploy CSP with these additional headers:
|
|
253
|
+
|
|
254
|
+
```http
|
|
255
|
+
X-Content-Type-Options: nosniff
|
|
256
|
+
X-Frame-Options: DENY
|
|
257
|
+
X-XSS-Protection: 0
|
|
258
|
+
Referrer-Policy: strict-origin-when-cross-origin
|
|
259
|
+
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
OWASP Reference: https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html
|