Skip to content

[spark-compete wave 5] security cross-cut (stacked) - #885

Merged
vibeforge1111 merged 13 commits into
mainfrom
spark-compete/wave5-security-crosscut
Jun 26, 2026
Merged

[spark-compete wave 5] security cross-cut (stacked)#885
vibeforge1111 merged 13 commits into
mainfrom
spark-compete/wave5-security-crosscut

Conversation

@vibeforge1111

Copy link
Copy Markdown
Owner

Spark Compete — Wave 5 (security cross-cut), stacked on spark-compete/wave3-builder-mission

Highest-value security set. Wave-5 delta only.

Commits

On-merge points

ifeoluwaaj 30 · yossweh 16

Verified: proportionate diff, no churn, no conflict markers. Compiles / tests green. Draft — gated on CI + approval.

🤖 Generated with Claude Code

Meta Alchemist and others added 13 commits June 24, 2026 13:18
Pin devalue to >=5.8.1 via package.json `overrides` to remediate a
DoS-via-sparse-array advisory. Regenerated package-lock.json so the
resolved devalue version moves 5.7.1 -> 5.8.1.

Narrowed from the original PR per maintainer review: the unrelated
`vitest ^4.0.16 -> ^4.1.0` devDependency bump was scope creep against
the security claim and was dropped.

Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consolidates a same-author path-redaction series:
- Remove tracePath from creator mission GET response (#877)
- Remove server path from mission active POST response (#876)
- Remove rawResponse field from analyze error response (#874)
- Remove x-spark-preview-root header leaking server path (#873)
- Use trusted x-real-ip / last x-forwarded-for entry for the
  rate-limiter client key (#875)

Surviving-line indentation normalized back to file style (the patches
bundled an over-indentation reflow of untouched lines).

Co-authored-by: Esc1200 <Esc1200@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Validate targetFolder in PRD bridge write stays within the workspace
  root before writing, rejecting traversal outside cwd (#862)
- Validate workingDirectory in the dispatch endpoint against the Spark
  workspace root so spawned processes cannot run with an arbitrary cwd (#861)
- Reject interpreter code-execution flags in creator-mission validation
  commands, and validate missionId format against path traversal (#869).
  Completed the flag denylist per review: added node -p/--print and
  python -m/--module alongside the original -c/--command/-e/--eval/-.

#860 (teams endpoint auth) is already satisfied by a superior
requireTeamsAuth implementation on the base branch — no change landed.

harness_core interim_until_migration for the creator-mission validation
path: re-home into Governor on migration.

Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Independent single-file hardening fixes:
- scheduler: in-flight Set so _tick cannot relaunch a record whose
  previous fire is still running (#858)
- command-runner: SIGKILL escalation timer at timeoutMs+5s, cleared on
  close and error, so a SIGTERM-ignoring child can't hang the caller (#855)
- retry-after: cap honoured Retry-After at 60s so a hostile/quota-exhausted
  upstream can't stall a mission for hours (#853)
- sync-client: cap reconnect backoff at 30s and add +/-25% jitter so a
  fleet of tabs doesn't reconnect in lockstep (#824)
- spark-harness-client: tolerate up to 3 transient status-poll failures
  before failing the mission (#823)
- events POST: dedup caller-supplied event ids within a 5m window so a
  retried POST doesn't fan out duplicate events (#851)
- brief-enricher: validate positive-numeric env overrides (#852)
- h70-skill-matcher: precompute multi-word phrase keys once at module
  load instead of per task (#872)
- canvas store: mirror sibling-tab writes via storage events, skipping
  while local edits are pending (#859)
- MissionBoard: guard NaN dates in relative-time formatting (#842)

harness_core interim_until_migration for scheduler: re-home into Governor
on migration.

Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…visibility

mission-control-relay.ts:
- Cap missionLifecycleStates / taskLifecycleStates at 5000 entries with
  oldest-insertion eviction so the process-lifetime maps can't grow
  unbounded (#854)
- Seed the dedup maps from persisted recent entries at module load so a
  restart doesn't re-broadcast already-recorded terminal lifecycle events
  to Spark ingest and webhooks (#827). Seeding routes through the bounded
  insertion path to preserve the cap invariant.
- Export isMissionControlMissionId and warn (only when a missionId is
  present) when a mission event is accepted but dropped from the board
  because its missionId doesn't match the required shape (#69)

events POST: surface boardEligible in the response so callers learn at
emit time that their event won't appear on the board (#69).

#854 and #827 overlap on the same maps via different mechanisms (cap vs
restart-seed) and compose cleanly; both land.

Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d relays

- mission-executor: after a debounced state-sync POST resolves, re-check
  the live status and DELETE the active-mission record if the mission is
  now terminal, so a cancel/complete that landed during the in-flight POST
  isn't clobbered back to "running" and resumed (#856). Adjusted per review:
  the new DELETE now passes getEventsAuthHeaders() to match the sync POST
  and clearFileSyncState DELETE in this file, so an auth-enforcing endpoint
  actually clears the stale record.
- prd-bridge/load-to-canvas: guard the lifecycle relays with an
  alreadyLoaded check (pendingRequestMeta.status === 'canvas_loaded')
  so a retried POST doesn't re-fire mission_created/task_completed to
  operators or seed the relay history twice (#850). Resolved the merge
  conflict against the base's added task_completed relay by guarding both
  relays; pendingRequestMeta is loaded fresh from the persisted file each
  request, so a retry sees the prior request's canvas_loaded status.

harness_core interim_until_migration for #856: re-home into Governor on
migration.

Co-authored-by: 4gjnbzb4zf-sudo <4gjnbzb4zf-sudo@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e/ID sites

Same-author entropy-hardening series, reduced to the disclosed security
lines per review (all undisclosed UX/gitignore/test hunks stripped — most
were already independently landed on the base branch):

- creator-mission saveCreatorMissionTrace: atomic-write temp path no
  longer embeds process.pid+Date.now() (predictable -> symlink
  pre-emption); use randomUUID() with the codebase's node:crypto import
  convention (#825)
- provider-runtime persist: same predictable temp-path fix, randomUUID()
  via node:crypto import (#826)
- canvas store: the 4 connection-ID sites switch from
  Math.random().toString(36) to crypto.randomUUID() Web Crypto global (#867)

Per review notes: took ONLY the security lines, used the node:crypto
randomUUID import (not a bare crypto. global) in the server files, and
discarded the tautological re-implementation tests.

Co-authored-by: TALLSOME24 <TALLSOME24@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface diagnostics from catch blocks that swallowed errors silently:
- access-execution-actions: spark binary lookup failure
- command-runner: dependency check + package.json parse failures
- spawner-state: file-scan entry + helper-text read failures

Resurrected from closed PR #109 and adjusted per review: re-indented the
added lines to tabs to match the file style (the PR introduced
space-indented catch blocks), and dropped the agent-event-ledger hunks
because the base already logs those two JSONL-parse failures via
warnMalformedJsonlLine. The logged error objects are binary-lookup /
JSON-parse / file-IO errors only — no request bodies, credentials, or
env secrets are surfaced.

Co-authored-by: johncrossu <johncrossu@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… compares

Pad both buffers to the longer length before timingSafeEqual so the
length comparison no longer short-circuits and leaks a timing
side-channel. The exact-length equality is still required for a true
result, but it is now evaluated only after the constant-time compare.
Applied to both hosted-ui-auth and mcp-auth constantTimeEquals.

Co-authored-by: ifeoluwaaj <ifeoluwaaj@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…flection)

The event bridge reflected any incoming Origin into
Access-Control-Allow-Origin, allowing any site to make credentialed
cross-origin requests. Gate corsHeaders behind an allowlist: loopback
dev origins are always permitted (matching mcp-auth's isOriginAllowed),
and any other origin must be explicitly listed in EVENTS_ALLOWED_ORIGINS.
The env var is resolved with the same process.env-then-dynamic-env
convention used by allowedOriginsEnvVar('EVENTS_ALLOWED_ORIGINS')
elsewhere, so configuration stays uniform across the control surfaces.

Per maintainer review, the redundant teams GET auth hunk from the
original PR is dropped — HEAD already authenticates teams GET via
requireTeamsAuth/requireControlAuth — so only the events corsHeaders
allowlist lands here.

Co-authored-by: yossweh <yossweh@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…not cwd; un-block python -m

The wave5 hardening over-reached in two places and broke legitimate runs:

- creator-mission validation rejected python -m <module>. `-m` runs a named,
  importable module on disk (e.g. `python -m pytest tests`) and never evaluates an
  inline code string, so it does not belong in the inline-eval denylist. It is the
  default manifest validation command and the planner's own invocation form. Removed
  -m/--module from DANGEROUS_INTERPRETER_FLAGS; -c/--command/-e/--eval/-p/--print/-
  still rejected.

- PRD-bridge write validated the extracted target folder against process.cwd() (the
  SvelteKit app root). The spawner writes generated projects and static proofs under
  its own state/workspace tree, so legitimate targets were rejected. Now checks
  containment against the configured Spark workspace root and the spawner state dir
  using the project's isWithinDirectory helper, honors SPARK_ALLOW_EXTERNAL_PROJECT_PATHS,
  canonicalizes through symlinks (stable /var vs /private/var), and preserves explicit
  foreign-OS absolute targets (a user's C:\... build folder) verbatim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- events.auth: the events route now also imports isMissionControlMissionId from
  mission-control-relay (board-eligibility flag). The vi.mock only exported
  relayMissionControlEvent, so the new call threw and every terminal-lifecycle
  assertion 400'd. Add a faithful isMissionControlMissionId to the mock.

- hosted-ui-auth: the rate-limit client key was hardened to trust the last
  x-forwarded-for hop (set by our proxy) instead of the spoofable leftmost entry.
  Assert the trusted hop (10.0.0.1) is used.

- creator-mission integration: the GET response intentionally no longer leaks the
  absolute server tracePath (#877 path-redaction series). Assert tracePath is
  undefined on GET (POST still returns it and is unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vibeforge1111
vibeforge1111 changed the base branch from spark-compete/wave3-builder-mission to main June 26, 2026 13:18
@vibeforge1111
vibeforge1111 marked this pull request as ready for review June 26, 2026 13:20
@vibeforge1111
vibeforge1111 merged commit 451d009 into main Jun 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants