Skip to content

Guarantee cached audio host: global response filter for all audioUrl fields - #135

Merged
uncleJim21 merged 1 commit into
masterfrom
jc/audio-host-response-filter
Aug 1, 2026
Merged

Guarantee cached audio host: global response filter for all audioUrl fields#135
uncleJim21 merged 1 commit into
masterfrom
jc/audio-host-response-filter

Conversation

@uncleJim21

Copy link
Copy Markdown
Owner

Why

Follow-up to #133 / #134. Those wrapped explicit audioUrl: fields, but several
agent-facing endpoints return audioUrl inside a dumped metadataRaw object,
which per-key wrapping can't catch. Confirmed live — these still served the raw
DigitalOcean origin (cache bypass):

  • GET /api/episode-with-chapters/:guidepisode.metadata.audioUrl (raw)
  • GET /api/fetch-adjacent-paragraphs → raw
  • plus 5 more metadata: doc.metadataRaw dump sites in jamieExploreRoutes /
    researchSessions

An agent using any of these pulls audio from outside the Cloudflare cache.

Change

Add a global response filter so no endpoint — current or future — can hand out
the raw host:

  • utils/audioFormat.js: rewriteAudioUrlsDeep(value) — recursively rewrites
    every audioUrl string field (in arrays, nested metadata, chapters, etc.) to
    the cached host. Idempotent; no-ops on non-bucket URLs and non-strings.
  • server.js: a small middleware (right after express.json()) that wraps
    res.json to run rewriteAudioUrlsDeep over every JSON body.

This is the backstop that guarantees the invariant "audioUrl is always the
cached host." The per-call-site wraps from #133/#134 remain as idempotent no-ops.
SSE endpoints use res.write (not res.json) and only carry clip IDs, so they're
unaffected.

Blast radius

Only string values under a key literally named audioUrl, and only the cascdr-…
audio-bucket host → audio.pullthatupjamie.ai. Podcaster-hosted RSS URLs,
already-cached URLs, non-audio URLs (video/images), and nulls are untouched.

Tests

  • node --check clean on server.js + utils/audioFormat.js.
  • Unit: deep rewrite over a structure mirroring the episode-with-chapters dump —
    verifies nested metadata, chapter arrays, both raw host forms (.cdn. +
    direct), podcaster-host pass-through, already-cached no-op, and null
    preservation.
  • Pre-fix live audit confirmed the raw-host leak on the endpoints above; this
    filter closes them all.

🤖 Generated with Claude Code

Endpoints that dump raw metadataRaw (episode-with-chapters,
fetch-adjacent-paragraphs, +5 more) still emitted the raw DigitalOcean host in
audioUrl, bypassing the Cloudflare cache — per-key wrapping (#133/#134) can't
catch audioUrl nested inside dumped metadata.

Add rewriteAudioUrlsDeep() + a res.json middleware that deep-rewrites every
audioUrl field in every JSON response to audio.pullthatupjamie.ai. Idempotent,
no-ops on non-bucket/podcaster/non-string values. SSE (res.write) unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@uncleJim21
uncleJim21 merged commit 54d6867 into master Aug 1, 2026
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.

1 participant