@directive-run/sandbox 0.3.13 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -24,6 +24,16 @@ interface RunInSandboxInput {
24
24
  files?: PlaygroundFile[];
25
25
  /** Wall-clock timeout in milliseconds. Defaults to 5000. Clamped to [100, 10000]. */
26
26
  timeoutMs?: number;
27
+ /**
28
+ * Optional cancellation signal. Wire this to your HTTP request's
29
+ * AbortSignal (Next.js / Express both expose one) so a client that
30
+ * disconnects mid-flight releases its worker slot immediately
31
+ * instead of leaking it. Without a signal, an abandoned `runInSandbox`
32
+ * call still queues into the per-process worker cap and only frees
33
+ * when the worker times out — under load this drives the cap to
34
+ * permanent deadlock.
35
+ */
36
+ signal?: AbortSignal;
27
37
  }
28
38
  interface SandboxResult {
29
39
  /**
@@ -41,7 +51,7 @@ interface SandboxResult {
41
51
  * Final `system.derive` snapshot — every derivation declared in the
42
52
  * module config, evaluated by reading `system.derive[key]`. Empty
43
53
  * when the module has no `derive:` block or when validation rejected
44
- * before bundle. The Phase A AE audit (P0-DM2) flagged the original
54
+ * before bundle. The June 2026 security audit flagged the original
45
55
  * sandbox for snapshotting only facts; modules whose primary product
46
56
  * is a derivation (`status`, `isReady`, etc.) returned an empty-
47
57
  * looking transcript.
@@ -95,6 +105,39 @@ interface ValidationError {
95
105
  message: string;
96
106
  }
97
107
 
108
+ /**
109
+ * Host-side worker_threads orchestration. Mirrors the lint-runner
110
+ * pattern in `@directive-run/mcp`: spawn a fresh worker per call,
111
+ * race the response against a wall-clock timer, terminate on overrun.
112
+ *
113
+ * Workers are NOT pooled. Each call gets a clean process state — no
114
+ * carry-over globals between snippets, no shared `console` patches,
115
+ * no leaked timers from a prior run. Cold-start is ~5ms which is
116
+ * cheap relative to the 50-200ms a typical Directive demo actually
117
+ * spends in `system.settle()`.
118
+ */
119
+
120
+ /**
121
+ * Override the per-process worker cap. Pass `Infinity` to disable.
122
+ * Returns the previous value.
123
+ *
124
+ * Lowering the cap below `activeWorkers` does NOT terminate running
125
+ * workers — the new ceiling applies as those workers drain. New
126
+ * `acquireSlot()` callers queue until `activeWorkers` falls below
127
+ * the new cap. Raising the cap immediately drains any waiters the
128
+ * new ceiling can absorb.
129
+ *
130
+ * @example Cap the worker pool at boot for a Next.js API route
131
+ * ```ts
132
+ * // app/api/sandbox/route.ts — runs once per cold start
133
+ * import { setMaxConcurrentWorkers } from "@directive-run/sandbox";
134
+ * setMaxConcurrentWorkers(8);
135
+ * ```
136
+ */
137
+ declare function setMaxConcurrentWorkers(value: number): number;
138
+
139
+ declare function sanitizeStack(s: string | undefined): string;
140
+
98
141
  /**
99
142
  * Public API for `@directive-run/sandbox`.
100
143
  *
@@ -114,4 +157,4 @@ interface ValidationError {
114
157
 
115
158
  declare function runInSandbox(input: RunInSandboxInput): Promise<SandboxResult>;
116
159
 
117
- export { type PlaygroundFile, type RunInSandboxInput, SandboxError, type SandboxResult, type ValidationError, runInSandbox };
160
+ export { type PlaygroundFile, type RunInSandboxInput, SandboxError, type SandboxResult, type ValidationError, runInSandbox, sanitizeStack, setMaxConcurrentWorkers };
package/dist/index.d.ts CHANGED
@@ -24,6 +24,16 @@ interface RunInSandboxInput {
24
24
  files?: PlaygroundFile[];
25
25
  /** Wall-clock timeout in milliseconds. Defaults to 5000. Clamped to [100, 10000]. */
26
26
  timeoutMs?: number;
27
+ /**
28
+ * Optional cancellation signal. Wire this to your HTTP request's
29
+ * AbortSignal (Next.js / Express both expose one) so a client that
30
+ * disconnects mid-flight releases its worker slot immediately
31
+ * instead of leaking it. Without a signal, an abandoned `runInSandbox`
32
+ * call still queues into the per-process worker cap and only frees
33
+ * when the worker times out — under load this drives the cap to
34
+ * permanent deadlock.
35
+ */
36
+ signal?: AbortSignal;
27
37
  }
