@fuzdev/fuz_app 0.139.0 → 0.141.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.
Files changed (182) hide show
  1. package/dist/actions/CLAUDE.md +62 -17
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +25 -2
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +8 -1
  8. package/dist/actions/register_action_ws.d.ts +42 -6
  9. package/dist/actions/register_action_ws.d.ts.map +1 -1
  10. package/dist/actions/register_action_ws.js +87 -19
  11. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  12. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  13. package/dist/actions/register_ws_endpoint.js +5 -2
  14. package/dist/actions/socket.svelte.d.ts +19 -4
  15. package/dist/actions/socket.svelte.d.ts.map +1 -1
  16. package/dist/actions/socket.svelte.js +31 -4
  17. package/dist/actions/transports.d.ts +30 -1
  18. package/dist/actions/transports.d.ts.map +1 -1
  19. package/dist/actions/transports.js +30 -1
  20. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  21. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  22. package/dist/actions/transports_ws_auth_guard.js +24 -10
  23. package/dist/actions/transports_ws_backend.d.ts +51 -5
  24. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_backend.js +62 -9
  26. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  27. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  28. package/dist/auth/CLAUDE.md +11 -9
  29. package/dist/auth/account_route_schema.d.ts +1 -0
  30. package/dist/auth/account_route_schema.d.ts.map +1 -1
  31. package/dist/auth/account_route_schema.js +4 -0
  32. package/dist/auth/api_token.d.ts +20 -0
  33. package/dist/auth/api_token.d.ts.map +1 -1
  34. package/dist/auth/api_token.js +21 -1
  35. package/dist/auth/api_token_queries.d.ts +22 -4
  36. package/dist/auth/api_token_queries.d.ts.map +1 -1
  37. package/dist/auth/api_token_queries.js +11 -6
  38. package/dist/auth/audit_log_routes.d.ts +10 -0
  39. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  40. package/dist/auth/audit_log_routes.js +20 -0
  41. package/dist/auth/audit_log_schema.d.ts +30 -5
  42. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  43. package/dist/auth/audit_log_schema.js +30 -0
  44. package/dist/auth/bearer_auth.d.ts +4 -2
  45. package/dist/auth/bearer_auth.d.ts.map +1 -1
  46. package/dist/auth/bearer_auth.js +19 -8
  47. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  48. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_route_schema.js +1 -0
  50. package/dist/auth/cleanup.d.ts +9 -9
  51. package/dist/auth/cleanup.js +9 -9
  52. package/dist/auth/credential_deadline.d.ts +95 -0
  53. package/dist/auth/credential_deadline.d.ts.map +1 -0
  54. package/dist/auth/credential_deadline.js +139 -0
  55. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  56. package/dist/auth/daemon_token_middleware.js +4 -1
  57. package/dist/auth/middleware.d.ts +90 -12
  58. package/dist/auth/middleware.d.ts.map +1 -1
  59. package/dist/auth/middleware.js +115 -16
  60. package/dist/auth/request_context.d.ts +4 -2
  61. package/dist/auth/request_context.d.ts.map +1 -1
  62. package/dist/auth/request_context.js +14 -3
  63. package/dist/auth/resolved_auth.d.ts +13 -2
  64. package/dist/auth/resolved_auth.d.ts.map +1 -1
  65. package/dist/auth/resolved_auth.js +5 -4
  66. package/dist/auth/session_cookie.d.ts +4 -0
  67. package/dist/auth/session_cookie.d.ts.map +1 -1
  68. package/dist/auth/session_cookie.js +3 -2
  69. package/dist/auth/session_middleware.d.ts +1 -1
  70. package/dist/auth/session_middleware.d.ts.map +1 -1
  71. package/dist/auth/session_middleware.js +4 -1
  72. package/dist/auth/session_queries.d.ts +19 -2
  73. package/dist/auth/session_queries.d.ts.map +1 -1
  74. package/dist/auth/session_queries.js +8 -2
  75. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  76. package/dist/auth/signup_route_schema.js +1 -0
  77. package/dist/hono_context.d.ts +23 -0
  78. package/dist/hono_context.d.ts.map +1 -1
  79. package/dist/hono_context.js +19 -0
  80. package/dist/http/CLAUDE.md +41 -15
  81. package/dist/http/middleware_spec.d.ts +16 -0
  82. package/dist/http/middleware_spec.d.ts.map +1 -1
  83. package/dist/http/origin.d.ts +113 -18
  84. package/dist/http/origin.d.ts.map +1 -1
  85. package/dist/http/origin.js +247 -127
  86. package/dist/http/route_spec.d.ts +21 -0
  87. package/dist/http/route_spec.d.ts.map +1 -1
  88. package/dist/http/schema_helpers.d.ts +22 -0
  89. package/dist/http/schema_helpers.d.ts.map +1 -1
  90. package/dist/http/schema_helpers.js +24 -0
  91. package/dist/http/surface.d.ts +6 -1
  92. package/dist/http/surface.d.ts.map +1 -1
  93. package/dist/http/surface.js +12 -5
  94. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  95. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  96. package/dist/realtime/sse_auth_guard.js +31 -12
  97. package/dist/realtime/subscriber_registry.d.ts +10 -1
  98. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  99. package/dist/realtime/subscriber_registry.js +10 -1
  100. package/dist/server/app_server.d.ts +20 -7
  101. package/dist/server/app_server.d.ts.map +1 -1
  102. package/dist/server/app_server.js +8 -3
  103. package/dist/server/env.d.ts +2 -1
  104. package/dist/server/env.d.ts.map +1 -1
  105. package/dist/server/env.js +7 -17
  106. package/dist/server/serve_fact_route.d.ts.map +1 -1
  107. package/dist/server/serve_fact_route.js +1 -0
  108. package/dist/testing/CLAUDE.md +154 -22
  109. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  110. package/dist/testing/adversarial_headers.js +13 -6
  111. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  112. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  113. package/dist/testing/connection_closer_helpers.js +4 -0
  114. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/body_size.js +14 -65
  116. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  117. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  118. package/dist/testing/cross_backend/capabilities.js +9 -3
  119. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  120. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  121. package/dist/testing/cross_backend/conformance_case.js +6 -0
  122. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/conformance_table.js +20 -19
  124. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  125. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  126. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  127. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  129. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  130. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  132. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  133. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  135. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/origin.js +6 -26
  137. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  138. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  139. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  140. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  141. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  142. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/origin_scope.js +140 -0
  144. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  145. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  147. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  148. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  150. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  152. package/dist/testing/cross_backend/spine_surface_constants.d.ts +19 -0
  153. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/spine_surface_constants.js +22 -0
  155. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  156. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  157. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  158. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  159. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  160. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  161. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  162. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  163. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  164. package/dist/testing/cross_backend/transport_composition.js +364 -0
  165. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  166. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  167. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  168. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  169. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  170. package/dist/testing/entities.d.ts +23 -0
  171. package/dist/testing/entities.d.ts.map +1 -1
  172. package/dist/testing/entities.js +37 -0
  173. package/dist/testing/integration.d.ts.map +1 -1
  174. package/dist/testing/integration.js +3 -1
  175. package/dist/testing/middleware.d.ts +10 -0
  176. package/dist/testing/middleware.d.ts.map +1 -1
  177. package/dist/testing/middleware.js +31 -7
  178. package/dist/testing/stubs.d.ts +16 -3
  179. package/dist/testing/stubs.d.ts.map +1 -1
  180. package/dist/testing/stubs.js +20 -7
  181. package/dist/testing/surface_invariants.js +2 -2
  182. package/package.json +12 -12
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Request source verification middleware for API protection.
3
3
  *
