mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
fix(ingestion): contain .nuxt/imports.d.ts source resolution to the repo
A crafted `.nuxt/imports.d.ts` source such as `from '../../../../etc/passwd'` passes the project-local relative-path check but resolves outside the analyzed repo, causing fs.stat probes against arbitrary host paths. Skip any source that resolves outside repoRoot before touching the filesystem. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU
This commit is contained in:
parent
fe24618814
commit
b54128f4f6
2 changed files with 46 additions and 1 deletions
|
|
@ -187,7 +187,13 @@ async function collectImportsDts(
|
|||
|
||||
if (!isProjectLocalPath(source)) continue;
|
||||
|
||||
const resolvedFile = await resolveExtension(path.resolve(nuxtDir, source));
|
||||
// Containment guard: a crafted source (`from '../../../../etc/passwd'`)
|
||||
// passes the relative-path check but escapes the repo. Skip anything that
|
||||
// resolves outside repoRoot before touching the filesystem.
|
||||
const resolvedBase = path.resolve(nuxtDir, source);
|
||||
if (!isWithinRepo(repoRoot, resolvedBase)) continue;
|
||||
|
||||
const resolvedFile = await resolveExtension(resolvedBase);
|
||||
if (resolvedFile === null) continue;
|
||||
|
||||
const sourceFile = toRepoPosix(repoRoot, resolvedFile);
|
||||
|
|
@ -252,6 +258,12 @@ function isProjectLocalPath(source: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
/** True when `absPath` is `repoRoot` itself or lives beneath it. */
|
||||
function isWithinRepo(repoRoot: string, absPath: string): boolean {
|
||||
const root = path.resolve(repoRoot);
|
||||
return absPath === root || absPath.startsWith(root + path.sep);
|
||||
}
|
||||
|
||||
async function resolveExtension(base: string): Promise<string | null> {
|
||||
const directFile = await firstExistingFile([...FILE_EXTENSIONS.map((ext) => base + ext), base]);
|
||||
if (directFile !== null) return directFile;
|
||||
|
|
|
|||
|
|
@ -150,6 +150,39 @@ describe('loadNuxtAutoImports', () => {
|
|||
'second',
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignores imports.d.ts sources that resolve outside the repo root', async () => {
|
||||
const root = makeRepo();
|
||||
// A real file in its own temp dir OUTSIDE the repo, reachable from .nuxt only
|
||||
// by directory traversal. Its own mkdtemp dir is tracked for cleanup.
|
||||
const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-nuxt-escape-'));
|
||||
tempRoots.push(outsideDir);
|
||||
const outside = path.join(outsideDir, 'escape.ts');
|
||||
fs.writeFileSync(outside, 'export function escape() {}', 'utf8');
|
||||
|
||||
// Relative traversal from root/.nuxt to the out-of-repo file (extensionless).
|
||||
const traversal = path
|
||||
.relative(path.join(root, '.nuxt'), outside)
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/\.ts$/, '');
|
||||
writeFile(
|
||||
root,
|
||||
'.nuxt/imports.d.ts',
|
||||
[
|
||||
`export { escape } from '${traversal}'`,
|
||||
"export { useGood } from '../composables/good'",
|
||||
].join('\n'),
|
||||
);
|
||||
writeFile(root, 'composables/good.ts', 'export function useGood() {}');
|
||||
|
||||
const config = await loadNuxtAutoImports(root);
|
||||
|
||||
// The traversal source is skipped (no entry, no throw); the in-repo one resolves.
|
||||
expect(config!.clientByLocalName.has('escape')).toBe(false);
|
||||
expect(config!.clientByLocalName.get('useGood')).toMatchObject({
|
||||
sourceFile: 'composables/good.ts',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNitroServerRuntimeFile', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue