@growth-labs/conformance 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @growth-labs/conformance Changelog
2
2
 
3
+ ## 0.2.0 — 2026-07-16
4
+
5
+ ### Added
6
+
7
+ - Added schema v2 security receipts for consumer-supplied enforced-CSP
8
+ functional probes and CSP reporting/canary evidence.
9
+ - Added required COOP/CORP response headers, conservative shared policy
10
+ defaults, and documented consumer overrides for verified auth/media/embed
11
+ constraints.
12
+ - Added required negative fixtures for missing COOP/CORP, HSTS below one year,
13
+ wildcard `default-src`, `unsafe-eval`, executable inline scripts without a
14
+ matching nonce or SHA-256 hash, conflicting duplicate security headers,
15
+ missing functional-probe receipts, and missing/failed CSP-report receipts.
16
+
17
+ ### Changed
18
+
19
+ - HSTS validation now requires `max-age >= 31536000`.
20
+ - `buildSharedPublicationHeaders()` now keeps `includeSubDomains` and `preload`
21
+ opt-in behind explicit host-inventory proof instead of emitting them by
22
+ default.
23
+ - CSP validation now parses directives instead of substring-matching the raw
24
+ header.
25
+
3
26
  ## 0.1.0 — 2026-07-15
4
27
 
5
28
  ### Added