4
- * Verifies requests are coming from expected origins/referers.
4
+ * Verifies requests carry an `Origin` the server allows, or none.
5
5
  * CSRF protection is `SameSite: strict` on session cookies
6
6
  * (see `auth/session_middleware.ts`) plus the CORS simple-request refusal
7
7
  * (`http/simple_request.ts`) — `SameSite` alone admits same-site pages, which
@@ -10,10 +10,18 @@
10
10
  * from making requests as the user browses the web — and is the only gate on
11
11
  * the WebSocket upgrade, which is never preflighted.
12
12
  *
13
+ * The allowlist grammar is shared with the Rust spine entry for entry:
14
+ * `parse_allowed_origins` accepts and refuses the same entries, naming each
15
+ * refusal with the same `AllowedOriginsErrorKind` string, and an accepted list
16
+ * matches the same origins. The shared conformance table
17
+ * (`testing/cross_backend/origin_conformance.json`) pins both.
18
+ *
13
19
  * @module
14
20
  */
15
21
  import { escape_regexp } from '@fuzdev/fuz_util/regexp.ts';
22
+ import { z } from 'zod';
16
23
  import { ERROR_FORBIDDEN_ORIGIN } from './error_schemas.js';
24
+ import { middleware_applies } from './schema_helpers.js';
17
25
  /**
18
26
  * True when the request looks like it originated from a browser context — it
19
27
  * carries an `Origin` or `Referer` header. Browsers attach these
@@ -32,37 +40,134 @@ import { ERROR_FORBIDDEN_ORIGIN } from './error_schemas.js';
32
40
  */
33
41
  export const is_browser_context = (c) => c.req.header('Origin') !== undefined || c.req.header('Referer') !== undefined;