28
38
  interface SandboxResult {
29
39
  /**
@@ -41,7 +51,7 @@ interface SandboxResult {
41
51
  * Final `system.derive` snapshot — every derivation declared in the
42
52
  * module config, evaluated by reading `system.derive[key]`. Empty
43
53
  * when the module has no `derive:` block or when validation rejected
44
- * before bundle. The Phase A AE audit (P0-DM2) flagged the original
54
+ * before bundle. The June 2026 security audit flagged the original
45
55
  * sandbox for snapshotting only facts; modules whose primary product
46
56
  * is a derivation (`status`, `isReady`, etc.) returned an empty-
47
57
  * looking transcript.
@@ -95,6 +105,39 @@ interface ValidationError {
95
105
  message: string;
96
106
  }
97
107
 
108
+ /**
109
+ * Host-side worker_threads orchestration. Mirrors the lint-runner
110
+ * pattern in `@directive-run/mcp`: spawn a fresh worker per call,
111
+ * race the response against a wall-clock timer, terminate on overrun.
112
+ *
113
+ * Workers are NOT pooled. Each call gets a clean process state — no
114
+ * carry-over globals between snippets, no shared `console` patches,
115
+ * no leaked timers from a prior run. Cold-start is ~5ms which is
116
+ * cheap relative to the 50-200ms a typical Directive demo actually
117
+ * spends in `system.settle()`.
118
+ */
119
+
120
+ /**
121
+ * Override the per-process worker cap. Pass `Infinity` to disable.
122
+ * Returns the previous value.
123
+ *
124
+ * Lowering the cap below `activeWorkers` does NOT terminate running
125
+ * workers — the new ceiling applies as those workers drain. New
126
+ * `acquireSlot()` callers queue until `activeWorkers` falls below
127
+ * the new cap. Raising the cap immediately drains any waiters the
128
+ * new ceiling can absorb.
129
+ *
130
+ * @example Cap the worker pool at boot for a Next.js API route
131
+ * ```ts
132
+ * // app/api/sandbox/route.ts — runs once per cold start
133
+ * import { setMaxConcurrentWorkers } from "@directive-run/sandbox";
134
+ * setMaxConcurrentWorkers(8);
135
+ * ```
136
+ */
137
+ declare function setMaxConcurrentWorkers(value: number): number;
138
+
139
+ declare function sanitizeStack(s: string | undefined): string;
140
+
98
141
  /**
99
142
  * Public API for `@directive-run/sandbox`.
100
143
  *
@@ -114,4 +157,4 @@ interface ValidationError {
114
157
 
115
158
  declare function runInSandbox(input: RunInSandboxInput): Promise<SandboxResult>;
116
159
 
117
- export { type PlaygroundFile, type RunInSandboxInput, SandboxError, type SandboxResult, type ValidationError, runInSandbox };
160
+ export { type PlaygroundFile, type RunInSandboxInput, SandboxError, type SandboxResult, type ValidationError, runInSandbox, sanitizeStack, setMaxConcurrentWorkers };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import{createRequire as W}from"module";import{pathToFileURL as K}from"url";import{build as C}from"esbuild";function B(t){try{let o=W(import.meta.url).resolve(t);return K(o).href}catch{return null}}var b="src/main.ts",x="directive-sandbox-vfs",h=class extends Error{constructor(o,e){super(o);this.cause=e;this.name="BundleError"}};function V(t){let r=/(var\s+(system\d*)\s*=\s*createSystem\s*\([\s\S]*?\)\s*;)/,o=t.match(r);if(!o)return t;let e=o[1],n=`
1
+ import{createRequire as G}from"module";import{pathToFileURL as H}from"url";import{build as X}from"esbuild";function Y(e){try{let o=G(import.meta.url).resolve(e);return H(o).href}catch{return null}}var E="src/main.ts",v="directive-sandbox-vfs",g=class extends Error{constructor(o,r){super(o);this.cause=r;this.name="BundleError"}};function J(e){let t=/(var\s+(system\d*)\s*=\s*createSystem\s*\([\s\S]*?\)\s*;)/,o=e.match(t);if(!o)return e;let r=o[1],n=`
2
2
  (globalThis).__directiveSandbox_system__ = ${o[2]};
3
- `;return t.replace(e,e+n)}async function E(t){if(!t.find(e=>e.path===b))throw new h(`payload must include "${b}" \u2014 that's the entry point the runner targets`);let o=new Map;for(let e of t)o.set(e.path,e.source);try{let i=(await C({entryPoints:[`${x}:${b}`],bundle:!0,format:"esm",target:"node20",platform:"node",write:!1,logLevel:"silent",external:["@directive-run/*"],supported:{"top-level-await":!0},plugins:[{name:"directive-sandbox-vfs",setup(c){c.onResolve({filter:/.*/},s=>{if(s.kind==="entry-point")return{path:s.path.replace(`${x}:`,""),namespace:x};if(s.namespace!==x)return null;if(s.path.startsWith("@directive-run/")){let a=B(s.path);return a?{external:!0,path:a}:{external:!0,path:s.path}}if(s.path.startsWith("./")||s.path.startsWith("../")){let a=s.importer.replace(/\/[^/]+$/,""),u=s.path.replace(/^\.\//,"").replace(/\.js$/,""),g=[`${a}/${u}.ts`,`${a}/${u}.js`,`${a}/${u}`,`${u}.ts`,`${u}.js`].map(m=>m.replace(/\/+/g,"/"));for(let m of g)if(o.has(m))return{path:m,namespace:x};throw new h(`cannot resolve "${s.path}" from "${s.importer}" \u2014 tried ${g.join(", ")}`)}throw new h(`unexpected import "${s.path}" from "${s.importer}" \u2014 only relative or @directive-run/* allowed`)}),c.onLoad({filter:/.*/,namespace:x},s=>{let a=o.get(s.path);if(a===void 0)throw new h(`virtual file missing: ${s.path}`);if(s.path===b){let u=["","// directive-sandbox: lift the runner's `system` binding","// onto a side-channel global so the worker can read","// system.facts.$store.toObject() after execution.","if (typeof system !== 'undefined') {"," (globalThis).__directiveSandbox_system__ = system;","}"].join(`
4
- `);return{contents:a+u,loader:"ts"}}return{contents:a,loader:"ts"}})}}]})).outputFiles[0]?.text;if(!i)throw new h("esbuild produced no output");let n=V(i);return{source:n,bytes:Buffer.byteLength(n,"utf8")}}catch(e){throw e instanceof h?e:new h(`bundle failed: ${e.message??String(e)}`,e)}}import{existsSync as M,mkdtempSync as U,rmSync as q,writeFileSync as G}from"fs";import{tmpdir as H}from"os";import{dirname as P,join as T}from"path";import{fileURLToPath as X,pathToFileURL as z}from"url";import{Worker as Y}from"worker_threads";var J=100,Q=1e4,I=5e3;async function k(){try{let o=new URL("./worker.js",import.meta.url),e=X(o);if(M(e))return e}catch{}let{createRequire:t}=await import("module");return t(import.meta.url).resolve("@directive-run/sandbox/worker")}var y=class extends Error{constructor(o,e){super(o);this.code=e;this.name="WorkerExecError"}};function Z(t){let r=t??I;return Number.isFinite(r)?Math.min(Q,Math.max(J,Math.floor(r))):I}async function ee(){let t=H();if(M(t))return t;let r=await k();return P(P(r))}async function te(t){let r=await ee(),o=U(T(r,"directive-sandbox-")),e=T(o,"bundle.mjs");return G(e,t,"utf8"),{bundlePath:e,cleanup:()=>{try{q(o,{recursive:!0,force:!0})}catch{}}}}async function $(t){let r=Z(t.timeoutMs),o=await k(),{bundlePath:e,cleanup:i}=await te(t.bundledSource),n=new Y(o,{resourceLimits:{maxOldGenerationSizeMb:32,maxYoungGenerationSizeMb:8,codeRangeSizeMb:16},stderr:!1}),c=null,s=!1,a=Date.now();try{return await new Promise((g,m)=>{let p=!1;n.once("message",d=>{p=!0,d.ok?g(d.result):m(new y(d.error,"worker-error"))}),n.once("error",d=>{p=!0,m(new y(d.message,"worker-error"))}),n.once("exit",d=>{!p&&d!==0&&d!==null&&m(new y(`worker exited with code ${d} before responding`,"worker-error"))}),c=setTimeout(()=>{s=!0,n.terminate(),m(new y(`wall-clock budget of ${r}ms elapsed`,"timeout"))},r);let w={bundlePath:z(e).href,timeoutMs:r,derivationKeys:t.derivationKeys};n.postMessage(w)})}catch(u){if(u instanceof y&&u.code==="timeout")return{logs:[],facts:{},derived:{},errors:[u.message],durationMs:Date.now()-a,timedOut:!0};throw u}finally{c&&clearTimeout(c),await n.terminate().catch(()=>{}),i()}}function re(t,r){let o=0;for(let e=r;e<t.length;e++){let i=t[e];if(i==="{")o+=1;else if(i==="}"&&(o-=1,o===0))return e}return-1}function ne(t){let r=[],o=0,e=0,i=0,n=0,c=s=>{let a=t.slice(o,s).trim();if(o=s+1,!a)return;let u=a.match(/^['"]?(\w+)['"]?\s*:/);u&&r.push(u[1])};for(let s=0;s<t.length;s++){let a=t[s];a==="{"?e+=1:a==="}"?e-=1:a==="("?i+=1:a===")"?i-=1:a==="["?n+=1:a==="]"?n-=1:(a===","||a===`
5
- `)&&e===0&&i===0&&n===0&&c(s)}return c(t.length),r}function A(t,r){let o=new RegExp(`\\b${r}\\s*:\\s*\\{`),e=t.match(o);if(!e||e.index===void 0)return[];let i=t.indexOf("{",e.index);if(i===-1)return[];let n=re(t,i);return n===-1?[]:ne(t.slice(i+1,n))}function F(t){let r=new Set,o=[];for(let e of t){for(let i of A(e.source,"derive"))r.has(i)||(r.add(i),o.push(i));for(let i of A(e.source,"derivations"))r.has(i)||(r.add(i),o.push(i))}return o}var f=class extends Error{constructor(o,e){super(o);this.code=e;this.name="SandboxError"}};import{Project as oe,SyntaxKind as l}from"ts-morph";var R=new Set(["core","ai","query","el","react","vue","svelte","solid","lit","optimistic","timeline","mutator","knowledge","scaffold","claude-plugin","lint","sources"]),N=new Set(["cli","mcp","sandbox","vite-plugin-api-proxy"]);function D(t){let r=t.match(/^@directive-run\/([^/]+)/);return r?r[1]:null}function se(t){if(/^\.{1,2}\/.+\.js$/.test(t))return!0;let r=D(t);return r===null||N.has(r)?!1:R.has(r)}var _=new Set(["createSystem","system","console","Math","JSON","Object","Array","Number","String","Boolean","Symbol","Promise","Error","Date","Map","Set","WeakMap","WeakSet","Reflect","globalThis","undefined","null","NaN","Infinity"]),v=new Set(["process","require","module","__dirname","__filename","fetch","XMLHttpRequest","WebSocket","eval","Function","Buffer","setImmediate","queueMicrotask","setTimeout","setInterval","clearTimeout","clearInterval"]);function ie(t){let r=D(t);return r&&N.has(r)?`import "${t}" is denied \u2014 @directive-run/${r} is a build/CLI/sandbox tool, not for use inside a sandboxed demo`:`import "${t}" is not allowed in the sandbox. Allowed: relative "./X.js" paths or any of @directive-run/{${Array.from(R).sort().join(",")}}.`}function ae(t,r,o){let e=r.getSourceFileOrThrow(t);for(let i of e.getImportDeclarations()){let n=i.getModuleSpecifierValue();if(!se(n)){let{line:c,column:s}=e.getLineAndColumnAtPos(i.getStart());o.push({path:t,line:c,column:s,message:ie(n)})}}}function ce(t,r,o){let e=r.getSourceFileOrThrow(t);e.forEachDescendant(i=>{if(i.getKind()===l.ImportKeyword){let n=i.getParent();if(n&&n.getKind()===l.CallExpression){let{line:c,column:s}=e.getLineAndColumnAtPos(i.getStart());o.push({path:t,line:c,column:s,message:"dynamic import() is not allowed in the sandbox"})}}if(i.getKind()===l.NewExpression){let n=i.getText();if(/^new\s+Function\s*\(/.test(n)){let{line:c,column:s}=e.getLineAndColumnAtPos(i.getStart());o.push({path:t,line:c,column:s,message:"new Function(...) is not allowed in the sandbox"})}}})}function ue(t,r,o){let e=r.getSourceFileOrThrow(t),i=(c,s)=>{let{line:a,column:u}=e.getLineAndColumnAtPos(c.getStart());o.push({path:t,line:a,column:u,message:s})},n=c=>{let s=c.trim();return s.startsWith('"')&&s.endsWith('"')||s.startsWith("'")&&s.endsWith("'")||s.startsWith("`")&&s.endsWith("`")?s.slice(1,-1):null};e.forEachDescendant(c=>{let s=c.getKind();if(s===l.PropertyAccessExpression){let a=c.getName?.();if(!a)return;if(a==="constructor"){i(c,"`.constructor` access is denied in the sandbox (Function-constructor smuggle vector)");return}if(v.has(a)){i(c,`\`.${a}\` access is denied in the sandbox (FS/network/eval surface) \u2014 accessing a denied global via property syntax was the property-access bypass closed in v0.3.0`);return}}if(s===l.ElementAccessExpression){let a=c.getExpression?.(),u=c.getArgumentExpression?.(),g=a?.getText()??"",m=u?.getText()??"",p=n(m);if(g==="globalThis"&&p!==null){i(c,"bracket-access on `globalThis` with a string literal is denied in the sandbox (use direct identifier reference for allowlisted names)");return}if(p!==null&&(p==="constructor"||v.has(p))){i(c,`bracket-access \`["${p}"]\` is denied in the sandbox (would reach a denied name)`);return}}if(s===l.CallExpression){let u=c.getExpression?.()?.getText()??"";if(u==="Function"){i(c,"`Function(...)` call is denied in the sandbox");return}let g=u.match(/^(Reflect|Object)\.(\w+)$/);if(g){let p=c.getArguments?.()??[];if(p.length>=2){let w=p[0].getText(),d=n(p[1].getText());new Set(["get","has","getOwnPropertyDescriptor","getOwnPropertyDescriptors","ownKeys","getPrototypeOf"]).has(g[2])&&(w==="globalThis"||_.has(w))&&d!==null&&(d==="constructor"||v.has(d))&&i(c,`\`${g[1]}.${g[2]}(${w}, "${d}")\` would reach a denied name`)}}}})}function le(t,r,o){let e=r.getSourceFileOrThrow(t),i=new Set;for(let n of e.getImportDeclarations()){for(let a of n.getNamedImports())i.add(a.getName());let c=n.getDefaultImport();c&&i.add(c.getText());let s=n.getNamespaceImport();s&&i.add(s.getText())}for(let n of e.getVariableDeclarations())i.add(n.getName());for(let n of e.getFunctions()){let c=n.getName();c&&i.add(c)}for(let n of e.getClasses()){let c=n.getName();c&&i.add(c)}e.forEachDescendant(n=>{if(n.getKind()!==l.Identifier)return;let c=n.getText();if(i.has(c)||_.has(c))return;let s=n.getParent();if(s){let a=s.getKind();if(a===l.PropertyAssignment&&s.getNameNode?.()===n||a===l.PropertyAccessExpression&&s.getNameNode?.()===n||a===l.MethodDeclaration||a===l.ImportSpecifier||a===l.ExportSpecifier||a===l.NamespaceImport||a===l.ImportClause||a===l.Parameter||a===l.TypeReference||a===l.TypeQuery)return}if(v.has(c)){let{line:a,column:u}=e.getLineAndColumnAtPos(n.getStart());o.push({path:t,line:a,column:u,message:`identifier "${c}" is denied in the sandbox (FS/network/eval surface)`})}})}function O(t){let r=[],o=new oe({useInMemoryFileSystem:!0,compilerOptions:{target:99,module:99,allowJs:!0,strict:!1}});for(let e of t)o.createSourceFile(e.path,e.source,{overwrite:!0});for(let e of t)ae(e.path,o,r),ce(e.path,o,r),ue(e.path,o,r),le(e.path,o,r);return r}var L=2e5,j=10,S="src/main.ts";function de(t){if(t.files&&t.source)throw new f("pass either `source` or `files`, not both","input-invalid");if(!t.files&&!t.source)throw new f("must pass either `source` or `files`","input-invalid");let r=t.files?t.files:[{path:S,source:t.source??""}];if(r.length===0)throw new f("files array is empty","input-invalid");if(r.length>j)throw new f(`payload exceeds ${j} files`,"input-invalid");let o=0;for(let e of r){if(typeof e.path!="string"||e.path.length===0)throw new f("every file must have a non-empty path","input-invalid");if(typeof e.source!="string"||e.source.length===0)throw new f(`file "${e.path}" has empty source`,"input-invalid");o+=Buffer.byteLength(e.source,"utf8")}if(o>L)throw new f(`total payload is ${o} bytes (max ${L})`,"input-invalid");if(!r.some(e=>e.path===S))throw new f(`payload must include "${S}" \u2014 the runner entry point`,"input-invalid");return r}async function Re(t){let r;try{r=de(t)}catch(n){if(n instanceof f)return{logs:[],facts:{},derived:{},errors:[n.message],durationMs:0,timedOut:!1};throw n}let o=O(r);if(o.length>0)return{logs:[],facts:{},derived:{},errors:o.map(n=>`${n.path}:${n.line}:${n.column} \u2014 ${n.message}`),durationMs:0,timedOut:!1};let e=F(r),i;try{i=await E(r)}catch(n){if(n instanceof h)return{logs:[],facts:{},derived:{},errors:[n.message],durationMs:0,timedOut:!1};throw n}try{return await $({bundledSource:i.source,derivationKeys:e,timeoutMs:t.timeoutMs})}catch(n){if(n instanceof y)return{logs:[],facts:{},derived:{},errors:[n.message],durationMs:0,timedOut:n.code==="timeout"};throw n}}export{f as SandboxError,Re as runInSandbox};
3
+ `;return e.replace(r,r+n)}async function M(e){if(!e.find(r=>r.path===E))throw new g(`payload must include "${E}" \u2014 that's the entry point the runner targets`);let o=new Map;for(let r of e)o.set(r.path,r.source);try{let i=(await X({entryPoints:[`${v}:${E}`],bundle:!0,format:"esm",target:"node20",platform:"node",write:!1,logLevel:"silent",external:["@directive-run/*"],supported:{"top-level-await":!0},plugins:[{name:"directive-sandbox-vfs",setup(c){c.onResolve({filter:/.*/},s=>{if(s.kind==="entry-point")return{path:s.path.replace(`${v}:`,""),namespace:v};if(s.namespace!==v)return null;if(s.path.startsWith("@directive-run/")){let a=Y(s.path);return a?{external:!0,path:a}:{external:!0,path:s.path}}if(s.path.startsWith("./")||s.path.startsWith("../")){let a=s.importer.replace(/\/[^/]+$/,""),l=s.path.replace(/^\.\//,"").replace(/\.js$/,""),f=[`${a}/${l}.ts`,`${a}/${l}.js`,`${a}/${l}`,`${l}.ts`,`${l}.js`].map(d=>d.replace(/\/+/g,"/"));for(let d of f)if(o.has(d))return{path:d,namespace:v};throw new g(`cannot resolve "${s.path}" from "${s.importer}" \u2014 tried ${f.join(", ")}`)}throw new g(`unexpected import "${s.path}" from "${s.importer}" \u2014 only relative or @directive-run/* allowed`)}),c.onLoad({filter:/.*/,namespace:v},s=>{let a=o.get(s.path);if(a===void 0)throw new g(`virtual file missing: ${s.path}`);if(s.path===E){let l=["","// directive-sandbox: lift the runner's `system` binding","// onto a side-channel global so the worker can read","// system.facts.$store.toObject() after execution.","if (typeof system !== 'undefined') {"," (globalThis).__directiveSandbox_system__ = system;","}"].join(`
4
+ `);return{contents:a+l,loader:"ts"}}return{contents:a,loader:"ts"}})}}]})).outputFiles[0]?.text;if(!i)throw new g("esbuild produced no output");let n=J(i);return{source:n,bytes:Buffer.byteLength(n,"utf8")}}catch(r){throw r instanceof g?r:new g(`bundle failed: ${r.message??String(r)}`,r)}}import{existsSync as N,mkdtempSync as Z,rmSync as Q,writeFileSync as ee}from"fs";import{tmpdir as te}from"os";import{dirname as I,join as A}from"path";import{fileURLToPath as re,pathToFileURL as ne}from"url";import{Worker as oe}from"worker_threads";var se=100,ie=1e4,R=5e3,ae=Math.max(1,globalThis.navigator?.hardwareConcurrency??4),S=ae,y=0,T=[];function _(){for(;T.length>0&&y<S;){let e=T.shift();if(!(!e||e.aborted)){y++,e.resolve();return}}}function ce(e){let t=S;if(!Number.isFinite(e)&&e!==Number.POSITIVE_INFINITY)return t;for(S=Math.max(1,Math.floor(e));T.length>0&&y<S;)_();return t}function le(e){return e?.aborted?Promise.reject(e.reason instanceof Error?e.reason:new Error("sandbox: acquireSlot aborted")):y<S?(y++,Promise.resolve()):new Promise((t,o)=>{let r={resolve:t,reject:o,aborted:!1};if(T.push(r),e){let i=()=>{r.aborted=!0,o(e.reason instanceof Error?e.reason:new Error("sandbox: acquireSlot aborted"))};e.addEventListener("abort",i,{once:!0});let n=r.resolve,c=r.reject;r.resolve=()=>{e.removeEventListener("abort",i),n()},r.reject=s=>{e.removeEventListener("abort",i),c(s)}}})}function F(){y=Math.max(0,y-1),y<S&&_()}async function D(){try{let o=new URL("./worker.js",import.meta.url),r=re(o);if(N(r))return r}catch{}let{createRequire:e}=await import("module");return e(import.meta.url).resolve("@directive-run/sandbox/worker")}var x=class extends Error{constructor(o,r){super(o);this.code=r;this.name="WorkerExecError"}};function ue(e){let t=e??R;return Number.isFinite(t)?Math.min(ie,Math.max(se,Math.floor(t))):R}async function de(){let e=te();if(N(e))return e;let t=await D();return I(I(t))}async function pe(e){let t=await de(),o=Z(A(t,"directive-sandbox-")),r=A(o,"bundle.mjs");return ee(r,e,"utf8"),{bundlePath:r,cleanup:()=>{try{Q(o,{recursive:!0,force:!0})}catch{}}}}async function L(e){let t=ue(e.timeoutMs);await le(e.signal);let o,r,i;try{o=await D(),{bundlePath:r,cleanup:i}=await pe(e.bundledSource)}catch(d){throw F(),d}let n=new oe(o,{resourceLimits:{maxOldGenerationSizeMb:32,maxYoungGenerationSizeMb:8,codeRangeSizeMb:16},stderr:!1}),c=null,s=!1,a=!1,l=null,f=Date.now();try{return await new Promise((m,w)=>{let h=!1;n.once("message",b=>{h=!0,b.ok?m(b.result):w(new x(b.error,"worker-error"))}),n.once("error",b=>{h=!0,w(new x(b.message,"worker-error"))}),n.once("exit",b=>{!h&&b!==0&&b!==null&&w(new x(`worker exited with code ${b} before responding`,"worker-error"))}),c=setTimeout(()=>{s=!0,n.terminate(),w(new x(`wall-clock budget of ${t}ms elapsed`,"timeout"))},t),e.signal&&(l=()=>{h||(h=!0,a=!0,n.terminate(),w(new x("sandbox: aborted by caller signal","worker-error")))},e.signal.aborted?l():e.signal.addEventListener("abort",l,{once:!0}));let $={bundlePath:ne(r).href,timeoutMs:t,derivationKeys:e.derivationKeys};n.postMessage($)})}catch(d){if(d instanceof x&&d.code==="timeout")return{logs:[],facts:{},derived:{},errors:[d.message],durationMs:Date.now()-f,timedOut:!0};throw d}finally{c&&clearTimeout(c),l&&e.signal&&e.signal.removeEventListener("abort",l),await n.terminate().catch(()=>{}),i(),F()}}function fe(e,t){let o=0;for(let r=t;r<e.length;r++){let i=e[r];if(i==="{")o+=1;else if(i==="}"&&(o-=1,o===0))return r}return-1}function me(e){let t=[],o=0,r=0,i=0,n=0,c=s=>{let a=e.slice(o,s).trim();if(o=s+1,!a)return;let l=a.match(/^['"]?(\w+)['"]?\s*:/);l&&t.push(l[1])};for(let s=0;s<e.length;s++){let a=e[s];a==="{"?r+=1:a==="}"?r-=1:a==="("?i+=1:a===")"?i-=1:a==="["?n+=1:a==="]"?n-=1:(a===","||a===`
5
+ `)&&r===0&&i===0&&n===0&&c(s)}return c(e.length),t}function O(e,t){let o=new RegExp(`\\b${t}\\s*:\\s*\\{`),r=e.match(o);if(!r||r.index===void 0)return[];let i=e.indexOf("{",r.index);if(i===-1)return[];let n=fe(e,i);return n===-1?[]:me(e.slice(i+1,n))}function W(e){let t=new Set,o=[];for(let r of e){for(let i of O(r.source,"derive"))t.has(i)||(t.add(i),o.push(i));for(let i of O(r.source,"derivations"))t.has(i)||(t.add(i),o.push(i))}return o}var p=class extends Error{constructor(o,r){super(o);this.code=r;this.name="SandboxError"}};import{Project as ge,SyntaxKind as u}from"ts-morph";var j=new Set(["core","ai","query","el","react","vue","svelte","solid","lit","optimistic","timeline","mutator","knowledge","scaffold","claude-plugin","lint","sources"]),C=new Set(["cli","mcp","sandbox","vite-plugin-api-proxy"]);function K(e){let t=e.match(/^@directive-run\/([^/]+)/);return t?t[1]:null}function he(e){if(/^\.{1,2}\/.+\.js$/.test(e))return!0;let t=K(e);return t===null||C.has(t)?!1:j.has(t)}var B=new Set(["createSystem","system","console","Math","JSON","Object","Array","Number","String","Boolean","Symbol","Promise","Error","Date","Map","Set","WeakMap","WeakSet","Reflect","globalThis","undefined","null","NaN","Infinity"]),k=new Set(["process","require","module","__dirname","__filename","fetch","XMLHttpRequest","WebSocket","eval","Function","Buffer","setImmediate","queueMicrotask","setTimeout","setInterval","clearTimeout","clearInterval"]);function xe(e){let t=K(e);return t&&C.has(t)?`import "${e}" is denied \u2014 @directive-run/${t} is a build/CLI/sandbox tool, not for use inside a sandboxed demo`:`import "${e}" is not allowed in the sandbox. Allowed: relative "./X.js" paths or any of @directive-run/{${Array.from(j).sort().join(",")}}.`}function we(e,t,o){let r=t.getSourceFileOrThrow(e);for(let i of r.getImportDeclarations()){let n=i.getModuleSpecifierValue();if(!he(n)){let{line:c,column:s}=r.getLineAndColumnAtPos(i.getStart());o.push({path:e,line:c,column:s,message:xe(n)})}}}function be(e,t,o){let r=t.getSourceFileOrThrow(e);r.forEachDescendant(i=>{if(i.getKind()===u.ImportKeyword){let n=i.getParent();if(n&&n.getKind()===u.CallExpression){let{line:c,column:s}=r.getLineAndColumnAtPos(i.getStart());o.push({path:e,line:c,column:s,message:"dynamic import() is not allowed in the sandbox"})}}if(i.getKind()===u.NewExpression){let n=i.getText();if(/^new\s+Function\s*\(/.test(n)){let{line:c,column:s}=r.getLineAndColumnAtPos(i.getStart());o.push({path:e,line:c,column:s,message:"new Function(...) is not allowed in the sandbox"})}}})}function ye(e,t,o){let r=t.getSourceFileOrThrow(e),i=(c,s)=>{let{line:a,column:l}=r.getLineAndColumnAtPos(c.getStart());o.push({path:e,line:a,column:l,message:s})},n=c=>{let s=c.trim();return s.startsWith('"')&&s.endsWith('"')||s.startsWith("'")&&s.endsWith("'")||s.startsWith("`")&&s.endsWith("`")?s.slice(1,-1):null};r.forEachDescendant(c=>{let s=c.getKind();if(s===u.PropertyAccessExpression){let a=c.getName?.();if(!a)return;if(a==="constructor"){i(c,"`.constructor` access is denied in the sandbox (Function-constructor smuggle vector)");return}if(k.has(a)){i(c,`\`.${a}\` access is denied in the sandbox (FS/network/eval surface) \u2014 accessing a denied global via property syntax was the property-access bypass closed in v0.3.0`);return}}if(s===u.ElementAccessExpression){let a=c.getExpression?.(),l=c.getArgumentExpression?.(),f=a?.getText()??"",d=l?.getText()??"",m=n(d);if(f==="globalThis"&&m!==null){i(c,"bracket-access on `globalThis` with a string literal is denied in the sandbox (use direct identifier reference for allowlisted names)");return}if(m!==null&&(m==="constructor"||k.has(m))){i(c,`bracket-access \`["${m}"]\` is denied in the sandbox (would reach a denied name)`);return}}if(s===u.CallExpression){let l=c.getExpression?.()?.getText()??"";if(l==="Function"){i(c,"`Function(...)` call is denied in the sandbox");return}let f=l.match(/^(Reflect|Object)\.(\w+)$/);if(f){let m=c.getArguments?.()??[];if(m.length>=2){let w=m[0].getText(),h=n(m[1].getText());new Set(["get","has","getOwnPropertyDescriptor","getOwnPropertyDescriptors","ownKeys","getPrototypeOf"]).has(f[2])&&(w==="globalThis"||B.has(w))&&h!==null&&(h==="constructor"||k.has(h))&&i(c,`\`${f[1]}.${f[2]}(${w}, "${h}")\` would reach a denied name`)}}}})}function ve(e,t,o){let r=t.getSourceFileOrThrow(e),i=new Set;for(let n of r.getImportDeclarations()){for(let a of n.getNamedImports())i.add(a.getName());let c=n.getDefaultImport();c&&i.add(c.getText());let s=n.getNamespaceImport();s&&i.add(s.getText())}for(let n of r.getVariableDeclarations())i.add(n.getName());for(let n of r.getFunctions()){let c=n.getName();c&&i.add(c)}for(let n of r.getClasses()){let c=n.getName();c&&i.add(c)}r.forEachDescendant(n=>{if(n.getKind()!==u.Identifier)return;let c=n.getText();if(i.has(c)||B.has(c))return;let s=n.getParent();if(s){let a=s.getKind();if(a===u.PropertyAssignment&&s.getNameNode?.()===n||a===u.PropertyAccessExpression&&s.getNameNode?.()===n||a===u.MethodDeclaration||a===u.ImportSpecifier||a===u.ExportSpecifier||a===u.NamespaceImport||a===u.ImportClause||a===u.Parameter||a===u.TypeReference||a===u.TypeQuery)return}if(k.has(c)){let{line:a,column:l}=r.getLineAndColumnAtPos(n.getStart());o.push({path:e,line:a,column:l,message:`identifier "${c}" is denied in the sandbox (FS/network/eval surface)`})}})}function U(e){let t=[],o=new ge({useInMemoryFileSystem:!0,compilerOptions:{target:99,module:99,allowJs:!0,strict:!1}});for(let r of e)o.createSourceFile(r.path,r.source,{overwrite:!0});for(let r of e)we(r.path,o,t),be(r.path,o,t),ye(r.path,o,t),ve(r.path,o,t);return t}var V=`[^\\s)\\]>"',]`;function Se(e){if(!e)return"";let t=`${V}+`;return e.replace(new RegExp(`file:\\/\\/\\/${V}+`,"g"),"<sandbox>").replace(new RegExp(`\\\\\\\\${t}`,"g"),"<unc>").replace(/[A-Za-z]:\\Users\\[^\\\s)\]>"',]+/g,"<home>").replace(/[A-Za-z]:\\Documents and Settings\\[^\\\s)\]>"',]+/g,"<home>").replace(/[A-Za-z]:\\[^\\\s)\]>"',]+(?:\\[^\\\s)\]>"',]+)+/g,"<path>").replace(new RegExp(`\\/Users\\/${t}`,"g"),"/<home>").replace(new RegExp(`\\/home\\/${t}`,"g"),"/<home>").replace(new RegExp(`\\/private\\/var\\/${t}`,"g"),"<tmp>").replace(new RegExp(`\\/var\\/task\\/${t}`,"g"),"<task>").replace(new RegExp(`\\/tmp\\/${t}`,"g"),"<tmp>").replace(new RegExp(`\\/opt\\/${t}`,"g"),"<opt>").replace(new RegExp(`\\/usr\\/local\\/${t}`,"g"),"<usr-local>").replace(new RegExp(`\\/app\\/${t}`,"g"),"<app>").replace(new RegExp(`\\/srv\\/${t}`,"g"),"<srv>").replace(new RegExp(`\\/workspace\\/${t}`,"g"),"<workspace>").replace(new RegExp(`\\/data\\/${t}`,"g"),"<data>").replace(new RegExp(`\\/etc\\/${t}`,"g"),"<etc>").replace(new RegExp(`\\/root\\/${t}`,"g"),"<root>")}var q=2e5,z=10,P="src/main.ts";function Ee(e){if(e.files&&e.source)throw new p("pass either `source` or `files`, not both","input-invalid");if(!e.files&&!e.source)throw new p("must pass either `source` or `files`","input-invalid");let t=e.files?e.files:[{path:P,source:e.source??""}];if(t.length===0)throw new p("files array is empty","input-invalid");if(t.length>z)throw new p(`payload exceeds ${z} files`,"input-invalid");let o=0;for(let r of t){if(typeof r.path!="string"||r.path.length===0)throw new p("every file must have a non-empty path","input-invalid");if(typeof r.source!="string"||r.source.length===0)throw new p(`file "${r.path}" has empty source`,"input-invalid");o+=Buffer.byteLength(r.source,"utf8")}if(o>q)throw new p(`total payload is ${o} bytes (max ${q})`,"input-invalid");if(!t.some(r=>r.path===P))throw new p(`payload must include "${P}" \u2014 the runner entry point`,"input-invalid");return t}async function Ve(e){let t;try{t=Ee(e)}catch(n){if(n instanceof p)return{logs:[],facts:{},derived:{},errors:[n.message],durationMs:0,timedOut:!1};throw n}let o=U(t);if(o.length>0)return{logs:[],facts:{},derived:{},errors:o.map(n=>`${n.path}:${n.line}:${n.column} \u2014 ${n.message}`),durationMs:0,timedOut:!1};let r=W(t),i;try{i=await M(t)}catch(n){if(n instanceof g)return{logs:[],facts:{},derived:{},errors:[n.message],durationMs:0,timedOut:!1};throw n}try{return await L({bundledSource:i.source,derivationKeys:r,timeoutMs:e.timeoutMs,signal:e.signal})}catch(n){if(n instanceof x)return{logs:[],facts:{},derived:{},errors:[n.message],durationMs:0,timedOut:n.code==="timeout"};throw n}}export{p as SandboxError,Ve as runInSandbox,Se as sanitizeStack,ce as setMaxConcurrentWorkers};
6
6
  //# sourceMappingURL=index.js.map