@sema-agent/core 7.24.2 → 7.24.3

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.
@@ -203,12 +203,12 @@ function outOfRootGrantCandidateOf(paths, boundary) {
203
203
  return paths.length === 1 ? { path: paths[0], exact: true } : undefined;
204
204
  return { path: candidate };
205
205
  }
206
- const NO_PATH_OPERAND_COMMANDS = new Set([
206
+ export const NO_PATH_OPERAND_COMMANDS = new Set([
207
207
  "pwd", "echo", "printf", "whoami", "uname", "which", "tr", "basename", "dirname",
208
208
  "cal", "uptime", "id", "groups", "nproc", "locale", "free", "true", "false", "seq", "expr", "type", "sleep",
209
209
  ]);
210
210
  const XARGS_TARGETS_ACCEPTED_UNJUDGED = NO_PATH_OPERAND_COMMANDS;
211
- function isPathShapedToken(tok) {
211
+ export function isPathShapedToken(tok) {
212
212
  return tok.includes("/") || tok.startsWith("~") || tok === "." || tok === "..";
213
213
  }
214
214
  export function resolveOperandLexically(base, operand, homeDir) {
@@ -261,7 +261,7 @@ function tokensMoveBase(toks) {
261
261
  return false;
262
262
  return pos.refusal !== undefined || pos.program?.basename === "cd";
263
263
  }
264
- const SHELL_REENTRY_PROGRAM_RE = /^(?:(?:ba|z|da|k|fi|a|mk|ya|pdk|c|tc)?sh|eval|source|\.|parallel)$/;
264
+ export const SHELL_REENTRY_PROGRAM_RE = /^(?:(?:ba|z|da|k|fi|a|mk|ya|pdk|c|tc)?sh|eval|source|\.|parallel)$/;
265
265
  export function refusedCommandMayRead(command, allow, boundary, declinesAreEvidence) {
266
266
  const scan = { declined: false };
267
267
  const exempt = refusedCommandExempt(command, allow, boundary, declinesAreEvidence, scan, 0);
@@ -436,7 +436,7 @@ function tokenizeSegment(segment) {
436
436
  return { folded: [], raw: [] };
437
437
  return wordsOf(lexed.segments[0]);
438
438
  }
439
- function hasUnquotedExpansionMetachar(rawToken) {
439
+ export function hasUnquotedExpansionMetachar(rawToken) {
440
440
  let open;
441
441
  for (const ch of rawToken) {
442
442
  if (open === undefined && (ch === '"' || ch === "'")) {
@@ -454,7 +454,7 @@ function hasUnquotedExpansionMetachar(rawToken) {
454
454
  }
455
455
  return false;
456
456
  }
457
- function hasUnquotedGlobMetachar(rawToken) {
457
+ export function hasUnquotedGlobMetachar(rawToken) {
458
458
  let open;
459
459
  for (const ch of rawToken) {
460
460
  if (open === undefined && (ch === '"' || ch === "'")) {
@@ -481,7 +481,7 @@ function longOptionNameOf(tok) {
481
481
  function isLongOptionAbbrevOf(name, full) {
482
482
  return full.startsWith(name);
483
483
  }
484
- function suppliesFirstOperandSlot(slot, tok) {
484
+ export function suppliesFirstOperandSlot(slot, tok) {
485
485
  if (tok.startsWith("--")) {
486
486
  const long = longOptionNameOf(tok);
487
487
  return long !== undefined && slot.suppliedByLongNames.some((full) => isLongOptionAbbrevOf(long, full));
@@ -716,7 +716,7 @@ const VERB_OPTION_SLOTS = {
716
716
  ],
717
717
  },
718
718
  };
719
- function readVerbOptionSlot(name, tok) {
719
+ export function readVerbOptionSlot(name, tok) {
720
720
  const row = VERB_OPTION_SLOTS[name];
721
721
  if (row === undefined)
722
722
  return undefined;
@@ -777,9 +777,12 @@ function readVerbOptionSlot(name, tok) {
777
777
  }
778
778
  return { form: "no-value" };
779
779
  }
780
- function verbOptionsArePositionIndependent(name) {
780
+ export function verbOptionsArePositionIndependent(name) {
781
781
  return VERB_OPTION_SLOTS[name]?.optionsArePositionIndependent === true;
782
782
  }
783
+ export function verbFirstOperandSlot(name) {
784
+ return VERB_OPTION_SLOTS[name]?.firstOperand;
785
+ }
783
786
  function localFileUrlPath(value) {
784
787
  const m = /^file:(?:\/\/[^/?#]*)?(\/[^?#]*)/i.exec(value.trim());
785
788
  if (m === null)
@@ -804,7 +807,7 @@ function formSpecFileNames(value) {
804
807
  after(part);
805
808
  return [...new Set(out)];
806
809
  }
807
- function slotValueCandidates(value, kind) {
810
+ export function slotValueCandidates(value, kind) {
808
811
  if (!valueNamesAFile(kind))
809
812
  return [];
810
813
  if (kind === "url") {
@@ -877,7 +880,7 @@ const GREP_RECURSIVE_READ_FORM = {
877
880
  enumOptions: [{ shortLetter: "d", longName: "directories", recursiveValue: "recurse" }],
878
881
  dashIsStdin: true,
879
882
  };
880
- const RECURSIVE_READ_FORMS = {
883
+ export const RECURSIVE_READ_FORMS = {
881
884
  grep: GREP_RECURSIVE_READ_FORM,
882
885
  egrep: GREP_RECURSIVE_READ_FORM,
883
886
  fgrep: GREP_RECURSIVE_READ_FORM,
@@ -914,12 +917,29 @@ function utf8Length(text) {
914
917
  }
915
918
  return bytes;
916
919
  }
920
+ function sedReadingKind(reading) {
921
+ return typeof reading === "string" ? reading : "names";
922
+ }
917
923
  function readSedScript(script) {
918
924
  if (script.split("\n").some((line) => utf8Length(line) >= SED_SCRIPT_LINE_LIMIT))
919
925
  return "unseen";
920
926
  let i = 0;
921
927
  let depth = 0;
922
928
  const len = script.length;
929
+ const reads = [];
930
+ const writes = [];
931
+ const commands = [];
932
+ let executesPatternSpace = false;
933
+ const anyName = () => reads.length + writes.length + commands.length > 0 || executesPatternSpace;
934
+ const named = () => ({ names: { reads, writes, commands, executesPatternSpace } });
935
+ const failed = () => (anyName() ? named() : "unparsed");
936
+ const restOfLine = () => {
937
+ skipBlank();
938
+ const from = i;
939
+ while (i < len && script[i] !== "\n")
940
+ i++;
941
+ return script.slice(from, i);
942
+ };
923
943
  const isBlank = (ch) => ch === " " || ch === "\t" || ch === "\v" || ch === "\f";
924
944
  const skipBlank = () => {
925
945
  while (i < len && isBlank(script[i]))
@@ -1011,14 +1031,14 @@ function readSedScript(script) {
1011
1031
  }
1012
1032
  if (ch === "}") {
1013
1033
  if (depth === 0)
1014
- return "unparsed";
1034
+ return failed();
1015
1035
  depth--;
1016
1036
  i++;
1017
1037
  continue;
1018
1038
  }
1019
1039
  const first = readAddress();
1020
1040
  if (first === false)
1021
- return "unparsed";
1041
+ return failed();
1022
1042
  if (first === true) {
1023
1043
  skipBlank();
1024
1044
  if (script[i] === ",") {
@@ -1027,11 +1047,11 @@ function readSedScript(script) {
1027
1047
  if (script[i] === "+" || script[i] === "~") {
1028
1048
  i++;
1029
1049
  if (!(script[i] >= "0" && script[i] <= "9"))
1030
- return "unparsed";
1050
+ return failed();
1031
1051
  digits();
1032
1052
  }
1033
1053
  else if (readAddress() !== true)
1034
- return "unparsed";
1054
+ return failed();
1035
1055
  }
1036
1056
  skipBlank();
1037
1057
  }
@@ -1041,10 +1061,22 @@ function readSedScript(script) {
1041
1061
  }
1042
1062
  const cmd = script[i];
1043
1063
  if (cmd === undefined)
1044
- return "unparsed";
1064
+ return failed();
1045
1065
  i++;
1046
- if (SED_FILE_OR_EXEC_COMMANDS.has(cmd))
1047
- return "names";
1066
+ if (SED_FILE_OR_EXEC_COMMANDS.has(cmd)) {
1067
+ const arg = restOfLine();
1068
+ if (cmd === "e") {
1069
+ if (arg === "")
1070
+ executesPatternSpace = true;
1071
+ else
1072
+ commands.push(arg);
1073
+ }
1074
+ else if (cmd === "r" || cmd === "R")
1075
+ reads.push(arg);
1076
+ else
1077
+ writes.push(arg);
1078
+ continue;
1079
+ }
1048
1080
  if (cmd === "{") {
1049
1081
  depth++;
1050
1082
  continue;
@@ -1052,25 +1084,33 @@ function readSedScript(script) {
1052
1084
  if (cmd === "s" || cmd === "y") {
1053
1085
  const delim = script[i];
1054
1086
  if (delim === undefined || delim === "\\" || delim === "\n")
1055
- return "unparsed";
1087
+ return failed();
1056
1088
  i++;
1057
1089
  if (!consumeDelimited(delim, cmd === "s"))
1058
- return "unparsed";
1090
+ return failed();
1059
1091
  if (!consumeDelimited(delim, false))
1060
- return "unparsed";
1092
+ return failed();
1061
1093
  if (cmd === "y")
1062
1094
  continue;
1063
1095
  while (i < len) {
1064
1096
  const flag = script[i];
1065
- if (flag === "w" || flag === "W" || flag === "e")
1066
- return "names";
1097
+ if (flag === "w" || flag === "W") {
1098
+ i++;
1099
+ writes.push(restOfLine());
1100
+ break;
1101
+ }
1102
+ if (flag === "e") {
1103
+ executesPatternSpace = true;
1104
+ i++;
1105
+ continue;
1106
+ }
1067
1107
  if (SED_SUBSTITUTE_FLAGS.test(flag) || isBlank(flag)) {
1068
1108
  i++;
1069
1109
  continue;
1070
1110
  }
1071
1111
  if (flag === ";" || flag === "\n" || flag === "\r" || flag === "}" || flag === "#")
1072
1112
  break;
1073
- return "unparsed";
1113
+ return failed();
1074
1114
  }
1075
1115
  continue;
1076
1116
  }
@@ -1091,9 +1131,11 @@ function readSedScript(script) {
1091
1131
  continue;
1092
1132
  }
1093
1133
  if (!SED_PLAIN_COMMANDS.has(cmd))
1094
- return "unparsed";
1134
+ return failed();
1095
1135
  }
1096
- return depth === 0 ? "clean" : "unparsed";
1136
+ if (depth !== 0)
1137
+ return failed();
1138
+ return anyName() ? named() : "clean";
1097
1139
  }
1098
1140
  const SED_OPTION_DIALECTS = [
1099
1141
  { separated: "efl", suffix: "iI", stopsAtFirstOperand: false },
@@ -1108,6 +1150,9 @@ const SED_LONG_PLAIN_OPTIONS = [
1108
1150
  "sandbox", "separate", "silent", "unbuffered", "version", "zero-terminated",
1109
1151
  ];
1110
1152
  function readSedInvocation(args, dialect) {
1153
+ return sedReadingKind(readSedInvocationDetailed(args, dialect));
1154
+ }
1155
+ function readSedInvocationDetailed(args, dialect) {
1111
1156
  const scripts = [];
1112
1157
  let scriptSupplied = false;
1113
1158
  let endOfOptions = false;
@@ -1181,6 +1226,25 @@ function readSedInvocation(args, dialect) {
1181
1226
  return readSedScript(script);
1182
1227
  }
1183
1228
  const SED_SHORT_PLAIN_LETTERS = "nsuzEraHl";
1229
+ export function sedScriptSlotFiles(args) {
1230
+ const reads = new Set();
1231
+ const writes = new Set();
1232
+ const commands = new Set();
1233
+ let executesPatternSpace = false;
1234
+ for (const dialect of SED_OPTION_DIALECTS) {
1235
+ const reading = readSedInvocationDetailed(args, dialect);
1236
+ if (typeof reading === "string")
1237
+ continue;
1238
+ for (const f of reading.names.reads)
1239
+ reads.add(f);
1240
+ for (const f of reading.names.writes)
1241
+ writes.add(f);
1242
+ for (const c of reading.names.commands)
1243
+ commands.add(c);
1244
+ executesPatternSpace ||= reading.names.executesPatternSpace;
1245
+ }
1246
+ return { reads: [...reads], writes: [...writes], commands: [...commands], executesPatternSpace };
1247
+ }
1184
1248
  const SCRIPT_SLOT_VERBS = new Map([
1185
1249
  ["sed", (args) => {
1186
1250
  const readings = SED_OPTION_DIALECTS.map((dialect) => readSedInvocation(args, dialect));
@@ -1286,7 +1350,7 @@ const BUN_INLINE = {
1286
1350
  ...BLANK_INLINE, longText: new Set(["--eval", "--print"]), textLetters: "ep",
1287
1351
  separatedValueLetters: "r", bannerLetters: "vh", stdinWhenUnnamed: false,
1288
1352
  };
1289
- const INTERPRETER_INLINE_PROGRAMS = new Map([
1353
+ export const INTERPRETER_INLINE_PROGRAMS = new Map([
1290
1354
  ["perl", PERL_INLINE],
1291
1355
  ["ruby", RUBY_INLINE],
1292
1356
  ["python", PYTHON_INLINE],
@@ -1314,10 +1378,10 @@ const INTERPRETER_INLINE_PROGRAMS = new Map([
1314
1378
  ["groovy", GROOVY_INLINE],
1315
1379
  ["bun", BUN_INLINE],
1316
1380
  ]);
1317
- function interpreterKey(name) {
1381
+ export function interpreterKey(name) {
1318
1382
  return name.replace(/\d+(?:\.\d+)*$/, "");
1319
1383
  }
1320
- function inlineProgramClause(model, args) {
1384
+ export function inlineProgramClause(model, args) {
1321
1385
  let endOfOptions = false;
1322
1386
  let banner = false;
1323
1387
  let interactive = false;
@@ -1527,7 +1591,7 @@ function guardedVerbFormReason(name, toks, raw) {
1527
1591
  }
1528
1592
  return undefined;
1529
1593
  }
1530
- function segmentSelectsRecursiveRead(name, args) {
1594
+ export function segmentSelectsRecursiveRead(name, args) {
1531
1595
  const model = RECURSIVE_READ_FORMS[name];
1532
1596
  if (model === undefined)
1533
1597
  return false;
@@ -1604,7 +1668,7 @@ function isGrepFileStdinLongOption(tok) {
1604
1668
  }
1605
1669
  const CLUSTER_OPTION_CHAR = /[A-Za-z0-9]/;
1606
1670
  const ASSIGNMENT_OR_APPEND_WORD = /^[A-Za-z_][A-Za-z0-9_]*\+?=/;
1607
- function assignmentWordPayload(word) {
1671
+ export function assignmentWordPayload(word) {
1608
1672
  if (!ASSIGNMENT_OR_APPEND_WORD.test(word))
1609
1673
  return undefined;
1610
1674
  const eq = word.indexOf("=");
@@ -1615,8 +1679,8 @@ function assignmentWordPayload(word) {
1615
1679
  return { kind: "unresolved", reason: `"${word}" carries a tilde whose expansion the text does not settle` };
1616
1680
  return value === "" ? undefined : { kind: "value", value };
1617
1681
  }
1618
- const ASSIGNMENT_VALUE_NOT_PATHS = new Set(["make", "gmake", "bmake", "cmake", "ninja", "meson", "docker", "podman"]);
1619
- function argvProgramOf(tokens) {
1682
+ export const ASSIGNMENT_VALUE_NOT_PATHS = new Set(["make", "gmake", "bmake", "cmake", "ninja", "meson", "docker", "podman"]);
1683
+ export function argvProgramOf(tokens) {
1620
1684
  let lead = 0;
1621
1685
  while (lead < tokens.folded.length && ASSIGNMENT_OR_APPEND_WORD.test(tokens.folded[lead]))
1622
1686
  lead++;
@@ -1626,7 +1690,7 @@ function argvProgramOf(tokens) {
1626
1690
  const at = lead + pos.program.index;
1627
1691
  return ASSIGNMENT_OR_APPEND_WORD.test(tokens.folded[at] ?? "") ? undefined : { program: pos.program.basename, from: at };
1628
1692
  }
1629
- function attachedOptionPayloads(tok) {
1693
+ export function attachedOptionPayloads(tok) {
1630
1694
  const literal = literalTildeSpelling;
1631
1695
  const out = [];
1632
1696
  if (tok.startsWith("--")) {
@@ -2309,7 +2373,7 @@ export function classifySimpleCommandReadBoundary(command, boundary) {
2309
2373
  }
2310
2374
  const NOT_GATED = { gated: false, paths: [], checked: [], undecided: [] };
2311
2375
  const UNREADABLE_WHITELIST_WORD = /[$`()*?[\]{}]/;
2312
- const UNMODELLED_BASE_MOVERS = new Set(["pushd", "popd"]);
2376
+ export const UNMODELLED_BASE_MOVERS = new Set(["pushd", "popd"]);
2313
2377
  export const COMMAND_LAUNCHERS_FOR_TEST = COMMAND_LAUNCHERS;
2314
2378
  const LAUNCHER_SCAN_HEAD = "cat";
2315
2379
  export function classifyOutOfRootReadGate(command, allow, boundary) {
@@ -10,7 +10,7 @@ import { delimitUntrusted } from "../../core/untrusted-text.js";
10
10
  import { sliceHeadSafe, sliceTailSafe } from "../../core/surrogate-safe-slice.js";
11
11
  import { MCP_IMAGE_MAX_BASE64 } from "../../core/mcp.js";
12
12
  import { imageMagicMatches, normalizeAbsPathLexically, violationDetails, withinAnyRoot } from "./safety.js";
13
- import { boundaryIsTheSandbox, canonicalPathIsAuthoritative, hasDestroy, isIsolated } from "../../core/remote-env.js";
13
+ import { boundaryIsTheSandbox, hasDestroy, isIsolated } from "../../core/remote-env.js";
14
14
  import { ghRateLimitHint } from "./gh-rate-limit.js";
15
15
  import { resolveBashTimeoutCaps, bashTimeoutCapsSec, bashTimeoutArgRefusal, bashTimeoutParamDescription, envErrorDetail, bashMaxOutputChars, clipShellOutput, writeShellOverflowFile, createShellOverflowSpoolFence, shellRecoveryHint, CWD_SENTINEL, stripCwdSentinel, BASH_READONLY_CONFINEMENT_NOTE, } from "./fs-shared.js";
16
16
  import { PROBE_CAUSE_PATH_MAX, PROBE_REASON_MAX, inlineUntrusted } from "../../core/untrusted-text.js";
@@ -1037,330 +1037,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
1037
1037
  },
1038
1038
  });
1039
1039
  }
1040
- const GLOB_METACHAR_RE = /[*?[]/;
1041
- const MAX_GLOB_DIRS = 256;
1042
- const MAX_GLOB_MATCHES = 8192;
1043
- function compileGlobSegment(pattern) {
1044
- if (pattern.includes("\\"))
1045
- return undefined;
1046
- let src = "";
1047
- for (let i = 0; i < pattern.length; i++) {
1048
- const ch = pattern[i];
1049
- if (ch === "*") {
1050
- src += "[^/]*";
1051
- }
1052
- else if (ch === "?") {
1053
- src += "[^/]";
1054
- }
1055
- else if (ch === "[") {
1056
- const cls = parseBracketExpression(pattern, i);
1057
- if (cls === undefined)
1058
- src += "\\[";
1059
- else {
1060
- src += cls.source;
1061
- i = cls.end;
1062
- }
1063
- }
1064
- else {
1065
- src += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1066
- }
1067
- }
1068
- try {
1069
- return new RegExp(`^${src}$`, "s");
1070
- }
1071
- catch {
1072
- return undefined;
1073
- }
1074
- }
1075
- function parseBracketExpression(pattern, start) {
1076
- let i = start + 1;
1077
- let negate = false;
1078
- if (pattern[i] === "!" || pattern[i] === "^") {
1079
- negate = true;
1080
- i++;
1081
- }
1082
- const contentStart = i;
1083
- let body = "";
1084
- let anyChar = false;
1085
- for (; i < pattern.length; i++) {
1086
- const ch = pattern[i];
1087
- if (ch === "]" && i > contentStart) {
1088
- if (body.length === 0 && !anyChar)
1089
- return undefined;
1090
- return { source: anyChar ? "[^/]" : `[${negate ? "^" : ""}${body}]`, end: i };
1091
- }
1092
- if (ch === "[" && (pattern[i + 1] === ":" || pattern[i + 1] === "." || pattern[i + 1] === "=")) {
1093
- const close = pattern.indexOf(`${pattern[i + 1]}]`, i + 2);
1094
- if (close < 0)
1095
- return undefined;
1096
- anyChar = true;
1097
- i = close + 1;
1098
- continue;
1099
- }
1100
- body += ch === "-" ? "-" : ch.replace(/[\]\\^]/g, "\\$&");
1101
- }
1102
- return undefined;
1103
- }
1104
- function globSegmentMatches(re, pattern, name) {
1105
- if (name.startsWith(".") && !pattern.startsWith("."))
1106
- return false;
1107
- return re.test(name);
1108
- }
1109
- function joinCandidate(dir, name) {
1110
- return `${dir.replace(/[\\/]+$/, "")}/${name}`;
1111
- }
1112
- function pathSegmentsFrom(p, from) {
1113
- const segs = [];
1114
- let scan = from;
1115
- while (scan < p.length) {
1116
- while (scan < p.length && (p[scan] === "/" || p[scan] === "\\"))
1117
- scan++;
1118
- const s = scan;
1119
- while (scan < p.length && p[scan] !== "/" && p[scan] !== "\\")
1120
- scan++;
1121
- if (scan > s)
1122
- segs.push({ text: p.slice(s, scan), start: s });
1123
- }
1124
- return segs;
1125
- }
1126
- function createCanonicalReadBoundary(env, readRoots) {
1127
- const lexicalOnly = boundaryIsTheSandbox(env);
1128
- const resolverIsAuthoritative = canonicalPathIsAuthoritative(env);
1129
- let rootsOnce;
1130
- const canonicalRoots = async (signal) => {
1131
- rootsOnce ??= (async () => {
1132
- const out = [];
1133
- for (const r of readRoots) {
1134
- const c = await env.canonicalPath(r, signal);
1135
- out.push(c.ok ? c.value : r);
1136
- }
1137
- return out;
1138
- })();
1139
- return await rootsOnce;
1140
- };
1141
- const resolveWithoutParentSteps = async (p, signal) => {
1142
- const parts = p.split(/[\\/]/);
1143
- const tail = [];
1144
- let allAbsent = true;
1145
- while (parts.length > 0) {
1146
- const canon = await env.canonicalPath(parts.join("/") || "/", signal);
1147
- if (canon.ok) {
1148
- if (tail.length === 0)
1149
- return { value: canon.value, exact: true };
1150
- return { value: [canon.value.replace(/[\\/]+$/, ""), ...[...tail].reverse()].join("/"), exact: false, missing: allAbsent };
1151
- }
1152
- if (canon.error.code !== "not_found" && canon.error.code !== "not_directory")
1153
- allAbsent = false;
1154
- tail.push(parts.pop());
1155
- }
1156
- return undefined;
1157
- };
1158
- const parentOf = (canonical) => normalizeAbsPathLexically(`${canonical}/..`);
1159
- const resolve = async (p, signal) => {
1160
- const parts = p.split(/[\\/]/);
1161
- const first = parts.findIndex((seg) => seg === "..");
1162
- if (first < 0)
1163
- return await resolveWithoutParentSteps(p, signal);
1164
- const headEnd = parts.slice(0, first).reduce((n, seg) => n + seg.length + 1, 0);
1165
- let head = await resolveWithoutParentSteps(p.slice(0, headEnd) || "/", signal);
1166
- if (head === undefined)
1167
- return undefined;
1168
- let pending = [];
1169
- const flush = async () => {
1170
- if (pending.length === 0)
1171
- return true;
1172
- const next = await resolveWithoutParentSteps([head.value.replace(/[\\/]+$/, ""), ...pending].join("/"), signal);
1173
- pending = [];
1174
- if (next === undefined)
1175
- return false;
1176
- head = next;
1177
- return true;
1178
- };
1179
- for (let i = first; i < parts.length; i++) {
1180
- const seg = parts[i];
1181
- if (seg === "" || seg === ".")
1182
- continue;
1183
- if (seg !== "..") {
1184
- pending.push(seg);
1185
- continue;
1186
- }
1187
- if (!(await flush()))
1188
- return undefined;
1189
- if (!head.exact)
1190
- return head;
1191
- head = { value: parentOf(head.value), exact: true };
1192
- }
1193
- return (await flush()) ? head : undefined;
1194
- };
1195
- const literalRootHead = (p, roots) => {
1196
- let best;
1197
- for (const r of [...readRoots, ...roots]) {
1198
- if (!r)
1199
- continue;
1200
- const withSep = r.endsWith("/") || r.endsWith("\\") ? r : `${r}/`;
1201
- if (p !== r && !p.startsWith(withSep))
1202
- continue;
1203
- if (best === undefined || r.length > best.length)
1204
- best = r;
1205
- }
1206
- return best;
1207
- };
1208
- const expandGlobCandidate = async (p, roots, signal) => {
1209
- const head = literalRootHead(p, roots);
1210
- let segs = pathSegmentsFrom(p, head?.length ?? 0);
1211
- let first = segs.findIndex((s) => GLOB_METACHAR_RE.test(s.text));
1212
- if (first < 0 && head !== undefined && GLOB_METACHAR_RE.test(head)) {
1213
- segs = pathSegmentsFrom(p, 0);
1214
- first = segs.findIndex((s) => GLOB_METACHAR_RE.test(s.text));
1215
- }
1216
- if (first < 0)
1217
- return undefined;
1218
- let frontier = [p.slice(0, segs[first].start).replace(/[\\/]+$/, "") || "/"];
1219
- let dirsUsed = 0;
1220
- let matches = 0;
1221
- const canonicalOfMatch = new Map();
1222
- for (let j = first; j < segs.length; j++) {
1223
- const seg = segs[j].text;
1224
- if (!GLOB_METACHAR_RE.test(seg)) {
1225
- frontier = frontier.map((d) => joinCandidate(d, seg));
1226
- continue;
1227
- }
1228
- const re = compileGlobSegment(seg);
1229
- if (re === undefined) {
1230
- return { unverifiable: `the pattern "${seg}" cannot be expanded to verify what it would read` };
1231
- }
1232
- const isLast = j === segs.length - 1;
1233
- const next = [];
1234
- for (const dir of frontier) {
1235
- if (++dirsUsed > MAX_GLOB_DIRS) {
1236
- return { unverifiable: `"${p}" expands over more than ${MAX_GLOB_DIRS} directories, too many to enumerate to verify` };
1237
- }
1238
- const canonDir = await resolve(dir, signal);
1239
- if (canonDir !== undefined && canonDir.missing === true)
1240
- continue;
1241
- if (canonDir !== undefined && !canonDir.exact) {
1242
- return { unverifiable: `"${dir}" cannot be resolved to verify what "${seg}" would expand to` };
1243
- }
1244
- const listed = await env.listDir(canonDir?.value ?? dir, signal);
1245
- if (!listed.ok) {
1246
- if (listed.error.code === "not_found" || listed.error.code === "not_directory")
1247
- continue;
1248
- return { unverifiable: `"${dir}" cannot be enumerated to verify what "${seg}" would expand to (${listed.error.code})` };
1249
- }
1250
- if (seg.startsWith(".")) {
1251
- for (const dot of [".", ".."])
1252
- if (re.test(dot))
1253
- next.push(joinCandidate(dir, dot));
1254
- }
1255
- const canonDirHere = isLast ? canonDir?.value : undefined;
1256
- for (const entry of listed.value) {
1257
- if (!globSegmentMatches(re, seg, entry.name))
1258
- continue;
1259
- if (++matches > MAX_GLOB_MATCHES) {
1260
- return { unverifiable: `"${p}" expands to more than ${MAX_GLOB_MATCHES} names, too many to enumerate to verify` };
1261
- }
1262
- const candidate = joinCandidate(dir, entry.name);
1263
- if (canonDirHere !== undefined && entry.kind !== "symlink")
1264
- canonicalOfMatch.set(candidate, joinCandidate(canonDirHere, entry.name));
1265
- next.push(candidate);
1266
- }
1267
- }
1268
- frontier = next;
1269
- if (frontier.length === 0)
1270
- return { paths: [], canonical: canonicalOfMatch };
1271
- }
1272
- return { paths: frontier, canonical: canonicalOfMatch };
1273
- };
1274
- return {
1275
- escapesAfterResolution: async (paths, ask, signal) => {
1276
- const deny = ask.deny;
1277
- if (lexicalOnly || (paths.literal.length === 0 && paths.patterns.length === 0))
1278
- return { escaping: [] };
1279
- const roots = await canonicalRoots(signal);
1280
- const escaping = [];
1281
- let unresolvable;
1282
- let denied;
1283
- const denyHit = (p) => {
1284
- if (deny === undefined || denied !== undefined)
1285
- return false;
1286
- const folded = normalizeAbsPathLexically(p);
1287
- if (ask.denyAnswered.has(folded))
1288
- return false;
1289
- const pattern = deny(folded);
1290
- if (pattern === null)
1291
- return false;
1292
- denied = { path: folded, pattern };
1293
- return true;
1294
- };
1295
- const judge = async (p, canonicalKnown, origin) => {
1296
- if (denyHit(p))
1297
- return;
1298
- const real = canonicalKnown !== undefined ? { value: canonicalKnown, exact: true } : await resolve(p, signal);
1299
- if (real === undefined) {
1300
- if (resolverIsAuthoritative)
1301
- unresolvable ??= `"${p}" cannot be resolved to verify where it reads`;
1302
- return;
1303
- }
1304
- if (!real.exact && real.missing !== true) {
1305
- unresolvable ??= `"${p}" cannot be resolved to verify where it reads`;
1306
- return;
1307
- }
1308
- if (denyHit(real.value))
1309
- return;
1310
- if (ask.containmentAnswered.has(normalizeAbsPathLexically(origin ?? p)))
1311
- return;
1312
- if (ask.containment && !withinAnyRoot(roots, real.value) && !escaping.includes(real.value))
1313
- escaping.push(real.value);
1314
- };
1315
- for (const p of paths.literal) {
1316
- if (denied !== undefined)
1317
- break;
1318
- await judge(p);
1319
- }
1320
- for (const p of paths.patterns) {
1321
- if (denied !== undefined)
1322
- break;
1323
- const expanded = await expandGlobCandidate(p, roots, signal);
1324
- if (expanded !== undefined && "unverifiable" in expanded) {
1325
- unresolvable ??= expanded.unverifiable;
1326
- continue;
1327
- }
1328
- if (expanded !== undefined && expanded.paths.length > 0) {
1329
- for (const t of expanded.paths) {
1330
- if (denied !== undefined)
1331
- break;
1332
- await judge(t, expanded.canonical.get(t), p);
1333
- }
1334
- }
1335
- else
1336
- await judge(p);
1337
- }
1338
- if (denied !== undefined)
1339
- return { escaping, denied };
1340
- return unresolvable === undefined ? { escaping } : { escaping, unverifiable: unresolvable };
1341
- },
1342
- canonicalOf: async (path, signal) => {
1343
- if (lexicalOnly)
1344
- return undefined;
1345
- const real = await resolve(path, signal);
1346
- return real === undefined || (!real.exact && real.missing !== true) ? undefined : real.value;
1347
- },
1348
- allResolveInside: async (paths, signal) => {
1349
- if (lexicalOnly || paths.length === 0)
1350
- return false;
1351
- const roots = await canonicalRoots(signal);
1352
- for (const p of paths) {
1353
- const real = await resolve(p, signal);
1354
- if (real === undefined || (!real.exact && real.missing !== true))
1355
- return false;
1356
- if (!withinAnyRoot(roots, real.value))
1357
- return false;
1358
- }
1359
- return true;
1360
- },
1361
- };
1362
- }
1363
- const NOTHING_ANSWERED = new Set();
1040
+ import { createCanonicalReadBoundary, NOTHING_ANSWERED } from "./shell-read-establish.js";
1364
1041
  export function createShellCanonicalRecheck(env, readBoundary, face) {
1365
1042
  const recheckReaders = new Set(bashClassifyDefaultAllow());
1366
1043
  let canonicalForRoots;