codelaw-rules-security-js 0.3.0__py3-none-any.whl
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.
- codelaw_rules_security_js/LICENSE +23 -0
- codelaw_rules_security_js/NOTICE +22 -0
- codelaw_rules_security_js/README.md +183 -0
- codelaw_rules_security_js/codelaw-pack.toml +6 -0
- codelaw_rules_security_js/corpus.toml +70 -0
- codelaw_rules_security_js/data/sources/browser.json +24 -0
- codelaw_rules_security_js/data/sources/express.json +28 -0
- codelaw_rules_security_js/data/sources/koa.json +18 -0
- codelaw_rules_security_js/data/sources/llm.json +14 -0
- codelaw_rules_security_js/data/sources/nextjs.json +13 -0
- codelaw_rules_security_js/presets/sec-js/recommended.toml +58 -0
- codelaw_rules_security_js/presets/sec-js/strict.toml +15 -0
- codelaw_rules_security_js/rules/sec-js/angularjs-sce-disabled.cases.md +129 -0
- codelaw_rules_security_js/rules/sec-js/angularjs-sce-disabled.md +132 -0
- codelaw_rules_security_js/rules/sec-js/cookie-flags.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/cookie-flags.md +117 -0
- codelaw_rules_security_js/rules/sec-js/express-hardening.cases.md +12 -0
- codelaw_rules_security_js/rules/sec-js/express-hardening.md +135 -0
- codelaw_rules_security_js/rules/sec-js/express-session-secret.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/express-session-secret.md +101 -0
- codelaw_rules_security_js/rules/sec-js/graphql-hardening.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/graphql-hardening.md +116 -0
- codelaw_rules_security_js/rules/sec-js/helmet-config.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/helmet-config.md +88 -0
- codelaw_rules_security_js/rules/sec-js/jwt-verify.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/jwt-verify.md +107 -0
- codelaw_rules_security_js/rules/sec-js/missing-rate-limit.cases.md +22 -0
- codelaw_rules_security_js/rules/sec-js/missing-rate-limit.md +119 -0
- codelaw_rules_security_js/rules/sec-js/no-archive-slip.cases.md +11 -0
- codelaw_rules_security_js/rules/sec-js/no-archive-slip.md +119 -0
- codelaw_rules_security_js/rules/sec-js/no-bad-tag-filter.cases.md +11 -0
- codelaw_rules_security_js/rules/sec-js/no-bad-tag-filter.md +85 -0
- codelaw_rules_security_js/rules/sec-js/no-biased-random.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-biased-random.md +115 -0
- codelaw_rules_security_js/rules/sec-js/no-bidi-characters.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-bidi-characters.md +123 -0
- codelaw_rules_security_js/rules/sec-js/no-child-process-shell.cases.md +71 -0
- codelaw_rules_security_js/rules/sec-js/no-child-process-shell.md +141 -0
- codelaw_rules_security_js/rules/sec-js/no-cleartext-http.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-cleartext-http.md +147 -0
- codelaw_rules_security_js/rules/sec-js/no-cors-wildcard-credentials.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-cors-wildcard-credentials.md +92 -0
- codelaw_rules_security_js/rules/sec-js/no-csrf-before-method-override.cases.md +12 -0
- codelaw_rules_security_js/rules/sec-js/no-csrf-before-method-override.md +128 -0
- codelaw_rules_security_js/rules/sec-js/no-dangerously-set-inner-html.cases.md +141 -0
- codelaw_rules_security_js/rules/sec-js/no-dangerously-set-inner-html.md +107 -0
- codelaw_rules_security_js/rules/sec-js/no-document-cookie-token.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-document-cookie-token.md +89 -0
- codelaw_rules_security_js/rules/sec-js/no-dynamic-script-src.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-dynamic-script-src.md +100 -0
- codelaw_rules_security_js/rules/sec-js/no-electron-insecure.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-electron-insecure.md +134 -0
- codelaw_rules_security_js/rules/sec-js/no-eval.cases.md +52 -0
- codelaw_rules_security_js/rules/sec-js/no-eval.md +102 -0
- codelaw_rules_security_js/rules/sec-js/no-format-string.cases.md +11 -0
- codelaw_rules_security_js/rules/sec-js/no-format-string.md +115 -0
- codelaw_rules_security_js/rules/sec-js/no-hardcoded-crypto-key.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-hardcoded-crypto-key.md +96 -0
- codelaw_rules_security_js/rules/sec-js/no-hardcoded-secret.cases.md +61 -0
- codelaw_rules_security_js/rules/sec-js/no-hardcoded-secret.md +112 -0
- codelaw_rules_security_js/rules/sec-js/no-header-injection.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-header-injection.md +95 -0
- codelaw_rules_security_js/rules/sec-js/no-incomplete-sanitization.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-incomplete-sanitization.md +113 -0
- codelaw_rules_security_js/rules/sec-js/no-incomplete-url-check.cases.md +61 -0
- codelaw_rules_security_js/rules/sec-js/no-incomplete-url-check.md +118 -0
- codelaw_rules_security_js/rules/sec-js/no-insecure-random.cases.md +51 -0
- codelaw_rules_security_js/rules/sec-js/no-insecure-random.md +127 -0
- codelaw_rules_security_js/rules/sec-js/no-javascript-url.cases.md +61 -0
- codelaw_rules_security_js/rules/sec-js/no-javascript-url.md +102 -0
- codelaw_rules_security_js/rules/sec-js/no-jquery-html-sink.cases.md +139 -0
- codelaw_rules_security_js/rules/sec-js/no-jquery-html-sink.md +131 -0
- codelaw_rules_security_js/rules/sec-js/no-ldap-injection.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-ldap-injection.md +87 -0
- codelaw_rules_security_js/rules/sec-js/no-llm-output-to-sink.cases.md +34 -0
- codelaw_rules_security_js/rules/sec-js/no-llm-output-to-sink.md +105 -0
- codelaw_rules_security_js/rules/sec-js/no-log-injection.cases.md +11 -0
- codelaw_rules_security_js/rules/sec-js/no-log-injection.md +113 -0
- codelaw_rules_security_js/rules/sec-js/no-mass-assignment.cases.md +51 -0
- codelaw_rules_security_js/rules/sec-js/no-mass-assignment.md +100 -0
- codelaw_rules_security_js/rules/sec-js/no-new-buffer.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-new-buffer.md +120 -0
- codelaw_rules_security_js/rules/sec-js/no-non-literal-fs-filename.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-non-literal-fs-filename.md +151 -0
- codelaw_rules_security_js/rules/sec-js/no-non-literal-require.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-non-literal-require.md +88 -0
- codelaw_rules_security_js/rules/sec-js/no-nosql-injection.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-nosql-injection.md +114 -0
- codelaw_rules_security_js/rules/sec-js/no-open-redirect.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-open-redirect.md +106 -0
- codelaw_rules_security_js/rules/sec-js/no-path-traversal.cases.md +54 -0
- codelaw_rules_security_js/rules/sec-js/no-path-traversal.md +187 -0
- codelaw_rules_security_js/rules/sec-js/no-prompt-injection.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-prompt-injection.md +118 -0
- codelaw_rules_security_js/rules/sec-js/no-prototype-pollution.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-prototype-pollution.md +135 -0
- codelaw_rules_security_js/rules/sec-js/no-secret-in-log.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-secret-in-log.md +86 -0
- codelaw_rules_security_js/rules/sec-js/no-sensitive-get-query.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-sensitive-get-query.md +89 -0
- codelaw_rules_security_js/rules/sec-js/no-server-env-in-client.cases.md +42 -0
- codelaw_rules_security_js/rules/sec-js/no-server-env-in-client.md +107 -0
- codelaw_rules_security_js/rules/sec-js/no-sql-template.cases.md +51 -0
- codelaw_rules_security_js/rules/sec-js/no-sql-template.md +112 -0
- codelaw_rules_security_js/rules/sec-js/no-ssrf.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-ssrf.md +165 -0
- codelaw_rules_security_js/rules/sec-js/no-ssti.cases.md +54 -0
- codelaw_rules_security_js/rules/sec-js/no-ssti.md +128 -0
- codelaw_rules_security_js/rules/sec-js/no-stack-trace-response.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-stack-trace-response.md +102 -0
- codelaw_rules_security_js/rules/sec-js/no-tls-bypass.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-tls-bypass.md +117 -0
- codelaw_rules_security_js/rules/sec-js/no-token-in-web-storage.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-token-in-web-storage.md +92 -0
- codelaw_rules_security_js/rules/sec-js/no-unsafe-deserialize.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-unsafe-deserialize.md +115 -0
- codelaw_rules_security_js/rules/sec-js/no-unsafe-regex.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-unsafe-regex.md +93 -0
- codelaw_rules_security_js/rules/sec-js/no-unsanitized-html.cases.md +199 -0
- codelaw_rules_security_js/rules/sec-js/no-unsanitized-html.md +143 -0
- codelaw_rules_security_js/rules/sec-js/no-unvalidated-dynamic-method.cases.md +34 -0
- codelaw_rules_security_js/rules/sec-js/no-unvalidated-dynamic-method.md +94 -0
- codelaw_rules_security_js/rules/sec-js/no-user-controlled-auth-bypass.cases.md +11 -0
- codelaw_rules_security_js/rules/sec-js/no-user-controlled-auth-bypass.md +102 -0
- codelaw_rules_security_js/rules/sec-js/no-weak-cipher.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/no-weak-cipher.md +95 -0
- codelaw_rules_security_js/rules/sec-js/no-weak-hash.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-weak-hash.md +97 -0
- codelaw_rules_security_js/rules/sec-js/no-weak-key-size.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-weak-key-size.md +117 -0
- codelaw_rules_security_js/rules/sec-js/no-window-open-opener.cases.md +31 -0
- codelaw_rules_security_js/rules/sec-js/no-window-open-opener.md +115 -0
- codelaw_rules_security_js/rules/sec-js/no-xpath-injection.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-xpath-injection.md +95 -0
- codelaw_rules_security_js/rules/sec-js/no-xxe.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/no-xxe.md +124 -0
- codelaw_rules_security_js/rules/sec-js/password-hash-effort.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/password-hash-effort.md +97 -0
- codelaw_rules_security_js/rules/sec-js/postmessage-origin.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/postmessage-origin.md +125 -0
- codelaw_rules_security_js/rules/sec-js/timing-safe-compare.cases.md +41 -0
- codelaw_rules_security_js/rules/sec-js/timing-safe-compare.md +142 -0
- codelaw_rules_security_js/rules/sec-js/upload-limits.cases.md +21 -0
- codelaw_rules_security_js/rules/sec-js/upload-limits.md +115 -0
- codelaw_rules_security_js-0.3.0.dist-info/METADATA +193 -0
- codelaw_rules_security_js-0.3.0.dist-info/RECORD +148 -0
- codelaw_rules_security_js-0.3.0.dist-info/WHEEL +4 -0
- codelaw_rules_security_js-0.3.0.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-js/angularjs-sce-disabled
|
|
3
|
+
title: "No AngularJS strict contextual escaping switched off"
|
|
4
|
+
languages: [javascript, jsx, typescript, tsx]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which sources the application loads from is a decision for the author"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [79], owasp: ["A05:2025"], severity: high }
|
|
9
|
+
since: 0.3.0
|
|
10
|
+
options:
|
|
11
|
+
sanitizers:
|
|
12
|
+
type: "call[]"
|
|
13
|
+
description: "functions whose result may reach the sink: a callee path (global) or { name, module }; `sanitizers_mode: replace` makes them the only ones that count"
|
|
14
|
+
default:
|
|
15
|
+
- { name: DOMPurify.sanitize, module: [dompurify, isomorphic-dompurify] }
|
|
16
|
+
- DOMPurify.sanitize
|
|
17
|
+
- { name: sanitizeHtml, module: sanitize-html }
|
|
18
|
+
- { name: xss, module: xss }
|
|
19
|
+
- { name: filterXSS, module: xss }
|
|
20
|
+
- { name: escapeHtml, module: escape-html }
|
|
21
|
+
- { name: escape, module: [lodash, lodash-es, lodash/escape, he] }
|
|
22
|
+
- { name: _.escape, module: [lodash, lodash-es] }
|
|
23
|
+
- { name: he.escape, module: he }
|
|
24
|
+
- "*.createHTML"
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
AngularJS's `$sce` stays on, `resourceUrlWhitelist` has no `"**"`, the href and image whitelists are not match-everything, and `$sce.trustAs*` is not given an unsanitized value.
|
|
28
|
+
|
|
29
|
+
**Your sanitizer:** a value passes when it is a call to one of the `sanitizers`: the built-in list (DOMPurify, sanitize-html, xss, escape-html, lodash and he `escape`, Trusted Types `createHTML`) plus your own, each matched by name and by the module it is imported from, so a local `function sanitize(x) { return x }` does not pass. `sanitizers_mode = "replace"` makes yours the only ones that count. It does not follow a value through a variable: `const c = clean(x)` then `c` at the sink is not a call. Add a project function with `sanitizers = [{ name = "sanitizeHtml", module = "@acme/security" }]`.
|
|
30
|
+
|
|
31
|
+
**Why:** `$sce` is what stops a bound string from becoming HTML, a script or a frame source; switching it off or trusting every value turns each binding into an XSS sink (CWE-79).
|
|
32
|
+
|
|
33
|
+
```codelaw
|
|
34
|
+
match:
|
|
35
|
+
any:
|
|
36
|
+
- pattern: $sceProvider.enabled(false)
|
|
37
|
+
- all:
|
|
38
|
+
- pattern: $SD.resourceUrlWhitelist($A)
|
|
39
|
+
- regex: '["'']\*\*["'']'
|
|
40
|
+
- all:
|
|
41
|
+
- pattern: $CP.$M($R)
|
|
42
|
+
- regex: '(aHref|imgSrc)SanitizationWhitelist\(\s*/(\^?\.\*?\$?|\^)/[a-z]*\s*\)'
|
|
43
|
+
- pattern: $SCE.trustAsHtml($V)
|
|
44
|
+
- pattern: $SCE.trustAsJs($V)
|
|
45
|
+
- pattern: $SCE.trustAsUrl($V)
|
|
46
|
+
- pattern: $SCE.trustAsResourceUrl($V)
|
|
47
|
+
constraints:
|
|
48
|
+
V: { not: { any: [ { kind: string }, { regex: '^`[^`$]*`$' } ] } }
|
|
49
|
+
where:
|
|
50
|
+
$V: { not: { calls: sanitizers } }
|
|
51
|
+
message: "AngularJS's strict contextual escaping is weakened here"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## How to fix
|
|
55
|
+
|
|
56
|
+
1. Leave `$sceProvider.enabled` at its default (`true`).
|
|
57
|
+
2. List the hosts a template may load from in `resourceUrlWhitelist`: `["self", "https://cdn.example.com/**"]`, not `"**"`.
|
|
58
|
+
3. Pass a value to `$sce.trustAsHtml` only after a sanitizer (`$sanitize`, DOMPurify) has cleaned it.
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### JavaScript
|
|
63
|
+
|
|
64
|
+
#### Don't
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
app.config(function ($sceProvider) {
|
|
68
|
+
$sceProvider.enabled(false);
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Do
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
app.config(function ($sceProvider) {
|
|
76
|
+
$sceProvider.enabled(true);
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### JSX
|
|
81
|
+
|
|
82
|
+
#### Don't
|
|
83
|
+
|
|
84
|
+
```jsx
|
|
85
|
+
app.config(function ($sceProvider) {
|
|
86
|
+
$sceProvider.enabled(false);
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Do
|
|
91
|
+
|
|
92
|
+
```jsx
|
|
93
|
+
app.config(function ($sceProvider) {
|
|
94
|
+
$sceProvider.enabled(true);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### TypeScript
|
|
99
|
+
|
|
100
|
+
#### Don't
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
app.config(function ($sceProvider) {
|
|
104
|
+
$sceProvider.enabled(false);
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Do
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
app.config(function ($sceProvider) {
|
|
112
|
+
$sceProvider.enabled(true);
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### TSX
|
|
117
|
+
|
|
118
|
+
#### Don't
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
app.config(function ($sceProvider) {
|
|
122
|
+
$sceProvider.enabled(false);
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Do
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
app.config(function ($sceProvider) {
|
|
130
|
+
$sceProvider.enabled(true);
|
|
131
|
+
});
|
|
132
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# sec-js/cookie-flags — cases
|
|
2
|
+
|
|
3
|
+
### case: options that lack sameSite have no mechanical fix
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true });
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:1 sec-js/cookie-flags
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: all three flags
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "strict" });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
(none)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### case: koa
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
ctx.cookies.set("sid", id);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```expect
|
|
30
|
+
1:1 sec-js/cookie-flags
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### case: fastify
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
reply.setCookie("sid", id);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```expect
|
|
40
|
+
1:1 sec-js/cookie-flags
|
|
41
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-js/cookie-flags
|
|
3
|
+
title: "A cookie sets httpOnly, secure and sameSite"
|
|
4
|
+
languages: [javascript, jsx, typescript, tsx]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "a cookie a script must read, or one used cross-site, needs different flags"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [614, 1004], owasp: ["A02:2025"], severity: medium }
|
|
9
|
+
since: 0.3.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`res.cookie`, `reply.setCookie` and `ctx.cookies.set` pass an options object with `httpOnly`, `secure` and `sameSite`.
|
|
13
|
+
|
|
14
|
+
**Why:** Without `httpOnly` a script reads the cookie, without `secure` it travels over plain HTTP, and without `sameSite` the browser sends it on cross-site requests (CWE-614, CWE-1004, CWE-352).
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
match:
|
|
18
|
+
any:
|
|
19
|
+
- pattern: $C($N, $V)
|
|
20
|
+
- pattern: $C2($N2, $V2, $OPTS)
|
|
21
|
+
constraints:
|
|
22
|
+
C: { regex: '^(res|response)\.cookie$|^reply\.setCookie$|^ctx\.cookies\.set$' }
|
|
23
|
+
C2: { regex: '^(res|response)\.cookie$|^reply\.setCookie$|^ctx\.cookies\.set$' }
|
|
24
|
+
OPTS:
|
|
25
|
+
any:
|
|
26
|
+
- not: { regex: 'httpOnly' }
|
|
27
|
+
- not: { regex: 'secure' }
|
|
28
|
+
- not: { regex: 'sameSite' }
|
|
29
|
+
message: "This cookie is missing `httpOnly`, `secure` or `sameSite`"
|
|
30
|
+
fix:
|
|
31
|
+
- replace: "$C($N, $V, { httpOnly: true, secure: true, sameSite: \"lax\" })"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## How to fix
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
### JavaScript
|
|
40
|
+
|
|
41
|
+
#### Don't
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
res.cookie("sid", sid);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Do
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Fixed
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### JSX
|
|
60
|
+
|
|
61
|
+
#### Don't
|
|
62
|
+
|
|
63
|
+
```jsx
|
|
64
|
+
res.cookie("sid", sid);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Do
|
|
68
|
+
|
|
69
|
+
```jsx
|
|
70
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Fixed
|
|
74
|
+
|
|
75
|
+
```jsx
|
|
76
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### TypeScript
|
|
80
|
+
|
|
81
|
+
#### Don't
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
res.cookie("sid", sid);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Do
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Fixed
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### TSX
|
|
100
|
+
|
|
101
|
+
#### Don't
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
res.cookie("sid", sid);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Do
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Fixed
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
res.cookie("sid", sid, { httpOnly: true, secure: true, sameSite: "lax" });
|
|
117
|
+
```
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-js/express-hardening
|
|
3
|
+
title: "An Express app turns off x-powered-by or uses helmet"
|
|
4
|
+
languages: [javascript, jsx, typescript, tsx]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "`app.use(helmet())` is the better fix and needs an import; the inserted line only hides the framework"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [200], owasp: ["A02:2025"], severity: low }
|
|
9
|
+
since: 0.3.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A file that creates an Express app also calls `helmet()` or disables `x-powered-by`.
|
|
13
|
+
|
|
14
|
+
**Why:** The default `X-Powered-By: Express` header names the framework for every scanner, and without `helmet` the app sends none of the headers that limit framing, sniffing and mixed content (CWE-200).
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
match:
|
|
18
|
+
all:
|
|
19
|
+
- pattern: const $A = express()
|
|
20
|
+
- not:
|
|
21
|
+
inside:
|
|
22
|
+
kind: program
|
|
23
|
+
stopBy: end
|
|
24
|
+
has:
|
|
25
|
+
stopBy: end
|
|
26
|
+
regex: 'helmet|x-powered-by'
|
|
27
|
+
message: "This Express app does not call `helmet()` or disable `x-powered-by`"
|
|
28
|
+
fix:
|
|
29
|
+
- insert-after: "\n$A.disable(\"x-powered-by\");"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## How to fix
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
### JavaScript
|
|
38
|
+
|
|
39
|
+
#### Don't
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const app = express();
|
|
43
|
+
app.get("/", handler);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Do
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const app = express();
|
|
50
|
+
app.use(helmet());
|
|
51
|
+
app.get("/", handler);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Fixed
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
const app = express();
|
|
58
|
+
app.disable("x-powered-by");
|
|
59
|
+
app.get("/", handler);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### JSX
|
|
63
|
+
|
|
64
|
+
#### Don't
|
|
65
|
+
|
|
66
|
+
```jsx
|
|
67
|
+
const app = express();
|
|
68
|
+
app.get("/", handler);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Do
|
|
72
|
+
|
|
73
|
+
```jsx
|
|
74
|
+
const app = express();
|
|
75
|
+
app.use(helmet());
|
|
76
|
+
app.get("/", handler);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Fixed
|
|
80
|
+
|
|
81
|
+
```jsx
|
|
82
|
+
const app = express();
|
|
83
|
+
app.disable("x-powered-by");
|
|
84
|
+
app.get("/", handler);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### TypeScript
|
|
88
|
+
|
|
89
|
+
#### Don't
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const app = express();
|
|
93
|
+
app.get("/", handler);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Do
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const app = express();
|
|
100
|
+
app.use(helmet());
|
|
101
|
+
app.get("/", handler);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### Fixed
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const app = express();
|
|
108
|
+
app.disable("x-powered-by");
|
|
109
|
+
app.get("/", handler);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### TSX
|
|
113
|
+
|
|
114
|
+
#### Don't
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
const app = express();
|
|
118
|
+
app.get("/", handler);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Do
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
const app = express();
|
|
125
|
+
app.use(helmet());
|
|
126
|
+
app.get("/", handler);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Fixed
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
const app = express();
|
|
133
|
+
app.disable("x-powered-by");
|
|
134
|
+
app.get("/", handler);
|
|
135
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# sec-js/express-session-secret — cases
|
|
2
|
+
|
|
3
|
+
### case: secure false
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
session({ secret: key, cookie: { secure: false } });
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:1 sec-js/express-session-secret
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: cookie-session keys
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
cookieSession({ keys: ["a"] });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
1:1 sec-js/express-session-secret
|
|
21
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-js/express-session-secret
|
|
3
|
+
title: "express-session has a secret from the environment and a secure cookie"
|
|
4
|
+
languages: [javascript, jsx, typescript, tsx]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "where the secret comes from is a deployment decision"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [798, 614], owasp: ["A07:2025"], severity: high }
|
|
9
|
+
since: 0.3.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`express-session`, `cookie-session` and `session()` are not given a literal `secret`, and their cookie does not set `secure: false`.
|
|
13
|
+
|
|
14
|
+
**Why:** A secret in the source is in every clone and every build; anyone who has it forges a session cookie, and `secure: false` lets the cookie travel over plain HTTP (CWE-798, CWE-614).
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
match:
|
|
18
|
+
any:
|
|
19
|
+
- all:
|
|
20
|
+
- any:
|
|
21
|
+
- pattern: session($$$A)
|
|
22
|
+
- pattern: expressSession($$$A)
|
|
23
|
+
- pattern: cookieSession($$$A)
|
|
24
|
+
- has:
|
|
25
|
+
kind: pair
|
|
26
|
+
stopBy: end
|
|
27
|
+
regex: '^["'']?(secret|keys?)["'']?\s*:\s*(["'']|\[\s*["''])'
|
|
28
|
+
- all:
|
|
29
|
+
- any:
|
|
30
|
+
- pattern: session($$$B)
|
|
31
|
+
- pattern: expressSession($$$B)
|
|
32
|
+
- pattern: cookieSession($$$B)
|
|
33
|
+
- has:
|
|
34
|
+
kind: pair
|
|
35
|
+
stopBy: end
|
|
36
|
+
regex: '^["'']?secure["'']?\s*:\s*false\s*$'
|
|
37
|
+
message: "The session secret is a literal in the source, or the session cookie is not marked secure"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## How to fix
|
|
41
|
+
|
|
42
|
+
1. Read the secret from the environment or a secret store, and rotate it if it was ever committed.
|
|
43
|
+
2. Set `cookie: { secure: true, httpOnly: true, sameSite: "lax" }` in production (behind a proxy, set `trust proxy`).
|
|
44
|
+
|
|
45
|
+
## Examples
|
|
46
|
+
|
|
47
|
+
### JavaScript
|
|
48
|
+
|
|
49
|
+
#### Don't
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
app.use(session({ secret: "keyboard cat", resave: false }));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Do
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
app.use(session({ secret: process.env.SESSION_SECRET, resave: false }));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### JSX
|
|
62
|
+
|
|
63
|
+
#### Don't
|
|
64
|
+
|
|
65
|
+
```jsx
|
|
66
|
+
app.use(session({ secret: "keyboard cat", resave: false }));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Do
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
app.use(session({ secret: process.env.SESSION_SECRET, resave: false }));
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### TypeScript
|
|
76
|
+
|
|
77
|
+
#### Don't
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
app.use(session({ secret: "keyboard cat", resave: false }));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Do
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
app.use(session({ secret: process.env.SESSION_SECRET, resave: false }));
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### TSX
|
|
90
|
+
|
|
91
|
+
#### Don't
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
app.use(session({ secret: "keyboard cat", resave: false }));
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Do
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
app.use(session({ secret: process.env.SESSION_SECRET, resave: false }));
|
|
101
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# sec-js/graphql-hardening — cases
|
|
2
|
+
|
|
3
|
+
### case: introspection
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
const o = { introspection: true };
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:13 sec-js/graphql-hardening
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: yoga without limits
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
createYoga({ schema });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
1:1 sec-js/graphql-hardening
|
|
21
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-js/graphql-hardening
|
|
3
|
+
title: "A GraphQL server limits query depth and cost, and hides introspection"
|
|
4
|
+
languages: [javascript, jsx, typescript, tsx]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the limits that fit the schema are a decision for the author"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [200, 770], owasp: ["A02:2025"], severity: medium }
|
|
9
|
+
since: 0.3.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An Apollo, Yoga or express-graphql server names a depth or cost limit in its options, and does not set `introspection: true`.
|
|
13
|
+
|
|
14
|
+
**Why:** A query can nest or fan out without bound and exhaust the server, and introspection hands an attacker the whole schema (CWE-770, CWE-200).
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
match:
|
|
18
|
+
any:
|
|
19
|
+
- all:
|
|
20
|
+
- any:
|
|
21
|
+
- pattern: new ApolloServer($$$A)
|
|
22
|
+
- pattern: createYoga($$$A)
|
|
23
|
+
- pattern: graphqlHTTP($$$A)
|
|
24
|
+
- pattern: createHandler($$$A)
|
|
25
|
+
- not:
|
|
26
|
+
has:
|
|
27
|
+
stopBy: end
|
|
28
|
+
regex: '(?i)depthLimit|costLimit|queryComplexity|maxDepth|validationRules|cost|complexity|armor'
|
|
29
|
+
- pattern:
|
|
30
|
+
context: '({ introspection: true })'
|
|
31
|
+
selector: pair
|
|
32
|
+
message: "This GraphQL server has no depth or cost limit, or leaves introspection on"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## How to fix
|
|
36
|
+
|
|
37
|
+
1. Add a depth limit (`graphql-depth-limit`) and a cost or complexity limit to `validationRules`, and cap the page size of list fields.
|
|
38
|
+
2. Turn introspection off in production, and use persisted queries where the clients are your own.
|
|
39
|
+
|
|
40
|
+
## Examples
|
|
41
|
+
|
|
42
|
+
### JavaScript
|
|
43
|
+
|
|
44
|
+
#### Don't
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
const server = new ApolloServer({ typeDefs, resolvers });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### Do
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const server = new ApolloServer({
|
|
54
|
+
typeDefs,
|
|
55
|
+
resolvers,
|
|
56
|
+
validationRules: [depthLimit(8)],
|
|
57
|
+
introspection: process.env.NODE_ENV !== "production",
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### JSX
|
|
62
|
+
|
|
63
|
+
#### Don't
|
|
64
|
+
|
|
65
|
+
```jsx
|
|
66
|
+
const server = new ApolloServer({ typeDefs, resolvers });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Do
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
const server = new ApolloServer({
|
|
73
|
+
typeDefs,
|
|
74
|
+
resolvers,
|
|
75
|
+
validationRules: [depthLimit(8)],
|
|
76
|
+
introspection: process.env.NODE_ENV !== "production",
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### TypeScript
|
|
81
|
+
|
|
82
|
+
#### Don't
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const server = new ApolloServer({ typeDefs, resolvers });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### Do
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const server = new ApolloServer({
|
|
92
|
+
typeDefs,
|
|
93
|
+
resolvers,
|
|
94
|
+
validationRules: [depthLimit(8)],
|
|
95
|
+
introspection: process.env.NODE_ENV !== "production",
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### TSX
|
|
100
|
+
|
|
101
|
+
#### Don't
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
const server = new ApolloServer({ typeDefs, resolvers });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Do
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
const server = new ApolloServer({
|
|
111
|
+
typeDefs,
|
|
112
|
+
resolvers,
|
|
113
|
+
validationRules: [depthLimit(8)],
|
|
114
|
+
introspection: process.env.NODE_ENV !== "production",
|
|
115
|
+
});
|
|
116
|
+
```
|