codelaw-rules-security-html 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_html/LICENSE +23 -0
- codelaw_rules_security_html/NOTICE +9 -0
- codelaw_rules_security_html/README.md +98 -0
- codelaw_rules_security_html/codelaw-pack.toml +6 -0
- codelaw_rules_security_html/corpus.toml +23 -0
- codelaw_rules_security_html/presets/sec-html/recommended.toml +18 -0
- codelaw_rules_security_html/presets/sec-html/strict.toml +9 -0
- codelaw_rules_security_html/rules/sec-html/angularjs-url-expression.cases.md +26 -0
- codelaw_rules_security_html/rules/sec-html/angularjs-url-expression.md +34 -0
- codelaw_rules_security_html/rules/sec-html/csp-meta-unsafe.cases.md +51 -0
- codelaw_rules_security_html/rules/sec-html/csp-meta-unsafe.md +40 -0
- codelaw_rules_security_html/rules/sec-html/form-csrf-token.cases.md +51 -0
- codelaw_rules_security_html/rules/sec-html/form-csrf-token.md +41 -0
- codelaw_rules_security_html/rules/sec-html/iframe-allow-powerful.cases.md +31 -0
- codelaw_rules_security_html/rules/sec-html/iframe-allow-powerful.md +39 -0
- codelaw_rules_security_html/rules/sec-html/iframe-sandbox.cases.md +63 -0
- codelaw_rules_security_html/rules/sec-html/iframe-sandbox.md +34 -0
- codelaw_rules_security_html/rules/sec-html/no-credentials-in-url.cases.md +21 -0
- codelaw_rules_security_html/rules/sec-html/no-credentials-in-url.md +39 -0
- codelaw_rules_security_html/rules/sec-html/no-external-base.cases.md +21 -0
- codelaw_rules_security_html/rules/sec-html/no-external-base.md +39 -0
- codelaw_rules_security_html/rules/sec-html/no-inline-event-handler.cases.md +32 -0
- codelaw_rules_security_html/rules/sec-html/no-inline-event-handler.md +40 -0
- codelaw_rules_security_html/rules/sec-html/no-javascript-url.cases.md +61 -0
- codelaw_rules_security_html/rules/sec-html/no-javascript-url.md +39 -0
- codelaw_rules_security_html/rules/sec-html/no-mixed-content.cases.md +79 -0
- codelaw_rules_security_html/rules/sec-html/no-mixed-content.md +34 -0
- codelaw_rules_security_html/rules/sec-html/no-plugin-embed.cases.md +25 -0
- codelaw_rules_security_html/rules/sec-html/no-plugin-embed.md +40 -0
- codelaw_rules_security_html/rules/sec-html/no-secret-in-hidden-input.cases.md +31 -0
- codelaw_rules_security_html/rules/sec-html/no-secret-in-hidden-input.md +39 -0
- codelaw_rules_security_html/rules/sec-html/no-sensitive-comments.cases.md +64 -0
- codelaw_rules_security_html/rules/sec-html/no-sensitive-comments.md +34 -0
- codelaw_rules_security_html/rules/sec-html/no-srcdoc-script.cases.md +21 -0
- codelaw_rules_security_html/rules/sec-html/no-srcdoc-script.md +39 -0
- codelaw_rules_security_html/rules/sec-html/no-template-html-sink.cases.md +23 -0
- codelaw_rules_security_html/rules/sec-html/no-template-html-sink.md +39 -0
- codelaw_rules_security_html/rules/sec-html/no-unsafe-template-interpolation.cases.md +45 -0
- codelaw_rules_security_html/rules/sec-html/no-unsafe-template-interpolation.md +39 -0
- codelaw_rules_security_html/rules/sec-html/password-form-post.cases.md +63 -0
- codelaw_rules_security_html/rules/sec-html/password-form-post.md +34 -0
- codelaw_rules_security_html/rules/sec-html/referrer-policy.cases.md +39 -0
- codelaw_rules_security_html/rules/sec-html/referrer-policy.md +34 -0
- codelaw_rules_security_html/rules/sec-html/sri-crossorigin.cases.md +35 -0
- codelaw_rules_security_html/rules/sec-html/sri-crossorigin.md +34 -0
- codelaw_rules_security_html-0.3.0.dist-info/METADATA +108 -0
- codelaw_rules_security_html-0.3.0.dist-info/RECORD +49 -0
- codelaw_rules_security_html-0.3.0.dist-info/WHEEL +4 -0
- codelaw_rules_security_html-0.3.0.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arpit Arya
|
|
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.
|
|
22
|
+
|
|
23
|
+
SPDX-License-Identifier: MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@codelaw/rules-security-html · codelaw-rules-security-html
|
|
2
|
+
|
|
3
|
+
Every rule in this pack is written for codelaw. The catalogue was read against
|
|
4
|
+
the OWASP HTML5 Security, Content Security Policy and Cross-Site Request
|
|
5
|
+
Forgery cheat sheets (CC BY-SA 4.0; ideas only, no text taken), MDN's
|
|
6
|
+
Subresource Integrity page and CodeQL's query list (MIT; ideas only).
|
|
7
|
+
Rule sets under a copyleft or source-available licence — eslint-plugin-no-unsanitized
|
|
8
|
+
(MPL-2.0), njsscan (LGPL-3.0), Semgrep's rules (the Semgrep Rules License) — are
|
|
9
|
+
read for ideas only; no text, pattern or code is taken from them (L2).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# `@codelaw/rules-security-html` · `codelaw-rules-security-html`
|
|
2
|
+
|
|
3
|
+
codelaw's HTML security rules (CSP, mixed content, iframes, forms, template sinks) — one of codelaw's first-party rule packs
|
|
4
|
+
([SR-RULE-PACKS](../../records/0101_rule-packs.md)). The same files ship on npm as `@codelaw/rules-security-html` and on PyPI as
|
|
5
|
+
`codelaw-rules-security-html`. Its presets **extend** `codelaw:html/security`,
|
|
6
|
+
so the html pack's five security rules (`html/require-sri`,
|
|
7
|
+
`html/target-blank-rel`, `html/no-inline-script`, `html/autocomplete-password`,
|
|
8
|
+
`html/meta-refresh`) run without being copied here; it needs the html pack
|
|
9
|
+
(`@codelaw/rules-html` or `codelaw-rules-html`) installed beside it.
|
|
10
|
+
|
|
11
|
+
```toml
|
|
12
|
+
# codelaw.toml
|
|
13
|
+
extends = ["codelaw:html/recommended", "codelaw:sec-html/recommended"]
|
|
14
|
+
preview = true # every rule is `preview` until a corpus run promotes it
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`codelaw init` does not add it: a security pack is chosen, not detected.
|
|
18
|
+
|
|
19
|
+
## Presets
|
|
20
|
+
|
|
21
|
+
<!-- docs-sync:presets -->
|
|
22
|
+
|
|
23
|
+
| preset | rules | ported from |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `sec-html/recommended` | 19 | extends `html/security` (the html pack's five security rules); high-confidence rules, at `error` |
|
|
26
|
+
| `sec-html/strict` | 24 | `sec-html/recommended` and the heuristic rules, at `warn` |
|
|
27
|
+
|
|
28
|
+
<!-- /docs-sync:presets -->
|
|
29
|
+
|
|
30
|
+
`recommended` holds only high-confidence rules, at `error`; `strict` adds the
|
|
31
|
+
heuristic ones, at `warn`, so a team can start without noise.
|
|
32
|
+
|
|
33
|
+
## Rules
|
|
34
|
+
|
|
35
|
+
| rule | flags | CWE | OWASP 2025 | fix | preset |
|
|
36
|
+
|---|---|---|---|---|---|
|
|
37
|
+
| `sec-html/no-inline-event-handler` | `onclick=`, `onerror=` … attributes (they block a strict CSP) | 79 | A05 | manual | `recommended` |
|
|
38
|
+
| `sec-html/no-javascript-url` | `javascript:` in `href`, `src`, `action`, `formaction`, `xlink:href`, `<object data>` | 79 | A05 | manual | `recommended` |
|
|
39
|
+
| `sec-html/no-mixed-content` | `http://` in `src`, `action`, a stylesheet/script `href`, a download link | 319 | A04 | suggest | `recommended` |
|
|
40
|
+
| `sec-html/password-form-post` | a password form with `method=get`, no `method`, or an `http:` action | 598 | A04 | suggest | `recommended` |
|
|
41
|
+
| `sec-html/iframe-sandbox` | `<iframe>` without `sandbox`, or `allow-scripts` with `allow-same-origin` | 1021 | A02 | suggest | `recommended` |
|
|
42
|
+
| `sec-html/csp-meta-unsafe` | a meta CSP with `'unsafe-inline'`, `'unsafe-eval'`, `*` or `data:` in script sources | 693 | A02 | manual | `recommended` |
|
|
43
|
+
| `sec-html/no-credentials-in-url` | `user:pass@host` in a URL attribute | 522 | A07 | manual | `recommended` |
|
|
44
|
+
| `sec-html/no-template-html-sink` | Angular `[innerHTML]`, Vue `v-html`, Alpine `x-html` | 79 | A05 | manual | `recommended` |
|
|
45
|
+
| `sec-html/referrer-policy` | `referrerpolicy` or `<meta name=referrer>` set to `unsafe-url` or `no-referrer-when-downgrade` | 200 | A02 | suggest | `recommended` |
|
|
46
|
+
| `sec-html/no-srcdoc-script` | `<iframe srcdoc>` holding a script, handler or `javascript:` | 79 | A05 | manual | `recommended` |
|
|
47
|
+
| `sec-html/sri-crossorigin` | `integrity` on a cross-origin file without `crossorigin` | 353 | A08 | suggest | `recommended` |
|
|
48
|
+
| `sec-html/no-secret-in-hidden-input` | a hidden `password`/`token`/`secret`/`api-key` input with a literal value (CSRF tokens excluded) | 798, 200 | A07 | manual | `recommended` |
|
|
49
|
+
| `sec-html/angularjs-url-expression` | `href="{{…}}"` / `src="{{…}}"` in an AngularJS page | 79 | A05 | suggest | `recommended` |
|
|
50
|
+
| `sec-html/no-unsafe-template-interpolation` | Handlebars `{{{ }}}`, `|safe`, `{% autoescape off %}`, EJS `<%-` | 79 | A05 | manual | `recommended` |
|
|
51
|
+
| `sec-html/no-external-base` | `<base href>` on another origin | — | A05 | manual | `strict` |
|
|
52
|
+
| `sec-html/no-plugin-embed` | `<object>`, `<embed>`, `<applet>` | — | A02 | manual | `strict` |
|
|
53
|
+
| `sec-html/no-sensitive-comments` | an HTML comment with a password, key, internal host or a note to remove | 615 | A02 | auto | `strict` |
|
|
54
|
+
| `sec-html/form-csrf-token` | a `method=post` form with no CSRF field (extra names: `names` option) | 352 | A01 | manual | `strict` |
|
|
55
|
+
| `sec-html/iframe-allow-powerful` | a cross-origin `<iframe allow="camera; microphone; …">` | 250 | A02 | manual | `strict` |
|
|
56
|
+
|
|
57
|
+
Each rule is native (a Rust implementation in the engine, read with the same
|
|
58
|
+
document model as the `html/*` rules) and is pattern-level: one start tag,
|
|
59
|
+
one attribute, one comment or one run of template syntax. A value that is
|
|
60
|
+
template code (`{{ }}`, `<% %>`) is judged by its literal prefix only, and
|
|
61
|
+
the AngularJS rule runs only on a page that shows AngularJS. The catalogue
|
|
62
|
+
and its sources are [W-56](../../archive/open/W-56-security-html-pack.md) and the
|
|
63
|
+
OWASP HTML5, CSP and Cross-Site Request Forgery cheat sheets.
|
|
64
|
+
|
|
65
|
+
`sec-html/no-inline-event-handler` and `sec-html/no-javascript-url` overlap
|
|
66
|
+
`html/no-inline-script`, which the preset also turns on: a handler is
|
|
67
|
+
reported twice, once with its CWE. Turn either off in `[rules]` if one report
|
|
68
|
+
is enough.
|
|
69
|
+
|
|
70
|
+
`suggest` fixes (`https://`, `method="post"`, `sandbox`, `crossorigin`,
|
|
71
|
+
`referrerpolicy`) apply with `codelaw fix --unsafe`. A fix is not applied when
|
|
72
|
+
it would add a finding the file did not have: under `strict`, turning a form
|
|
73
|
+
to POST is refused while `sec-html/form-csrf-token` has nothing to see.
|
|
74
|
+
|
|
75
|
+
Every rule here carries `security: { cwe, owasp, severity }`
|
|
76
|
+
([SR-RULE-FORMAT](../../records/0112_rule-format.md) decision 2a), so
|
|
77
|
+
|
|
78
|
+
- its finding names the CWE (`sec-html/<rule> (CWE-79) …`);
|
|
79
|
+
- `codelaw rules --tag cwe:79` and `--tag owasp:A05` find it, and
|
|
80
|
+
`codelaw rules --security-coverage` puts it in the OWASP Top 10:2025 matrix;
|
|
81
|
+
- SARIF carries its `security` and `external/cwe/cwe-<n>` tags and a
|
|
82
|
+
`security-severity` that GitHub code scanning ranks by
|
|
83
|
+
([SR-CLI-MCP](../../records/0115_cli-and-mcp.md) decision 4).
|
|
84
|
+
|
|
85
|
+
## `from_source:` is not taint analysis
|
|
86
|
+
|
|
87
|
+
A rule here that asks whether a sink's argument comes from a request
|
|
88
|
+
uses the `from_source:` predicate ([SR-RULE-FORMAT](../../records/0112_rule-format.md)
|
|
89
|
+
decision 3a). It holds when the argument **is** a listed request source
|
|
90
|
+
(or a method called on one), is built from one by concatenation,
|
|
91
|
+
interpolation or `?:`, or is a local name bound to one **in the same
|
|
92
|
+
function** — one or more assignments or destructurings away. It never
|
|
93
|
+
follows a call, a return value, a parameter, a closure's outer variable or
|
|
94
|
+
another file, so a value that crosses a function boundary is not seen
|
|
95
|
+
(cross-function flow waits on a deeper analysis, as B-049's type-aware
|
|
96
|
+
rules do). Every rule
|
|
97
|
+
page that uses it says so. The sources are listed per framework in this
|
|
98
|
+
pack's `data/sources/<framework>.json`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
name = "@codelaw/rules-security-html"
|
|
2
|
+
version = "0.3.0"
|
|
3
|
+
min_codelaw = "0.2.0"
|
|
4
|
+
languages = ["html"]
|
|
5
|
+
presets = ["sec-html/recommended", "sec-html/strict"]
|
|
6
|
+
description = "codelaw's security rules for HTML; its presets also turn on the html pack's security rules (SR-RULE-PACKS)"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# `codelaw test --corpus` finding counts (SR-RULE-AUTHORING decision 4).
|
|
2
|
+
# Rewrite with --update-snapshot, and review the change.
|
|
3
|
+
|
|
4
|
+
[findings]
|
|
5
|
+
"sec-html/angularjs-url-expression" = 0
|
|
6
|
+
"sec-html/csp-meta-unsafe" = 0
|
|
7
|
+
"sec-html/form-csrf-token" = 9
|
|
8
|
+
"sec-html/iframe-allow-powerful" = 0
|
|
9
|
+
"sec-html/iframe-sandbox" = 3
|
|
10
|
+
"sec-html/no-credentials-in-url" = 0
|
|
11
|
+
"sec-html/no-external-base" = 0
|
|
12
|
+
"sec-html/no-inline-event-handler" = 1
|
|
13
|
+
"sec-html/no-javascript-url" = 0
|
|
14
|
+
"sec-html/no-mixed-content" = 0
|
|
15
|
+
"sec-html/no-plugin-embed" = 0
|
|
16
|
+
"sec-html/no-secret-in-hidden-input" = 0
|
|
17
|
+
"sec-html/no-sensitive-comments" = 0
|
|
18
|
+
"sec-html/no-srcdoc-script" = 0
|
|
19
|
+
"sec-html/no-template-html-sink" = 0
|
|
20
|
+
"sec-html/no-unsafe-template-interpolation" = 27
|
|
21
|
+
"sec-html/password-form-post" = 0
|
|
22
|
+
"sec-html/referrer-policy" = 0
|
|
23
|
+
"sec-html/sri-crossorigin" = 0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
description = "html/security and the high-confidence HTML security rules, at error"
|
|
2
|
+
extends = ["codelaw:html/security"]
|
|
3
|
+
|
|
4
|
+
[rules]
|
|
5
|
+
"sec-html/angularjs-url-expression" = "error"
|
|
6
|
+
"sec-html/csp-meta-unsafe" = "error"
|
|
7
|
+
"sec-html/iframe-sandbox" = "error"
|
|
8
|
+
"sec-html/no-credentials-in-url" = "error"
|
|
9
|
+
"sec-html/no-inline-event-handler" = "error"
|
|
10
|
+
"sec-html/no-javascript-url" = "error"
|
|
11
|
+
"sec-html/no-mixed-content" = "error"
|
|
12
|
+
"sec-html/no-secret-in-hidden-input" = "error"
|
|
13
|
+
"sec-html/no-srcdoc-script" = "error"
|
|
14
|
+
"sec-html/no-template-html-sink" = "error"
|
|
15
|
+
"sec-html/no-unsafe-template-interpolation" = "error"
|
|
16
|
+
"sec-html/password-form-post" = "error"
|
|
17
|
+
"sec-html/referrer-policy" = "error"
|
|
18
|
+
"sec-html/sri-crossorigin" = "error"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
description = "sec-html/recommended and the heuristic HTML security rules, at warn"
|
|
2
|
+
extends = ["codelaw:sec-html/recommended"]
|
|
3
|
+
|
|
4
|
+
[rules]
|
|
5
|
+
"sec-html/form-csrf-token" = "warn"
|
|
6
|
+
"sec-html/iframe-allow-powerful" = "warn"
|
|
7
|
+
"sec-html/no-external-base" = "warn"
|
|
8
|
+
"sec-html/no-plugin-embed" = "warn"
|
|
9
|
+
"sec-html/no-sensitive-comments" = "warn"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# sec-html/angularjs-url-expression — cases
|
|
2
|
+
|
|
3
|
+
### case: href and src
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<div ng-app><a href="{{url}}">a</a><img src="{{ p }}" alt=""></div>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:16 sec-html/angularjs-url-expression
|
|
11
|
+
1:41 sec-html/angularjs-url-expression
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```fixed
|
|
15
|
+
<div ng-app><a ng-href="{{url}}">a</a><img ng-src="{{ p }}" alt=""></div>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### case: a Flask page is not AngularJS
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<a href="{{ url_for('x') }}">a</a>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```expect
|
|
25
|
+
(none)
|
|
26
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-html/angularjs-url-expression
|
|
3
|
+
title: "AngularJS URLs use ng-href and ng-src"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "the expression's value should be checked too; the rename is only the first step"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [79], owasp: ["A05:2025"], severity: medium }
|
|
9
|
+
since: 0.2.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
In a page that uses AngularJS, `href="{{…}}"` and `src="{{…}}"` are written `ng-href` and `ng-src`.
|
|
13
|
+
|
|
14
|
+
**Why:** The browser requests the literal `{{…}}` before AngularJS fills it in, and an unvetted expression in a URL is an injection point; the `ng-` forms are set only after interpolation.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: sec-html/angularjs-url-expression
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### HTML
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<div ng-app><a href="{{ profileUrl }}">Profile</a></div>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Do
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<div ng-app><a ng-href="{{ profileUrl }}">Profile</a></div>
|
|
34
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# sec-html/csp-meta-unsafe — cases
|
|
2
|
+
|
|
3
|
+
### case: unsafe-inline and unsafe-eval
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval'">
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:44 sec-html/csp-meta-unsafe
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: default-src applies when there is no script-src
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<meta http-equiv="content-security-policy" content="default-src *">
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
1:44 sec-html/csp-meta-unsafe
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### case: data: as a script source
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self' data:">
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```expect
|
|
30
|
+
1:44 sec-html/csp-meta-unsafe
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### case: unsafe-inline in style-src is another rule
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; style-src 'unsafe-inline'">
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```expect
|
|
40
|
+
(none)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### case: a nonce makes the browser ignore unsafe-inline
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-abc' 'unsafe-inline'">
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```expect
|
|
50
|
+
(none)
|
|
51
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-html/csp-meta-unsafe
|
|
3
|
+
title: "A meta Content Security Policy does not allow unsafe script"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the page's scripts have to be given nonces or hashes, or moved to files"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [693], owasp: ["A02:2025"], severity: medium }
|
|
9
|
+
since: 0.2.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A `<meta http-equiv="Content-Security-Policy">` does not allow `'unsafe-inline'`, `'unsafe-eval'`, `*` or `data:` as a script source.
|
|
13
|
+
|
|
14
|
+
**Why:** Each of them lets injected script run, which is what the policy exists to stop.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: sec-html/csp-meta-unsafe
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move inline script into files, or give it a nonce or a hash (`'nonce-…'`, `'sha256-…'`).
|
|
23
|
+
2. Remove `'unsafe-eval'` and replace `eval`, `new Function` and string timers.
|
|
24
|
+
3. List the origins scripts load from instead of `*` or `data:`.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
### HTML
|
|
29
|
+
|
|
30
|
+
#### Don't
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-r4nd0m'">
|
|
40
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# sec-html/form-csrf-token — cases
|
|
2
|
+
|
|
3
|
+
### case: no token
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<form method="post" action="/a"><input name="x"></form>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:1 sec-html/form-csrf-token
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: a hidden field
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<form method="post"><input type="hidden" name="csrfmiddlewaretoken" value="x"></form>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
(none)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### case: a template tag that writes the field
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<form method="post">{% csrf_token %}</form>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```expect
|
|
30
|
+
(none)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### case: a form that posts to another site
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<form method="post" action="https://pay.example/"><input name="x"></form>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```expect
|
|
40
|
+
(none)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### case: GET needs none
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<form method="get"><input name="q"></form>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```expect
|
|
50
|
+
(none)
|
|
51
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-html/form-csrf-token
|
|
3
|
+
title: "A POST form carries a CSRF token"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which token the server checks depends on its framework"
|
|
7
|
+
stability: preview
|
|
8
|
+
options:
|
|
9
|
+
names: { type: "string[]", default: [], description: "extra names (field names or template tags) that count as a CSRF token, besides csrf, xsrf, authenticity_token, antiforgery, csrfmiddlewaretoken, __requestverificationtoken and _token" }
|
|
10
|
+
security: { cwe: [352], owasp: ["A01:2025"], severity: medium }
|
|
11
|
+
since: 0.2.0
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
A `<form method="post">` has a hidden anti-forgery field, or a template tag that writes one.
|
|
15
|
+
|
|
16
|
+
**Why:** Without a token another site can submit the form with the signed-in user's cookies.
|
|
17
|
+
|
|
18
|
+
```codelaw
|
|
19
|
+
native: sec-html/form-csrf-token
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## How to fix
|
|
23
|
+
|
|
24
|
+
1. Add the framework's field: `{% csrf_token %}` (Django), `@csrf` (Laravel), `<%= csrf_meta_tags %>` / `authenticity_token` (Rails), `{{ form.hidden_tag() }}` (Flask-WTF), or a hidden `_csrf` input.
|
|
25
|
+
2. If the form posts to another site, ignore it with `exceptions`.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
### HTML
|
|
30
|
+
|
|
31
|
+
#### Don't
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<form method="post" action="/transfer"><input name="to"></form>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Do
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<form method="post" action="/transfer"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input name="to"></form>
|
|
41
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# sec-html/iframe-allow-powerful — cases
|
|
2
|
+
|
|
3
|
+
### case: camera and microphone
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<iframe src="https://a.example/e" allow="camera; microphone; fullscreen"></iframe>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:35 sec-html/iframe-allow-powerful
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: withheld with none
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<iframe src="https://a.example/e" allow="camera 'none'"></iframe>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
(none)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### case: same origin
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<iframe src="/e" allow="camera"></iframe>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```expect
|
|
30
|
+
(none)
|
|
31
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-html/iframe-allow-powerful
|
|
3
|
+
title: "A cross-origin iframe is not given a powerful feature"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which framed sites deserve the permission is the owner's decision"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [250], owasp: ["A02:2025"], severity: low }
|
|
9
|
+
since: 0.2.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An `<iframe>` whose `src` is on another origin does not list `camera`, `microphone`, `geolocation`, `payment` or another powerful feature in `allow`.
|
|
13
|
+
|
|
14
|
+
**Why:** The framed site gets the permission prompt, with the page's name on it, and whatever that site serves later inherits the grant.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: sec-html/iframe-allow-powerful
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Drop the feature from `allow`.
|
|
23
|
+
2. Or limit it to the origin that needs it: `allow="camera https://meet.example.com"`, and keep `'none'` for the rest.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<iframe src="https://meet.example.com/room" allow="camera; microphone"></iframe>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<iframe src="https://meet.example.com/room" allow="fullscreen"></iframe>
|
|
39
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# sec-html/iframe-sandbox — cases
|
|
2
|
+
|
|
3
|
+
### case: no sandbox
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<iframe src="/a"></iframe>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
1:1 sec-html/iframe-sandbox
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```fixed
|
|
14
|
+
<iframe sandbox src="/a"></iframe>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### case: a sandbox with other tokens is fine
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<iframe sandbox="allow-forms" src="/a"></iframe>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```expect
|
|
24
|
+
(none)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### case: scripts and the same origin together
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<iframe sandbox="allow-scripts allow-same-origin" src="/a"></iframe>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```expect
|
|
34
|
+
1:9 sec-html/iframe-sandbox
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```fixed
|
|
38
|
+
<iframe sandbox="allow-scripts" src="/a"></iframe>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### case: the same origin token first
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<iframe sandbox="allow-same-origin allow-scripts"></iframe>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```expect
|
|
48
|
+
1:9 sec-html/iframe-sandbox
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```fixed
|
|
52
|
+
<iframe sandbox="allow-scripts"></iframe>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### case: a templated sandbox is not judged
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<iframe sandbox="{{ tokens }}" src="/a"></iframe>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```expect
|
|
62
|
+
(none)
|
|
63
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-html/iframe-sandbox
|
|
3
|
+
title: "An iframe is sandboxed"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "the framed page may need `allow-*` tokens, which only its author knows"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [1021], owasp: ["A02:2025"], severity: medium }
|
|
9
|
+
since: 0.2.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An `<iframe>` has a `sandbox` attribute, and one that allows scripts does not also allow the same origin.
|
|
13
|
+
|
|
14
|
+
**Why:** A framed page without `sandbox` runs with every power a top-level page has; `allow-scripts` with `allow-same-origin` lets it remove its own sandbox.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: sec-html/iframe-sandbox
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### HTML
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<iframe src="/widget"></iframe>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Do
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<iframe sandbox src="/widget"></iframe>
|
|
34
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# sec-html/no-credentials-in-url — cases
|
|
2
|
+
|
|
3
|
+
### case: a username alone is not a secret
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<a href="https://user@host.example/">a</a>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
(none)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: a protocol-relative image URL
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<img src="//u:p@h.example/a.png" alt="">
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```expect
|
|
20
|
+
1:6 sec-html/no-credentials-in-url
|
|
21
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: sec-html/no-credentials-in-url
|
|
3
|
+
title: "No username and password in a URL"
|
|
4
|
+
languages: [html]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "where the credential should live is the author's decision"
|
|
7
|
+
stability: preview
|
|
8
|
+
security: { cwe: [522], owasp: ["A07:2025"], severity: high }
|
|
9
|
+
since: 0.2.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A URL attribute does not carry `user:password@` in front of its host.
|
|
13
|
+
|
|
14
|
+
**Why:** A URL is stored in history, logs, referrers and bookmarks, so a credential written into one is disclosed to all of them.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: sec-html/no-credentials-in-url
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Remove the credentials from the URL.
|
|
23
|
+
2. Send them in an `Authorization` header, or keep the request on the server.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<a href="https://admin:hunter2@intranet.example.com/">Admin</a>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<a href="https://intranet.example.com/">Admin</a>
|
|
39
|
+
```
|