veryfront 0.1.1213 → 0.1.1215
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/esm/deno.js +1 -1
- package/esm/src/agent/conversation/delegation-policy.d.ts +1 -3
- package/esm/src/agent/conversation/delegation-policy.d.ts.map +1 -1
- package/esm/src/agent/conversation/delegation-policy.js +1 -3
- package/esm/src/agent/index.d.ts +1 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +1 -1
- package/esm/src/agent/runtime/agent-loop-skill-state.d.ts +10 -12
- package/esm/src/agent/runtime/agent-loop-skill-state.d.ts.map +1 -1
- package/esm/src/agent/runtime/agent-loop-skill-state.js +11 -17
- package/esm/src/agent/runtime/agent-runtime-step.d.ts +0 -1
- package/esm/src/agent/runtime/agent-runtime-step.d.ts.map +1 -1
- package/esm/src/agent/runtime/agent-runtime-step.js +2 -2
- package/esm/src/agent/runtime/index.d.ts +1 -1
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +7 -13
- package/esm/src/agent/runtime/load-skill-tool.d.ts +2 -2
- package/esm/src/agent/runtime/load-skill-tool.d.ts.map +1 -1
- package/esm/src/agent/runtime/load-skill-tool.js +12 -68
- package/esm/src/agent/runtime/project-skill-catalog.d.ts.map +1 -1
- package/esm/src/agent/runtime/project-skill-catalog.js +1 -6
- package/esm/src/agent/runtime/skill-metadata.d.ts +4 -18
- package/esm/src/agent/runtime/skill-metadata.d.ts.map +1 -1
- package/esm/src/agent/runtime/skill-metadata.js +17 -78
- package/esm/src/agent/runtime/skill-policy-enforcement.d.ts +1 -5
- package/esm/src/agent/runtime/skill-policy-enforcement.d.ts.map +1 -1
- package/esm/src/agent/runtime/skill-policy-enforcement.js +12 -55
- package/esm/src/agent/runtime/skill-prompt.d.ts +2 -2
- package/esm/src/agent/runtime/skill-prompt.d.ts.map +1 -1
- package/esm/src/agent/runtime/skill-prompt.js +8 -44
- package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
- package/esm/src/platform/adapters/index.d.ts +25 -0
- package/esm/src/platform/adapters/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/index.js +23 -0
- package/esm/src/platform/adapters/security/index.d.ts +8 -0
- package/esm/src/platform/adapters/security/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/security/index.js +7 -0
- package/esm/src/platform/adapters/token/factory.d.ts +10 -0
- package/esm/src/platform/adapters/token/factory.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/factory.js +79 -0
- package/esm/src/platform/adapters/token/index.d.ts +9 -0
- package/esm/src/platform/adapters/token/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/index.js +8 -0
- package/esm/src/platform/adapters/token/integration.d.ts +6 -0
- package/esm/src/platform/adapters/token/integration.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/integration.js +61 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.d.ts +22 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.js +88 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.d.ts +15 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.js +212 -0
- package/esm/src/platform/adapters/token/veryfront/index.d.ts +10 -0
- package/esm/src/platform/adapters/token/veryfront/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/index.js +9 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts +20 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.js +52 -0
- package/esm/src/platform/adapters/token/veryfront/types.d.ts +86 -0
- package/esm/src/platform/adapters/token/veryfront/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/types.js +81 -0
- package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
- package/esm/src/proxy/handler.d.ts.map +1 -1
- package/esm/src/proxy/handler.js +17 -1
- package/esm/src/security/http/auth.d.ts.map +1 -1
- package/esm/src/security/http/auth.js +7 -0
- package/esm/src/security/http/config.d.ts +5 -0
- package/esm/src/security/http/config.d.ts.map +1 -1
- package/esm/src/security/http/config.js +16 -1
- package/esm/src/security/http/csp-report-endpoint.d.ts +29 -0
- package/esm/src/security/http/csp-report-endpoint.d.ts.map +1 -0
- package/esm/src/security/http/csp-report-endpoint.js +30 -0
- package/esm/src/security/http/csrf/csrf-handler.d.ts.map +1 -1
- package/esm/src/security/http/csrf/csrf-handler.js +6 -0
- package/esm/src/security/http/derived-csp-cache.d.ts +33 -0
- package/esm/src/security/http/derived-csp-cache.d.ts.map +1 -0
- package/esm/src/security/http/derived-csp-cache.js +101 -0
- package/esm/src/security/http/derived-csp-origins.d.ts +54 -0
- package/esm/src/security/http/derived-csp-origins.d.ts.map +1 -0
- package/esm/src/security/http/derived-csp-origins.js +156 -0
- package/esm/src/security/http/response/security-handler.d.ts +1 -2
- package/esm/src/security/http/response/security-handler.d.ts.map +1 -1
- package/esm/src/security/http/response/security-handler.js +25 -9
- package/esm/src/security/sandbox/constants.d.ts +3 -0
- package/esm/src/security/sandbox/constants.d.ts.map +1 -0
- package/esm/src/security/sandbox/constants.js +2 -0
- package/esm/src/security/sandbox/deno-sandbox.d.ts +71 -0
- package/esm/src/security/sandbox/deno-sandbox.d.ts.map +1 -0
- package/esm/src/security/sandbox/deno-sandbox.js +184 -0
- package/esm/src/security/sandbox/permission-system.d.ts +11 -0
- package/esm/src/security/sandbox/permission-system.d.ts.map +1 -0
- package/esm/src/security/sandbox/permission-system.js +85 -0
- package/esm/src/server/handlers/request/csp-report.handler.d.ts +56 -0
- package/esm/src/server/handlers/request/csp-report.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/csp-report.handler.js +189 -0
- package/esm/src/server/runtime-handler/index.d.ts +1 -1
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +3 -0
- package/esm/src/server/runtime-handler/project-runtime-context.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/project-runtime-context.js +97 -1
- package/esm/src/server/utils/domain-parser.d.ts +9 -0
- package/esm/src/server/utils/domain-parser.d.ts.map +1 -1
- package/esm/src/server/utils/domain-parser.js +11 -0
- package/esm/src/skill/allowed-tools.d.ts +18 -42
- package/esm/src/skill/allowed-tools.d.ts.map +1 -1
- package/esm/src/skill/allowed-tools.js +31 -182
- package/esm/src/skill/index.d.ts +1 -1
- package/esm/src/skill/index.d.ts.map +1 -1
- package/esm/src/skill/index.js +2 -2
- package/esm/src/skill/parser.d.ts.map +1 -1
- package/esm/src/skill/parser.js +6 -37
- package/esm/src/skill/tools.d.ts.map +1 -1
- package/esm/src/skill/tools.js +2 -3
- package/esm/src/skill/types.d.ts +1 -3
- package/esm/src/skill/types.d.ts.map +1 -1
- package/esm/src/skill/validation.d.ts.map +1 -1
- package/esm/src/skill/validation.js +31 -3
- package/esm/src/types/server.d.ts +10 -0
- package/esm/src/types/server.d.ts.map +1 -1
- package/esm/src/utils/cors-policy-limits.d.ts +9 -2
- package/esm/src/utils/cors-policy-limits.d.ts.map +1 -1
- package/esm/src/utils/cors-policy-limits.js +8 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/esm/src/workflow/api.d.ts +2 -2
- package/package.json +8 -8
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Storage Adapter Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for token storage backends.
|
|
5
|
+
* Tokens are encrypted client-side before being sent to the backend.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_TOKEN_STORAGE_REQUEST_TIMEOUT_MS = 30000;
|
|
8
|
+
/**
|
|
9
|
+
* Token storage adapter interface
|
|
10
|
+
*
|
|
11
|
+
* Simple key-value interface for storing encrypted tokens.
|
|
12
|
+
* Keys are formatted as "{userId}:{serviceId}" (e.g., "user123:gmail").
|
|
13
|
+
* Values are encrypted token blobs (client encrypts before sending).
|
|
14
|
+
*/
|
|
15
|
+
export interface TokenStorageAdapter {
|
|
16
|
+
/** Get encrypted token by key */
|
|
17
|
+
get(key: string): Promise<string | null>;
|
|
18
|
+
/** Set encrypted token by key (upsert) */
|
|
19
|
+
set(key: string, value: string): Promise<void>;
|
|
20
|
+
/** Delete token by key (idempotent) */
|
|
21
|
+
delete(key: string): Promise<void>;
|
|
22
|
+
/** List all keys with optional prefix filter */
|
|
23
|
+
list?(prefix?: string): Promise<string[]>;
|
|
24
|
+
/** Initialize the adapter (e.g., verify connection) */
|
|
25
|
+
initialize?(): Promise<void>;
|
|
26
|
+
/** Cleanup resources */
|
|
27
|
+
dispose?(): void;
|
|
28
|
+
}
|
|
29
|
+
/** Veryfront Cloud token-storage options. */
|
|
30
|
+
export interface VeryfrontTokenStorageOptions {
|
|
31
|
+
/** API token for authentication */
|
|
32
|
+
apiToken: string;
|
|
33
|
+
/** Project slug */
|
|
34
|
+
projectSlug: string;
|
|
35
|
+
/** API base URL (defaults to production) */
|
|
36
|
+
apiBaseUrl?: string;
|
|
37
|
+
/** Per-request timeout in milliseconds. Defaults to 30000ms. */
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
/** Retry configuration */
|
|
40
|
+
retry?: {
|
|
41
|
+
/** Retries after the initial request, from 0 through 9. */
|
|
42
|
+
maxRetries?: number;
|
|
43
|
+
/** Initial retry delay in whole milliseconds. */
|
|
44
|
+
initialDelay?: number;
|
|
45
|
+
/** Maximum retry delay in whole milliseconds. */
|
|
46
|
+
maxDelay?: number;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Configuration for token storage adapters.
|
|
51
|
+
*
|
|
52
|
+
* The discriminator is optional only for the empty/default memory config.
|
|
53
|
+
* Cloud options can never be combined with, or silently downgraded to, the
|
|
54
|
+
* in-memory adapter.
|
|
55
|
+
*/
|
|
56
|
+
export type TokenStorageAdapterConfig = {
|
|
57
|
+
type?: "memory";
|
|
58
|
+
veryfront?: never;
|
|
59
|
+
} | {
|
|
60
|
+
type: "veryfront-api";
|
|
61
|
+
veryfront: VeryfrontTokenStorageOptions;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Internal config with defaults applied
|
|
65
|
+
*/
|
|
66
|
+
export interface VeryfrontTokenConfig {
|
|
67
|
+
apiBaseUrl: string;
|
|
68
|
+
apiToken: string;
|
|
69
|
+
projectSlug: string;
|
|
70
|
+
/** Request timeout in milliseconds. */
|
|
71
|
+
timeoutMs: number;
|
|
72
|
+
retry: {
|
|
73
|
+
maxRetries: number;
|
|
74
|
+
initialDelay: number;
|
|
75
|
+
maxDelay: number;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create verified config from adapter config
|
|
80
|
+
*/
|
|
81
|
+
export declare function createTokenConfig(config: TokenStorageAdapterConfig): VeryfrontTokenConfig;
|
|
82
|
+
export declare function assertTokenStorageKey(key: string): void;
|
|
83
|
+
export declare function assertTokenStoragePrefix(prefix: string | undefined): void;
|
|
84
|
+
export declare function assertTokenStorageValue(value: string): void;
|
|
85
|
+
export { TOKEN_STORAGE_ERROR } from "../../../../errors/index.js";
|
|
86
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/token/veryfront/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,eAAO,MAAM,wCAAwC,QAAS,CAAC;AAG/D;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEzC,0CAA0C;IAC1C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C,uCAAuC;IACvC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC,gDAAgD;IAChD,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE1C,uDAAuD;IACvD,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B,wBAAwB;IACxB,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB;AAED,6CAA6C;AAC7C,MAAM,WAAW,4BAA4B;IAC3C,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,KAAK,CAAC,EAAE;QACN,2DAA2D;QAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iDAAiD;QACjD,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,iDAAiD;QACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GACjC;IAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,SAAS,EAAE,4BAA4B,CAAA;CAAE,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAgBD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,GAAG,oBAAoB,CAiCzF;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGzE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAI3D;AA8BD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Storage Adapter Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for token storage backends.
|
|
5
|
+
* Tokens are encrypted client-side before being sent to the backend.
|
|
6
|
+
*/
|
|
7
|
+
import { createError, toError } from "../../../../errors/index.js";
|
|
8
|
+
import { normalizeVeryfrontApiRetryConfig } from "../../../../utils/config-resource-limits.js";
|
|
9
|
+
import { normalizeTimerDurationMs } from "../../../../utils/timer.js";
|
|
10
|
+
export const DEFAULT_TOKEN_STORAGE_REQUEST_TIMEOUT_MS = 30_000;
|
|
11
|
+
const MAX_TOKEN_STORAGE_KEY_CODE_UNITS = 4_096;
|
|
12
|
+
function requireVeryfrontConfig(config) {
|
|
13
|
+
const veryfront = config.veryfront;
|
|
14
|
+
if (veryfront)
|
|
15
|
+
return veryfront;
|
|
16
|
+
throw toError(createError({
|
|
17
|
+
type: "config",
|
|
18
|
+
message: "Veryfront token adapter requires veryfront configuration",
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create verified config from adapter config
|
|
23
|
+
*/
|
|
24
|
+
export function createTokenConfig(config) {
|
|
25
|
+
const veryfront = requireVeryfrontConfig(config);
|
|
26
|
+
const apiToken = veryfront.apiToken;
|
|
27
|
+
if (!isNonBlankString(apiToken)) {
|
|
28
|
+
throw toError(createError({
|
|
29
|
+
type: "config",
|
|
30
|
+
message: "Veryfront token adapter requires apiToken",
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
const projectSlug = veryfront.projectSlug;
|
|
34
|
+
if (!isNonBlankString(projectSlug)) {
|
|
35
|
+
throw toError(createError({
|
|
36
|
+
type: "config",
|
|
37
|
+
message: "Veryfront token adapter requires projectSlug",
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
apiBaseUrl: veryfront.apiBaseUrl ?? "https://api.veryfront.com",
|
|
42
|
+
apiToken,
|
|
43
|
+
projectSlug,
|
|
44
|
+
timeoutMs: normalizeTimerDurationMs(veryfront.timeoutMs ?? DEFAULT_TOKEN_STORAGE_REQUEST_TIMEOUT_MS, "Token storage timeoutMs"),
|
|
45
|
+
retry: normalizeVeryfrontApiRetryConfig(veryfront.retry),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function assertTokenStorageKey(key) {
|
|
49
|
+
assertBoundedTokenStorageIdentifier(key, "key", false);
|
|
50
|
+
}
|
|
51
|
+
export function assertTokenStoragePrefix(prefix) {
|
|
52
|
+
if (prefix === undefined)
|
|
53
|
+
return;
|
|
54
|
+
assertBoundedTokenStorageIdentifier(prefix, "prefix", true);
|
|
55
|
+
}
|
|
56
|
+
export function assertTokenStorageValue(value) {
|
|
57
|
+
if (typeof value !== "string") {
|
|
58
|
+
throw new TypeError("Token storage value must be a string");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isNonBlankString(value) {
|
|
62
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
63
|
+
}
|
|
64
|
+
function assertBoundedTokenStorageIdentifier(value, label, allowEmpty) {
|
|
65
|
+
if (typeof value !== "string") {
|
|
66
|
+
throw new TypeError(`Token storage ${label} must be a string`);
|
|
67
|
+
}
|
|
68
|
+
if (!allowEmpty && value.length === 0) {
|
|
69
|
+
throw new TypeError("Token storage key must be non-empty");
|
|
70
|
+
}
|
|
71
|
+
if (value.length > MAX_TOKEN_STORAGE_KEY_CODE_UNITS) {
|
|
72
|
+
throw new RangeError(`Token storage ${label} must not exceed ${MAX_TOKEN_STORAGE_KEY_CODE_UNITS} code units`);
|
|
73
|
+
}
|
|
74
|
+
for (let index = 0; index < value.length; index++) {
|
|
75
|
+
const codeUnit = value.charCodeAt(index);
|
|
76
|
+
if (codeUnit <= 0x1f || codeUnit === 0x7f) {
|
|
77
|
+
throw new TypeError(`Token storage ${label} must not contain control characters`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export { TOKEN_STORAGE_ERROR } from "../../../../errors/index.js";
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Client identity for guarded egress, standing in for the runtime-supplied
|
|
8
8
|
* `user-agent` (`node`, `Deno/x.y.z`) that this transport cannot inherit.
|
|
9
9
|
*/
|
|
10
|
-
export declare const DEFAULT_OUTBOUND_USER_AGENT = "veryfront/0.1.
|
|
10
|
+
export declare const DEFAULT_OUTBOUND_USER_AGENT = "veryfront/0.1.1215";
|
|
11
11
|
/**
|
|
12
12
|
* Fill in the request headers a plain `fetch` attaches on its own, leaving any
|
|
13
13
|
* the caller set untouched. This transport talks to `node:http` directly, so
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"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"}
|
package/esm/src/proxy/handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TokenManager } from "./token-manager.js";
|
|
2
|
-
import { parseProjectDomain } from "../server/utils/domain-parser.js";
|
|
2
|
+
import { isHostedVeryfrontDomain, parseProjectDomain, } from "../server/utils/domain-parser.js";
|
|
3
3
|
import { computeContentSourceId } from "../cache/keys.js";
|
|
4
4
|
import { getEnv } from "../platform/compat/process.js";
|
|
5
5
|
import { checkProtectedProxyAccess } from "./proxy-access-control.js";
|
|
@@ -469,6 +469,22 @@ export function createProxyHandler(options) {
|
|
|
469
469
|
expectedProjectId: resolvedProjectId,
|
|
470
470
|
}, logger);
|
|
471
471
|
if (!projectSlug && parsedDomain.isVeryfrontDomain) {
|
|
472
|
+
// A hosted environment root (staging.veryfront.com) names no project and
|
|
473
|
+
// nothing downstream can supply one, so forwarding only sends
|
|
474
|
+
// x-project-slug: "" and earns 502 "Missing project context" — a
|
|
475
|
+
// configuration gap reported as an upstream failure. A custom domain in
|
|
476
|
+
// that state already answers 404.
|
|
477
|
+
//
|
|
478
|
+
// Locally the same shape means something else: on lvh.me and friends a
|
|
479
|
+
// project-less host is how the project chooser is reached, so those keep
|
|
480
|
+
// forwarding. See ProjectsHandler, enabled for exactly this state.
|
|
481
|
+
if (isHostedVeryfrontDomain(host)) {
|
|
482
|
+
logger?.info("No project for hosted veryfront domain", { host });
|
|
483
|
+
return createProxyErrorContext(base, {
|
|
484
|
+
status: 404,
|
|
485
|
+
message: `No project configured for domain: ${host}`,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
472
488
|
return {
|
|
473
489
|
token: undefined,
|
|
474
490
|
projectSlug: undefined,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../../src/src/security/http/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../../src/src/security/http/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,sBAAsB,CAAC;AAoJ9B,qBAAa,WAAY,SAAQ,WAAW;IAC1C,QAAQ,EAAE,eAAe,CAIvB;IAEF,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAsBjE,OAAO,CAAC,WAAW;IA4CnB,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,iBAAiB;CAY1B"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isCspReportRequest } from "./csp-report-endpoint.js";
|
|
1
2
|
import { BaseHandler } from "./base-handler.js";
|
|
2
3
|
import { encodeBase64 } from "../../utils/index.js";
|
|
3
4
|
import { constantTimeEqual } from "../utils/constant-time.js";
|
|
@@ -121,6 +122,12 @@ export class AuthHandler extends BaseHandler {
|
|
|
121
122
|
handle(req, ctx) {
|
|
122
123
|
if (req.method.toUpperCase() === "OPTIONS")
|
|
123
124
|
return Promise.resolve(this.continue());
|
|
125
|
+
// Same reasoning as the CSRF gate: a browser reports a violation without
|
|
126
|
+
// credentials, so a protected project would collect nothing. See
|
|
127
|
+
// `isCspReportRequest` for why exempting it discloses nothing.
|
|
128
|
+
if (isCspReportRequest(req.method, new URL(req.url).pathname)) {
|
|
129
|
+
return Promise.resolve(this.continue());
|
|
130
|
+
}
|
|
124
131
|
const auth = this.resolveAuth(ctx);
|
|
125
132
|
if (!auth)
|
|
126
133
|
return Promise.resolve(this.continue());
|
|
@@ -11,6 +11,11 @@ export interface DeriveSecurityContextOptions {
|
|
|
11
11
|
* classification may override it explicitly.
|
|
12
12
|
*/
|
|
13
13
|
productionDefaults?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Origins derived from the project's released source. Platform-supplied;
|
|
16
|
+
* anything a project config carries under this name is discarded.
|
|
17
|
+
*/
|
|
18
|
+
derivedCsp?: SecurityConfig["derivedCsp"];
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Check a standalone security configuration against the canonical project
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/src/security/http/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/src/security/http/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;CAC3C;AA4LD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,cAAc,CAO/E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAIhC;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,CAAC,EAAE,eAAe,EACrB,OAAO,GAAE,4BAAiC,GACzC,sBAAsB,CA4BxB;AAED,qBAAa,oBAAoB;IAM7B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,cAAc,CAAC;IACvB,OAAO,CAAC,iBAAiB;IAR3B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAA8B;gBAGvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,cAAc,CAAC,EAAE,eAAe,YAAA,EAChC,iBAAiB,UAAQ;IAG7B,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBrB,IAAI;IAKlB,OAAO,CAAC,WAAW;IAiBnB,iBAAiB,IAAI,cAAc,GAAG,IAAI;IAI1C,aAAa,IAAI,cAAc,CAAC,MAAM,CAAC;IAIvC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,GAAE,MAAwB,GAAG,MAAM;IAIjE,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;CAQpE"}
|
|
@@ -160,8 +160,12 @@ function readProductionDefaults(options) {
|
|
|
160
160
|
const prototype = Object.getPrototypeOf(options);
|
|
161
161
|
if (prototype !== Object.prototype && prototype !== null)
|
|
162
162
|
return invalidSecurityConfig();
|
|
163
|
+
// Allowlisted rather than ignored: this object reaches a security decision,
|
|
164
|
+
// so an unrecognised key is a caller mistake worth failing on, not a
|
|
165
|
+
// silently discarded one.
|
|
166
|
+
const allowed = new Set(["productionDefaults", "derivedCsp"]);
|
|
163
167
|
const keys = Reflect.ownKeys(options);
|
|
164
|
-
if (keys.some((key) => key
|
|
168
|
+
if (keys.some((key) => !allowed.has(key)))
|
|
165
169
|
return invalidSecurityConfig();
|
|
166
170
|
const descriptor = Object.getOwnPropertyDescriptor(options, "productionDefaults");
|
|
167
171
|
if (!descriptor)
|
|
@@ -228,6 +232,17 @@ export function deriveSecurityContext(cfg, options = {}) {
|
|
|
228
232
|
if (normalized.csrf === undefined && productionDefaults) {
|
|
229
233
|
normalized.csrf = true;
|
|
230
234
|
}
|
|
235
|
+
// Assigned unconditionally, never merged. `SecurityConfig` carries an index
|
|
236
|
+
// signature, so the clone above would happily copy a `derivedCsp` a project
|
|
237
|
+
// wrote in its own config; overwriting here makes the platform the only
|
|
238
|
+
// author of this layer. Deleting when absent keeps a stale project value
|
|
239
|
+
// from surviving as the derived set.
|
|
240
|
+
if (options.derivedCsp && Object.keys(options.derivedCsp).length > 0) {
|
|
241
|
+
normalized.derivedCsp = options.derivedCsp;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
delete normalized.derivedCsp;
|
|
245
|
+
}
|
|
231
246
|
const securityConfig = Object.freeze(normalized);
|
|
232
247
|
return Object.freeze({
|
|
233
248
|
securityConfig,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the platform CSP asks browsers to send violation reports.
|
|
3
|
+
*
|
|
4
|
+
* The policy is built in the security module and the reports are received by a
|
|
5
|
+
* server handler, so the path and the group name live here, on the side both
|
|
6
|
+
* can depend on. They have to agree: a `report-to` directive naming a group the
|
|
7
|
+
* `Reporting-Endpoints` header does not define makes the browser send nothing,
|
|
8
|
+
* and it fails silently.
|
|
9
|
+
*/
|
|
10
|
+
/** Path serving {@link CSP_REPORT_ENDPOINT_NAME}, on the project's own origin. */
|
|
11
|
+
export declare const CSP_REPORT_PATH = "/_vf/csp-report";
|
|
12
|
+
/** Reporting group tying the CSP `report-to` directive to `Reporting-Endpoints`. */
|
|
13
|
+
export declare const CSP_REPORT_ENDPOINT_NAME = "veryfront-csp";
|
|
14
|
+
/**
|
|
15
|
+
* Whether a request is the platform's own CSP report submission.
|
|
16
|
+
*
|
|
17
|
+
* The auth and CSRF gates both run ahead of the handler and both refuse a
|
|
18
|
+
* browser-generated report: it carries no credentials and no CSRF token,
|
|
19
|
+
* because a violation report is not a user action. Left alone, any project
|
|
20
|
+
* enabling either feature advertises a reporting endpoint that silently
|
|
21
|
+
* collects nothing.
|
|
22
|
+
*
|
|
23
|
+
* Exempting it is safe on the terms those gates exist for. The endpoint reads
|
|
24
|
+
* no credentials, changes no state, and answers 204 regardless of the body, so
|
|
25
|
+
* it discloses nothing about a protected project; what it writes is a bounded,
|
|
26
|
+
* sanitized, rate-limited log line.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isCspReportRequest(method: string, pathname: string): boolean;
|
|
29
|
+
//# sourceMappingURL=csp-report-endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp-report-endpoint.d.ts","sourceRoot":"","sources":["../../../../src/src/security/http/csp-report-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,kFAAkF;AAClF,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the platform CSP asks browsers to send violation reports.
|
|
3
|
+
*
|
|
4
|
+
* The policy is built in the security module and the reports are received by a
|
|
5
|
+
* server handler, so the path and the group name live here, on the side both
|
|
6
|
+
* can depend on. They have to agree: a `report-to` directive naming a group the
|
|
7
|
+
* `Reporting-Endpoints` header does not define makes the browser send nothing,
|
|
8
|
+
* and it fails silently.
|
|
9
|
+
*/
|
|
10
|
+
/** Path serving {@link CSP_REPORT_ENDPOINT_NAME}, on the project's own origin. */
|
|
11
|
+
export const CSP_REPORT_PATH = "/_vf/csp-report";
|
|
12
|
+
/** Reporting group tying the CSP `report-to` directive to `Reporting-Endpoints`. */
|
|
13
|
+
export const CSP_REPORT_ENDPOINT_NAME = "veryfront-csp";
|
|
14
|
+
/**
|
|
15
|
+
* Whether a request is the platform's own CSP report submission.
|
|
16
|
+
*
|
|
17
|
+
* The auth and CSRF gates both run ahead of the handler and both refuse a
|
|
18
|
+
* browser-generated report: it carries no credentials and no CSRF token,
|
|
19
|
+
* because a violation report is not a user action. Left alone, any project
|
|
20
|
+
* enabling either feature advertises a reporting endpoint that silently
|
|
21
|
+
* collects nothing.
|
|
22
|
+
*
|
|
23
|
+
* Exempting it is safe on the terms those gates exist for. The endpoint reads
|
|
24
|
+
* no credentials, changes no state, and answers 204 regardless of the body, so
|
|
25
|
+
* it discloses nothing about a protected project; what it writes is a bounded,
|
|
26
|
+
* sanitized, rate-limited log line.
|
|
27
|
+
*/
|
|
28
|
+
export function isCspReportRequest(method, pathname) {
|
|
29
|
+
return method.toUpperCase() === "POST" && pathname === CSP_REPORT_PATH;
|
|
30
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf-handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/security/http/csrf/csrf-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;
|
|
1
|
+
{"version":3,"file":"csrf-handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/security/http/csrf/csrf-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,aAAa,EACd,MAAM,yBAAyB,CAAC;AAIjC,qBAAa,WAAY,SAAQ,WAAW;IAC1C,QAAQ,EAAE,eAAe,CAIvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA4CxE"}
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*
|
|
41
41
|
* @module security/http/csrf/csrf-handler
|
|
42
42
|
*/
|
|
43
|
+
import { isCspReportRequest } from "../csp-report-endpoint.js";
|
|
43
44
|
import { BaseHandler } from "../base-handler.js";
|
|
44
45
|
import { validateCsrf } from "../../csrf/helpers.js";
|
|
45
46
|
const CSRF_SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
@@ -60,6 +61,11 @@ export class CsrfHandler extends BaseHandler {
|
|
|
60
61
|
if (CSRF_SAFE_METHODS.has(method))
|
|
61
62
|
return this.continue();
|
|
62
63
|
const { pathname } = new URL(req.url);
|
|
64
|
+
// A CSP violation report is not a user action and carries no token. See
|
|
65
|
+
// `isCspReportRequest`; relying on a project to add it to `excludePaths`
|
|
66
|
+
// would make reporting another thing a project has to configure first.
|
|
67
|
+
if (isCspReportRequest(method, pathname))
|
|
68
|
+
return this.continue();
|
|
63
69
|
// Check exclude paths
|
|
64
70
|
if (typeof csrfConfig === "object" && csrfConfig.excludePaths?.length) {
|
|
65
71
|
for (const excludePath of csrfConfig.excludePaths) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-release cache for {@link deriveCspOriginsFromSource}.
|
|
3
|
+
*
|
|
4
|
+
* Derivation reads every source file a release pins, which is far too much work
|
|
5
|
+
* to repeat per response but exactly the right amount to do once per release:
|
|
6
|
+
* the input is immutable for a given content version, so the result is too.
|
|
7
|
+
*
|
|
8
|
+
* @module security/http/derived-csp-cache
|
|
9
|
+
*/
|
|
10
|
+
import { type DerivationSourceFile, type DerivedCspOrigins } from "./derived-csp-origins.js";
|
|
11
|
+
export interface DerivedCspLookup {
|
|
12
|
+
/** Project scope, so two projects on one pod cannot share an entry. */
|
|
13
|
+
readonly projectScope: string;
|
|
14
|
+
/**
|
|
15
|
+
* Content version, as `resolveStyleContentVersion` spells it. A branch
|
|
16
|
+
* version is stable while its content changes, which is why the caller passes
|
|
17
|
+
* a snapshot identity rather than a branch name where one is available.
|
|
18
|
+
*/
|
|
19
|
+
readonly contentVersion: string;
|
|
20
|
+
/** Reads the release's source files. Called only on a miss. */
|
|
21
|
+
readonly loadSourceFiles: () => Promise<readonly DerivationSourceFile[] | null>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Derived origins for a content version, computing them at most once.
|
|
25
|
+
*
|
|
26
|
+
* Failure is not fatal: a project whose sources cannot be read gets an empty
|
|
27
|
+
* derivation, which leaves it exactly where it was before this existed -- the
|
|
28
|
+
* platform floor plus whatever `security.csp` declares.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getDerivedCspOrigins(lookup: DerivedCspLookup): Promise<DerivedCspOrigins>;
|
|
31
|
+
/** @internal Test seam. */
|
|
32
|
+
export declare function __clearDerivedCspCacheForTests(): void;
|
|
33
|
+
//# sourceMappingURL=derived-csp-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derived-csp-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/security/http/derived-csp-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EACL,KAAK,oBAAoB,EAEzB,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAuClC,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,eAAe,EAAE,MAAM,OAAO,CAAC,SAAS,oBAAoB,EAAE,GAAG,IAAI,CAAC,CAAC;CACjF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,iBAAiB,CAAC,CAmB5B;AA+BD,2BAA2B;AAC3B,wBAAgB,8BAA8B,IAAI,IAAI,CAGrD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-release cache for {@link deriveCspOriginsFromSource}.
|
|
3
|
+
*
|
|
4
|
+
* Derivation reads every source file a release pins, which is far too much work
|
|
5
|
+
* to repeat per response but exactly the right amount to do once per release:
|
|
6
|
+
* the input is immutable for a given content version, so the result is too.
|
|
7
|
+
*
|
|
8
|
+
* @module security/http/derived-csp-cache
|
|
9
|
+
*/
|
|
10
|
+
import { registerCache } from "../../utils/memory/index.js";
|
|
11
|
+
import { serverLogger } from "../../utils/logger/logger.js";
|
|
12
|
+
import { deriveCspOriginsFromSource, } from "./derived-csp-origins.js";
|
|
13
|
+
const logger = serverLogger.component("derived-csp");
|
|
14
|
+
/** Bounded so a fleet of projects cannot grow this without limit. */
|
|
15
|
+
const MAX_ENTRIES = 200;
|
|
16
|
+
const EMPTY = Object.freeze({});
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Derivations currently running, keyed exactly as the resolved cache is.
|
|
20
|
+
*
|
|
21
|
+
* A key is coldest immediately after a release, when every pod serves that
|
|
22
|
+
* content version for the first time. Without this, each concurrent request for
|
|
23
|
+
* the same key reads and scans the whole source set; with it the first caller
|
|
24
|
+
* does the work and the rest await its result.
|
|
25
|
+
*/
|
|
26
|
+
const inFlight = new Map();
|
|
27
|
+
registerCache("derived-csp-origins", () => ({
|
|
28
|
+
name: "derived-csp-origins",
|
|
29
|
+
entries: cache.size,
|
|
30
|
+
maxEntries: MAX_ENTRIES,
|
|
31
|
+
}));
|
|
32
|
+
function remember(key, value) {
|
|
33
|
+
// Insertion-ordered eviction: the oldest content version is the one least
|
|
34
|
+
// likely to still be serving traffic.
|
|
35
|
+
while (cache.size >= MAX_ENTRIES) {
|
|
36
|
+
const oldest = cache.keys().next().value;
|
|
37
|
+
if (oldest === undefined)
|
|
38
|
+
break;
|
|
39
|
+
cache.delete(oldest);
|
|
40
|
+
}
|
|
41
|
+
cache.set(key, value);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Derived origins for a content version, computing them at most once.
|
|
46
|
+
*
|
|
47
|
+
* Failure is not fatal: a project whose sources cannot be read gets an empty
|
|
48
|
+
* derivation, which leaves it exactly where it was before this existed -- the
|
|
49
|
+
* platform floor plus whatever `security.csp` declares.
|
|
50
|
+
*/
|
|
51
|
+
export function getDerivedCspOrigins(lookup) {
|
|
52
|
+
// NUL separates the two components because neither can contain one, so no
|
|
53
|
+
// scope/version pair can collide with another by concatenation. Written as an
|
|
54
|
+
// escape rather than a raw byte: a literal NUL in the source makes the file
|
|
55
|
+
// binary to grep and friends.
|
|
56
|
+
const key = `${lookup.projectScope}\u0000${lookup.contentVersion}`;
|
|
57
|
+
const cached = cache.get(key);
|
|
58
|
+
if (cached)
|
|
59
|
+
return Promise.resolve(cached);
|
|
60
|
+
const pending = inFlight.get(key);
|
|
61
|
+
if (pending)
|
|
62
|
+
return pending;
|
|
63
|
+
const derivation = deriveOnce(key, lookup);
|
|
64
|
+
inFlight.set(key, derivation);
|
|
65
|
+
return derivation.finally(() => {
|
|
66
|
+
// Clear only our own entry: after an eviction a later call may already have
|
|
67
|
+
// started a fresh derivation under the same key.
|
|
68
|
+
if (inFlight.get(key) === derivation)
|
|
69
|
+
inFlight.delete(key);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async function deriveOnce(key, lookup) {
|
|
73
|
+
let files;
|
|
74
|
+
try {
|
|
75
|
+
files = await lookup.loadSourceFiles();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
logger.debug("Could not read sources for CSP derivation", {
|
|
79
|
+
projectScope: lookup.projectScope,
|
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
|
81
|
+
});
|
|
82
|
+
return remember(key, EMPTY);
|
|
83
|
+
}
|
|
84
|
+
if (!files || files.length === 0)
|
|
85
|
+
return remember(key, EMPTY);
|
|
86
|
+
const derived = deriveCspOriginsFromSource(files);
|
|
87
|
+
const count = derived["img-src"]?.length ?? 0;
|
|
88
|
+
if (count > 0) {
|
|
89
|
+
logger.debug("Derived CSP origins from project source", {
|
|
90
|
+
projectScope: lookup.projectScope,
|
|
91
|
+
contentVersion: lookup.contentVersion,
|
|
92
|
+
originCount: count,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return remember(key, derived);
|
|
96
|
+
}
|
|
97
|
+
/** @internal Test seam. */
|
|
98
|
+
export function __clearDerivedCspCacheForTests() {
|
|
99
|
+
cache.clear();
|
|
100
|
+
inFlight.clear();
|
|
101
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a project's CSP origins from the source it publishes.
|
|
3
|
+
*
|
|
4
|
+
* The platform floor lists what the *platform* emits. Anything a project loads
|
|
5
|
+
* from elsewhere -- a stock-photo host, its own asset CDN, a video origin --
|
|
6
|
+
* has to be admitted separately, and until now the only way to do that was
|
|
7
|
+
* `security.csp`. That made a working site depend on configuration the project
|
|
8
|
+
* had no reason to know it needed: an audit of the hosted fleet found ~100
|
|
9
|
+
* projects loading blocked assets and two that had declared anything.
|
|
10
|
+
*
|
|
11
|
+
* This module closes that gap by reading the origins out of the project's own
|
|
12
|
+
* released source, so `security.csp` becomes an override for what static
|
|
13
|
+
* analysis cannot see rather than a prerequisite for a site that works.
|
|
14
|
+
*
|
|
15
|
+
* Two properties make that safe rather than merely convenient:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Only passive-content directives are derived.** `img-src`, `media-src`
|
|
18
|
+
* and `font-src` decide whether a logo renders. `script-src` and
|
|
19
|
+
* `connect-src` decide whether a third party can execute code in the page
|
|
20
|
+
* or receive data from it. Deriving those would mean anyone able to
|
|
21
|
+
* influence source -- a compromised dependency, a merged pull request,
|
|
22
|
+
* CMS-authored MDX -- could grant themselves execution or an exfiltration
|
|
23
|
+
* endpoint, collapsing two independent barriers into one. Every project in
|
|
24
|
+
* the audit was fixed by passive directives alone.
|
|
25
|
+
*
|
|
26
|
+
* 2. **Origins come from an immutable release.** Derivation runs over the
|
|
27
|
+
* source a release pins, not over live content, so a script injected at
|
|
28
|
+
* runtime cannot extend the allowlist it is subject to.
|
|
29
|
+
*
|
|
30
|
+
* Derivation is deliberately incomplete. A URL assembled at runtime -- a
|
|
31
|
+
* template literal, a CMS field, an environment variable -- is invisible here,
|
|
32
|
+
* and those projects still declare `security.csp`. Under-deriving costs a
|
|
33
|
+
* broken image; over-deriving costs the policy its meaning.
|
|
34
|
+
*
|
|
35
|
+
* @module security/http/derived-csp-origins
|
|
36
|
+
*/
|
|
37
|
+
/** Directives this module is permitted to contribute to. */
|
|
38
|
+
export declare const DERIVABLE_CSP_DIRECTIVES: readonly ["img-src", "media-src", "font-src"];
|
|
39
|
+
export type DerivableCspDirective = (typeof DERIVABLE_CSP_DIRECTIVES)[number];
|
|
40
|
+
export type DerivedCspOrigins = Readonly<Partial<Record<DerivableCspDirective, readonly string[]>>>;
|
|
41
|
+
/** A source file as the release stores it. */
|
|
42
|
+
export interface DerivationSourceFile {
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly content?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Collect the passive-content origins a project's source references.
|
|
48
|
+
*
|
|
49
|
+
* Pure over its input: callers hand it the file set a release pins, and the
|
|
50
|
+
* result is stable for that set, which is what lets it be computed once per
|
|
51
|
+
* release rather than per request.
|
|
52
|
+
*/
|
|
53
|
+
export declare function deriveCspOriginsFromSource(files: readonly DerivationSourceFile[]): DerivedCspOrigins;
|
|
54
|
+
//# sourceMappingURL=derived-csp-origins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derived-csp-origins.d.ts","sourceRoot":"","sources":["../../../../src/src/security/http/derived-csp-origins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,wBAAwB,+CAEpC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CACtC,OAAO,CAAC,MAAM,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAC1D,CAAC;AAEF,8CAA8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2ED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,SAAS,oBAAoB,EAAE,GACrC,iBAAiB,CAsCnB"}
|