@thymian/rules-rfc-6797 0.0.0-stage → 0.3.0
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/README.md +158 -2
- package/dist/coverage.d.ts +291 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +160 -0
- package/dist/coverage.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/effective-request-uri/hsts-host-must-not-treat-empty-path-as-slash-when-comparing-effective-request-uris.rule.d.ts +3 -0
- package/dist/rules/effective-request-uri/hsts-host-must-not-treat-empty-path-as-slash-when-comparing-effective-request-uris.rule.d.ts.map +1 -0
- package/dist/rules/effective-request-uri/hsts-host-must-not-treat-empty-path-as-slash-when-comparing-effective-request-uris.rule.js +12 -0
- package/dist/rules/effective-request-uri/hsts-host-must-not-treat-empty-path-as-slash-when-comparing-effective-request-uris.rule.js.map +1 -0
- package/dist/rules/server-processing-model/hsts-host-must-not-send-sts-header-over-insecure-transport.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/hsts-host-must-not-send-sts-header-over-insecure-transport.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/hsts-host-must-not-send-sts-header-over-insecure-transport.rule.js +39 -0
- package/dist/rules/server-processing-model/hsts-host-must-not-send-sts-header-over-insecure-transport.rule.js.map +1 -0
- package/dist/rules/server-processing-model/hsts-host-must-send-only-one-sts-header.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/hsts-host-must-send-only-one-sts-header.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/hsts-host-must-send-only-one-sts-header.rule.js +35 -0
- package/dist/rules/server-processing-model/hsts-host-must-send-only-one-sts-header.rule.js.map +1 -0
- package/dist/rules/server-processing-model/hsts-host-should-redirect-insecure-requests-to-https.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/hsts-host-should-redirect-insecure-requests-to-https.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/hsts-host-should-redirect-insecure-requests-to-https.rule.js +24 -0
- package/dist/rules/server-processing-model/hsts-host-should-redirect-insecure-requests-to-https.rule.js.map +1 -0
- package/dist/rules/server-processing-model/https-redirect-is-should-for-deployment-reasons.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/https-redirect-is-should-for-deployment-reasons.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/https-redirect-is-should-for-deployment-reasons.rule.js +14 -0
- package/dist/rules/server-processing-model/https-redirect-is-should-for-deployment-reasons.rule.js.map +1 -0
- package/dist/rules/server-processing-model/server-may-establish-hsts-over-secure-transport.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/server-may-establish-hsts-over-secure-transport.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/server-may-establish-hsts-over-secure-transport.rule.js +19 -0
- package/dist/rules/server-processing-model/server-may-establish-hsts-over-secure-transport.rule.js.map +1 -0
- package/dist/rules/server-processing-model/server-should-redirect-insecure-requests-to-https.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/server-should-redirect-insecure-requests-to-https.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/server-should-redirect-insecure-requests-to-https.rule.js +25 -0
- package/dist/rules/server-processing-model/server-should-redirect-insecure-requests-to-https.rule.js.map +1 -0
- package/dist/rules/server-processing-model/server-should-send-sts-header-over-secure-transport.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/server-should-send-sts-header-over-secure-transport.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/server-should-send-sts-header-over-secure-transport.rule.js +22 -0
- package/dist/rules/server-processing-model/server-should-send-sts-header-over-secure-transport.rule.js.map +1 -0
- package/dist/rules/server-processing-model/sts-header-inclusion-is-should-to-accommodate-caches.rule.d.ts +3 -0
- package/dist/rules/server-processing-model/sts-header-inclusion-is-should-to-accommodate-caches.rule.d.ts.map +1 -0
- package/dist/rules/server-processing-model/sts-header-inclusion-is-should-to-accommodate-caches.rule.js +14 -0
- package/dist/rules/server-processing-model/sts-header-inclusion-is-should-to-accommodate-caches.rule.js.map +1 -0
- package/dist/rules/syntax/hsts-host-may-assert-include-subdomains.rule.d.ts +3 -0
- package/dist/rules/syntax/hsts-host-may-assert-include-subdomains.rule.d.ts.map +1 -0
- package/dist/rules/syntax/hsts-host-may-assert-include-subdomains.rule.js +35 -0
- package/dist/rules/syntax/hsts-host-may-assert-include-subdomains.rule.js.map +1 -0
- package/dist/rules/syntax/hsts-host-must-not-repeat-sts-directives.rule.d.ts +3 -0
- package/dist/rules/syntax/hsts-host-must-not-repeat-sts-directives.rule.d.ts.map +1 -0
- package/dist/rules/syntax/hsts-host-must-not-repeat-sts-directives.rule.js +33 -0
- package/dist/rules/syntax/hsts-host-must-not-repeat-sts-directives.rule.js.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-include-subdomains-without-value.rule.d.ts +3 -0
- package/dist/rules/syntax/hsts-host-must-send-include-subdomains-without-value.rule.d.ts.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-include-subdomains-without-value.rule.js +24 -0
- package/dist/rules/syntax/hsts-host-must-send-include-subdomains-without-value.rule.js.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-as-delta-seconds.rule.d.ts +3 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-as-delta-seconds.rule.d.ts.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-as-delta-seconds.rule.js +33 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-as-delta-seconds.rule.js.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-directive.rule.d.ts +3 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-directive.rule.d.ts.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-directive.rule.js +24 -0
- package/dist/rules/syntax/hsts-host-must-send-max-age-directive.rule.js.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-sts-header-conforming-to-grammar.rule.d.ts +3 -0
- package/dist/rules/syntax/hsts-host-must-send-sts-header-conforming-to-grammar.rule.d.ts.map +1 -0
- package/dist/rules/syntax/hsts-host-must-send-sts-header-conforming-to-grammar.rule.js +29 -0
- package/dist/rules/syntax/hsts-host-must-send-sts-header-conforming-to-grammar.rule.js.map +1 -0
- package/dist/rules/syntax/user-agent-must-enforce-hsts-policy.rule.d.ts +3 -0
- package/dist/rules/syntax/user-agent-must-enforce-hsts-policy.rule.d.ts.map +1 -0
- package/dist/rules/syntax/user-agent-must-enforce-hsts-policy.rule.js +15 -0
- package/dist/rules/syntax/user-agent-must-enforce-hsts-policy.rule.js.map +1 -0
- package/dist/rules/syntax/user-agent-must-ignore-unrecognized-sts-directives.rule.d.ts +3 -0
- package/dist/rules/syntax/user-agent-must-ignore-unrecognized-sts-directives.rule.d.ts.map +1 -0
- package/dist/rules/syntax/user-agent-must-ignore-unrecognized-sts-directives.rule.js +37 -0
- package/dist/rules/syntax/user-agent-must-ignore-unrecognized-sts-directives.rule.js.map +1 -0
- package/dist/rules/utils/headers.d.ts +7 -0
- package/dist/rules/utils/headers.d.ts.map +1 -0
- package/dist/rules/utils/headers.js +54 -0
- package/dist/rules/utils/headers.js.map +1 -0
- package/dist/rules/utils/redirect.d.ts +8 -0
- package/dist/rules/utils/redirect.d.ts.map +1 -0
- package/dist/rules/utils/redirect.js +150 -0
- package/dist/rules/utils/redirect.js.map +1 -0
- package/dist/rules/utils/results.d.ts +5 -0
- package/dist/rules/utils/results.d.ts.map +1 -0
- package/dist/rules/utils/results.js +14 -0
- package/dist/rules/utils/results.js.map +1 -0
- package/dist/rules/utils/sts-contexts.d.ts +12 -0
- package/dist/rules/utils/sts-contexts.d.ts.map +1 -0
- package/dist/rules/utils/sts-contexts.js +69 -0
- package/dist/rules/utils/sts-contexts.js.map +1 -0
- package/dist/rules/utils/sts-field-value.d.ts +17 -0
- package/dist/rules/utils/sts-field-value.d.ts.map +1 -0
- package/dist/rules/utils/sts-field-value.js +133 -0
- package/dist/rules/utils/sts-field-value.js.map +1 -0
- package/dist/rules/utils/transport.d.ts +4 -0
- package/dist/rules/utils/transport.d.ts.map +1 -0
- package/dist/rules/utils/transport.js +24 -0
- package/dist/rules/utils/transport.js.map +1 -0
- package/package.json +55 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
import { onConformingValue, stsValueRuleFns } from '../utils/sts-contexts.js';
|
|
3
|
+
import { findDirectives, maxAgeSeconds } from '../utils/sts-field-value.js';
|
|
4
|
+
const NAME = 'rfc6797/hsts-host-may-assert-include-subdomains';
|
|
5
|
+
// An OPTIONAL directive is checkable at `hint`: the finding is that the
|
|
6
|
+
// protocol offers the mechanism and the host does not use it, not that the
|
|
7
|
+
// host is non-conformant.
|
|
8
|
+
const { lint, live } = stsValueRuleFns(NAME, onConformingValue((directives, fieldValue) => {
|
|
9
|
+
// No policy to extend: without a max-age there is none, and max-age=0
|
|
10
|
+
// deletes it, subdomains included (§6.1.1).
|
|
11
|
+
const maxAge = maxAgeSeconds(directives);
|
|
12
|
+
if (maxAge === undefined || maxAge === 0) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
return findDirectives(directives, 'includesubdomains').length > 0
|
|
16
|
+
? undefined
|
|
17
|
+
: `The Strict-Transport-Security value "${fieldValue}" does not assert includeSubDomains, so the HSTS Policy does not extend to subdomains of this host.`;
|
|
18
|
+
}));
|
|
19
|
+
export default httpRule(NAME)
|
|
20
|
+
.severity('hint')
|
|
21
|
+
.type('static', 'test', 'analytics')
|
|
22
|
+
.tags('security:transport')
|
|
23
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1.2')
|
|
24
|
+
.description('The OPTIONAL "includeSubDomains" directive is a valueless directive which, if present (i.e., it is "asserted"), signals the UA that the HSTS Policy applies to this HSTS Host as well as any subdomains of the host\'s domain name.')
|
|
25
|
+
.summary('HSTS Host may assert includeSubDomains to extend its policy to subdomains.')
|
|
26
|
+
.explanation('Without includeSubDomains, a browser still talks plain HTTP to every subdomain it has not received a policy from, and an attacker who can answer for such a subdomain can set cookies for the parent domain. Asserting it closes that gap, but only once every subdomain is actually served over https — whether that holds is something only the operator knows, which is why RFC 6797 makes the directive OPTIONAL and this rule only hints at it.')
|
|
27
|
+
.appliesTo('server')
|
|
28
|
+
// Value-blind common interface, so all three contexts are overridden: the
|
|
29
|
+
// pinned value in `static` (an unpinned one is a `rule-skip`), the value
|
|
30
|
+
// sent in `test` and `analytics`.
|
|
31
|
+
.overrideStaticRule(lint)
|
|
32
|
+
.overrideTest(live)
|
|
33
|
+
.overrideAnalyticsRule(live)
|
|
34
|
+
.done();
|
|
35
|
+
//# sourceMappingURL=hsts-host-may-assert-include-subdomains.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-may-assert-include-subdomains.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-may-assert-include-subdomains.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5E,MAAM,IAAI,GAAG,iDAAiD,CAAC;AAE/D,wEAAwE;AACxE,2EAA2E;AAC3E,0BAA0B;AAC1B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,eAAe,CACpC,IAAI,EACJ,iBAAiB,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE;IAC3C,sEAAsE;IACtE,4CAA4C;IAC5C,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,cAAc,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC,MAAM,GAAG,CAAC;QAC/D,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,wCAAwC,UAAU,qGAAqG,CAAC;AAC9J,CAAC,CAAC,CACH,CAAC;AAEF,eAAe,QAAQ,CAAC,IAAI,CAAC;KAC1B,QAAQ,CAAC,MAAM,CAAC;KAChB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC;KACnC,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,2DAA2D,CAAC;KAChE,WAAW,CACV,qOAAqO,CACtO;KACA,OAAO,CACN,4EAA4E,CAC7E;KACA,WAAW,CACV,sbAAsb,CACvb;KACA,SAAS,CAAC,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,yEAAyE;IACzE,kCAAkC;KACjC,kBAAkB,CAAC,IAAI,CAAC;KACxB,YAAY,CAAC,IAAI,CAAC;KAClB,qBAAqB,CAAC,IAAI,CAAC;KAC3B,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-not-repeat-sts-directives.rule.d.ts","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-not-repeat-sts-directives.rule.ts"],"names":[],"mappings":";AAwBA,wBAqBU"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
import { onConformingValue, stsValueRuleFns } from '../utils/sts-contexts.js';
|
|
3
|
+
const NAME = 'rfc6797/hsts-host-must-not-repeat-sts-directives';
|
|
4
|
+
const { lint, live } = stsValueRuleFns(NAME, onConformingValue((directives, fieldValue) => {
|
|
5
|
+
const seen = new Set();
|
|
6
|
+
const repeated = new Set();
|
|
7
|
+
for (const { name } of directives) {
|
|
8
|
+
if (seen.has(name)) {
|
|
9
|
+
repeated.add(name);
|
|
10
|
+
}
|
|
11
|
+
seen.add(name);
|
|
12
|
+
}
|
|
13
|
+
return repeated.size === 0
|
|
14
|
+
? undefined
|
|
15
|
+
: `The Strict-Transport-Security value "${fieldValue}" repeats the directive(s) ${[...repeated].map((name) => `"${name}"`).join(', ')}. All directives MUST appear only once, and a UA ignores an STS header field that repeats one.`;
|
|
16
|
+
}));
|
|
17
|
+
export default httpRule(NAME)
|
|
18
|
+
.severity('error')
|
|
19
|
+
.type('static', 'test', 'analytics')
|
|
20
|
+
.tags('security:transport')
|
|
21
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1')
|
|
22
|
+
.description('All directives MUST appear only once in an STS header field. Directives are either optional or required, as stipulated in their definitions.')
|
|
23
|
+
.summary('HSTS Host must not repeat a directive in a Strict-Transport-Security value.')
|
|
24
|
+
.explanation('A repeated directive — two max-age values, say, often the result of two layers of configuration each adding their own — makes the header non-conformant, and a browser ignores a non-conformant Strict-Transport-Security header entirely. Directive names compare case-insensitively, so "max-age" and "Max-Age" count as the same directive.')
|
|
25
|
+
.appliesTo('server')
|
|
26
|
+
// Value-blind common interface, so all three contexts are overridden: the
|
|
27
|
+
// pinned value in `static` (an unpinned one is a `rule-skip`), the value
|
|
28
|
+
// sent in `test` and `analytics`.
|
|
29
|
+
.overrideStaticRule(lint)
|
|
30
|
+
.overrideTest(live)
|
|
31
|
+
.overrideAnalyticsRule(live)
|
|
32
|
+
.done();
|
|
33
|
+
//# sourceMappingURL=hsts-host-must-not-repeat-sts-directives.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-not-repeat-sts-directives.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-not-repeat-sts-directives.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE9E,MAAM,IAAI,GAAG,kDAAkD,CAAC;AAEhE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,eAAe,CACpC,IAAI,EACJ,iBAAiB,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE;IAC3C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACnB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,KAAK,CAAC;QACxB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,wCAAwC,UAAU,8BAA8B,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,gGAAgG,CAAC;AAC1O,CAAC,CAAC,CACH,CAAC;AAEF,eAAe,QAAQ,CAAC,IAAI,CAAC;KAC1B,QAAQ,CAAC,OAAO,CAAC;KACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC;KACnC,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,yDAAyD,CAAC;KAC9D,WAAW,CACV,8IAA8I,CAC/I;KACA,OAAO,CACN,6EAA6E,CAC9E;KACA,WAAW,CACV,gVAAgV,CACjV;KACA,SAAS,CAAC,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,yEAAyE;IACzE,kCAAkC;KACjC,kBAAkB,CAAC,IAAI,CAAC;KACxB,YAAY,CAAC,IAAI,CAAC;KAClB,qBAAqB,CAAC,IAAI,CAAC;KAC3B,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-include-subdomains-without-value.rule.d.ts","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-include-subdomains-without-value.rule.ts"],"names":[],"mappings":";AAkBA,wBAmBU"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
import { onConformingValue, stsValueRuleFns } from '../utils/sts-contexts.js';
|
|
3
|
+
import { findDirectives } from '../utils/sts-field-value.js';
|
|
4
|
+
const NAME = 'rfc6797/hsts-host-must-send-include-subdomains-without-value';
|
|
5
|
+
const { lint, live } = stsValueRuleFns(NAME, onConformingValue((directives, fieldValue) => findDirectives(directives, 'includesubdomains').some(({ value }) => value !== undefined)
|
|
6
|
+
? `The Strict-Transport-Security value "${fieldValue}" gives includeSubDomains a value, but it is a valueless directive; a UA ignores an STS header field that does not conform to the syntax.`
|
|
7
|
+
: undefined));
|
|
8
|
+
export default httpRule(NAME)
|
|
9
|
+
.severity('error')
|
|
10
|
+
.type('static', 'test', 'analytics')
|
|
11
|
+
.tags('security:transport')
|
|
12
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1.2')
|
|
13
|
+
.description('The OPTIONAL "includeSubDomains" directive is a valueless directive which, if present (i.e., it is "asserted"), signals the UA that the HSTS Policy applies to this HSTS Host as well as any subdomains of the host\'s domain name.')
|
|
14
|
+
.summary('HSTS Host must send includeSubDomains without a value.')
|
|
15
|
+
.explanation('includeSubDomains is asserted by its presence alone. Writing "includeSubDomains=true" looks harmless but makes the header non-conformant, so a browser ignores it entirely — the opposite of the intended stronger policy: not only are subdomains left out, the host itself is no longer protected.')
|
|
16
|
+
.appliesTo('server')
|
|
17
|
+
// Value-blind common interface, so all three contexts are overridden: the
|
|
18
|
+
// pinned value in `static` (an unpinned one is a `rule-skip`), the value
|
|
19
|
+
// sent in `test` and `analytics`.
|
|
20
|
+
.overrideStaticRule(lint)
|
|
21
|
+
.overrideTest(live)
|
|
22
|
+
.overrideAnalyticsRule(live)
|
|
23
|
+
.done();
|
|
24
|
+
//# sourceMappingURL=hsts-host-must-send-include-subdomains-without-value.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-include-subdomains-without-value.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-include-subdomains-without-value.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,IAAI,GAAG,8DAA8D,CAAC;AAE5E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,eAAe,CACpC,IAAI,EACJ,iBAAiB,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,CAC3C,cAAc,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC,IAAI,CAClD,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CACnC;IACC,CAAC,CAAC,wCAAwC,UAAU,2IAA2I;IAC/L,CAAC,CAAC,SAAS,CACd,CACF,CAAC;AAEF,eAAe,QAAQ,CAAC,IAAI,CAAC;KAC1B,QAAQ,CAAC,OAAO,CAAC;KACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC;KACnC,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,2DAA2D,CAAC;KAChE,WAAW,CACV,qOAAqO,CACtO;KACA,OAAO,CAAC,wDAAwD,CAAC;KACjE,WAAW,CACV,sSAAsS,CACvS;KACA,SAAS,CAAC,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,yEAAyE;IACzE,kCAAkC;KACjC,kBAAkB,CAAC,IAAI,CAAC;KACxB,YAAY,CAAC,IAAI,CAAC;KAClB,qBAAqB,CAAC,IAAI,CAAC;KAC3B,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-max-age-as-delta-seconds.rule.d.ts","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-max-age-as-delta-seconds.rule.ts"],"names":[],"mappings":";AA0BA,wBAmBU"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
import { onConformingValue, stsValueRuleFns } from '../utils/sts-contexts.js';
|
|
3
|
+
import { findDirectives, isDeltaSeconds } from '../utils/sts-field-value.js';
|
|
4
|
+
const NAME = 'rfc6797/hsts-host-must-send-max-age-as-delta-seconds';
|
|
5
|
+
// A missing max-age is the max-age directive rule's to report; this one judges
|
|
6
|
+
// the value of every max-age that is there.
|
|
7
|
+
const { lint, live } = stsValueRuleFns(NAME, onConformingValue((directives, fieldValue) => {
|
|
8
|
+
const problems = findDirectives(directives, 'max-age')
|
|
9
|
+
.filter(({ value }) => !isDeltaSeconds(value))
|
|
10
|
+
.map(({ value }) => value === undefined
|
|
11
|
+
? 'max-age carries no value'
|
|
12
|
+
: `max-age="${value}" is not a whole number of seconds`);
|
|
13
|
+
return problems.length === 0
|
|
14
|
+
? undefined
|
|
15
|
+
: `In the Strict-Transport-Security value "${fieldValue}", ${problems.join(' and ')}. max-age's REQUIRED value is delta-seconds (1*DIGIT), and a UA ignores an STS header field whose value is not.`;
|
|
16
|
+
}));
|
|
17
|
+
export default httpRule(NAME)
|
|
18
|
+
.severity('error')
|
|
19
|
+
.type('static', 'test', 'analytics')
|
|
20
|
+
.tags('security:transport')
|
|
21
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1.1')
|
|
22
|
+
.description("The syntax of the max-age directive's REQUIRED value (after quoted-string unescaping, if necessary) is defined as: max-age-value = delta-seconds, where delta-seconds = 1*DIGIT.")
|
|
23
|
+
.summary('HSTS Host must send max-age as a whole number of seconds.')
|
|
24
|
+
.explanation('max-age counts seconds, written as digits only: "31536000" or, quoted, "\\"31536000\\"". A sign, a decimal point, a unit such as "1y", or a missing value makes the whole header non-conformant, and a browser ignores it — so the policy the operator meant to set never takes effect.')
|
|
25
|
+
.appliesTo('server')
|
|
26
|
+
// Value-blind common interface, so all three contexts are overridden: the
|
|
27
|
+
// pinned value in `static` (an unpinned one is a `rule-skip`), the value
|
|
28
|
+
// sent in `test` and `analytics`.
|
|
29
|
+
.overrideStaticRule(lint)
|
|
30
|
+
.overrideTest(live)
|
|
31
|
+
.overrideAnalyticsRule(live)
|
|
32
|
+
.done();
|
|
33
|
+
//# sourceMappingURL=hsts-host-must-send-max-age-as-delta-seconds.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-max-age-as-delta-seconds.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-max-age-as-delta-seconds.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7E,MAAM,IAAI,GAAG,sDAAsD,CAAC;AAEpE,+EAA+E;AAC/E,4CAA4C;AAC5C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,eAAe,CACpC,IAAI,EACJ,iBAAiB,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE;IAC3C,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,EAAE,SAAS,CAAC;SACnD,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;SAC7C,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CACjB,KAAK,KAAK,SAAS;QACjB,CAAC,CAAC,0BAA0B;QAC5B,CAAC,CAAC,YAAY,KAAK,oCAAoC,CAC1D,CAAC;IAEJ,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC1B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,2CAA2C,UAAU,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,iHAAiH,CAAC;AACzM,CAAC,CAAC,CACH,CAAC;AAEF,eAAe,QAAQ,CAAC,IAAI,CAAC;KAC1B,QAAQ,CAAC,OAAO,CAAC;KACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC;KACnC,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,2DAA2D,CAAC;KAChE,WAAW,CACV,kLAAkL,CACnL;KACA,OAAO,CAAC,2DAA2D,CAAC;KACpE,WAAW,CACV,yRAAyR,CAC1R;KACA,SAAS,CAAC,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,yEAAyE;IACzE,kCAAkC;KACjC,kBAAkB,CAAC,IAAI,CAAC;KACxB,YAAY,CAAC,IAAI,CAAC;KAClB,qBAAqB,CAAC,IAAI,CAAC;KAC3B,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-max-age-directive.rule.d.ts","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-max-age-directive.rule.ts"],"names":[],"mappings":";AAgBA,wBAqBU"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
import { onConformingValue, stsValueRuleFns } from '../utils/sts-contexts.js';
|
|
3
|
+
import { findDirectives } from '../utils/sts-field-value.js';
|
|
4
|
+
const NAME = 'rfc6797/hsts-host-must-send-max-age-directive';
|
|
5
|
+
const { lint, live } = stsValueRuleFns(NAME, onConformingValue((directives, fieldValue) => findDirectives(directives, 'max-age').length > 0
|
|
6
|
+
? undefined
|
|
7
|
+
: `The Strict-Transport-Security value "${fieldValue}" has no max-age directive, which is REQUIRED; a UA ignores an STS header field without one.`));
|
|
8
|
+
export default httpRule(NAME)
|
|
9
|
+
.severity('error')
|
|
10
|
+
.type('static', 'test', 'analytics')
|
|
11
|
+
.tags('security:transport')
|
|
12
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1.1')
|
|
13
|
+
.description('The REQUIRED "max-age" directive specifies the number of seconds, after the reception of the STS header field, during which the UA regards the host (from whom the message was received) as a Known HSTS Host.')
|
|
14
|
+
.summary('HSTS Host must send a max-age directive in every Strict-Transport-Security value.')
|
|
15
|
+
.explanation('max-age is what tells the browser how long to remember the policy; without it there is no policy to remember. A header such as "Strict-Transport-Security: includeSubDomains" is not a shorter policy but no policy at all, because a browser ignores a header that lacks a required directive.')
|
|
16
|
+
.appliesTo('server')
|
|
17
|
+
// Value-blind common interface, so all three contexts are overridden: the
|
|
18
|
+
// pinned value in `static` (an unpinned one is a `rule-skip`), the value
|
|
19
|
+
// sent in `test` and `analytics`.
|
|
20
|
+
.overrideStaticRule(lint)
|
|
21
|
+
.overrideTest(live)
|
|
22
|
+
.overrideAnalyticsRule(live)
|
|
23
|
+
.done();
|
|
24
|
+
//# sourceMappingURL=hsts-host-must-send-max-age-directive.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-max-age-directive.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-max-age-directive.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,IAAI,GAAG,+CAA+C,CAAC;AAE7D,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,eAAe,CACpC,IAAI,EACJ,iBAAiB,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,CAC3C,cAAc,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC;IAC9C,CAAC,CAAC,SAAS;IACX,CAAC,CAAC,wCAAwC,UAAU,8FAA8F,CACrJ,CACF,CAAC;AAEF,eAAe,QAAQ,CAAC,IAAI,CAAC;KAC1B,QAAQ,CAAC,OAAO,CAAC;KACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC;KACnC,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,2DAA2D,CAAC;KAChE,WAAW,CACV,gNAAgN,CACjN;KACA,OAAO,CACN,mFAAmF,CACpF;KACA,WAAW,CACV,iSAAiS,CAClS;KACA,SAAS,CAAC,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,yEAAyE;IACzE,kCAAkC;KACjC,kBAAkB,CAAC,IAAI,CAAC;KACxB,YAAY,CAAC,IAAI,CAAC;KAClB,qBAAqB,CAAC,IAAI,CAAC;KAC3B,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-sts-header-conforming-to-grammar.rule.d.ts","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-sts-header-conforming-to-grammar.rule.ts"],"names":[],"mappings":";AAgBA,wBAqBU"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
import { stsValueRuleFns } from '../utils/sts-contexts.js';
|
|
3
|
+
import { parseStsFieldValue } from '../utils/sts-field-value.js';
|
|
4
|
+
const NAME = 'rfc6797/hsts-host-must-send-sts-header-conforming-to-grammar';
|
|
5
|
+
// The one rule that owns a parse failure: every directive-level rule runs
|
|
6
|
+
// only on a value that parses, so a malformed value is reported once, here.
|
|
7
|
+
const { lint, live } = stsValueRuleFns(NAME, (fieldValue) => {
|
|
8
|
+
const parsed = parseStsFieldValue(fieldValue);
|
|
9
|
+
return parsed.conforms
|
|
10
|
+
? undefined
|
|
11
|
+
: `The Strict-Transport-Security value "${fieldValue}" does not conform to the RFC 6797 §6.1 grammar: ${parsed.problem}. A UA MUST ignore a non-conforming STS header field, so no HSTS Policy is in effect.`;
|
|
12
|
+
});
|
|
13
|
+
export default httpRule(NAME)
|
|
14
|
+
.severity('error')
|
|
15
|
+
.type('static', 'test', 'analytics')
|
|
16
|
+
.tags('security:transport')
|
|
17
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-7.1')
|
|
18
|
+
.description('An STS header field an HSTS Host includes in its response message MUST satisfy the grammar specified in Section 6.1, and UAs MUST ignore any STS header field containing directives, or other header field value data, that does not conform to the syntax defined in this specification.')
|
|
19
|
+
.summary('HSTS Host must send a Strict-Transport-Security value that conforms to the RFC 6797 grammar.')
|
|
20
|
+
.explanation('Browsers do not repair a malformed Strict-Transport-Security header: RFC 6797 requires them to ignore it entirely. A comma where a semicolon belongs, or a stray character in a directive, therefore switches HSTS off without any visible error, and the site stays open to protocol downgrade and cookie hijacking while its operators believe it is protected. Directives are separated by ";", and each is a token optionally followed by "=" and a token or a quoted string.')
|
|
21
|
+
.appliesTo('server')
|
|
22
|
+
// Value-blind common interface, so all three contexts are overridden: the
|
|
23
|
+
// pinned value in `static` (an unpinned one is a `rule-skip`), the value
|
|
24
|
+
// sent in `test` and `analytics`.
|
|
25
|
+
.overrideStaticRule(lint)
|
|
26
|
+
.overrideTest(live)
|
|
27
|
+
.overrideAnalyticsRule(live)
|
|
28
|
+
.done();
|
|
29
|
+
//# sourceMappingURL=hsts-host-must-send-sts-header-conforming-to-grammar.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hsts-host-must-send-sts-header-conforming-to-grammar.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/hsts-host-must-send-sts-header-conforming-to-grammar.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEjE,MAAM,IAAI,GAAG,8DAA8D,CAAC;AAE5E,0EAA0E;AAC1E,4EAA4E;AAC5E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC,UAAU,EAAE,EAAE;IAC1D,MAAM,MAAM,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,QAAQ;QACpB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,wCAAwC,UAAU,oDAAoD,MAAM,CAAC,OAAO,uFAAuF,CAAC;AAClN,CAAC,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC,IAAI,CAAC;KAC1B,QAAQ,CAAC,OAAO,CAAC;KACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC;KACnC,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,yDAAyD,CAAC;KAC9D,WAAW,CACV,2RAA2R,CAC5R;KACA,OAAO,CACN,8FAA8F,CAC/F;KACA,WAAW,CACV,mdAAmd,CACpd;KACA,SAAS,CAAC,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,yEAAyE;IACzE,kCAAkC;KACjC,kBAAkB,CAAC,IAAI,CAAC;KACxB,YAAY,CAAC,IAAI,CAAC;KAClB,qBAAqB,CAAC,IAAI,CAAC;KAC3B,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-agent-must-enforce-hsts-policy.rule.d.ts","sourceRoot":"","sources":["../../../src/rules/syntax/user-agent-must-enforce-hsts-policy.rule.ts"],"names":[],"mappings":";AAKA,wBAmBU"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
// The one rule in the package addressed to the user agent: 6.1/1 is the
|
|
3
|
+
// obligation the mechanism rests on, and it binds a peer whose state Thymian
|
|
4
|
+
// cannot see.
|
|
5
|
+
export default httpRule('rfc6797/user-agent-must-enforce-hsts-policy')
|
|
6
|
+
.severity('error')
|
|
7
|
+
.type('informational', 'peer-not-observable', 'Enforcing the policy means the user agent rewrites http URIs for a Known HSTS Host to https before sending anything, and refuses to continue past a secure-transport error. Whether a host is a Known HSTS Host is state the user agent stores itself — noted from an earlier response, possibly expired or deleted since — so no message Thymian sees, sends or records says whether a user agent should have enforced the policy. Not even as a heuristic: recorded traffic does not say which user agent sent a request, so a plain-http request that follows a policy cannot be tied to one that received it.')
|
|
8
|
+
.tags('security:transport')
|
|
9
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1')
|
|
10
|
+
.description('The Strict-Transport-Security HTTP response header field (STS header field) indicates to a UA that it MUST enforce the HSTS Policy in regards to the host emitting the response message containing this header field.')
|
|
11
|
+
.summary('User agent must enforce the HSTS Policy of a host that sends the STS header field.')
|
|
12
|
+
.explanation('This is the obligation the whole mechanism rests on: once a browser has seen a valid Strict-Transport-Security header, it must never again talk to that host over plain HTTP until the policy expires. The host cannot enforce this itself; everything this rule set checks on the host side exists to make sure the header the browser receives is one it will act on.')
|
|
13
|
+
.appliesTo('user-agent')
|
|
14
|
+
.done();
|
|
15
|
+
//# sourceMappingURL=user-agent-must-enforce-hsts-policy.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-agent-must-enforce-hsts-policy.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/user-agent-must-enforce-hsts-policy.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,wEAAwE;AACxE,6EAA6E;AAC7E,cAAc;AACd,eAAe,QAAQ,CAAC,6CAA6C,CAAC;KACnE,QAAQ,CAAC,OAAO,CAAC;KACjB,IAAI,CACH,eAAe,EACf,qBAAqB,EACrB,mlBAAmlB,CACplB;KACA,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,yDAAyD,CAAC;KAC9D,WAAW,CACV,uNAAuN,CACxN;KACA,OAAO,CACN,oFAAoF,CACrF;KACA,WAAW,CACV,yWAAyW,CAC1W;KACA,SAAS,CAAC,YAAY,CAAC;KACvB,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-agent-must-ignore-unrecognized-sts-directives.rule.d.ts","sourceRoot":"","sources":["../../../src/rules/syntax/user-agent-must-ignore-unrecognized-sts-directives.rule.ts"],"names":[],"mappings":";AAmCA,wBAqBU"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { httpRule } from '@thymian/core';
|
|
2
|
+
import { onConformingValue, stsValueRuleFns } from '../utils/sts-contexts.js';
|
|
3
|
+
const NAME = 'rfc6797/user-agent-must-ignore-unrecognized-sts-directives';
|
|
4
|
+
const DEFINED_DIRECTIVES = new Set(['max-age', 'includesubdomains']);
|
|
5
|
+
// A UA implementing only RFC 6797 ignores `preload` too, but it is sent on
|
|
6
|
+
// purpose — for the browser pre-load lists — so it is no mistake to point out.
|
|
7
|
+
const DELIBERATE_DIRECTIVES = new Set(['preload']);
|
|
8
|
+
// The host-side contrapositive of a UA requirement: a directive RFC 6797 does
|
|
9
|
+
// not define is one a UA will skip. Heuristic in every context, because a
|
|
10
|
+
// directive defined after RFC 6797, or one some UAs recognise, is no defect.
|
|
11
|
+
const { lint, live } = stsValueRuleFns(NAME, onConformingValue((directives, fieldValue) => {
|
|
12
|
+
const unrecognized = [
|
|
13
|
+
...new Set(directives
|
|
14
|
+
.map(({ name }) => name)
|
|
15
|
+
.filter((name) => !DEFINED_DIRECTIVES.has(name) && !DELIBERATE_DIRECTIVES.has(name))),
|
|
16
|
+
];
|
|
17
|
+
return unrecognized.length === 0
|
|
18
|
+
? undefined
|
|
19
|
+
: `The Strict-Transport-Security value "${fieldValue}" carries ${unrecognized.map((name) => `"${name}"`).join(', ')}, which RFC 6797 does not define; a UA implementing RFC 6797 ignores it.`;
|
|
20
|
+
}));
|
|
21
|
+
export default httpRule(NAME)
|
|
22
|
+
.severity('hint')
|
|
23
|
+
.type('static', 'test', 'analytics')
|
|
24
|
+
.tags('security:transport')
|
|
25
|
+
.url('https://www.rfc-editor.org/rfc/rfc6797.html#section-6.1')
|
|
26
|
+
.description('If an STS header field contains directive(s) not recognized by the UA, the UA MUST ignore the unrecognized directives, and if the STS header field otherwise satisfies the above requirements (1 through 4), the UA MUST process the recognized directives.')
|
|
27
|
+
.summary('A Strict-Transport-Security directive RFC 6797 does not define is ignored by user agents.')
|
|
28
|
+
.explanation('RFC 6797 defines two directives, max-age and includeSubDomains, and requires user agents to skip any other — so a misspelled "includeSubDomain" silently leaves every subdomain unprotected. This rule points out a directive a user agent implementing only RFC 6797 will skip. It is a hint and a heuristic: a directive defined after RFC 6797, or one some user agents recognise, is not a mistake. "preload", which browsers read to admit a host to their pre-load lists, is sent on purpose and never pointed out.')
|
|
29
|
+
.appliesTo('server')
|
|
30
|
+
// Value-blind common interface, so all three contexts are overridden: the
|
|
31
|
+
// pinned value in `static` (an unpinned one is a `rule-skip`), the value
|
|
32
|
+
// sent in `test` and `analytics`.
|
|
33
|
+
.overrideStaticRule(lint)
|
|
34
|
+
.overrideTest(live)
|
|
35
|
+
.overrideAnalyticsRule(live)
|
|
36
|
+
.done();
|
|
37
|
+
//# sourceMappingURL=user-agent-must-ignore-unrecognized-sts-directives.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-agent-must-ignore-unrecognized-sts-directives.rule.js","sourceRoot":"","sources":["../../../src/rules/syntax/user-agent-must-ignore-unrecognized-sts-directives.rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE9E,MAAM,IAAI,GAAG,4DAA4D,CAAC;AAE1E,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAErE,2EAA2E;AAC3E,+EAA+E;AAC/E,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAEnD,8EAA8E;AAC9E,0EAA0E;AAC1E,6EAA6E;AAC7E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,eAAe,CACpC,IAAI,EACJ,iBAAiB,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE;IAC3C,MAAM,YAAY,GAAG;QACnB,GAAG,IAAI,GAAG,CACR,UAAU;aACP,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;aACvB,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CACpE,CACJ;KACF,CAAC;IAEF,OAAO,YAAY,CAAC,MAAM,KAAK,CAAC;QAC9B,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,wCAAwC,UAAU,aAAa,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,0EAA0E,CAAC;AAClM,CAAC,CAAC,CACH,CAAC;AAEF,eAAe,QAAQ,CAAC,IAAI,CAAC;KAC1B,QAAQ,CAAC,MAAM,CAAC;KAChB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC;KACnC,IAAI,CAAC,oBAAoB,CAAC;KAC1B,GAAG,CAAC,yDAAyD,CAAC;KAC9D,WAAW,CACV,6PAA6P,CAC9P;KACA,OAAO,CACN,2FAA2F,CAC5F;KACA,WAAW,CACV,2fAA2f,CAC5f;KACA,SAAS,CAAC,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,yEAAyE;IACzE,kCAAkC;KACjC,kBAAkB,CAAC,IAAI,CAAC;KACxB,YAAY,CAAC,IAAI,CAAC;KAClB,qBAAqB,CAAC,IAAI,CAAC;KAC3B,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ApiContext, type CommonHttpResponse, type HttpResponse, type RuleViolationLocation, type ThymianHttpResponse, type ThymianHttpTransaction } from '@thymian/core';
|
|
2
|
+
export declare function describedTransaction(ctx: ApiContext, location: RuleViolationLocation): ThymianHttpTransaction | undefined;
|
|
3
|
+
export declare function declaresHeader(res: ThymianHttpResponse, header: string): boolean;
|
|
4
|
+
export declare function carriesHeader(res: CommonHttpResponse, header: string): boolean;
|
|
5
|
+
export declare function pinnedHeaderValues(res: ThymianHttpResponse, header: string): string[] | undefined;
|
|
6
|
+
export declare function liveHeaderValues(headers: HttpResponse['headers'], header: string): string[];
|
|
7
|
+
//# sourceMappingURL=headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../../src/rules/utils/headers.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,kBAAkB,EAEvB,KAAK,YAAY,EAEjB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,eAAe,CAAC;AAIvB,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,qBAAqB,GAC9B,sBAAsB,GAAG,SAAS,CAIpC;AAiBD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,mBAAmB,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAET;AAID,wBAAgB,aAAa,CAC3B,GAAG,EAAE,kBAAkB,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAET;AAMD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,mBAAmB,EACxB,MAAM,EAAE,MAAM,GACb,MAAM,EAAE,GAAG,SAAS,CAatB;AAID,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,MAAM,EAAE,MAAM,GACb,MAAM,EAAE,CAMV"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// How each validation context reaches a response header: as the API
|
|
2
|
+
// description declares it in `static`, by name only through the common
|
|
3
|
+
// interface, and as it was sent in `test` and `analytics`. Header names
|
|
4
|
+
// compare case-insensitively.
|
|
5
|
+
import { getHeader, } from '@thymian/core';
|
|
6
|
+
// The described transaction behind a `static` or `test` location, both of
|
|
7
|
+
// which sit on the transaction's edge in the format.
|
|
8
|
+
export function describedTransaction(ctx, location) {
|
|
9
|
+
return typeof location === 'string'
|
|
10
|
+
? undefined
|
|
11
|
+
: ctx.format.getThymianHttpTransactionById(location.elementId);
|
|
12
|
+
}
|
|
13
|
+
function sameHeaderName(a, b) {
|
|
14
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
// A response header as the API description declares it.
|
|
17
|
+
function declaredHeader(res, header) {
|
|
18
|
+
const name = Object.keys(res.headers).find((declared) => sameHeaderName(declared, header));
|
|
19
|
+
return name === undefined ? undefined : res.headers[name];
|
|
20
|
+
}
|
|
21
|
+
export function declaresHeader(res, header) {
|
|
22
|
+
return declaredHeader(res, header) !== undefined;
|
|
23
|
+
}
|
|
24
|
+
// Whether a response carries a header, as the common interface sees it: by
|
|
25
|
+
// name only — declared in `static`, sent in `test` and `analytics`.
|
|
26
|
+
export function carriesHeader(res, header) {
|
|
27
|
+
return res.headers.some((name) => sameHeaderName(name, header));
|
|
28
|
+
}
|
|
29
|
+
// The values an API description pins for one response header: a `const`,
|
|
30
|
+
// every `enum` member, and every example. `undefined` means the header is not
|
|
31
|
+
// declared, or is declared without a pinned value — which is not an
|
|
32
|
+
// impossibility: the rule declares `static` and skips at runtime (ADR-0021 §4).
|
|
33
|
+
export function pinnedHeaderValues(res, header) {
|
|
34
|
+
const schema = declaredHeader(res, header)?.schema;
|
|
35
|
+
if (schema === undefined) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const values = [
|
|
39
|
+
schema.const,
|
|
40
|
+
...(schema.enum ?? []),
|
|
41
|
+
...(schema.examples ?? []),
|
|
42
|
+
].filter((value) => typeof value === 'string');
|
|
43
|
+
return values.length > 0 ? [...new Set(values)] : undefined;
|
|
44
|
+
}
|
|
45
|
+
// Every field line of one header in a live response: repeated field lines
|
|
46
|
+
// arrive as an array.
|
|
47
|
+
export function liveHeaderValues(headers, header) {
|
|
48
|
+
const value = getHeader(headers, header);
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
return Array.isArray(value) ? value : [value];
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=headers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../src/rules/utils/headers.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,uEAAuE;AACvE,wEAAwE;AACxE,8BAA8B;AAE9B,OAAO,EAGL,SAAS,GAMV,MAAM,eAAe,CAAC;AAEvB,0EAA0E;AAC1E,qDAAqD;AACrD,MAAM,UAAU,oBAAoB,CAClC,GAAe,EACf,QAA+B;IAE/B,OAAO,OAAO,QAAQ,KAAK,QAAQ;QACjC,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,6BAA6B,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,cAAc,CAAC,CAAS,EAAE,CAAS;IAC1C,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AAC7C,CAAC;AAED,wDAAwD;AACxD,SAAS,cAAc,CACrB,GAAwB,EACxB,MAAc;IAEd,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CACtD,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CACjC,CAAC;IACF,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,GAAwB,EACxB,MAAc;IAEd,OAAO,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,SAAS,CAAC;AACnD,CAAC;AAED,2EAA2E;AAC3E,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAC3B,GAAuB,EACvB,MAAc;IAEd,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,yEAAyE;AACzE,8EAA8E;AAC9E,oEAAoE;AACpE,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAChC,GAAwB,EACxB,MAAc;IAEd,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IACnD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,MAAM,GAAG;QACb,MAAM,CAAC,KAAK;QACZ,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACtB,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;KAC3B,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;IAEhE,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9D,CAAC;AAED,0EAA0E;AAC1E,sBAAsB;AACtB,MAAM,UAAU,gBAAgB,CAC9B,OAAgC,EAChC,MAAc;IAEd,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type LintContext, type LiveApiContext, type RuleFn, type TestContext } from '@thymian/core';
|
|
2
|
+
import { type RuleOptions } from './results.js';
|
|
3
|
+
export declare function redirectRuleFns(ruleName: string): {
|
|
4
|
+
lint: RuleFn<LintContext, RuleOptions>;
|
|
5
|
+
test: RuleFn<TestContext, RuleOptions>;
|
|
6
|
+
analytics: RuleFn<LiveApiContext, RuleOptions>;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=redirect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../../src/rules/utils/redirect.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,cAAc,EAEnB,KAAK,MAAM,EAIX,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAC;AAQvB,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,cAAc,CAAC;AAoIrE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;CAChD,CA0EA"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// RFC 6797 §7.2: a request over non-secure transport SHOULD be answered with
|
|
2
|
+
// "a status code indicating a permanent redirect, such as status code 301",
|
|
3
|
+
// and a Location whose URI scheme is https. Shared by the RFC's own rule and
|
|
4
|
+
// the convention rule that asks the same of every server, so the two cannot
|
|
5
|
+
// disagree on what a redirect is.
|
|
6
|
+
import { protocol, singleTestCase, } from '@thymian/core';
|
|
7
|
+
import { declaresHeader, describedTransaction, liveHeaderValues, pinnedHeaderValues, } from './headers.js';
|
|
8
|
+
import { ruleSkip, violation } from './results.js';
|
|
9
|
+
import { isServerFallbackOrigin, serverFallbackSkip } from './transport.js';
|
|
10
|
+
// 301 (Moved Permanently) and 308 (Permanent Redirect) are HTTP's two
|
|
11
|
+
// permanent redirects. 302, 303 and 307 redirect too, but not permanently:
|
|
12
|
+
// the user agent comes back over plain HTTP every time.
|
|
13
|
+
const PERMANENT_REDIRECTS = [301, 308];
|
|
14
|
+
const OTHER_REDIRECTS = [302, 303, 307];
|
|
15
|
+
const SHOULD_REDIRECT = 'A request over non-secure transport should be answered with a permanent redirect (301 or 308) to the resource over https.';
|
|
16
|
+
// The URI a request was made for, which a relative Location resolves
|
|
17
|
+
// against: the same shape whether declared, sent or recorded.
|
|
18
|
+
function requestUri({ origin, path }) {
|
|
19
|
+
return new URL(path, origin).toString();
|
|
20
|
+
}
|
|
21
|
+
// The scheme a Location reference names once resolved against the URI it
|
|
22
|
+
// was received for: a relative reference takes the request's own scheme.
|
|
23
|
+
function locationScheme(location, base) {
|
|
24
|
+
try {
|
|
25
|
+
return new URL(location, base).protocol.replace(/:$/, '');
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const DECLARED = {
|
|
32
|
+
answered: 'This operation is served over non-secure transport (http) and declares a',
|
|
33
|
+
redirected: 'This operation is served over non-secure transport (http) and redirects to',
|
|
34
|
+
};
|
|
35
|
+
const ANSWERED = {
|
|
36
|
+
answered: 'This request over non-secure transport (http) was answered with',
|
|
37
|
+
redirected: 'This request over non-secure transport (http) is redirected to',
|
|
38
|
+
};
|
|
39
|
+
// Why one response to a plain-http request is not the redirect §7.2 asks
|
|
40
|
+
// for, or undefined when it is. `locations` are the Location values: the
|
|
41
|
+
// ones the description pins in `static`, the ones sent in `test` and
|
|
42
|
+
// `analytics`. Every one of them must name an https URI.
|
|
43
|
+
function redirectProblem(wording, status, locations, base) {
|
|
44
|
+
if (!PERMANENT_REDIRECTS.includes(status)) {
|
|
45
|
+
return OTHER_REDIRECTS.includes(status)
|
|
46
|
+
? `${wording.answered} ${status}, a redirect that is not permanent, which the user agent follows again over plain http on every visit. ${SHOULD_REDIRECT}`
|
|
47
|
+
: `${wording.answered} ${status}, not a redirect. ${SHOULD_REDIRECT}`;
|
|
48
|
+
}
|
|
49
|
+
const targets = locations.filter((location) => location.trim() !== '');
|
|
50
|
+
if (targets.length === 0) {
|
|
51
|
+
return `${wording.answered} ${status} without a Location header field, so the redirect names no target. ${SHOULD_REDIRECT}`;
|
|
52
|
+
}
|
|
53
|
+
const insecure = targets.filter((target) => locationScheme(target, base) !== 'https');
|
|
54
|
+
return insecure.length === 0
|
|
55
|
+
? undefined
|
|
56
|
+
: `${wording.redirected} ${insecure.map((target) => `"${target}"`).join(', ')}, which is not an https URI. ${SHOULD_REDIRECT}`;
|
|
57
|
+
}
|
|
58
|
+
function liveRedirectProblem(request, response) {
|
|
59
|
+
return redirectProblem(ANSWERED, response.statusCode, liveHeaderValues(response.headers, 'location'), requestUri(request));
|
|
60
|
+
}
|
|
61
|
+
// Static: each response the description declares for an operation served
|
|
62
|
+
// over http is held to the redirect on its own, as each answer is live —
|
|
63
|
+
// a permanent redirect beside it does not excuse a 200 or a 302. A Location
|
|
64
|
+
// declared without a pinned value cannot be checked, which is a
|
|
65
|
+
// `rule-skip`, never a pass.
|
|
66
|
+
function staticRedirectVerdict(base, res) {
|
|
67
|
+
const locations = pinnedHeaderValues(res, 'location');
|
|
68
|
+
if (locations === undefined &&
|
|
69
|
+
PERMANENT_REDIRECTS.includes(res.statusCode) &&
|
|
70
|
+
declaresHeader(res, 'location')) {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'undecidable',
|
|
73
|
+
message: 'The API description declares the redirect with a Location header field but does not pin its value (const, enum or examples), so its scheme cannot be checked statically.',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const problem = redirectProblem(DECLARED, res.statusCode, locations ?? [], base);
|
|
77
|
+
return problem === undefined
|
|
78
|
+
? { kind: 'passes' }
|
|
79
|
+
: { kind: 'violates', message: problem };
|
|
80
|
+
}
|
|
81
|
+
// The three execution functions of a rule that holds plain-http requests to
|
|
82
|
+
// §7.2's redirect, for `.overrideStaticRule()`, `.overrideTest()` and
|
|
83
|
+
// `.overrideAnalyticsRule()`. Such a rule overrides all three contexts:
|
|
84
|
+
// whether the redirect names an https target is in the Location's value,
|
|
85
|
+
// which the common interface cannot see, and each context judges it its own
|
|
86
|
+
// way — per declared response in `static`, without the status-code check in
|
|
87
|
+
// `test`, per recorded answer in `analytics`. The server URL fallback (see
|
|
88
|
+
// `isServerFallbackOrigin`) is skipped in `static` and in `test`, and judged
|
|
89
|
+
// in `analytics`.
|
|
90
|
+
export function redirectRuleFns(ruleName) {
|
|
91
|
+
return {
|
|
92
|
+
// A function validator, because the lint context's
|
|
93
|
+
// `validateHttpTransactions` keeps only results carrying a violation and
|
|
94
|
+
// a skip must surface. One verdict per declared response.
|
|
95
|
+
lint: (ctx) => ctx.validateCommonHttpTransactions(protocol('http'), (req, _res, location) => {
|
|
96
|
+
const res = describedTransaction(ctx, location)?.thymianRes;
|
|
97
|
+
if (res === undefined) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
if (isServerFallbackOrigin(req.origin)) {
|
|
101
|
+
return [serverFallbackSkip(location, ruleName)];
|
|
102
|
+
}
|
|
103
|
+
const verdict = staticRedirectVerdict(requestUri(req), res);
|
|
104
|
+
switch (verdict.kind) {
|
|
105
|
+
case 'violates':
|
|
106
|
+
return [violation(location, verdict.message)];
|
|
107
|
+
case 'undecidable':
|
|
108
|
+
return [ruleSkip(location, ruleName, verdict.message)];
|
|
109
|
+
case 'passes':
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
// The redirect is a status the description does not declare for the
|
|
114
|
+
// operation, so the step opts out of the status-code check, which would
|
|
115
|
+
// otherwise skip exactly the responses this rule exists to judge. A
|
|
116
|
+
// request is sent once per declared response; it gets one verdict.
|
|
117
|
+
test: async (ctx) => {
|
|
118
|
+
const results = [];
|
|
119
|
+
const judged = new Set();
|
|
120
|
+
await ctx.httpTest(singleTestCase()
|
|
121
|
+
.forTransactionsWith(protocol('http'))
|
|
122
|
+
.run({ checkStatusCode: false })
|
|
123
|
+
.transactions(([{ source, request, response }]) => {
|
|
124
|
+
if (judged.has(source.thymianReqId)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
judged.add(source.thymianReqId);
|
|
128
|
+
const location = {
|
|
129
|
+
elementType: 'edge',
|
|
130
|
+
elementId: source.transactionId,
|
|
131
|
+
};
|
|
132
|
+
if (isServerFallbackOrigin(request.origin)) {
|
|
133
|
+
results.push(serverFallbackSkip(location, ruleName));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const problem = liveRedirectProblem(request, response);
|
|
137
|
+
if (problem !== undefined) {
|
|
138
|
+
results.push(violation(location, problem));
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.done());
|
|
142
|
+
return results;
|
|
143
|
+
},
|
|
144
|
+
analytics: (ctx) => ctx.validateHttpTransactions(protocol('http'), (req, res, location) => {
|
|
145
|
+
const problem = liveRedirectProblem(req, res);
|
|
146
|
+
return problem === undefined ? [] : [violation(location, problem)];
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=redirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../../src/rules/utils/redirect.ts"],"names":[],"mappings":"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"}
|