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.
Files changed (126) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/conversation/delegation-policy.d.ts +1 -3
  3. package/esm/src/agent/conversation/delegation-policy.d.ts.map +1 -1
  4. package/esm/src/agent/conversation/delegation-policy.js +1 -3
  5. package/esm/src/agent/index.d.ts +1 -1
  6. package/esm/src/agent/index.d.ts.map +1 -1
  7. package/esm/src/agent/index.js +1 -1
  8. package/esm/src/agent/runtime/agent-loop-skill-state.d.ts +10 -12
  9. package/esm/src/agent/runtime/agent-loop-skill-state.d.ts.map +1 -1
  10. package/esm/src/agent/runtime/agent-loop-skill-state.js +11 -17
  11. package/esm/src/agent/runtime/agent-runtime-step.d.ts +0 -1
  12. package/esm/src/agent/runtime/agent-runtime-step.d.ts.map +1 -1
  13. package/esm/src/agent/runtime/agent-runtime-step.js +2 -2
  14. package/esm/src/agent/runtime/index.d.ts +1 -1
  15. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  16. package/esm/src/agent/runtime/index.js +7 -13
  17. package/esm/src/agent/runtime/load-skill-tool.d.ts +2 -2
  18. package/esm/src/agent/runtime/load-skill-tool.d.ts.map +1 -1
  19. package/esm/src/agent/runtime/load-skill-tool.js +12 -68
  20. package/esm/src/agent/runtime/project-skill-catalog.d.ts.map +1 -1
  21. package/esm/src/agent/runtime/project-skill-catalog.js +1 -6
  22. package/esm/src/agent/runtime/skill-metadata.d.ts +4 -18
  23. package/esm/src/agent/runtime/skill-metadata.d.ts.map +1 -1
  24. package/esm/src/agent/runtime/skill-metadata.js +17 -78
  25. package/esm/src/agent/runtime/skill-policy-enforcement.d.ts +1 -5
  26. package/esm/src/agent/runtime/skill-policy-enforcement.d.ts.map +1 -1
  27. package/esm/src/agent/runtime/skill-policy-enforcement.js +12 -55
  28. package/esm/src/agent/runtime/skill-prompt.d.ts +2 -2
  29. package/esm/src/agent/runtime/skill-prompt.d.ts.map +1 -1
  30. package/esm/src/agent/runtime/skill-prompt.js +8 -44
  31. package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
  32. package/esm/src/platform/adapters/index.d.ts +25 -0
  33. package/esm/src/platform/adapters/index.d.ts.map +1 -0
  34. package/esm/src/platform/adapters/index.js +23 -0
  35. package/esm/src/platform/adapters/security/index.d.ts +8 -0
  36. package/esm/src/platform/adapters/security/index.d.ts.map +1 -0
  37. package/esm/src/platform/adapters/security/index.js +7 -0
  38. package/esm/src/platform/adapters/token/factory.d.ts +10 -0
  39. package/esm/src/platform/adapters/token/factory.d.ts.map +1 -0
  40. package/esm/src/platform/adapters/token/factory.js +79 -0
  41. package/esm/src/platform/adapters/token/index.d.ts +9 -0
  42. package/esm/src/platform/adapters/token/index.d.ts.map +1 -0
  43. package/esm/src/platform/adapters/token/index.js +8 -0
  44. package/esm/src/platform/adapters/token/integration.d.ts +6 -0
  45. package/esm/src/platform/adapters/token/integration.d.ts.map +1 -0
  46. package/esm/src/platform/adapters/token/integration.js +61 -0
  47. package/esm/src/platform/adapters/token/veryfront/adapter.d.ts +22 -0
  48. package/esm/src/platform/adapters/token/veryfront/adapter.d.ts.map +1 -0
  49. package/esm/src/platform/adapters/token/veryfront/adapter.js +88 -0
  50. package/esm/src/platform/adapters/token/veryfront/api-client.d.ts +15 -0
  51. package/esm/src/platform/adapters/token/veryfront/api-client.d.ts.map +1 -0
  52. package/esm/src/platform/adapters/token/veryfront/api-client.js +212 -0
  53. package/esm/src/platform/adapters/token/veryfront/index.d.ts +10 -0
  54. package/esm/src/platform/adapters/token/veryfront/index.d.ts.map +1 -0
  55. package/esm/src/platform/adapters/token/veryfront/index.js +9 -0
  56. package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts +20 -0
  57. package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts.map +1 -0
  58. package/esm/src/platform/adapters/token/veryfront/memory-adapter.js +52 -0
  59. package/esm/src/platform/adapters/token/veryfront/types.d.ts +86 -0
  60. package/esm/src/platform/adapters/token/veryfront/types.d.ts.map +1 -0
  61. package/esm/src/platform/adapters/token/veryfront/types.js +81 -0
  62. package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
  63. package/esm/src/proxy/handler.d.ts.map +1 -1
  64. package/esm/src/proxy/handler.js +17 -1
  65. package/esm/src/security/http/auth.d.ts.map +1 -1
  66. package/esm/src/security/http/auth.js +7 -0
  67. package/esm/src/security/http/config.d.ts +5 -0
  68. package/esm/src/security/http/config.d.ts.map +1 -1
  69. package/esm/src/security/http/config.js +16 -1
  70. package/esm/src/security/http/csp-report-endpoint.d.ts +29 -0
  71. package/esm/src/security/http/csp-report-endpoint.d.ts.map +1 -0
  72. package/esm/src/security/http/csp-report-endpoint.js +30 -0
  73. package/esm/src/security/http/csrf/csrf-handler.d.ts.map +1 -1
  74. package/esm/src/security/http/csrf/csrf-handler.js +6 -0
  75. package/esm/src/security/http/derived-csp-cache.d.ts +33 -0
  76. package/esm/src/security/http/derived-csp-cache.d.ts.map +1 -0
  77. package/esm/src/security/http/derived-csp-cache.js +101 -0
  78. package/esm/src/security/http/derived-csp-origins.d.ts +54 -0
  79. package/esm/src/security/http/derived-csp-origins.d.ts.map +1 -0
  80. package/esm/src/security/http/derived-csp-origins.js +156 -0
  81. package/esm/src/security/http/response/security-handler.d.ts +1 -2
  82. package/esm/src/security/http/response/security-handler.d.ts.map +1 -1
  83. package/esm/src/security/http/response/security-handler.js +25 -9
  84. package/esm/src/security/sandbox/constants.d.ts +3 -0
  85. package/esm/src/security/sandbox/constants.d.ts.map +1 -0
  86. package/esm/src/security/sandbox/constants.js +2 -0
  87. package/esm/src/security/sandbox/deno-sandbox.d.ts +71 -0
  88. package/esm/src/security/sandbox/deno-sandbox.d.ts.map +1 -0
  89. package/esm/src/security/sandbox/deno-sandbox.js +184 -0
  90. package/esm/src/security/sandbox/permission-system.d.ts +11 -0
  91. package/esm/src/security/sandbox/permission-system.d.ts.map +1 -0
  92. package/esm/src/security/sandbox/permission-system.js +85 -0
  93. package/esm/src/server/handlers/request/csp-report.handler.d.ts +56 -0
  94. package/esm/src/server/handlers/request/csp-report.handler.d.ts.map +1 -0
  95. package/esm/src/server/handlers/request/csp-report.handler.js +189 -0
  96. package/esm/src/server/runtime-handler/index.d.ts +1 -1
  97. package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
  98. package/esm/src/server/runtime-handler/index.js +3 -0
  99. package/esm/src/server/runtime-handler/project-runtime-context.d.ts.map +1 -1
  100. package/esm/src/server/runtime-handler/project-runtime-context.js +97 -1
  101. package/esm/src/server/utils/domain-parser.d.ts +9 -0
  102. package/esm/src/server/utils/domain-parser.d.ts.map +1 -1
  103. package/esm/src/server/utils/domain-parser.js +11 -0
  104. package/esm/src/skill/allowed-tools.d.ts +18 -42
  105. package/esm/src/skill/allowed-tools.d.ts.map +1 -1
  106. package/esm/src/skill/allowed-tools.js +31 -182
  107. package/esm/src/skill/index.d.ts +1 -1
  108. package/esm/src/skill/index.d.ts.map +1 -1
  109. package/esm/src/skill/index.js +2 -2
  110. package/esm/src/skill/parser.d.ts.map +1 -1
  111. package/esm/src/skill/parser.js +6 -37
  112. package/esm/src/skill/tools.d.ts.map +1 -1
  113. package/esm/src/skill/tools.js +2 -3
  114. package/esm/src/skill/types.d.ts +1 -3
  115. package/esm/src/skill/types.d.ts.map +1 -1
  116. package/esm/src/skill/validation.d.ts.map +1 -1
  117. package/esm/src/skill/validation.js +31 -3
  118. package/esm/src/types/server.d.ts +10 -0
  119. package/esm/src/types/server.d.ts.map +1 -1
  120. package/esm/src/utils/cors-policy-limits.d.ts +9 -2
  121. package/esm/src/utils/cors-policy-limits.d.ts.map +1 -1
  122. package/esm/src/utils/cors-policy-limits.js +8 -1
  123. package/esm/src/utils/version-constant.d.ts +1 -1
  124. package/esm/src/utils/version-constant.js +1 -1
  125. package/esm/src/workflow/api.d.ts +2 -2
  126. package/package.json +8 -8
