wogiflow 2.30.3 → 2.30.4
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/package.json
CHANGED
|
@@ -315,33 +315,100 @@ function checkWriteGate(filePath, newContentRaw, config) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
318
|
+
* Strip quoted regions + heredoc bodies from a Bash command so the structural
|
|
319
|
+
* regex below only sees actual shell tokens. Released v2.30.3 over-triggered
|
|
320
|
+
* because the previous regex matched markdown blockquote `> "text"` inside
|
|
321
|
+
* heredoc bodies of `gh release create --notes "$(cat <<'EOF'...EOF)"`.
|
|
322
322
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
|
|
323
|
+
* Best-effort: handles single-quoted, double-quoted, backtick, and heredoc
|
|
324
|
+
* patterns. Doesn't attempt full shell parsing.
|
|
325
|
+
*/
|
|
326
|
+
function stripQuotedContent(cmd) {
|
|
327
|
+
if (typeof cmd !== 'string') return '';
|
|
328
|
+
let stripped = cmd;
|
|
329
|
+
// Heredocs first (multiline) — replace body with a sentinel
|
|
330
|
+
stripped = stripped.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n\1\s*$/gm, ' <<HEREDOC>> ');
|
|
331
|
+
stripped = stripped.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n\1\b/g, ' <<HEREDOC>> ');
|
|
332
|
+
// Single-quoted strings
|
|
333
|
+
stripped = stripped.replace(/'[^']*'/g, "''");
|
|
334
|
+
// Backtick command substitution
|
|
335
|
+
stripped = stripped.replace(/`[^`]*`/g, '``');
|
|
336
|
+
// Double-quoted strings (allow escaped quotes inside)
|
|
337
|
+
stripped = stripped.replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
|
338
|
+
return stripped;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Validate a Bash command against the deferral gate.
|
|
343
|
+
*
|
|
344
|
+
* wf-4a5b7a6f rewrite (2026-05-11): previously this used three independent
|
|
345
|
+
* regex checks AND'd together, which over-triggered on commands that merely
|
|
346
|
+
* REFERENCED the target file and the word "deferred" as text content
|
|
347
|
+
* (markdown blockquotes, commit messages, gh release notes). The
|
|
348
|
+
* `>\s*[^&|]` part of `mutates` matched markdown blockquote syntax inside
|
|
349
|
+
* heredocs. The bare-word `\bdeferred\b` part of `mentionsDeferral` matched
|
|
350
|
+
* any prose mention of "deferred".
|
|
351
|
+
*
|
|
352
|
+
* Fix:
|
|
353
|
+
* 1. Run the structural mutation check on a QUOTE-STRIPPED command —
|
|
354
|
+
* a `>` inside `"..."` or `'...'` is not a shell redirect.
|
|
355
|
+
* 2. Tighten the mutation check to require the target file be the WRITE
|
|
356
|
+
* DESTINATION, not merely mentioned anywhere.
|
|
357
|
+
* 3. Tighten deferral-content detection to the JSON-shape pattern only;
|
|
358
|
+
* drop the bare-word match.
|
|
359
|
+
*
|
|
360
|
+
* If the AI tries to actually mutate the file via Bash with deferred
|
|
361
|
+
* content, the gate still catches it. Prose mentions pass through.
|
|
326
362
|
*/
|
|
327
363
|
function checkBashGate(command, config) {
|
|
328
364
|
try {
|
|
329
365
|
if (!isGateEnabled(config)) return { blocked: false };
|
|
330
366
|
if (typeof command !== 'string' || !command) return { blocked: false };
|
|
331
367
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
368
|
+
// Step 1: strip quoted/heredoc content for the SHELL-LEVEL structural
|
|
369
|
+
// check (catches `>`, `tee` in actual shell positions, not inside markdown).
|
|
370
|
+
const stripped = stripQuotedContent(command);
|
|
371
|
+
|
|
372
|
+
// Step 2: detect a mutation operation targeting the review/audit file
|
|
373
|
+
// SPECIFICALLY. The patterns require the target file to be the WRITE
|
|
374
|
+
// DESTINATION — not merely mentioned. We test against BOTH the stripped
|
|
375
|
+
// command (catches shell-level redirects) AND the original command
|
|
376
|
+
// (catches in-language constructs like `node -e "fs.writeFileSync(...)"`
|
|
377
|
+
// where the JS payload is inside double-quotes and would be stripped).
|
|
378
|
+
// The patterns themselves are tight enough that running on the original
|
|
379
|
+
// doesn't re-introduce the prose-mention false positives — they require
|
|
380
|
+
// a write-verb token (writeFileSync, tee, etc.) IMMEDIATELY before the
|
|
381
|
+
// file path.
|
|
382
|
+
const writeToTargetPatterns = [
|
|
383
|
+
/(?:>>?|>\|)\s+['"]?[^\s'"`|&;]*last-(?:review|audit)\.json/,
|
|
384
|
+
/\btee\b(?:\s+-[a-zA-Z]+)*\s+['"]?[^\s'"`|&;]*last-(?:review|audit)\.json/,
|
|
385
|
+
/\b(?:fs\.)?writeFileSync\s*\(\s*[`'"][^`'"]*last-(?:review|audit)\.json/,
|
|
386
|
+
/\bfs\.write[A-Z][a-zA-Z]*\s*\(\s*[`'"][^`'"]*last-(?:review|audit)\.json/,
|
|
387
|
+
/\bsed\s+-i\b[^|;&]*\blast-(?:review|audit)\.json/,
|
|
388
|
+
/\b(?:mv|cp|rename(?:Sync)?)\s+\S+\s+['"]?[^\s'"`|&;]*last-(?:review|audit)\.json/
|
|
389
|
+
];
|
|
390
|
+
const mutatesTarget = writeToTargetPatterns.some(re => re.test(stripped) || re.test(command));
|
|
391
|
+
if (!mutatesTarget) return { blocked: false };
|
|
392
|
+
|
|
393
|
+
// Step 3: check the ORIGINAL command for deferred-status content. We
|
|
394
|
+
// accept TWO signals:
|
|
395
|
+
// - Quoted value: "deferred" / 'deferred' / `deferred` — JSON, JS,
|
|
396
|
+
// template-literal styles.
|
|
397
|
+
// - Bare word `\bdeferred\b` (or wont-?fix, skipped, dismissed) — fallback
|
|
398
|
+
// for cases where escaping mangles the quote chars (e.g. shell-escaped
|
|
399
|
+
// `\"deferred\"` inside a `node -e` payload where the quote becomes
|
|
400
|
+
// non-adjacent to the word).
|
|
401
|
+
//
|
|
402
|
+
// The earlier false-positive case (prose mentions in release notes) is
|
|
403
|
+
// already closed by the tightened mutation check above — we only reach
|
|
404
|
+
// this step when the command demonstrably writes TO the target file.
|
|
405
|
+
// At that point, ANY mention of the deferral keyword is genuinely
|
|
406
|
+
// suspicious; the gate should err on the side of blocking.
|
|
407
|
+
const quotedDeferral = /['"`](deferred(?:[-_][a-zA-Z0-9]+)?|wont-?fix|won-?t-?fix|skipped|dismissed)['"`]/i;
|
|
408
|
+
const bareDeferral = /\b(deferred(?:[-_][a-zA-Z0-9]+)?|wont-?fix|won-?t-?fix|skipped|dismissed)\b/i;
|
|
409
|
+
const mentionsDeferral = quotedDeferral.test(command) || bareDeferral.test(command);
|
|
342
410
|
if (!mentionsDeferral) return { blocked: false };
|
|
343
411
|
|
|
344
|
-
// We can't easily extract and validate the new content from arbitrary bash.
|
|
345
412
|
// Check auth: if the user has authorized deferrals, allow. Otherwise block.
|
|
346
413
|
const authResult = isAuthorized([{ id: 'unspecified' }]);
|
|
347
414
|
if (authResult.authorized) return { blocked: false };
|
|
@@ -393,6 +460,7 @@ module.exports = {
|
|
|
393
460
|
// Core checks
|
|
394
461
|
checkWriteGate,
|
|
395
462
|
checkBashGate,
|
|
463
|
+
stripQuotedContent,
|
|
396
464
|
|
|
397
465
|
// Auth API (used by classifier + CLI helper)
|
|
398
466
|
loadAuth,
|