@sigil-security/policy 0.0.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/dist/index.js ADDED
@@ -0,0 +1,339 @@
1
+ // src/types.ts
2
+ var DEFAULT_PROTECTED_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
3
+ var DEFAULT_ALLOWED_CONTENT_TYPES = [
4
+ "application/json",
5
+ "application/x-www-form-urlencoded",
6
+ "multipart/form-data"
7
+ ];
8
+ var DEFAULT_HEADER_NAME = "x-csrf-token";
9
+ var DEFAULT_ONESHOT_HEADER_NAME = "x-csrf-one-shot-token";
10
+ var DEFAULT_JSON_FIELD_NAME = "csrf_token";
11
+ var DEFAULT_FORM_FIELD_NAME = "csrf_token";
12
+ var DEFAULT_CONTEXT_GRACE_PERIOD_MS = 5 * 60 * 1e3;
13
+
14
+ // src/fetch-metadata.ts
15
+ var VALID_FETCH_SITE_VALUES = /* @__PURE__ */ new Set([
16
+ "same-origin",
17
+ "same-site",
18
+ "cross-site",
19
+ "none"
20
+ ]);
21
+ function createFetchMetadataPolicy(config) {
22
+ const legacyMode = config?.legacyBrowserMode ?? "degraded";
23
+ return {
24
+ name: "fetch-metadata",
25
+ validate(metadata) {
26
+ const secFetchSite = metadata.secFetchSite;
27
+ if (secFetchSite === null || secFetchSite === "") {
28
+ if (legacyMode === "strict") {
29
+ return {
30
+ allowed: false,
31
+ reason: "fetch_metadata_missing_strict"
32
+ };
33
+ }
34
+ return { allowed: true };
35
+ }
36
+ const normalized = secFetchSite.toLowerCase();
37
+ if (!VALID_FETCH_SITE_VALUES.has(normalized)) {
38
+ return {
39
+ allowed: false,
40
+ reason: `fetch_metadata_invalid_value:${normalized}`
41
+ };
42
+ }
43
+ if (normalized === "same-origin") {
44
+ return { allowed: true };
45
+ }
46
+ if (normalized === "same-site") {
47
+ return { allowed: true };
48
+ }
49
+ if (normalized === "cross-site") {
50
+ return {
51
+ allowed: false,
52
+ reason: "fetch_metadata_cross_site"
53
+ };
54
+ }
55
+ return {
56
+ allowed: false,
57
+ reason: "fetch_metadata_none"
58
+ };
59
+ }
60
+ };
61
+ }
62
+
63
+ // src/origin.ts
64
+ function extractOriginFromReferer(referer) {
65
+ try {
66
+ const url = new URL(referer);
67
+ return url.origin;
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+ function normalizeOrigin(origin) {
73
+ try {
74
+ const url = new URL(origin);
75
+ return url.origin;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ function createOriginPolicy(config) {
81
+ const normalizedAllowed = /* @__PURE__ */ new Set();
82
+ for (const o of config.allowedOrigins) {
83
+ const normalized = normalizeOrigin(o);
84
+ if (normalized !== null) {
85
+ normalizedAllowed.add(normalized);
86
+ }
87
+ }
88
+ return {
89
+ name: "origin",
90
+ validate(metadata) {
91
+ const { origin, referer } = metadata;
92
+ if (origin !== null && origin !== "") {
93
+ const normalizedOrigin = normalizeOrigin(origin);
94
+ if (normalizedOrigin !== null && normalizedAllowed.has(normalizedOrigin)) {
95
+ return { allowed: true };
96
+ }
97
+ return {
98
+ allowed: false,
99
+ reason: `origin_mismatch:${normalizedOrigin ?? origin}`
100
+ };
101
+ }
102
+ if (referer !== null && referer !== "") {
103
+ const refererOrigin = extractOriginFromReferer(referer);
104
+ if (refererOrigin === null) {
105
+ return {
106
+ allowed: false,
107
+ reason: "origin_referer_invalid"
108
+ };
109
+ }
110
+ const normalizedRefererOrigin = normalizeOrigin(refererOrigin);
111
+ if (normalizedRefererOrigin !== null && normalizedAllowed.has(normalizedRefererOrigin)) {
112
+ return { allowed: true };
113
+ }
114
+ return {
115
+ allowed: false,
116
+ reason: `origin_referer_mismatch:${normalizedRefererOrigin ?? refererOrigin}`
117
+ };
118
+ }
119
+ return {
120
+ allowed: false,
121
+ reason: "origin_missing"
122
+ };
123
+ }
124
+ };
125
+ }
126
+
127
+ // src/method.ts
128
+ var DEFAULT_PROTECTED_SET = new Set(
129
+ DEFAULT_PROTECTED_METHODS.map((m) => m.toUpperCase())
130
+ );
131
+ function createMethodPolicy(config) {
132
+ const _protectedMethods = config?.protectedMethods ? new Set(config.protectedMethods.map((m) => m.toUpperCase())) : DEFAULT_PROTECTED_SET;
133
+ return {
134
+ name: "method",
135
+ validate(_metadata) {
136
+ return { allowed: true };
137
+ }
138
+ };
139
+ }
140
+ function isProtectedMethod(method, protectedMethods) {
141
+ const methods = protectedMethods ? new Set(protectedMethods.map((m) => m.toUpperCase())) : DEFAULT_PROTECTED_SET;
142
+ return methods.has(method.toUpperCase());
143
+ }
144
+
145
+ // src/content-type.ts
146
+ function extractMimeType(contentType) {
147
+ const semicolonIndex = contentType.indexOf(";");
148
+ const mimeType = semicolonIndex >= 0 ? contentType.slice(0, semicolonIndex) : contentType;
149
+ return mimeType.trim().toLowerCase();
150
+ }
151
+ function createContentTypePolicy(config) {
152
+ const allowedTypes = new Set(
153
+ (config?.allowedContentTypes ?? DEFAULT_ALLOWED_CONTENT_TYPES).map((t) => t.toLowerCase())
154
+ );
155
+ const stateChangingMethods = new Set(DEFAULT_PROTECTED_METHODS);
156
+ return {
157
+ name: "content-type",
158
+ validate(metadata) {
159
+ const { contentType, method } = metadata;
160
+ if (contentType === null || contentType === "") {
161
+ if (stateChangingMethods.has(method.toUpperCase())) {
162
+ return {
163
+ allowed: false,
164
+ reason: "content_type_missing_on_state_change"
165
+ };
166
+ }
167
+ return { allowed: true };
168
+ }
169
+ const mimeType = extractMimeType(contentType);
170
+ if (allowedTypes.has(mimeType)) {
171
+ return { allowed: true };
172
+ }
173
+ return {
174
+ allowed: false,
175
+ reason: `content_type_disallowed:${mimeType}`
176
+ };
177
+ }
178
+ };
179
+ }
180
+
181
+ // src/mode-detection.ts
182
+ function detectClientMode(metadata, config) {
183
+ if (config?.disableClientModeOverride !== true && metadata.clientType !== void 0 && metadata.clientType.toLowerCase() === "api") {
184
+ return "api";
185
+ }
186
+ if (metadata.secFetchSite !== null && metadata.secFetchSite !== "") {
187
+ return "browser";
188
+ }
189
+ return "api";
190
+ }
191
+
192
+ // src/context-binding.ts
193
+ function evaluateContextBinding(contextMatches, config, sessionAge) {
194
+ const { tier } = config;
195
+ const gracePeriodMs = config.gracePeriodMs ?? DEFAULT_CONTEXT_GRACE_PERIOD_MS;
196
+ if (contextMatches) {
197
+ return {
198
+ matches: true,
199
+ enforced: false,
200
+ inGracePeriod: false,
201
+ tier
202
+ };
203
+ }
204
+ switch (tier) {
205
+ case "low":
206
+ return {
207
+ matches: false,
208
+ enforced: false,
209
+ inGracePeriod: false,
210
+ tier
211
+ };
212
+ case "medium": {
213
+ const inGrace = sessionAge !== void 0 && sessionAge >= 0 && sessionAge < gracePeriodMs;
214
+ return {
215
+ matches: false,
216
+ enforced: !inGrace,
217
+ // enforce only if NOT in grace period
218
+ inGracePeriod: inGrace,
219
+ tier
220
+ };
221
+ }
222
+ case "high":
223
+ return {
224
+ matches: false,
225
+ enforced: true,
226
+ inGracePeriod: false,
227
+ tier
228
+ };
229
+ }
230
+ }
231
+ function createContextBindingPolicy(_config) {
232
+ return {
233
+ name: "context-binding",
234
+ validate(_metadata) {
235
+ return { allowed: true };
236
+ }
237
+ };
238
+ }
239
+
240
+ // src/policy-chain.ts
241
+ function createPolicyChain(policies) {
242
+ return {
243
+ name: "policy-chain",
244
+ validate(metadata) {
245
+ return evaluatePolicyChain(policies, metadata);
246
+ }
247
+ };
248
+ }
249
+ function evaluatePolicyChain(policies, metadata) {
250
+ if (policies.length === 0) {
251
+ return {
252
+ allowed: false,
253
+ reason: "empty_policy_chain",
254
+ evaluated: [],
255
+ failures: []
256
+ };
257
+ }
258
+ let allAllowed = true;
259
+ let firstReason = "";
260
+ const evaluated = [];
261
+ const failures = [];
262
+ for (const policy of policies) {
263
+ evaluated.push(policy.name);
264
+ const result = policy.validate(metadata);
265
+ if (!result.allowed) {
266
+ failures.push(policy.name);
267
+ if (allAllowed) {
268
+ firstReason = result.reason;
269
+ allAllowed = false;
270
+ }
271
+ }
272
+ }
273
+ if (allAllowed) {
274
+ return {
275
+ allowed: true,
276
+ evaluated,
277
+ failures
278
+ };
279
+ }
280
+ return {
281
+ allowed: false,
282
+ reason: firstReason,
283
+ evaluated,
284
+ failures
285
+ };
286
+ }
287
+
288
+ // src/token-transport.ts
289
+ function resolveTokenTransport(metadata, _config) {
290
+ const { tokenSource } = metadata;
291
+ if (tokenSource.from === "none") {
292
+ return {
293
+ found: false,
294
+ reason: "no_token_present"
295
+ };
296
+ }
297
+ return {
298
+ found: true,
299
+ source: tokenSource,
300
+ warnings: []
301
+ };
302
+ }
303
+ function isValidTokenTransport(source) {
304
+ return source.from === "header" || source.from === "body-json" || source.from === "body-form";
305
+ }
306
+ function getTokenHeaderName(config) {
307
+ return config?.headerName ?? DEFAULT_HEADER_NAME;
308
+ }
309
+ function getTokenJsonFieldName(config) {
310
+ return config?.jsonFieldName ?? DEFAULT_JSON_FIELD_NAME;
311
+ }
312
+ function getTokenFormFieldName(config) {
313
+ return config?.formFieldName ?? DEFAULT_FORM_FIELD_NAME;
314
+ }
315
+ export {
316
+ DEFAULT_ALLOWED_CONTENT_TYPES,
317
+ DEFAULT_CONTEXT_GRACE_PERIOD_MS,
318
+ DEFAULT_FORM_FIELD_NAME,
319
+ DEFAULT_HEADER_NAME,
320
+ DEFAULT_JSON_FIELD_NAME,
321
+ DEFAULT_ONESHOT_HEADER_NAME,
322
+ DEFAULT_PROTECTED_METHODS,
323
+ createContentTypePolicy,
324
+ createContextBindingPolicy,
325
+ createFetchMetadataPolicy,
326
+ createMethodPolicy,
327
+ createOriginPolicy,
328
+ createPolicyChain,
329
+ detectClientMode,
330
+ evaluateContextBinding,
331
+ evaluatePolicyChain,
332
+ getTokenFormFieldName,
333
+ getTokenHeaderName,
334
+ getTokenJsonFieldName,
335
+ isProtectedMethod,
336
+ isValidTokenTransport,
337
+ resolveTokenTransport
338
+ };
339
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/fetch-metadata.ts","../src/origin.ts","../src/method.ts","../src/content-type.ts","../src/mode-detection.ts","../src/context-binding.ts","../src/policy-chain.ts","../src/token-transport.ts"],"sourcesContent":["// @sigil-security/policy — Types and interfaces\n// Reference: SPECIFICATION.md Sections 5, 6, 8\n\n// ============================================================\n// Token Source (how the token was transported)\n// ============================================================\n\n/**\n * Describes where a CSRF token was found in the request.\n *\n * Transport precedence (strict order per SPECIFICATION.md §8.3):\n * 1. Custom header (X-CSRF-Token)\n * 2. Request body (JSON)\n * 3. Request body (form)\n * 4. None (no token found)\n *\n * Query parameter transport is NEVER allowed.\n */\nexport type TokenSource =\n | { readonly from: 'header'; readonly value: string }\n | { readonly from: 'body-json'; readonly value: string }\n | { readonly from: 'body-form'; readonly value: string }\n | { readonly from: 'none' }\n\n// ============================================================\n// Request Metadata (normalized, framework-agnostic)\n// ============================================================\n\n/**\n * Normalized request metadata extracted from HTTP requests.\n *\n * **CRITICAL:** This is a plain object — NOT a framework-specific Request, req, res,\n * or any HTTP object. The runtime layer is responsible for extracting RequestMetadata\n * from framework objects (Express, Fastify, Hono, etc.).\n *\n * The policy layer NEVER touches raw HTTP objects.\n */\nexport interface RequestMetadata {\n /** HTTP method (uppercase: GET, POST, PUT, PATCH, DELETE, etc.) */\n readonly method: string\n\n /** Origin header value, or null if absent */\n readonly origin: string | null\n\n /** Referer header value, or null if absent */\n readonly referer: string | null\n\n /** Sec-Fetch-Site header: same-origin, same-site, cross-site, none, or null */\n readonly secFetchSite: string | null\n\n /** Sec-Fetch-Mode header: cors, navigate, no-cors, same-origin, websocket, or null */\n readonly secFetchMode: string | null\n\n /** Sec-Fetch-Dest header: document, embed, font, image, script, style, etc., or null */\n readonly secFetchDest: string | null\n\n /** Content-Type header value (without parameters), or null if absent */\n readonly contentType: string | null\n\n /** Describes how the CSRF token was transported */\n readonly tokenSource: TokenSource\n\n /** Optional: explicit client type override header (X-Client-Type) */\n readonly clientType?: string | undefined\n}\n\n// ============================================================\n// Policy Result\n// ============================================================\n\n/**\n * Result of a policy validation check.\n *\n * - `allowed: true` — request passes this policy check\n * - `allowed: false` — request fails with an internal reason (NEVER exposed to client)\n */\nexport type PolicyResult =\n | { readonly allowed: true }\n | { readonly allowed: false; readonly reason: string }\n\n// ============================================================\n// Policy Validator Interface\n// ============================================================\n\n/**\n * A single validation policy that examines request metadata.\n *\n * Policies are composable via `createPolicyChain`.\n * Each policy is a pure function of `RequestMetadata` — no side effects, no I/O.\n */\nexport interface PolicyValidator {\n /** Unique identifier for this policy (for logging/metrics) */\n readonly name: string\n\n /** Validate request metadata against this policy */\n validate(metadata: RequestMetadata): PolicyResult\n}\n\n// ============================================================\n// Configuration Types\n// ============================================================\n\n/** Legacy browser handling mode for Fetch Metadata policy */\nexport type LegacyBrowserMode = 'degraded' | 'strict'\n\n/** Configuration for Fetch Metadata policy */\nexport interface FetchMetadataConfig {\n /** How to handle requests without Fetch Metadata headers (default: 'degraded') */\n readonly legacyBrowserMode?: LegacyBrowserMode | undefined\n}\n\n/** Configuration for Origin policy */\nexport interface OriginConfig {\n /** List of allowed origins (e.g., ['https://example.com', 'https://api.example.com']) */\n readonly allowedOrigins: readonly string[]\n}\n\n/** Configuration for Method policy */\nexport interface MethodConfig {\n /** HTTP methods that require CSRF protection (default: POST, PUT, PATCH, DELETE) */\n readonly protectedMethods?: readonly string[] | undefined\n}\n\n/** Configuration for Content-Type policy */\nexport interface ContentTypeConfig {\n /** Allowed Content-Type values (default: application/json, application/x-www-form-urlencoded, multipart/form-data) */\n readonly allowedContentTypes?: readonly string[] | undefined\n}\n\n// ============================================================\n// Context Binding Types (Risk Tier Model)\n// ============================================================\n\n/** Risk tier for context binding per SPECIFICATION.md §6.2 */\nexport type RiskTier = 'low' | 'medium' | 'high'\n\n/** Configuration for context binding policy */\nexport interface ContextBindingConfig {\n /** Risk tier determining binding strictness */\n readonly tier: RiskTier\n\n /**\n * Grace period in milliseconds for session rotation tolerance.\n * Only applies to 'medium' tier (soft-fail with grace period).\n * Default: 5 minutes (300_000 ms)\n */\n readonly gracePeriodMs?: number | undefined\n}\n\n// ============================================================\n// Token Transport Types\n// ============================================================\n\n/** Configuration for token transport extraction */\nexport interface TokenTransportConfig {\n /** Custom header name (default: 'x-csrf-token') */\n readonly headerName?: string | undefined\n\n /** JSON body field name (default: 'csrf_token') */\n readonly jsonFieldName?: string | undefined\n\n /** Form body field name (default: 'csrf_token') */\n readonly formFieldName?: string | undefined\n}\n\n/** Result of token transport extraction */\nexport type TokenTransportResult =\n | { readonly found: true; readonly source: TokenSource; readonly warnings: readonly string[] }\n | { readonly found: false; readonly reason: string }\n\n// ============================================================\n// Client Mode\n// ============================================================\n\n/** Detected client mode per SPECIFICATION.md §8.2 */\nexport type ClientMode = 'browser' | 'api'\n\n// ============================================================\n// Default Constants\n// ============================================================\n\n/** Default HTTP methods requiring CSRF protection */\nexport const DEFAULT_PROTECTED_METHODS: readonly string[] = ['POST', 'PUT', 'PATCH', 'DELETE']\n\n/** Default allowed Content-Type values */\nexport const DEFAULT_ALLOWED_CONTENT_TYPES: readonly string[] = [\n 'application/json',\n 'application/x-www-form-urlencoded',\n 'multipart/form-data',\n]\n\n/** Default token header name */\nexport const DEFAULT_HEADER_NAME = 'x-csrf-token'\n\n/** Default one-shot token header name */\nexport const DEFAULT_ONESHOT_HEADER_NAME = 'x-csrf-one-shot-token'\n\n/** Default JSON body field name for CSRF token */\nexport const DEFAULT_JSON_FIELD_NAME = 'csrf_token'\n\n/** Default form body field name for CSRF token */\nexport const DEFAULT_FORM_FIELD_NAME = 'csrf_token'\n\n/** Default grace period for medium-tier context binding (5 minutes) */\nexport const DEFAULT_CONTEXT_GRACE_PERIOD_MS = 5 * 60 * 1000\n","// @sigil-security/policy — Fetch Metadata validation\n// Reference: SPECIFICATION.md §5.1, §8.4\n\nimport type {\n FetchMetadataConfig,\n LegacyBrowserMode,\n PolicyResult,\n PolicyValidator,\n RequestMetadata,\n} from './types.js'\n\n/** Valid Sec-Fetch-Site header values */\nconst VALID_FETCH_SITE_VALUES = new Set([\n 'same-origin',\n 'same-site',\n 'cross-site',\n 'none',\n])\n\n/**\n * Creates a Fetch Metadata policy validator.\n *\n * Validates requests using the `Sec-Fetch-Site` header (W3C Fetch Metadata):\n * - `same-origin` → allow\n * - `same-site` → allow (log warning for cross-origin subdomain)\n * - `cross-site` → reject (state-changing request from external origin)\n * - `none` → reject (browser extension or untrusted origin)\n * - Header absent → depends on `legacyBrowserMode`:\n * - `'degraded'` (default) → allow (fallback to Origin + Token validation)\n * - `'strict'` → reject (modern browser required)\n *\n * @param config - Optional configuration for legacy browser handling\n * @returns PolicyValidator for Fetch Metadata\n */\nexport function createFetchMetadataPolicy(config?: FetchMetadataConfig): PolicyValidator {\n const legacyMode: LegacyBrowserMode = config?.legacyBrowserMode ?? 'degraded'\n\n return {\n name: 'fetch-metadata',\n\n validate(metadata: RequestMetadata): PolicyResult {\n const secFetchSite = metadata.secFetchSite\n\n // Header absent → legacy browser or non-browser client\n if (secFetchSite === null || secFetchSite === '') {\n if (legacyMode === 'strict') {\n return {\n allowed: false,\n reason: 'fetch_metadata_missing_strict',\n }\n }\n // Degraded mode: allow, rely on other validation layers (Origin + Token)\n return { allowed: true }\n }\n\n // Normalize to lowercase for consistent comparison\n const normalized = secFetchSite.toLowerCase()\n\n // Unrecognized value → reject\n if (!VALID_FETCH_SITE_VALUES.has(normalized)) {\n return {\n allowed: false,\n reason: `fetch_metadata_invalid_value:${normalized}`,\n }\n }\n\n // same-origin → allow (trusted)\n if (normalized === 'same-origin') {\n return { allowed: true }\n }\n\n // same-site → allow (subdomain, cross-origin but same site)\n // Per SPECIFICATION.md §8.4: Allow but log (cross-origin)\n if (normalized === 'same-site') {\n return { allowed: true }\n }\n\n // cross-site → reject (external origin)\n if (normalized === 'cross-site') {\n return {\n allowed: false,\n reason: 'fetch_metadata_cross_site',\n }\n }\n\n // none → reject (browser extension or untrusted origin)\n // Per SPECIFICATION.md §8.4: Browser extension initiated requests are rejected\n // This is the last valid value in VALID_FETCH_SITE_VALUES, so no else needed\n return {\n allowed: false,\n reason: 'fetch_metadata_none',\n }\n },\n }\n}\n","// @sigil-security/policy — Origin / Referer validation\n// Reference: SPECIFICATION.md §5.2, RFC 6454\n\nimport type { OriginConfig, PolicyResult, PolicyValidator, RequestMetadata } from './types.js'\n\n/**\n * Extracts origin from a Referer URL.\n *\n * Example: \"https://example.com/path/page?q=1\" → \"https://example.com\"\n *\n * Returns null if the Referer is not a valid URL.\n */\nfunction extractOriginFromReferer(referer: string): string | null {\n try {\n const url = new URL(referer)\n return url.origin\n } catch {\n return null\n }\n}\n\n/**\n * Normalizes an origin string by removing trailing slashes and lowering the scheme/host.\n *\n * **Security (L5 fix):** Returns `null` on URL parse failure instead of a\n * fallback string comparison. A malformed origin can never match any entry\n * in `allowedOrigins`, eliminating unintentional string-level matches.\n *\n * @param origin - Origin string (e.g., \"https://Example.COM/\")\n * @returns Normalized origin (e.g., \"https://example.com\"), or null if invalid\n */\nfunction normalizeOrigin(origin: string): string | null {\n try {\n const url = new URL(origin)\n return url.origin\n } catch {\n // Invalid origin — return null so it never matches any allowed origin\n return null\n }\n}\n\n/**\n * Creates an Origin/Referer policy validator.\n *\n * Validates request provenance using Origin and Referer headers:\n * - If Origin header present → strict match against allowed origins\n * - If Origin absent → Referer header fallback (extract origin from URL)\n * - Both absent → reject (no provenance signal)\n *\n * @param config - Configuration with list of allowed origins\n * @returns PolicyValidator for Origin/Referer\n */\nexport function createOriginPolicy(config: OriginConfig): PolicyValidator {\n // Pre-normalize allowed origins — filter out invalid entries (null from parse failure)\n const normalizedAllowed = new Set<string>()\n for (const o of config.allowedOrigins) {\n const normalized = normalizeOrigin(o)\n if (normalized !== null) {\n normalizedAllowed.add(normalized)\n }\n }\n\n return {\n name: 'origin',\n\n validate(metadata: RequestMetadata): PolicyResult {\n const { origin, referer } = metadata\n\n // Try Origin header first\n if (origin !== null && origin !== '') {\n const normalizedOrigin = normalizeOrigin(origin)\n\n // null = malformed origin → automatic mismatch (L5 fix)\n if (normalizedOrigin !== null && normalizedAllowed.has(normalizedOrigin)) {\n return { allowed: true }\n }\n\n return {\n allowed: false,\n reason: `origin_mismatch:${normalizedOrigin ?? origin}`,\n }\n }\n\n // Origin absent → fallback to Referer\n if (referer !== null && referer !== '') {\n const refererOrigin = extractOriginFromReferer(referer)\n\n if (refererOrigin === null) {\n return {\n allowed: false,\n reason: 'origin_referer_invalid',\n }\n }\n\n const normalizedRefererOrigin = normalizeOrigin(refererOrigin)\n\n // null = malformed → automatic mismatch\n if (normalizedRefererOrigin !== null && normalizedAllowed.has(normalizedRefererOrigin)) {\n return { allowed: true }\n }\n\n return {\n allowed: false,\n reason: `origin_referer_mismatch:${normalizedRefererOrigin ?? refererOrigin}`,\n }\n }\n\n // Both absent → reject (no provenance signal)\n return {\n allowed: false,\n reason: 'origin_missing',\n }\n },\n }\n}\n","// @sigil-security/policy — HTTP Method filtering\n// Reference: SPECIFICATION.md §5.4\n\nimport type { MethodConfig, PolicyResult, PolicyValidator, RequestMetadata } from './types.js'\nimport { DEFAULT_PROTECTED_METHODS } from './types.js'\n\n/**\n * Pre-built Set of default protected methods for hot-path lookups.\n * Avoids creating a new Set on every `isProtectedMethod` call.\n */\nconst DEFAULT_PROTECTED_SET = new Set(\n DEFAULT_PROTECTED_METHODS.map((m) => m.toUpperCase()),\n)\n\n/**\n * Creates an HTTP Method policy validator.\n *\n * This policy acts as a **gate**: it determines whether the request's HTTP method\n * requires CSRF protection. Safe methods (GET, HEAD, OPTIONS) are allowed through\n * immediately. Protected methods (POST, PUT, PATCH, DELETE) pass the gate too —\n * the actual token validation is done by the runtime layer.\n *\n * **Usage in policy chains:** This policy never rejects. The runtime layer uses\n * `isProtectedMethod()` to decide whether to run the CSRF validation pipeline\n * at all. This policy is included in the chain for audit/metrics purposes\n * (knowing which policies were evaluated).\n *\n * @param config - Optional configuration with custom protected methods\n * @returns PolicyValidator for HTTP method classification\n */\nexport function createMethodPolicy(config?: MethodConfig): PolicyValidator {\n const _protectedMethods = config?.protectedMethods\n ? new Set(config.protectedMethods.map((m) => m.toUpperCase()))\n : DEFAULT_PROTECTED_SET\n\n return {\n name: 'method',\n\n validate(_metadata: RequestMetadata): PolicyResult {\n // This policy is a classifier, not a gatekeeper.\n // The runtime layer uses isProtectedMethod() to decide whether to\n // run the full validation pipeline. This always allows through.\n return { allowed: true }\n },\n }\n}\n\n/**\n * Checks whether an HTTP method requires CSRF protection.\n *\n * This is the primary utility used by the runtime layer to determine\n * whether to run the full policy chain + token validation for a request.\n *\n * Uses a pre-built Set for default methods to avoid per-call allocation.\n *\n * @param method - HTTP method string\n * @param protectedMethods - Custom protected methods list (default: POST, PUT, PATCH, DELETE)\n * @returns true if the method requires CSRF protection\n */\nexport function isProtectedMethod(\n method: string,\n protectedMethods?: readonly string[],\n): boolean {\n const methods = protectedMethods\n ? new Set(protectedMethods.map((m) => m.toUpperCase()))\n : DEFAULT_PROTECTED_SET\n return methods.has(method.toUpperCase())\n}\n","// @sigil-security/policy — Content-Type restriction\n// Reference: SPECIFICATION.md §5.5\n\nimport type {\n ContentTypeConfig,\n PolicyResult,\n PolicyValidator,\n RequestMetadata,\n} from './types.js'\nimport { DEFAULT_ALLOWED_CONTENT_TYPES, DEFAULT_PROTECTED_METHODS } from './types.js'\n\n/**\n * Extracts the MIME type from a Content-Type header value,\n * stripping any parameters (charset, boundary, etc.).\n *\n * Example: \"application/json; charset=utf-8\" → \"application/json\"\n * Example: \"multipart/form-data; boundary=---\" → \"multipart/form-data\"\n *\n * @param contentType - Raw Content-Type header value\n * @returns Normalized MIME type (lowercase, no parameters)\n */\nfunction extractMimeType(contentType: string): string {\n const semicolonIndex = contentType.indexOf(';')\n const mimeType = semicolonIndex >= 0 ? contentType.slice(0, semicolonIndex) : contentType\n return mimeType.trim().toLowerCase()\n}\n\n/**\n * Creates a Content-Type policy validator.\n *\n * Restricts requests to known-safe Content-Type values:\n * - `application/json` (default)\n * - `application/x-www-form-urlencoded` (default)\n * - `multipart/form-data` (default)\n *\n * **Security (L6 fix):** State-changing methods (POST, PUT, PATCH, DELETE)\n * WITHOUT a Content-Type header are now rejected. Safe methods (GET, HEAD,\n * OPTIONS) without Content-Type are still allowed (no body expected).\n *\n * Content-Type parameters (charset, boundary) are stripped before comparison.\n *\n * Per SPECIFICATION.md §8.3: Content-Type mismatch (e.g., claiming JSON but\n * sending form data) is handled by the runtime layer, not the policy layer.\n *\n * @param config - Optional configuration with custom allowed Content-Types\n * @returns PolicyValidator for Content-Type restriction\n */\nexport function createContentTypePolicy(config?: ContentTypeConfig): PolicyValidator {\n const allowedTypes = new Set(\n (config?.allowedContentTypes ?? DEFAULT_ALLOWED_CONTENT_TYPES).map((t) => t.toLowerCase()),\n )\n const stateChangingMethods = new Set(DEFAULT_PROTECTED_METHODS)\n\n return {\n name: 'content-type',\n\n validate(metadata: RequestMetadata): PolicyResult {\n const { contentType, method } = metadata\n\n // No Content-Type header\n if (contentType === null || contentType === '') {\n // State-changing methods MUST have a Content-Type (L6 fix)\n if (stateChangingMethods.has(method.toUpperCase())) {\n return {\n allowed: false,\n reason: 'content_type_missing_on_state_change',\n }\n }\n // Safe methods (GET, HEAD, OPTIONS) — allow without Content-Type\n return { allowed: true }\n }\n\n // Extract MIME type without parameters\n const mimeType = extractMimeType(contentType)\n\n if (allowedTypes.has(mimeType)) {\n return { allowed: true }\n }\n\n return {\n allowed: false,\n reason: `content_type_disallowed:${mimeType}`,\n }\n },\n }\n}\n","// @sigil-security/policy — Browser vs API Mode Detection\n// Reference: SPECIFICATION.md §8.2\n\nimport type { ClientMode, RequestMetadata } from './types.js'\n\n/**\n * Configuration for client mode detection.\n */\nexport interface ModeDetectionConfig {\n /**\n * When true, the `X-Client-Type: api` header override is disabled.\n * Clients cannot self-declare as API mode to bypass Fetch Metadata and\n * Origin validation. Mode is determined solely by `Sec-Fetch-Site` presence.\n *\n * **Security (M3 fix):** A server with permissive CORS configuration\n * (`Access-Control-Allow-Headers: *`) would allow cross-origin attackers\n * to set `X-Client-Type: api` and bypass browser-specific policies.\n * Set this to `true` if CORS cannot be tightly controlled.\n *\n * Default: `false` (override allowed for backward compatibility)\n */\n readonly disableClientModeOverride?: boolean | undefined\n}\n\n/**\n * Detects client mode (browser vs API) from request metadata.\n *\n * Mode detection logic (per SPECIFICATION.md §8.2):\n *\n * 1. Manual override: `X-Client-Type: api` → Force API Mode (unless disabled)\n * 2. `Sec-Fetch-Site` header present → Browser Mode\n * (modern browsers always send Fetch Metadata headers)\n * 3. `Sec-Fetch-Site` header absent → API Mode\n * (non-browser clients: mobile apps, CLI, services)\n *\n * **Browser Mode:**\n * - Full multi-layer validation: Fetch Metadata + Origin + Token\n * - All policies in the chain are enforced\n *\n * **API Mode:**\n * - Token-only validation (no Fetch Metadata enforcement)\n * - Context binding recommended (API key hash)\n * - Fetch Metadata and Origin policies are relaxed\n *\n * @param metadata - Normalized request metadata\n * @param config - Optional mode detection configuration\n * @returns 'browser' or 'api'\n */\nexport function detectClientMode(\n metadata: RequestMetadata,\n config?: ModeDetectionConfig,\n): ClientMode {\n // Manual override via X-Client-Type header (unless disabled)\n if (\n config?.disableClientModeOverride !== true &&\n metadata.clientType !== undefined &&\n metadata.clientType.toLowerCase() === 'api'\n ) {\n return 'api'\n }\n\n // Sec-Fetch-Site present → Browser Mode\n // Modern browsers (Chrome 76+, Firefox 90+, Edge 79+, Safari 16.4+)\n // always send this header on navigation and subresource requests\n if (metadata.secFetchSite !== null && metadata.secFetchSite !== '') {\n return 'browser'\n }\n\n // No Fetch Metadata → API Mode (non-browser client)\n return 'api'\n}\n","// @sigil-security/policy — Context Binding (Risk Tier Model)\n// Reference: SPECIFICATION.md §6.2, §6.3\n\nimport type {\n ContextBindingConfig,\n PolicyResult,\n PolicyValidator,\n RequestMetadata,\n RiskTier,\n} from './types.js'\nimport { DEFAULT_CONTEXT_GRACE_PERIOD_MS } from './types.js'\n\n/**\n * Result of context binding validation with tier-specific behavior.\n */\nexport interface ContextBindingResult {\n /** Whether the context matches */\n readonly matches: boolean\n\n /** Whether the result should be enforced (fail-closed) or logged (soft-fail) */\n readonly enforced: boolean\n\n /** Whether the request is within the grace period (medium tier only) */\n readonly inGracePeriod: boolean\n\n /** Risk tier that was applied */\n readonly tier: RiskTier\n}\n\n/**\n * Evaluates context binding based on risk tier.\n *\n * Risk Tier Model (per SPECIFICATION.md §6.2):\n *\n * | Tier | Binding | Failure Mode | Use Case |\n * |--------|----------------------|------------------------|----------------|\n * | Low | Optional / soft-fail | Log only | Read endpoints |\n * | Medium | Session ID hash | Log + allow (grace) | Settings |\n * | High | Session+User+Origin | Reject + audit | Transfers |\n *\n * @param contextMatches - Whether the context hash matches\n * @param config - Context binding configuration with risk tier\n * @param sessionAge - Age of the current session in milliseconds (for grace period)\n * @returns ContextBindingResult with tier-specific behavior\n */\nexport function evaluateContextBinding(\n contextMatches: boolean,\n config: ContextBindingConfig,\n sessionAge?: number,\n): ContextBindingResult {\n const { tier } = config\n const gracePeriodMs = config.gracePeriodMs ?? DEFAULT_CONTEXT_GRACE_PERIOD_MS\n\n if (contextMatches) {\n return {\n matches: true,\n enforced: false,\n inGracePeriod: false,\n tier,\n }\n }\n\n // Context does NOT match — behavior depends on tier\n switch (tier) {\n case 'low':\n // Low assurance: soft-fail, log only, allow the request\n return {\n matches: false,\n enforced: false,\n inGracePeriod: false,\n tier,\n }\n\n case 'medium': {\n // Medium assurance: soft-fail with grace period\n // If session was recently rotated, allow within grace period\n const inGrace =\n sessionAge !== undefined && sessionAge >= 0 && sessionAge < gracePeriodMs\n return {\n matches: false,\n enforced: !inGrace, // enforce only if NOT in grace period\n inGracePeriod: inGrace,\n tier,\n }\n }\n\n case 'high':\n // High assurance: fail-closed, no grace period\n return {\n matches: false,\n enforced: true,\n inGracePeriod: false,\n tier,\n }\n }\n}\n\n/**\n * Creates a context binding policy validator.\n *\n * This policy checks whether context binding validation should result in\n * a hard rejection. For low-tier endpoints, context mismatch is logged\n * but allowed. For high-tier, it's a hard reject.\n *\n * **Note:** The actual context hash comparison is performed by `@sigil-security/core`.\n * This policy determines the *enforcement behavior* based on the risk tier.\n *\n * Since the policy layer doesn't have access to token internals, this validator\n * works with pre-computed context match results passed via metadata extensions.\n *\n * @param config - Context binding configuration\n * @returns PolicyValidator for context binding enforcement\n */\nexport function createContextBindingPolicy(_config: ContextBindingConfig): PolicyValidator {\n return {\n name: 'context-binding',\n\n validate(_metadata: RequestMetadata): PolicyResult {\n // Context binding validation is tier-dependent and requires\n // context match information that comes from core token validation.\n //\n // The actual enforcement is done by `evaluateContextBinding()` at\n // the runtime layer after core validation provides the match result.\n //\n // This policy always allows — the runtime layer uses\n // `evaluateContextBinding()` for the actual decision.\n //\n // This exists in the policy chain primarily as a marker/placeholder\n // that context binding is configured for this endpoint.\n return { allowed: true }\n },\n }\n}\n","// @sigil-security/policy — Policy Composition (no short-circuit)\n// Reference: SPECIFICATION.md §5.8 (Deterministic Failure Model)\n\nimport type { PolicyResult, PolicyValidator, RequestMetadata } from './types.js'\n\n/**\n * Result of a policy chain evaluation.\n *\n * Includes all PolicyResult fields plus metadata about which policies\n * were evaluated and which ones failed (for internal logging only).\n */\nexport type PolicyChainResult =\n | {\n readonly allowed: true\n readonly evaluated: readonly string[]\n readonly failures: readonly string[]\n }\n | {\n readonly allowed: false\n readonly reason: string\n readonly evaluated: readonly string[]\n readonly failures: readonly string[]\n }\n\n/**\n * Creates a composite policy validator from multiple individual policies.\n *\n * **CRITICAL:** All policies in the chain are executed regardless of individual\n * results. There is NO short-circuit evaluation. This follows the Deterministic\n * Failure Model from SPECIFICATION.md §5.8:\n *\n * - Every policy runs, even if an earlier one fails\n * - First failure reason is captured (for internal logging)\n * - All failure names are collected (for metrics)\n * - Single exit point, deterministic execution path\n *\n * @param policies - Array of PolicyValidator instances to compose\n * @returns A composite PolicyValidator that runs all policies\n */\nexport function createPolicyChain(policies: readonly PolicyValidator[]): PolicyValidator {\n return {\n name: 'policy-chain',\n\n validate(metadata: RequestMetadata): PolicyResult {\n return evaluatePolicyChain(policies, metadata)\n },\n }\n}\n\n/**\n * Evaluates a chain of policies against request metadata.\n *\n * Returns a detailed result including all evaluated and failed policy names.\n * No short-circuit: ALL policies execute regardless of individual results.\n *\n * **Security (M4 fix):** An empty policy chain fails closed. A configuration\n * bug that produces an empty chain MUST NOT silently approve all requests.\n *\n * @param policies - Array of policies to evaluate\n * @param metadata - Normalized request metadata\n * @returns Detailed chain evaluation result\n */\nexport function evaluatePolicyChain(\n policies: readonly PolicyValidator[],\n metadata: RequestMetadata,\n): PolicyChainResult {\n // Fail closed on empty policy chain — prevent accidental misconfiguration\n // from silently approving all requests\n if (policies.length === 0) {\n return {\n allowed: false,\n reason: 'empty_policy_chain',\n evaluated: [],\n failures: [],\n }\n }\n\n let allAllowed = true\n let firstReason = ''\n const evaluated: string[] = []\n const failures: string[] = []\n\n // Execute ALL policies — no short-circuit (deterministic timing)\n for (const policy of policies) {\n evaluated.push(policy.name)\n const result = policy.validate(metadata)\n\n if (!result.allowed) {\n failures.push(policy.name)\n\n if (allAllowed) {\n // Capture first failure reason (for internal logging)\n firstReason = result.reason\n allAllowed = false\n }\n }\n }\n\n if (allAllowed) {\n return {\n allowed: true,\n evaluated,\n failures,\n }\n }\n\n return {\n allowed: false,\n reason: firstReason,\n evaluated,\n failures,\n }\n}\n","// @sigil-security/policy — Token Transport Precedence\n// Reference: SPECIFICATION.md §8.3\n\nimport type {\n RequestMetadata,\n TokenSource,\n TokenTransportConfig,\n TokenTransportResult,\n} from './types.js'\nimport {\n DEFAULT_FORM_FIELD_NAME,\n DEFAULT_HEADER_NAME,\n DEFAULT_JSON_FIELD_NAME,\n} from './types.js'\n\n/**\n * Resolves token transport from request metadata.\n *\n * Transport precedence (strict order per SPECIFICATION.md §8.3):\n *\n * 1. **Custom Header** (recommended): `X-CSRF-Token`\n * 2. **Request Body** (JSON): `{ \"csrf_token\": \"...\" }`\n * 3. **Request Body** (form): `csrf_token=...`\n * 4. **Query Parameter**: NEVER allowed (deprecated, insecure — reject with warning)\n *\n * Rules:\n * - First valid token found is used\n * - Multiple tokens → first match wins, warning logged\n * - Token source is captured for audit logging\n *\n * @param metadata - Normalized request metadata with token source\n * @param _config - Optional transport configuration\n * @returns TokenTransportResult with found token and any warnings\n */\nexport function resolveTokenTransport(\n metadata: RequestMetadata,\n _config?: TokenTransportConfig,\n): TokenTransportResult {\n const { tokenSource } = metadata\n\n // Token already extracted by the runtime layer and normalized into TokenSource\n if (tokenSource.from === 'none') {\n return {\n found: false,\n reason: 'no_token_present',\n }\n }\n\n // Token found from a valid source\n return {\n found: true,\n source: tokenSource,\n warnings: [],\n }\n}\n\n/**\n * Validates that a token source is acceptable.\n *\n * Verifies the token was transported via an approved channel:\n * - Header: always acceptable\n * - Body (JSON or form): acceptable\n * - Query parameter: NEVER acceptable\n *\n * @param source - The token source to validate\n * @returns true if the transport method is acceptable\n */\nexport function isValidTokenTransport(source: TokenSource): boolean {\n return source.from === 'header' || source.from === 'body-json' || source.from === 'body-form'\n}\n\n/**\n * Returns the expected header name for CSRF tokens.\n *\n * @param config - Optional transport configuration\n * @returns Header name (lowercase)\n */\nexport function getTokenHeaderName(config?: TokenTransportConfig): string {\n return config?.headerName ?? DEFAULT_HEADER_NAME\n}\n\n/**\n * Returns the expected JSON field name for CSRF tokens.\n *\n * @param config - Optional transport configuration\n * @returns JSON field name\n */\nexport function getTokenJsonFieldName(config?: TokenTransportConfig): string {\n return config?.jsonFieldName ?? DEFAULT_JSON_FIELD_NAME\n}\n\n/**\n * Returns the expected form field name for CSRF tokens.\n *\n * @param config - Optional transport configuration\n * @returns Form field name\n */\nexport function getTokenFormFieldName(config?: TokenTransportConfig): string {\n return config?.formFieldName ?? DEFAULT_FORM_FIELD_NAME\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@sigil-security/policy",
3
+ "version": "0.0.0",
4
+ "description": "Validation policies for request metadata — Fetch Metadata, Origin, context binding",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "require": {
13
+ "types": "./dist/index.d.cts",
14
+ "default": "./dist/index.cjs"
15
+ }
16
+ }
17
+ },
18
+ "main": "./dist/index.cjs",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "keywords": [
25
+ "csrf",
26
+ "security",
27
+ "policy",
28
+ "fetch-metadata",
29
+ "origin"
30
+ ],
31
+ "license": "Apache-2.0",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/laphilosophia/sigil-security.git",
35
+ "directory": "packages/policy"
36
+ },
37
+ "dependencies": {
38
+ "@sigil-security/core": "0.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "tsup": "^8.0.0",
42
+ "typescript": "^5.7.0"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "typecheck": "tsc --noEmit"
50
+ }
51
+ }