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.
Files changed (49) hide show
  1. codelaw_rules_security_html/LICENSE +23 -0
  2. codelaw_rules_security_html/NOTICE +9 -0
  3. codelaw_rules_security_html/README.md +98 -0
  4. codelaw_rules_security_html/codelaw-pack.toml +6 -0
  5. codelaw_rules_security_html/corpus.toml +23 -0
  6. codelaw_rules_security_html/presets/sec-html/recommended.toml +18 -0
  7. codelaw_rules_security_html/presets/sec-html/strict.toml +9 -0
  8. codelaw_rules_security_html/rules/sec-html/angularjs-url-expression.cases.md +26 -0
  9. codelaw_rules_security_html/rules/sec-html/angularjs-url-expression.md +34 -0
  10. codelaw_rules_security_html/rules/sec-html/csp-meta-unsafe.cases.md +51 -0
  11. codelaw_rules_security_html/rules/sec-html/csp-meta-unsafe.md +40 -0
  12. codelaw_rules_security_html/rules/sec-html/form-csrf-token.cases.md +51 -0
  13. codelaw_rules_security_html/rules/sec-html/form-csrf-token.md +41 -0
  14. codelaw_rules_security_html/rules/sec-html/iframe-allow-powerful.cases.md +31 -0
  15. codelaw_rules_security_html/rules/sec-html/iframe-allow-powerful.md +39 -0
  16. codelaw_rules_security_html/rules/sec-html/iframe-sandbox.cases.md +63 -0
  17. codelaw_rules_security_html/rules/sec-html/iframe-sandbox.md +34 -0
  18. codelaw_rules_security_html/rules/sec-html/no-credentials-in-url.cases.md +21 -0
  19. codelaw_rules_security_html/rules/sec-html/no-credentials-in-url.md +39 -0
  20. codelaw_rules_security_html/rules/sec-html/no-external-base.cases.md +21 -0
  21. codelaw_rules_security_html/rules/sec-html/no-external-base.md +39 -0
  22. codelaw_rules_security_html/rules/sec-html/no-inline-event-handler.cases.md +32 -0
  23. codelaw_rules_security_html/rules/sec-html/no-inline-event-handler.md +40 -0
  24. codelaw_rules_security_html/rules/sec-html/no-javascript-url.cases.md +61 -0
  25. codelaw_rules_security_html/rules/sec-html/no-javascript-url.md +39 -0
  26. codelaw_rules_security_html/rules/sec-html/no-mixed-content.cases.md +79 -0
  27. codelaw_rules_security_html/rules/sec-html/no-mixed-content.md +34 -0
  28. codelaw_rules_security_html/rules/sec-html/no-plugin-embed.cases.md +25 -0
  29. codelaw_rules_security_html/rules/sec-html/no-plugin-embed.md +40 -0
  30. codelaw_rules_security_html/rules/sec-html/no-secret-in-hidden-input.cases.md +31 -0
  31. codelaw_rules_security_html/rules/sec-html/no-secret-in-hidden-input.md +39 -0
  32. codelaw_rules_security_html/rules/sec-html/no-sensitive-comments.cases.md +64 -0
  33. codelaw_rules_security_html/rules/sec-html/no-sensitive-comments.md +34 -0
  34. codelaw_rules_security_html/rules/sec-html/no-srcdoc-script.cases.md +21 -0
  35. codelaw_rules_security_html/rules/sec-html/no-srcdoc-script.md +39 -0
  36. codelaw_rules_security_html/rules/sec-html/no-template-html-sink.cases.md +23 -0
  37. codelaw_rules_security_html/rules/sec-html/no-template-html-sink.md +39 -0
  38. codelaw_rules_security_html/rules/sec-html/no-unsafe-template-interpolation.cases.md +45 -0
  39. codelaw_rules_security_html/rules/sec-html/no-unsafe-template-interpolation.md +39 -0
  40. codelaw_rules_security_html/rules/sec-html/password-form-post.cases.md +63 -0
  41. codelaw_rules_security_html/rules/sec-html/password-form-post.md +34 -0
  42. codelaw_rules_security_html/rules/sec-html/referrer-policy.cases.md +39 -0
  43. codelaw_rules_security_html/rules/sec-html/referrer-policy.md +34 -0
  44. codelaw_rules_security_html/rules/sec-html/sri-crossorigin.cases.md +35 -0
  45. codelaw_rules_security_html/rules/sec-html/sri-crossorigin.md +34 -0
  46. codelaw_rules_security_html-0.3.0.dist-info/METADATA +108 -0
  47. codelaw_rules_security_html-0.3.0.dist-info/RECORD +49 -0
  48. codelaw_rules_security_html-0.3.0.dist-info/WHEEL +4 -0
  49. 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
+ ```