@growth-labs/conformance 0.2.0 → 0.3.1

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,36 @@
1
1
  # @growth-labs/conformance Changelog
2
2
 
3
+ ## 0.3.1 — 2026-07-17
4
+
5
+ - Mark the `astro` peer dependency as optional (`peerDependenciesMeta.astro.optional: true`).
6
+ Non-Astro tooling consumers are no longer burdened with Astro's native/tooling
7
+ graph during install. The compatible peer range (`^7.1.0`) is retained for Astro
8
+ consumers, and the sole Astro reference in source (`import type { AstroConfig }`)
9
+ remains a type-only import that is erased at compile time.
10
+
11
+ ## 0.3.0 — 2026-07-17
12
+
13
+ - Add `buildAstroCspConfig()` for Astro 7.1+ native SHA-256 CSP generation with
14
+ exact external-origin validation and no wildcard or `unsafe-*` escape hatch.
15
+ Its return shape is compile-checked against Astro's exported CSP config type,
16
+ and a real Astro build proves the attribute-only directive is emitted.
17
+ - Allow Astro 7.1+ consumers with a proven inline-style-attribute inventory to
18
+ scope `'unsafe-inline'` to `style-src-attr` only. Script and style-element
19
+ execution remain hash-protected and fail closed. The runner requires the
20
+ inventory receipt to match the exact build commit. The policy API requires
21
+ that structured receipt rather than a boolean escape hatch, including a
22
+ versioned scanner identity and SHA-256 output hash.
23
+ - Require canonical lowercase commit SHAs, SHA-256 evidence hashes, and CSP
24
+ hash algorithm tokens in security evidence.
25
+ - Let consumers set `contentSecurityPolicy: false` on
26
+ `buildSharedPublicationHeaders()` when Astro owns the per-response hash CSP;
27
+ all other shared response headers remain package-owned.
28
+ - Add an explicit per-fixture frame policy contract. The default remains
29
+ `frame-ancestors 'none'` plus `X-Frame-Options: DENY`; deliberately embeddable
30
+ routes may declare only `'self'`, `https:`, or exact HTTPS origins and must
31
+ omit a conflicting legacy frame header. The runner validates the CSP sources
32
+ exactly rather than accepting a fleet-wide relaxation.
33
+
3
34
  ## 0.2.0 — 2026-07-16
4
35
 
5
36
  ### Added
package/README.md CHANGED
@@ -45,6 +45,10 @@ const fixture = {
45
45
  canonicalUrl: 'https://fronts.co/example',
46
46
  schemaTypes: ['NewsArticle'],
47
47
  discoverEligible: true,
48
+ framePolicy: {
49
+ ancestors: ["'none'"],
50
+ legacyHeader: 'DENY',
51
+ },
48
52
  },
49
53
  publicationSurfaces: [
50
54
  { name: 'sitemap', status: 'present', httpStatus: 200, bodyBytes: 1024 },
@@ -72,10 +76,18 @@ const fixture = {
72
76
  evidence: 'sanitized CSP report or blocked-canary receipt',
73
77
  },
74
78
  ],
79
+ inlineStyleAttributes: {
80
+ commitSha: 'bd39d718f6a5b0df9a249e11638a8b430050941e',
81
+ occurrenceCount: 4,
82
+ evidence: {
83
+ tool: 'platform-foundations:inline-style-inventory@1',
84
+ outputHash: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
85
+ },
86
+ },
75
87
  },
76
88
  buildEvidence: {
77
89
  commitSha: 'bd39d718f6a5b0df9a249e11638a8b430050941e',
78
- packageVersions: { '@growth-labs/conformance': '0.2.0', '@growth-labs/seo': '0.9.0' },
90
+ packageVersions: { '@growth-labs/conformance': '0.3.0', '@growth-labs/seo': '0.9.0' },
79
91
  staticHeaderPolicy: true,
80
92
  ssrHeaderPolicy: true,
81
93
  },
