fix(core): address extension context stickiness and symlink path resolution

This commit addresses two bugs identified during review:

1. Cleared the sticky `activeExtensionContext` when the standard `/plan` command is executed, ensuring subsequent prompts correctly target the default global plan directory.
2. Fixed a path resolution regression in `Storage.getPlansDir()` by constructing the fallback ENOENT path directly against the real project root. This prevents `isSubpath` validation failures and potential traversal vulnerabilities when the project root is a symlink.
This commit is contained in:
Mahima Shanware
2026-04-06 17:41:56 +00:00
parent 39a7d59b27
commit c578567488
5 changed files with 64 additions and 3 deletions
+20
View File
@@ -358,6 +358,26 @@ describe('Storage additional helpers', () => {
expectedError:
"Custom plans directory 'symlink-to-outside' resolves to '/outside/project/root', which is outside the project root '/tmp/project'.",
},
{
name: 'non-existent plan dir in a symlinked project root',
customDir: 'new-plans',
setup: () => {
vi.mocked(fs.realpathSync).mockImplementation((p: fs.PathLike) => {
const pStr = p.toString();
if (pStr === projectRoot) {
return '/private/tmp/project';
}
if (pStr.includes('new-plans')) {
const err = new Error('ENOENT') as NodeJS.ErrnoException;
err.code = 'ENOENT';
throw err;
}
return pStr;
});
return () => vi.mocked(fs.realpathSync).mockRestore();
},
expected: path.resolve(projectRoot, 'new-plans'),
},
];
testCases.forEach(({ name, customDir, expected, expectedError, setup }) => {
+2
View File
@@ -333,6 +333,8 @@ export class Storage {
) {
throw e;
}
// Construct the fallback path safely against the real project root
realResolvedPath = path.resolve(realProjectRoot, customDir);
}
if (!isSubpath(realProjectRoot, realResolvedPath)) {