cc-haha/src/tools/BashTool/bashPermissions.test.ts
程序员阿江(Relakkes) b156be8d8d feat: make PR quality verification self-enforcing
Contributors and coding agents need one local command that both reports and enforces the quality contract. This change turns the PR gate into the shared verification entrypoint, adds path-selected local lanes, tightens coverage accounting around changed lines, and documents the repair loop in contributor and agent-facing guidance.

Constraint: Ordinary PR verification must stay non-live and runnable without provider credentials
Constraint: Coverage policy updates in this commit require maintainer approval before push/merge
Rejected: Keep quality guidance only in docs | agents need executable scripts and AGENTS.md instructions to follow the loop consistently
Confidence: high
Scope-risk: broad
Directive: Do not bypass `bun run verify` for production changes; fix failed lanes and coverage reports instead of lowering thresholds
Tested: bun run check:policy
Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run verify
Not-tested: live provider baseline; no provider credentials were required for this non-live PR gate
2026-05-06 22:33:43 +08:00

48 lines
2.3 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import {
BINARY_HIJACK_VARS,
getFirstWordPrefix,
getSimpleCommandPrefix,
matchWildcardPattern,
stripAllLeadingEnvVars,
stripSafeWrappers,
stripWrappersFromArgv,
} from './bashPermissions'
describe('bash permission command normalization', () => {
test('extracts stable prefixes only through safe env vars', () => {
expect(getSimpleCommandPrefix('NODE_ENV=production npm run build')).toBe('npm run')
expect(getSimpleCommandPrefix('MY_VAR=value npm run build')).toBeNull()
expect(getSimpleCommandPrefix('git commit -m "fix"')).toBe('git commit')
expect(getSimpleCommandPrefix('chmod 755 file')).toBeNull()
})
test('uses first-word fallback without suggesting broad shell wrappers', () => {
expect(getFirstWordPrefix('python3 script.py 2>&1 | tail -20')).toBe('python3')
expect(getFirstWordPrefix('bash -c "rm -rf /tmp/x"')).toBeNull()
expect(getFirstWordPrefix('./script.sh')).toBeNull()
})
test('strips only safe wrappers and leading safe env assignments', () => {
expect(stripSafeWrappers('RUST_LOG=debug timeout -k 5s --signal TERM 10s nohup nice -n 5 -- git status')).toBe('git status')
expect(stripSafeWrappers('timeout -k$(id) 10s ls')).toBe('timeout -k$(id) 10s ls')
expect(stripSafeWrappers('timeout 10s FOO=bar npm test')).toBe('FOO=bar npm test')
})
test('normalizes wrapper argv with the same safety boundary', () => {
expect(stripWrappersFromArgv(['timeout', '--signal', 'TERM', '10s', 'nohup', 'git', 'status'])).toEqual(['git', 'status'])
expect(stripWrappersFromArgv(['timeout', '--signal', '$(id)', '10s', 'ls'])).toEqual(['timeout', '--signal', '$(id)', '10s', 'ls'])
expect(stripWrappersFromArgv(['nice', '-n', '5', '--', 'git', 'status'])).toEqual(['git', 'status'])
})
test('strips arbitrary leading env vars for deny matching but keeps binary hijack vars', () => {
expect(stripAllLeadingEnvVars('FOO=bar TOKEN="safe value" claude --help')).toBe('claude --help')
expect(stripAllLeadingEnvVars('PATH=/tmp/bin claude --help', BINARY_HIJACK_VARS)).toBe('PATH=/tmp/bin claude --help')
})
test('matches wildcard permission patterns consistently', () => {
expect(matchWildcardPattern('git *', 'git status --short')).toBe(true)
expect(matchWildcardPattern('git commit', 'git status')).toBe(false)
})
})