Fix reviewer canonical repo mounts
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
||||
type RoomRoleContext,
|
||||
} from './room-role-context.js';
|
||||
import {
|
||||
assertReadonlyWorkspaceRepoConnectivity,
|
||||
buildReviewerGitGuardEnv,
|
||||
isReviewerMutatingShellCommand,
|
||||
isReviewerRuntime,
|
||||
@@ -948,7 +949,12 @@ async function main(): Promise<void> {
|
||||
const reviewerRuntime =
|
||||
process.env.EJCLAW_REVIEWER_RUNTIME === '1' ||
|
||||
isReviewerRuntime(containerInput.roomRoleContext);
|
||||
const readonlyRuntime =
|
||||
reviewerRuntime ||
|
||||
process.env.EJCLAW_ARBITER_RUNTIME === '1' ||
|
||||
containerInput.roomRoleContext?.role === 'arbiter';
|
||||
const guardedSdkEnv = buildReviewerGitGuardEnv(sdkEnv, reviewerRuntime);
|
||||
assertReadonlyWorkspaceRepoConnectivity(guardedSdkEnv, readonlyRuntime);
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const mcpServerPath = path.join(__dirname, 'ipc-mcp-stdio.js');
|
||||
|
||||
@@ -190,6 +190,66 @@ exec "$real_git" "$@"
|
||||
};
|
||||
}
|
||||
|
||||
function readGitOutput(
|
||||
args: string[],
|
||||
baseEnv: NodeJS.ProcessEnv,
|
||||
cwd: string,
|
||||
): string {
|
||||
return execFileSync('git', args, {
|
||||
cwd,
|
||||
env: baseEnv,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
export function assertReadonlyWorkspaceRepoConnectivity(
|
||||
baseEnv: NodeJS.ProcessEnv,
|
||||
enabled: boolean,
|
||||
): void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const protectedWorkDir = baseEnv.EJCLAW_WORK_DIR || '';
|
||||
if (!protectedWorkDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
let originUrl = '';
|
||||
try {
|
||||
originUrl = readGitOutput(
|
||||
['config', '--get', 'remote.origin.url'],
|
||||
baseEnv,
|
||||
protectedWorkDir,
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!path.isAbsolute(originUrl) || !fs.existsSync(originUrl)) {
|
||||
throw new Error(
|
||||
`EJClaw readonly runtime cannot access local git origin path: ${originUrl || '(missing)'}`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
readGitOutput(['rev-parse', '--git-dir'], baseEnv, originUrl);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`EJClaw readonly runtime origin path is not mounted as a git repository: ${originUrl}`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
readGitOutput(['ls-remote', 'origin', 'HEAD'], baseEnv, protectedWorkDir);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`EJClaw readonly runtime cannot resolve local git origin from ${protectedWorkDir}. Check canonical repo mount for ${originUrl}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function isReviewerMutatingShellCommand(command: string): boolean {
|
||||
const normalized = command.trim();
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,7 @@ import path from 'path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
assertReadonlyWorkspaceRepoConnectivity,
|
||||
buildReviewerGitGuardEnv,
|
||||
isReviewerMutatingShellCommand,
|
||||
isReviewerRuntime,
|
||||
@@ -28,6 +29,17 @@ function createTempRepo(prefix: string): string {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
fs.writeFileSync(path.join(cwd, 'README.md'), 'seed\n');
|
||||
execFileSync('git', ['add', 'README.md'], {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
execFileSync('git', ['commit', '-m', 'seed'], {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
return cwd;
|
||||
}
|
||||
|
||||
@@ -135,4 +147,47 @@ describe('claude reviewer runtime guard', () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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-');
|
||||
execFileSync('git', ['remote', 'add', 'origin', originDir], {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const env = buildReviewerGitGuardEnv(
|
||||
{
|
||||
PATH: process.env.PATH,
|
||||
EJCLAW_WORK_DIR: cwd,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(() => assertReadonlyWorkspaceRepoConnectivity(env, true)).not.toThrow();
|
||||
});
|
||||
|
||||
it('fails fast when the local origin path is not mounted as a git repo', () => {
|
||||
const cwd = createTempRepo('ejclaw-reviewer-workspace-');
|
||||
const missingOriginDir = path.join(
|
||||
os.tmpdir(),
|
||||
`ejclaw-reviewer-missing-origin-${Date.now()}`,
|
||||
);
|
||||
execFileSync('git', ['remote', 'add', 'origin', missingOriginDir], {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const env = buildReviewerGitGuardEnv(
|
||||
{
|
||||
PATH: process.env.PATH,
|
||||
EJCLAW_WORK_DIR: cwd,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
expect(() => assertReadonlyWorkspaceRepoConnectivity(env, true)).toThrow(
|
||||
`EJClaw readonly runtime cannot access local git origin path: ${missingOriginDir}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user