@sagentlab/navarch-runtime 0.1.117 → 0.1.118
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/bin/worktree-guard-hook.cjs +74 -2
- package/package.json +1 -1
|
@@ -173,12 +173,80 @@ function expandHome(value) {
|
|
|
173
173
|
return value;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Previous character on the same line, ignoring blanks; "" at a true line
|
|
178
|
+
* start. A line that ends in an unescaped backslash continues onto the next
|
|
179
|
+
* line in the shell, so that backslash is returned instead.
|
|
180
|
+
*/
|
|
181
|
+
function previousSignificantChar(command, index) {
|
|
182
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
183
|
+
const ch = command[i];
|
|
184
|
+
if (ch === "\n") {
|
|
185
|
+
let j = i - 1;
|
|
186
|
+
while (j >= 0 && (command[j] === "\r" || command[j] === " " || command[j] === "\t")) j--;
|
|
187
|
+
let slashes = 0;
|
|
188
|
+
while (j >= 0 && command[j] === "\\") {
|
|
189
|
+
slashes++;
|
|
190
|
+
j--;
|
|
191
|
+
}
|
|
192
|
+
return slashes % 2 === 1 ? "\\" : "";
|
|
193
|
+
}
|
|
194
|
+
if (ch !== " " && ch !== "\t") return ch;
|
|
195
|
+
}
|
|
196
|
+
return "";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* True when the token sits directly after a quote that opens a word
|
|
201
|
+
* (`"/** x *\/"`). A closing quote (`"" /*`, `"x"/*`) leaves the token as a
|
|
202
|
+
* bare shell argument, so it does not count.
|
|
203
|
+
*/
|
|
204
|
+
function isInsideOpeningQuote(command, start) {
|
|
205
|
+
const quote = command[start - 1];
|
|
206
|
+
// Backticks run a command, so never use them to exempt a path token.
|
|
207
|
+
if (quote !== '"' && quote !== "'") return false;
|
|
208
|
+
// Only the command's first quote can prove an opening quoted word without
|
|
209
|
+
// parsing shell comments, heredocs and nested substitutions. Earlier quote
|
|
210
|
+
// characters make this ambiguous: keep screening rather than risk treating
|
|
211
|
+
// a closing quote after a continuation as an opening one.
|
|
212
|
+
if (/["'`]/.test(command.slice(0, start - 1))) return false;
|
|
213
|
+
const beforeQuote = command[start - 2];
|
|
214
|
+
return beforeQuote === undefined || /[\s=(<>;|&]/.test(beforeQuote);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* True when a "/"-leading token is comment or markup text rather than a path:
|
|
219
|
+
* a closing tag (`</p>`), a block-comment opener (`/**`, `/*`), or a line
|
|
220
|
+
* comment (`// note`). Each rule is deliberately narrow so real escapes stay
|
|
221
|
+
* screened: tag names that exist at the filesystem root (`</home>`) are still
|
|
222
|
+
* checked, `/*` and `//` only count at a comment position (line start, right
|
|
223
|
+
* after an opening quote, or after `;`) so `rm -rf /*`, `find // -name x`
|
|
224
|
+
* and `rm -rf "" /*` stay blocked, and a block comment needs a closing
|
|
225
|
+
* `*` + `/` later in the text.
|
|
226
|
+
*/
|
|
227
|
+
function isCommentOrMarkupToken(command, start, token) {
|
|
228
|
+
const end = start + token.length;
|
|
229
|
+
const next = command[end];
|
|
230
|
+
|
|
231
|
+
if (command[start - 1] === "<" && next === ">" && /^\/[A-Za-z][\w:.-]*$/.test(token)) {
|
|
232
|
+
return !fs.existsSync(token);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const atCommentPosition =
|
|
236
|
+
isInsideOpeningQuote(command, start) ||
|
|
237
|
+
["", ";"].includes(previousSignificantChar(command, start));
|
|
238
|
+
if (!atCommentPosition || next === undefined || !/\s/.test(next)) return false;
|
|
239
|
+
if (token === "/*" || token === "/**") return command.indexOf("*/", end) !== -1;
|
|
240
|
+
return token === "//" && /\S/.test(command.slice(end));
|
|
241
|
+
}
|
|
242
|
+
|
|
176
243
|
/**
|
|
177
244
|
* Pulls path-like references out of a shell command: absolute paths,
|
|
178
245
|
* home-anchored paths (`~...`, `$HOME/...`), and relative `..` traversals.
|
|
179
246
|
* Lexical by design — see the file header for why this is a guardrail, not
|
|
180
247
|
* a parser. URLs survive untouched (the char before `//` in `https://` is
|
|
181
|
-
* `:`, which is not a path delimiter here).
|
|
248
|
+
* `:`, which is not a path delimiter here). Comment and markup text that
|
|
249
|
+
* merely starts with "/" (`/** doc *\/`, `// note`, `</p>`) is not a path.
|
|
182
250
|
*/
|
|
183
251
|
function extractBashPathCandidates(command) {
|
|
184
252
|
const candidates = [];
|
|
@@ -193,7 +261,11 @@ function extractBashPathCandidates(command) {
|
|
|
193
261
|
const absoluteRe = /(?:^|[\s'"`=(<>;|&])(\/[^\s'"`<>;|&)]*)/g;
|
|
194
262
|
const homeRe = /(?:^|[\s'"`=(<>;|&])((?:~|\$\{?HOME\}?)[^\s'"`<>;|&)]*)/g;
|
|
195
263
|
let match;
|
|
196
|
-
while ((match = absoluteRe.exec(command)))
|
|
264
|
+
while ((match = absoluteRe.exec(command))) {
|
|
265
|
+
const start = match.index + match[0].length - match[1].length;
|
|
266
|
+
if (isCommentOrMarkupToken(command, start, match[1])) continue;
|
|
267
|
+
add(match[1]);
|
|
268
|
+
}
|
|
197
269
|
while ((match = homeRe.exec(command))) add(match[1]);
|
|
198
270
|
|
|
199
271
|
for (const token of command.split(/[\s'"`;|&<>()]+/)) {
|
package/package.json
CHANGED