test: replace product tests that fake the product with real-boundary tests (F)

- design: serve.test.ts drove an inline mirror server; now two tests run the
  real serve() on an ephemeral port (reload confinement, submit exit 0).
- setup-gbrain: rollback + voyage tests execute the template-extracted init
  blocks (3 sites) instead of drifted local bash copies.
- terminal-agent: internalHandler source greps replaced by a behavioral
  /internal/grant + /internal/revoke auth matrix (no/wrong/valid token).
- /health: server-security-surface and the server-auth / security-audit-r2 /
  sidebar-tabs source greps fold into one liveness-only check on the real
  body; the L4 sidecar wiring gets a behavioral /pty-inject-scan test.
- delete tautologies (browser-manager onDisconnect, memory-command #12),
  ios swiftui tap fixture self-check, memory-ingest put_page grep, detach
  source greps, sidebar-agent absence pins, dead-CSS pins + the dead CSS,
  security-audit-r2 Task 1 + the test-only meta-commands re-export,
  duplicate generated-SKILL.md checks.
- make-pdf coverage-gaps cases move into their owner test files.
This commit is contained in:
garrytan committed 2026-09-29 04:43:28 +00:00
1 parent 6dc624eda6
commit 5ec930d569
33 files changed
+638 -1830

No files matched your search

+2 -134
View File
@@ -6,24 +6,15 @@
* that could silently remove a fix without breaking compilation.
*/
import { describe, it, expect, beforeAll, afterAll, spyOn } from 'bun:test';
import { describe, it, expect, spyOn } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
// ─── Shared source reads (used across multiple test sections) ───────────────
const META_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/meta-commands.ts'), 'utf-8');
const WRITE_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/write-commands.ts'), 'utf-8');
const SERVER_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/server.ts'), 'utf-8');
// sidebar-agent.ts was ripped (chat queue replaced by interactive PTY).
// AGENT_SRC kept as empty string so the legacy describe block below skips
// without crashing module load on a missing file.
const AGENT_SRC = (() => {
try { return fs.readFileSync(path.join(import.meta.dir, '../src/sidebar-agent.ts'), 'utf-8'); }
catch { return ''; }
})();
const SNAPSHOT_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/snapshot.ts'), 'utf-8');
const PATH_SECURITY_SRC = fs.readFileSync(path.join(import.meta.dir, '../src/path-security.ts'), 'utf-8');
// ─── Helper ─────────────────────────────────────────────────────────────────
@@ -121,104 +112,6 @@ describe('Task 2: CSS value validator blocks dangerous patterns', () => {
});
});
// ─── Task 1: Harden validateOutputPath to use realpathSync ──────────────────
describe('Task 1: validateOutputPath uses realpathSync', () => {
describe('source-level checks', () => {
it('path-security.ts validateOutputPath contains realpathSync', () => {
const fn = extractFunction(PATH_SECURITY_SRC, 'validateOutputPath');
expect(fn).toBeTruthy();
expect(fn).toContain('realpathSync');
});
it('path-security.ts SAFE_DIRECTORIES resolves with realpathSync', () => {
const safeBlock = sliceBetween(PATH_SECURITY_SRC, 'const SAFE_DIRECTORIES', ';');
expect(safeBlock).toContain('realpathSync');
});
it('meta-commands.ts re-exports validateOutputPath from path-security', () => {
expect(META_SRC).toContain("from './path-security'");
expect(META_SRC).toContain('validateOutputPath');
});
it('write-commands.ts imports validateOutputPath from path-security', () => {
expect(WRITE_SRC).toContain("from './path-security'");
expect(WRITE_SRC).toContain('validateOutputPath');
});
});
describe('behavioral checks', () => {
let tmpDir: string;
let symlinkPath: string;
beforeAll(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-sec-test-'));
symlinkPath = path.join(tmpDir, 'evil-link');
try {
fs.symlinkSync('/etc', symlinkPath);
} catch {
symlinkPath = '';
}
});
afterAll(() => {
try {
if (symlinkPath) fs.unlinkSync(symlinkPath);
fs.rmdirSync(tmpDir);
} catch {
// best-effort cleanup
}
});
it('meta-commands validateOutputPath rejects path through /etc symlink', async () => {
if (!symlinkPath) {
console.warn('Skipping: symlink creation failed');
return;
}
const mod = await import('../src/meta-commands.ts');
const attackPath = path.join(symlinkPath, 'passwd');
expect(() => mod.validateOutputPath(attackPath)).toThrow();
});
it('realpathSync on symlink-to-/etc resolves to /etc (out of safe dirs)', () => {
if (!symlinkPath) {
console.warn('Skipping: symlink creation failed');
return;
}
const resolvedLink = fs.realpathSync(symlinkPath);
// macOS: /etc -> /private/etc
expect(resolvedLink).toBe(fs.realpathSync('/etc'));
const TEMP_DIR_VAL = process.platform === 'win32' ? os.tmpdir() : '/tmp';
const safeDirs = [TEMP_DIR_VAL, process.cwd()].map(d => {
try { return fs.realpathSync(d); } catch { return d; }
});
const passwdReal = path.join(resolvedLink, 'passwd');
const isSafe = safeDirs.some(d => passwdReal === d || passwdReal.startsWith(d + path.sep));
expect(isSafe).toBe(false);
});
it('meta-commands validateOutputPath accepts legitimate tmpdir paths', async () => {
const mod = await import('../src/meta-commands.ts');
// Use /tmp (which resolves to /private/tmp on macOS) — matches SAFE_DIRECTORIES
const tmpBase = process.platform === 'darwin' ? '/tmp' : os.tmpdir();
const legitimatePath = path.join(tmpBase, 'gstack-screenshot.png');
expect(() => mod.validateOutputPath(legitimatePath)).not.toThrow();
});
it('meta-commands validateOutputPath accepts paths in cwd', async () => {
const mod = await import('../src/meta-commands.ts');
const cwdPath = path.join(process.cwd(), 'output.png');
expect(() => mod.validateOutputPath(cwdPath)).not.toThrow();
});
it('meta-commands validateOutputPath rejects paths outside safe dirs', async () => {
const mod = await import('../src/meta-commands.ts');
expect(() => mod.validateOutputPath('/home/user/secret.png')).toThrow(/Path must be within/);
expect(() => mod.validateOutputPath('/var/log/access.log')).toThrow(/Path must be within/);
});
});
});
// ─── Round-2 review findings: applyStyle CSS check ──────────────────────────
describe('Round-2 finding 1: extension applyStyle blocks dangerous CSS values', () => {
@@ -298,19 +191,6 @@ describe('Round-2 finding 2: snapshot.ts annotated path uses realpathSync', () =
// traversal in browse-server's tab-state writer is covered by
// browse/test/terminal-agent.test.ts (handleTabState atomic-write tests).
// ─── Task 5: /health endpoint must not expose sensitive fields ───────────────
describe('/health endpoint security', () => {
it('must not expose currentMessage', () => {
const block = sliceBetween(SERVER_SRC, "url.pathname === '/health'", "url.pathname === '/refs'");
expect(block).not.toContain('currentMessage');
});
it('must not expose currentUrl', () => {
const block = sliceBetween(SERVER_SRC, "url.pathname === '/health'", "url.pathname === '/refs'");
expect(block).not.toContain('currentUrl');
});
});
// ─── Task 6: frame --url ReDoS fix ──────────────────────────────────────────
describe('frame --url ReDoS fix', () => {
@@ -325,9 +205,7 @@ describe('frame --url ReDoS fix', () => {
});
it('escapeRegExp neutralizes catastrophic patterns (behavioral)', async () => {
const mod = await import('../src/meta-commands.ts');
const { escapeRegExp } = mod as any;
expect(typeof escapeRegExp).toBe('function');
const { escapeRegExp } = await import('../src/path-security.ts');
const evil = '(a+)+$';
const escaped = escapeRegExp(evil);
const start = Date.now();
@@ -429,10 +307,6 @@ describe('Task 10: responsive screenshot path validation', () => {
expect(validateIdx).toBeLessThan(screenshotIdx);
});
it('results.push is present in the loop block (loop structure intact)', () => {
const block = sliceBetween(META_SRC, 'for (const vp of viewports)', 'Restore original viewport');
expect(block).toContain('results.push');
});
});
// ─── Task 11: State load — cookie + page URL validation ──────────────────────
@@ -538,12 +412,6 @@ describe('Task 17: viewport dimensions and wait timeouts are clamped', () => {
expect(block).toMatch(/Math\.min|Math\.max/);
});
it('viewport case uses rawW/rawH before clamping (not direct destructure)', () => {
const block = sliceBetween(WRITE_SRC, "case 'viewport':", "case 'cookie':");
expect(block).toContain('rawW');
expect(block).toContain('rawH');
});
it('wait case (networkidle branch) clamps timeout with MAX_WAIT_MS', () => {
const block = sliceBetween(WRITE_SRC, "case 'wait':", "case 'viewport':");
expect(block).toBeTruthy();