phase: 02_simulate_fetch_commands
Build and Push Container / build-and-push (push) Successful in 26s

All green. Final report:

**Phase 02 — final verification pass** (both tasks already in `.agents/phases/complete/02_simulate_fetch_commands/`)
- Verified: no `fetch(` remains in `src/` (grep exit 1) or in built `dist/*.js`
- Verified: `simulatedCurlOutput()` / `simulatedWgetOutput()` in `src/terminal-commands.js` (lines 63/73), wired into `src/terminal.js` curl/wget branches
- Verified: regression test `curl/wget are simulated — no real network request leaves the origin` in `tests/e2e/02-terminal.spec.js` — asserts simulated bodies, `web_navigator` toast + localStorage persistence, and zero non-`baseURL` requests
- Tests: `./build.sh` → OK; `npm test` → **46 passed, 0 failed** (≥46 required); `bash .agents/validate.sh` → **exit 0** (E2E + repo-hygiene gates)
- No linter configured beyond validate.sh (vanilla JS, LOCKED no-toolchain)
- Completion criteria: ①no `fetch(` ✅ ②simulators present ✅ ③regression tests present & passing ✅ ④build+test 46 green ✅ ⑤no behavior change (page/terminal/vim/achievements/menu suites all green) ✅
- Documentation: full README/AGENTS.md review — both already updated in working tree and consistent with the project (simulated curl/wget, validate.sh gate, Podman build flow vs Dockerfile, Node ≥20 matches `@playwright/test` engines, M-4 header gap correctly documented as pending Phase 03) — **verified consistent, no changes needed**
- Note: `PLAN.md` still says "44 tests" (now 46) but PLAN is locked — flagged for the harness/user, not edited
- Next pending phase: `03_nginx_security_headers`
This commit is contained in:
2026-10-22 23:14:49 -04:00
parent dcd066d953
commit bcfaa1cac6
21 changed files with 1423 additions and 21 deletions
+33
View File
@@ -267,6 +267,39 @@ test.describe('Fake terminal — state and special commands', () => {
await expect(page.locator('body input')).toHaveCount(1);
});
// Regression guard for audit finding M-2: curl/wget used to `fetch()` the
// URL typed by the visitor — a client-side request-forgery primitive.
// They must render simulated output and touch the network zero times.
test('curl/wget are simulated — no real network request leaves the origin', async ({ page }, testInfo) => {
// Track every request that is NOT aimed at the test origin. The origin
// is read from `baseURL` (playwright.config.js) so the two cannot drift.
const ORIGIN = testInfo.project.use.baseURL || 'http://127.0.0.1:8123';
const external = [];
page.on('request', (r) => {
if (!r.url().startsWith(ORIGIN)) external.push(r.url());
});
await typeCommand(page, 'curl https://example.com/hello');
// achievement fires on the first curl/wget (toasts auto-remove ~4.3 s)
await expect(page.locator('.toast').filter({ hasText: 'Web Navigator' })).toBeVisible();
const curlLine = terminalLines(page).nth(-2);
await expect(curlLine).toContainText('simulated body for https://example.com/hello');
await typeCommand(page, 'wget https://example.org/file.txt');
const wgetLine = terminalLines(page).nth(-2);
await expect(wgetLine).toContainText('No real network request was made');
await expect(wgetLine).toContainText('https://example.org/file.txt');
// Let the simulated 400 ms response delay (and any pre-fix fetch) settle
// before asserting that nothing ever left the origin.
await page.waitForTimeout(800);
expect(external, `requests left the origin: ${external.join(', ')}`).toEqual([]);
// The achievement itself is persisted, not just toasted.
const stored = await page.evaluate(k => JSON.parse(localStorage.getItem(k) || '[]'), 'reese-terminal-achievements');
expect(stored).toContain('web_navigator');
});
test('backspace edits the current command', async ({ page }) => {
await page.keyboard.type('hostnamex');
await page.keyboard.press('Backspace');