@poe-platform/safe-bash 0.1.423 → 0.1.424

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.
@@ -9195,6 +9195,7 @@ var InvocationScope = class _InvocationScope {
9195
9195
  parent;
9196
9196
  #children = /* @__PURE__ */ new Set();
9197
9197
  #callbacks = [];
9198
+ #finalizers = [];
9198
9199
  #work = /* @__PURE__ */ new Set();
9199
9200
  #controller = new AbortController();
9200
9201
  #closed = false;
@@ -9202,6 +9203,10 @@ var InvocationScope = class _InvocationScope {
9202
9203
  get signal() {
9203
9204
  return this.#controller.signal;
9204
9205
  }
9206
+ registerFinalizer(finalize) {
9207
+ this.assertOpen();
9208
+ this.#finalizers.push(finalize);
9209
+ }
9205
9210
  assertOpen() {
9206
9211
  this.callerSignal?.throwIfAborted();
9207
9212
  if (this.#closed) throw this.signal.reason;
@@ -9256,6 +9261,13 @@ var InvocationScope = class _InvocationScope {
9256
9261
  ...this.#work
9257
9262
  ]);
9258
9263
  } finally {
9264
+ for (const finalize of this.#finalizers.splice(0)) {
9265
+ try {
9266
+ finalize();
9267
+ } catch (error) {
9268
+ this.failures.push(error);
9269
+ }
9270
+ }
9259
9271
  if (this.parent) this.parent.#children.delete(this);
9260
9272
  }
9261
9273
  });
@@ -11605,6 +11617,89 @@ async function matchEre(program, subject, ledger, signal) {
11605
11617
  return Object.freeze({ matched: false, captures: Object.freeze([]), values: Object.freeze([]) });
11606
11618
  }
11607
11619
 
11620
+ // packages/safe-bash/src/shell/path-lookup.ts
11621
+ init_platform();
11622
+ var maxCacheEntries = 256;
11623
+ var maxCacheBytes = 64 * 1024;
11624
+ var PathLookup = class {
11625
+ #entries = /* @__PURE__ */ new Map();
11626
+ #bytes = 0;
11627
+ #pending = 0;
11628
+ #generation = 0;
11629
+ suspend() {
11630
+ this.#entries.clear();
11631
+ this.#bytes = 0;
11632
+ this.#generation++;
11633
+ this.#pending++;
11634
+ let closed = false;
11635
+ return () => {
11636
+ if (closed) return;
11637
+ closed = true;
11638
+ this.#pending--;
11639
+ this.#generation++;
11640
+ };
11641
+ }
11642
+ suspendUntilClosed(scope) {
11643
+ scope.assertOpen();
11644
+ const resume = this.suspend();
11645
+ scope.registerFinalizer(resume);
11646
+ }
11647
+ async isFile(fs, path, signal) {
11648
+ signal.throwIfAborted();
11649
+ const cached = this.#entries.get(path);
11650
+ if (cached) return cached.file;
11651
+ const generation = this.#generation;
11652
+ let file;
11653
+ try {
11654
+ file = (await fs.stat(path, { signal })).type === "file";
11655
+ } catch (error) {
11656
+ signal.throwIfAborted();
11657
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
11658
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
11659
+ file = false;
11660
+ }
11661
+ signal.throwIfAborted();
11662
+ const bytes2 = import_buffer.Buffer.byteLength(path) + 64;
11663
+ if (!this.#pending && generation === this.#generation && bytes2 <= maxCacheBytes && !this.#entries.has(path)) {
11664
+ while (this.#entries.size >= maxCacheEntries || this.#bytes + bytes2 > maxCacheBytes) {
11665
+ const oldest = this.#entries.entries().next().value;
11666
+ this.#bytes -= oldest[1].bytes;
11667
+ this.#entries.delete(oldest[0]);
11668
+ }
11669
+ this.#entries.set(path, { file, bytes: bytes2 });
11670
+ this.#bytes += bytes2;
11671
+ }
11672
+ return file;
11673
+ }
11674
+ };
11675
+ function* pathTargets(name2, path, limits, signal, fail2) {
11676
+ signal.throwIfAborted();
11677
+ if (path !== void 0 && import_buffer.Buffer.byteLength(path) > limits.maxExpansionBytes) fail2("maxExpansionBytes");
11678
+ if (name2.includes("/") || path === void 0) {
11679
+ if (limits.maxExpansionFields < 1) fail2("maxExpansionFields");
11680
+ yield name2;
11681
+ return;
11682
+ }
11683
+ let fields = 1;
11684
+ for (let separator = path.indexOf(":"); separator >= 0; separator = path.indexOf(":", separator + 1)) {
11685
+ signal.throwIfAborted();
11686
+ if (++fields > limits.maxExpansionFields) fail2("maxExpansionFields");
11687
+ }
11688
+ if (fields > limits.maxExpansionFields) fail2("maxExpansionFields");
11689
+ let start = 0;
11690
+ let consulted = 0;
11691
+ while (start <= path.length) {
11692
+ signal.throwIfAborted();
11693
+ if (consulted >= limits.maxPathComponents) fail2("maxPathComponents");
11694
+ consulted++;
11695
+ const end = path.indexOf(":", start);
11696
+ const component = path.slice(start, end < 0 ? path.length : end);
11697
+ yield `${component || "."}${component.endsWith("/") ? "" : "/"}${name2}`;
11698
+ if (end < 0) return;
11699
+ start = end + 1;
11700
+ }
11701
+ }
11702
+
11608
11703
  // packages/safe-bash/src/shell/runtime.ts
