fix(hooks): the args-heredoc guard checks every opener with a line after it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8be0c05221
commit
eca974aaa6
2 changed files with 37 additions and 11 deletions
|
|
@ -264,20 +264,26 @@ const commandView = commandPositionView(deobfuscated.replace(/\x1B\[[0-9;]*m/g,
|
|||
// delimiter would close the heredoc there, and the shell would run every line
|
||||
// after it as a command (fix round 4.5b, R6). This check reads the raw
|
||||
// command: `normalized` and `commandView` collapse lines and drop heredoc
|
||||
// bodies (hooks/lib/bash-view.mjs). Openers <<'D', <<"D" and <<\D are quoted
|
||||
// (the body is not expanded); any other opener of the delimiter is treated as
|
||||
// unquoted and blocked, since its body would expand. A quoted opener passes
|
||||
// only when exactly one line, trimmed, equals the delimiter and every line
|
||||
// after it is empty.
|
||||
// bodies (hooks/lib/bash-view.mjs). Every opener of the delimiter in the
|
||||
// command is checked, so a quoted first mention cannot hide a later unquoted
|
||||
// heredoc. bash reads a heredoc body only from the lines after the opener's
|
||||
// line, so an opener with no non-blank line after its own line (a one-line
|
||||
// grep or commit message that only mentions it) has no body: nothing can
|
||||
// close early and an unquoted opener has nothing to expand, so it is skipped.
|
||||
// An opener with a non-blank line after it is blocked unless it is quoted
|
||||
// (<<'D', <<"D" or <<\D, whose body is not expanded) and exactly one line,
|
||||
// trimmed, equals the delimiter with every line after that one empty.
|
||||
const ARGS_DELIM = 'VOYAGE_ARGS_EOF_7F3C9E';
|
||||
const argsOpener = new RegExp(`<<-?\\s*(['"\\\\]?)${ARGS_DELIM}(['"]?)`).exec(command);
|
||||
if (argsOpener) {
|
||||
const argsOpeners = command.matchAll(new RegExp(`<<-?\\s*(['"\\\\]?)${ARGS_DELIM}(['"]?)`, 'g'));
|
||||
const argsLines = command.split('\n');
|
||||
const argsClosing = argsLines.flatMap((l, i) => (l.trim() === ARGS_DELIM ? [i] : []));
|
||||
const closesOnceAtEnd = argsClosing.length === 1 &&
|
||||
argsLines.slice(argsClosing[0] + 1).every((line) => line.trim() === '');
|
||||
for (const argsOpener of argsOpeners) {
|
||||
const hasBody = command.slice(argsOpener.index).split('\n').slice(1).some((l) => l.trim() !== '');
|
||||
if (!hasBody) continue;
|
||||
const [, open, close] = argsOpener;
|
||||
const quoted = open === '\\' ? close === '' : open !== '' && close === open;
|
||||
const lines = command.split('\n');
|
||||
const closing = lines.flatMap((l, i) => (l.trim() === ARGS_DELIM ? [i] : []));
|
||||
const closesOnceAtEnd = closing.length === 1 &&
|
||||
lines.slice(closing[0] + 1).every((line) => line.trim() === '');
|
||||
if (!quoted || !closesOnceAtEnd) {
|
||||
const why = !quoted
|
||||
? `The heredoc that hands the arguments to the arg parser opens ${ARGS_DELIM} unquoted, so the shell would expand its body. ` +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue