diff --git a/.agentcortex/context/archive/INDEX.jsonl b/.agentcortex/context/archive/INDEX.jsonl index 3c8302c..84e1fc9 100644 --- a/.agentcortex/context/archive/INDEX.jsonl +++ b/.agentcortex/context/archive/INDEX.jsonl @@ -128,3 +128,4 @@ {"branch": "fix/dev-allowed-hosts", "classification": "quick-win", "decisions": ["OFFICE_ALLOWED_HOSTS maps to Vite server.allowedHosts only when set; never true"], "log": "fix-dev-allowed-hosts-20260927.md", "modules": ["src/utils/hostAllowlist.mjs", "server.mjs", "vite.config.mjs"], "patterns": ["shared-parser-single-source", "prod-parity-matrix-before-refactor"], "prev_sha": "cb00e04f", "shipped": "2026-09-27", "specs": ["docs/specs/engineering-audit-remediation.md"]} {"branch": "feat/avo-161-character-dialogue-depth", "classification": "feature", "decisions": ["deepened 8 roles with distinct hobbies, philosophies and quirks with open-ended ADR-007 compliance"], "log": "feat-avo-161-character-dialogue-depth-20261001.md", "modules": ["src/config/characters.json", "src/locales/en.json", "src/locales/zh-TW.json"], "patterns": ["character-enrichment", "open-ended-honesty", "1-to-1-locale-parity"], "prev_sha": "891a6f42", "shipped": "2026-10-01", "specs": ["docs/specs/dialogue-interaction-layer.md"]} {"branch": "docs/character-roster-lore", "classification": "quick-win", "decisions": ["documented 8-agent character lore, 4 interconnected narrative threads, dialogue mosaic, and playwright visual preview"], "log": "docs-character-roster-lore-20261002.md", "modules": ["docs/CHARACTER_LORE.md", "docs/specs/character-roster-lore.md", "scripts/capture-office-lore-preview.mjs", "docs/assets/office-lore-preview.png"], "patterns": ["character-lore-book", "interconnected-narratives", "playwright-visual-capture"], "prev_sha": "21a17712", "shipped": "2026-10-02", "specs": ["docs/specs/character-roster-lore.md"]} +{"branch": "fix/https-status-and-security-audit", "classification": "quick-win", "decisions": ["D-1: Remove hostname !== localhost check on HTTPS"], "log": "fix-https-status-and-security-audit-20261007.md", "modules": ["src/inference/inferStatus.js", "package.json"], "patterns": ["https-same-origin-polling", "cve-remediation"], "prev_sha": "b54ae5a0", "shipped": "2026-10-07", "specs": []} diff --git a/.agentcortex/context/archive/fix-https-status-and-security-audit-20261007.md b/.agentcortex/context/archive/fix-https-status-and-security-audit-20261007.md new file mode 100644 index 0000000..e155de2 --- /dev/null +++ b/.agentcortex/context/archive/fix-https-status-and-security-audit-20261007.md @@ -0,0 +1,152 @@ +# Work Log: fix/https-status-and-security-audit + +## Header + +- Branch: `fix/https-status-and-security-audit` +- Classification: `quick-win` +- Classified by: `Antigravity` +- Frozen: `true` +- Created Date: `2026-10-07` +- Owner: `Antigravity` +- Guardrails Mode: `Quick` +- Current Phase: `ship` +- Diff Base SHA: `1816cfc8313a2f7ba4b2c8990ec25bb94164ac7f` +- Checkpoint SHA: `ebcacea5071d51f57cea23b32bef9b6c6544a32e` +- Recommended Skills: `none` +- Primary Domain Snapshot: `none` +- SSoT Sequence: `145` + +--- + +## Session Info + +- Agent: `Gemini 3.8 Flash (High)` +- Session: `2026-10-07 17:34:00 UTC+8` +- Platform: `antigravity` +- Files Read: `15` + +--- + +## Task Description + +Remediate 2 verified defects: (1) Fix HTTPS remote deployment status polling & SSE termination in src/inference/inferStatus.js where non-localhost HTTPS origins falsely skipped polling and SSE, and (2) override source-map-js to ^1.2.2 in package.json to eliminate high-severity CVE (GHSA-68fv-2mgg-jv7q). + +--- + +## Phase Sequence + +| Phase | Status | Entered | Notes | +|---|---|---|---| +| bootstrap | completed | 2026-10-07T17:34:00+08:00 | task classified as quick-win | +| plan | completed | 2026-10-07T17:34:10+08:00 | plan gate passed | +| implement | completed | 2026-10-07T17:34:20+08:00 | inferStatus.js fixed + package.json override | +| review | completed | 2026-10-07T17:37:30+08:00 | diff verified surgical and non-breaking | +| test | completed | 2026-10-07T17:38:00+08:00 | 151 test suites, 2714 tests passed | +| handoff | skipped | — | quick-win exempt | +| ship | completed | 2026-10-07T17:58:48+08:00 | ready for archival and merge | + +--- + +## Phase Summary + +- Remediated HTTPS remote origin polling & SSE disablement in `src/inference/inferStatus.js`. +- Pinned `source-map-js` to `^1.2.2` via `package.json` overrides, bringing `npm audit` to 0 vulnerabilities. +- Added regression test `tests/httpsStatusIntegration.test.js` validating HTTPS remote domains and LAN IPs. ⚡ ACX +- ship: PASS — ebcacea5071d51f57cea23b32bef9b6c6544a32e archived to .agentcortex/context/archive/fix-https-status-and-security-audit-20261007.md + +--- + +## Gate Evidence + +- Gate: bootstrap | Verdict: PASS | Classification: quick-win | Timestamp: 2026-10-07T17:34:00+08:00 +- Gate: plan | Verdict: PASS | Classification: quick-win | Timestamp: 2026-10-07T17:34:10+08:00 +- Gate: implement | Verdict: PASS | Classification: quick-win | Timestamp: 2026-10-07T17:37:00+08:00 +- Gate: review | Verdict: PASS | Classification: quick-win | Timestamp: 2026-10-07T17:37:30+08:00 +- Gate: test | Verdict: PASS | Classification: quick-win | Timestamp: 2026-10-07T17:38:00+08:00 +- Gate: ship | Verdict: PASS | Classification: quick-win | Timestamp: 2026-10-07T17:58:48+08:00 + +--- + +## External References + +| Type | Path / URL | Notes | +|---|---|---| +| Issue | GHSA-68fv-2mgg-jv7q | source-map-js ReDoS event loop denial of service | +| Doc | docs/deployment/DEPLOYMENT.md | Nginx TLS deployment documentation | + +--- + +## Known Risk + +none + +--- + +## Decisions + +### D-1: Remove hostname !== 'localhost' check on HTTPS +- Decision: Remove `proto === 'https:' && window.location.hostname !== 'localhost'` guards from `startFilePolling` and `startSSEListening`. → local +- Reason: `/api/status` and `/api/status/stream` are relative paths resolved against the current origin. When the page is HTTPS, the requests are HTTPS (same-origin). No mixed-content is possible. Retaining `proto === 'file:'` guard for local file protocol. + +--- + +## Conflict Resolution + +none + +--- + +## Skill Notes + +none + +--- + +## Drift Log + +none + +--- + +## Review Feedback + +none + +--- + +## Red Team Findings + +none + +--- + +## Design Reference + +none + +--- + +## Observability + +none + +--- + +## Resume + +none + +--- + +## Test Gate Results + +- Command: `npm test` +- Outcome: 151 passed | 1 skipped, 2714 passed | 3 skipped + +--- + +## Evidence + +- `npm audit`: found 0 vulnerabilities (remediated GHSA-68fv-2mgg-jv7q via source-map-js@1.2.2) +- `npx vitest run tests/httpsStatusIntegration.test.js`: 3 passed (https domain & IP polling + SSE proven) +- `npm run smoke`: render-smoke PASS across 4 viewports, 0 errors; smoke:panel PASS diff --git a/.agentcortex/context/archive/ship-history-2026.md b/.agentcortex/context/archive/ship-history-2026.md index 44c8d04..36c2a77 100644 --- a/.agentcortex/context/archive/ship-history-2026.md +++ b/.agentcortex/context/archive/ship-history-2026.md @@ -70,6 +70,17 @@ Rotated 1 additional entry on 2026-09-27 (SSoT Update Sequence 141 -> 142). Rotated 1 additional entry on 2026-09-27 (SSoT Update Sequence 142 -> 143). +Rotated 1 additional entry on 2026-10-07 (SSoT Update Sequence 145 -> 146). + +--- + +### Ship-fix-audit-remediation-2026-09-24-2026-09-24 (dev server monotonic clock parity and bridge UI controls) + +- Quick-win shipped: remediated high-confidence findings F-01 (dev server clock parity) and F-05 (bridge UI interactive controls) from 2026-09-24 audit. +- vite.config.mjs now imports canonical nextSeq from statusContract.mjs to maintain single-clock invariant under concurrent dev traffic. +- public/bridge-ui.js & public/bridge.html updated with planning and awaiting-approval buttons and styles. +- Tests: 132 test files passed (2511 passed, 1 skipped); render smoke PASS; panel smoke PASS; pack smoke PASS; bundle budget PASS. Branch fix/audit-remediation-2026-09-24. + --- ### Ship-fix-avo-197-oneshot-animations-2026-09-20 (the one-shot animations actually play now) · AVO-197 diff --git a/.agentcortex/context/current_state.md b/.agentcortex/context/current_state.md index bf2aaaf..63ec092 100644 --- a/.agentcortex/context/current_state.md +++ b/.agentcortex/context/current_state.md @@ -12,9 +12,9 @@ - Task Isolation: `.agentcortex/context/work/.md` - Active Work Log Path: derive from the raw branch name using filesystem-safe normalization before any gate checks. - Workflows & Policies: `.agent/workflows/*.md`, `.agent/rules/*.md` -- **Last Updated**: 2026-10-02T00:25:00+08:00 -- **Last Verified**: 2026-10-02 -- **Update Sequence**: 145 +- **Last Updated**: 2026-10-07T17:58:48+08:00 +- **Last Verified**: 2026-10-07 +- **Update Sequence**: 146 - **ADR Index**: - docs/adr/ADR-001-vnext-self-managed-architecture.md — vNext self-managed AI architecture - docs/adr/ADR-002-multi-worktree-session-design.md — multi-worktree session isolation design @@ -157,6 +157,11 @@ ## Ship History +### Ship-fix-https-status-and-security-audit-2026-10-07 (fix HTTPS remote status polling and resolve source-map-js vulnerability) + +- Quick-win shipped: Remediated 2 verified defects: (1) Fixed HTTPS remote deployment status polling & SSE termination in src/inference/inferStatus.js where non-localhost HTTPS origins falsely skipped polling and SSE, and (2) pinned source-map-js to ^1.2.2 via package.json overrides, eliminating high-severity CVE (GHSA-68fv-2mgg-jv7q). +- Tests: 151 test suites passed (2,714 passed, 0 failed), npm audit 0 vulnerabilities, pack-smoke 4/4 assertions PASS, render-smoke PASS across 4 viewports, validate.ps1 PASS (115/115). + ### Ship-docs-character-roster-lore-2026-10-02 (character lore book, interconnected storylines, and visual preview) - Feature shipped: Authored comprehensive 8-agent Character Lore & Office Life book (`docs/CHARACTER_LORE.md`) featuring full agent profiles, desk objects, habits, and 4 interconnected narrative threads weaving character bubbles into collective storylines. Added automated Playwright capture harness (`scripts/capture-office-lore-preview.mjs`) generating high-fidelity visual asset (`docs/assets/office-lore-preview.png`). @@ -231,13 +236,6 @@ - Quick-win shipped: fixed regex `replace(/active-[\w-]+/g, '')` in `public/bridge-ui.js` to match kebab-case status names without leaving trailing `-approval` fragments; added URL param parsing support for `planning` and `awaiting-approval`. - Tests: 132 test files passed (2513 passed, 3 skipped); render-smoke and panel-smoke PASS. Branch fix/bridge-ui-status-toggle-regex merged into main (35cbfd5). -### Ship-fix-audit-remediation-2026-09-24-2026-09-24 (dev server monotonic clock parity and bridge UI controls) - -- Quick-win shipped: remediated high-confidence findings F-01 (dev server clock parity) and F-05 (bridge UI interactive controls) from 2026-09-24 audit. -- vite.config.mjs now imports canonical nextSeq from statusContract.mjs to maintain single-clock invariant under concurrent dev traffic. -- public/bridge-ui.js & public/bridge.html updated with planning and awaiting-approval buttons and styles. -- Tests: 132 test files passed (2511 passed, 1 skipped); render smoke PASS; panel smoke PASS; pack smoke PASS; bundle budget PASS. Branch fix/audit-remediation-2026-09-24. - ## Spec Index Archive > Rotated out of the live **Spec Index** on 2026-08-16 to satisfy the `check_ssot_caps.py` diff --git a/package-lock.json b/package-lock.json index a490538..76ecc39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1318,9 +1318,9 @@ "license": "ISC" }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 5b02bf7..111d0ae 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "vitest": "^4.1.11" }, "overrides": { - "esbuild": "^0.28.1" + "esbuild": "^0.28.1", + "source-map-js": "^1.2.2" } } diff --git a/src/inference/inferStatus.js b/src/inference/inferStatus.js index 9107a59..57eb542 100644 --- a/src/inference/inferStatus.js +++ b/src/inference/inferStatus.js @@ -462,17 +462,12 @@ export async function pollFileStatusOnce(fetchImpl, state, callback) { function startFilePolling(callback, baseIntervalMs = 1000, onProbe = null) { // Skip file polling when /api/status can't work: // - file:// protocol (no server) - // - HTTPS page can't fetch HTTP localhost (mixed content) if (typeof window !== 'undefined') { const proto = window.location.protocol if (proto === 'file:') { console.info('[Office] Skipping API polling (file:// protocol). Use URL hash or postMessage instead.') return () => {} } - if (proto === 'https:' && window.location.hostname !== 'localhost') { - console.info('[Office] Skipping API polling (HTTPS page cannot reach HTTP API). Use postMessage or hash instead.') - return () => {} - } } const pollingState = createFilePollingState() @@ -520,7 +515,7 @@ function startSSEListening(callback, onProbe = null, onGiveUp = null, onOpen = n if (typeof EventSource === 'undefined') return null if (typeof window !== 'undefined') { const proto = window.location.protocol - if (proto === 'file:' || (proto === 'https:' && window.location.hostname !== 'localhost')) return null + if (proto === 'file:') return null } let es = null diff --git a/tests/httpsStatusIntegration.test.js b/tests/httpsStatusIntegration.test.js new file mode 100644 index 0000000..3285596 --- /dev/null +++ b/tests/httpsStatusIntegration.test.js @@ -0,0 +1,122 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { startStatusIntegration } from '../src/inference/inferStatus.js' + +globalThis.localStorage = { + _m: {}, + getItem(k) { return this._m[k] ?? null }, + setItem(k, v) { this._m[k] = String(v) }, + removeItem(k) { delete this._m[k] }, +} + +class FakeES { + static all = [] + constructor(url) { + this.url = url + this.listeners = {} + this.closed = false + FakeES.all.push(this) + } + addEventListener(t, fn) { (this.listeners[t] ||= []).push(fn) } + close() { this.closed = true } +} + +function mkStore() { + const probes = [] + const st = { + statusSource: 'organic', + activeWorkflow: null, + externalStatus: {}, + agents: {}, + markIntegrationProbe: (p) => probes.push(Boolean(p.ok)), + clearExternalStatus: () => {}, + pruneHelpers: () => {}, + setActiveWorkflow: () => {}, + applyExternalStatus: () => {}, + } + return { store: { getState: () => st }, st, probes } +} + +describe('HTTPS Status Integration (remote deployment & TLS)', () => { + beforeEach(() => { + vi.useFakeTimers() + FakeES.all = [] + globalThis.EventSource = FakeES + }) + + afterEach(() => { + vi.useRealTimers() + delete globalThis.window + delete globalThis.EventSource + delete globalThis.BroadcastChannel + }) + + it('starts both SSE and polling when hosted on https with a remote domain', async () => { + const fetchedUrls = [] + globalThis.window = { + location: { protocol: 'https:', hostname: 'office.example.com', hash: '', search: '' }, + addEventListener() {}, removeEventListener() {}, parent: null, + } + globalThis.fetch = vi.fn(async (url) => { + fetchedUrls.push(url) + return { status: 304, ok: true, headers: { get: () => null }, text: async () => 'null' } + }) + + const { store } = mkStore() + const stop = startStatusIntegration(store) + + // Verify SSE connects with relative path to same HTTPS origin + expect(FakeES.all.length).toBe(1) + expect(FakeES.all[0].url).toBe('/api/status/stream') + + // Advance timers to trigger polling + await vi.advanceTimersByTimeAsync(1100) + expect(globalThis.fetch).toHaveBeenCalled() + expect(fetchedUrls).toContain('/api/status') + + stop() + }) + + it('starts both SSE and polling when hosted on https with 127.0.0.1 or LAN IP', async () => { + const fetchedUrls = [] + globalThis.window = { + location: { protocol: 'https:', hostname: '127.0.0.1', hash: '', search: '' }, + addEventListener() {}, removeEventListener() {}, parent: null, + } + globalThis.fetch = vi.fn(async (url) => { + fetchedUrls.push(url) + return { status: 304, ok: true, headers: { get: () => null }, text: async () => 'null' } + }) + + const { store } = mkStore() + const stop = startStatusIntegration(store) + + expect(FakeES.all.length).toBe(1) + expect(FakeES.all[0].url).toBe('/api/status/stream') + + await vi.advanceTimersByTimeAsync(1100) + expect(globalThis.fetch).toHaveBeenCalled() + expect(fetchedUrls).toContain('/api/status') + + stop() + }) + + it('skips API polling and SSE on file:// protocol', async () => { + globalThis.window = { + location: { protocol: 'file:', hostname: '', hash: '', search: '' }, + addEventListener() {}, removeEventListener() {}, parent: null, + } + globalThis.fetch = vi.fn() + + const { store } = mkStore() + const stop = startStatusIntegration(store) + + // No SSE initiated + expect(FakeES.all.length).toBe(0) + + // Advance timers — no fetch called + await vi.advanceTimersByTimeAsync(5000) + expect(globalThis.fetch).not.toHaveBeenCalled() + + stop() + }) +})