11609
11704
  var defaultLimits = {
11610
11705
  maxParseUnits: defaultMaxParseUnits,
@@ -11612,6 +11707,7 @@ var defaultLimits = {
11612
11707
  maxOutputBytes: 16 * 1024 * 1024,
11613
11708
  maxCommands: 1e4,
11614
11709
  maxFileSystemOperations: 1e5,
11710
+ maxPathComponents: 64,
11615
11711
  maxRedirects: 64,
11616
11712
  maxPipelineStages: 64,
11617
11713
  maxLoopIterations: 1e4,
@@ -11705,6 +11801,7 @@ var Budget = class {
11705
11801
  }
11706
11802
  limits;
11707
11803
  executionScope = Object.freeze({});
11804
+ pathLookup = new PathLookup();
11708
11805
  parsing;
11709
11806
  values;
11710
11807
  commands = 0;
@@ -13914,9 +14011,11 @@ var Runtime = class _Runtime {
13914
14011
  });
13915
14012
  } };
13916
14013
  };
14014
+ const resumePathCache = this.budget.pathLookup.suspend();
13917
14015
  const release = () => {
13918
14016
  if (closed) return;
13919
14017
  closed = true;
14018
+ resumePathCache();
13920
14019
  if (--file.references === 0 && this.outputFiles.get(path) === file) this.outputFiles.delete(path);
13921
14020
  };
13922
14021
  let target2;