@@ -88,20 +100,67 @@ const fleetBundle = runFleetConformance([fixture])
88
100
  ## Shared Header Policy
89
101
 
90
102
  Use `buildSharedPublicationHeaders()` when a site wants the package-owned
91
- baseline for SSR and static response headers:
103
+ baseline for SSR and static response headers. Astro 7.1+ sites should let Astro
104
+ own the per-route hash CSP and retain the remaining shared headers. The helper's
105
+ peer contract is intentionally pinned to the first Astro release that supports
106
+ attribute-scoped style resources:
92
107
 
93
108
  ```ts
94
- import { buildSharedPublicationHeaders } from '@growth-labs/conformance/policy'
109
+ import {
110
+ buildAstroCspConfig,
111
+ buildSharedPublicationHeaders,
112
+ } from '@growth-labs/conformance/policy'
113
+
114
+ const csp = buildAstroCspConfig({
115
+ scriptSources: ['https://challenges.cloudflare.com'],
116
+ styleSources: ['https://fonts.googleapis.com'],
117
+ inlineStyleAttributes: {
118
+ commitSha: 'bd39d718f6a5b0df9a249e11638a8b430050941e',
119
+ occurrenceCount: 4,
120
+ evidence: {
121
+ tool: 'platform-foundations:inline-style-inventory@1',
122
+ outputHash: 'sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
123
+ },
124
+ },
125
+ })
95
126
 
96
127
  const headers = buildSharedPublicationHeaders({
97
128
  renderMode: 'ssr',
98
129
  audience: 'public',
99
- nonce: runtimeNonce,
100
- cspReportUri: 'https://example.com/api/csp-report',
130
+ contentSecurityPolicy: false,
101
131
  markdownTwinUrl: 'https://example.com/article.md',
102
132
  })
103
133
  ```
104
134
 
135
+ The fixture default is the non-embeddable policy above. For a deliberately
136
+ embeddable route, declare its exact response class and omit a conflicting
137
+ `X-Frame-Options` header:
138
+
139
+ ```ts
140
+ expected: {
141
+ framePolicy: {
142
+ ancestors: ['https://trusted.example'],
143
+ legacyHeader: 'omit',
144
+ },
145
+ }
146
+ ```
147
+
148
+ Accepted ancestors are `'none'`, `'self'`, `https:`, and exact HTTPS origins;
149
+ wildcards, paths, credentials, query strings, and fragments are rejected.
150
+ `DENY` is valid only with `'none'`, and `SAMEORIGIN` only with `'self'`; broader
151
+ embeddable policies must use `legacyHeader: 'omit'` to avoid contradictory
152
+ browser behavior.
153
+
154
+ Astro 7.1+ consumers that prove they still emit HTML `style` attributes may set
155
+ `inlineStyleAttributes` to the validated inventory receipt. There is no boolean
156
+ escape hatch. The helper then scopes `'unsafe-inline'` to `style-src-attr`
157
+ while preserving hash protection for scripts and `<style>` elements. The
158
+ receipt requires the exact lowercase 40-character build commit SHA, a positive
159
+ occurrence count, the versioned scanner identity, and the lowercase SHA-256
160
+ content hash of its machine output. The same receipt must appear in the
161
+ conformance fixture and its commit must match `buildEvidence.commitSha`. Do not
162
+ enable this option as a generic compatibility fix.
163
+
105
164
  The policy emits CSP, HSTS, frame protection, MIME sniffing protection,
106
165
  COOP, CORP, referrer policy, permissions policy, cache semantics, and optional
107
166
  markdown twin `Link` metadata. Private audiences use `private, no-store`;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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';
