@spacefast/common 0.0.12 → 0.0.17

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 (189) hide show
  1. package/dist/agents/client-registry.d.ts +22 -0
  2. package/dist/agents/client-registry.js +41 -0
  3. package/dist/agents/connect-targets.d.ts +51 -6
  4. package/dist/agents/connect-targets.js +93 -28
  5. package/dist/brand.d.ts +0 -11
  6. package/dist/brand.js +0 -20
  7. package/dist/config/domains.d.ts +15 -6
  8. package/dist/config/domains.js +24 -10
  9. package/dist/config/postgres-budget.d.ts +3 -0
  10. package/dist/config/postgres-budget.js +6 -1
  11. package/dist/config/postgres-budget.json +2 -1
  12. package/dist/contracts/abuse.js +1 -1
  13. package/dist/contracts/access.d.ts +486 -1196
  14. package/dist/contracts/access.js +323 -1054
  15. package/dist/contracts/activity.d.ts +1 -1
  16. package/dist/contracts/activity.js +11 -15
  17. package/dist/contracts/api-keys.d.ts +16 -0
  18. package/dist/contracts/api-keys.js +11 -9
  19. package/dist/contracts/archives.d.ts +1 -2
  20. package/dist/contracts/archives.js +15 -17
  21. package/dist/contracts/beta.js +1 -1
  22. package/dist/contracts/builds.js +14 -14
  23. package/dist/contracts/channels.js +2 -2
  24. package/dist/contracts/comments.d.ts +143 -99
  25. package/dist/contracts/comments.js +194 -79
  26. package/dist/contracts/common.d.ts +12 -12
  27. package/dist/contracts/common.js +33 -30
  28. package/dist/contracts/continuation.js +5 -5
  29. package/dist/contracts/countries.js +2 -7
  30. package/dist/contracts/data-source-routes.d.ts +20 -0
  31. package/dist/contracts/data-source-routes.js +15 -0
  32. package/dist/contracts/device-auth.d.ts +13 -9
  33. package/dist/contracts/device-auth.js +12 -12
  34. package/dist/contracts/docs.d.ts +10 -10
  35. package/dist/contracts/domains.js +39 -33
  36. package/dist/contracts/enums.d.ts +4 -14
  37. package/dist/contracts/enums.js +3 -10
  38. package/dist/contracts/error-code-meta.d.ts +122 -56
  39. package/dist/contracts/error-code-meta.js +43 -19
  40. package/dist/contracts/error-codes.d.ts +7 -4
  41. package/dist/contracts/error-codes.js +48 -24
  42. package/dist/contracts/events.d.ts +22 -15
  43. package/dist/contracts/events.js +33 -5
  44. package/dist/contracts/execution.d.ts +238 -0
  45. package/dist/contracts/execution.js +309 -0
  46. package/dist/contracts/feature-lifecycle.d.ts +39 -188
  47. package/dist/contracts/feature-lifecycle.js +52 -196
  48. package/dist/contracts/features.d.ts +13 -115
  49. package/dist/contracts/features.js +5 -35
  50. package/dist/contracts/functions.d.ts +597 -0
  51. package/dist/contracts/functions.js +475 -0
  52. package/dist/contracts/grants.d.ts +1175 -0
  53. package/dist/contracts/grants.js +783 -0
  54. package/dist/contracts/ids.d.ts +7 -5
  55. package/dist/contracts/ids.js +7 -7
  56. package/dist/contracts/internal.js +2 -2
  57. package/dist/contracts/mcp.d.ts +17 -556
  58. package/dist/contracts/mcp.js +9 -103
  59. package/dist/contracts/me.js +3 -4
  60. package/dist/contracts/notifications.d.ts +108 -0
  61. package/dist/contracts/notifications.js +54 -0
  62. package/dist/contracts/oauth-resources.d.ts +22 -5
  63. package/dist/contracts/oauth-resources.js +23 -2
  64. package/dist/contracts/operations.d.ts +0 -48
  65. package/dist/contracts/operations.js +0 -19
  66. package/dist/contracts/pages.d.ts +10 -14
  67. package/dist/contracts/pages.js +17 -21
  68. package/dist/contracts/plan-policy.d.ts +1 -8
  69. package/dist/contracts/plan-policy.js +1 -4
  70. package/dist/contracts/platform.d.ts +78 -30
  71. package/dist/contracts/platform.js +69 -20
  72. package/dist/contracts/problem-document.d.ts +14 -0
  73. package/dist/contracts/problem-document.js +35 -0
  74. package/dist/contracts/push-new.d.ts +41 -2
  75. package/dist/contracts/push-new.js +4 -0
  76. package/dist/contracts/quotas.d.ts +0 -1
  77. package/dist/contracts/quotas.js +0 -5
  78. package/dist/contracts/repository-connections.d.ts +1 -1
  79. package/dist/contracts/resources.d.ts +16 -71
  80. package/dist/contracts/resources.js +13 -16
  81. package/dist/contracts/runtime-api.d.ts +666 -303
  82. package/dist/contracts/runtime-api.js +272 -113
  83. package/dist/contracts/runtime-app.d.ts +168 -0
  84. package/dist/contracts/runtime-app.js +42 -0
  85. package/dist/contracts/runtime-db.d.ts +424 -0
  86. package/dist/contracts/runtime-db.js +108 -0
  87. package/dist/contracts/runtime-storage.d.ts +120 -0
  88. package/dist/contracts/runtime-storage.js +40 -0
  89. package/dist/contracts/sf-config-v1.d.ts +54 -1
  90. package/dist/contracts/sf-config-v1.js +2 -2
  91. package/dist/contracts/sites.d.ts +6 -0
  92. package/dist/contracts/sites.js +30 -11
  93. package/dist/contracts/space-config.d.ts +91 -121
  94. package/dist/contracts/space-config.js +464 -42
  95. package/dist/contracts/spaces.d.ts +300 -171
  96. package/dist/contracts/spaces.js +128 -98
  97. package/dist/contracts/superadmin-emails.d.ts +2 -2
  98. package/dist/contracts/superadmin-spaces.d.ts +26 -130
  99. package/dist/contracts/superadmin-spaces.js +22 -0
  100. package/dist/contracts/superadmin-tenants.js +4 -4
  101. package/dist/contracts/superadmin.d.ts +4 -0
  102. package/dist/contracts/superadmin.js +14 -1
  103. package/dist/contracts/tags.d.ts +6 -6
  104. package/dist/contracts/tags.js +1 -1
  105. package/dist/contracts/teams.d.ts +9 -9
  106. package/dist/contracts/teams.js +25 -16
  107. package/dist/contracts/theme-json.js +1 -1
  108. package/dist/contracts/transfers.d.ts +0 -1
  109. package/dist/contracts/transfers.js +4 -4
  110. package/dist/contracts/variables.js +3 -3
  111. package/dist/contracts/zero.d.ts +384 -596
  112. package/dist/contracts/zero.js +167 -299
  113. package/dist/dashboard-paths/index.d.ts +18 -3
  114. package/dist/dashboard-paths/index.js +19 -0
  115. package/dist/docs/agent-handoff-document.d.ts +2 -2
  116. package/dist/docs/agent-handoff-document.js +8 -7
  117. package/dist/docs/agent-prose.d.ts +22 -3
  118. package/dist/docs/agent-prose.js +227 -55
  119. package/dist/docs/agent-setup.d.ts +14 -11
  120. package/dist/docs/agent-setup.js +289 -61
  121. package/dist/docs/agent-solutions.d.ts +6 -6
  122. package/dist/docs/agent-solutions.js +24 -22
  123. package/dist/docs/catalog.d.ts +53 -98
  124. package/dist/docs/catalog.js +90 -117
  125. package/dist/docs/error-docs.js +304 -236
  126. package/dist/docs/skill-distribution.d.ts +2 -0
  127. package/dist/docs/skill-distribution.js +7 -4
  128. package/dist/docs/skills.d.ts +546 -0
  129. package/dist/docs/skills.js +340 -0
  130. package/dist/utils/auth-redirect.d.ts +1 -3
  131. package/dist/utils/auth-redirect.js +1 -21
  132. package/dist/utils/browser-credential.d.ts +19 -0
  133. package/dist/utils/browser-credential.js +62 -0
  134. package/dist/utils/build-settings.d.ts +2 -0
  135. package/dist/utils/build-settings.js +61 -9
  136. package/dist/utils/comment-avatars.d.ts +7 -0
  137. package/dist/utils/comment-avatars.js +42 -0
  138. package/dist/utils/content-type.d.ts +3 -3
  139. package/dist/utils/content-type.js +38 -61
  140. package/dist/utils/credential-policy.d.ts +1 -1
  141. package/dist/utils/credential-policy.js +4 -0
  142. package/dist/utils/domain-diagnostics.d.ts +5 -5
  143. package/dist/utils/domain-diagnostics.js +21 -10
  144. package/dist/utils/egress-policy.fixtures.json +1 -1
  145. package/dist/utils/grants.d.ts +53 -0
  146. package/dist/utils/grants.js +160 -0
  147. package/dist/utils/local-space-state.d.ts +78 -4
  148. package/dist/utils/local-space-state.js +384 -82
  149. package/dist/utils/one-shot-replay.d.ts +14 -0
  150. package/dist/utils/one-shot-replay.js +21 -0
  151. package/dist/utils/page-preview.d.ts +7 -0
  152. package/dist/utils/page-preview.js +9 -0
  153. package/dist/utils/pages.d.ts +2 -5
  154. package/dist/utils/pages.js +64 -33
  155. package/dist/utils/publish-form-data.d.ts +22 -0
  156. package/dist/utils/publish-form-data.js +26 -0
  157. package/dist/utils/publish-policy.d.ts +34 -4
  158. package/dist/utils/publish-policy.js +149 -62
  159. package/dist/utils/query-keys.d.ts +8 -10
  160. package/dist/utils/query-keys.js +14 -10
  161. package/dist/utils/runtime-paths.d.ts +3 -0
  162. package/dist/utils/runtime-paths.js +7 -0
  163. package/dist/utils/secure-local-file.d.ts +73 -0
  164. package/dist/utils/secure-local-file.js +1373 -0
  165. package/dist/utils/space-config.d.ts +17 -4
  166. package/dist/utils/space-config.js +151 -8
  167. package/dist/utils/static-runtime-policy.d.ts +5 -0
  168. package/dist/utils/static-runtime-policy.js +54 -10
  169. package/dist/utils/storage-policy.d.ts +20 -0
  170. package/dist/utils/storage-policy.fixtures.json +174 -0
  171. package/dist/utils/storage-policy.js +193 -0
  172. package/dist/utils/upload-session.d.ts +15 -1
  173. package/dist/utils/upload-session.js +68 -2
  174. package/dist/utils/version-file-browser.d.ts +13 -0
  175. package/dist/utils/version-file-browser.js +59 -0
  176. package/package.json +20 -1
  177. package/dist/contracts/mounts.d.ts +0 -142
  178. package/dist/contracts/mounts.js +0 -56
  179. package/dist/utils/access-grant.d.ts +0 -14
  180. package/dist/utils/access-grant.js +0 -88
  181. package/dist/utils/access-match.d.ts +0 -46
  182. package/dist/utils/access-match.fixtures.json +0 -513
  183. package/dist/utils/access-match.js +0 -312
  184. package/dist/utils/access-verdict.fixtures.json +0 -421
  185. package/dist/utils/asset-fingerprint.d.ts +0 -53
  186. package/dist/utils/asset-fingerprint.js +0 -574
  187. package/dist/utils/sf-config-v1.d.ts +0 -2
  188. package/dist/utils/sf-config-v1.fixtures.json +0 -9
  189. package/dist/utils/sf-config-v1.js +0 -551
