@node9/proxy 2.54.0 → 2.54.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/cli-main.js CHANGED
@@ -976,12 +976,46 @@ function wordValues(word, state) {
976
976
  if (!dynamic) return out.map(asWord);
977
977
  return out.filter((piece) => piece.value !== "").map((piece) => ({ value: piece.value, pattern: piece.pattern + "*", dynamic: true }));
978
978
  }
979
+ function sedSubstitutions(script) {
980
+ const out = [];
981
+ for (const raw of script.split(/\r?\n|;/)) {
982
+ const cmd = raw.trim();
983
+ if (cmd === "") continue;
984
+ const m = /^(?:(?:\d+|\$|\/(?:\\.|[^\\/])*\/)(?:,(?:\d+|\$|\/(?:\\.|[^\\/])*\/))?)?s(.)/.exec(
985
+ cmd
986
+ );
987
+ if (!m) return null;
988
+ const delimiter = m[1];
989
+ if (delimiter === "\\") return null;
990
+ const parts = [];
991
+ let cur = "";
992
+ let i = m[0].length;
993
+ for (; i < cmd.length && parts.length < 2; i++) {
994
+ const ch = cmd[i];
995
+ if (ch === "\\" && i + 1 < cmd.length) {
996
+ cur += ch + cmd[++i];
997
+ continue;
998
+ }
999
+ if (ch === delimiter) {
1000
+ parts.push(cur);
1001
+ cur = "";
1002
+ continue;
1003
+ }
1004
+ cur += ch;
1005
+ }
1006
+ if (parts.length < 2) return null;
1007
+ if (!/^[gpiImM0-9]*$/.test(cmd.slice(i))) return null;
1008
+ out.push(parts[1].replace(/\\(.)/g, "$1"));
1009
+ }
1010
+ return out.length > 0 ? out : null;
1011
+ }
979
1012
  function collectShellFileAccesses(command, context = {}) {
980
1013
  const out = [];
981
1014
  let payloads = 256;
982
1015
  const functions = /* @__PURE__ */ new Map();
983
1016
  const seenCalls = /* @__PURE__ */ new Set();
984
1017
  const pipedStdin = /* @__PURE__ */ new WeakMap();
1018
+ let currentStdin = null;
985
1019
  let callWalks2 = MAX_CALL_WALKS;
986
1020
  const initial = {
987
1021
  cwd: [context.cwd ?? ""],
@@ -1076,8 +1110,9 @@ function collectShellFileAccesses(command, context = {}) {
1076
1110
  }
1077
1111
  const locate = (p) => tokens.find((w) => w?.value === p) ?? { value: p };
1078
1112
  if (name === "sed") {
1079
- let inplace = false, hasScript = false, ended = false;
1113
+ let inplace = false, hasScript = false, ended = false, scriptUnknown = false;
1080
1114
  const files = [];
1115
+ const scriptParts = [];
1081
1116
  for (let i = 0; i < args.length; i++) {
1082
1117
  const token = args[i];
1083
1118
  if (!token) {
@@ -1095,7 +1130,11 @@ function collectShellFileAccesses(command, context = {}) {
1095
1130
  hasScript = true;
1096
1131
  const eq = value.indexOf("=");
1097
1132
  const script = eq < 0 ? args[++i] : { value: value.slice(eq + 1) };
1098
- if (value.startsWith("--file") && script) add("read", script, state);
1133
+ if (value.startsWith("--file")) {
1134
+ scriptUnknown = true;
1135
+ if (script) add("read", script, state);
1136
+ } else if (script?.value) scriptParts.push(script.value);
1137
+ else scriptUnknown = true;
1099
1138
  }
1100
1139
  continue;
1101
1140
  }
@@ -1110,7 +1149,11 @@ function collectShellFileAccesses(command, context = {}) {
1110
1149
  if (flag === "e" || flag === "f") {
1111
1150
  hasScript = true;
1112
1151
  const script = value.slice(j + 1) ? { value: value.slice(j + 1) } : args[++i];
1113
- if (flag === "f" && script) add("read", script, state);
1152
+ if (flag === "f") {
1153
+ scriptUnknown = true;
1154
+ if (script) add("read", script, state);
1155
+ } else if (script?.value) scriptParts.push(script.value);
1156
+ else scriptUnknown = true;
1114
1157
  break;
1115
1158
  }
1116
1159
  if (flag === "l") {
@@ -1120,10 +1163,14 @@ function collectShellFileAccesses(command, context = {}) {
1120
1163
  }
1121
1164
  continue;
1122
1165
  }
1123
- if (!hasScript) hasScript = true;
1124
- else files.push(token);
1166
+ if (!hasScript) {
1167
+ hasScript = true;
1168
+ scriptParts.push(value);
1169
+ } else files.push(token);
1125
1170
  }
1126
- for (const file2 of files) add(inplace ? "write" : "read", file2, state);
1171
+ const edits = inplace && !scriptUnknown ? sedSubstitutions(scriptParts.join("\n")) : null;
1172
+ for (const file2 of files)
1173
+ add(inplace ? "write" : "read", file2, state, edits ? { edits } : {});
1127
1174
  return;
1128
1175
  }
1129
1176
  if (FS_READ_TOOLS.has(name) && name !== "dd") {
@@ -1250,9 +1297,11 @@ function collectShellFileAccesses(command, context = {}) {
1250
1297
  continue;
1251
1298
  const remove = name === "rm" || name === "unlink";
1252
1299
  const append = name === "tee" && strings.some((s) => s === "--append" || /^-[^-]*a/.test(s));
1300
+ const teed = name === "tee" && !append ? currentStdin : null;
1253
1301
  add(remove ? "remove" : name === "touch" ? "create" : "write", token, state, {
1254
1302
  recursive: remove && strings.some((s) => /^-[^-]*[rR]/.test(s) || s === "--recursive"),
1255
- ...append ? { append: true } : {}
1303
+ ...append ? { append: true } : {},
1304
+ ...teed !== null ? { text: teed } : {}
1256
1305
  });
1257
1306
  }
1258
1307
  return;
@@ -1574,11 +1623,18 @@ function collectShellFileAccesses(command, context = {}) {
1574
1623
  if (stmt.Background) state = clone(state);
1575
1624
  const headWord = stmt.Cmd && stmt.Cmd.kind === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
1576
1625
  const sends = SENDING_STDIN.has(headWord);
1626
+ let produced;
1577
1627
  for (const redir of stmt.Redirs ?? []) {
1578
1628
  substitutions2(redir.Word, state, depth);
1579
1629
  for (const word of wordValues(redir.Word, state)) {
1580
- if (ops().WRITES.has(redir.Op))
1581
- add("write", word, state, ops().APPENDS.has(redir.Op) ? { append: true } : {});
1630
+ if (ops().WRITES.has(redir.Op)) {
1631
+ if (ops().APPENDS.has(redir.Op)) add("write", word, state, { append: true });
1632
+ else {
1633
+ const stdout = redir.N == null || redir.N.Value === "1";
1634
+ if (stdout && produced === void 0) produced = producedText(stmt);
1635
+ add("write", word, state, stdout && produced ? { text: produced } : {});
1636
+ }
1637
+ }
1582
1638
  if (ops().READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
1583
1639
  if (redir.Op === ops().DUP_OUT && word.value && !/^\d+$/.test(word.value))
1584
1640
  add("write", word, state);
@@ -1590,6 +1646,7 @@ function collectShellFileAccesses(command, context = {}) {
1590
1646
  stdinScript(stmt, state, depth);
1591
1647
  const fedText = stdinText(stmt);
1592
1648
  if (fedText !== null) nestedPayload(xargsPayload(stmt, fedText), clone(state), depth);
1649
+ currentStdin = fedText;
1593
1650
  switch (cmd.kind) {
1594
1651
  case "CallExpr":
1595
1652
  call(cmd, state, depth);
@@ -6717,8 +6774,16 @@ function evaluateGitHooksFileAccess(args, accesses, context, settings, shell) {
6717
6774
  disarms = true;
6718
6775
  how = "removed";
6719
6776
  } else if (texts === null) {
6720
- disarms = true;
6721
- how = "overwritten";
6777
+ if (access.edits) {
6778
+ disarms = access.edits.some(editDisarms);
6779
+ how = "edited to exit";
6780
+ } else if (access.text !== void 0) {
6781
+ disarms = hookTextRunsNothing(access.text);
6782
+ how = "emptied";
6783
+ } else {
6784
+ disarms = true;
6785
+ how = "overwritten";
6786
+ }
6722
6787
  } else if (texts.whole.length > 0) {
6723
6788
  disarms = texts.whole.some(hookTextRunsNothing);
6724
6789
  how = "emptied";
@@ -994,12 +994,46 @@ function wordValues(word, state) {
994
994
  if (!dynamic) return out.map(asWord);
995
995
  return out.filter((piece) => piece.value !== "").map((piece) => ({ value: piece.value, pattern: piece.pattern + "*", dynamic: true }));
996
996
  }
997
+ function sedSubstitutions(script) {
998
+ const out = [];
999
+ for (const raw of script.split(/\r?\n|;/)) {
1000
+ const cmd = raw.trim();
1001
+ if (cmd === "") continue;
1002
+ const m = /^(?:(?:\d+|\$|\/(?:\\.|[^\\/])*\/)(?:,(?:\d+|\$|\/(?:\\.|[^\\/])*\/))?)?s(.)/.exec(
1003
+ cmd
1004
+ );
1005
+ if (!m) return null;
1006
+ const delimiter = m[1];
1007
+ if (delimiter === "\\") return null;
1008
+ const parts = [];
1009
+ let cur = "";
1010
+ let i = m[0].length;
1011
+ for (; i < cmd.length && parts.length < 2; i++) {
1012
+ const ch = cmd[i];
1013
+ if (ch === "\\" && i + 1 < cmd.length) {
1014
+ cur += ch + cmd[++i];
1015
+ continue;
1016
+ }
1017
+ if (ch === delimiter) {
1018
+ parts.push(cur);
1019
+ cur = "";
1020
+ continue;
1021
+ }
1022
+ cur += ch;
1023
+ }
1024
+ if (parts.length < 2) return null;
1025
+ if (!/^[gpiImM0-9]*$/.test(cmd.slice(i))) return null;
1026
+ out.push(parts[1].replace(/\\(.)/g, "$1"));
1027
+ }
1028
+ return out.length > 0 ? out : null;
1029
+ }
997
1030
  function collectShellFileAccesses(command, context = {}) {
998
1031
  const out = [];
999
1032
  let payloads = 256;
1000
1033
  const functions = /* @__PURE__ */ new Map();
1001
1034
  const seenCalls = /* @__PURE__ */ new Set();
1002
1035
  const pipedStdin = /* @__PURE__ */ new WeakMap();
1036
+ let currentStdin = null;
1003
1037
  let callWalks2 = MAX_CALL_WALKS;
1004
1038
  const initial = {
1005
1039
  cwd: [context.cwd ?? ""],
@@ -1094,8 +1128,9 @@ function collectShellFileAccesses(command, context = {}) {
1094
1128
  }
1095
1129
  const locate = (p) => tokens.find((w) => w?.value === p) ?? { value: p };
1096
1130
  if (name === "sed") {
1097
- let inplace = false, hasScript = false, ended = false;
1131
+ let inplace = false, hasScript = false, ended = false, scriptUnknown = false;
1098
1132
  const files = [];
1133
+ const scriptParts = [];
1099
1134
  for (let i = 0; i < args.length; i++) {
1100
1135
  const token = args[i];
1101
1136
  if (!token) {
@@ -1113,7 +1148,11 @@ function collectShellFileAccesses(command, context = {}) {
1113
1148
  hasScript = true;
1114
1149
  const eq = value.indexOf("=");
1115
1150
  const script = eq < 0 ? args[++i] : { value: value.slice(eq + 1) };
1116
- if (value.startsWith("--file") && script) add("read", script, state);
1151
+ if (value.startsWith("--file")) {
1152
+ scriptUnknown = true;
1153
+ if (script) add("read", script, state);
1154
+ } else if (script?.value) scriptParts.push(script.value);
1155
+ else scriptUnknown = true;
1117
1156
  }
1118
1157
  continue;
1119
1158
  }
@@ -1128,7 +1167,11 @@ function collectShellFileAccesses(command, context = {}) {
1128
1167
  if (flag === "e" || flag === "f") {
1129
1168
  hasScript = true;
1130
1169
  const script = value.slice(j + 1) ? { value: value.slice(j + 1) } : args[++i];
1131
- if (flag === "f" && script) add("read", script, state);
1170
+ if (flag === "f") {
1171
+ scriptUnknown = true;
1172
+ if (script) add("read", script, state);
1173
+ } else if (script?.value) scriptParts.push(script.value);
1174
+ else scriptUnknown = true;
1132
1175
  break;
1133
1176
  }
1134
1177
  if (flag === "l") {
@@ -1138,10 +1181,14 @@ function collectShellFileAccesses(command, context = {}) {
1138
1181
  }
1139
1182
  continue;
1140
1183
  }
1141
- if (!hasScript) hasScript = true;
1142
- else files.push(token);
1184
+ if (!hasScript) {
1185
+ hasScript = true;
1186
+ scriptParts.push(value);
1187
+ } else files.push(token);
1143
1188
  }
1144
- for (const file2 of files) add(inplace ? "write" : "read", file2, state);
1189
+ const edits = inplace && !scriptUnknown ? sedSubstitutions(scriptParts.join("\n")) : null;
1190
+ for (const file2 of files)
1191
+ add(inplace ? "write" : "read", file2, state, edits ? { edits } : {});
1145
1192
  return;
1146
1193
  }
1147
1194
  if (FS_READ_TOOLS.has(name) && name !== "dd") {
@@ -1268,9 +1315,11 @@ function collectShellFileAccesses(command, context = {}) {
1268
1315
  continue;
1269
1316
  const remove = name === "rm" || name === "unlink";
1270
1317
  const append = name === "tee" && strings.some((s) => s === "--append" || /^-[^-]*a/.test(s));
1318
+ const teed = name === "tee" && !append ? currentStdin : null;
1271
1319
  add(remove ? "remove" : name === "touch" ? "create" : "write", token, state, {
1272
1320
  recursive: remove && strings.some((s) => /^-[^-]*[rR]/.test(s) || s === "--recursive"),
1273
- ...append ? { append: true } : {}
1321
+ ...append ? { append: true } : {},
1322
+ ...teed !== null ? { text: teed } : {}
1274
1323
  });
1275
1324
  }
1276
1325
  return;
@@ -1592,11 +1641,18 @@ function collectShellFileAccesses(command, context = {}) {
1592
1641
  if (stmt.Background) state = clone(state);
1593
1642
  const headWord = stmt.Cmd && stmt.Cmd.kind === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
1594
1643
  const sends = SENDING_STDIN.has(headWord);
1644
+ let produced;
1595
1645
  for (const redir of stmt.Redirs ?? []) {
1596
1646
  substitutions(redir.Word, state, depth);
1597
1647
  for (const word of wordValues(redir.Word, state)) {
1598
- if (ops().WRITES.has(redir.Op))
1599
- add("write", word, state, ops().APPENDS.has(redir.Op) ? { append: true } : {});
1648
+ if (ops().WRITES.has(redir.Op)) {
1649
+ if (ops().APPENDS.has(redir.Op)) add("write", word, state, { append: true });
1650
+ else {
1651
+ const stdout = redir.N == null || redir.N.Value === "1";
1652
+ if (stdout && produced === void 0) produced = producedText(stmt);
1653
+ add("write", word, state, stdout && produced ? { text: produced } : {});
1654
+ }
1655
+ }
1600
1656
  if (ops().READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
1601
1657
  if (redir.Op === ops().DUP_OUT && word.value && !/^\d+$/.test(word.value))
1602
1658
  add("write", word, state);
@@ -1608,6 +1664,7 @@ function collectShellFileAccesses(command, context = {}) {
1608
1664
  stdinScript(stmt, state, depth);
1609
1665
  const fedText = stdinText(stmt);
1610
1666
  if (fedText !== null) nestedPayload(xargsPayload(stmt, fedText), clone(state), depth);
1667
+ currentStdin = fedText;
1611
1668
  switch (cmd.kind) {
1612
1669
  case "CallExpr":
1613
1670
  call(cmd, state, depth);
@@ -6641,8 +6698,16 @@ function evaluateGitHooksFileAccess(args, accesses, context, settings, shell) {
6641
6698
  disarms = true;
6642
6699
  how = "removed";
6643
6700
  } else if (texts === null) {
6644
- disarms = true;
6645
- how = "overwritten";
6701
+ if (access.edits) {
6702
+ disarms = access.edits.some(editDisarms);
6703
+ how = "edited to exit";
6704
+ } else if (access.text !== void 0) {
6705
+ disarms = hookTextRunsNothing(access.text);
6706
+ how = "emptied";
6707
+ } else {
6708
+ disarms = true;
6709
+ how = "overwritten";
6710
+ }
6646
6711
  } else if (texts.whole.length > 0) {
6647
6712
  disarms = texts.whole.some(hookTextRunsNothing);
6648
6713
  how = "emptied";
package/dist/hook.js CHANGED
@@ -903,12 +903,46 @@ function wordValues(word, state) {
903
903
  if (!dynamic) return out.map(asWord);
904
904
  return out.filter((piece) => piece.value !== "").map((piece) => ({ value: piece.value, pattern: piece.pattern + "*", dynamic: true }));
905
905
  }
906
+ function sedSubstitutions(script) {
907
+ const out = [];
908
+ for (const raw of script.split(/\r?\n|;/)) {
909
+ const cmd = raw.trim();
910
+ if (cmd === "") continue;
911
+ const m = /^(?:(?:\d+|\$|\/(?:\\.|[^\\/])*\/)(?:,(?:\d+|\$|\/(?:\\.|[^\\/])*\/))?)?s(.)/.exec(
912
+ cmd
913
+ );
914
+ if (!m) return null;
915
+ const delimiter = m[1];
916
+ if (delimiter === "\\") return null;
917
+ const parts = [];
918
+ let cur = "";
919
+ let i = m[0].length;
920
+ for (; i < cmd.length && parts.length < 2; i++) {
921
+ const ch = cmd[i];
922
+ if (ch === "\\" && i + 1 < cmd.length) {
923
+ cur += ch + cmd[++i];
924
+ continue;
925
+ }
926
+ if (ch === delimiter) {
927
+ parts.push(cur);
928
+ cur = "";
929
+ continue;
930
+ }
931
+ cur += ch;
932
+ }
933
+ if (parts.length < 2) return null;
934
+ if (!/^[gpiImM0-9]*$/.test(cmd.slice(i))) return null;
935
+ out.push(parts[1].replace(/\\(.)/g, "$1"));
936
+ }
937
+ return out.length > 0 ? out : null;
938
+ }
906
939
  function collectShellFileAccesses(command, context = {}) {
907
940
  const out = [];
908
941
  let payloads = 256;
909
942
  const functions = /* @__PURE__ */ new Map();
910
943
  const seenCalls = /* @__PURE__ */ new Set();
911
944
  const pipedStdin = /* @__PURE__ */ new WeakMap();
945
+ let currentStdin = null;
912
946
  let callWalks2 = MAX_CALL_WALKS;
913
947
  const initial = {
914
948
  cwd: [context.cwd ?? ""],
@@ -1003,8 +1037,9 @@ function collectShellFileAccesses(command, context = {}) {
1003
1037
  }
1004
1038
  const locate = (p) => tokens.find((w) => w?.value === p) ?? { value: p };
1005
1039
  if (name === "sed") {
1006
- let inplace = false, hasScript = false, ended = false;
1040
+ let inplace = false, hasScript = false, ended = false, scriptUnknown = false;
1007
1041
  const files = [];
1042
+ const scriptParts = [];
1008
1043
  for (let i = 0; i < args.length; i++) {
1009
1044
  const token = args[i];
1010
1045
  if (!token) {
@@ -1022,7 +1057,11 @@ function collectShellFileAccesses(command, context = {}) {
1022
1057
  hasScript = true;
1023
1058
  const eq = value.indexOf("=");
1024
1059
  const script = eq < 0 ? args[++i] : { value: value.slice(eq + 1) };
1025
- if (value.startsWith("--file") && script) add("read", script, state);
1060
+ if (value.startsWith("--file")) {
1061
+ scriptUnknown = true;
1062
+ if (script) add("read", script, state);
1063
+ } else if (script?.value) scriptParts.push(script.value);
1064
+ else scriptUnknown = true;
1026
1065
  }
1027
1066
  continue;
1028
1067
  }
@@ -1037,7 +1076,11 @@ function collectShellFileAccesses(command, context = {}) {
1037
1076
  if (flag === "e" || flag === "f") {
1038
1077
  hasScript = true;
1039
1078
  const script = value.slice(j + 1) ? { value: value.slice(j + 1) } : args[++i];
1040
- if (flag === "f" && script) add("read", script, state);
1079
+ if (flag === "f") {
1080
+ scriptUnknown = true;
1081
+ if (script) add("read", script, state);
1082
+ } else if (script?.value) scriptParts.push(script.value);
1083
+ else scriptUnknown = true;
1041
1084
  break;
1042
1085
  }
1043
1086
  if (flag === "l") {
@@ -1047,10 +1090,14 @@ function collectShellFileAccesses(command, context = {}) {
1047
1090
  }
1048
1091
  continue;
1049
1092
  }
1050
- if (!hasScript) hasScript = true;
1051
- else files.push(token);
1093
+ if (!hasScript) {
1094
+ hasScript = true;
1095
+ scriptParts.push(value);
1096
+ } else files.push(token);
1052
1097
  }
1053
- for (const file2 of files) add(inplace ? "write" : "read", file2, state);
1098
+ const edits = inplace && !scriptUnknown ? sedSubstitutions(scriptParts.join("\n")) : null;
1099
+ for (const file2 of files)
1100
+ add(inplace ? "write" : "read", file2, state, edits ? { edits } : {});
1054
1101
  return;
1055
1102
  }
1056
1103
  if (FS_READ_TOOLS.has(name) && name !== "dd") {
@@ -1177,9 +1224,11 @@ function collectShellFileAccesses(command, context = {}) {
1177
1224
  continue;
1178
1225
  const remove = name === "rm" || name === "unlink";
1179
1226
  const append = name === "tee" && strings.some((s) => s === "--append" || /^-[^-]*a/.test(s));
1227
+ const teed = name === "tee" && !append ? currentStdin : null;
1180
1228
  add(remove ? "remove" : name === "touch" ? "create" : "write", token, state, {
1181
1229
  recursive: remove && strings.some((s) => /^-[^-]*[rR]/.test(s) || s === "--recursive"),
1182
- ...append ? { append: true } : {}
1230
+ ...append ? { append: true } : {},
1231
+ ...teed !== null ? { text: teed } : {}
1183
1232
  });
1184
1233
  }
1185
1234
  return;
@@ -1501,11 +1550,18 @@ function collectShellFileAccesses(command, context = {}) {
1501
1550
  if (stmt.Background) state = clone(state);
1502
1551
  const headWord = stmt.Cmd && stmt.Cmd.kind === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
1503
1552
  const sends = SENDING_STDIN.has(headWord);
1553
+ let produced;
1504
1554
  for (const redir of stmt.Redirs ?? []) {
1505
1555
  substitutions(redir.Word, state, depth);
1506
1556
  for (const word of wordValues(redir.Word, state)) {
1507
- if (ops().WRITES.has(redir.Op))
1508
- add("write", word, state, ops().APPENDS.has(redir.Op) ? { append: true } : {});
1557
+ if (ops().WRITES.has(redir.Op)) {
1558
+ if (ops().APPENDS.has(redir.Op)) add("write", word, state, { append: true });
1559
+ else {
1560
+ const stdout = redir.N == null || redir.N.Value === "1";
1561
+ if (stdout && produced === void 0) produced = producedText(stmt);
1562
+ add("write", word, state, stdout && produced ? { text: produced } : {});
1563
+ }
1564
+ }
1509
1565
  if (ops().READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
1510
1566
  if (redir.Op === ops().DUP_OUT && word.value && !/^\d+$/.test(word.value))
1511
1567
  add("write", word, state);
@@ -1517,6 +1573,7 @@ function collectShellFileAccesses(command, context = {}) {
1517
1573
  stdinScript(stmt, state, depth);
1518
1574
  const fedText = stdinText(stmt);
1519
1575
  if (fedText !== null) nestedPayload(xargsPayload(stmt, fedText), clone(state), depth);
1576
+ currentStdin = fedText;
1520
1577
  switch (cmd.kind) {
1521
1578
  case "CallExpr":
1522
1579
  call(cmd, state, depth);
@@ -6644,8 +6701,16 @@ function evaluateGitHooksFileAccess(args, accesses, context, settings, shell) {
6644
6701
  disarms = true;
6645
6702
  how = "removed";
6646
6703
  } else if (texts === null) {
6647
- disarms = true;
6648
- how = "overwritten";
6704
+ if (access.edits) {
6705
+ disarms = access.edits.some(editDisarms);
6706
+ how = "edited to exit";
6707
+ } else if (access.text !== void 0) {
6708
+ disarms = hookTextRunsNothing(access.text);
6709
+ how = "emptied";
6710
+ } else {
6711
+ disarms = true;
6712
+ how = "overwritten";
6713
+ }
6649
6714
  } else if (texts.whole.length > 0) {
6650
6715
  disarms = texts.whole.some(hookTextRunsNothing);
6651
6716
  how = "emptied";
package/dist/index.js CHANGED
@@ -957,12 +957,46 @@ function wordValues(word, state) {
957
957
  if (!dynamic) return out.map(asWord);
958
958
  return out.filter((piece) => piece.value !== "").map((piece) => ({ value: piece.value, pattern: piece.pattern + "*", dynamic: true }));
959
959
  }
960
+ function sedSubstitutions(script) {
961
+ const out = [];
962
+ for (const raw of script.split(/\r?\n|;/)) {
963
+ const cmd = raw.trim();
964
+ if (cmd === "") continue;
965
+ const m = /^(?:(?:\d+|\$|\/(?:\\.|[^\\/])*\/)(?:,(?:\d+|\$|\/(?:\\.|[^\\/])*\/))?)?s(.)/.exec(
966
+ cmd
967
+ );
968
+ if (!m) return null;
969
+ const delimiter = m[1];
970
+ if (delimiter === "\\") return null;
971
+ const parts = [];
972
+ let cur = "";
973
+ let i = m[0].length;
974
+ for (; i < cmd.length && parts.length < 2; i++) {
975
+ const ch = cmd[i];
976
+ if (ch === "\\" && i + 1 < cmd.length) {
977
+ cur += ch + cmd[++i];
978
+ continue;
979
+ }
980
+ if (ch === delimiter) {
981
+ parts.push(cur);
982
+ cur = "";
983
+ continue;
984
+ }
985
+ cur += ch;
986
+ }
987
+ if (parts.length < 2) return null;
988
+ if (!/^[gpiImM0-9]*$/.test(cmd.slice(i))) return null;
989
+ out.push(parts[1].replace(/\\(.)/g, "$1"));
990
+ }
991
+ return out.length > 0 ? out : null;
992
+ }
960
993
  function collectShellFileAccesses(command, context = {}) {
961
994
  const out = [];
962
995
  let payloads = 256;
963
996
  const functions = /* @__PURE__ */ new Map();
964
997
  const seenCalls = /* @__PURE__ */ new Set();
965
998
  const pipedStdin = /* @__PURE__ */ new WeakMap();
999
+ let currentStdin = null;
966
1000
  let callWalks2 = MAX_CALL_WALKS;
967
1001
  const initial = {
968
1002
  cwd: [context.cwd ?? ""],
@@ -1057,8 +1091,9 @@ function collectShellFileAccesses(command, context = {}) {
1057
1091
  }
1058
1092
  const locate = (p) => tokens.find((w) => w?.value === p) ?? { value: p };
1059
1093
  if (name === "sed") {
1060
- let inplace = false, hasScript = false, ended = false;
1094
+ let inplace = false, hasScript = false, ended = false, scriptUnknown = false;
1061
1095
  const files = [];
1096
+ const scriptParts = [];
1062
1097
  for (let i = 0; i < args.length; i++) {
1063
1098
  const token = args[i];
1064
1099
  if (!token) {
@@ -1076,7 +1111,11 @@ function collectShellFileAccesses(command, context = {}) {
1076
1111
  hasScript = true;
1077
1112
  const eq = value.indexOf("=");
1078
1113
  const script = eq < 0 ? args[++i] : { value: value.slice(eq + 1) };
1079
- if (value.startsWith("--file") && script) add("read", script, state);
1114
+ if (value.startsWith("--file")) {
1115
+ scriptUnknown = true;
1116
+ if (script) add("read", script, state);
1117
+ } else if (script?.value) scriptParts.push(script.value);
1118
+ else scriptUnknown = true;
1080
1119
  }
1081
1120
  continue;
1082
1121
  }
@@ -1091,7 +1130,11 @@ function collectShellFileAccesses(command, context = {}) {
1091
1130
  if (flag === "e" || flag === "f") {
1092
1131
  hasScript = true;
1093
1132
  const script = value.slice(j + 1) ? { value: value.slice(j + 1) } : args[++i];
1094
- if (flag === "f" && script) add("read", script, state);
1133
+ if (flag === "f") {
1134
+ scriptUnknown = true;
1135
+ if (script) add("read", script, state);
1136
+ } else if (script?.value) scriptParts.push(script.value);
1137
+ else scriptUnknown = true;
1095
1138
  break;
1096
1139
  }
1097
1140
  if (flag === "l") {
@@ -1101,10 +1144,14 @@ function collectShellFileAccesses(command, context = {}) {
1101
1144
  }
1102
1145
  continue;
1103
1146
  }
1104
- if (!hasScript) hasScript = true;
1105
- else files.push(token);
1147
+ if (!hasScript) {
1148
+ hasScript = true;
1149
+ scriptParts.push(value);
1150
+ } else files.push(token);
1106
1151
  }
1107
- for (const file2 of files) add(inplace ? "write" : "read", file2, state);
1152
+ const edits = inplace && !scriptUnknown ? sedSubstitutions(scriptParts.join("\n")) : null;
1153
+ for (const file2 of files)
1154
+ add(inplace ? "write" : "read", file2, state, edits ? { edits } : {});
1108
1155
  return;
1109
1156
  }
1110
1157
  if (FS_READ_TOOLS.has(name) && name !== "dd") {
@@ -1231,9 +1278,11 @@ function collectShellFileAccesses(command, context = {}) {
1231
1278
  continue;
1232
1279
  const remove = name === "rm" || name === "unlink";
1233
1280
  const append = name === "tee" && strings.some((s) => s === "--append" || /^-[^-]*a/.test(s));
1281
+ const teed = name === "tee" && !append ? currentStdin : null;
1234
1282
  add(remove ? "remove" : name === "touch" ? "create" : "write", token, state, {
1235
1283
  recursive: remove && strings.some((s) => /^-[^-]*[rR]/.test(s) || s === "--recursive"),
1236
- ...append ? { append: true } : {}
1284
+ ...append ? { append: true } : {},
1285
+ ...teed !== null ? { text: teed } : {}
1237
1286
  });
1238
1287
  }
1239
1288
  return;
@@ -1555,11 +1604,18 @@ function collectShellFileAccesses(command, context = {}) {
1555
1604
  if (stmt.Background) state = clone(state);
1556
1605
  const headWord = stmt.Cmd && stmt.Cmd.kind === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
1557
1606
  const sends = SENDING_STDIN.has(headWord);
1607
+ let produced;
1558
1608
  for (const redir of stmt.Redirs ?? []) {
1559
1609
  substitutions(redir.Word, state, depth);
1560
1610
  for (const word of wordValues(redir.Word, state)) {
1561
- if (ops().WRITES.has(redir.Op))
1562
- add("write", word, state, ops().APPENDS.has(redir.Op) ? { append: true } : {});
1611
+ if (ops().WRITES.has(redir.Op)) {
1612
+ if (ops().APPENDS.has(redir.Op)) add("write", word, state, { append: true });
1613
+ else {
1614
+ const stdout = redir.N == null || redir.N.Value === "1";
1615
+ if (stdout && produced === void 0) produced = producedText(stmt);
1616
+ add("write", word, state, stdout && produced ? { text: produced } : {});
1617
+ }
1618
+ }
1563
1619
  if (ops().READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
1564
1620
  if (redir.Op === ops().DUP_OUT && word.value && !/^\d+$/.test(word.value))
1565
1621
  add("write", word, state);
@@ -1571,6 +1627,7 @@ function collectShellFileAccesses(command, context = {}) {
1571
1627
  stdinScript(stmt, state, depth);
1572
1628
  const fedText = stdinText(stmt);
1573
1629
  if (fedText !== null) nestedPayload(xargsPayload(stmt, fedText), clone(state), depth);
1630
+ currentStdin = fedText;
1574
1631
  switch (cmd.kind) {
1575
1632
  case "CallExpr":
1576
1633
  call(cmd, state, depth);
@@ -6572,8 +6629,16 @@ function evaluateGitHooksFileAccess(args, accesses, context, settings, shell) {
6572
6629
  disarms = true;
6573
6630
  how = "removed";
6574
6631
  } else if (texts === null) {
6575
- disarms = true;
6576
- how = "overwritten";
6632
+ if (access.edits) {
6633
+ disarms = access.edits.some(editDisarms);
6634
+ how = "edited to exit";
6635
+ } else if (access.text !== void 0) {
6636
+ disarms = hookTextRunsNothing(access.text);
6637
+ how = "emptied";
6638
+ } else {
6639
+ disarms = true;
6640
+ how = "overwritten";
6641
+ }
6577
6642
  } else if (texts.whole.length > 0) {
6578
6643
  disarms = texts.whole.some(hookTextRunsNothing);
6579
6644
  how = "emptied";
package/dist/index.mjs CHANGED
@@ -944,12 +944,46 @@ function wordValues(word, state) {
944
944
  if (!dynamic) return out.map(asWord);
945
945
  return out.filter((piece) => piece.value !== "").map((piece) => ({ value: piece.value, pattern: piece.pattern + "*", dynamic: true }));
946
946
  }
947
+ function sedSubstitutions(script) {
948
+ const out = [];
949
+ for (const raw of script.split(/\r?\n|;/)) {
950
+ const cmd = raw.trim();
951
+ if (cmd === "") continue;
952
+ const m = /^(?:(?:\d+|\$|\/(?:\\.|[^\\/])*\/)(?:,(?:\d+|\$|\/(?:\\.|[^\\/])*\/))?)?s(.)/.exec(
953
+ cmd
954
+ );
955
+ if (!m) return null;
956
+ const delimiter = m[1];
957
+ if (delimiter === "\\") return null;
958
+ const parts = [];
959
+ let cur = "";
960
+ let i = m[0].length;
961
+ for (; i < cmd.length && parts.length < 2; i++) {
962
+ const ch = cmd[i];
963
+ if (ch === "\\" && i + 1 < cmd.length) {
964
+ cur += ch + cmd[++i];
965
+ continue;
966
+ }
967
+ if (ch === delimiter) {
968
+ parts.push(cur);
969
+ cur = "";
970
+ continue;
971
+ }
972
+ cur += ch;
973
+ }
974
+ if (parts.length < 2) return null;
975
+ if (!/^[gpiImM0-9]*$/.test(cmd.slice(i))) return null;
976
+ out.push(parts[1].replace(/\\(.)/g, "$1"));
977
+ }
978
+ return out.length > 0 ? out : null;
979
+ }
947
980
  function collectShellFileAccesses(command, context = {}) {
948
981
  const out = [];
949
982
  let payloads = 256;
950
983
  const functions = /* @__PURE__ */ new Map();
951
984
  const seenCalls = /* @__PURE__ */ new Set();
952
985
  const pipedStdin = /* @__PURE__ */ new WeakMap();
986
+ let currentStdin = null;
953
987
  let callWalks2 = MAX_CALL_WALKS;
954
988
  const initial = {
955
989
  cwd: [context.cwd ?? ""],
@@ -1044,8 +1078,9 @@ function collectShellFileAccesses(command, context = {}) {
1044
1078
  }
1045
1079
  const locate = (p) => tokens.find((w) => w?.value === p) ?? { value: p };
1046
1080
  if (name === "sed") {
1047
- let inplace = false, hasScript = false, ended = false;
1081
+ let inplace = false, hasScript = false, ended = false, scriptUnknown = false;
1048
1082
  const files = [];
1083
+ const scriptParts = [];
1049
1084
  for (let i = 0; i < args.length; i++) {
1050
1085
  const token = args[i];
1051
1086
  if (!token) {
@@ -1063,7 +1098,11 @@ function collectShellFileAccesses(command, context = {}) {
1063
1098
  hasScript = true;
1064
1099
  const eq = value.indexOf("=");
1065
1100
  const script = eq < 0 ? args[++i] : { value: value.slice(eq + 1) };
1066
- if (value.startsWith("--file") && script) add("read", script, state);
1101
+ if (value.startsWith("--file")) {
1102
+ scriptUnknown = true;
1103
+ if (script) add("read", script, state);
1104
+ } else if (script?.value) scriptParts.push(script.value);
1105
+ else scriptUnknown = true;
1067
1106
  }
1068
1107
  continue;
1069
1108
  }
@@ -1078,7 +1117,11 @@ function collectShellFileAccesses(command, context = {}) {
1078
1117
  if (flag === "e" || flag === "f") {
1079
1118
  hasScript = true;
1080
1119
  const script = value.slice(j + 1) ? { value: value.slice(j + 1) } : args[++i];
1081
- if (flag === "f" && script) add("read", script, state);
1120
+ if (flag === "f") {
1121
+ scriptUnknown = true;
1122
+ if (script) add("read", script, state);
1123
+ } else if (script?.value) scriptParts.push(script.value);
1124
+ else scriptUnknown = true;
1082
1125
  break;
1083
1126
  }
1084
1127
  if (flag === "l") {
@@ -1088,10 +1131,14 @@ function collectShellFileAccesses(command, context = {}) {
1088
1131
  }
1089
1132
  continue;
1090
1133
  }
1091
- if (!hasScript) hasScript = true;
1092
- else files.push(token);
1134
+ if (!hasScript) {
1135
+ hasScript = true;
1136
+ scriptParts.push(value);
1137
+ } else files.push(token);
1093
1138
  }
1094
- for (const file2 of files) add(inplace ? "write" : "read", file2, state);
1139
+ const edits = inplace && !scriptUnknown ? sedSubstitutions(scriptParts.join("\n")) : null;
1140
+ for (const file2 of files)
1141
+ add(inplace ? "write" : "read", file2, state, edits ? { edits } : {});
1095
1142
  return;
1096
1143
  }
1097
1144
  if (FS_READ_TOOLS.has(name) && name !== "dd") {
@@ -1218,9 +1265,11 @@ function collectShellFileAccesses(command, context = {}) {
1218
1265
  continue;
1219
1266
  const remove = name === "rm" || name === "unlink";
1220
1267
  const append = name === "tee" && strings.some((s) => s === "--append" || /^-[^-]*a/.test(s));
1268
+ const teed = name === "tee" && !append ? currentStdin : null;
1221
1269
  add(remove ? "remove" : name === "touch" ? "create" : "write", token, state, {
1222
1270
  recursive: remove && strings.some((s) => /^-[^-]*[rR]/.test(s) || s === "--recursive"),
1223
- ...append ? { append: true } : {}
1271
+ ...append ? { append: true } : {},
1272
+ ...teed !== null ? { text: teed } : {}
1224
1273
  });
1225
1274
  }
1226
1275
  return;
@@ -1542,11 +1591,18 @@ function collectShellFileAccesses(command, context = {}) {
1542
1591
  if (stmt.Background) state = clone(state);
1543
1592
  const headWord = stmt.Cmd && stmt.Cmd.kind === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
1544
1593
  const sends = SENDING_STDIN.has(headWord);
1594
+ let produced;
1545
1595
  for (const redir of stmt.Redirs ?? []) {
1546
1596
  substitutions(redir.Word, state, depth);
1547
1597
  for (const word of wordValues(redir.Word, state)) {
1548
- if (ops().WRITES.has(redir.Op))
1549
- add("write", word, state, ops().APPENDS.has(redir.Op) ? { append: true } : {});
1598
+ if (ops().WRITES.has(redir.Op)) {
1599
+ if (ops().APPENDS.has(redir.Op)) add("write", word, state, { append: true });
1600
+ else {
1601
+ const stdout = redir.N == null || redir.N.Value === "1";
1602
+ if (stdout && produced === void 0) produced = producedText(stmt);
1603
+ add("write", word, state, stdout && produced ? { text: produced } : {});
1604
+ }
1605
+ }
1550
1606
  if (ops().READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
1551
1607
  if (redir.Op === ops().DUP_OUT && word.value && !/^\d+$/.test(word.value))
1552
1608
  add("write", word, state);
@@ -1558,6 +1614,7 @@ function collectShellFileAccesses(command, context = {}) {
1558
1614
  stdinScript(stmt, state, depth);
1559
1615
  const fedText = stdinText(stmt);
1560
1616
  if (fedText !== null) nestedPayload(xargsPayload(stmt, fedText), clone(state), depth);
1617
+ currentStdin = fedText;
1561
1618
  switch (cmd.kind) {
1562
1619
  case "CallExpr":
1563
1620
  call(cmd, state, depth);
@@ -6559,8 +6616,16 @@ function evaluateGitHooksFileAccess(args, accesses, context, settings, shell) {
6559
6616
  disarms = true;
6560
6617
  how = "removed";
6561
6618
  } else if (texts === null) {
6562
- disarms = true;
6563
- how = "overwritten";
6619
+ if (access.edits) {
6620
+ disarms = access.edits.some(editDisarms);
6621
+ how = "edited to exit";
6622
+ } else if (access.text !== void 0) {
6623
+ disarms = hookTextRunsNothing(access.text);
6624
+ how = "emptied";
6625
+ } else {
6626
+ disarms = true;
6627
+ how = "overwritten";
6628
+ }
6564
6629
  } else if (texts.whole.length > 0) {
6565
6630
  disarms = texts.whole.some(hookTextRunsNothing);
6566
6631
  how = "emptied";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "2.54.0",
3
+ "version": "2.54.1",
4
4
  "description": "Access control for AI agents. Set what Claude Code, Codex, Gemini, Cursor and any MCP server are allowed to do, review risky actions before they run, and keep every action on the record.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",