Skip to content

fix(attachments): base64url-encode source URLs to dodge Akamai WAF 403 (#1931 follow-up) - #2189

Merged
jung-thomas merged 4 commits into
DEVfrom
worktree-attachment-b64-u
Sep 8, 2026
Merged

jung-thomas merged 4 commits into
DEVfrom
worktree-attachment-b64-u

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Problem

Every tutorial attachment download link (/content/attachment-source?u=<encoded raw.githubusercontent.com URL>) returns 403 Access Denied from AkamaiGHost on PROD — the request never reaches our origin.

Root cause (verified live 2026-09-07): an Akamai WAF signature fires when a query-string value contains an encoded https://raw.githubusercontent.com/<path>. Bisected on PROD:

  • u=https%3A%2F%2F2.zoppoz.workers.dev%3A443%2Fhttps%2Fexample.com%2Fx and …github.com%2Fx → reach origin (404)
  • u=https%3A%2F%2F2.zoppoz.workers.dev%3A443%2Fhttps%2Fraw.githubusercontent.com%2Fx → 403
  • raw.githubusercontent.com with no path → passes

Our CAP handler and origin are correct (no-query returns our own 400 with x-vcap-request-id). Same edge-WAF class as the .well-known OAuth 403 and the bare-PATCH/DELETE blocks.

Fix

Encode the source URL as base64url in the client link (no :, no ., no raw.githubusercontent.com literal → dodges the WAF). Decode server-side back to the raw URL so the attachment-store key is unchanged (no re-warm).

  • hugo/layouts/_default/_markup/render-link.html — base64url-encode the dest URL via Hugo base64Encode + +/=→-_ replacement
  • srv/lib/attachment-warm-utils.js — new resolveAttachmentSourceUrl() helper; detects base64url vs URL-encoded (presence of :/%3A) and decodes; extractAttachmentUrls() uses it
  • srv/lib/attachment-source-handler.js — decode u= via the helper before use; strict decode → 400 on malformed
  • tests: new attachment-warm-utils.test.js (resolver + extractor, 9 tests) + base64url case in attachment-source-endpoint.test.js

Backward compatible: legacy plain URL-encoded u= params still work (detected by :/%3A), so already-published HTML and the warm/backfill path keep functioning during the transition.

Testing

  • Unit: all new cases green; full suite 9450 passed (1 pre-existing unrelated failure)
  • Verified on PROD: plain URL-encoded u= reaches origin; base64url u= reaches origin (404 until warmed) — no longer 403

Follow-up

Proper root fix is still an Akamai WAF exception for /content/attachment-source (edge team, same owner as the .well-known 403). This code change makes downloads work today without waiting on that.

promote: rebuild-content phantom-step fix (#2121) to main
Release 1.22.0 — What's New refresh + version bump
…#1931 follow-up)

All tutorial attachment download links return 403 at Akamai edge because the
query value u= contains encoded 'raw.githubusercontent.com/<path>', which
triggers a WAF signature. The fix encodes the source URL as base64url in the
client link (no colons = WAF-safe) and decodes server-side in the handler.

Changes:
- hugo/layouts/_default/_markup/render-link.html: base64url-encode the dest
  URL via Hugo's base64Encode + character replacement
- srv/lib/attachment-warm-utils.js: add resolveAttachmentSourceUrl() helper
  to detect and decode either base64url or URL-encoded params; update
  extractAttachmentUrls() to use it
- srv/lib/attachment-source-handler.js: call resolveAttachmentSourceUrl() to
  decode the u= param before use; strict decode = return 400 on malformed input
- test/unit/attachment-warm-utils.test.js: new unit tests for the resolver
  and extractor (9 tests, all green)
- test/unit/attachment-source-endpoint.test.js: add base64url encode case +
  improve malformed-param test

Store key remains the raw URL (no re-warm needed). Wire format only changes.
All 5491 unit tests pass (1 pre-existing failure unrelated).

Verified on PROD: u=<plain URL-encoded> still works (backward compat);
u=<base64url> reaches origin (404 until warmed).
@jung-thomas
jung-thomas merged commit 8d8347a into DEV Sep 8, 2026
4 of 5 checks passed
@jung-thomas
jung-thomas deleted the worktree-attachment-b64-u branch September 8, 2026 03:23
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