/archive (Archive tab) reported failing with a CORS error — needs the console line to root-cause #110
Closed
opened 2026-09-06 23:47:08 -04:00 by logan
·
4 comments
No Branch/Tag Specified
Labels
Clear labels
area/ai-pipeline
area/auth
area/billing
area/correlation
area/discord
area/frontend
area/governance
area/growth
area/infra-deploy
area/legal
area/map
area/mqtt
area/node-ota
area/osint
area/recording-audio
area/sdr-radio
area/transcription
board
board:agenda
minutes:draft
minutes:final
role:ceo
role:cfo
role:cio
role:ciso
role:cmo
role:coo
role:cto
Scene/entity extraction, summarizer, AI providers, embeddings
AuthN/Z, roles, tenancy/org scoping, API keys, credentials
Plans, entitlements, AI spend accounting and caps
Incident correlation, linking, lifecycle, severity, corr_debug
Discord bot, radio bots, voice streams, bot tokens, webhooks
Web UI: Next.js app and the edge-node local dashboard
Board process, minutes, unattended runner, agent tooling
Go-to-market, outreach, cold email, discovery conversations
CI/CD, VM, registry, Firestore rules/indexes deploy, Terraform/Caddy
ToS, retention, licences/DAAs, CAN-SPAM, E&O, name suppression
Live map: pins, paths, tiles, legend, geocoding
Node-C2 MQTT, checkins, commands, node sweeper, dynsec
Node install, enrollment, OTA updater, image skew
External feeds: cameras, 511, ADS-B, AIS, outages, and their licences
Call capture, boundaries, PulseAudio, ring buffer, trimming, attribution
OP25 decode, SDR hardware/tuning, conventional, second SDR, mesh radio
STT, transcript correction, place verification, name accuracy
Board process: minutes and board-filed items
Needs board scoping at the next sitting; CEO removes the label once scoped
One exec pre-read, closed by CEO
CEO ruling; open until enacted
Filed by the CEO
Filed by the CFO
Filed by the CIO
Filed by the CISO
Filed by the CMO
Filed by the COO
Filed by the CTO
kind
behavior-bug
Wrong product behavior; the instrument reads it correctly
kind
measurement-bug
Corrupts the recording/instrument (debug data, evals, health checks), not behavior
priority
p0
Drop everything: prod broken, live exposure, or owner-ruled P0
priority
p1
Next up: blocks a gate, a customer, or core incident quality
priority
p2
Normal: real defect or planned work, schedule in turn
priority
p3
Low: minor, cosmetic, or someday
status
blocked
Waiting on an external party, spend, or another issue
status
in-progress
Branch or PR open against it
status
needs-decision
Waiting on an owner or board ruling before work
status
needs-info
Root cause or repro unknown; needs a reading first
type
bug
Something built is wrong: code, data, or a measurement
type
chore
Maintenance, refactor, tests, pinning, cleanup: no user-visible change
type
decision
A ruling to make or record: board minutes, owner calls, legal reviews
type
docs
Docs, runbooks, CLAUDE.md, skills, SOPs
type
feature
New capability or an extension of an existing one
type
security
Security, privacy, credential or tenant-isolation exposure
type
tracking
Tracker, checklist, initiative or punch-list spanning several changes
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: logan/server-26#110
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Owner reported the Archive page failing with a CORS error (2026-09-07). Not root-caused — needs the exact browser console error (the blocked URL + the message text).
Ruled out:
GET /calls/searchCORS is correctly configured in prod. Verified from an external host:So
CORS_ORIGINSin the prod.envis set correctly fordrb.cusano.net.Most likely causes (confirm with the console line):
fetch()es the recording from astorage.googleapis.comsigned URL (for a waveform / blob), GCS sends noAccess-Control-Allow-Originunless the recordings bucket has a CORS policy → shows as a CORS error. Fix: set a bucket CORS config allowing GET fromhttps://drb.cusano.net, or route audio through themedia.pyendpoint.media.pyserves audio withAccept-Ranges: none(already a DEFERRED item); check it also emits CORS headers if the playerfetch()es it rather than using a plain<audio src>./calls/searchneeding a Firestore composite index that isn't deployed (#13 / #51), or the endpoint not deployed at all (see punch-list #109 item 9).Next: owner pastes the red console error from
/archive; then pick the fix.Root cause confirmed (live audit 2026-09-07, fetch hook on the Archive page).
Failing request:
Content-Type: application/jsonon a bodyless GET makes it a non-simple CORS request -> browser sends anOPTIONSpreflight.OPTIONS https://api.drb.cusano.net/calls/search-> 405, zeroAccess-Control-Allow-*headers.GET https://api.drb.cusano.net/health-> 200 but also noAccess-Control-Allow-Origin./calls/searchis the sole surface that calls the REST API from the browser.Fix (real): add
CORSMiddlewaretoc2-core(allow_origins=["https://drb.cusano.net"],allow_methodsincl. GET/POST,allow_headers=["authorization","content-type"]) and ensureOPTIONSis answered before the 405.Fix (band-aid): drop the
Content-Typeheader from the/calls/searchGET indrb-frontend/lib— but it still preflights onAuthorizationalone, so the server fix is required regardless.Not a CORS misconfiguration — CORS was never set up. See
Version 5C/UI_AUDIT_0907b.md.Fixed on branch
fix/110-c2-core-cors(local commitd60fef6, pending push/PR — no push auth from this worktree).Root cause refinement:
CORSMiddlewarewas already mounted indrb-c2-core/app/main.py, butsettings.cors_originsdefaulted to["*"]and the live VM'sdrb-c2-core/.envhas no workingCORS_ORIGINSfor the browser origin (either unset, or a staleapp.value from the incident described in theinfra/ansible/.../c2-core.env.j2comments). With no matching origin Starlette emits zeroAccess-Control-*headers, so the preflight looked like there was no CORS middleware at all.Fix:
config.py:cors_originsdefault["*"]→["https://drb.cusano.net"]so a deploy that never received the env var is still correct.main.pymiddleware made explicit:allow_methods=["GET","POST","PUT","PATCH","DELETE","OPTIONS"],allow_headers=["authorization","content-type"],allow_credentials=False(auth is a Bearer header, not a cookie — keeping it False lets the explicit-origin allowlist work and makes the*+credentials footgun unrepresentable). A*entry still logs a loud ERROR..env.example: documentedCORS_ORIGINS.Origin list =
["https://drb.cusano.net"], only that. Verified againstinfra/:outputs.tfandansible/.../Caddyfile.j2both state the frontend is served on the bare domain — "only drb. and api. have public DNS records", noapp.vhost.ansible/.../c2-core.env.j2already templatesCORS_ORIGINS=["https://{{ domain }}"], confirming intent. There is noapp.drb.cusano.netfrontend to add.Tests: new
tests/test_cors.py(4 tests) drives the real app viaTestClient: preflightOPTIONS /calls/searchfromhttps://drb.cusano.net→ 200 withaccess-control-allow-originechoed and no-allow-credentials; a disallowed origin gets no allow-origin header;GET /healthfrom the app origin carries the header. Updatedtest_cors_policy.pyto the new "credentials never enabled" invariant. Sandboxed suite: 305 → 309 passing, green.Server action needed: none strictly — the new default covers prod. Recommended: set
CORS_ORIGINS=["https://drb.cusano.net"]indrb-c2-core/.envon the VM (ansible already templates this; a plaingit pulldeploy does not). If the live.envcurrently holds a wrong explicit value such as["https://app.drb.cusano.net"], it must be corrected or removed — a stale explicit value overrides the new default and reproduces this bug.Deployed in
bc3251e8(PR #120). Verified fixed in prod.OPTIONS https://api.drb.cusano.net/calls/searchwithOrigin: https://drb.cusano.netnow returns 200 withaccess-control-allow-origin: https://drb.cusano.net,access-control-allow-methods: GET, POST, PUT, PATCH, DELETE, OPTIONS,access-control-allow-headers: ...authorization, content-type. The browser preflight passes.Note the earlier root-cause note was close but not exact:
CORSMiddlewarewas already mounted; the defect wassettings.cors_originsdefaulting to["*"]with no matching live override, so Starlette emitted nothing. Default is now["https://drb.cusano.net"].The Archive page still fails, but it is no longer a CORS problem — see the new 503 finding below / #33. Closing the CORS half.
Confirmed fixed in prod (re-audit 2026-09-08 ~03:50Z). OPTIONS /calls/search returns 200 with correct Access-Control-Allow-* headers and the Archive page loads. Note: once CORS was fixed a second bug surfaced underneath — /calls/search was 503ing on a missing calls (org_id, started_at DESC) Firestore index (see #33). Both now resolved.