34
42
  /**
35
- * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
43
+ * Why one allowlist entry was refused. The wire names both spines emit — the
44
+ * Rust spine's `OriginPatternError::kind` returns the same strings.
45
+ */
46
+ export const OriginPatternErrorKind = z.enum([
47
+ 'empty_entry',
48
+ 'not_printable_ascii',
49
+ 'missing_scheme',
50
+ 'unsupported_scheme',
51
+ 'has_path',
52
+ 'has_userinfo',
53
+ 'missing_host',
54
+ 'invalid_host_char',
55
+ 'unbracketed_ipv6',
56
+ 'partial_wildcard',
57
+ 'non_canonical_ipv4',
58
+ 'unclosed_ipv6',
59
+ 'invalid_ipv6',
60
+ 'invalid_after_ipv6',
61
+ 'invalid_port',
62
+ 'default_port'
63
+ ]);
64
+ /** Why an allowlist value was refused: `no_origins` (a blank value) or an entry's kind. */
65
+ export const AllowedOriginsErrorKind = z.enum(['no_origins', ...OriginPatternErrorKind.options]);
66
+ /** The operator-facing reason for each refusal kind. */
67
+ export const ALLOWED_ORIGINS_ERROR_MESSAGES = {
68
+ no_origins: 'no origins supplied — list at least one',
69
+ empty_entry: 'it is empty (a stray comma?)',
70
+ not_printable_ascii: 'an origin is printable ASCII with no spaces (an international hostname is written in its punycode form)',
71
+ missing_scheme: 'it has no `scheme://`',
72
+ unsupported_scheme: 'the scheme is neither http nor https',
73
+ has_path: 'an origin ends at the host or port — no path, query, or fragment, and no trailing slash',
74
+ has_userinfo: 'an origin carries no userinfo',
75
+ missing_host: 'it has no host',
76
+ invalid_host_char: 'a hostname holds none of `%`, `\\`, `^`, `|`, `<`, `>`, `[`, `]` (a browser sends it decoded, with no percent-encoding)',
77
+ unbracketed_ipv6: 'an IPv6 address is written in brackets, `[::1]`',
78
+ partial_wildcard: 'a `*` stands for one whole host label, as in `https://*.example.com`',
79
+ non_canonical_ipv4: 'a host ending in a number is an IPv4 address, which a browser sends as four decimal numbers 0-255 with no leading zeros and no trailing dot',
80
+ unclosed_ipv6: 'the IPv6 address has no closing `]`',
81
+ invalid_ipv6: 'the bracketed host is not an IPv6 address written the way a browser sends it: hex groups without leading zeros, the longest run of zero groups as `::`, no dotted-quad tail, no zone',
82
+ invalid_after_ipv6: 'only `:port` may follow the IPv6 address',
83
+ invalid_port: 'the port is neither a number in 1-65535 (no leading zeros) nor `*`',
84
+ default_port: "it names the scheme's default port, which a browser leaves out of `Origin` and `Host` — write it without the port"
85
+ };
86
+ /** The longest excerpt of a refused entry an error message quotes. */
87
+ const ENTRY_EXCERPT_MAX = 200;
88
+ /**
89
+ * Render one refusal for a boot error — the entry in double quotes with every
90
+ * character but visible ASCII and the space escaped as `\uXXXX` (and `"` and
91
+ * `\` backslash-escaped), so an env value can't put a control, bidi, or other
92
+ * non-printing character in a log, nor close the quotes it is shown in.
93
+ *
94
+ * @param error - one of `parse_allowed_origins`'s errors
95
+ * @returns the reason as one line
96
+ */
97
+ export const format_allowed_origins_error = (error) => {
98
+ const reason = ALLOWED_ORIGINS_ERROR_MESSAGES[error.kind];
99
+ if (error.index === undefined)
100
+ return reason;
101
+ const entry = error.entry ?? '';
102
+ const excerpt = entry.length > ENTRY_EXCERPT_MAX ? entry.slice(0, ENTRY_EXCERPT_MAX) + '...' : entry;
103
+ return `entry ${error.index} ${quote_ascii(excerpt)} is refused: ${reason}`;
104
+ };
105
+ /** Everything outside visible ASCII and the space, plus the quote and backslash. */
106
+ const NEEDS_ASCII_ESCAPE = /[^\x20-\x7e]|["\\]/g;
107
+ /**
108
+ * `value` in double quotes, ASCII-only: `"` and `\` backslash-escaped, every
109
+ * other UTF-16 code unit outside `\x20-\x7e` as `\uXXXX`.
110
+ */
111
+ const quote_ascii = (value) => '"' +
112
+ value.replace(NEEDS_ASCII_ESCAPE, (c) => c === '"' || c === '\\' ? '\\' + c : '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')) +
113
+ '"';
114
+ /** Printable ASCII without the space. */
115
+ const VISIBLE_ASCII = /^[\x21-\x7e]*$/;
116
+ /** The ASCII whitespace an entry is trimmed of — space, tab, LF, FF, CR. */
117
+ const ASCII_WHITESPACE_EDGES = /^[ \t\n\f\r]+|[ \t\n\f\r]+$/g;
118
+ const trim_ascii_whitespace = (value) => value.replace(ASCII_WHITESPACE_EDGES, '');
119
+ /**
120
+ * Parses an allowlist value (conventionally the `FUZ_ALLOWED_ORIGINS` env var)
121
+ * into regex matchers for request source verification.
36
122
  * Origin allowlisting for locally-running services — not the CSRF layer
37
123
  * (that's `SameSite: strict` on session cookies plus `http/simple_request.ts`).
38
124
  *
39
- * Accepts comma-separated patterns with limited wildcards:
40
- * - Exact origins: `https://api.fuz.dev`
41
- * - Wildcard subdomains: `https://*.fuz.dev` (matches exactly one subdomain level)
42
- * - Multiple wildcards: `https://*.staging.*.fuz.dev` (for deep subdomains)
43
- * - Wildcard ports: `http://localhost:*` (matches any port or no port)
44
- * - IPv6 addresses: `http://[::1]:3000`, `https://[2001:db8::1]`
125
+ * Entries are comma-separated and trimmed of ASCII whitespace. Each is written
126
+ * the way a browser writes an `Origin` — `scheme://host[:port]` — where a whole
127
+ * host label or the port may be `*`:
128
+ * - Exact origins: `https://api.fuz.dev`, `http://localhost:3000`
129
+ * - Wildcard labels: `https://*.fuz.dev` (each `*` is exactly one label, in any
130
+ * position — `api.fuz.dev` matches, `fuz.dev` and `a.b.fuz.dev` don't)
131
+ * - Wildcard ports: `http://localhost:*` (any port or none)
132
+ * - IPv6 addresses: `http://[::1]:3000`, `https://[2001:db8::1]` (exact)
45
133
  * - Combined: `https://*.fuz.dev:*`
46
134
  *
47
- * Examples:
48
- * - `http://localhost:3000,https://prod.fuz.dev`
49
- * - `https://*.api.fuz.dev,http://127.0.0.1:*`
50
- * - `http://[::1]:*,https://*.*.corp.fuz.dev:*`
135
+ * Refused, nothing normalized: anything but printable ASCII (punycode an
136
+ * international hostname), a scheme other than http or https (`null`,
137
+ * `file://`, `chrome-extension://`), a path, query, fragment or trailing
138
+ * slash, userinfo, a character no hostname holds, a partial wildcard, an IPv4
139
+ * address not written as four canonical decimal numbers (`127.1`), an IPv6
140
+ * address not in the form a browser sends, a port outside `1..=65535` or with
141
+ * leading zeros, the scheme's default port, and an empty entry. Every refused
142
+ * entry is reported, not just the first.
51
143
  *
52
- * @throws Error if any individual pattern is invalid (missing protocol, partial wildcards, etc.)
144
+ * @param raw - the comma-separated allowlist value
145
+ * @returns the matchers, or every refusal — `no_origins` when the value is blank
53
146
  */
