mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
fix(git-remote): resolve relative pull origins against the repo dir, matching git -C
isTrustedLocalOrigin realpath'd a relative origin against process.cwd(), but `git -C repoPath pull` resolves relative file remotes against the repo dir — the containment guard was checking a different path than the one git actually pulls. Resolve relative origins against repoPath before the realpath containment check. Regression test mutation-checked (fails on the cwd-based resolution). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
421f892703
commit
e2fc3df9fe
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
import { execFileSync } from 'child_process';
|
||||
import { lstatSync, existsSync, readdirSync, realpathSync } from 'fs';
|
||||
import { join, sep } from 'path';
|
||||
import { isAbsolute, join, sep } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { isInternalUrl } from './url-safety.ts';
|
||||
|
||||
@@ -275,7 +275,11 @@ function pullOriginUrl(repoPath: string): string | null {
|
||||
function isTrustedLocalOrigin(repoPath: string, trustedRoot: string): boolean {
|
||||
const url = pullOriginUrl(repoPath);
|
||||
if (!url || !isLocalFileRemote(url)) return false;
|
||||
const originPath = url.startsWith('file://') ? fileURLToPath(url) : url;
|
||||
// Resolve a relative origin against the repo, matching what `git -C repoPath
|
||||
// pull` actually pulls (git resolves relative file remotes against the repo
|
||||
// dir, NOT this process's cwd) — the guard must check the same path git uses.
|
||||
const raw = url.startsWith('file://') ? fileURLToPath(url) : url;
|
||||
const originPath = isAbsolute(raw) ? raw : join(repoPath, raw);
|
||||
try {
|
||||
const real = realpathSync(originPath);
|
||||
const root = realpathSync(trustedRoot);
|
||||
|
||||
@@ -453,6 +453,25 @@ describe('pullRepo', () => {
|
||||
expect(flagBlock).not.toContain('protocol.file.allow=always');
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('relative origin resolves against the repo dir (matching `git -C`), not process.cwd()', async () => {
|
||||
// git resolves a relative file remote against the repo dir (`git -C repo
|
||||
// pull`), so the containment guard must too. The relative name exists
|
||||
// under repo (inside the trusted root) but NOT under process.cwd() — a
|
||||
// cwd-based guard would realpath-fail and stay strict.
|
||||
const repo = join(FAKE_GIT_DIR, 'pull-rel-origin');
|
||||
mkdirSync(join(repo, 'rel-origin.git'), { recursive: true });
|
||||
setMode('local-origin');
|
||||
setOrigin('rel-origin.git');
|
||||
await withEnv({ PATH: fakePath() }, async () => {
|
||||
pullRepo(repo, { allowLocalFileOrigin: true, trustedOriginRoot: FAKE_GIT_DIR });
|
||||
});
|
||||
const pullCall = readArgvLog().find(c => c.includes('pull'));
|
||||
expect(pullCall).toBeDefined();
|
||||
const flagBlock = pullCall!.slice(2, pullCall!.indexOf('pull'));
|
||||
expect(flagBlock).toEqual([...GIT_SSRF_FLAGS_LOCAL]);
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user