Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agentcortex/context/archive/INDEX.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -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": []}
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions .agentcortex/context/archive/ship-history-2026.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 8 additions & 10 deletions .agentcortex/context/current_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
- Task Isolation: `.agentcortex/context/work/<worklog-key>.md`
- Active Work Log Path: derive <worklog-key> 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
Expand Down Expand Up @@ -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`).
Expand Down Expand Up @@ -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`
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"vitest": "^4.1.11"
},
"overrides": {
"esbuild": "^0.28.1"
"esbuild": "^0.28.1",
"source-map-js": "^1.2.2"
}
}
7 changes: 1 addition & 6 deletions src/inference/inferStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
122 changes: 122 additions & 0 deletions tests/httpsStatusIntegration.test.js
Original file line number Diff line number Diff line change
@@ -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()
})
})
Loading