54
- export const parse_allowed_origins = (env_value) => env_value
55
- ? env_value
56
- .split(',')
57
- .map((s) => s.trim())
58
- .filter(Boolean)
59
- .map(origin_pattern_to_regexp)
60
- : [];
147
+ export const parse_allowed_origins = (raw) => {
148
+ if (trim_ascii_whitespace(raw) === '') {
149
+ return { ok: false, errors: [{ kind: 'no_origins' }] };
150
+ }
151
+ const origins = [];
152
+ const errors = [];
153
+ raw.split(',').forEach((part, i) => {
154
+ const entry = trim_ascii_whitespace(part);
155
+ const parsed = origin_pattern_to_regexp(entry);
156
+ if (typeof parsed === 'string') {
157
+ errors.push({ kind: parsed, index: i + 1, entry });
158
+ }
159
+ else {
160
+ origins.push(parsed);
161
+ }
162
+ });
163
+ return errors.length > 0 ? { ok: false, errors } : { ok: true, origins };
164
+ };
61
165
  /**
62
- * Tests if a request source (origin or referer) matches any of the allowed patterns.
63
- * Pattern matching is case-insensitive for domains (as per web standards).
166
+ * Tests if an `Origin` value matches any of the allowed patterns.
167
+ * Pattern matching is case-insensitive (web hosts and schemes are). A value
168
+ * that isn't visible ASCII matches nothing — a browser never sends one.
64
169
  */
