mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-07 08:26:11 +00:00
* feat(group): add GraphQL contract extraction * fix(group): tighten GraphQL contract guards * fix(group): complete GraphQL review hardening * fix(group): isolate bounded GraphQL reads * fix(group): harden GraphQL contract extraction
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
import * as fs from 'node:fs/promises';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { readSafe, readSafeBounded } from '../../../src/core/group/extractors/fs-utils.js';
|
|
import { cleanupTempDir } from '../../helpers/test-db.js';
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
describe('group extractor readSafe', () => {
|
|
afterEach(async () => {
|
|
await Promise.all(tempDirs.splice(0).map((dir) => cleanupTempDir(dir)));
|
|
});
|
|
|
|
it('rejects a path whose canonical target escapes through a directory symlink', async () => {
|
|
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
|
|
const outside = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-outside-'));
|
|
tempDirs.push(repo, outside);
|
|
await fs.writeFile(path.join(outside, 'secret.graphql'), 'query Secret { secret }', 'utf8');
|
|
await fs.symlink(
|
|
outside,
|
|
path.join(repo, 'linked'),
|
|
process.platform === 'win32' ? 'junction' : 'dir',
|
|
);
|
|
|
|
await expect(readSafeBounded(repo, 'linked/secret.graphql', 1024)).resolves.toBeNull();
|
|
});
|
|
|
|
it('reads a regular file within the canonical repository root', async () => {
|
|
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
|
|
tempDirs.push(repo);
|
|
await fs.writeFile(path.join(repo, 'schema.graphql'), 'query Health { health }', 'utf8');
|
|
|
|
expect(readSafe(repo, 'schema.graphql')).toBe('query Health { health }');
|
|
await expect(readSafeBounded(repo, 'schema.graphql', 1024)).resolves.toBe(
|
|
'query Health { health }',
|
|
);
|
|
});
|
|
|
|
it('rejects an oversized sparse file before reading its contents', async () => {
|
|
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
|
|
tempDirs.push(repo);
|
|
const file = await fs.open(path.join(repo, 'oversized.graphql'), 'w');
|
|
try {
|
|
await file.truncate(16 * 1024 * 1024);
|
|
} finally {
|
|
await file.close();
|
|
}
|
|
|
|
await expect(readSafeBounded(repo, 'oversized.graphql', 1024)).resolves.toBeNull();
|
|
});
|
|
|
|
it('accepts a regular file whose size is exactly maxBytes', async () => {
|
|
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
|
|
tempDirs.push(repo);
|
|
await fs.writeFile(path.join(repo, 'exact.graphql'), '12345678', 'utf8');
|
|
|
|
await expect(readSafeBounded(repo, 'exact.graphql', 8)).resolves.toBe('12345678');
|
|
await expect(readSafeBounded(repo, 'exact.graphql', 7)).resolves.toBeNull();
|
|
});
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'reads a final-file symlink whose canonical target stays inside the repository',
|
|
async () => {
|
|
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
|
|
tempDirs.push(repo);
|
|
await fs.writeFile(path.join(repo, 'schema.graphql'), 'query Health { health }', 'utf8');
|
|
await fs.symlink('schema.graphql', path.join(repo, 'schema-link.graphql'), 'file');
|
|
|
|
await expect(readSafeBounded(repo, 'schema-link.graphql', 1024)).resolves.toBe(
|
|
'query Health { health }',
|
|
);
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'rejects a final-file symlink whose canonical target escapes the repository',
|
|
async () => {
|
|
const repo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-repo-'));
|
|
const outside = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-readsafe-outside-'));
|
|
tempDirs.push(repo, outside);
|
|
const secret = path.join(outside, 'secret.graphql');
|
|
await fs.writeFile(secret, 'query Secret { secret }', 'utf8');
|
|
await fs.symlink(secret, path.join(repo, 'secret-link.graphql'), 'file');
|
|
|
|
await expect(readSafeBounded(repo, 'secret-link.graphql', 1024)).resolves.toBeNull();
|
|
},
|
|
);
|
|
});
|