feat(DIS-152): mapped project paths (host:virtual) for agents #66
2 changed files with 23 additions and 7 deletions
|
|
@ -172,23 +172,39 @@ export function createClaudeConfig(
|
|||
// Create directory structure
|
||||
fs.mkdirSync(commandsDir, { recursive: true });
|
||||
|
||||
// Base allow list — always includes workspace-local operations
|
||||
const allow: string[] = ["Read", "Write(./**)", "Edit(./**)", "Bash(git diff *)"];
|
||||
const mappings = pathMappings ?? [];
|
||||
|
||||
// Add explicit read/write/edit permissions for each mapped host path
|
||||
for (const mapping of pathMappings ?? []) {
|
||||
// Build allow list — always workspace-local, plus explicit mapped host paths.
|
||||
// When path mappings exist we use explicit Read(./**) instead of a blanket
|
||||
// Read, because a blanket Read combined with deny(../**) would silently block
|
||||
// the mapped absolute paths (deny always wins over allow in Claude Code).
|
||||
const allow: string[] = [
|
||||
"Read(./**)",
|
||||
"Write(./**)",
|
||||
"Edit(./**)",
|
||||
"Bash(git diff *)",
|
||||
];
|
||||
|
||||
for (const mapping of mappings) {
|
||||
const normalized = mapping.host.replace(/\\/g, "/");
|
||||
allow.push(`Read(${normalized}/**)`);
|
||||
allow.push(`Write(${normalized}/**)`);
|
||||
allow.push(`Edit(${normalized}/**)`);
|
||||
}
|
||||
|
||||
// Deny list — the ../** rules are only added when there are NO path mappings.
|
||||
// With mappings, deny(../**) would override the explicit host-path allows.
|
||||
// Without mappings, ../** acts as a catch-all traversal guard.
|
||||
const denyTraversal = mappings.length === 0
|
||||
? ["Read(../**)", "Write(../**)", "Edit(../**)"]
|
||||
: [];
|
||||
|
||||
const settings = {
|
||||
permissions: {
|
||||
allow,
|
||||
ask: ["Bash(git push *)"],
|
||||
deny: [
|
||||
"Read(../**)", "Write(../**)", "Edit(../**)",
|
||||
...denyTraversal,
|
||||
"Read(./.env)", "WebFetch",
|
||||
"Bash(rm -rf *)", "Bash(curl * | sh)",
|
||||
"Bash(ssh *)", "Bash(scp *)",
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ describe("identity-templates", () => {
|
|||
expect(settings.permissions.deny).toContain("Read(./.env)");
|
||||
});
|
||||
|
||||
it("settings.json permissions.allow contains Read", () => {
|
||||
it("settings.json permissions.allow contains Read(./**)", () => {
|
||||
setupAgentWorkspace(tmpDir, "test-agent", "A test agent", "chan-123");
|
||||
const settingsPath = path.join(tmpDir, ".claude", "settings.json");
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
||||
expect(settings.permissions.allow).toContain("Read");
|
||||
expect(settings.permissions.allow).toContain("Read(./**)");
|
||||
});
|
||||
|
||||
it("CLAUDE.md contains Prompt-Injection section", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue