Skip to content

release: promote develop → main — security (cross-org IDOR #11981 + app-key scope #11943/#11958) + money fixes to prod - #12051

Merged
NubsCarson merged 175 commits into
mainfrom
develop
Jul 3, 2026
Merged

release: promote develop → main — security (cross-org IDOR #11981 + app-key scope #11943/#11958) + money fixes to prod#12051
NubsCarson merged 175 commits into
mainfrom
develop

Conversation

@NubsCarson

Copy link
Copy Markdown
Member

Promote develop → main (prod)

Carries 174 commits from develop to main, headlined by today's merged security fixes:

plus the money fixes and general hardening merged to develop since the last promote.

Safety checks (pre-merge)

  • Clean merge: git merge-tree --write-tree origin/main origin/develop → conflict-free (tree bbf5d2f442), incl. no cloud-cf-deploy.yml conflict.
  • Migrations in range: 1packages/cloud/shared/src/db/migrations/0171_press_release_domain.sql (runs fail-closed on the prod approval gate).
  • Shortlog scan: the revert/WIP hits are benign — 5161348e2a (accidental generated/asset files) is exactly reverted by 0bc446f16e (net ~zero, restored to pre-fix(ios): keep KeepAwakeBridge core JavaScriptCore-free (unblocks full-Bun device build) #12016 tree c9803c422); remaining WIP commits are test-only salvage.
  • No other promote in flight: no open PRs base=main.

Merging with a merge commit (not squash) per promote convention.

— [cloud-frontdoor]

claude and others added 30 commits July 2, 2026 20:53
…idation on UPDATE_APP/UPDATE_MONETIZATION

Three confirmed bugs from the 07-02 Fable adversarial apps-surface scan:

- MED: DEPLOY_FRONTEND claimed 'v{N} is now live' unconditionally after
  deployAppFrontend, but the deployment can settle into a 'ready' (built, not
  serving) state where activation failed/pended. Reporting that as live is a
  lie the user acts on. Now branch on deployment.status === 'active': live copy
  only when truly active; otherwise report 'built but NOT yet live (status: X)'.
- LOW: UPDATE_APP and UPDATE_MONETIZATION mutated app state without calling
  invalidateAppsCache(runtime), so the ~60s provider WeakMap cache served stale
  name/description/monetization to the next turn (documented cache-invalidation
  invariant; every other mutating action already calls it). Added the eviction.

plugin-cloud-apps: 288 pass / 0 fail; biome clean.
…ce 'meeting', plugin-meetings package + internal seams (#11856)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…1856)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… STT gate (#11856)

Registers the existing handleTranscription in the always-on models map so
useModel(TRANSCRIPTION) can arbitrate local-first with Eliza Cloud fallback
(/api/v1/voice/stt). Widens input handling to all documented param shapes
(Buffer/Blob/audioUrl/string via SSRF-guarded fetch), adds isCloudSttAvailable
(ELIZAOS_CLOUD_ENABLED || ELIZAOS_CLOUD_USE_STT) mirroring the TTS gate, and
typed CloudSttUnavailableError for clean router fallthrough.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ges in Transcripts view (#11856)

client-meetings ElizaClient methods (/api/meetings), MeetingWsEvent guards,
live confirmed+pending pane with ws-fresh poll suppression, paste-URL join bar
validated by parseMeetingUrl, platform/participant metadata on meeting records.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t join, DOM speaker attribution, humanized input, browser audio capture (#11856)

runMeetingFlow state machine + selector racing + playwright-core stealth
launch; Meet anonymous guest join/admission/removal with verbatim Vexa
selectors and reCAPTCHA-stay; vote-and-lock speaker identity; per-element
16kHz ScriptProcessor capture via exposeBinding; pluggable InputDriver
(Playwright min-jerk trajectories / Linux XTEST xdotool). 119 unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…greement-2), useModel ASR backend, hallucination filter, WAV codec (#11856)

Faithful Vexa port: per-speaker buffered windows (2s cadence, 30s cap, 15s
idle), word-prefix confirmation with double-match fallback, retry/backoff ASR
via ModelType.TRANSCRIPTION, verbatim multi-language hallucination corpus,
session mix for retained audio. 40 real-logic tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Teams: guest prejoin, live-caption enablement, caption-author speaker routing
(10s RMS ring buffer, 2s lookback), voice-level fallback, Edge-preferred
launch. Zoom: web-client join (waiting room/passcode/host-not-started),
capability-detected capture (element → PulseAudio null-sink parecord on
Linux → hard error, no silent success), active-speaker vote-and-lock.
Vexa selectors verbatim; 33 real-logic tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…S events, transcript writer + plugin assembly (#11856)

Session state machine (single bot per meeting, rooms/entities wiring),
JOIN/LEAVE/GET_MEETING_TRANSCRIPT actions, /api/meetings rawPath routes,
meeting-status + throttled meeting-transcript WS envelopes via the
connector-setup broadcastWs seam, transcript writer producing records the
existing /api/transcripts routes + Transcripts view render (golden-tested
against the view reader), knowledge mirror via documents service. Opt-in via
ELIZA_MEETINGS_ENABLED. 24 tests; plugin suite 119/119.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t-to-meeting UX (#11856)

Auto-join policy off|ask|all: relative_to_anchor(-1min) join tasks with
calendar_event subjects + per-event anchors that follow reschedules; ask mode
= approval task + after_task join; typed DispatchResult failures through the
spine's retry/escalation. meeting_join channel registered by the PA host.
Google conferenceData fix: prefer video entry point over dial-in. Calendar
views gain Send-agent button + in-meeting badge. 25 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…live transcription (#11856)

Tees each speaker's decoded PCM (per-SSRC = exact diarization) into the
plugin-meetings pipeline; transcript writer + live WS envelopes; /transcribe
start|stop slash command + DISCORD_VOICE_TRANSCRIPTS gate (default off);
existing utterance-reply, barge-in, and TTS paths untouched. 26 new tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…connector consumers (#11856)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e link (#11856)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- meeting-flow: race join + admission phases against the abort signal so a
  user-requested stop mid-join short-circuits to graceful leave (browser
  closes via adapter finally) instead of running every remaining Playwright
  step first — found by the live-LLM JOIN_MEETING trajectory.
- MeetingJoinBar: widen bot-name input so the placeholder no longer clips.
- TranscriptsView: nowrap+truncate the meeting meta row (no ragged wrap in
  the narrow sidebar).
- Add live-join-meeting scenario + PR evidence: 14 UI screenshots (desktop +
  mobile), live Cerebras trajectory (planner genuinely selected JOIN_MEETING,
  real Chromium reached Meet's guest page), backend logs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…contrast (#11864)

The WebSocket reconnecting banner rendered in document flow as a shrink-0
flex sibling of the page (App.tsx:2479), so it pushed the header + content
down on every reconnect blip. It also used bg-warn (#ff8a24) with white
text (~2:1 contrast, fails WCAG).

- Render the transient "reconnecting" state as a floating overlay pill
  (absolutely positioned, out of flow) → 0px content shift.
- Near-black text on the orange pill for ~8:1 contrast.
- Keep the persistent, actionable "connection lost" state as an in-flow bar
  (Retry/Dismiss), preserving the macOS titlebar-banner integration.
- Add ConnectionFailedBanner.test.tsx (overlay vs in-flow regression),
  a ReconnectingOverContent story, and a Playwright proof measuring the
  header Y delta (in-flow bar = 44px shift, overlay pill = 0px).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eployment docs (#11856)

launch.ts resolves headless via ELIZA_MEETINGS_HEADLESS or display autodetect
(--headless=new keeps getUserMedia/WebAudio); new platform-support.ts
(resolveMeetingRuntimeSupport / chromiumExecutable / resolveHeadlessMode) so
the plugin refuses cleanly on mobile or no-Chromium hosts and no longer
auto-enables on iOS/Android; docs/DEPLOYMENT.md covers the Eliza Cloud
container (Xvfb + PulseAudio + apt/Dockerfile) and why on-device mobile routes
to a cloud-hosted agent instead. +19 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d, target resolution, reply helper (#11856)

Dedupes the service-missing guard (requireMeetingService), the near-identical
leave/get target picker (resolveTargetSession with single-or-ambiguous vs
most-recent fallback), and the callback+return boilerplate (reply). Net −61
handler lines, behavior-identical. +59 tests incl. adversarial URL parsing
(multiple/wrapped/punctuated links, empty/non-string text, planner-nested
options) and every action error path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…m) with typed error (#11856)

requestJoin now calls resolveMeetingRuntimeSupport before launching; mobile or
no-Chromium hosts get MeetingJoinError('unsupported_host') → HTTP 422 instead
of a doomed browser start. Wires H-B's capability probe into the service.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…overage + mock audit (#11856)

Real headless Chromium drives a local fake-meeting page (per-participant
WebAudio streams) through the REAL browser capture → pipeline → transcript
writer with only a scripted ASR backend — proves capture→transcript runs
headless with no real Meet and no real ASR (18/18 assertions). +8 tests
(pipeline/writer finalize-throw fallback, world-retry, stop-aborts-and-awaits,
concurrent joins, empty-segment + audio null/present). MOCK-AUDIT.md confirms
no unit test hits a real network/model/browser.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…me upgrades

New real-Chromium ui-smoke gate tap-target-geometry-all-views.spec.ts asserts
every interactive element's rendered bbox meets the 44x44px minimum + role<->DOM
coherence across app views; all-views-interaction.spec.ts upgrades a subset of
'no page error' assertions to semantic outcomes. Test-only (no product source).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d lane

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…invalid-URL, multi-meeting (#11856)

Mock adapter + canned pipeline injected via a custom seed that overrides
MeetingService.dependencyFactory before the service starts, so the model→action
→service→transcript-writer path is proven in CI with no browser and no model
dependency (deterministic lane). 4 scenarios; happy path hand-reviewed to
produce a real ready transcript with the scripted two-speaker text. Mock
scaffolding appended additively to test-support.ts. Live browser scenario kept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ription, examples) (#11856)

Live-model routing was flaky — the planner often picked calendar/reminder/reply
over JOIN_MEETING when a meeting URL was present. Add explicit similes
(TAKE_MEETING_NOTES / TRANSCRIBE_MEETING / RECORD_MEETING / SEND_NOTETAKER /
JOIN_CALL), a description that names the URL patterns and says to prefer this
over schedule/acknowledge actions when a joinable link is present, and Zoom +
Teams examples so the planner selects it reliably.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…a finish error

The in-chat first-run wizard could loop forever with no escape. On any
finish/provision error, `seedError` re-appended the runtime CHOICE
(`${message}\n\n${RUNTIME_CHOICE}`), so a persistent failure — e.g. the
"Not found" 404 from `POST /api/first-run` — re-offered the same runtime
question indefinitely with no distinct error surface and no way out. The
"Other / configure in Settings" provider pick made it worse: it ran a local
finish that hit the same 404 and re-looped instead of ever opening Settings.

This is the UX/navigation fix (the underlying 404 is a separate backend/env
issue, out of scope):

- Finish errors now seed a DISTINCT, non-looping recovery turn
  (`first-run:error:*`) with a human message and a dedicated
  `[CHOICE:first-run id=error]`: Try again (`error:retry`, re-runs the last
  runtime's finish), Choose a different way to run (`error:restart`, re-offers a
  fresh unlocked runtime CHOICE), and Configure in Settings (`error:settings`).
- "Other / configure in Settings" (`provider:other`) now opens the Settings tab
  (`setTab("settings")`) and exits first-run (`completeFirstRun("settings")`)
  via a shared `exitToSettings` helper, latched by `completedRef` so a
  double-tap can't flip the gate twice.
- Raw terse errors ("Not found", "Failed to fetch", …) are wrapped in a clear,
  human sentence via `finishErrorMessage`.

Local-success, cloud, and needs-cloud-login paths are unchanged.

Tests: updated the affected conductor tests and added coverage for the
persistent-404 no-loop + retry + Settings escape, the cloud `error:retry`
re-run, and `provider:other` -> Settings exit. Also hardened `beforeEach` to
restore default mock implementations (clearAllMocks keeps implementations, so a
leaked mockRejectedValue would poison later tests). Full src/first-run suite:
191 passed.

Closes #11882

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lalalune and others added 7 commits July 3, 2026 15:27
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…kslash escapes (#12037)

sendMessageInChunks escapes each chunk via convertMarkdownToTelegram, and on a
MarkdownV2 parse-entity 400 the sendWithRetry fallback resent that SAME escaped
chunk with no parse_mode — so the user saw literal backslashes ("Sure\! Step
1 \- done\.") instead of clean text. The sibling editMessage fallback already
does this right (cleanText(text)).

Fix: the fallback sends cleanText(chunks[i]) — the ORIGINAL unescaped chunk —
so a parse failure degrades to clean unformatted text.

Test: new send-resilience case asserting the fallback text carries no backslash
escapes. 37/37 green; red without the fix (fallback text contains backslashes).
typecheck + biome clean. Tracked in #12030.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Makes the #12031 evidence files parseable and whitespace-clean without changing the captured evidence.
…ll-UUID choice rejection, dangling topics fragment (#12040)

- RECENT_MESSAGES provider: a message with no metadata from a sender whose
  entity row is unresolvable threw on metaData.entityName; the catch then
  collapsed the ENTIRE conversation history to 'No recent messages available'
  for the turn. Optional-chain the metadata read.
- CHOOSE_OPTION action: the taskId parameter is documented as 'Short or full
  ID of the pending choice task' but the lookup map only keyed the 8-char
  short id, so a model passing the task's real UUID got TASK_NOT_FOUND. Key
  the map by both short and full ids.
- CHARACTER provider: when the deterministically-picked topic is the only
  topic (or all remaining topics equal it), the 'is also interested in' list
  was empty and the prompt rendered the dangling fragment
  'X is also interested in '. Omit the sentence when no other topics remain.

Each fix ships a mutation-checked vitest regression test.
…10721) (#12045)

Capture a real-model LifeOps benchmark run for the #10721 PA audit closure,
using a REAL local model on-host (no proxy/mock judge, no
registerCalibratedJudgeFixture).

- Serve eliza-1-2b (gemma-4-E2B) and eliza-1-4b (gemma-4-E4B Q8) via a
  locally-built llama.cpp llama-server (CPU backend — host GPU wedged),
  and drive packages/benchmarks/lifeops-bench (Hermes adapter, --mode
  static, deterministic state-hash scoring — no hosted judge).
- Score history retained under
  .github/issue-evidence/10721-lifeops-benchmark-history/: perfect-oracle
  smoke pass@1=1.000 (validates the scorer), eliza-1-2b + eliza-1-4b smoke
  pass@1=0.000, eliza-1-2b 10-scenario calendar slice pass@1=0.000, with
  per-scenario JSON (raw agent_message, tokens, latency), stdout logs, and
  scenario-runner trajectory bundle.
- Hand-review: the 4B reasons correctly (right tool + args) but emits
  gemma-native tool syntax the Hermes adapter cannot parse; the native
  eliza-runtime path (faithful adapter) is environment-blocked on CPU
  (40k+ token PA turns exceed budget). All caveats + skipped-scenario
  reasons documented in the evidence README.
- Add SCENARIO_TURN_TIMEOUT_MS env override to the scenario-runner CLI so a
  slow local-model CPU lane can raise the 120s default per-turn budget.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… (#12047)

The embeddings route omitted affiliateCode from its reserveCredits
context — a residual of #11972 that the #11976 reserve-inclusion fix
covered on /v1/chat/completions and /v1/messages but not here. The
upfront hold was base+platform only (buffered 1.5x), while the deferred
billUsage still credited the affiliate the full attacker-set markup (up
to 1000%) as cashable redeemable earnings. With the org balance
exhausted, the settle recorded an uncollectable overage and the
affiliate credit stood — minting money the platform never collected,
repeatable per request via 2-account collusion.

Thread affiliateCode into the reserve context exactly like the covered
routes, so resolveBillableAffiliate folds the markup into the hold via
estimatedCostMultiplier and the payout is always backed by collected
money (fail-closed at reserve time).

Regression suite drives the REAL route + REAL reserveCredits/billUsage
(affiliate resolution, markup math, earnings credit all real; only
auth/embedder/pricing/ledger-reserve/writers stubbed) and asserts the
money invariant itself: reserved >= settled with the affiliate present,
plus no-header / self-referral / inactive-code guards. The key test
fails on the unfixed route (hold multiplier absent, /usr/bin/zsh.15 hold vs .10
settle).

Fixes #12017
…12049)

The chat-first onboarding composer placeholder shipped as "Pick an option
to continue" (ContinuousChatOverlay.tsx), but the shared ui-smoke helper
expectChatFirstOnboarding still asserted the stale "Tap a highlighted
option above to continue" copy, failing all onboarding-to-home specs
that route through it.

Update the spec to match the shipped copy.
@NubsCarson
NubsCarson merged commit 5b7b4c2 into main Jul 3, 2026
48 of 100 checks passed
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 602d33e9-bdf9-4918-afbb-8ac194d14abc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (1136 files found, 100 file limit)

NubsCarson added a commit that referenced this pull request Jul 4, 2026
…xports

fix(cloud): unblock the prod Worker deploy — add missing @elizaos/core stub exports (completes #12051 security promote)
lalalune added a commit that referenced this pull request Jul 4, 2026
…e stub exports (#12807)

The #12051 develop→main promote brought code that imports two @elizaos/core
symbols the Worker bundle's stub (wrangler [alias] target) does not export,
so 'wrangler deploy' esbuild fails and the API Worker never shipped:

- runWithTrajectoryPurpose — pulled in via @elizaos/shared
  email-classification; stubbed as a passthrough, same rationale as the
  existing runWithTrajectoryContext stub (no trajectory manager on the
  Worker).
- fetchWithSsrfGuard — pulled in via plugin-elizacloud transcription
  (audioUrl fetch). Stubbed as a real Worker-safe port of
  core/network/fetch-guard (http(s)-only, blocked-hostname +
  private/reserved-IP-literal rejection incl. v4-mapped IPv6, manual
  redirect following with per-hop re-validation, cross-origin credential
  stripping, spec 301/302/303 GET rewrites, timeout wiring) — NOT a bare
  fetch passthrough, so the SSRF guarantee survives if the path ever runs
  on the Worker. DNS pinning is not portable to workerd (no node:dns) and
  is the one intentional delta.

Deploy typecheck ('Verify Worker') resolves the real @elizaos/core types, so
this class of break only surfaces at the wrangler bundling step; the stub
parity test now imports and exercises both new exports so 'bun test' catches
the next one.

Unblocks the security promote (#11981 cross-org IDOR + #11943/#11958
app-key scope + money fixes) reaching the production Worker.

Co-authored-by: nubs <nubsontopgang@gmail.com>
Co-authored-by: Shaw <shawmakesmagic@gmail.com>
@NubsCarson

Copy link
Copy Markdown
Member Author

Heads-up: this promote's prod deploy FAILED at the wrangler Worker build (run 28680305905 — No matching export in "src/stubs/elizaos-core.ts" for runWithTrajectoryPurpose + fetchWithSsrfGuard; a re-run failed identically), so the money fixes in this promote (#12047, #11976/#11989, #11785) are NOT live on prod yet.

Fix is up: PR #12870 (Worker core-stub exports, #11845/#11875 drift class — dry-run build proof attached there). Note the develop deploy lane also has a second, independent blocker that must clear before the next promote ships: #12872 (build:core TS2307 @elizaos/cloud-routing in the deploy jobs).

[cloud-security]

@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

LifeOps Benchmark — eliza

Run ID: lifeops-eliza-28680301165

LifeOps Benchmark

Model: gemma-4-31b
Judge: claude-opus-4-7
Scenarios: 25
pass@1: 0.000
pass@k: 0.000
Total cost: $0.0000

Full artifacts: see the lifeops-run-eliza-28680301165 upload on this run.

@NubsCarson

Copy link
Copy Markdown
Member Author

UPDATE — the money/security fixes in this promote are LIVE on prod (supersedes my 04:52Z note above; nobody needs to re-deploy or treat this as still-broken).

Net: nothing in this promote is waiting on a deploy anymore.

— [cloud-security]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants