@oxygen-agent/cli 1.46.0 → 1.64.5

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,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import * as vm from "node:vm";
2
+ import * as vm from "node:vm"; // skipcq: JS-C1003
3
3
  export const WORKFLOW_MANIFEST_VERSION = 1;
4
4
  export const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
5
5
  export const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
@@ -8,16 +8,16 @@ export const DEFAULT_WORKFLOW_CRON_TIMEZONE = "UTC";
8
8
  // Compatibility and determinism lint only. The Vercel sandbox process,
9
9
  // denied network policy, and runtime global guards are the security boundary.
10
10
  const UNSAFE_RECIPE_BUNDLE_PATTERNS = [
11
- { token: "globalThis", pattern: /\bglobalThis\b/ },
11
+ { token: "globalThis", pattern: /\bglobalThis\b/ }, // skipcq: SCT-A000
12
12
  { token: "global", pattern: /\bglobal\b/ },
13
13
  { token: "process", pattern: /\bprocess\b/ },
14
14
  { token: "fetch", pattern: /\bfetch\b/ },
15
- { token: "XMLHttpRequest", pattern: /\bXMLHttpRequest\b/ },
15
+ { token: "XMLHttpRequest", pattern: /\bXMLHttpRequest\b/ }, // skipcq: SCT-A000
16
16
  { token: "WebSocket", pattern: /\bWebSocket\b/ },
17
- { token: "EventSource", pattern: /\bEventSource\b/ },
17
+ { token: "EventSource", pattern: /\bEventSource\b/ }, // skipcq: SCT-A000
18
18
  { token: "require", pattern: /\brequire\b/ },
19
- { token: "dynamic import", pattern: /\bimport(?:\s|\/\*[\s\S]*?\*\/)*\(/ },
20
- { token: "node module import", pattern: /\b(?:node:)?(?:fs|child_process|net|tls|http|https|dns|dgram|worker_threads)\b/ },
19
+ { token: "dynamic import", pattern: /\bimport(?:\s|\/\*[\s\S]*?\*\/)*\(/ }, // skipcq: SCT-A000
20
+ { token: "node module import", pattern: /\b(?:node:)?(?:fs|child_process|net|tls|http|https|dns|dgram|worker_threads)\b/ }, // skipcq: SCT-A000
21
21
  { token: "eval", pattern: /\beval\b/ },
22
22
  { token: "Function", pattern: /\bFunction\b/ },
23
23
  { token: "constructor", pattern: /\bconstructor\b/ },
@@ -26,7 +26,7 @@ const UNSAFE_RECIPE_BUNDLE_PATTERNS = [
26
26
  { token: "Math.random", pattern: /\bMath\s*\.\s*random\b/ },
27
27
  { token: "crypto.randomUUID", pattern: /\bcrypto\s*\.\s*randomUUID\b/ },
28
28
  { token: "setTimeout", pattern: /\bsetTimeout\b/ },
29
- { token: "setInterval", pattern: /\bsetInterval\b/ },
29
+ { token: "setInterval", pattern: /\bsetInterval\b/ }, // skipcq: SCT-A000
30
30
  ];
31
31
  export function defineWorkflow(input) {
32
32
  return {
@@ -195,7 +195,8 @@ export function buildRecipeManifest(input) {
195
195
  };
196
196
  return manifest;
197
197
  }
198
- export function lintWorkflowManifest(value, options = {}) {
198
+ export function lintWorkflowManifest(// skipcq: JS-R1005
199
+ value, options = {}) {
199
200
  if (isRecord(value)
200
201
  && typeof value.compiler_version === "string"
201
202
  && value.compiler_version.startsWith("oxygen-recipes-")) {
@@ -294,7 +295,8 @@ export function lintWorkflowManifest(value, options = {}) {
294
295
  }
295
296
  return { ok: issues.length === 0, issues };
296
297
  }
297
- export function lintRecipeManifest(value, options = {}) {
298
+ export function lintRecipeManifest(// skipcq: JS-R1005
299
+ value, options = {}) {
298
300
  const issues = [];
299
301
  const add = (path, code, message) => issues.push({ path, code, message });
300
302
  if (!isRecord(value)) {
@@ -409,31 +411,49 @@ export async function runPureWorkflowFunction(input) {
409
411
  const first = issues[0];
410
412
  throw new Error(first ? `${first.code}: ${first.message}` : "Invalid workflow function source.");
411
413
  }
414
+ // Cross the host/sandbox boundary as a JSON string, then parse INSIDE the
415
+ // vm context. If we passed the host object directly its prototype chain
416
+ // would point at the host's Object/Function, and the regex token blacklist
417
+ // is trivially bypassed (e.g. "con"+"structor") to reach
418
+ // __oxygen_context.constructor.constructor === host Function — which is
419
+ // not affected by the new context's codeGeneration setting and yields
420
+ // worker-process RCE. Parsing inside the context rebinds the prototype to
421
+ // the sandboxed Object, so the same walk reaches the sandboxed Function,
422
+ // which then trips contextCodeGeneration.strings=false below.
412
423
  const context = toJsonValue(input.context, "context");
424
+ const contextJson = JSON.stringify(context);
413
425
  const sandbox = Object.create(null);
414
- sandbox.__oxygen_context = context;
415
- const script = new vm.Script(`"use strict";\nconst __oxygen_fn = (${input.source});\n__oxygen_fn(__oxygen_context);`);
416
- const result = script.runInNewContext(sandbox, { timeout: timeoutMs });
426
+ sandbox.__oxygen_context_json = contextJson;
427
+ // Shadow dangerous globals on the sandbox surface. Defense-in-depth: the
428
+ // load-bearing block is contextCodeGeneration; this just removes the
429
+ // obvious top-level handles.
430
+ sandbox.Function = undefined;
431
+ sandbox.eval = undefined;
432
+ sandbox.setTimeout = undefined;
433
+ sandbox.setInterval = undefined;
434
+ sandbox.setImmediate = undefined;
435
+ sandbox.queueMicrotask = undefined;
436
+ sandbox.WebAssembly = undefined;
437
+ const script = new vm.Script(`"use strict";\n`
438
+ + `const __oxygen_context = JSON.parse(__oxygen_context_json);\n`
439
+ + `const __oxygen_fn = (${input.source});\n`
440
+ + `__oxygen_fn(__oxygen_context);`);
441
+ // Disable dynamic code generation in the sandboxed context. Combined with
442
+ // the JSON re-parse above, this means every prototype-walk path to
443
+ // Function — direct, via context.constructor.constructor, or any other
444
+ // reachable Function instance — throws EvalError when called with source.
445
+ const result = script.runInNewContext(sandbox, {
446
+ timeout: timeoutMs,
447
+ contextCodeGeneration: {
448
+ strings: false,
449
+ wasm: false,
450
+ },
451
+ });
417
452
  const resolved = isPromiseLike(result)
418
453
  ? await withTimeout(result, timeoutMs)
419
454
  : result;
420
455
  return enforceJsonOutput(resolved, maxOutputBytes);
421
456
  }
422
- export function getWorkflowApplySchema() {
423
- return workflowApplySchema;
424
- }
425
- export function getWorkflowSchema(subject = "apply") {
426
- const schemas = {
427
- apply: workflowApplySchema,
428
- call: workflowCallSchema,
429
- event: workflowEventEmitSchema,
430
- trigger: workflowTriggerSchema,
431
- manifest: workflowManifestSchema,
432
- };
433
- if (subject === "all")
434
- return { schemas };
435
- return schemas[subject];
436
- }
437
457
  export const workflowApplySchema = {
438
458
  $schema: "https://json-schema.org/draft/2020-12/schema",
439
459
  title: "OXYGEN Workflow Apply Input",
@@ -516,7 +536,23 @@ export const workflowManifestSchema = {
516
536
  additionalProperties: true,
517
537
  required: ["manifest_version", "workflow", "steps", "source_hash", "compiler_version"],
518
538
  };
519
- function validateJsonSchemaValueInto(value, schema, path, issues) {
539
+ export function getWorkflowApplySchema() {
540
+ return workflowApplySchema;
541
+ }
542
+ export function getWorkflowSchema(subject = "apply") {
543
+ const schemas = {
544
+ apply: workflowApplySchema,
545
+ call: workflowCallSchema,
546
+ event: workflowEventEmitSchema,
547
+ trigger: workflowTriggerSchema,
548
+ manifest: workflowManifestSchema,
549
+ };
550
+ if (subject === "all")
551
+ return { schemas };
552
+ return schemas[subject];
553
+ }
554
+ function validateJsonSchemaValueInto(// skipcq: JS-R1005
555
+ value, schema, path, issues) {
520
556
  const startCount = issues.length;
521
557
  if (schema.const !== undefined && !jsonEqual(value, schema.const)) {
522
558
  issues.push({
@@ -638,7 +674,8 @@ function formatSchemaTypes(types) {
638
674
  function jsonEqual(left, right) {
639
675
  return JSON.stringify(left) === JSON.stringify(right);
640
676
  }
641
- function validateTrigger(value, path, add) {
677
+ function validateTrigger(// skipcq: JS-R1005
678
+ value, path, add) {
642
679
  if (!isRecord(value)) {
643
680
  add(path, "invalid_trigger", "Workflow trigger must be an object.");
644
681
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.46.0",
3
+ "version": "1.64.5",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",