@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 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,4 @@
1
+ interface:
2
+ display_name: "Frontend Security"
3
+ short_description: "Audit browser code for security vulnerabilities."
4
+ default_prompt: "Use $frontend-security to audit this frontend code for security risks."
@@ -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