@rulvar/planner 1.38.0 → 1.40.0

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.
@@ -1,4 +1,6 @@
1
1
  import { ScriptRejected } from "@rulvar/core";
2
+ import * as acorn from "acorn";
3
+ import { scanDialect } from "eslint-plugin-rulvar";
2
4
  //#region src/compile.ts
3
5
  /**
4
6
  * The exact curated sandbox global set, in canonical order.
@@ -164,41 +166,62 @@ function scanImports(source, blanked, allowImports) {
164
166
  }
165
167
  return diagnostics;
166
168
  }
167
- const CODEGEN_WORD = /\b(eval|Function)\b/g;
168
- const CONSTRUCTOR_ACCESS = /\.\s*constructor\b/g;
169
+ const CODEGEN_DIAGNOSTIC = {
170
+ eval: {
171
+ ruleId: "no-eval",
172
+ message: "eval is not part of the sandbox dialect; dynamically generated code reopens the import allowlist and the ambient capability surface the dialect closes"
173
+ },
174
+ "function-constructor": {
175
+ ruleId: "no-function-constructor",
176
+ message: "the Function constructor is not part of the sandbox dialect; it compiles arbitrary code that bypasses the import allowlist (use only the curated globals)"
177
+ },
178
+ "constructor-access": {
179
+ ruleId: "no-constructor-access",
180
+ message: "constructor access is not part of the sandbox dialect; it reaches the Function constructor from any function value and reopens dynamic code generation"
181
+ }
182
+ };
169
183
  /**
170
184
  * Rejects dynamic code generation. `eval` and the Function constructor
171
185
  * compile attacker supplied text in a fresh scope where the import
172
186
  * expression, ambient time, and host capability are back in reach, so
173
187
  * banning `import` while allowing `new Function("return import(x)")` closes
174
- * nothing. `.constructor` access is the same vector by another name: every
175
- * function value exposes the Function constructor as its `.constructor`
176
- * ((function(){}).constructor === Function). Literals and comments are
177
- * blanked before the scan, so a banned word inside a string never matches;
178
- * the identifier form (not just calls) is rejected because a captured
179
- * reference (`const f = Function`) is the trivial workaround.
188
+ * nothing. Constructor reconstruction is the same vector by another name:
189
+ * every function value exposes the Function constructor through its prototype
190
+ * ((function(){}).constructor === Function), reachable as `.constructor`,
191
+ * `["constructor"]`, a computed key that folds to the constant, a
192
+ * `{ constructor: x }` destructuring, or `Reflect.get(fn, "constructor")`.
193
+ *
194
+ * The scan runs the SAME shared AST policy the `no-code-generation` ESLint
195
+ * rule uses (`scanDialect`), so the linter and this structural gate reach one
196
+ * decision for every statically visible form. A truly dynamic key
197
+ * (`fn[valueFromAnAgent]`) cannot be resolved statically without rejecting
198
+ * every dynamic property access, so it is left to the worker realm, which
199
+ * neutralizes the runtime reconstruction path. A syntax error is reported by
200
+ * the AsyncFunction construction below, so a parse failure here yields no
201
+ * diagnostics rather than a duplicate.
180
202
  */
