review: harden readonly git checks and lint verification
This commit is contained in:
@@ -479,12 +479,12 @@ server.tool(
|
||||
|
||||
server.tool(
|
||||
'run_verification',
|
||||
'Run a fixed verification profile directly on the host against the current repo snapshot using a restricted environment and snapshot checks. Use this instead of broad shell write access for test/typecheck/build verification.',
|
||||
'Run a fixed verification profile directly on the host against the current repo snapshot using a restricted environment and snapshot checks. Use this instead of broad shell write access for test/typecheck/build/lint verification.',
|
||||
{
|
||||
profile: z
|
||||
.enum(VERIFICATION_PROFILES)
|
||||
.describe(
|
||||
'Fixed verification profile. Runs the workspace test/typecheck/build scripts with the repo-configured package manager.',
|
||||
'Fixed verification profile. Runs the workspace test/typecheck/build/lint scripts with the repo-configured package manager.',
|
||||
),
|
||||
},
|
||||
async (args) => {
|
||||
|
||||
@@ -3,7 +3,12 @@ import path from 'path';
|
||||
import { pathToFileURL } from 'url';
|
||||
export { computeVerificationSnapshotId } from '../../../shared/verification-snapshot.js';
|
||||
|
||||
export const VERIFICATION_PROFILES = ['test', 'typecheck', 'build'] as const;
|
||||
export const VERIFICATION_PROFILES = [
|
||||
'test',
|
||||
'typecheck',
|
||||
'build',
|
||||
'lint',
|
||||
] as const;
|
||||
|
||||
export type VerificationProfile = (typeof VERIFICATION_PROFILES)[number];
|
||||
|
||||
|
||||
@@ -169,6 +169,9 @@ describe('claude reviewer runtime guard', () => {
|
||||
const env = buildReviewerGitGuardEnv({ PATH: process.env.PATH }, true);
|
||||
expect(env.PATH).toContain('ejclaw-reviewer-git-');
|
||||
expect(env.EJCLAW_REAL_GIT).toBeTruthy();
|
||||
expect(env.GIT_CONFIG_GLOBAL).toContain('global.gitconfig');
|
||||
expect(env.GIT_CONFIG_NOSYSTEM).toBe('1');
|
||||
expect(fs.existsSync(env.GIT_CONFIG_GLOBAL!)).toBe(true);
|
||||
});
|
||||
|
||||
it('prefers an executable HOME-scoped wrapper dir before tmp', () => {
|
||||
@@ -245,6 +248,30 @@ describe('claude reviewer runtime guard', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('overrides problematic git config paths so read-only git queries still work', () => {
|
||||
const cwd = createTempRepo('ejclaw-reviewer-readonly-git-');
|
||||
const env = buildReviewerGitGuardEnv(
|
||||
{
|
||||
PATH: process.env.PATH,
|
||||
HOME: cwd,
|
||||
EJCLAW_WORK_DIR: cwd,
|
||||
GIT_CONFIG_GLOBAL: path.join(cwd, '.gitconfig'),
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(env.GIT_CONFIG_GLOBAL).not.toBe(path.join(cwd, '.gitconfig'));
|
||||
expect(() =>
|
||||
execFileSync('git', ['log', '--oneline', '-1'], {
|
||||
cwd,
|
||||
env,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}),
|
||||
).not.toThrow();
|
||||
expect(fs.existsSync(path.join(cwd, '.gitconfig'))).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts a mounted local origin path that resolves as a git repo', () => {
|
||||
const originDir = createTempRepo('ejclaw-reviewer-origin-');
|
||||
const cwd = createTempRepo('ejclaw-reviewer-workspace-');
|
||||
|
||||
@@ -75,6 +75,9 @@ describe('codex reviewer runtime guard', () => {
|
||||
const env = buildReviewerGitGuardEnv({ PATH: process.env.PATH }, true);
|
||||
expect(env.PATH).toContain('ejclaw-reviewer-git-');
|
||||
expect(env.EJCLAW_REAL_GIT).toBeTruthy();
|
||||
expect(env.GIT_CONFIG_GLOBAL).toContain('global.gitconfig');
|
||||
expect(env.GIT_CONFIG_NOSYSTEM).toBe('1');
|
||||
expect(fs.existsSync(env.GIT_CONFIG_GLOBAL!)).toBe(true);
|
||||
});
|
||||
|
||||
it('prefers an executable HOME-scoped wrapper dir before tmp', () => {
|
||||
@@ -151,6 +154,30 @@ describe('codex reviewer runtime guard', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('overrides problematic git config paths so read-only git queries still work', () => {
|
||||
const cwd = createTempRepo('ejclaw-reviewer-readonly-git-');
|
||||
const env = buildReviewerGitGuardEnv(
|
||||
{
|
||||
PATH: process.env.PATH,
|
||||
HOME: cwd,
|
||||
EJCLAW_WORK_DIR: cwd,
|
||||
GIT_CONFIG_GLOBAL: path.join(cwd, '.gitconfig'),
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(env.GIT_CONFIG_GLOBAL).not.toBe(path.join(cwd, '.gitconfig'));
|
||||
expect(() =>
|
||||
execFileSync('git', ['log', '--oneline', '-1'], {
|
||||
cwd,
|
||||
env,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}),
|
||||
).not.toThrow();
|
||||
expect(fs.existsSync(path.join(cwd, '.gitconfig'))).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts a mounted local origin path that resolves as a git repo', () => {
|
||||
const originDir = createTempRepo('ejclaw-reviewer-origin-');
|
||||
const cwd = createTempRepo('ejclaw-reviewer-workspace-');
|
||||
|
||||
@@ -78,6 +78,18 @@ function createExecutableWrapperDir(baseEnv: NodeJS.ProcessEnv): string {
|
||||
);
|
||||
}
|
||||
|
||||
function prepareGitConfigSandbox(wrapperDir: string): {
|
||||
gitConfigGlobalPath: string;
|
||||
} {
|
||||
const configDir = path.join(wrapperDir, 'git-config');
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
const gitConfigGlobalPath = path.join(configDir, 'global.gitconfig');
|
||||
if (!fs.existsSync(gitConfigGlobalPath)) {
|
||||
fs.writeFileSync(gitConfigGlobalPath, '');
|
||||
}
|
||||
return { gitConfigGlobalPath };
|
||||
}
|
||||
|
||||
export function buildReviewerGitGuardEnv(
|
||||
baseEnv: NodeJS.ProcessEnv,
|
||||
reviewerRuntime: boolean,
|
||||
@@ -90,6 +102,7 @@ export function buildReviewerGitGuardEnv(
|
||||
const protectedWorkDir = baseEnv.EJCLAW_WORK_DIR || '';
|
||||
const wrapperDir = createExecutableWrapperDir(baseEnv);
|
||||
const wrapperPath = path.join(wrapperDir, 'git');
|
||||
const { gitConfigGlobalPath } = prepareGitConfigSandbox(wrapperDir);
|
||||
const blocked = [...BLOCKED_GIT_SUBCOMMANDS]
|
||||
.map((value) => `'${value}'`)
|
||||
.join(' ');
|
||||
@@ -179,6 +192,8 @@ exec "$real_git" "$@"
|
||||
...baseEnv,
|
||||
EJCLAW_REAL_GIT: realGitPath,
|
||||
EJCLAW_PROTECTED_WORK_DIR: protectedWorkDir,
|
||||
GIT_CONFIG_GLOBAL: gitConfigGlobalPath,
|
||||
GIT_CONFIG_NOSYSTEM: '1',
|
||||
PATH: `${wrapperDir}:${baseEnv.PATH || ''}`,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user