veryfront 0.1.1213 → 0.1.1214

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 (66) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  3. package/esm/src/agent/runtime/index.js +2 -6
  4. package/esm/src/agent/runtime/skill-policy-enforcement.d.ts +1 -1
  5. package/esm/src/agent/runtime/skill-policy-enforcement.d.ts.map +1 -1
  6. package/esm/src/agent/runtime/skill-policy-enforcement.js +1 -8
  7. package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
  8. package/esm/src/platform/adapters/index.d.ts +25 -0
  9. package/esm/src/platform/adapters/index.d.ts.map +1 -0
  10. package/esm/src/platform/adapters/index.js +23 -0
  11. package/esm/src/platform/adapters/security/index.d.ts +8 -0
  12. package/esm/src/platform/adapters/security/index.d.ts.map +1 -0
  13. package/esm/src/platform/adapters/security/index.js +7 -0
  14. package/esm/src/platform/adapters/token/factory.d.ts +10 -0
  15. package/esm/src/platform/adapters/token/factory.d.ts.map +1 -0
  16. package/esm/src/platform/adapters/token/factory.js +79 -0
  17. package/esm/src/platform/adapters/token/index.d.ts +9 -0
  18. package/esm/src/platform/adapters/token/index.d.ts.map +1 -0
  19. package/esm/src/platform/adapters/token/index.js +8 -0
  20. package/esm/src/platform/adapters/token/integration.d.ts +6 -0
  21. package/esm/src/platform/adapters/token/integration.d.ts.map +1 -0
  22. package/esm/src/platform/adapters/token/integration.js +61 -0
  23. package/esm/src/platform/adapters/token/veryfront/adapter.d.ts +22 -0
  24. package/esm/src/platform/adapters/token/veryfront/adapter.d.ts.map +1 -0
  25. package/esm/src/platform/adapters/token/veryfront/adapter.js +88 -0
  26. package/esm/src/platform/adapters/token/veryfront/api-client.d.ts +15 -0
  27. package/esm/src/platform/adapters/token/veryfront/api-client.d.ts.map +1 -0
  28. package/esm/src/platform/adapters/token/veryfront/api-client.js +212 -0
  29. package/esm/src/platform/adapters/token/veryfront/index.d.ts +10 -0
  30. package/esm/src/platform/adapters/token/veryfront/index.d.ts.map +1 -0
  31. package/esm/src/platform/adapters/token/veryfront/index.js +9 -0
  32. package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts +20 -0
  33. package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts.map +1 -0
  34. package/esm/src/platform/adapters/token/veryfront/memory-adapter.js +52 -0
  35. package/esm/src/platform/adapters/token/veryfront/types.d.ts +86 -0
  36. package/esm/src/platform/adapters/token/veryfront/types.d.ts.map +1 -0
  37. package/esm/src/platform/adapters/token/veryfront/types.js +81 -0
  38. package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
  39. package/esm/src/security/http/config.d.ts +5 -0
  40. package/esm/src/security/http/config.d.ts.map +1 -1
  41. package/esm/src/security/http/config.js +16 -1
  42. package/esm/src/security/http/derived-csp-cache.d.ts +33 -0
  43. package/esm/src/security/http/derived-csp-cache.d.ts.map +1 -0
  44. package/esm/src/security/http/derived-csp-cache.js +101 -0
  45. package/esm/src/security/http/derived-csp-origins.d.ts +54 -0
  46. package/esm/src/security/http/derived-csp-origins.d.ts.map +1 -0
  47. package/esm/src/security/http/derived-csp-origins.js +156 -0
  48. package/esm/src/security/http/response/security-handler.d.ts.map +1 -1
  49. package/esm/src/security/http/response/security-handler.js +9 -2
  50. package/esm/src/security/sandbox/constants.d.ts +3 -0
  51. package/esm/src/security/sandbox/constants.d.ts.map +1 -0
  52. package/esm/src/security/sandbox/constants.js +2 -0
  53. package/esm/src/security/sandbox/deno-sandbox.d.ts +71 -0
  54. package/esm/src/security/sandbox/deno-sandbox.d.ts.map +1 -0
  55. package/esm/src/security/sandbox/deno-sandbox.js +184 -0
  56. package/esm/src/security/sandbox/permission-system.d.ts +11 -0
  57. package/esm/src/security/sandbox/permission-system.d.ts.map +1 -0
  58. package/esm/src/security/sandbox/permission-system.js +85 -0
  59. package/esm/src/server/runtime-handler/project-runtime-context.d.ts.map +1 -1
  60. package/esm/src/server/runtime-handler/project-runtime-context.js +97 -1
  61. package/esm/src/types/server.d.ts +10 -0
  62. package/esm/src/types/server.d.ts.map +1 -1
  63. package/esm/src/utils/version-constant.d.ts +1 -1
  64. package/esm/src/utils/version-constant.js +1 -1
  65. package/esm/src/workflow/api.d.ts +2 -2
  66. 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.1213";