181
- function scanCodegen(source, blanked) {
182
- const diagnostics = [];
183
- for (let match = CODEGEN_WORD.exec(blanked); match !== null; match = CODEGEN_WORD.exec(blanked)) {
184
- const at = positionAt(source, match.index);
185
- if (match[1] === "eval") diagnostics.push({
186
- ruleId: "no-eval",
187
- message: "eval is not part of the sandbox dialect; dynamically generated code reopens the import allowlist and the ambient capability surface the dialect closes",
188
- ...at
189
- });
190
- else diagnostics.push({
191
- ruleId: "no-function-constructor",
192
- message: "the Function constructor is not part of the sandbox dialect; it compiles arbitrary code that bypasses the import allowlist (use only the curated globals)",
193
- ...at
203
+ function scanCodegen(source) {
204
+ let program;
205
+ try {
206
+ program = acorn.parse(source, {
207
+ ecmaVersion: "latest",
208
+ sourceType: "module",
209
+ allowReturnOutsideFunction: true,
210
+ allowAwaitOutsideFunction: true,
211
+ locations: true
194
212
  });
213
+ } catch {
214
+ return [];
195
215
  }
196
- for (let match = CONSTRUCTOR_ACCESS.exec(blanked); match !== null; match = CONSTRUCTOR_ACCESS.exec(blanked)) diagnostics.push({
197
- ruleId: "no-constructor-access",
198
- message: ".constructor access is not part of the sandbox dialect; it reaches the Function constructor from any function value and reopens dynamic code generation",
199
- ...positionAt(source, match.index)
216
+ return scanDialect(program).map((finding) => {
217
+ const { ruleId, message } = CODEGEN_DIAGNOSTIC[finding.kind];
218
+ return {
219
+ ruleId,
220
+ message,
221
+ line: finding.line,
222
+ column: finding.column
223
+ };
200
224
  });
201
- return diagnostics;
202
225
  }
203
226
  /**
204
227
  * Validates and compiles planner-generated source into a CompiledWorkflow.
@@ -216,7 +239,7 @@ function compileScript(source, o) {
216
239
  }] } });
217
240
  const blanked = blankLiterals(source);
218
241
  diagnostics.push(...scanImports(source, blanked, allowImports));
219
- diagnostics.push(...scanCodegen(source, blanked));
242
+ diagnostics.push(...scanCodegen(source));
220
243
  try {
221
244
  new AsyncFunctionCtor(...SANDBOX_GLOBALS, source);
222
245
  } catch (error) {
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { n as compileScript, r as scriptDiagnosticsOf, t as SANDBOX_GLOBALS } from "./compile-BOStPCrA.js";
1
+ import { n as compileScript, r as scriptDiagnosticsOf, t as SANDBOX_GLOBALS } from "./compile-DjzdBuva.js";
2
2
  import { ConfigError, InMemoryStore, SANDBOX_AGENT_OPT_KEYS, SandboxError, ScriptRejected, createEngine, createSandboxBridge, defineWorkflow, readRunMeta, toJournalValue } from "@rulvar/core";
3
+ import { toJsonDiagnostics, workflowsConfig } from "eslint-plugin-rulvar";
3
4
  import { createHash } from "node:crypto";
4
5
  import { Linter } from "eslint";
5
- import { toJsonDiagnostics, workflowsConfig } from "eslint-plugin-rulvar";
6
6
  import { MessageChannel, Worker } from "node:worker_threads";
7
7
  //#region src/api-card.ts
8
8
  /**
@@ -1,4 +1,4 @@
1
- import { t as SANDBOX_GLOBALS } from "./compile-BOStPCrA.js";
1
+ import { t as SANDBOX_GLOBALS } from "./compile-DjzdBuva.js";
2
2
  import { NonSerializableValueError, toJournalValue } from "@rulvar/core";
3
3
  import { createHash } from "node:crypto";
4
4
  import { parentPort } from "node:worker_threads";
@@ -137,6 +137,22 @@ function main(port, init) {
137
137
  Reflect.set(globalThis, "process", void 0);
138
138
  Reflect.set(globalThis, "eval", void 0);
139
139
  Reflect.set(globalThis, "Function", void 0);
140
+ const asyncProto = Object.getPrototypeOf(shimAsyncMarker);
141
+ const AsyncFunctionCtor = asyncProto.constructor;
142
+ const denyCodegen = function constructor() {
143
+ throw new EvalError("dynamic code generation is not available in the sandbox dialect");
144
+ };
145
+ for (const proto of [
146
+ Object.getPrototypeOf(shimRegularMarker),
147
+ asyncProto,
148
+ Object.getPrototypeOf(shimGeneratorMarker),
149
+ Object.getPrototypeOf(shimAsyncGeneratorMarker)
150
+ ]) Object.defineProperty(proto, "constructor", {
151
+ value: denyCodegen,
152
+ writable: false,
153
+ enumerable: false,
154
+ configurable: false
155
+ });
140
156
  const sandboxGlobals = {
141
157
  agent: (prompt, opts) => callHost("agent", {
142
158
  prompt,
@@ -270,7 +286,6 @@ function main(port, init) {
270
286
  default: return;
271
287
  }
272
288
  });
273
- const AsyncFunctionCtor = Object.getPrototypeOf(shimAsyncMarker).constructor;
274
289
  enterFrame();
275
290
  (async () => {
276
291
  try {
@@ -288,8 +303,13 @@ function main(port, init) {
288
303
  }
289
304
  })();
290
305
  }
291
- /** Marker reaching the AsyncFunction constructor; never invoked. */
306
+ /** Markers reaching each Function family constructor prototype; never invoked. */
292
307
  const shimAsyncMarker = async () => void 0;
308
+ const shimRegularMarker = () => void 0;
309
+ function* shimGeneratorMarker() {}
310
+ async function* shimAsyncGeneratorMarker() {
311
+ return void 0;
312
+ }
293
313
  parentPort?.once("message", (init) => {
294
314
  main(init.port, init);
295
315
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/planner",
3
- "version": "1.38.0",
3
+ "version": "1.40.0",
4
4
  "description": "Rulvar flagship hybrid mode: plan agent, compileScript, WorkerSandboxRunner, self-repair loop.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,15 +22,16 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
+ "acorn": "^8.17.0",
25
26
  "eslint": "^9.39.4",
26
- "eslint-plugin-rulvar": "1.38.0",
27
- "@rulvar/core": "1.38.0"
27
+ "@rulvar/core": "1.40.0",
28
+ "eslint-plugin-rulvar": "1.40.0"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/node": "^22.20.0",
31
32
  "tsdown": "^0.22.3",
32
33
  "typescript": "~6.0.3",
33
- "@rulvar/testing": "1.38.0"
34
+ "@rulvar/testing": "1.40.0"
34
35
  },
35
36
  "repository": {
36
37
  "type": "git",