package/README.md CHANGED
@@ -30,7 +30,9 @@ const fixture = {
30
30
  status: 200,
31
31
  headers: {
32
32
  'content-security-policy': "default-src 'self'; script-src 'self' 'nonce-runtime'",
33
- 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
33
+ 'strict-transport-security': 'max-age=31536000',
34
+ 'cross-origin-opener-policy': 'same-origin',
35
+ 'cross-origin-resource-policy': 'same-origin',
34
36
  'x-frame-options': 'DENY',
35
37
  'x-content-type-options': 'nosniff',
36
38
  'referrer-policy': 'strict-origin-when-cross-origin',
@@ -53,9 +55,27 @@ const fixture = {
53
55
  { audience: 'public', cacheKey: 'fronts:public:/example' },
54
56
  { audience: 'crawler', cacheKey: 'fronts:crawler:/example' },
55
57
  ],
58
+ securityReceipts: {
59
+ functionalProbes: [
60
+ { name: 'oauth', status: 'pass', evidence: 'sanitized consumer probe receipt' },
61
+ { name: 'media', status: 'pass', evidence: 'sanitized consumer probe receipt' },
62
+ { name: 'images', status: 'pass', evidence: 'sanitized consumer probe receipt' },
63
+ { name: 'analytics', status: 'pass', evidence: 'sanitized consumer probe receipt' },
64
+ { name: 'embeds', status: 'pass', evidence: 'sanitized consumer probe receipt' },
65
+ { name: 'error-pages', status: 'pass', evidence: 'sanitized consumer probe receipt' },
66
+ ],
67
+ cspReports: [
68
+ {
69
+ name: 'csp-report-canary',
70
+ status: 'pass',
71
+ endpoint: 'consumer-owned-report-endpoint',
72
+ evidence: 'sanitized CSP report or blocked-canary receipt',
73
+ },
74
+ ],
75
+ },
56
76
  buildEvidence: {
57
77
  commitSha: 'bd39d718f6a5b0df9a249e11638a8b430050941e',
58
- packageVersions: { '@growth-labs/seo': '0.9.0' },
78
+ packageVersions: { '@growth-labs/conformance': '0.2.0', '@growth-labs/seo': '0.9.0' },
59
79
  staticHeaderPolicy: true,
60
80
  ssrHeaderPolicy: true,
61
81
  },
@@ -77,16 +97,25 @@ const headers = buildSharedPublicationHeaders({
77
97
  renderMode: 'ssr',
78
98
  audience: 'public',
79
99
  nonce: runtimeNonce,
100
+ cspReportUri: 'https://example.com/api/csp-report',
80
101
  markdownTwinUrl: 'https://example.com/article.md',
81
102
  })
82
103
  ```
83
104
 
84
105
  The policy emits CSP, HSTS, frame protection, MIME sniffing protection,
85
- referrer policy, permissions policy, cache semantics, and optional markdown
86
- twin `Link` metadata. Private audiences use `private, no-store`; public SSR
87
- uses short shared-cache semantics; public static surfaces get longer
106
+ COOP, CORP, referrer policy, permissions policy, cache semantics, and optional
107
+ markdown twin `Link` metadata. Private audiences use `private, no-store`;
108
+ public SSR uses short shared-cache semantics; public static surfaces get longer
88
109
  shared-cache semantics.
89
110
 
111
+ HSTS defaults to `max-age=31536000` only. Add `includeSubDomains` and `preload`
112
+ through `strictTransportSecurity: { includeSubDomains: true, preload: true }`
113
+ only after the consumer has separately verified the full host inventory can
114
+ enforce HTTPS. COOP defaults to `same-origin` and CORP defaults to
115
+ `same-origin`; consumers may override them only when verified OAuth popup,
116
+ media, or embed constraints require a looser policy and the receipt proves the
117
+ result under enforced CSP.
118
+
90
119
  ## Fixture Commands
91
120
 
92
121
  The root repo exposes:
@@ -100,6 +129,16 @@ The fixture command proves the required negative fixtures fail loudly with
100
129
  stable finding codes:
101
130
 
102
131
  - `csp-unsafe-inline`
132
+ - `missing-coop`
133
+ - `missing-corp`
134
+ - `hsts-below-one-year`
135
+ - `csp-wildcard-default-src`
136
+ - `csp-unsafe-eval`
137
+ - `csp-inline-executable-without-nonce-or-hash`
138
+ - `conflicting-duplicate-security-header`
139
+ - `missing-functional-probe-receipt`
140
+ - `missing-csp-report-receipt`
141
+ - `failed-csp-report-receipt`
103
142
  - `headers-static-only-while-ssr-missing`
104
143
  - `undeclared-na`
105
144
  - `stringified-is-accessible-for-free`
@@ -114,5 +153,11 @@ stable finding codes:
114
153
 
115
154
  `runSiteConformance()` returns a `SiteEvidenceBundle`; `runFleetConformance()`
116
155
  returns a `FleetEvidenceBundle`. Both are validated by exported Zod schemas
117
- and carry `schemaVersion: "growth-labs.conformance.v1"` so downstream jobs can
156
+ and carry `schemaVersion: "growth-labs.conformance.v2"` so downstream jobs can
118
157
  parse them without relying on console text.
158
+
159
+ Schema v2 adds `securityReceipts` to fixtures and site evidence bundles. Existing
160
+ v1 fixtures must add explicit functional-probe receipts for `oauth`, `media`,
161
+ `images`, `analytics`, `embeds`, and `error-pages`, plus at least one passing CSP
162
+ reporting/canary receipt. The package does not perform those live probes itself;
163
+ consumer CI supplies sanitized pass/fail/NA receipts.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { buildCacheControl, buildContentSecurityPolicy, buildSharedPublicationHeaders, type CachePolicyOptions, type HeaderPolicyOptions, isPrivateAudience, normalizeHeaders, REQUIRED_SECURITY_HEADERS, } from './policy.js';
1
+ export { buildCacheControl, buildContentSecurityPolicy, buildSharedPublicationHeaders, buildStrictTransportSecurity, type CachePolicyOptions, type ContentSecurityPolicyOptions, type CrossOriginOpenerPolicy, type CrossOriginResourcePolicy, collectHeaders, type HeaderPolicyOptions, isPrivateAudience, normalizeHeaders, REQUIRED_SECURITY_HEADERS, type StrictTransportSecurityOptions, } from './policy.js';
2
2
  export { type RunConformanceOptions, runFleetConformance, runSiteConformance } from './runner.js';
3
- export { type Audience, audienceSchema, type BuildEvidence, buildEvidenceSchema, type CacheVariant, type ConformanceCheckReceipt, type ConformanceFinding, type ConformanceFixture, type ConformanceFixtureInput, cacheVariantSchema, checkStatusSchema, conformanceCheckReceiptSchema, conformanceExpectedSchema, conformanceFindingSchema, conformanceFixtureSchema, EVIDENCE_SCHEMA_VERSION, type EvidenceSummary, evidenceSummarySchema, type FleetEvidenceBundle, findingSeveritySchema, fleetEvidenceBundleSchema, type HeaderMap, headerMapSchema, headerValueSchema, type PublicationSurfaceEvidence, parseConformanceFixture, publicationSurfaceSchema, REQUIRED_NEGATIVE_FIXTURE_CODES, type RenderMode, type RequiredNegativeFindingCode, type RequiredNegativeFixtureId, renderModeSchema, type SiteEvidenceBundle, type SurfaceResponse, siteEvidenceBundleSchema, surfaceResponseSchema, validateFleetEvidenceBundle, validateSiteEvidenceBundle, } from './schema.js';
3
+ export { type Audience, audienceSchema, type BuildEvidence, buildEvidenceSchema, type CacheVariant, type ConformanceCheckReceipt, type ConformanceFinding, type ConformanceFixture, type ConformanceFixtureInput, type CspReportReceipt, cacheVariantSchema, checkStatusSchema, conformanceCheckReceiptSchema, conformanceExpectedSchema, conformanceFindingSchema, conformanceFixtureSchema, cspReportReceiptSchema, EVIDENCE_SCHEMA_VERSION, type EvidenceSummary, evidenceSummarySchema, type FleetEvidenceBundle, findingSeveritySchema, fleetEvidenceBundleSchema, type HeaderMap, headerMapSchema, headerValueSchema, type PublicationSurfaceEvidence, parseConformanceFixture, publicationSurfaceSchema, REQUIRED_FUNCTIONAL_PROBE_NAMES, REQUIRED_NEGATIVE_FIXTURE_CODES, type RenderMode, type RequiredNegativeFindingCode, type RequiredNegativeFixtureId, renderModeSchema, type SecurityFunctionalProbeReceipt, type SecurityReceipts, type SiteEvidenceBundle, type SurfaceResponse, securityFunctionalProbeReceiptSchema, securityReceiptsSchema, siteEvidenceBundleSchema, surfaceResponseSchema, validateFleetEvidenceBundle, validateSiteEvidenceBundle, } from './schema.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,GACzB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,KAAK,qBAAqB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACjG,OAAO,EACN,KAAK,QAAQ,EACb,cAAc,EACd,KAAK,aAAa,EAClB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,EAC7B,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,KAAK,SAAS,EACd,eAAe,EACf,iBAAiB,EACjB,KAAK,0BAA0B,EAC/B,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAC/B,KAAK,UAAU,EACf,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,GAC1B,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC5B,KAAK,kBAAkB,EACvB,KAAK,4BAA4B,EACjC,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,cAAc,EACd,KAAK,mBAAmB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,EACzB,KAAK,8BAA8B,GACnC,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,KAAK,qBAAqB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACjG,OAAO,EACN,KAAK,QAAQ,EACb,cAAc,EACd,KAAK,aAAa,EAClB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,EAC7B,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,KAAK,SAAS,EACd,eAAe,EACf,iBAAiB,EACjB,KAAK,0BAA0B,EAC/B,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAC/B,+BAA+B,EAC/B,KAAK,UAAU,EACf,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,gBAAgB,EAChB,KAAK,8BAA8B,EACnC,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,oCAAoC,EACpC,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,GAC1B,MAAM,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { buildCacheControl, buildContentSecurityPolicy, buildSharedPublicationHeaders, isPrivateAudience, normalizeHeaders, REQUIRED_SECURITY_HEADERS, } from './policy.js';
1
+ export { buildCacheControl, buildContentSecurityPolicy, buildSharedPublicationHeaders, buildStrictTransportSecurity, collectHeaders, isPrivateAudience, normalizeHeaders, REQUIRED_SECURITY_HEADERS, } from './policy.js';
2
2
  export { runFleetConformance, runSiteConformance } from './runner.js';
3
- export { audienceSchema, buildEvidenceSchema, cacheVariantSchema, checkStatusSchema, conformanceCheckReceiptSchema, conformanceExpectedSchema, conformanceFindingSchema, conformanceFixtureSchema, EVIDENCE_SCHEMA_VERSION, evidenceSummarySchema, findingSeveritySchema, fleetEvidenceBundleSchema, headerMapSchema, headerValueSchema, parseConformanceFixture, publicationSurfaceSchema, REQUIRED_NEGATIVE_FIXTURE_CODES, renderModeSchema, siteEvidenceBundleSchema, surfaceResponseSchema, validateFleetEvidenceBundle, validateSiteEvidenceBundle, } from './schema.js';
3
+ export { audienceSchema, buildEvidenceSchema, cacheVariantSchema, checkStatusSchema, conformanceCheckReceiptSchema, conformanceExpectedSchema, conformanceFindingSchema, conformanceFixtureSchema, cspReportReceiptSchema, EVIDENCE_SCHEMA_VERSION, evidenceSummarySchema, findingSeveritySchema, fleetEvidenceBundleSchema, headerMapSchema, headerValueSchema, parseConformanceFixture, publicationSurfaceSchema, REQUIRED_FUNCTIONAL_PROBE_NAMES, REQUIRED_NEGATIVE_FIXTURE_CODES, renderModeSchema, securityFunctionalProbeReceiptSchema, securityReceiptsSchema, siteEvidenceBundleSchema, surfaceResponseSchema, validateFleetEvidenceBundle, validateSiteEvidenceBundle, } from './schema.js';
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,0BAA0B,EAC1B,6BAA6B,EAG7B,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,GACzB,MAAM,aAAa,CAAA;AACpB,OAAO,EAA8B,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACjG,OAAO,EAEN,cAAc,EAEd,mBAAmB,EAMnB,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,EAC7B,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EAEvB,qBAAqB,EAErB,qBAAqB,EACrB,yBAAyB,EAEzB,eAAe,EACf,iBAAiB,EAEjB,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAI/B,gBAAgB,EAGhB,wBAAwB,EACxB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,GAC1B,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EACjB,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAK5B,cAAc,EAEd,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,GAEzB,MAAM,aAAa,CAAA;AACpB,OAAO,EAA8B,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACjG,OAAO,EAEN,cAAc,EAEd,mBAAmB,EAOnB,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,EAC7B,yBAAyB,EACzB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EAEvB,qBAAqB,EAErB,qBAAqB,EACrB,yBAAyB,EAEzB,eAAe,EACf,iBAAiB,EAEjB,uBAAuB,EACvB,wBAAwB,EACxB,+BAA+B,EAC/B,+BAA+B,EAI/B,gBAAgB,EAKhB,oCAAoC,EACpC,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,GAC1B,MAAM,aAAa,CAAA"}
package/dist/policy.d.ts CHANGED
@@ -1,9 +1,26 @@
1
1
  import type { Audience, HeaderMap, RenderMode } from './schema.js';
2
- export declare const REQUIRED_SECURITY_HEADERS: readonly ["content-security-policy", "strict-transport-security", "x-frame-options", "x-content-type-options", "referrer-policy", "permissions-policy"];
2
+ export declare const REQUIRED_SECURITY_HEADERS: readonly ["content-security-policy", "strict-transport-security", "cross-origin-opener-policy", "cross-origin-resource-policy", "x-frame-options", "x-content-type-options", "referrer-policy", "permissions-policy"];
3
+ export type CrossOriginOpenerPolicy = 'same-origin' | 'same-origin-allow-popups' | 'unsafe-none';
4
+ export type CrossOriginResourcePolicy = 'same-origin' | 'same-site' | 'cross-origin';
5
+ export interface StrictTransportSecurityOptions {
6
+ maxAgeSeconds?: number;
7
+ includeSubDomains?: boolean;
8
+ preload?: boolean;
9
+ }
10
+ export interface ContentSecurityPolicyOptions {
11
+ nonce?: string;
12
+ reportUri?: string;
13
+ reportTo?: string;
14
+ }
3
15
  export interface HeaderPolicyOptions {
4
16
  renderMode: RenderMode;
5
17
  audience: Audience;
6
18
  nonce?: string;
19
+ cspReportUri?: string;
20
+ cspReportTo?: string;
21
+ strictTransportSecurity?: StrictTransportSecurityOptions;
22
+ crossOriginOpenerPolicy?: CrossOriginOpenerPolicy;
23
+ crossOriginResourcePolicy?: CrossOriginResourcePolicy;
7
24
  markdownTwinUrl?: string;
8
25
  }
9
26
  export interface CachePolicyOptions {
@@ -11,8 +28,10 @@ export interface CachePolicyOptions {
11
28
  audience: Audience;
12
29
  }
13
30
  export declare function buildSharedPublicationHeaders(options: HeaderPolicyOptions): Record<string, string>;
14
- export declare function buildContentSecurityPolicy(nonce?: string): string;
31
+ export declare function buildContentSecurityPolicy(input?: string | ContentSecurityPolicyOptions): string;
32
+ export declare function buildStrictTransportSecurity(options?: StrictTransportSecurityOptions): string;
15
33
  export declare function buildCacheControl({ renderMode, audience }: CachePolicyOptions): string;
16
34
  export declare function normalizeHeaders(headers: HeaderMap | undefined): Record<string, string>;
35
+ export declare function collectHeaders(headers: HeaderMap | undefined): Record<string, string[]>;
17
36
  export declare function isPrivateAudience(audience: Audience): boolean;
18
37
  //# sourceMappingURL=policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAElE,eAAO,MAAM,yBAAyB,yJAO5B,CAAA;AAEV,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;CAClB;AAED,wBAAgB,6BAA6B,CAC5C,OAAO,EAAE,mBAAmB,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiBxB;AAED,wBAAgB,0BAA0B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBjE;AAED,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,MAAM,CAMtF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAavF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAO7D"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAElE,eAAO,MAAM,yBAAyB,uNAS5B,CAAA;AAEV,MAAM,MAAM,uBAAuB,GAAG,aAAa,GAAG,0BAA0B,GAAG,aAAa,CAAA;AAChG,MAAM,MAAM,yBAAyB,GAAG,aAAa,GAAG,WAAW,GAAG,cAAc,CAAA;AAEpF,MAAM,WAAW,8BAA8B;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,4BAA4B;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uBAAuB,CAAC,EAAE,8BAA8B,CAAA;IACxD,uBAAuB,CAAC,EAAE,uBAAuB,CAAA;IACjD,yBAAyB,CAAC,EAAE,yBAAyB,CAAA;IACrD,eAAe,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;CAClB;AAED,wBAAgB,6BAA6B,CAC5C,OAAO,EAAE,mBAAmB,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAuBxB;AAED,wBAAgB,0BAA0B,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,4BAA4B,GAAG,MAAM,CAyBhG;AAED,wBAAgB,4BAA4B,CAAC,OAAO,GAAE,8BAAmC,GAAG,MAAM,CAejG;AAED,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,MAAM,CAMtF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOvF;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAavF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAO7D"}
package/dist/policy.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export const REQUIRED_SECURITY_HEADERS = [
2
2
  'content-security-policy',
3
3
  'strict-transport-security',
4
+ 'cross-origin-opener-policy',
5
+ 'cross-origin-resource-policy',
4
6
  'x-frame-options',
5
7
  'x-content-type-options',
6
8
  'referrer-policy',
@@ -8,8 +10,14 @@ export const REQUIRED_SECURITY_HEADERS = [
8
10
  ];
9
11
  export function buildSharedPublicationHeaders(options) {
10
12
  const headers = {
11
- 'Content-Security-Policy': buildContentSecurityPolicy(options.nonce),
12
- 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
13
+ 'Content-Security-Policy': buildContentSecurityPolicy({
14
+ nonce: options.nonce,
15
+ reportUri: options.cspReportUri,
16
+ reportTo: options.cspReportTo,
17
+ }),
18
+ 'Strict-Transport-Security': buildStrictTransportSecurity(options.strictTransportSecurity),
19
+ 'Cross-Origin-Opener-Policy': options.crossOriginOpenerPolicy ?? 'same-origin',
20
+ 'Cross-Origin-Resource-Policy': options.crossOriginResourcePolicy ?? 'same-origin',
13
21
  'X-Frame-Options': 'DENY',
14
22
  'X-Content-Type-Options': 'nosniff',
15
23
  'Referrer-Policy': 'strict-origin-when-cross-origin',
@@ -21,14 +29,15 @@ export function buildSharedPublicationHeaders(options) {
21
29
  }
22
30
  return headers;
23
31
  }
24
- export function buildContentSecurityPolicy(nonce) {
32
+ export function buildContentSecurityPolicy(input) {
33
+ const options = typeof input === 'string' ? { nonce: input } : (input ?? {});
25
34
  const scriptSources = ["'self'"];
26
35
  const styleSources = ["'self'"];
27
- if (nonce) {
28
- scriptSources.push(`'nonce-${nonce}'`);
29
- styleSources.push(`'nonce-${nonce}'`);
36
+ if (options.nonce) {
37
+ scriptSources.push(`'nonce-${options.nonce}'`);
38
+ styleSources.push(`'nonce-${options.nonce}'`);
30
39
  }
31
- return [
40
+ const directives = [
32
41
  "default-src 'self'",
33
42
  `script-src ${scriptSources.join(' ')}`,
34
43
  `style-src ${styleSources.join(' ')}`,
@@ -39,7 +48,27 @@ export function buildContentSecurityPolicy(nonce) {
39
48
  "base-uri 'self'",
40
49
  "frame-ancestors 'none'",
41
50
  'upgrade-insecure-requests',
42
- ].join('; ');
51
+ ];
52
+ if (options.reportTo)
53
+ directives.push(`report-to ${options.reportTo}`);
54
+ if (options.reportUri)
55
+ directives.push(`report-uri ${options.reportUri}`);
56
+ return directives.join('; ');
57
+ }
58
+ export function buildStrictTransportSecurity(options = {}) {
59
+ const maxAgeSeconds = options.maxAgeSeconds ?? 31536000;
60
+ if (!Number.isInteger(maxAgeSeconds) || maxAgeSeconds < 31536000) {
61
+ throw new Error('Strict-Transport-Security maxAgeSeconds must be at least 31536000');
62
+ }
63
+ if (options.preload === true && options.includeSubDomains !== true) {
64
+ throw new Error('Strict-Transport-Security preload requires includeSubDomains after host inventory proof');
65
+ }
66
+ const directives = [`max-age=${maxAgeSeconds}`];
67
+ if (options.includeSubDomains === true)
68
+ directives.push('includeSubDomains');
69
+ if (options.preload === true)
70
+ directives.push('preload');
71
+ return directives.join('; ');
43
72
  }
44
73
  export function buildCacheControl({ renderMode, audience }) {
45
74
  if (isPrivateAudience(audience))
@@ -50,15 +79,24 @@ export function buildCacheControl({ renderMode, audience }) {
50
79
  return 'public, max-age=0, s-maxage=300, stale-while-revalidate=86400';
51
80
  }
52
81
  export function normalizeHeaders(headers) {
82
+ const entries = collectHeaders(headers);
83
+ const normalized = {};
84
+ for (const [name, values] of Object.entries(entries)) {
85
+ normalized[name] = values.join(', ');
86
+ }
87
+ return normalized;
88
+ }
89
+ export function collectHeaders(headers) {
53
90
  const normalized = {};
54
91
  if (!headers)
55
92
  return normalized;
56
93
  for (const [name, value] of Object.entries(headers)) {
94
+ const normalizedName = name.toLowerCase();
57
95
  if (typeof value === 'string') {
58
- normalized[name.toLowerCase()] = value;
96
+ normalized[normalizedName] = [...(normalized[normalizedName] ?? []), value];
59
97
  }
60
98
  else if (Array.isArray(value)) {
61
- normalized[name.toLowerCase()] = value.join(', ');
99
+ normalized[normalizedName] = [...(normalized[normalizedName] ?? []), ...value];
62
100
  }
63
101
  }
64
102
  return normalized;
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACxC,yBAAyB;IACzB,2BAA2B;IAC3B,iBAAiB;IACjB,wBAAwB;IACxB,iBAAiB;IACjB,oBAAoB;CACX,CAAA;AAcV,MAAM,UAAU,6BAA6B,CAC5C,OAA4B;IAE5B,MAAM,OAAO,GAA2B;QACvC,yBAAyB,EAAE,0BAA0B,CAAC,OAAO,CAAC,KAAK,CAAC;QACpE,2BAA2B,EAAE,8CAA8C;QAC3E,iBAAiB,EAAE,MAAM;QACzB,wBAAwB,EAAE,SAAS;QACnC,iBAAiB,EAAE,iCAAiC;QACpD,oBAAoB,EACnB,8FAA8F;QAC/F,eAAe,EAAE,iBAAiB,CAAC,OAAO,CAAC;KAC3C,CAAA;IAED,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,GAAG,IAAI,OAAO,CAAC,eAAe,0CAA0C,CAAA;IACrF,CAAC;IAED,OAAO,OAAO,CAAA;AACf,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,KAAc;IACxD,MAAM,aAAa,GAAG,CAAC,QAAQ,CAAC,CAAA;IAChC,MAAM,YAAY,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC/B,IAAI,KAAK,EAAE,CAAC;QACX,aAAa,CAAC,IAAI,CAAC,UAAU,KAAK,GAAG,CAAC,CAAA;QACtC,YAAY,CAAC,IAAI,CAAC,UAAU,KAAK,GAAG,CAAC,CAAA;IACtC,CAAC;IAED,OAAO;QACN,oBAAoB;QACpB,cAAc,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACvC,aAAa,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACrC,6BAA6B;QAC7B,uBAAuB;QACvB,2BAA2B;QAC3B,mBAAmB;QACnB,iBAAiB;QACjB,wBAAwB;QACxB,2BAA2B;KAC3B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACb,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAsB;IAC7E,IAAI,iBAAiB,CAAC,QAAQ,CAAC;QAAE,OAAO,mBAAmB,CAAA;IAC3D,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,oEAAoE,CAAA;IAC5E,CAAC;IACD,OAAO,+DAA+D,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAA8B;IAC9D,MAAM,UAAU,GAA2B,EAAE,CAAA;IAC7C,IAAI,CAAC,OAAO;QAAE,OAAO,UAAU,CAAA;IAE/B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/B,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAA;QACvC,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClD,CAAC;IACF,CAAC;IAED,OAAO,UAAU,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAkB;IACnD,OAAO,CACN,QAAQ,KAAK,YAAY;QACzB,QAAQ,KAAK,QAAQ;QACrB,QAAQ,KAAK,OAAO;QACpB,QAAQ,KAAK,SAAS,CACtB,CAAA;AACF,CAAC"}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAKN,KAAK,uBAAuB,EAG5B,KAAK,mBAAmB,EAExB,KAAK,kBAAkB,EAEvB,MAAM,aAAa,CAAA;AAKpB,MAAM,WAAW,qBAAqB;IACrC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAClB;AAED,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,uBAAuB,EAC9B,OAAO,GAAE,qBAA0B,GACjC,kBAAkB,CA6BpB;AAED,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,uBAAuB,EAAE,EACjC,OAAO,GAAE,qBAA0B,GACjC,mBAAmB,CAuBrB"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAOA,OAAO,EAKN,KAAK,uBAAuB,EAG5B,KAAK,mBAAmB,EAGxB,KAAK,kBAAkB,EAEvB,MAAM,aAAa,CAAA;AAuBpB,MAAM,WAAW,qBAAqB;IACrC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAClB;AAED,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,uBAAuB,EAC9B,OAAO,GAAE,qBAA0B,GACjC,kBAAkB,CA+BpB;AAED,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,uBAAuB,EAAE,EACjC,OAAO,GAAE,qBAA0B,GACjC,mBAAmB,CAuBrB"}