10
+ export declare const DEFAULT_OUTBOUND_USER_AGENT = "veryfront/0.1.1214";
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
@@ -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;CAC9B;AAwLD;;;;;;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,CAiBxB;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"}
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 !== "productionDefaults"))
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,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"}
@@ -0,0 +1,156 @@
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 const DERIVABLE_CSP_DIRECTIVES = Object.freeze(["img-src", "media-src", "font-src"]);
39
+ /**
40
+ * Cap on how much of one file is scanned. A generated bundle or an inlined
41
+ * data blob can be megabytes, and scanning it yields nothing a hand-written
42
+ * source file would not.
43
+ *
44
+ * Measured in UTF-16 code units, matching `String.prototype.length`, because
45
+ * that is the unit the regex engine actually steps through -- 512K code units
46
+ * is the same amount of scanning whether they are ASCII or CJK. A UTF-8 byte
47
+ * cap would read as the more natural bound but would not correspond to the
48
+ * cost being bounded, and computing it means walking the string to decide how
49
+ * much of the string to walk.
50
+ */
51
+ const MAX_SCANNED_CHARS_PER_FILE = 512 * 1024;
52
+ /**
53
+ * Upper bound on distinct origins, so a generated file cannot inflate the
54
+ * policy. Origins are ranked before the cap applies -- see
55
+ * {@link deriveCspOriginsFromSource}.
56
+ */
57
+ const MAX_DERIVED_ORIGINS = 32;
58
+ /**
59
+ * Every absolute https URL in the source, whatever syntax surrounds it.
60
+ *
61
+ * An earlier version of this matched by position -- `<img src>`, `url()`,
62
+ * `poster` -- on the theory that knowing which element referenced a URL would
63
+ * let each origin land in exactly the right directive. Measured against real
64
+ * released content that recovers about a third of the references. The same
65
+ * project reaches its asset host through `<img src>`, a JSX `imageSrc` prop,
66
+ * YAML frontmatter, markdown `![](url)`, a bare string in a props array, a
67
+ * `srcFallback` key, and `href` on a favicon link. Partial derivation is worse
68
+ * than none: the site still breaks, but now it looks configured.
69
+ *
70
+ * Recall is what protects the project here, and precision is not what protects
71
+ * the visitor -- the passive/active split is. An origin admitted for images
72
+ * that is never referenced costs nothing; one that is missed costs a broken
73
+ * page.
74
+ */
75
+ const HTTPS_URL_PATTERN = /https:\/\/[a-zA-Z0-9.-]+(?::\d{1,5})?(?![a-zA-Z0-9.:-])/g;
76
+ /**
77
+ * Reduce a URL to the `scheme://host[:port]` form CSP matches on.
78
+ *
79
+ * Returns undefined for anything unparseable, which is the same as declining
80
+ * to derive from it.
81
+ */
82
+ function toCspOrigin(rawUrl) {
83
+ let parsed;
84
+ try {
85
+ parsed = new URL(rawUrl);
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ if (parsed.protocol !== "https:")
91
+ return undefined;
92
+ if (!parsed.hostname)
93
+ return undefined;
94
+ // A hostname with no dot is a bare label, not a public origin; a wildcard is
95
+ // an unfilled template. Neither belongs in a served policy.
96
+ if (!parsed.hostname.includes("."))
97
+ return undefined;
98
+ if (parsed.hostname.includes("*"))
99
+ return undefined;
100
+ return parsed.port ? `https://${parsed.hostname}:${parsed.port}` : `https://${parsed.hostname}`;
101
+ }
102
+ function scanContent(content, into) {
103
+ const scanned = content.length > MAX_SCANNED_CHARS_PER_FILE
104
+ ? content.slice(0, MAX_SCANNED_CHARS_PER_FILE)
105
+ : content;
106
+ for (const match of scanned.matchAll(HTTPS_URL_PATTERN)) {
107
+ const origin = toCspOrigin(match[0]);
108
+ if (origin)
109
+ into.set(origin, (into.get(origin) ?? 0) + 1);
110
+ }
111
+ }
112
+ /**
113
+ * Collect the passive-content origins a project's source references.
114
+ *
115
+ * Pure over its input: callers hand it the file set a release pins, and the
116
+ * result is stable for that set, which is what lets it be computed once per
117
+ * release rather than per request.
118
+ */
119
+ export function deriveCspOriginsFromSource(files) {
120
+ const counts = new Map();
121
+ for (const file of files) {
122
+ if (!file?.content)
123
+ continue;
124
+ scanContent(file.content, counts);
125
+ }
126
+ if (counts.size === 0)
127
+ return Object.freeze({});
128
+ // Rank by how often an origin is referenced before applying the cap.
129
+ //
130
+ // Truncating in discovery order looked fine until it was run over a real
131
+ // project: one article yielded its asset CDN plus five hosts that appear
132
+ // only as prose links. Across a content site those one-off links vastly
133
+ // outnumber asset hosts, so an arbitrary cut can drop the very origin the
134
+ // site depends on and silently reintroduce the breakage this exists to
135
+ // prevent. An asset host is referenced from every page that uses it; a link
136
+ // in body copy is referenced once. Frequency separates them cleanly.
137
+ //
138
+ // Ties break alphabetically so the result stays deterministic for a given
139
+ // release, which is what lets the header and anything keyed on it be cached.
140
+ const ranked = [...counts.entries()]
141
+ .sort(([originA, countA], [originB, countB]) => countB - countA || originA.localeCompare(originB))
142
+ .slice(0, MAX_DERIVED_ORIGINS)
143
+ .map(([origin]) => origin)
144
+ .sort();
145
+ // Every retained origin is granted to all three passive directives rather
146
+ // than routed between them. Splitting by file extension would reintroduce a
147
+ // failure mode with no compensating benefit: an origin allowed for images
148
+ // but not media is not a boundary anyone reasons about, while guessing wrong
149
+ // leaves the asset blocked. The boundary that matters -- passive content
150
+ // versus script and connect -- is enforced by this list, not by the split.
151
+ const sorted = Object.freeze(ranked);
152
+ const derived = {};
153
+ for (const directive of DERIVABLE_CSP_DIRECTIVES)
154
+ derived[directive] = sorted;
155
+ return Object.freeze(derived);
156
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"security-handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/security/http/response/security-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAYzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AASjD;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,4RAgBjD,CAAC;AAMF,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExE;AAkBD,wBAAgB,aAAa,IAAI,MAAM,CAItC;AA0KD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,OAAO,CAAC,EAAE,cAAc,EACxB,iBAAiB,CAAC,EAAE,OAAO,GAC1B,MAAM,CAWR;AA0CD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CAMR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,OAAO,CAAC,EAAE,cAAc,EACxB,iBAAiB,CAAC,EAAE,OAAO,GAC1B,IAAI,CA8GN"}
1
+ {"version":3,"file":"security-handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/security/http/response/security-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAYzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AASjD;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,4RAgBjD,CAAC;AAMF,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExE;AAkBD,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAkLD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,OAAO,CAAC,EAAE,cAAc,EACxB,iBAAiB,CAAC,EAAE,OAAO,GAC1B,MAAM,CAgBR;AA0CD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CAMR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,OAAO,CAAC,EAAE,cAAc,EACxB,iBAAiB,CAAC,EAAE,OAAO,GAC1B,IAAI,CA8GN"}
@@ -149,7 +149,7 @@ function normalizeSources(sources) {
149
149
  * directive but never the required class, which is how a project hardens past
150
150
  * the floor without being able to break its own site.
151
151
  */
152
- function mergeCspDirectives(required, projectCsp, nonce) {
152
+ function mergeCspDirectives(required, projectCsp, nonce, derived) {
153
153
  const project = new Map();
154
154
  for (const [key, value] of Object.entries(projectCsp ?? {})) {
155
155
  if (value === undefined)
@@ -177,16 +177,23 @@ function mergeCspDirectives(required, projectCsp, nonce) {
177
177
  const names = new Set([
178
178
  ...requiredByName.keys(),
179
179
  ...BASELINE_DIRECTIVES.keys(),
180
+ ...Object.keys(derived ?? {}),
180
181
  ...project.keys(),
181
182
  ]);
182
183
  const merged = Object.create(null);
183
184
  for (const name of names) {
184
185
  const configured = project.get(name);
185
186
  const baseline = configured === null ? [] : BASELINE_DIRECTIVES.get(name) ?? [];
187
+ // Derived origins sit above the floor and below project config, and follow
188
+ // the baseline's `null` semantics: a project that explicitly drops a
189
+ // directive means it, and static analysis must not put back what the
190
+ // project just said it does not want.
191
+ const derivedForName = configured === null ? [] : derived?.[name] ?? [];
186
192
  const additions = configured ?? [];
187
193
  merged[name] = normalizeSources([
188
194
  ...(requiredByName.get(name) ?? []),
189
195
  ...baseline,
196
+ ...derivedForName,
190
197
  ...additions,
191
198
  ]);
192
199
  }
@@ -219,7 +226,7 @@ export function buildCSP(isDev, nonce, config, adapter, isVeryfrontDomain) {
219
226
  // development allowance can never widen the production policy.
220
227
  if (isDev)
221
228
  return "";
222
- return serializeDirectives(mergeCspDirectives(requiredDirectives(nonce, isVeryfrontDomain ?? false), config?.csp, nonce));
229
+ return serializeDirectives(mergeCspDirectives(requiredDirectives(nonce, isVeryfrontDomain ?? false), config?.csp, nonce, config?.derivedCsp));
223
230
  }
224
231
  /**
225
232
  * Whether the built policy is enforced or merely reported.
@@ -0,0 +1,3 @@
1
+ export declare const DEFAULT_SANDBOX_TIMEOUT_MS = 5000;
2
+ export declare const MAX_SANDBOX_CODE_SIZE = 1000000;
3
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/src/security/sandbox/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAC/C,eAAO,MAAM,qBAAqB,UAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_SANDBOX_TIMEOUT_MS = 5000;
2
+ export const MAX_SANDBOX_CODE_SIZE = 1_000_000; // 1 MB