mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-12 04:37:54 +02:00
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.
This commit is contained in:
@@ -78,6 +78,10 @@ const COMMIT_OPTIONS_WITH_INLINE_VALUE = [
|
||||
// must stop at this character — anything after it is the inline value,
|
||||
// not another flag.
|
||||
const COMMIT_SHORT_OPTIONS_WITH_VALUE = new Set(['m', 'F', 'C', 'c', 't']);
|
||||
// Short options whose value is OPTIONAL and must be stuck to the flag
|
||||
// (`-uno`, `-S<keyid>`). The rest of the cluster is that value, so an `n`
|
||||
// after them is not the -n flag: `git commit -uno` means --untracked-files=no.
|
||||
const COMMIT_SHORT_OPTIONS_WITH_OPTIONAL_VALUE = new Set(['u', 'S']);
|
||||
|
||||
function tokenizeShellWords(input, start = 0, end = input.length) {
|
||||
const tokens = [];
|
||||
@@ -264,6 +268,7 @@ function isCommitNoVerifyShortFlag(value) {
|
||||
const option = options.charAt(i);
|
||||
if (option === 'n') return true;
|
||||
if (COMMIT_SHORT_OPTIONS_WITH_VALUE.has(option)) return false;
|
||||
if (COMMIT_SHORT_OPTIONS_WITH_OPTIONAL_VALUE.has(option)) return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -388,6 +393,16 @@ function detectGitCommand(input, start = 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* git's option parser accepts any unambiguous prefix of a long option, so
|
||||
* `--no-veri` and `--no-verif` run as --no-verify. Shorter prefixes such as
|
||||
* `--no-ver` are ambiguous with --no-verbose and git rejects them itself, so
|
||||
* refusing every prefix from `--no-v` up blocks nothing that would have run.
|
||||
*/
|
||||
function isNoVerifyLongFlag(value) {
|
||||
return value.length >= '--no-v'.length && '--no-verify'.startsWith(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the input contains a --no-verify flag for a specific git command.
|
||||
* Only inspects the portion of the input starting at `offset` (the position
|
||||
@@ -422,7 +437,7 @@ function hasNoVerifyFlag(input, command, offset) {
|
||||
}
|
||||
}
|
||||
|
||||
if (value === '--no-verify') return true;
|
||||
if (isNoVerifyLongFlag(value)) return true;
|
||||
|
||||
// For commit, -n is shorthand for --no-verify.
|
||||
if (command === 'commit' && isCommitNoVerifyShortFlag(value)) {
|
||||
|
||||
@@ -219,6 +219,38 @@ if (test('still allows -tn (n is the -t template path, not a flag)', () => {
|
||||
assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}: ${r.stderr}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
// --- Optional stuck values (-u, -S) and long-option prefixes ---
|
||||
|
||||
if (test('allows -uno (n is the -u untracked-files mode, not a flag)', () => {
|
||||
const r = runHook({ tool_input: { command: 'git commit -uno -m "msg"' } });
|
||||
assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}: ${r.stderr}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('allows -Sn (n is the -S key id, not a flag)', () => {
|
||||
const r = runHook({ tool_input: { command: 'git commit -Sn -m "msg"' } });
|
||||
assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}: ${r.stderr}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('still blocks -nu (n comes before the optional-value flag)', () => {
|
||||
const r = runHook({ tool_input: { command: 'git commit -nu -m "msg"' } });
|
||||
assert.strictEqual(r.code, 2, `expected exit 2, got ${r.code}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('blocks --no-veri (git accepts unambiguous long-option prefixes)', () => {
|
||||
const r = runHook({ tool_input: { command: 'git commit --no-veri -m "msg"' } });
|
||||
assert.strictEqual(r.code, 2, `expected exit 2, got ${r.code}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('blocks --no-verif on git push', () => {
|
||||
const r = runHook({ tool_input: { command: 'git push --no-verif origin main' } });
|
||||
assert.strictEqual(r.code, 2, `expected exit 2, got ${r.code}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('allows --no-verbose (not a prefix of --no-verify)', () => {
|
||||
const r = runHook({ tool_input: { command: 'git commit --no-verbose -m "msg"' } });
|
||||
assert.strictEqual(r.code, 0, `expected exit 0, got ${r.code}: ${r.stderr}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('─'.repeat(50));
|
||||
console.log(`Passed: ${passed} Failed: ${failed}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user