@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.
- package/dist/agents/client-registry.d.ts +22 -0
- package/dist/agents/client-registry.js +41 -0
- package/dist/agents/connect-targets.d.ts +51 -6
- package/dist/agents/connect-targets.js +93 -28
- package/dist/brand.d.ts +0 -11
- package/dist/brand.js +0 -20
- package/dist/config/domains.d.ts +15 -6
- package/dist/config/domains.js +24 -10
- package/dist/config/postgres-budget.d.ts +3 -0
- package/dist/config/postgres-budget.js +6 -1
- package/dist/config/postgres-budget.json +2 -1
- package/dist/contracts/abuse.js +1 -1
- package/dist/contracts/access.d.ts +486 -1196
- package/dist/contracts/access.js +323 -1054
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +11 -15
- package/dist/contracts/api-keys.d.ts +16 -0
- package/dist/contracts/api-keys.js +11 -9
- package/dist/contracts/archives.d.ts +1 -2
- package/dist/contracts/archives.js +15 -17
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/builds.js +14 -14
- package/dist/contracts/channels.js +2 -2
- package/dist/contracts/comments.d.ts +143 -99
- package/dist/contracts/comments.js +194 -79
- package/dist/contracts/common.d.ts +12 -12
- package/dist/contracts/common.js +33 -30
- package/dist/contracts/continuation.js +5 -5
- package/dist/contracts/countries.js +2 -7
- package/dist/contracts/data-source-routes.d.ts +20 -0
- package/dist/contracts/data-source-routes.js +15 -0
- package/dist/contracts/device-auth.d.ts +13 -9
- package/dist/contracts/device-auth.js +12 -12
- package/dist/contracts/docs.d.ts +10 -10
- package/dist/contracts/domains.js +39 -33
- package/dist/contracts/enums.d.ts +4 -14
- package/dist/contracts/enums.js +3 -10
- package/dist/contracts/error-code-meta.d.ts +122 -56
- package/dist/contracts/error-code-meta.js +43 -19
- package/dist/contracts/error-codes.d.ts +7 -4
- package/dist/contracts/error-codes.js +48 -24
- package/dist/contracts/events.d.ts +22 -15
- package/dist/contracts/events.js +33 -5
- package/dist/contracts/execution.d.ts +238 -0
- package/dist/contracts/execution.js +309 -0
- package/dist/contracts/feature-lifecycle.d.ts +39 -188
- package/dist/contracts/feature-lifecycle.js +52 -196
- package/dist/contracts/features.d.ts +13 -115
- package/dist/contracts/features.js +5 -35
- package/dist/contracts/functions.d.ts +597 -0
- package/dist/contracts/functions.js +475 -0
- package/dist/contracts/grants.d.ts +1175 -0
- package/dist/contracts/grants.js +783 -0
- package/dist/contracts/ids.d.ts +7 -5
- package/dist/contracts/ids.js +7 -7
- package/dist/contracts/internal.js +2 -2
- package/dist/contracts/mcp.d.ts +17 -556
- package/dist/contracts/mcp.js +9 -103
- package/dist/contracts/me.js +3 -4
- package/dist/contracts/notifications.d.ts +108 -0
- package/dist/contracts/notifications.js +54 -0
- package/dist/contracts/oauth-resources.d.ts +22 -5
- package/dist/contracts/oauth-resources.js +23 -2
- package/dist/contracts/operations.d.ts +0 -48
- package/dist/contracts/operations.js +0 -19
- package/dist/contracts/pages.d.ts +10 -14
- package/dist/contracts/pages.js +17 -21
- package/dist/contracts/plan-policy.d.ts +1 -8
- package/dist/contracts/plan-policy.js +1 -4
- package/dist/contracts/platform.d.ts +78 -30
- package/dist/contracts/platform.js +69 -20
- package/dist/contracts/problem-document.d.ts +14 -0
- package/dist/contracts/problem-document.js +35 -0
- package/dist/contracts/push-new.d.ts +41 -2
- package/dist/contracts/push-new.js +4 -0
- package/dist/contracts/quotas.d.ts +0 -1
- package/dist/contracts/quotas.js +0 -5
- package/dist/contracts/repository-connections.d.ts +1 -1
- package/dist/contracts/resources.d.ts +16 -71
- package/dist/contracts/resources.js +13 -16
- package/dist/contracts/runtime-api.d.ts +666 -303
- package/dist/contracts/runtime-api.js +272 -113
- package/dist/contracts/runtime-app.d.ts +168 -0
- package/dist/contracts/runtime-app.js +42 -0
- package/dist/contracts/runtime-db.d.ts +424 -0
- package/dist/contracts/runtime-db.js +108 -0
- package/dist/contracts/runtime-storage.d.ts +120 -0
- package/dist/contracts/runtime-storage.js +40 -0
- package/dist/contracts/sf-config-v1.d.ts +54 -1
- package/dist/contracts/sf-config-v1.js +2 -2
- package/dist/contracts/sites.d.ts +6 -0
- package/dist/contracts/sites.js +30 -11
- package/dist/contracts/space-config.d.ts +91 -121
- package/dist/contracts/space-config.js +464 -42
- package/dist/contracts/spaces.d.ts +300 -171
- package/dist/contracts/spaces.js +128 -98
- package/dist/contracts/superadmin-emails.d.ts +2 -2
- package/dist/contracts/superadmin-spaces.d.ts +26 -130
- package/dist/contracts/superadmin-spaces.js +22 -0
- package/dist/contracts/superadmin-tenants.js +4 -4
- package/dist/contracts/superadmin.d.ts +4 -0
- package/dist/contracts/superadmin.js +14 -1
- package/dist/contracts/tags.d.ts +6 -6
- package/dist/contracts/tags.js +1 -1
- package/dist/contracts/teams.d.ts +9 -9
- package/dist/contracts/teams.js +25 -16
- package/dist/contracts/theme-json.js +1 -1
- package/dist/contracts/transfers.d.ts +0 -1
- package/dist/contracts/transfers.js +4 -4
- package/dist/contracts/variables.js +3 -3
- package/dist/contracts/zero.d.ts +384 -596
- package/dist/contracts/zero.js +167 -299
- package/dist/dashboard-paths/index.d.ts +18 -3
- package/dist/dashboard-paths/index.js +19 -0
- package/dist/docs/agent-handoff-document.d.ts +2 -2
- package/dist/docs/agent-handoff-document.js +8 -7
- package/dist/docs/agent-prose.d.ts +22 -3
- package/dist/docs/agent-prose.js +227 -55
- package/dist/docs/agent-setup.d.ts +14 -11
- package/dist/docs/agent-setup.js +289 -61
- package/dist/docs/agent-solutions.d.ts +6 -6
- package/dist/docs/agent-solutions.js +24 -22
- package/dist/docs/catalog.d.ts +53 -98
- package/dist/docs/catalog.js +90 -117
- package/dist/docs/error-docs.js +304 -236
- package/dist/docs/skill-distribution.d.ts +2 -0
- package/dist/docs/skill-distribution.js +7 -4
- package/dist/docs/skills.d.ts +546 -0
- package/dist/docs/skills.js +340 -0
- package/dist/utils/auth-redirect.d.ts +1 -3
- package/dist/utils/auth-redirect.js +1 -21
- package/dist/utils/browser-credential.d.ts +19 -0
- package/dist/utils/browser-credential.js +62 -0
- package/dist/utils/build-settings.d.ts +2 -0
- package/dist/utils/build-settings.js +61 -9
- package/dist/utils/comment-avatars.d.ts +7 -0
- package/dist/utils/comment-avatars.js +42 -0
- package/dist/utils/content-type.d.ts +3 -3
- package/dist/utils/content-type.js +38 -61
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/credential-policy.js +4 -0
- package/dist/utils/domain-diagnostics.d.ts +5 -5
- package/dist/utils/domain-diagnostics.js +21 -10
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/grants.d.ts +53 -0
- package/dist/utils/grants.js +160 -0
- package/dist/utils/local-space-state.d.ts +78 -4
- package/dist/utils/local-space-state.js +384 -82
- package/dist/utils/one-shot-replay.d.ts +14 -0
- package/dist/utils/one-shot-replay.js +21 -0
- package/dist/utils/page-preview.d.ts +7 -0
- package/dist/utils/page-preview.js +9 -0
- package/dist/utils/pages.d.ts +2 -5
- package/dist/utils/pages.js +64 -33
- package/dist/utils/publish-form-data.d.ts +22 -0
- package/dist/utils/publish-form-data.js +26 -0
- package/dist/utils/publish-policy.d.ts +34 -4
- package/dist/utils/publish-policy.js +149 -62
- package/dist/utils/query-keys.d.ts +8 -10
- package/dist/utils/query-keys.js +14 -10
- package/dist/utils/runtime-paths.d.ts +3 -0
- package/dist/utils/runtime-paths.js +7 -0
- package/dist/utils/secure-local-file.d.ts +73 -0
- package/dist/utils/secure-local-file.js +1373 -0
- package/dist/utils/space-config.d.ts +17 -4
- package/dist/utils/space-config.js +151 -8
- package/dist/utils/static-runtime-policy.d.ts +5 -0
- package/dist/utils/static-runtime-policy.js +54 -10
- package/dist/utils/storage-policy.d.ts +20 -0
- package/dist/utils/storage-policy.fixtures.json +174 -0
- package/dist/utils/storage-policy.js +193 -0
- package/dist/utils/upload-session.d.ts +15 -1
- package/dist/utils/upload-session.js +68 -2
- package/dist/utils/version-file-browser.d.ts +13 -0
- package/dist/utils/version-file-browser.js +59 -0
- package/package.json +20 -1
- package/dist/contracts/mounts.d.ts +0 -142
- package/dist/contracts/mounts.js +0 -56
- package/dist/utils/access-grant.d.ts +0 -14
- package/dist/utils/access-grant.js +0 -88
- package/dist/utils/access-match.d.ts +0 -46
- package/dist/utils/access-match.fixtures.json +0 -513
- package/dist/utils/access-match.js +0 -312
- package/dist/utils/access-verdict.fixtures.json +0 -421
- package/dist/utils/asset-fingerprint.d.ts +0 -53
- package/dist/utils/asset-fingerprint.js +0 -574
- package/dist/utils/sf-config-v1.d.ts +0 -2
- package/dist/utils/sf-config-v1.fixtures.json +0 -9
- 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
|
|
17
|
-
* Returns the first blocking issue, or null when the overlay is
|
|
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:
|
|
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
|
|
102
|
-
* Returns the first blocking issue, or null when the overlay is
|
|
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
|
-
//
|
|
118
|
-
//
|
|
119
|
-
const ROUTING_KEYS = new Set(["
|
|
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 (
|
|
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
|
-
|
|
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
|
|
12
|
-
// internal-redirect/sendfile family
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
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
|
-
|
|
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
|
+
}
|