65
- export const should_allow_origin = (origin, allowed_patterns) => allowed_patterns.some((p) => p.test(origin));
170
+ export const should_allow_origin = (origin, allowed_patterns) => VISIBLE_ASCII.test(origin) && allowed_patterns.some((p) => p.test(origin));
66
171
  /**
67
172
  * Middleware that verifies the request source against an allowlist.
68
173
  *
@@ -70,6 +175,9 @@ export const should_allow_origin = (origin, allowed_patterns) => allowed_pattern
70
175
  * plus `http/simple_request.ts`):
71
176
  * - Checks the `Origin` header (if present) against the allowlist
72
177
  * - Allows requests without an `Origin` header (direct access, curl, etc.)
178
+ * - Refuses an `Origin` that isn't visible ASCII, and a duplicated one — the
179
+ * runtimes join a repeated header into one value (`a, b`), which carries a
180
+ * space and so matches nothing
73
181
  *
74
182
  * Origin-only by design — Fetch spec mandates `Origin` on every unsafe
75
183
  * method (POST / PUT / DELETE / PATCH) regardless of `Referrer-Policy`,
@@ -78,126 +186,138 @@ export const should_allow_origin = (origin, allowed_patterns) => allowed_pattern
78
186
  * attached session cookies, so CSRF isn't the relevant threat there —
79
187
  * auth (bearer / daemon token) is the actual control. A Referer fallback
80
188
  * would only widen the accepted-shape envelope without closing a real
81
- * CSRF hole; mirrors `zzz_server::auth::is_request_origin_allowed`.
189
+ * CSRF hole. The Rust spine's `origin_layer` is the twin.
190
+ *
191
+ * `create_app_server` mounts it on every path (`ORIGIN_MIDDLEWARE_PATH`); a
192
+ * request whose path a `carve_outs` entry matches passes unchecked — the named,
193
+ * explicit exception, each with its reason, as the Rust stack's
194
+ * `origin_carve_out` is.
82
195
  *
83
196
  * @param allowed_patterns - compiled regex patterns from `parse_allowed_origins`
197
+ * @param carve_outs - paths the check skips, matched like a middleware path (`middleware_applies`)
84
198
  */
