Skip to content

v2.6.0 — Code-Review Backlog Cut

Choose a tag to compare

@Chepko932 Chepko932 released this 08 May 20:45
· 23 commits to main since this release

OctoOffice v2.6.0

Released: 2026-05-08
Type: Minor release (security + architecture + testing)

TL;DR

Closes the entire 2026-05-07 code-review backlog: 41/41 high-and-critical
issues
across five waves, plus three transitive-dependency security overrides.
Backend test coverage expands from 35 % statements to 39 % statements / 72 %
functions / 72 % branches; backend test count grows from ~1900 to 2492.

No breaking API changes. Existing runtime contracts preserved.

Overview by Stream

Wave 1 — Critical security (4 issues)

Already shipped via hotfixes between v2.5.2 and this cut:

  • Shell-injection prevention in GIT_ASKPASS (#51).
  • Whole-string validation for CLI API keys (#53).
  • safeCommunityPath containment for default_workspace (#88).
  • /api/github/clone containment to home root (#54).

Wave 2 — Frontend & Accessibility (7 issues)

  • #52 MobileBottomSheet exposes role="dialog", aria-modal, focus-trap, and Escape-to-close.
  • #60 useMobile hook is SSR-safe and falls back to legacy addListener on older Safari.
  • #61 Pixi.js office canvas honors prefers-reduced-motion for wandering, walk-cycle, head-bob, typing-jitter, and pulse animations.
  • #62 Light theme --text-muted darkened to #52525b (≥7:1) and --accent-text to #047857 (≥5.25:1) to meet WCAG AA.
  • #63 Topbar nav, ghost buttons, + NEW MISSION, and pack selector enlarged to ≥36×36 (WCAG 2.5.8).
  • #64 Pixi office canvas exposes a visually-hidden DOM-twin department list revealed on :focus-within.
  • #65 App.tsx extracted into RoomThemesContext, DecisionInboxContext, and useOfficePackBootstrap; handlers memoized with useCallback.

Wave 3 — Security & Architecture (8 issues)

  • #89 SSRF guard pre-resolves DNS and pins the validated IP to a request-scoped undici.Agent dispatcher, closing the TOCTOU window. Reviewer-driven follow-up (#89 review #2): redirect loop re-validates every hop with a 5-hop limit and strips Authorization on cross-origin redirects.
  • #90 pnpm.overrides pin ip-address >= 10.1.1 (GHSA-v2v4-37r5-5v8g, XSS in Address6).
  • #91 pnpm.overrides pin hono >= 4.12.16 (GHSA-9vqf-7f2p-gf9v bodyLimit bypass + GHSA-69xw-7hcm-h432 JSX injection).
  • #55 ComfyUI HTTP helpers moved into connectors/built-in/comfyui/http.ts; legacy modules/workflow/comfyui/* exports are @deprecated re-exports. New architecture test enforces one-way layering.
  • #56 graph-builder.ts moved into server/packs/; orchestration now imports from packs/, breaking the mutual recursion. New architecture test enforces packs/ does not import from modules/workflow/.
  • #57 Approve handler stops the active agent process before mutating phase state, mirroring /reset and /reset-from.
  • #58 routeFollowUpViaCeo returns a discriminated RoutingResult ({decision, source} | {decision: null, reason}) with metrics emitted per branch (ceo.followup.routing).
  • #59 Phase-approve re-run uses deps.runTask instead of an HTTP self-loop, removing dead SESSION_AUTH_TOKEN/port logic.

Wave 4 — TypeScript Strictness (10 issues)

  • #78 Drop Promise<any> casts from src/api/messaging-runtime-oauth.ts; six functions now use typed post<T>() and resp.json() as Promise<T> matching their declared signatures.
  • #79 CrossDeptCooperationDeps replaced with concrete Pick<RuntimeContext, …> interface (32 fields).
  • #80 ReportRoutingDeps, ReviewConsensusDeps, and OutcomeContext replaced with concrete Pick<RuntimeContext, …> interfaces.
  • #81 PUT /api/settings enforces a 32-key allowlist; unknown keys return 400 unknown_setting_key. GET /api/settings filters internal keys (access_password_hash, mcp_servers, remote_session:*, etc.) so they cannot leak via authenticated round-trip.
  • #82 PUT /api/ops/workflow-packs/:key/positions validates the body with a strict Zod schema (≤200 phases, finite numbers, strictObject for {x, y}).
  • #83 db: any replaced with structural DbLike across 13 deps interfaces. New shared server/types/db-like.ts.
  • #84 useAgentLayer per-agent sprite-load failures isolated via Promise.all(agents.map(...)) and logged.
  • #85 LLM API responses validated with per-provider Zod schemas (Anthropic, Google, OpenAI). Throws typed LlmResponseParseError on shape mismatch instead of silently returning empty strings.
  • #86 CSRF guard res parameter typed as express.Response across execution-control, api-providers, operations, and mcp-servers.
  • #87 cli-auth route handlers use catch (err: unknown) + toErrorMessage(err).

Wave 5 — Test Coverage (11 issues)

Module Coverage gain
message-idempotency.ts 0 % → 99 % statements (#66)
oauth/encryption (encrypt/decrypt) 0 % → 100 % branch (#67)
decision-inbox-routes.ts 23.66 % → 96.79 % (#68)
opencode adapter 30.95 % → 100 % (#69)
context-sharing.ts 0 % → 100 % (#70)
planned-approval.ts 0 % → 100 % statements / 80.76 % branch (#71)
planning-archive-tools.ts 0 % → 100 % statements / 90.21 % branch (#72)
oauth/helpers (PKCE/redirect) 27.27 % func → 100 % branch (combined with #67, #73)
oauth-runtime.ts 0 % → 95.91 % (#74)
cli-runtime.ts (agent spawn path) 0 % → 86.92 % (#76)
ceo-orchestrator.ts (tick loop) 18.55 % → 97.30 % (#77)

Plus a Vitest regression suite that asserts every mergeSettingsWithDefaults({}) key is in ALLOWED_SETTING_KEYS, preventing future drift.

Bonus — fast-uri override

Surfaced during Wave 5 CI: pnpm audit flagged fast-uri <= 3.1.0 (GHSA-q3j6-qgpj-74h6, path traversal via percent-encoded dot segments) pulled transitively via @modelcontextprotocol/sdk > ajv. Pinned via pnpm.overrides to >= 3.1.1.

Reviewer Findings Resolved Mid-Stream

The release also incorporates reviewer-driven follow-up commits:

  • PR #100 / #61 review: Reduced-motion gate extended to per-tick path-following so sprites freeze entirely (not just stop typing-jitter).
  • PR #101 / #63 review: Nav tabs, + NEW MISSION, and pack selector all bumped from 32/28 px to ≥36 px so the title commitment matched the diff.
  • PR #98 / #64 review: .sr-only-focusable utility introduced; the department panel becomes a visible, framed widget when any inner button has focus.
  • PR #103 / #89 review #1 (TOCTOU): safeFetch() helper introduced with pinned-IP undici.Agent.
  • PR #103 / #89 review #2 (redirects): Redirect loop re-validates every hop, hop limit 5, Authorization stripped on cross-origin.
  • PR #110 / #59 review: Build break from required runTask dep fixed in test harnesses.
  • PR #115 / #81 review: GET filter added so internal keys cannot round-trip back through PUT.
  • PR #123 / #67 review: Encryption tests made .env-independent via vi.doMock("…/runtime.ts").
  • PR #129 / #73 review: sanitizeOAuthRedirect rejects protocol-relative // redirects.

Known Follow-ups

Captured as low-priority issues for a later cycle:

  • #134 encryptSecret("") produces a payload that cannot round-trip; current behavior is pinned by a regression test.
  • #135 sanitizeOAuthRedirect rejects IPv6 [::1] redirects because URL.hostname returns the bracketed form; equality check should normalize.
  • #136 Vitest scope includes type-only files (server/types/*) which suppress reported statement coverage; adding them to coverage.exclude is a one-line fix.

Numbers

Metric v2.5.2 v2.6.0
Open code-review issues 41 0
Backend test count ~1900 2492 (+30 %)
Backend statement coverage 35.47 % 39.34 %
Backend branch coverage not measured 71.78 %
Backend function coverage not measured 72.97 %
Frontend test count 217 270 (+24 %)
pnpm audit --prod --audit-level=high 4 advisories 0 advisories

Upgrade Notes

No migrations required. After pulling:

pnpm install --frozen-lockfile
pnpm run setup

Existing OAuth tokens, settings rows, and pack positions continue to work without
re-encryption or schema changes.