fix(cso): refine Windows native diagnostics

This commit is contained in:
Garry Tan committed 2026-09-11 18:54:51 +00:00
1 parent 257321b70b
commit 27c04e632b
3 files changed
+20 -4

No files matched your search

+3 -1
View File
@@ -205,8 +205,10 @@ export async function git(repo: string, args: string[], home: string): Promise<s
// content. Name only the fixed helper-owned operation and bounded process
// outcome so native failures are actionable without exposing either.
const knownOperations=new Set(['rev-parse','symbolic-ref','ls-files','ls-tree','log','merge-base']),operation=args.find(value=>knownOperations.has(value))??'metadata',
phase=operation==='rev-parse'&&args.includes('--show-object-format')?'object-format':operation==='rev-parse'&&args.includes('--is-inside-work-tree')?'worktree-probe':operation,
reason=/not a git repository|outside repository/i.test(result.stderr)?'repository unavailable':/dubious ownership/i.test(result.stderr)?'repository ownership rejected':/(?:bad|invalid|unable to read).*config|config (?:error|file)/i.test(result.stderr)?'configuration rejected':/unknown option|unknown switch|unrecognized option|usage:/i.test(result.stderr)?'unsupported invocation':/(?:cannot|could not|unable to) (?:chdir|change directory)|no such file or directory/i.test(result.stderr)?'path unavailable':'request rejected',
outcome=result.timedOut?'timed out':result.truncated?'exceeded the output limit':`exited ${result.code}`;
throw new CsoError('MISSING_INPUT',`Could not read bounded Git metadata: ${operation} ${outcome}; source may not be a Git repository`);
throw new CsoError('MISSING_INPUT',`Could not read bounded Git metadata: ${phase} ${outcome} (${reason}); source may not be a Git repository`);
}
return result.stdout;
}
+10
View File
@@ -36,10 +36,20 @@ describe('CSO Git metadata hardening',()=>{
try{await readGitMetadata(repo,['rev-parse','--verify','secret-ref-name'],root);}catch(error){failure=error;}
expect(failure).toMatchObject({code:'MISSING_INPUT'});
expect(failure.message).toContain('rev-parse exited');
expect(failure.message).toContain('(request rejected)');
expect(failure.message).not.toContain(root);
expect(failure.message).not.toContain('secret-ref-name');
});
test.skipIf(process.platform==='win32')('labels the fixed object-format phase without exposing repository data',async()=>{
const {root,repo}=fixture();fs.appendFileSync(path.join(repo,'.git','config'),'\n[broken configuration\n');let failure:any;
try{await readGitMetadata(repo,['rev-parse','--show-object-format'],root);}catch(error){failure=error;}
expect(failure).toMatchObject({code:'MISSING_INPUT'});
expect(failure.message).toContain('object-format exited');
expect(failure.message).toContain('(configuration rejected)');
expect(failure.message).not.toContain(root);
});
test.skipIf(process.platform==='win32')('refuses trusted Git calls that are not bound to one audited worktree',async()=>{
const {root}=fixture();
await expect(runProcess(executable('git'),['rev-parse','--is-inside-work-tree'],{cwd:root,env:childEnvironment(root),raw:true})).rejects.toMatchObject({code:'INVALID_ARGUMENT'});
+7 -3
View File
@@ -16,11 +16,15 @@ function trustedGit():string{
function gitEnvironment(home:string):NodeJS.ProcessEnv{
return{...process.env,HOME:home,GIT_CONFIG_NOSYSTEM:'1',GIT_CONFIG_GLOBAL:process.platform==='win32'?'NUL':'/dev/null',GIT_TERMINAL_PROMPT:'0'};
}
type FileIdentity={dev:bigint;ino:bigint};
function fileIdentity(file:string):FileIdentity{const stat=fs.statSync(file,{bigint:true});return{dev:stat.dev,ino:stat.ino};}
type FileIdentity={path:string;dev:bigint;ino:bigint};
function canonicalFile(file:string):string{
const resolved=fs.realpathSync.native(file);
return process.platform==='win32'?resolved.replace(/^\\\\\?\\UNC\\/i,'\\\\').replace(/^\\\\\?\\/,'').toLowerCase():resolved;
}
function fileIdentity(file:string):FileIdentity{const stat=fs.statSync(file,{bigint:true});return{path:canonicalFile(file),dev:stat.dev,ino:stat.ino};}
function isFileIdentity(candidate:unknown,expected:FileIdentity):boolean{
if(path.basename(String(candidate)).toLowerCase()!=='tracked.txt')return false;
try{const current=fs.statSync(String(candidate),{bigint:true});return current.isFile()&&current.dev===expected.dev&&current.ino===expected.ino;}catch{return false;}
try{const file=String(candidate),current=fs.statSync(file,{bigint:true});return canonicalFile(file)===expected.path&&current.dev===expected.dev&&current.ino===expected.ino;}catch{return false;}
}
function fixture(){