85
- export const verify_request_source = (allowed_patterns) => (c, next) => {
86
- // Check origin header (preferred, sent by browsers for CORS requests).
87
- // Uses !== undefined so empty-string Origin headers are treated as
88
- // present (abnormal, checked against allowlist — empty string never matches).
89
- const origin = c.req.header('origin');
90
- if (origin !== undefined) {
91
- if (!should_allow_origin(origin, allowed_patterns)) {
92
- return c.json({ error: ERROR_FORBIDDEN_ORIGIN }, 403);
93
- }
199
+ export const verify_request_source = (allowed_patterns, carve_outs = []) => (c, next) => {
200
+ if (carve_outs.some((carve_out) => middleware_applies(carve_out.path, c.req.path))) {
94
201
  return next();
95
202
  }
96
- // No origin header - direct access (curl, CLI, etc.)
97
- // Allow through since token auth is the primary security control.
203
+ // `!== undefined` so an empty-string Origin is present (abnormal, checked
204
+ // against the allowlist — the empty string never matches)
205
+ const origin = c.req.header('origin');
206
+ if (origin !== undefined && !should_allow_origin(origin, allowed_patterns)) {
207
+ return c.json({ error: ERROR_FORBIDDEN_ORIGIN }, 403);
208
+ }
209
+ // no Origin — direct access (curl, CLI); token auth is the control
98
210
  return next();
99
211
  };
100
212
  /**
101
- * Converts origin patterns with wildcards to regex patterns.
102
- *
103
- * Pattern format: protocol://hostname[:port]
104
- *
105
- * Wildcard support:
106
- * - Subdomain wildcards: `*.fuz.dev` matches `sub.fuz.dev` (NOT `fuz.dev`)
107
- * - Multiple wildcards: `*.*.fuz.dev` matches `api.staging.fuz.dev`
108
- * - Port wildcards: `fuz.dev:*` matches any port or no port
109
- * - IPv6 support: `[::1]`, `[2001:db8::1]` (no wildcards in IPv6)
213
+ * Validates one trimmed entry and compiles it to a matcher, or names its
214
+ * refusal. The checks run in a fixed order and the first that fails names the
215
+ * kind — the Rust spine runs the same order, so both name the same kind.
110
216
  *
111
- * Restrictions:
112
- * - No paths allowed (origins don't include paths)
113
- * - Wildcards must be complete labels (`*.fuz.dev`, not `*fuz.dev`)
114
- * - No wildcards in IPv6 addresses
115
- * - Port wildcards must be `:*` exactly
116
- *
117
- * Note: Patterns are normalized via URL constructor. IPv4-mapped IPv6 addresses
118
- * like `[::ffff:127.0.0.1]` will be normalized to `[::ffff:7f00:1]`. IPv6 zone
119
- * identifiers (e.g., `%eth0`) are not supported.
120
- *
121
- * @throws Error if pattern format is invalid
217
+ * The compiled regex is `^scheme://host(:port)?$`, case-insensitive: an exact
218
+ * label matches itself, a `*` label `[^./:]+` (one non-empty label), a `:*`
219
+ * port `(:\d+)?`.
122
220
  */
123
- const origin_pattern_to_regexp = (pattern) => {
124
- // Quick validation: no paths, query strings, or fragments allowed
125
- const protocol_idx = pattern.indexOf('://');
126
- if (protocol_idx === -1) {
127
- throw new Error(`Invalid origin pattern: ${pattern}`);
128
- }
129
- const after_protocol = pattern.slice(protocol_idx + 3);
130
- if (/[/?#]/.test(after_protocol)) {
131
- throw new Error(`Paths not allowed in origin patterns: ${pattern}`);
132
- }
133
- // Check for wildcards in IPv6 before URL parsing (URL rejects these with unhelpful error)
134
- const ipv6_match = /^\[([^\]]+)\]/.exec(after_protocol);
135
- if (ipv6_match?.[1]?.includes('*')) {
136
- throw new Error(`Wildcards not allowed in IPv6 addresses: ${pattern}`);
137
- }
138
- // Handle port wildcard - must be at the end
139
- let port_wildcard = false;
140
- let parse_pattern = pattern;
141
- if (pattern.endsWith(':*')) {
142
- port_wildcard = true;
143
- parse_pattern = pattern.slice(0, -2);
144
- }
145
- // Parse with URL constructor for robust handling of protocol, hostname, port, IPv6
146
- let url;
147
- try {
148
- url = new URL(parse_pattern);
149
- }
150
- catch {
151
- throw new Error(`Invalid origin pattern: ${pattern}`);
152
- }
153
- // Validate protocol is http or https
154
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
155
- throw new Error(`Invalid origin pattern: ${pattern}`);
156
- }
157
- const hostname = url.hostname;
158
- const is_ipv6 = hostname.startsWith('[');
159
- // For regular hostnames, wildcards must be complete labels
160
- if (!is_ipv6) {
161
- for (const label of hostname.split('.')) {
162
- if (label.includes('*') && label !== '*') {
163
- throw new Error(`Wildcards must be complete labels (e.g., *.fuz.dev, not *fuz.dev): ${pattern}`);
164
- }
221
+ const origin_pattern_to_regexp = (entry) => {
222
+ if (entry === '')
223
+ return 'empty_entry';
224
+ if (!VISIBLE_ASCII.test(entry))
225
+ return 'not_printable_ascii';
226
+ const scheme_end = entry.indexOf('://');
227
+ if (scheme_end === -1)
228
+ return 'missing_scheme';
229
+ const scheme = entry.slice(0, scheme_end).toLowerCase();
230
+ if (scheme !== 'http' && scheme !== 'https')
231
+ return 'unsupported_scheme';
232
+ const authority = entry.slice(scheme_end + 3);
233
+ if (/[/?#]/.test(authority))
234
+ return 'has_path';
235
+ if (authority.includes('@'))
236
+ return 'has_userinfo';
237
+ let host_source;
238
+ let port;
239
+ if (authority.startsWith('[')) {
240
+ const end = authority.indexOf(']');
241
+ if (end === -1)
242
+ return 'unclosed_ipv6';
243
+ const inner = authority.slice(1, end);
244
+ if (!is_ipv6_origin_form(inner))
245
+ return 'invalid_ipv6';
246
+ const rest = authority.slice(end + 1);
247
+ if (rest === '') {
248
+ port = null;
165
249
  }
166
- }
167
- // Build regex pattern
168
- let regex_pattern = '^' + escape_regexp(url.protocol) + '//';
169
- // Handle hostname
170
- if (is_ipv6) {
171
- // IPv6 address - URL.hostname includes brackets
172
- regex_pattern += escape_regexp(hostname);
250
+ else if (rest.startsWith(':')) {
251
+ port = rest.slice(1);
252
+ }
253
+ else {
254
+ return 'invalid_after_ipv6';
255
+ }
256
+ host_source = escape_regexp(authority.slice(0, end + 1));
173
257
  }
174
258
  else {
175
- // Regular hostname - process wildcards
176
- const labels = hostname.split('.');
177
- regex_pattern += labels
259
+ const colon = authority.lastIndexOf(':');
260
+ const host = colon === -1 ? authority : authority.slice(0, colon);
261
+ port = colon === -1 ? null : authority.slice(colon + 1);
262
+ if (host === '')
263
+ return 'missing_host';
264
+ if (host.includes(':'))
265
+ return 'unbracketed_ipv6';
266
+ if (/[%\\^|<>[\]]/.test(host))
267
+ return 'invalid_host_char';
268
+ const labels = host.split('.');
269
+ if (labels.some((label) => label.includes('*') && label !== '*'))
270
+ return 'partial_wildcard';
271
+ if (!is_canonical_ipv4_or_name(host))
272
+ return 'non_canonical_ipv4';
273
+ host_source = labels
178
274
  .map((label) => (label === '*' ? '[^./:]+' : escape_regexp(label)))
179
275
  .join('\\.');
180
276
  }
181
- // Handle port
182
- if (port_wildcard) {
183
- // Optional port (matches both with and without port)
184
- regex_pattern += '(:\\d+)?';
277
+ let port_source = '';
278
+ if (port === '*') {
279
+ port_source = '(?::\\d+)?';
185
280
  }
186
- else {
187
- // URL normalizes default ports (80 for HTTP, 443 for HTTPS) away,
188
- // so check original pattern for explicit port when url.port is empty
189
- let port = url.port;
190
- if (!port) {
191
- const port_match = /:(\d+)$/.exec(parse_pattern);
192
- if (port_match?.[1]) {
193
- port = port_match[1];
194
- }
195
- }
196
- if (port) {
197
- regex_pattern += ':' + escape_regexp(port);
198
- }
281
+ else if (port !== null) {
282
+ if (!/^[1-9]\d*$/.test(port) || Number(port) > 65535)
283
+ return 'invalid_port';
284
+ if (port === (scheme === 'https' ? '443' : '80'))
285
+ return 'default_port';
286
+ port_source = ':' + port;
287
+ }
288
+ return new RegExp('^' + escape_regexp(scheme + '://') + host_source + port_source + '$', 'i');
289
+ };
290
+ /**
291
+ * True when `inner` (a bracketed host without its brackets) is an IPv6 address
292
+ * written exactly as a browser serializes it — lowercase-insensitive hex groups
293
+ * without leading zeros, the first longest run of two or more zero groups as
294
+ * `::`, no dotted-quad tail. The URL parser is the serializer here: an address
295
+ * it rewrites, or can't read, is refused rather than normalized.
296
+ */
297
+ const is_ipv6_origin_form = (inner) => {
298
+ let hostname;
299
+ try {
300
+ hostname = new URL(`http://[${inner}]`).hostname;
301
+ }
302
+ catch {
303
+ return false;
199
304
  }
200
- regex_pattern += '$';
201
- // Case-insensitive matching (web standards specify domains are case-insensitive)
202
- return new RegExp(regex_pattern, 'i');
305
+ return hostname === `[${inner.toLowerCase()}]`;
306
+ };
307
+ /**
308
+ * False when `host` (no brackets, wildcard labels allowed) is one a browser
309
+ * reads as an IPv4 address and then writes differently. A browser takes any
310
+ * host whose last label is a number — decimal or `0x` hex, one trailing dot
311
+ * aside — for an IPv4 address and sends it as four decimal numbers.
312
+ */
313
+ const is_canonical_ipv4_or_name = (host) => {
314
+ const name = host.endsWith('.') ? host.slice(0, -1) : host;
315
+ const last = name.slice(name.lastIndexOf('.') + 1);
316
+ const is_number = /^0[xX][0-9a-fA-F]*$/.test(last) || /^\d+$/.test(last);
317
+ if (!is_number)
318
+ return true;
319
+ // a trailing dot makes a fifth, empty label
320
+ const labels = host.split('.');
321
+ return (labels.length === 4 &&
322
+ labels.every((label) => label === '*' || (/^(?:0|[1-9]\d{0,2})$/.test(label) && Number(label) <= 255)));
203
323
  };
@@ -150,6 +150,27 @@ export interface RouteSpec {
150
150
  * misleading `null` body.
151
151
  */
152
152
  raw_body?: boolean;
153
+ /**
154
+ * Marks a route that issues a credential — a session cookie — while
155
+ * declaring no auth itself: login, signup, bootstrap. A forged cross-site
156
+ * request to one is login CSRF, so it must take the origin check, and
157
+ * assembly refuses it outside the auth middleware's scope, where no origin
158
+ * carve-out can reach (`assert_credential_routes_in_auth_scope`). A route
159
+ * that reads a credential (`auth.account` not `'none'`) is held there
160
+ * without the mark.
161
+ */
162
+ mints_credential?: boolean;
163
+ /**
164
+ * Marks a route that reads the resolved credential in its handler while
165
+ * declaring no auth gate — it answers an anonymous caller itself instead
166
+ * of taking the dispatcher's `401` (account status, the cell-scoped fact
167
+ * read, the JSON-RPC endpoint whose dispatcher gates each action).
168
+ * Outside the auth middleware's scope no session or bearer parsing runs
169
+ * and it would see every caller as anonymous, so assembly refuses it
170
+ * there (`assert_credential_routes_in_auth_scope`), as it does a route
171
+ * whose `auth.account` is not `'none'`.
172
+ */
173
+ reads_credential?: boolean;
153
174
  /**
154
175
  * Rate limit key type — declares what this route's rate limiter is keyed on.
155
176
  *
@@ -1 +1 @@
1
- {"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"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"}
@@ -106,6 +106,28 @@ export declare const schema_to_surface: (schema: z.ZodType) => unknown;
106
106
  * - Exact match: `/health` matches `/health`
107
107
  */
108
108
  export declare const middleware_applies: (mw_path: string, route_path: string) => boolean;
109
+ /**
110
+ * Check if two middleware path patterns can match a common route path — the
111
+ * pattern-against-pattern form of `middleware_applies`, over the same
112
+ * grammar (`*`, an exact path, or a `/prefix/*` that also matches `/prefix`).
113
+ *
114
+ * @param a - one Hono-style pattern
115
+ * @param b - the other
116
+ */
117
+ export declare const middleware_paths_overlap: (a: string, b: string) => boolean;
118
+ /**
119
+ * Check if a middleware layer runs on a route path: its `path` pattern
120
+ * applies (`middleware_applies`) and none of its `carve_outs` does.
121
+ *
122
+ * @param spec - the layer's `path` and optional `carve_outs`
123
+ * @param route_path - the route path to check
124
+ */
125
+ export declare const middleware_spec_applies: (spec: {
126
+ readonly path: string;
127
+ readonly carve_outs?: ReadonlyArray<{
128
+ readonly path: string;
129
+ }> | null;
130
+ }, route_path: string) => boolean;
109
131
  /**
110
132
  * Merge auto-derived, middleware, and explicit error schemas for a route spec.
111
133
  *
@@ -1 +1 @@
1
- {"version":3,"file":"schema_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/schema_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAAI,YAAY,OAAO,KAAG,OAUpD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,CAAC,CAAC,OAAO,EAAE,YAAY,OAAO,KAAG,OACA,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gCAAgC,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EACnE,QAAQ,CAAC,EACT,OAAO,OAAO,KACZ,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAclC,CAAC;AAWF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OACe,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAQrD,CAAC;AAoBF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,OAQxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM;IACL,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,EACD,oBAAoB,iBAAiB,GAAG,IAAI,KAC1C,iBAAiB,GAAG,IAWtB,CAAC"}
1
+ {"version":3,"file":"schema_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/schema_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAAsC,CAAC;AAE1F;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAAI,YAAY,OAAO,KAAG,OAUpD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,CAAC,CAAC,OAAO,EAAE,YAAY,OAAO,KAAG,OACA,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gCAAgC,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EACnE,QAAQ,CAAC,EACT,OAAO,OAAO,KACZ,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAclC,CAAC;AAWF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OACe,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAQrD,CAAC;AAoBF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,EAAE,YAAY,MAAM,KAAG,OAQxE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,OAM/D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM;IACL,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;CACtE,EACD,YAAY,MAAM,KAChB,OAE0F,CAAC;AAE9F;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM;IACL,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,EACD,oBAAoB,iBAAiB,GAAG,IAAI,KAC1C,iBAAiB,GAAG,IAWtB,CAAC"}
@@ -179,6 +179,30 @@ export const middleware_applies = (mw_path, route_path) => {
179
179
  }
180
180
  return false;
181
181
  };
182
+ /**
183
+ * Check if two middleware path patterns can match a common route path — the
184
+ * pattern-against-pattern form of `middleware_applies`, over the same
185
+ * grammar (`*`, an exact path, or a `/prefix/*` that also matches `/prefix`).
186
+ *
187
+ * @param a - one Hono-style pattern
188
+ * @param b - the other
189
+ */
190
+ export const middleware_paths_overlap = (a, b) => {
191
+ if (a === '*' || b === '*')
192
+ return true;
193
+ // a `/prefix/*` pattern's shortest match is the bare `/prefix` (`/*` → ``)
194
+ const base = (pattern) => pattern.endsWith('/*') ? pattern.slice(0, -2) : pattern;
195
+ return middleware_applies(a, base(b)) || middleware_applies(b, base(a));
196
+ };
197
+ /**
198
+ * Check if a middleware layer runs on a route path: its `path` pattern
199
+ * applies (`middleware_applies`) and none of its `carve_outs` does.
200
+ *
201
+ * @param spec - the layer's `path` and optional `carve_outs`
202
+ * @param route_path - the route path to check
203
+ */
204
+ export const middleware_spec_applies = (spec, route_path) => middleware_applies(spec.path, route_path) &&
205
+ !(spec.carve_outs ?? []).some((carve_out) => middleware_applies(carve_out.path, route_path));
182
206
  /**
183
207
  * Merge auto-derived, middleware, and explicit error schemas for a route spec.
184
208
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * App surface generation — JSON-serializable attack surface from route and middleware specs.
3
3
  *
4
- * Pure schema helpers (`is_null_schema`, `schema_to_surface`, `middleware_applies`,
4
+ * Pure schema helpers (`is_null_schema`, `schema_to_surface`, `middleware_applies`, `middleware_spec_applies`,
5
5
  * `merge_error_schemas`) live in `http/schema_helpers.ts`.
6
6
  *
7
7
  * @module
@@ -52,6 +52,11 @@ export interface AppSurfaceMiddleware {
52
52
  path: string;
53
53
  /** JSON Schema representations of error responses, keyed by HTTP status code. `null` when none. */
54
54
  error_schemas: Record<string, unknown> | null;
55
+ /** Paths under `path` the layer does not run on, each with its reason. Absent when none. */
56
+ carve_outs?: Array<{
57
+ path: string;
58
+ reason: string;
59
+ }>;
55
60
  }
56
61
  /** An env var in the generated attack surface (JSON-serializable). */
57
62
  export interface AppSurfaceEnv {
@@ -1 +1 @@
1
- {"version":3,"file":"surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/surface.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/surface.ts"],"names":[],"mappings":"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"}