@@ -1,5 +1,6 @@
1
+ import { type Grant } from "../contracts/grants.js";
1
2
  import { type SpaceConfig, type SpaceConfigFile } from "../contracts/space-config.js";
2
- export type SpaceConfigBlockingIssueCode = "config_file_too_large" | "config_templates_over_limit" | "config_meta_too_long" | "config_invalid" | "inject_snippet_too_large" | "inject_invalid";
3
+ export type SpaceConfigBlockingIssueCode = "config_file_too_large" | "config_templates_over_limit" | "config_meta_too_long" | "config_name_too_long" | "config_functions_key_removed" | "config_invalid" | "config_runtime_invalid_kind" | "config_runtime_entry_missing" | "inject_snippet_too_large" | "inject_invalid";
3
4
  export type SpaceConfigIssue = {
4
5
  severity: "info" | "warning" | "error";
5
6
  code: SpaceConfigBlockingIssueCode | "single_html_index" | "files_mode_inferred";
@@ -8,15 +9,27 @@ export type SpaceConfigIssue = {
8
9
  path?: string;
9
10
  details?: Record<string, unknown>;
10
11
  };
12
+ export declare const CONFIG_PUBLIC_GRANT_COMPILED_ID = "config:access.public";
13
+ /** Compiles the file-only access declaration; activation decides which Version is live. */
14
+ export declare function compileSpaceConfigPublicGrant(fileConfig: Pick<SpaceConfigFile, "access"> | null | undefined): Grant | null;
11
15
  type SpaceConfigBlockingIssue = SpaceConfigIssue & {
12
16
  code: SpaceConfigBlockingIssueCode;
13
17
  };
18
+ /**
19
+ * Keys a committed `sf.jsonc` owns outright: they are facts about the content
20
+ * of a version (who may read it, what it is called, what runs it), so they roll
21
+ * forward and back with the version instead of living in the space's mutable
22
+ * overlay. API intake refuses them rather than storing or dropping them.
23
+ */
24
+ export declare const SPACE_CONFIG_FILE_ONLY_KEYS: readonly ["access", "name", "runtime"];
14
25
  /**
15
26
  * Validates a space `config` overlay at API intake (PATCH /spaces, publish
16
- * `config`): the 16 KB compiled-overlay cap plus the shared shape caps.
17
- * Returns the first blocking issue, or null when the overlay is acceptable.
27
+ * `config`): file-only keys, the 16 KB compiled-overlay cap, plus the shared
28
+ * shape caps. Returns the first blocking issue, or null when the overlay is
29
+ * acceptable. Takes the raw request value, because the overlay schema strips
30
+ * file-only keys and this is what has to see them.
18
31
  */
19
- export declare function spaceConfigOverlayIssue(config: SpaceConfig): SpaceConfigBlockingIssue | null;
32
+ export declare function spaceConfigOverlayIssue(config: unknown): SpaceConfigBlockingIssue | null;
20
33
  export declare function stripJsonComments(raw: string): string;
21
34
  export type ParsedSpaceConfigFile = {
22
35
  /** Parsed config (cap- and shape-valid keys only); null when unusable. */
@@ -1,8 +1,35 @@
1
+ import { CONFIG_PUBLIC_GRANT_SOURCE_REFERENCE, grantSchema, } from "../contracts/grants.js";
1
2
  import { SPACE_CONFIG_CAPS, SPACE_CONFIG_CANONICAL_FILE, SPACE_CONFIG_PRIVATE_FILES, spaceConfigFileSchema, spaceConfigSchema, } from "../contracts/space-config.js";
2
3
  const TEXT_BYTES = new TextEncoder();
4
+ export const CONFIG_PUBLIC_GRANT_COMPILED_ID = "config:access.public";
3
5
  function byteLength(value) {
4
6
  return TEXT_BYTES.encode(value).length;
5
7
  }
8
+ /** Compiles the file-only access declaration; activation decides which Version is live. */
9
+ export function compileSpaceConfigPublicGrant(fileConfig) {
10
+ const patterns = fileConfig?.access?.public ?? [];
11
+ const include = patterns.filter((pattern) => !pattern.startsWith("!"));
12
+ if (include.length === 0)
13
+ return null;
14
+ return grantSchema.parse({
15
+ id: CONFIG_PUBLIC_GRANT_COMPILED_ID,
16
+ name: "Public paths from sf.jsonc",
17
+ audience: { kind: "public" },
18
+ resources: {
19
+ include,
20
+ exclude: patterns
21
+ .filter((pattern) => pattern.startsWith("!"))
22
+ .map((pattern) => pattern.slice(1)),
23
+ },
24
+ capabilities: ["page.view"],
25
+ constraints: {},
26
+ target: { kind: "live" },
27
+ source: {
28
+ kind: "config",
29
+ reference: CONFIG_PUBLIC_GRANT_SOURCE_REFERENCE,
30
+ },
31
+ });
32
+ }
6
33
  function capIssues(candidate) {
7
34
  const issues = [];
8
35
  const templates = candidate.templates;
@@ -96,12 +123,104 @@ function capIssues(candidate) {
96
123
  }
97
124
  return issues;
98
125
  }
126
+ // File-only keys (`name`, `runtime`) never reach the API overlay, so their
127
+ // checks live beside the shared caps rather than inside them. Both run BEFORE
128
+ // zod so a bad declaration reports its stable code instead of a shape error.
129
+ const RUNTIME_KINDS = new Set(["zero", "functions"]);
130
+ function fileOnlyIssues(candidate) {
131
+ const issues = [];
132
+ const name = candidate.name;
133
+ if (typeof name === "string" && name.trim().length > SPACE_CONFIG_CAPS.nameChars) {
134
+ issues.push({
135
+ severity: "error",
136
+ code: "config_name_too_long",
137
+ message: `name supports up to ${SPACE_CONFIG_CAPS.nameChars} characters.`,
138
+ path: "name",
139
+ details: { length: name.trim().length, limit: SPACE_CONFIG_CAPS.nameChars },
140
+ });
141
+ }
142
+ const runtime = candidate.runtime;
143
+ if (runtime === undefined) {
144
+ return issues;
145
+ }
146
+ if (typeof runtime !== "object" || runtime === null || Array.isArray(runtime)) {
147
+ issues.push({
148
+ severity: "error",
149
+ code: "config_runtime_invalid_kind",
150
+ message: 'runtime must be an object declaring a kind of "zero" or "functions".',
151
+ path: "runtime",
152
+ });
153
+ return issues;
154
+ }
155
+ const declaration = runtime;
156
+ const kind = declaration.kind;
157
+ if (typeof kind !== "string" || !RUNTIME_KINDS.has(kind)) {
158
+ issues.push({
159
+ severity: "error",
160
+ code: "config_runtime_invalid_kind",
161
+ message: 'runtime.kind must be "zero" or "functions".',
162
+ path: "runtime.kind",
163
+ ...(typeof kind === "string" ? { details: { kind } } : {}),
164
+ });
165
+ return issues;
166
+ }
167
+ if (kind === "zero") {
168
+ // Zero is never inferred, so a declaration that names neither entry has
169
+ // nothing to compile — failing here beats publishing static files under a
170
+ // config that promised a database app.
171
+ for (const key of ["server", "client"]) {
172
+ const value = declaration[key];
173
+ if (typeof value !== "string" || value.trim().length === 0) {
174
+ issues.push({
175
+ severity: "error",
176
+ code: "config_runtime_entry_missing",
177
+ message: `runtime.${key} is required and must name a module when runtime.kind is "zero".`,
178
+ path: `runtime.${key}`,
179
+ });
180
+ }
181
+ }
182
+ return issues;
183
+ }
184
+ const entry = declaration.entry;
185
+ if (entry !== undefined && (typeof entry !== "string" || entry.trim().length === 0)) {
186
+ issues.push({
187
+ severity: "error",
188
+ code: "config_runtime_entry_missing",
189
+ message: "runtime.entry must name a module when present; omit it to detect the worker instead.",
190
+ path: "runtime.entry",
191
+ });
192
+ }
193
+ return issues;
194
+ }
195
+ /**
196
+ * Keys a committed `sf.jsonc` owns outright: they are facts about the content
197
+ * of a version (who may read it, what it is called, what runs it), so they roll
198
+ * forward and back with the version instead of living in the space's mutable
199
+ * overlay. API intake refuses them rather than storing or dropping them.
200
+ */
201
+ export const SPACE_CONFIG_FILE_ONLY_KEYS = ["access", "name", "runtime"];
99
202
  /**
100
203
  * Validates a space `config` overlay at API intake (PATCH /spaces, publish
101
- * `config`): the 16 KB compiled-overlay cap plus the shared shape caps.
102
- * Returns the first blocking issue, or null when the overlay is acceptable.
204
+ * `config`): file-only keys, the 16 KB compiled-overlay cap, plus the shared
205
+ * shape caps. Returns the first blocking issue, or null when the overlay is
206
+ * acceptable. Takes the raw request value, because the overlay schema strips
207
+ * file-only keys and this is what has to see them.
103
208
  */
104
209
  export function spaceConfigOverlayIssue(config) {
210
+ if (config === null || config === undefined || typeof config !== "object") {
211
+ return null;
212
+ }
213
+ for (const key of SPACE_CONFIG_FILE_ONLY_KEYS) {
214
+ if (Object.prototype.hasOwnProperty.call(config, key)) {
215
+ return {
216
+ severity: "error",
217
+ code: "config_invalid",
218
+ message: `${key} is only valid in the committed ${SPACE_CONFIG_CANONICAL_FILE} file.`,
219
+ path: key,
220
+ details: { file: SPACE_CONFIG_CANONICAL_FILE },
221
+ };
222
+ }
223
+ }
105
224
  const encoded = JSON.stringify(config);
106
225
  if (byteLength(encoded) > SPACE_CONFIG_CAPS.overlayBytes) {
107
226
  return {
@@ -114,9 +233,18 @@ export function spaceConfigOverlayIssue(config) {
114
233
  }
115
234
  return capIssues(config)[0] ?? null;
116
235
  }
117
- // Routing concerns never live in sf.jsonc (spec: redirects, rewrites,
118
- // proxies, and headers stay exclusively in `_redirects`/`_headers`).
119
- const ROUTING_KEYS = new Set(["redirects", "rewrites", "proxies", "proxy", "headers", "routes"]);
236
+ // Other platforms' routing vocabulary. We have one matcher grammar; naming it
237
+ // four more ways would be four more things to keep in sync.
238
+ const ROUTING_KEYS = new Set(["proxies", "proxy", "routes"]);
239
+ // OUR routing vocabulary. These keys are real sf.jsonc keys, owned end to end
240
+ // by the routing compiler (crates/stattic-runtime-core): the strict v1 compiler
241
+ // validates them and the merge folds them in behind `_redirects`/`_headers`.
242
+ // This lane resolves the serving config, so it neither validates nor carries
243
+ // them — it just leaves them alone.
244
+ const ROUTING_RULE_KEYS = new Set(["redirects", "rewrites", "headers"]);
245
+ // Replaced by the `runtime` declaration; never silently ignored like other
246
+ // unknown keys, because it is the one key whose loss changes what gets served.
247
+ const REMOVED_FUNCTIONS_KEY = "functions";
120
248
  const KNOWN_FILE_KEYS = new Set(Object.keys(spaceConfigFileSchema.shape));
121
249
  // Strips JSONC comments and trailing commas (string-aware single pass).
122
250
  export function stripJsonComments(raw) {
@@ -255,7 +383,23 @@ export function parseSpaceConfigFile(raw, options = {}) {
255
383
  if (KNOWN_FILE_KEYS.has(key)) {
256
384
  continue;
257
385
  }
258
- if (ROUTING_KEYS.has(key)) {
386
+ if (ROUTING_RULE_KEYS.has(key)) {
387
+ delete record[key];
388
+ continue;
389
+ }
390
+ if (key === REMOVED_FUNCTIONS_KEY) {
391
+ // The runtime declaration replaced this key outright (spec "The old
392
+ // top-level functions.* key is removed in the same cutover"): warning-and-
393
+ // strip would publish static files under a config that asked for a
394
+ // worker, so it blocks with its own code instead.
395
+ issues.push({
396
+ severity: "error",
397
+ code: "config_functions_key_removed",
398
+ message: `The "${REMOVED_FUNCTIONS_KEY}" key was removed from ${configPath}; declare runtime.kind "functions" instead.`,
399
+ path: key,
400
+ });
401
+ }
402
+ else if (ROUTING_KEYS.has(key)) {
259
403
  issues.push({
260
404
  severity: "error",
261
405
  code: "config_invalid",
@@ -273,8 +417,7 @@ export function parseSpaceConfigFile(raw, options = {}) {
273
417
  }
274
418
  delete record[key];
275
419
  }
276
- const caps = capIssues(record);
277
- issues.push(...caps);
420
+ issues.push(...capIssues(record), ...fileOnlyIssues(record));
278
421
  if (issues.some((issue) => issue.severity === "error")) {
279
422
  return { config: null, issues };
280
423
  }
@@ -1,5 +1,10 @@
1
1
  export declare const PHP_LIKE_EXTENSIONS: string[];
2
+ export declare const INTERNAL_REDIRECT_RESPONSE_HEADERS: string[];
2
3
  export declare const PLATFORM_MANAGED_RESPONSE_HEADERS: string[];
4
+ /** Reserved response-header namespaces owned by the platform's own serving signature. */
5
+ export declare const PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES: string[];
6
+ /** True when user rules may neither set nor remove this response header. */
7
+ export declare function isPlatformManagedResponseHeader(name: string): boolean;
3
8
  export declare const NON_IMMUTABLE_EXTENSIONS: string[];
4
9
  export declare const GUARANTEED_IMMUTABLE_PREFIXES: string[];
5
10
  /** Lowercased extension without the leading dot, posix semantics. Dotfiles have no extension. */
@@ -7,13 +7,47 @@
7
7
  // (static-runtime-policy.fixtures.json). Do not add platform/filesystem
8
8
  // behavior here.
9
9
  export const PHP_LIKE_EXTENSIONS = ["php", "php3", "php4", "php5", "php7", "php8", "phtml", "phar"];
10
+ // The internal-redirect/sendfile family: headers that do not describe a response, they
11
+ // instruct the web server to produce a different one. Under nginx+php-fpm
12
+ // X-Accel-Redirect (and every vendor spelling of it) turns a response into a server-side
13
+ // read of an arbitrary internal path — a private-path disclosure primitive — while
14
+ // X-Accel-Limit-Rate pins the serving worker for the length of the transfer.
15
+ //
16
+ // These are dangerous from ANY origin we do not control, so this family is factored out
17
+ // of the platform-managed list below: a relay lane (runtime/engine/runtime/proxy.php)
18
+ // must drop exactly this set from an untrusted upstream while still relaying the
19
+ // ordinary response headers (location, content-encoding, content-range, allow) that the
20
+ // rest of the managed list contains.
21
+ export const INTERNAL_REDIRECT_RESPONSE_HEADERS = [
22
+ "x-accel-buffering",
23
+ "x-accel-charset",
24
+ "x-accel-expires",
25
+ "x-accel-limit-rate",
26
+ "x-accel-redirect",
27
+ "x-lighttpd-send-file",
28
+ "x-lighttpd-sendfile",
29
+ "x-lighttpd-sendfile2",
30
+ "x-reproxy-url",
31
+ "x-sendfile",
32
+ ];
10
33
  // Rejected/platform-managed response headers (internal-docs/platform.md "Routing And
11
- // Headers"): user `_headers` rules can never set or remove these. The list includes the
12
- // internal-redirect/sendfile family (X-Accel-Redirect, X-Sendfile, X-LIGHTTPD-send-file,
13
- // X-Accel-Buffering) because under nginx+php-fpm those headers turn a response into a
14
- // server-side file read a private-path disclosure primitive. The native Rust compiler
15
- // rejects them at publish; the PHP runtime refuses them again at apply time so a compiler
16
- // gap can never emit one.
34
+ // Headers"): user `_headers` rules can never set or remove these. The
35
+ // internal-redirect/sendfile family above is spread in, so it stays a strict subset —
36
+ // anything the proxy lane refuses to relay is also refused from `_headers`. The rest is
37
+ // the connection and transport surface: hop-by-hop names (Connection, Keep-Alive, TE,
38
+ // Trailer, Transfer-Encoding, Upgrade, Proxy-*), framing, and the edge-cache trio.
39
+ //
40
+ // `isPlatformManagedResponseHeader` — not this list alone — is the policy: the reserved
41
+ // `x-spacefast-*` / `x-stattic-*` namespaces below carry the platform's own response
42
+ // signature (X-Spacefast-Runtime, X-Spacefast-Version, X-Spacefast-Reason, …), so user
43
+ // rules can never forge or clobber them.
44
+ //
45
+ // Every enforcement point derives from here: the `_headers` compiler in
46
+ // @spacefast/routing imports it directly, the PHP runtime maps and prefixes are
47
+ // generated from it into runtime/engine/shared/safety.php, and the Rust authorities
48
+ // (crates/stattic-runtime-core policy.rs, crates/stattic-zero-runner
49
+ // response_headers.rs) are drift-guarded against it by
50
+ // apps/control-plane/src/runtime/php-policy-parity.test.ts.
17
51
  export const PLATFORM_MANAGED_RESPONSE_HEADERS = [
18
52
  "accept-ranges",
19
53
  "age",
@@ -28,21 +62,31 @@ export const PLATFORM_MANAGED_RESPONSE_HEADERS = [
28
62
  "cookie",
29
63
  "date",
30
64
  "host",
65
+ "keep-alive",
31
66
  "location",
32
67
  "netlify-cdn-cache-control",
68
+ "proxy-authenticate",
69
+ "proxy-authorization",
33
70
  "server",
34
71
  "set-cookie",
35
72
  "strict-transport-security",
36
73
  "surrogate-control",
74
+ "te",
37
75
  "trailer",
38
76
  "transfer-encoding",
39
77
  "upgrade",
40
78
  "vary",
41
- "x-accel-buffering",
42
- "x-accel-redirect",
43
- "x-lighttpd-send-file",
44
- "x-sendfile",
79
+ ...INTERNAL_REDIRECT_RESPONSE_HEADERS,
45
80
  ];
81
+ /** Reserved response-header namespaces owned by the platform's own serving signature. */
82
+ export const PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES = ["x-spacefast-", "x-stattic-"];
83
+ const PLATFORM_MANAGED_RESPONSE_HEADER_SET = new Set(PLATFORM_MANAGED_RESPONSE_HEADERS);
84
+ /** True when user rules may neither set nor remove this response header. */
85
+ export function isPlatformManagedResponseHeader(name) {
86
+ const lower = name.trim().toLowerCase();
87
+ return (PLATFORM_MANAGED_RESPONSE_HEADER_SET.has(lower) ||
88
+ PLATFORM_MANAGED_RESPONSE_HEADER_PREFIXES.some((prefix) => lower.startsWith(prefix)));
89
+ }
46
90
  export const NON_IMMUTABLE_EXTENSIONS = ["html", "htm", "php", "txt", "xml"];
47
91
  const PHP_LIKE_EXTENSION_SET = new Set(PHP_LIKE_EXTENSIONS);
48
92
  const NON_IMMUTABLE_EXTENSION_SET = new Set(NON_IMMUTABLE_EXTENSIONS);
@@ -0,0 +1,20 @@
1
+ export declare const STORAGE_OBJECT_MAX_FILE_BYTES: number;
2
+ export declare const STORAGE_OBJECT_MAX_OWNER_BYTES: number;
3
+ export declare const STORAGE_CONTENT_SNIFF_BYTES = 512;
4
+ export declare const STORAGE_BLOCKED_CONTENT_TYPES: string[];
5
+ export declare const STORAGE_BLOCKED_CONTENT_TYPE_FRAGMENTS: string[];
6
+ export declare const STORAGE_BLOCKED_MAGIC_HEX: string[];
7
+ export declare const STORAGE_BLOCKED_TEXT_PREFIXES: string[];
8
+ export declare function storageDeclaredTypeBlocked(contentType: string): boolean;
9
+ export declare function storageContentBlocked(contentType: string, input: Uint8Array): boolean;
10
+ export type StorageByteRange = {
11
+ unsatisfiable: true;
12
+ } | {
13
+ start: number;
14
+ end: number;
15
+ header: string;
16
+ };
17
+ export declare function storageParseSingleRange(rangeHeader: string | null | undefined, size: number): StorageByteRange | null;
18
+ export declare function storageIfNoneMatchMatches(header: string | null | undefined, etag: string): boolean;
19
+ export declare function storageParseHttpDate(value: string): number | null;
20
+ export declare function storageIfRangeMatches(ifRange: string, etag: string, lastModified: string): boolean;
@@ -0,0 +1,174 @@
1
+ {
2
+ "content": [
3
+ {
4
+ "name": "declared html",
5
+ "contentType": "text/html; charset=utf-8",
6
+ "hex": "706c61696e",
7
+ "expected": true
8
+ },
9
+ {
10
+ "name": "declared javascript",
11
+ "contentType": "application/javascript",
12
+ "hex": "706c61696e",
13
+ "expected": true
14
+ },
15
+ {
16
+ "name": "windows executable magic",
17
+ "contentType": "application/octet-stream",
18
+ "hex": "4d5a0000",
19
+ "expected": true
20
+ },
21
+ {
22
+ "name": "mach executable magic",
23
+ "contentType": "application/octet-stream",
24
+ "hex": "feedfacf0000",
25
+ "expected": true
26
+ },
27
+ {
28
+ "name": "trimmed active html",
29
+ "contentType": "text/plain",
30
+ "hex": "20090a3c7363726970743e",
31
+ "expected": true
32
+ },
33
+ {
34
+ "name": "svg remains allowed",
35
+ "contentType": "image/svg+xml",
36
+ "hex": "3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f737667223e",
37
+ "expected": false
38
+ },
39
+ {
40
+ "name": "plain bytes remain allowed",
41
+ "contentType": "application/octet-stream",
42
+ "hex": "68656c6c6f2073746f72616765",
43
+ "expected": false
44
+ }
45
+ ],
46
+ "ranges": [
47
+ { "name": "absent", "header": null, "size": 14, "expected": null },
48
+ {
49
+ "name": "full tail",
50
+ "header": "bytes=7-",
51
+ "size": 14,
52
+ "expected": { "start": 7, "end": 13, "header": "bytes=7-13" }
53
+ },
54
+ {
55
+ "name": "bounded",
56
+ "header": "bytes=0-6",
57
+ "size": 14,
58
+ "expected": { "start": 0, "end": 6, "header": "bytes=0-6" }
59
+ },
60
+ {
61
+ "name": "suffix",
62
+ "header": "bytes=-4",
63
+ "size": 14,
64
+ "expected": { "start": 10, "end": 13, "header": "bytes=10-13" }
65
+ },
66
+ {
67
+ "name": "clamped",
68
+ "header": "bytes=10-99",
69
+ "size": 14,
70
+ "expected": { "start": 10, "end": 13, "header": "bytes=10-13" }
71
+ },
72
+ {
73
+ "name": "unsatisfiable",
74
+ "header": "bytes=20-30",
75
+ "size": 14,
76
+ "expected": { "unsatisfiable": true }
77
+ },
78
+ { "name": "multiple ignored", "header": "bytes=0-1,4-5", "size": 14, "expected": null },
79
+ { "name": "malformed ignored", "header": "items=0-1", "size": 14, "expected": null }
80
+ ],
81
+ "ifNoneMatch": [
82
+ { "name": "exact", "header": "\"abc\"", "etag": "\"abc\"", "expected": true },
83
+ { "name": "weak", "header": "W/\"abc\"", "etag": "\"abc\"", "expected": true },
84
+ { "name": "list", "header": "\"no\", W/\"abc\"", "etag": "\"abc\"", "expected": true },
85
+ { "name": "wildcard", "header": "*", "etag": "\"abc\"", "expected": true },
86
+ { "name": "miss", "header": "\"no\"", "etag": "\"abc\"", "expected": false }
87
+ ],
88
+ "ifRange": [
89
+ {
90
+ "name": "exact etag",
91
+ "header": "\"abc\"",
92
+ "etag": "\"abc\"",
93
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
94
+ "expected": true
95
+ },
96
+ {
97
+ "name": "weak etag rejected",
98
+ "header": "W/\"abc\"",
99
+ "etag": "\"abc\"",
100
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
101
+ "expected": false
102
+ },
103
+ {
104
+ "name": "newer date",
105
+ "header": "Wed, 21 Oct 2015 07:29:00 GMT",
106
+ "etag": "\"abc\"",
107
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
108
+ "expected": true
109
+ },
110
+ {
111
+ "name": "older date",
112
+ "header": "Wed, 21 Oct 2015 07:27:00 GMT",
113
+ "etag": "\"abc\"",
114
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
115
+ "expected": false
116
+ },
117
+ {
118
+ "name": "leap second",
119
+ "header": "Wed, 21 Oct 2015 07:28:60 GMT",
120
+ "etag": "\"abc\"",
121
+ "lastModified": "Wed, 21 Oct 2015 07:28:59 GMT",
122
+ "expected": true
123
+ },
124
+ {
125
+ "name": "obsolete RFC 850 date",
126
+ "header": "Sunday, 06-Nov-94 08:49:37 GMT",
127
+ "etag": "\"abc\"",
128
+ "lastModified": "Sun, 06 Nov 1994 08:49:37 GMT",
129
+ "expected": true
130
+ },
131
+ {
132
+ "name": "obsolete asctime date",
133
+ "header": "Sun Nov 6 08:49:37 1994",
134
+ "etag": "\"abc\"",
135
+ "lastModified": "Sun, 06 Nov 1994 08:49:37 GMT",
136
+ "expected": true
137
+ },
138
+ {
139
+ "name": "relative date rejected",
140
+ "header": "tomorrow",
141
+ "etag": "\"abc\"",
142
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
143
+ "expected": false
144
+ },
145
+ {
146
+ "name": "wrong weekday rejected",
147
+ "header": "Thu, 21 Oct 2015 07:29:00 GMT",
148
+ "etag": "\"abc\"",
149
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
150
+ "expected": false
151
+ },
152
+ {
153
+ "name": "invalid calendar date rejected",
154
+ "header": "Fri, 31 Feb 2023 07:29:00 GMT",
155
+ "etag": "\"abc\"",
156
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
157
+ "expected": false
158
+ },
159
+ {
160
+ "name": "out-of-range time rejected",
161
+ "header": "Wed, 21 Oct 2015 07:28:61 GMT",
162
+ "etag": "\"abc\"",
163
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
164
+ "expected": false
165
+ },
166
+ {
167
+ "name": "trailing garbage rejected",
168
+ "header": "Wed, 21 Oct 2015 07:29:00 GMT later",
169
+ "etag": "\"abc\"",
170
+ "lastModified": "Wed, 21 Oct 2015 07:28:00 GMT",
171
+ "expected": false
172
+ }
173
+ ]
174
+ }