1
+ export { type AstroCspConfig, type AstroCspPolicyOptions, type AstroCspResource, buildAstroCspConfig, 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, 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';
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 InlineStyleAttributeInventory, inlineStyleAttributeInventorySchema, 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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,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,6BAA6B,EAClC,mCAAmC,EACnC,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, buildStrictTransportSecurity, collectHeaders, isPrivateAudience, normalizeHeaders, REQUIRED_SECURITY_HEADERS, } from './policy.js';
1
+ export { buildAstroCspConfig, 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, 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';
3
+ export { audienceSchema, buildEvidenceSchema, cacheVariantSchema, checkStatusSchema, conformanceCheckReceiptSchema, conformanceExpectedSchema, conformanceFindingSchema, conformanceFixtureSchema, cspReportReceiptSchema, EVIDENCE_SCHEMA_VERSION, evidenceSummarySchema, findingSeveritySchema, fleetEvidenceBundleSchema, headerMapSchema, headerValueSchema, inlineStyleAttributeInventorySchema, 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,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,mBAAmB,EACnB,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,mCAAmC,EAEnC,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,4 +1,5 @@
1
- import type { Audience, HeaderMap, RenderMode } from './schema.js';
1
+ import type { AstroConfig } from 'astro';
2
+ import { type Audience, type HeaderMap, type InlineStyleAttributeInventory, type RenderMode } from './schema.js';
2
3
  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
4
  export type CrossOriginOpenerPolicy = 'same-origin' | 'same-origin-allow-popups' | 'unsafe-none';
4
5
  export type CrossOriginResourcePolicy = 'same-origin' | 'same-site' | 'cross-origin';
@@ -18,20 +19,51 @@ export interface HeaderPolicyOptions {
18
19
  nonce?: string;
19
20
  cspReportUri?: string;
20
21
  cspReportTo?: string;
22
+ contentSecurityPolicy?: false | ContentSecurityPolicyOptions;
21
23
  strictTransportSecurity?: StrictTransportSecurityOptions;
22
24
  crossOriginOpenerPolicy?: CrossOriginOpenerPolicy;
23
25
  crossOriginResourcePolicy?: CrossOriginResourcePolicy;
24
26
  markdownTwinUrl?: string;
25
27
  }
28
+ export interface AstroCspPolicyOptions {
29
+ scriptSources?: readonly string[];
30
+ styleSources?: readonly string[];
31
+ inlineStyleAttributes?: InlineStyleAttributeInventory;
32
+ imgSources?: readonly string[];
33
+ fontSources?: readonly string[];
34
+ connectSources?: readonly string[];
35
+ mediaSources?: readonly string[];
36
+ frameSources?: readonly string[];
37
+ }
38
+ type AstroNativeCspConfig = Exclude<NonNullable<NonNullable<AstroConfig['security']>['csp']>, boolean>;
39
+ type AstroCspDirective = NonNullable<AstroNativeCspConfig['directives']>[number];
40
+ export type AstroCspResource = NonNullable<NonNullable<AstroNativeCspConfig['styleDirective']>['resources']>[number];
41
+ export interface AstroCspConfig {
42
+ algorithm: 'SHA-256';
43
+ directives: AstroCspDirective[];
44
+ scriptDirective: {
45
+ resources: string[];
46
+ };
47
+ styleDirective: {
48
+ resources: AstroCspResource[];
49
+ };
50
+ }
26
51
  export interface CachePolicyOptions {
27
52
  renderMode: RenderMode;
28
53
  audience: Audience;
29
54
  }
30
55
  export declare function buildSharedPublicationHeaders(options: HeaderPolicyOptions): Record<string, string>;
56
+ /**
57
+ * Build framework-owned CSP configuration for Astro 7.1+.
58
+ * Astro hashes every script and style it emits, while this helper keeps the
59
+ * consumer's external source inventory exact and fail-closed.
60
+ */
61
+ export declare function buildAstroCspConfig(options?: AstroCspPolicyOptions): AstroCspConfig;
31
62
  export declare function buildContentSecurityPolicy(input?: string | ContentSecurityPolicyOptions): string;
32
63
  export declare function buildStrictTransportSecurity(options?: StrictTransportSecurityOptions): string;
33
64
  export declare function buildCacheControl({ renderMode, audience }: CachePolicyOptions): string;
34
65
  export declare function normalizeHeaders(headers: HeaderMap | undefined): Record<string, string>;
35
66
  export declare function collectHeaders(headers: HeaderMap | undefined): Record<string, string[]>;
36
67
  export declare function isPrivateAudience(audience: Audience): boolean;
68
+ export {};
37
69
  //# 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,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"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
package/dist/policy.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { inlineStyleAttributeInventorySchema, } from './schema.js';
1
2
  export const REQUIRED_SECURITY_HEADERS = [
2
3
  'content-security-policy',
3
4
  'strict-transport-security',
@@ -10,11 +11,6 @@ export const REQUIRED_SECURITY_HEADERS = [
10
11
  ];
11
12
  export function buildSharedPublicationHeaders(options) {
12
13
  const headers = {
13
- 'Content-Security-Policy': buildContentSecurityPolicy({
14
- nonce: options.nonce,
15
- reportUri: options.cspReportUri,
16
- reportTo: options.cspReportTo,
17
- }),
18
14
  'Strict-Transport-Security': buildStrictTransportSecurity(options.strictTransportSecurity),
19
15
  'Cross-Origin-Opener-Policy': options.crossOriginOpenerPolicy ?? 'same-origin',
20
16
  'Cross-Origin-Resource-Policy': options.crossOriginResourcePolicy ?? 'same-origin',
@@ -24,11 +20,101 @@ export function buildSharedPublicationHeaders(options) {
24
20
  'Permissions-Policy': 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), microphone=(), payment=(), usb=()',
25
21
  'Cache-Control': buildCacheControl(options),
26
22
  };
23
+ if (options.contentSecurityPolicy !== false) {
24
+ headers['Content-Security-Policy'] = buildContentSecurityPolicy(options.contentSecurityPolicy ?? {
25
+ nonce: options.nonce,
26
+ reportUri: options.cspReportUri,
27
+ reportTo: options.cspReportTo,
28
+ });
29
+ }
27
30
  if (options.markdownTwinUrl) {
28
31
  headers.Link = `<${options.markdownTwinUrl}>; rel="alternate"; type="text/markdown"`;
29
32
  }
30
33
  return headers;
31
34
  }
35
+ /**
36
+ * Build framework-owned CSP configuration for Astro 7.1+.
37
+ * Astro hashes every script and style it emits, while this helper keeps the
38
+ * consumer's external source inventory exact and fail-closed.
39
+ */
40
+ export function buildAstroCspConfig(options = {}) {
41
+ const inlineStyleAttributes = options.inlineStyleAttributes
42
+ ? inlineStyleAttributeInventorySchema.parse(options.inlineStyleAttributes)
43
+ : undefined;
44
+ const scriptSources = exactCspSources(options.scriptSources);
45
+ const styleSources = exactCspSources(options.styleSources);
46
+ const imgSources = exactCspSources(options.imgSources);
47
+ const fontSources = exactCspSources(options.fontSources);
48
+ const connectSources = exactCspSources(options.connectSources);
49
+ const mediaSources = exactCspSources(options.mediaSources);
50
+ const frameSources = exactCspSources(options.frameSources);
51
+ return {
52
+ algorithm: 'SHA-256',
53
+ directives: [
54
+ "default-src 'self'",
55
+ cspDirective('img-src', ["'self'", 'data:', 'blob:', ...imgSources]),
56
+ cspDirective('font-src', ["'self'", 'data:', ...fontSources]),
57
+ cspDirective('connect-src', ["'self'", ...connectSources]),
58
+ cspDirective('media-src', ["'self'", 'blob:', ...mediaSources]),
59
+ cspDirective('frame-src', ["'self'", ...frameSources]),
60
+ "object-src 'none'",
61
+ "base-uri 'self'",
62
+ "frame-ancestors 'none'",
63
+ "form-action 'self'",
64
+ 'upgrade-insecure-requests',
65
+ ],
66
+ scriptDirective: {
67
+ resources: uniqueCspSources(["'self'", ...scriptSources]),
68
+ },
69
+ styleDirective: {
70
+ resources: inlineStyleAttributes
71
+ ? [
72
+ ...uniqueCspSources(["'self'", ...styleSources]).map((resource) => ({
73
+ resource,
74
+ kind: 'element',
75
+ })),
76
+ { resource: "'unsafe-inline'", kind: 'attribute' },
77
+ ]
78
+ : uniqueCspSources(["'self'", ...styleSources]),
79
+ },
80
+ };
81
+ }
82
+ function uniqueCspSources(sources) {
83
+ return [...new Set(sources)];
84
+ }
85
+ function cspDirective(name, sources) {
86
+ return `${name} ${[...new Set(sources)].join(' ')}`;
87
+ }
88
+ function exactCspSources(sources) {
89
+ return [...new Set(sources ?? [])].map((source) => {
90
+ const normalized = source.trim();
91
+ if (normalized === "'self'")
92
+ return normalized;
93
+ if (normalized === '' ||
94
+ normalized.includes('*') ||
95
+ normalized === 'https:' ||
96
+ normalized === 'http:' ||
97
+ normalized.startsWith("'unsafe-")) {
98
+ throw new Error(`Astro CSP sources must be exact and cannot use ${source}`);
99
+ }
100
+ let parsed;
101
+ try {
102
+ parsed = new URL(normalized);
103
+ }
104
+ catch {
105
+ throw new Error(`Astro CSP sources must be exact HTTPS/WSS origins: ${source}`);
106
+ }
107
+ if ((parsed.protocol !== 'https:' && parsed.protocol !== 'wss:') ||
108
+ parsed.username !== '' ||
109
+ parsed.password !== '' ||
110
+ parsed.search !== '' ||
111
+ parsed.hash !== '' ||
112
+ (parsed.pathname !== '' && parsed.pathname !== '/')) {
113
+ throw new Error(`Astro CSP sources must be exact HTTPS/WSS origins: ${source}`);
114
+ }
115
+ return parsed.origin;
116
+ });
117
+ }
32
118
  export function buildContentSecurityPolicy(input) {
33
119
  const options = typeof input === 'string' ? { nonce: input } : (input ?? {});
34
120
  const scriptSources = ["'self'"];
@@ -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,4BAA4B;IAC5B,8BAA8B;IAC9B,iBAAiB;IACjB,wBAAwB;IACxB,iBAAiB;IACjB,oBAAoB;CACX,CAAA;AAkCV,MAAM,UAAU,6BAA6B,CAC5C,OAA4B;IAE5B,MAAM,OAAO,GAA2B;QACvC,yBAAyB,EAAE,0BAA0B,CAAC;YACrD,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,OAAO,CAAC,YAAY;YAC/B,QAAQ,EAAE,OAAO,CAAC,WAAW;SAC7B,CAAC;QACF,2BAA2B,EAAE,4BAA4B,CAAC,OAAO,CAAC,uBAAuB,CAAC;QAC1F,4BAA4B,EAAE,OAAO,CAAC,uBAAuB,IAAI,aAAa;QAC9E,8BAA8B,EAAE,OAAO,CAAC,yBAAyB,IAAI,aAAa;QAClF,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,KAA6C;IACvF,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IAC5E,MAAM,aAAa,GAAG,CAAC,QAAQ,CAAC,CAAA;IAChC,MAAM,YAAY,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC/B,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACnB,aAAa,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,KAAK,GAAG,CAAC,CAAA;QAC9C,YAAY,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,KAAK,GAAG,CAAC,CAAA;IAC9C,CAAC;IAED,MAAM,UAAU,GAAG;QAClB,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,CAAA;IACD,IAAI,OAAO,CAAC,QAAQ;QAAE,UAAU,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;IACtE,IAAI,OAAO,CAAC,SAAS;QAAE,UAAU,CAAC,IAAI,CAAC,cAAc,OAAO,CAAC,SAAS,EAAE,CAAC,CAAA;IAEzE,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,UAA0C,EAAE;IACxF,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,QAAQ,CAAA;IACvD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,QAAQ,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAA;IACrF,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CACd,yFAAyF,CACzF,CAAA;IACF,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,WAAW,aAAa,EAAE,CAAC,CAAA;IAC/C,IAAI,OAAO,CAAC,iBAAiB,KAAK,IAAI;QAAE,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;IAC5E,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;QAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACxD,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAC7B,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,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,UAAU,GAA2B,EAAE,CAAA;IAC7C,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACtD,UAAU,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,UAAU,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC5D,MAAM,UAAU,GAA6B,EAAE,CAAA;IAC/C,IAAI,CAAC,OAAO;QAAE,OAAO,UAAU,CAAA;IAC/B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC/B,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,CAAA;QAC5E,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC,CAAA;QAC/E,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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"}
package/dist/runner.js CHANGED
@@ -75,7 +75,8 @@ function checkSecurityHeaders(fixture, html) {
75
75
  const headerEntries = collectHeaders(fixture.response.headers);
76
76
  const headers = normalizeHeaders(fixture.response.headers);
77
77
  const findings = [];
78
- const missingSecurityHeaders = REQUIRED_SECURITY_HEADERS.filter((name) => {
78
+ const requiredSecurityHeaders = REQUIRED_SECURITY_HEADERS.filter((name) => name !== 'x-frame-options' || fixture.expected.framePolicy.legacyHeader !== 'omit');
79
+ const missingSecurityHeaders = requiredSecurityHeaders.filter((name) => {
79
80
  const values = headerEntries[name] ?? [];
80
81
  return !values.some((value) => value.trim().length > 0);
81
82
  });
@@ -116,7 +117,30 @@ function checkSecurityHeaders(fixture, html) {
116
117
  }
117
118
  const csp = headers['content-security-policy'];
118
119
  if (csp)
119
- findings.push(...validateContentSecurityPolicy(csp, html));
120
+ findings.push(...validateContentSecurityPolicy(csp, html, fixture));
121
+ const xFrameOptions = headers['x-frame-options'];
122
+ const expectedLegacyFrameHeader = fixture.expected.framePolicy.legacyHeader;
123
+ if (expectedLegacyFrameHeader === 'omit' && xFrameOptions) {
124
+ findings.push({
125
+ code: 'security.frame_legacy_conflict',
126
+ severity: 'error',
127
+ message: 'X-Frame-Options conflicts with the declared embeddable frame-ancestors policy.',
128
+ path: 'response.headers.x-frame-options',
129
+ evidence: xFrameOptions,
130
+ remediation: 'Omit the legacy header only on the explicitly declared embeddable route and retain the exact CSP frame-ancestors directive.',
131
+ });
132
+ }
133
+ else if (expectedLegacyFrameHeader !== 'omit' &&
134
+ xFrameOptions &&
135
+ xFrameOptions.toUpperCase() !== expectedLegacyFrameHeader) {
136
+ findings.push({
137
+ code: 'security.header.invalid',
138
+ severity: 'error',
139
+ message: `X-Frame-Options must be ${expectedLegacyFrameHeader} for this response class.`,
140
+ path: 'response.headers.x-frame-options',
141
+ evidence: xFrameOptions,
142
+ });
143
+ }
120
144
  const coop = headers['cross-origin-opener-policy'];
121
145
  if (coop && !VALID_COOP_VALUES.has(coop.toLowerCase())) {
122
146
  findings.push({
@@ -310,10 +334,30 @@ function checkSecurityReceipts(fixture) {
310
334
  }
311
335
  return receipt('security-receipts', findings);
312
336
  }
313
- function validateContentSecurityPolicy(csp, html) {
337
+ function validateContentSecurityPolicy(csp, html, fixture) {
314
338
  const findings = [];
315
339
  const { directives, findings: parseFindings } = parseContentSecurityPolicy(csp);
316
340
  findings.push(...parseFindings);
341
+ const frameAncestors = directives.get('frame-ancestors');
342
+ const expectedFrameAncestors = fixture.expected.framePolicy.ancestors;
343
+ if (!frameAncestors) {
344
+ findings.push({
345
+ code: 'security.csp.frame_ancestors_missing',
346
+ severity: 'error',
347
+ message: 'Content-Security-Policy must declare frame-ancestors.',
348
+ path: 'response.headers.content-security-policy.frame-ancestors',
349
+ });
350
+ }
351
+ else if (normalizeCspSources(frameAncestors.sources) !== normalizeCspSources(expectedFrameAncestors)) {
352
+ findings.push({
353
+ code: 'security.csp.frame_ancestors_mismatch',
354
+ severity: 'error',
355
+ message: 'Content-Security-Policy frame-ancestors does not match the declared response class.',
356
+ path: 'response.headers.content-security-policy.frame-ancestors',
357
+ evidence: frameAncestors.raw,
358
+ remediation: 'Emit only the exact declared frame ancestors and keep embeddable routes explicitly bounded.',
359
+ });
360
+ }
317
361
  const defaultSources = lowerSources(directives.get('default-src')?.sources ?? []);
318
362
  if (defaultSources.includes('*')) {
319
363
  findings.push({
@@ -345,6 +389,32 @@ function validateContentSecurityPolicy(csp, html) {
345
389
  if (!directive)
346
390
  continue;
347
391
  if (lowerSources(directive.sources).includes("'unsafe-inline'")) {
392
+ if (directiveName === 'style-src-attr') {
393
+ const inventory = fixture.securityReceipts.inlineStyleAttributes;
394
+ if (!inventory) {
395
+ findings.push({
396
+ code: 'security.csp.style_attr_inline_inventory_missing',
397
+ severity: 'error',
398
+ message: "style-src-attr 'unsafe-inline' requires a commit-bound inline-style-attribute inventory receipt.",
399
+ path: 'securityReceipts.inlineStyleAttributes',
400
+ evidence: directive.raw,
401
+ remediation: 'Remove the allowance or attach the exact consumer commit SHA, positive occurrence count, and scan evidence.',
402
+ });
403
+ continue;
404
+ }
405
+ if (!fixture.buildEvidence?.commitSha ||
406
+ fixture.buildEvidence.commitSha !== inventory.commitSha) {
407
+ findings.push({
408
+ code: 'security.csp.style_attr_inline_inventory_stale',
409
+ severity: 'error',
410
+ message: 'The inline-style-attribute inventory must match the exact commit in build evidence.',
411
+ path: 'securityReceipts.inlineStyleAttributes.commitSha',
412
+ evidence: `inventory=${inventory.commitSha}; build=${fixture.buildEvidence?.commitSha ?? 'missing'}`,
413
+ remediation: 'Rescan the exact build commit before retaining the style attribute allowance.',
414
+ });
415
+ }
416
+ continue;
417
+ }
348
418
  findings.push({
349
419
  code: 'security.csp.unsafe_inline',
350
420
  severity: 'error',
@@ -437,7 +507,7 @@ function inlineExecutableScriptAllowed(script, sources) {
437
507
  return sources.some((source) => hashSourceMatchesInlineScript(source, script.content));
438
508
  }
439
509
  function hashSourceMatchesInlineScript(source, content) {
440
- const match = /^'(sha256|sha384|sha512)-([^']+)'$/i.exec(source);
510
+ const match = /^'(sha256|sha384|sha512)-([^']+)'$/.exec(source);
441
511
  if (!match)
442
512
  return false;
443
513
  const algorithm = match[1]?.toLowerCase();