Commit Graph
20 Commits
Author SHA1 Message Date
haelyra c4b8325945 fix: prepare six focused ECC reliability repairs
Preserve complete source ancestry for PRs #3512, #3513, #3515, #3516, #3517 and #3518. Repair bounded recovery, alias compatibility and evaluator packaging gaps while keeping registry reuse inside the canonical loader. Verified actual hook, setup, dashboard, evaluator, sandbox and profile workflows plus full npm, Python, lint and supply-chain checks. ECC-043 preparation only; integration remains with the delivery owner.
2026-10-11 11:10:53 -04:00
chenhz01 cffcf05e7a hooks: sed labels end at semicolon — commands after b/t/T/: are still scanned
CodeRabbit security finding on the merged sed work (CWE-693, verified live
on main via #3495): GNU sed labels for b, t, T and : end at ';' or newline —
verified against GNU sed 4.9 ('bar;e cmd' parses label 'a' and still runs
cmd; '}' is not a label terminator). Reading those operands to end of line
let 'sed :a;e git commit --no-verify' pass the hook while sed executed the
e command.

Labels are now read up to ';' or newline and command parsing resumes after
them. Text, filename and quit operands (a, c, i, r, w, q, Q) keep the
end-of-line reading: nothing after them on the line executes.

5 regression tests pin the three bypass paths and the benign boundary
(a bare label and a bare branch stay data). Suite: 817/817.
2026-10-11 18:52:44 +08:00
chenhz01 3af000e143 hooks: first operand after -- is still the sed script when no script option was given
Greptile P1: sedScriptParts stopped at -- and discarded the remaining
operands, so 'sed -- <script>' skipped script collection entirely. GNU sed
4.9 (verified): with no -e/--expression anywhere, the first operand after --
is the script and an s///e in it still executes.

The operands after -- are now collected as positionals, so the existing
first-positional-becomes-script rule applies. Operands after the script stay
file names (data). 2 regression tests pin both sides. Suite: 812/812.
2026-10-11 03:55:27 +08:00
chenhz01 95964ee623 hooks: block escaped sed exec fragments conservatively; decompose sed readers
Greptile P1 findings (both verified by running GNU sed against the hook):

1. Escaped replacements evade the check: GNU sed decodes \xHH, \oOOO, \dNNN,
   case operations and unknown escapes like \g before executing an s///e
   replacement or e operand. Scanning the raw text let \x67it decode to git.
   Decoding that surface faithfully is out of scope, so any backslash in an
   exec-bearing fragment now throws UnsupportedSedEscape and the hook blocks
   with an explicit reason — mirroring the existing env -S escape precedent
   instead of pretending to interpret sed. Escaped-newline continuations of an
   e operand are refused by the same check.

2. Continued commands went unchecked: the e operand reader stopped at every
   newline, but GNU sed joins escaped newlines into one operand. Now covered
   by the same conservative refusal.

Also decomposes the sed readers into sub-50-line helpers per the repository
function-size rule (AGENTS.md): readSedDelimited, readSedCommand,
readSedExecute, readSedLineOperand, assertSedLiteral.

5 regression tests pin the escape paths and the benign boundary (a backslash
in a non-executing script stays data). Suite: 810/810.
2026-10-11 03:52:06 +08:00
chenhz01 34a7f41539 hooks: parse sed options through the whole argv — later -e scripts still execute
CodeRabbit security review (Major, CWE-693) correctly identified that GNU sed
collects -e/--expression scripts from any argv position: a script option after
positional operands is still executed, so stopping the option walk at the first
positional let an e-flagged script hide behind it.

Verified against GNU sed 4.9 before fixing:
- 'sed -e s/x/y/ - -e s/y/X/e' runs X (the later -e executes)
- with any script option present, every positional is a file operand

sedScriptParts now walks the full argv, collects scripts from all -e/
--expression positions, and only promotes the first positional to the script
when no script option was given. Unknown options after positionals still fall
back to the opaque default.

4 regression tests pin the reported bypass paths (suite: 805/805).
2026-10-11 03:38:02 +08:00
chenhz01 ffe0e36d46 hooks: model GNU sed execution — script and file operands are data, e/s///e are shell receivers
Ported from the #3051 discussion (test intent, not the old scanner):

- A confidently-parsed sed invocation whose script contains no execution
  construct is a pure text transformation: the script operand and every file
  operand are data. This removes the specific false positives where file names
  or non-executing scripts merely containing 'git ...' were blocked.
- The GNU sed `e` command (bare or with operand) and the `s` command with the
  `e` flag run text through the shell. Their literal fragments (the s
  replacement, the e operand) and any piped/redirected source are scanned as
  real shell source, closing the `printf x | sed 's/x/git commit --no-verify/e'`
  gap reported on 2026-10-09.
- Anything the literal parser cannot confidently read (addresses it cannot
  consume, -f script files, -i suffix ambiguity, unknown options, expansion
  residue) falls back to today's opaque default. No behavior changes for
  unmodeled launchers.
- shell-scan.js is untouched and remains the canonical scanner.

Tests: 14 focused cases ported from the #3051 litmus set; full hook suite
801/801.
2026-10-11 03:15:36 +08:00
34a97fa898 fix: reject unsupported env split-string escapes
Validate env -S and --split-string escape syntax before shell scanning or
opaque fallback. Preserve literal single-quoted data and charge each
validation character against the shared input-proportional work budget.

Verified 787 focused cases and 290 real Git/config, harmless native env
and registered-routing controls. Keeps the prior paid-copy protection.

Co-authored-by: Nguyen Thanh Dat <ntdat812.dev@gmail.com>
Co-authored-by: Davemaina1 <dmain7015@gmail.com>
2026-10-09 21:04:20 -04:00
993b5f230f fix: guard literal Git hook redirects and env split arguments
Carry forward bounded literal include/includeIf configuration capabilities
and env -S/--split-string words from PRs #2858 and #2897. Charge array
copy and expansion work before allocation to preserve the shared quota.

Verified 731 hook cases and 60 real Git/config and registered-route
controls. Dynamic remainder and original full source PRs remain held.

Co-authored-by: Nguyen Thanh Dat <ntdat812.dev@gmail.com>
Co-authored-by: Davemaina1 <dmain7015@gmail.com>
2026-10-09 20:41:09 -04:00
affaan-m eca846248d fix(hooks): track append assignments in Git override checks
Preserve the original contributor histories and apply the exact independently reviewed repair.

Source-Parent: dee884656e
Review-Manifest-SHA256: 0c7eeef7c2722a605d049daf2dc3c2473c10dd616005afcba21049fc036d8ce9
2026-09-28 07:05:14 -04:00
affaan-m dee884656e fix(hooks): preserve Git configuration across shell command scopes
Preserve contributor history while applying the exact reviewed current-main repair.

Source-PR: https://github.com/affaan-m/ECC/pull/2965
Source-Parent: ec0753bc7f
Review-Manifest-SHA256: 0172e3e695e672305f9bc41b0d385d2a299d8ec520c763dd9d7f6da743f76971
2026-09-28 05:38:05 -04:00
affaan-m ec0753bc7f fix(hooks): preserve shell boundaries and detect literal hook bypasses
Preserve contributor history while applying the exact reviewed current-main repair.

Source-PR: https://github.com/affaan-m/ECC/pull/2965
Source-Parent: de480c65fd
Review-Manifest-SHA256: 53ead3eb859a4c6c13d6751b1fe4f9a94f1a5d35a37430f6b8524c50e903490b
2026-09-28 03:56:48 -04:00
affaan-m de480c65fd fix(hooks): distinguish literal data from executable shell contexts
Preserve original contributor history and all current-main assertions while bounding quote, heredoc, shell-option and deferred-scan handling.

Source-PR: https://github.com/affaan-m/ECC/pull/2965
Source-Parent: 5a878131de
Main-Parent: d3b8a3e908
Review-Manifest-SHA256: 5d23f47e60516b66733805aec0ebcd47f19265c964529fa0b1220c8aa6903add
2026-09-28 02:27:11 -04:00
ZaalandGitHub 4f37387420 fix(hooks): block-no-verify handles stuck optional values and long-option prefixes (#3073)
Two cases the word-level rewrite still got wrong. Short options that take an optional stuck value (-u[mode], -S[keyid]) end the cluster scan, so git commit -uno and -Sn are allowed while -nu stays blocked. Git accepts any unambiguous long-option prefix, so --no-veri and --no-verif on commit, push, merge and rebase are now blocked; --no-verbose stays allowed. Quoted data such as -m "--no-verify" is still treated as data. Independent exact-head review probed 34 commands in-process and against real git with no bypass and no false positive; hook test 35/35, eslint clean, CI 44/44 at the head.
2026-09-12 03:07:29 +01:00
5a878131de fix(block-no-verify): split scanner and close quoting bypasses
Merge buildCommentMask into buildScanBoundaries so one pass owns comment mask and scan bounds.
Extract shell-scan primitives to scripts/hooks/lib/shell-scan.js; keep policy in the hook.
Use a sticky heredoc regex instead of copying the remaining input on every <<.
Replace the short-circuiting chain test with clean-leading sequences that exercise checkCommand advance.
Add quoted-executable non-leakage coverage for the scanEnd=length path.
Treat $'…' ANSI-C quoting as a quoted executable word.
Join unquoted backslash-newline for the flag scan and heredoc-line bound.

Co-Authored-By: grok (fleet lane) <noreply@fleet.local>

Co-authored-by: groy75 <groy75@users.noreply.github.com>
2026-09-05 03:32:30 +00:00
b3a2d3586b fix(block-no-verify): bound the flag scan to the enclosing quote/heredoc line
The scanner locked onto a `git commit` literal that sat inside a quoted
string of an OUTER command (a python heredoc's string, a VAR="..."
assignment, a printf JSON payload), then hasNoVerifyFlag() re-tokenized from
that inner offset with a fresh quote state. When quote parity flipped, the
"segment" ran past the enclosing string and picked up an unrelated later
`bash -n` / `sed -n` / `grep -n` as `commit -n`. Two legitimate commands were
blocked this way in one working session.

The first attempt at this PR (d5c428c) fixed the false positive by SKIPPING
`git` tokens judged to be in "data" context. That was the wrong lever: data
becomes executable the moment it is piped to a shell (`echo '...' | bash`,
`bash <<< '...'`, `cat <<EOF | bash`), and no regex list of shell invocations
is complete (`bash --noprofile -c` slipped). Three independent reviewers
(codex, Greptile, CodeRabbit) demonstrated executing bypasses; that commit is
squashed away here and none of its behaviour survives.

This version keeps every `git` candidate visible and keeps main's blocking
surface intact. A single O(n) preprocessing pass (buildScanBoundaries)
records, for each enclosed character, the closing quote or the current
heredoc-body line end. Subcommand discovery and flag tokenization are capped
at that boundary, so tokens after the enclosing data can never leak into a
candidate's argument list. Also: comment detection is a single mask pass
instead of a repeated prefix scan; heredoc parsing accepts quoted hyphenated
delimiters and CRLF terminators and preserves the `<<` vs `<<-` tab
distinction; quote-assembled words (`g''it`) are recognized.

Verified (all payloads run through the hook as JSON on stdin):
- red-list, 24 executing forms incl. every reviewer-reported bypass -> exit 2
  on main AND here (CRLF heredoc trailer: 0 on main, 2 here);
- green-list, 16 leak-class false positives -> exit 2 on main, 0 here;
- tests/hooks/block-no-verify.test.js 84/84 (main's 25 retained verbatim,
  d5c428c's 8 removed, 59 table-driven red/green cases added);
  tests/hooks/cursor-block-no-verify.test.js 14/14;
- 204,807-byte command with 8,905 non-command `git` literals: 8.5 ms.

Co-Authored-By: Codex (GPT) <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyuZY1LZNuShHiewExekLT
2026-09-04 22:35:09 -04:00
a8c6da485d fix(hooks): catch the bypass short flag anywhere in a cluster (#2668)
isCommitNoVerifyShortFlag anchored on the first character, so it only recognised the flag when it led the cluster. Git clusters short options, which means git commit -an is -a plus the bypass flag and skips the hooks. -sn and -vn slip through the same way, while -na and -nm are caught — the difference is position, not intent.

Scanning now walks the cluster and stops at a value-taking option, since that option swallows the rest as its inline value. The n in -mn stays message text, and the existing -tn case keeps working.

Adds 4 tests: the three clustered forms that were escaping, plus -mn to pin the inline-value boundary. Verified the three fail against current main. Suite 25 to 29.

Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
2026-08-04 17:08:56 -04:00
SeungHyunandGitHub 6be241a463 fix: close block-no-verify bypass holes
Backport Jamkris's fix for case-insensitive core.hooksPath overrides and the git commit -tn template-path false positive. Verified locally on current main with 25/25 block-no-verify tests and node tests/run-all.js passing 2369/2369.
2026-05-12 22:28:12 -04:00
Affaan MustafaandAffaan Mustafa 0dcde13384 fix: parse block-no-verify flags by shell words 2026-04-29 21:59:12 -04:00
Ke WangandClaude Opus 4.6 809e0fa0a9 fix: address PR review comments on block-no-verify hook
- Add `minimal` profile so the security hook runs in all profiles
- Scope -n/--no-verify flag check to the detected subcommand region,
  preventing false positives on chained commands (e.g. `git log -n 10`)
- Guard stdin listeners with `require.main === module` so require()
  from run-with-flags.js does not register unnecessary listeners
- Verify subcommand token is preceded only by flags/flag-args after
  "git", preventing misclassification of argument values as subcommands
- Add integration tests for block-no-verify hook

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:29:01 -05:00
Ke Wang dae663d856 fix: route block-no-verify hook through run-with-flags.js
Replace inline `npx block-no-verify@1.1.2` with a standalone Node.js
script routed through `run-with-flags.js`, matching every other hook.

Fixes two bugs:
1. npx inherits the project cwd and triggers EBADDEVENGINES in
   pnpm-only projects that set devEngines.packageManager.onFail=error.
2. The hook bypassed run-with-flags.js so ECC_DISABLED_HOOKS had no
   effect — the isHookEnabled() check never ran.

The new script replicates the full block-no-verify@1.1.2 detection
logic (--no-verify, -n shorthand for commit, core.hooksPath override)
with zero external dependencies.

Closes #1378
2026-04-12 19:53:15 -05:00