fix(plan): isolate plan files per session (#18757)

This commit is contained in:
Adib234
2026-02-12 14:02:59 -05:00
committed by GitHub
parent d243dfce14
commit 0b3130cec7
6 changed files with 32 additions and 9 deletions

View File

@@ -99,7 +99,7 @@ These are the only allowed tools:
- **MCP Tools (Read):** Read-only [MCP tools] (e.g., `github_read_issue`,
`postgres_read_schema`) are allowed.
- **Planning (Write):** [`write_file`] and [`replace`] ONLY allowed for `.md`
files in the `~/.gemini/tmp/<project>/plans/` directory.
files in the `~/.gemini/tmp/<project>/<session-id>/plans/` directory.
- **Skills:** [`activate_skill`] (allows loading specialized instructions and
resources in a read-only manner)

View File

@@ -336,9 +336,9 @@ describe('Policy Engine Integration Tests', () => {
// Valid plan file paths
const validPaths = [
'/home/user/.gemini/tmp/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/plans/my-plan.md',
'/home/user/.gemini/tmp/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/plans/feature_auth.md',
'/home/user/.gemini/tmp/new-temp_dir_123/plans/plan.md', // new style of temp directory
'/home/user/.gemini/tmp/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/session-1/plans/my-plan.md',
'/home/user/.gemini/tmp/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/session-1/plans/feature_auth.md',
'/home/user/.gemini/tmp/new-temp_dir_123/session-1/plans/plan.md', // new style of temp directory
];
for (const file_path of validPaths) {
@@ -365,7 +365,6 @@ describe('Policy Engine Integration Tests', () => {
'/project/src/file.ts', // Workspace
'/home/user/.gemini/tmp/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/plans/script.js', // Wrong extension
'/home/user/.gemini/tmp/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/plans/../../../etc/passwd.md', // Path traversal
'/home/user/.gemini/tmp/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/plans/subdir/plan.md', // Subdirectory
'/home/user/.gemini/non-tmp/new-temp_dir_123/plans/plan.md', // outside of temp dir
];

View File

@@ -823,7 +823,7 @@ export class Config {
(params.shellToolInactivityTimeout ?? 300) * 1000; // 5 minutes
this.extensionManagement = params.extensionManagement ?? true;
this.enableExtensionReloading = params.enableExtensionReloading ?? false;
this.storage = new Storage(this.targetDir);
this.storage = new Storage(this.targetDir, this.sessionId);
this.fakeResponses = params.fakeResponses;
this.recordResponses = params.recordResponses;

View File

@@ -154,12 +154,24 @@ describe('Storage additional helpers', () => {
expect(Storage.getGlobalBinDir()).toBe(expected);
});
it('getProjectTempPlansDir returns ~/.gemini/tmp/<identifier>/plans', async () => {
it('getProjectTempPlansDir returns ~/.gemini/tmp/<identifier>/plans when no sessionId is provided', async () => {
await storage.initialize();
const tempDir = storage.getProjectTempDir();
const expected = path.join(tempDir, 'plans');
expect(storage.getProjectTempPlansDir()).toBe(expected);
});
it('getProjectTempPlansDir returns ~/.gemini/tmp/<identifier>/<sessionId>/plans when sessionId is provided', async () => {
const sessionId = 'test-session-id';
const storageWithSession = new Storage(projectRoot, sessionId);
ProjectRegistry.prototype.getShortId = vi
.fn()
.mockReturnValue(PROJECT_SLUG);
await storageWithSession.initialize();
const tempDir = storageWithSession.getProjectTempDir();
const expected = path.join(tempDir, sessionId, 'plans');
expect(storageWithSession.getProjectTempPlansDir()).toBe(expected);
});
});
describe('Storage - System Paths', () => {

View File

@@ -20,11 +20,13 @@ const AGENTS_DIR_NAME = '.agents';
export class Storage {
private readonly targetDir: string;
private readonly sessionId: string | undefined;
private projectIdentifier: string | undefined;
private initPromise: Promise<void> | undefined;
constructor(targetDir: string) {
constructor(targetDir: string, sessionId?: string) {
this.targetDir = targetDir;
this.sessionId = sessionId;
}
static getGlobalGeminiDir(): string {
@@ -242,9 +244,19 @@ export class Storage {
}
getProjectTempPlansDir(): string {
if (this.sessionId) {
return path.join(this.getProjectTempDir(), this.sessionId, 'plans');
}
return path.join(this.getProjectTempDir(), 'plans');
}
getProjectTempTasksDir(): string {
if (this.sessionId) {
return path.join(this.getProjectTempDir(), this.sessionId, 'tasks');
}
return path.join(this.getProjectTempDir(), 'tasks');
}
getExtensionsDir(): string {
return path.join(this.getGeminiDir(), 'extensions');
}

View File

@@ -53,4 +53,4 @@ toolName = ["write_file", "replace"]
decision = "allow"
priority = 70
modes = ["plan"]
argsPattern = "\"file_path\":\"[^\"]+/\\.gemini/tmp/[a-zA-Z0-9_-]+/plans/[a-zA-Z0-9_-]+\\.md\""
argsPattern = "\"file_path\":\"[^\"]+/\\.gemini/tmp/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+/plans/[a-zA-Z0-9_-]+\\.md\""