@@ -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
+ }
@@ -5,8 +5,7 @@ import type { SecurityConfig } from "./types.js";
5
5
  * security policy. Server integrations must remove project-provided values
6
6
  * before applying policy so development omissions remain authoritative.
7
7
  */
8
- export declare const SECURITY_POLICY_RESPONSE_HEADER_NAMES: readonly ["content-security-policy", "content-security-policy-report-only", "cross-origin-embedder-policy", "cross-origin-opener-policy", "cross-origin-resource-policy", "referrer-policy", "strict-transport-security", "x-content-type-options", "x-frame-options", "x-xss-protection"];
9
- export declare function isSecurityPolicyResponseHeaderName(name: string): boolean;
8
+ export declare const SECURITY_POLICY_RESPONSE_HEADER_NAMES: readonly ["content-security-policy", "content-security-policy-report-only", "cross-origin-embedder-policy", "cross-origin-opener-policy", "cross-origin-resource-policy", "referrer-policy", "reporting-endpoints", "strict-transport-security", "x-content-type-options", "x-frame-options", "x-xss-protection"];
10
9
  export declare function generateNonce(): string;
11
10
  /**
12
11
  * Build the served policy: the platform floor with project sources merged in.
@@ -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;AAgBzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AASjD;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,mTAmBjD,CAAC;AAqBF,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAsLD;;;;;;;;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,CAoHN"}
@@ -3,6 +3,7 @@ import { recordSecurityHeaders } from "../../../observability/index.js";
3
3
  import { HOSTED_STUDIO_ORIGINS } from "../studio-origin-policy.js";
4
4
  import { isCorsPolicyResponseHeaderName } from "../../../utils/cors-policy-limits.js";
5
5
  import { serverLogger } from "../../../utils/logger/logger.js";
6
+ import { CSP_REPORT_ENDPOINT_NAME, CSP_REPORT_PATH, } from "../csp-report-endpoint.js";
6
7
  import { PLATFORM_FONT_FILE_ORIGINS, PLATFORM_FONT_STYLE_ORIGINS, PLATFORM_IMAGE_ORIGINS, PLATFORM_SCRIPT_ORIGINS, } from "../platform-asset-origins.js";
7
8
  import { toCspDirectiveName } from "../csp-directives.js";
8
9
  const logger = serverLogger.component("security-headers");
@@ -26,15 +27,16 @@ export const SECURITY_POLICY_RESPONSE_HEADER_NAMES = Object.freeze([
26
27
  "cross-origin-opener-policy",
27
28
  "cross-origin-resource-policy",
28
29
  "referrer-policy",
30
+ // Names where violation reports go. A project-provided value would send
31
+ // them somewhere else, or nowhere, and the policy would look healthy.
32
+ "reporting-endpoints",
29
33
  "strict-transport-security",
30
34
  "x-content-type-options",
31
35
  "x-frame-options",
32
36
  "x-xss-protection",
33
37
  ]);
34
- const SECURITY_POLICY_RESPONSE_HEADER_NAME_SET = new Set(SECURITY_POLICY_RESPONSE_HEADER_NAMES);
35
- export function isSecurityPolicyResponseHeaderName(name) {
36
- return SECURITY_POLICY_RESPONSE_HEADER_NAME_SET.has(name.toLowerCase());
37
- }
38
+ /** Response header defining the reporting groups the policy refers to. */
39
+ const REPORTING_ENDPOINTS_HEADER = "reporting-endpoints";
38
40
  /** The two names the computed policy may be delivered under. */
39
41
  const CSP_RESPONSE_HEADER_NAMES = new Set([
40
42
  "content-security-policy",
@@ -114,6 +116,10 @@ function requiredDirectives(nonce, isVeryfrontDomain) {
114
116
  "frame-ancestors": isVeryfrontDomain ? [...VERYFRONT_FRAME_ANCESTORS] : ["'none'"],
115
117
  "base-uri": ["'self'"],
116
118
  "form-action": ["'self'"],
119
+ // Both spellings: `report-to` is the current one, `report-uri` is
120
+ // deprecated but still the only one several shipping browsers honour.
121
+ "report-to": [CSP_REPORT_ENDPOINT_NAME],
122
+ "report-uri": [CSP_REPORT_PATH],
117
123
  };
118
124
  }
119
125
  /**
@@ -149,7 +155,7 @@ function normalizeSources(sources) {
149
155
  * directive but never the required class, which is how a project hardens past
150
156
  * the floor without being able to break its own site.
151
157
  */
152
- function mergeCspDirectives(required, projectCsp, nonce) {
158
+ function mergeCspDirectives(required, projectCsp, nonce, derived) {
153
159
  const project = new Map();
154
160
  for (const [key, value] of Object.entries(projectCsp ?? {})) {
155
161
  if (value === undefined)
@@ -177,16 +183,23 @@ function mergeCspDirectives(required, projectCsp, nonce) {
177
183
  const names = new Set([
178
184
  ...requiredByName.keys(),
179
185
  ...BASELINE_DIRECTIVES.keys(),
186
+ ...Object.keys(derived ?? {}),
180
187
  ...project.keys(),
181
188
  ]);
182
189
  const merged = Object.create(null);
183
190
  for (const name of names) {
184
191
  const configured = project.get(name);
185
192
  const baseline = configured === null ? [] : BASELINE_DIRECTIVES.get(name) ?? [];
193
+ // Derived origins sit above the floor and below project config, and follow
194
+ // the baseline's `null` semantics: a project that explicitly drops a
195
+ // directive means it, and static analysis must not put back what the
196
+ // project just said it does not want.
197
+ const derivedForName = configured === null ? [] : derived?.[name] ?? [];
186
198
  const additions = configured ?? [];
187
199
  merged[name] = normalizeSources([
188
200
  ...(requiredByName.get(name) ?? []),
189
201
  ...baseline,
202
+ ...derivedForName,
190
203
  ...additions,
191
204
  ]);
192
205
  }
@@ -219,7 +232,7 @@ export function buildCSP(isDev, nonce, config, adapter, isVeryfrontDomain) {
219
232
  // development allowance can never widen the production policy.
220
233
  if (isDev)
221
234
  return "";
222
- return serializeDirectives(mergeCspDirectives(requiredDirectives(nonce, isVeryfrontDomain ?? false), config?.csp, nonce));
235
+ return serializeDirectives(mergeCspDirectives(requiredDirectives(nonce, isVeryfrontDomain ?? false), config?.csp, nonce, config?.derivedCsp));
223
236
  }
224
237
  /**
225
238
  * Whether the built policy is enforced or merely reported.
@@ -290,6 +303,9 @@ export function applySecurityHeaders(headers, isDev, nonce, config, adapter, isV
290
303
  if (csp) {
291
304
  const hasEnvOverride = Boolean(adapter?.env?.get?.("VERYFRONT_CSP")?.trim());
292
305
  headers.set(cspHeaderName(config, adapter, hasEnvOverride), csp);
306
+ // Names the group the policy's `report-to` refers to. Without this header
307
+ // the directive names nothing and the browser sends no reports at all.
308
+ headers.set(REPORTING_ENDPOINTS_HEADER, `${CSP_REPORT_ENDPOINT_NAME}="${CSP_REPORT_PATH}"`);
293
309
  }
294
310
  if (!isDev) {
295
311
  const hstsMaxAge = config?.hsts?.maxAge ?? HSTS_MAX_AGE_SECONDS;
@@ -328,9 +344,9 @@ export function applySecurityHeaders(headers, isDev, nonce, config, adapter, isV
328
344
  // `security.headers` would silently replace the merged platform floor --
329
345
  // and, now that the floor may be delivered report-only, could also flip
330
346
  // which mode is served. Header names are case-insensitive, so match that
331
- // way. `isSecurityPolicyResponseHeaderName` is deliberately not reused:
332
- // it covers headers this loop is still allowed to override.
333
- if (isCspResponseHeaderName(key)) {
347
+ // way.
348
+ const headerName = key.toLowerCase();
349
+ if (isCspResponseHeaderName(headerName) || headerName === REPORTING_ENDPOINTS_HEADER) {
334
350
  ignoredCspHeader = true;
335
351
  continue;
336
352
  }
@@ -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
@@ -0,0 +1,71 @@
1
+ export interface SandboxOptions {
2
+ timeoutMs?: number;
3
+ memoryLimitMb?: number;
4
+ }
5
+ /**
6
+ * Operator opt-in env var that allows {@link runInWorker} to execute on Node.js
7
+ * despite the lack of permission isolation. Read via {@link getHostEnv} so that
8
+ * a per-request project env overlay cannot enable unsafe execution from inside
9
+ * a tenant context.
10
+ *
11
+ * @internal Exported for testing.
12
+ */
13
+ export declare const NODE_SANDBOX_ALLOW_UNSAFE_ENV = "VERYFRONT_NODE_SANDBOX_ALLOW_UNSAFE";
14
+ /**
15
+ * Pure decision helper for the Node.js sandbox guard. Given the raw env-var
16
+ * value, decide whether to block execution on Node.
17
+ *
18
+ * Returns `true` only when the value is the literal string `"1"`. Any other
19
+ * value (including `"true"`, `"yes"`, whitespace, or empty string) keeps the
20
+ * guard active. Strict equality is intentional for a security opt-in.
21
+ *
22
+ * @internal Exported for testing only.
23
+ */
24
+ export declare function isNodeSandboxAllowedUnsafe(envValue: string | undefined): boolean;
25
+ /**
26
+ * Operator opt-in env var that allows {@link runInWorker} to execute on Bun
27
+ * despite the lack of permission isolation. Read via {@link getHostEnv} so that
28
+ * a per-request project env overlay cannot enable unsafe execution from inside
29
+ * a tenant context.
30
+ *
31
+ * @internal Exported for testing.
32
+ */
33
+ export declare const BUN_SANDBOX_ALLOW_UNSAFE_ENV = "VERYFRONT_BUN_SANDBOX_ALLOW_UNSAFE";
34
+ /**
35
+ * Pure decision helper for the Bun sandbox guard. Given the raw env-var
36
+ * value, decide whether to block execution on Bun.
37
+ *
38
+ * Returns `true` only when the value is the literal string `"1"`. Any other
39
+ * value (including `"true"`, `"yes"`, whitespace, or empty string) keeps the
40
+ * guard active. Strict equality is intentional for a security opt-in.
41
+ *
42
+ * @internal Exported for testing only.
43
+ */
44
+ export declare function isBunSandboxAllowedUnsafe(envValue: string | undefined): boolean;
45
+ /**
46
+ * Run untrusted JavaScript in an isolated Worker.
47
+ *
48
+ * ## Isolation model
49
+ *
50
+ * - **Deno (recommended):** the worker is spawned with `permissions: "none"`,
51
+ * denying filesystem, network, env, and subprocess access. This is the
52
+ * primary safe execution path.
53
+ * - **Node.js:** Node Workers do not support permission isolation. They
54
+ * inherit full access to the filesystem, network, env vars, and built-in
55
+ * modules. Only memory limits can be enforced. Because of this, the Node
56
+ * path is **disabled by default** and throws {@link NOT_SUPPORTED}.
57
+ * Operators who deliberately trust their input may set the env var
58
+ * `VERYFRONT_NODE_SANDBOX_ALLOW_UNSAFE=1` to bypass the guard.
59
+ * - **Bun:** Bun Workers have the same lack of permission isolation as Node.js.
60
+ * The Bun path is **disabled by default** and throws {@link NOT_SUPPORTED}.
61
+ * Operators who deliberately trust their input may set the env var
62
+ * `VERYFRONT_BUN_SANDBOX_ALLOW_UNSAFE=1` to bypass the guard.
63
+ *
64
+ * Callers SHOULD NOT pass untrusted code to this function on Node.js or Bun
65
+ * unless they have audited every caller and operator and accept the risk.
66
+ *
67
+ * @throws NOT_SUPPORTED on Node.js without the explicit opt-in env var.
68
+ * @throws NOT_SUPPORTED on Bun without the explicit opt-in env var.
69
+ */
70
+ export declare function runInWorker<T = unknown>(code: string, options?: SandboxOptions): Promise<T>;
71
+ //# sourceMappingURL=deno-sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deno-sandbox.d.ts","sourceRoot":"","sources":["../../../../src/src/security/sandbox/deno-sandbox.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAYD;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,wCAAwC,CAAC;AAEnF;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,uCAAuC,CAAC;AAEjF;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CA6H/F"}
@@ -0,0 +1,184 @@
1
+ import { DEFAULT_SANDBOX_TIMEOUT_MS, MAX_SANDBOX_CODE_SIZE } from "./constants.js";
2
+ import { isCompiledBinary, serverLogger } from "../../utils/index.js";
3
+ import { isBun, isDeno, isNode } from "../../platform/compat/runtime.js";
4
+ import { getHostEnv } from "../../platform/compat/process/env.js";
5
+ import { withSpan } from "../../observability/tracing/otlp-setup.js";
6
+ import { INVALID_ARGUMENT, NOT_SUPPORTED, TIMEOUT_ERROR, UNKNOWN_ERROR } from "../../errors/index.js";
7
+ /**
8
+ * Operator opt-in env var that allows {@link runInWorker} to execute on Node.js
9
+ * despite the lack of permission isolation. Read via {@link getHostEnv} so that
10
+ * a per-request project env overlay cannot enable unsafe execution from inside
11
+ * a tenant context.
12
+ *
13
+ * @internal Exported for testing.
14
+ */
15
+ export const NODE_SANDBOX_ALLOW_UNSAFE_ENV = "VERYFRONT_NODE_SANDBOX_ALLOW_UNSAFE";
16
+ /**
17
+ * Pure decision helper for the Node.js sandbox guard. Given the raw env-var
18
+ * value, decide whether to block execution on Node.
19
+ *
20
+ * Returns `true` only when the value is the literal string `"1"`. Any other
21
+ * value (including `"true"`, `"yes"`, whitespace, or empty string) keeps the
22
+ * guard active. Strict equality is intentional for a security opt-in.
23
+ *
24
+ * @internal Exported for testing only.
25
+ */
26
+ export function isNodeSandboxAllowedUnsafe(envValue) {
27
+ return envValue === "1";
28
+ }
29
+ /**
30
+ * Operator opt-in env var that allows {@link runInWorker} to execute on Bun
31
+ * despite the lack of permission isolation. Read via {@link getHostEnv} so that
32
+ * a per-request project env overlay cannot enable unsafe execution from inside
33
+ * a tenant context.
34
+ *
35
+ * @internal Exported for testing.
36
+ */
37
+ export const BUN_SANDBOX_ALLOW_UNSAFE_ENV = "VERYFRONT_BUN_SANDBOX_ALLOW_UNSAFE";
38
+ /**
39
+ * Pure decision helper for the Bun sandbox guard. Given the raw env-var
40
+ * value, decide whether to block execution on Bun.
41
+ *
42
+ * Returns `true` only when the value is the literal string `"1"`. Any other
43
+ * value (including `"true"`, `"yes"`, whitespace, or empty string) keeps the
44
+ * guard active. Strict equality is intentional for a security opt-in.
45
+ *
46
+ * @internal Exported for testing only.
47
+ */
48
+ export function isBunSandboxAllowedUnsafe(envValue) {
49
+ return envValue === "1";
50
+ }
51
+ /**
52
+ * Run untrusted JavaScript in an isolated Worker.
53
+ *
54
+ * ## Isolation model
55
+ *
56
+ * - **Deno (recommended):** the worker is spawned with `permissions: "none"`,
57
+ * denying filesystem, network, env, and subprocess access. This is the
58
+ * primary safe execution path.
59
+ * - **Node.js:** Node Workers do not support permission isolation. They
60
+ * inherit full access to the filesystem, network, env vars, and built-in
61
+ * modules. Only memory limits can be enforced. Because of this, the Node
62
+ * path is **disabled by default** and throws {@link NOT_SUPPORTED}.
63
+ * Operators who deliberately trust their input may set the env var
64
+ * `VERYFRONT_NODE_SANDBOX_ALLOW_UNSAFE=1` to bypass the guard.
65
+ * - **Bun:** Bun Workers have the same lack of permission isolation as Node.js.
66
+ * The Bun path is **disabled by default** and throws {@link NOT_SUPPORTED}.
67
+ * Operators who deliberately trust their input may set the env var
68
+ * `VERYFRONT_BUN_SANDBOX_ALLOW_UNSAFE=1` to bypass the guard.
69
+ *
70
+ * Callers SHOULD NOT pass untrusted code to this function on Node.js or Bun
71
+ * unless they have audited every caller and operator and accept the risk.
72
+ *
73
+ * @throws NOT_SUPPORTED on Node.js without the explicit opt-in env var.
74
+ * @throws NOT_SUPPORTED on Bun without the explicit opt-in env var.
75
+ */
76
+ export function runInWorker(code, options = {}) {
77
+ if (typeof code !== "string") {
78
+ return Promise.reject(INVALID_ARGUMENT.create({ message: "Sandbox code must be a string" }));
79
+ }
80
+ if (code.length === 0) {
81
+ return Promise.reject(INVALID_ARGUMENT.create({ message: "Sandbox code cannot be empty" }));
82
+ }
83
+ const codeByteLength = new TextEncoder().encode(code).byteLength;
84
+ if (codeByteLength > MAX_SANDBOX_CODE_SIZE) {
85
+ return Promise.reject(INVALID_ARGUMENT.create({
86
+ message: `Sandbox code exceeds maximum size (${MAX_SANDBOX_CODE_SIZE} bytes)`,
87
+ }));
88
+ }
89
+ // SEC-008: Node Workers have no permission isolation. Refuse execution
90
+ // unless the operator has explicitly opted in via host env var. Use
91
+ // getHostEnv so a tenant project env overlay cannot enable this.
92
+ if (isNode && !isNodeSandboxAllowedUnsafe(getHostEnv(NODE_SANDBOX_ALLOW_UNSAFE_ENV))) {
93
+ return Promise.reject(NOT_SUPPORTED.create({
94
+ detail: "Sandbox execution is not safely supported on Node.js. The current " +
95
+ "implementation provides only memory limits, not permission isolation. " +
96
+ "Set VERYFRONT_NODE_SANDBOX_ALLOW_UNSAFE=1 to enable execution at your " +
97
+ "own risk, or run under Deno for full isolation.",
98
+ }));
99
+ }
100
+ // SEC-008: Bun Workers have no permission isolation (same risk as Node.js).
101
+ // Refuse execution unless the operator has explicitly opted in via host env
102
+ // var. Use getHostEnv so a tenant project env overlay cannot enable this.
103
+ if (isBun && !isBunSandboxAllowedUnsafe(getHostEnv(BUN_SANDBOX_ALLOW_UNSAFE_ENV))) {
104
+ return Promise.reject(NOT_SUPPORTED.create({
105
+ detail: "Sandbox execution is not safely supported on Bun. The current " +
106
+ "implementation provides no permission isolation. " +
107
+ "Set VERYFRONT_BUN_SANDBOX_ALLOW_UNSAFE=1 to enable execution at your " +
108
+ "own risk, or run under Deno for full isolation.",
109
+ }));
110
+ }
111
+ const timeoutMs = options.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS;
112
+ return withSpan("security.sandbox.runInWorker", () => {
113
+ const workerOptions = { type: "module" };
114
+ if (isDeno)
115
+ workerOptions.deno = { permissions: "none" };
116
+ const { memoryLimitMb } = options;
117
+ if (typeof memoryLimitMb === "number") {
118
+ if (!Number.isFinite(memoryLimitMb) || memoryLimitMb <= 0) {
119
+ throw INVALID_ARGUMENT.create({
120
+ detail: "Sandbox memoryLimitMb must be a positive, finite number",
121
+ });
122
+ }
123
+ if (!isNode) {
124
+ throw NOT_SUPPORTED.create({
125
+ detail: "Sandbox memory limits are not supported in this runtime",
126
+ });
127
+ }
128
+ workerOptions.resourceLimits = {
129
+ ...workerOptions.resourceLimits,
130
+ maxOldGenerationSizeMb: Math.floor(memoryLimitMb),
131
+ };
132
+ }
133
+ const workerCode = `self.onmessage = async (e) => {` +
134
+ ` const { code } = e.data;` +
135
+ ` let result;` +
136
+ ` try { result = await (async () => {` +
137
+ ` return await (new Function(code))();` +
138
+ ` })(); } catch (error) {` +
139
+ ` self.postMessage({ error: String(error && error.message || error) });` +
140
+ ` return;` +
141
+ ` }` +
142
+ ` self.postMessage({ result });` +
143
+ `};`;
144
+ // Use data URL for compiled binaries (blob URLs don't work in deno compile)
145
+ // See: https://github.com/denoland/deno/issues/18327
146
+ const workerUrl = isCompiledBinary()
147
+ ? `data:text/javascript;base64,${btoa(workerCode)}`
148
+ : URL.createObjectURL(new Blob([workerCode], { type: "application/javascript" }));
149
+ const worker = new Worker(workerUrl, workerOptions);
150
+ function safeTerminate(logMessage, error) {
151
+ try {
152
+ worker.terminate();
153
+ }
154
+ catch (e) {
155
+ serverLogger.debug(logMessage, { error: error ?? e });
156
+ }
157
+ }
158
+ const promise = new Promise((resolve, reject) => {
159
+ const timer = setTimeout(() => {
160
+ safeTerminate("[sandbox] worker terminate failed");
161
+ reject(TIMEOUT_ERROR.create({ detail: "Sandbox timeout" }));
162
+ }, timeoutMs);
163
+ worker.onmessage = (e) => {
164
+ clearTimeout(timer);
165
+ const { result, error } = e.data ?? {};
166
+ if (error)
167
+ reject(UNKNOWN_ERROR.create({ detail: error }));
168
+ else
169
+ resolve(result);
170
+ safeTerminate("[sandbox] worker terminate failed");
171
+ };
172
+ worker.onerror = (e) => {
173
+ clearTimeout(timer);
174
+ reject(UNKNOWN_ERROR.create({ detail: String(e.message || e.error || "Worker error") }));
175
+ safeTerminate("[sandbox] worker terminate failed on error");
176
+ };
177
+ });
178
+ worker.postMessage({ code });
179
+ return promise;
180
+ }, {
181
+ "sandbox.timeoutMs": timeoutMs,
182
+ "sandbox.memoryLimitMb": options.memoryLimitMb ?? 0,
183
+ });
184
+ }
@@ -0,0 +1,11 @@
1
+ export type Permission = "net" | "fs" | "env" | "run" | "read" | "write";
2
+ export interface PermissionRequest {
3
+ name: Permission;
4
+ host?: string;
5
+ path?: string;
6
+ }
7
+ export interface PermissionResult {
8
+ state: "granted" | "denied" | "prompt";
9
+ }
10
+ export declare function requestPermission(request: PermissionRequest): Promise<PermissionResult>;
11
+ //# sourceMappingURL=permission-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-system.d.ts","sourceRoot":"","sources":["../../../../src/src/security/sandbox/permission-system.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACxC;AAkGD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
@@ -0,0 +1,85 @@
1
+ import * as dntShim from "../../../_dnt.shims.js";
2
+ import { serverLogger } from "../../utils/index.js";
3
+ import { isDeno } from "../../platform/compat/runtime.js";
4
+ import { withSpan } from "../../observability/tracing/otlp-setup.js";
5
+ const logger = serverLogger.component("permissions");
6
+ /**
7
+ * Access the Deno permissions API through a narrow, typed view.
8
+ * Returns null when not running under Deno or the API is unavailable.
9
+ */
10
+ function getDenoPermissions() {
11
+ const denoGlobal = dntShim.dntGlobalThis.Deno;
12
+ const permissions = denoGlobal?.permissions;
13
+ if (!permissions || typeof permissions.request !== "function")
14
+ return null;
15
+ return permissions;
16
+ }
17
+ function createPermissionDescriptor(request) {
18
+ const { name, host, path } = request;
19
+ switch (name) {
20
+ case "net":
21
+ return host ? { name, host } : { name };
22
+ case "read":
23
+ case "write":
24
+ return path ? { name, path } : { name };
25
+ case "env":
26
+ case "run":
27
+ return { name };
28
+ case "fs":
29
+ default:
30
+ return null;
31
+ }
32
+ }
33
+ function requestDenoPermission(request) {
34
+ return withSpan("security.permissions.requestDeno", async () => {
35
+ const permissions = getDenoPermissions();
36
+ if (!isDeno || !permissions)
37
+ return { state: "denied" };
38
+ if (request.name === "fs") {
39
+ const path = request.path;
40
+ const readStatus = await permissions.request({ name: "read", path });
41
+ if (readStatus.state !== "granted")
42
+ return { state: readStatus.state };
43
+ const writeStatus = await permissions.request({ name: "write", path });
44
+ return { state: writeStatus.state };
45
+ }
46
+ const descriptor = createPermissionDescriptor(request);
47
+ if (!descriptor) {
48
+ logger.warn("Unsupported permission request", request);
49
+ return { state: "denied" };
50
+ }
51
+ const status = await permissions.request(descriptor);
52
+ return { state: status.state };
53
+ }, { "permission.name": request.name });
54
+ }
55
+ let warnedNonDenoAutoGrant = false;
56
+ function warnNonDenoAutoGrantOnce() {
57
+ if (warnedNonDenoAutoGrant)
58
+ return;
59
+ warnedNonDenoAutoGrant = true;
60
+ logger.warn("Permission sandbox is a no-op on non-Deno runtimes: all permission " +
61
+ "requests are auto-granted. Do not rely on this guard for isolation " +
62
+ "when running outside Deno.");
63
+ }
64
+ export function requestPermission(request) {
65
+ return withSpan("security.permissions.request", async () => {
66
+ try {
67
+ if (isDeno)
68
+ return await requestDenoPermission(request);
69
+ // On non-Deno runtimes there is no OS-level permission API to consult,
70
+ // so every request is auto-granted — this guard provides NO isolation
71
+ // here. Warn loudly (once) so an operator running the runtime outside
72
+ // Deno cannot mistake this for an enforced sandbox.
73
+ warnNonDenoAutoGrantOnce();
74
+ serverLogger.debug("[permissions] Permission auto-granted (non-Deno runtime)", { permission: request.name });
75
+ return { state: "granted" };
76
+ }
77
+ catch (error) {
78
+ logger.warn("Permission request failed", {
79
+ permission: request.name,
80
+ error,
81
+ });
82
+ return { state: "denied" };
83
+ }
84
+ }, { "permission.name": request.name });
85
+ }