@@ -14191,6 +14290,7 @@ var Runtime = class _Runtime {
14191
14290
  if (argumentValues.values.every((value2) => typeof value2 === "string")) Reflect.deleteProperty(context, "argumentValues");
14192
14291
  const middleware = this.middleware.map((handler) => (context2, next) => {
14193
14292
  scope.assertOpen();
14293
+ this.budget.pathLookup.suspendUntilClosed(scope);
14194
14294
  let downstream;
14195
14295
  const raw = handler(context2, () => {
14196
14296
  downstream = next();
@@ -14384,6 +14484,7 @@ var Runtime = class _Runtime {
14384
14484
  await this.diagnostic({ ...io, ...context2 }, `${context2.command}: command not found`);
14385
14485
  return { exitCode: 127 };
14386
14486
  }
14487
+ this.budget.pathLookup.suspendUntilClosed(scope);
14387
14488
  const raw = definition.execute(forwarded);
14388
14489
  const observed = this.observeRuntimeReturn(raw, runtimeFrame);
14389
14490
  return await interruptible(observed, this.signal);
@@ -14537,25 +14638,20 @@ ${functionDisplay(name2, state.functions.get(name2))}`;
14537
14638
  }
14538
14639
  async searchPaths(name2, state, all = false, discovery = false) {
14539
14640
  if (!name2) return [];
14540
- const path = state.variables.PATH;
14541
- if (path !== void 0 && import_buffer.Buffer.byteLength(path) > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
14542
- const components = name2.includes("/") || path === void 0 ? [void 0] : path.split(":");
14543
- if (components.length > this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
14544
14641
  let denied;
14545
14642
  const matches = [];
14546
- for (const component of components) {
14547
- this.signal.throwIfAborted();
14548
- const target = component === void 0 ? name2 : `${component || "."}${component?.endsWith("/") ? "" : "/"}${name2}`;
14643
+ for (const target of pathTargets(name2, state.variables.PATH, this.budget.limits, this.signal, (limit) => this.budget.fail(limit))) {
14549
14644
  const resolved = resolvePath(state.cwd, target);
14550
14645
  try {
14551
14646
  const options3 = { signal: this.signal };
14552
- if ((await interruptible(this.fs.stat(resolved, options3), this.signal)).type !== "file") continue;
14647
+ if (!await interruptible(this.budget.pathLookup.isFile(this.fs, resolved, this.signal), this.signal)) continue;
14553
14648
  if (this.fs.capabilities.permissions !== true) throw new CommandFailure(`${target}: execution permissions are not supported by this filesystem`, 126);
14554
14649
  await interruptible(this.fs.access(resolved, ACCESS_MODES.X_OK, options3), this.signal);
14555
14650
  matches.push(target);
14556
14651
  if (!all) return matches;
14557
14652
  } catch (error) {
14558
14653
  this.signal.throwIfAborted();
14654
+ if (error instanceof ShellLimitError) throw error;
14559
14655
  if (error instanceof CommandFailure) {
14560
14656
  if (discovery) continue;
14561
14657
  throw error;
@@ -14805,6 +14901,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
14805
14901
  const runtimeFrame = {};
14806
14902
  const middleware = this.middleware.map((handler) => (context2, next) => {
14807
14903
  scope.assertOpen();
14904
+ this.budget.pathLookup.suspendUntilClosed(scope);
14808
14905
  let downstream;
14809
14906
  const raw = handler(context2, () => {
14810
14907
  downstream = next();
@@ -14887,7 +14984,10 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
14887
14984
  return { exitCode: await runtime.interpreter(forwarded, state2, childIO, loadedSource) };
14888
14985
  }
14889
14986
  if (direct) return { exitCode: await runtime.scriptFile(forwarded, state2, childIO, command2, forwarded.args, true) };
14890
- if (definition) return definition.execute(forwarded);
14987
+ if (definition) {
14988
+ runtime.budget.pathLookup.suspendUntilClosed(childIO[invocationScope]);
14989
+ return definition.execute(forwarded);
14990
+ }
14891
14991
  await writeDiagnostic(forwarded.stderr, `env: ${command2}: command not found
14892
14992
  `);
14893
14993
  return { exitCode: 127 };
@@ -14907,6 +15007,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
14907
15007
  args: argumentValues.args,
14908
15008
  argumentValues
14909
15009
  }, state, io, async (runtime, forwarded) => {
15010
+ runtime.budget.pathLookup.suspendUntilClosed(io[invocationScope]);
14910
15011
  let failed = false;
14911
15012
  let failure;
14912
15013
  let failureReport;
@@ -15079,16 +15180,11 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
15079
15180
  try {
15080
15181
  const options3 = { signal: this.signal };
15081
15182
  if (filename && !filename.includes("/") && state.variables.PATH) {
15082
- if (import_buffer.Buffer.byteLength(state.variables.PATH) > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
15083
- const components = state.variables.PATH.split(":");
15084
- if (components.length > this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
15085
15183
  let found = false;
15086
- for (const component of components) {
15087
- this.signal.throwIfAborted();
15088
- const candidate = `${component || "."}${component.endsWith("/") ? "" : "/"}${filename}`;
15184
+ for (const candidate of pathTargets(filename, state.variables.PATH, this.budget.limits, this.signal, (limit) => this.budget.fail(limit))) {
15089
15185
  const path2 = resolvePath(state.cwd, candidate);
15090
15186
  try {
15091
- if ((await interruptible(this.fs.stat(path2, options3), this.signal)).type !== "file") continue;
15187
+ if (!await interruptible(this.budget.pathLookup.isFile(this.fs, path2, this.signal), this.signal)) continue;
15092
15188
  await interruptible(this.fs.access(path2, ACCESS_MODES.R_OK, options3), this.signal);
15093
15189
  target = candidate;
15094
15190
  found = true;
@@ -17697,6 +17793,7 @@ var cloudflareWorkerLimits = Object.freeze({
17697
17793
  maxOutputBytes: 4 * 1024 * 1024,
17698
17794
  maxCommands: 1e3,
17699
17795
  maxFileSystemOperations: 1e4,
17796
+ maxPathComponents: 64,
17700
17797
  maxRedirects: 64,
17701
17798
  maxPipelineStages: 64,
17702
17799
  maxLoopIterations: 1e3,