EdgeWorker test origin

Origin for testing the Transmit Security injection EdgeWorker. Open these through the Akamai edge hostname.

Pages

RouteExpected EdgeWorker behavior
/utf8.htmlInjected at the end of <head>.
/no-charset.htmlInjected (treated as UTF-8).
/cp1252.htmlPassed through untouched: header names windows-1252.
/cp1252-labelled-utf8.htmlDocumented limitation: invalid UTF-8 labelled utf-8; observe (rewriter may fail).
/not-html.htmltext/html that is not HTML: passed through unchanged (no <head>/<body> to append to).
/xhtml.xhtmlapplication/xhtml+xml: passed through (content-type is not text/html).
/no-transform.htmlPassed through untouched: Cache-Control: no-transform.

Markup

RouteExpected EdgeWorker behavior
/amp.htmlNo tags: <html amp>.
/amp-bolt.htmlNo tags: <html ⚡>.
/no-head.htmlInjected at the end of <body> (no <head> element).
/implicit-head.htmlObserve: head content with no <head> tags at all (browser implies one).
/no-head-close.htmlObserve: <head> is never closed. If nothing is injected, the body fallback was already disabled.
/uppercase-head.htmlInjected: tags are upper case.
/head-attrs.htmlInjected: <head> carries attributes.
/head-decoys.htmlInjected once, at the real </head>: decoys sit in a script, a comment and an attribute.
/bom.htmlInjected: UTF-8 byte order mark before the doctype.
/xml-prolog.htmlInjected: <?xml?> prolog before the doctype.
/large-head.htmlInjected at </head>, which is ~60 KB into the stream (chunk boundaries, CPU).
/sdk-present.htmlPage already has the loader tag (same id): observe double load / double init.
/sdk-other.htmlPage loads the SDK itself with its own init: two initializations.

Size and time

RouteExpected EdgeWorker behavior
/big.html?mb=3 (default) injected; ?mb=6 is over the 5 MB sub-request cap: observe.
/slow.html?ms=9000: headers after N ms. Over the EdgeWorker timeout it fails (logged, failover); under it, injected.
/slow-body.html?ms=500&chunks=20: headers at once, then a chunk every N ms. Tests wall time while streaming.
/chunked.htmlInjected: chunked transfer, no Content-Length from the origin.
/hangNever responds. The EdgeWorker times out: logged, failover.
/resetConnection dropped without a response: logged, failover.

Encodings

RouteExpected EdgeWorker behavior
/gzip.htmlOrigin forces gzip despite accept-encoding: identity. httpRequest decodes it: injected.
/br.htmlOrigin forces br despite accept-encoding: identity. httpRequest decodes it: injected.
/deflate.htmlOrigin forces deflate despite accept-encoding: identity. httpRequest decodes it: injected.
/deflate-raw.htmlOrigin forces deflate-raw despite accept-encoding: identity. httpRequest decodes it: injected.
/zstd.htmlzstd is not decoded by Akamai: passed through with its Content-Encoding (501 here if Node lacks zstd).

Status and headers

RouteExpected EdgeWorker behavior
/status//status/404, /status/500, /status/204: HTML body with that status, passed through (not 200).
/redirect?code=302&to=/utf8.html: passed through as the redirect (httpRequest does not follow it).
/etag.htmlInjected, with the strong ETag turned weak (W/). ?weak=1 for a weak one. If-None-Match / If-Modified-Since get a 304.
/digest.htmlInjected, with Content-Digest / Repr-Digest / Content-MD5 removed.
/vary.htmlInjected. Check Vary: Accept-Encoding should go (uncompressed body), Cookie and Accept-Language should stay.
/cookies.htmlInjected, with all three Set-Cookie headers intact.
/big-headers.html?kb=9: response headers totalling N KB. Over 8 KB the sub-request fails: logged, failover.
/range.htmlSupports Range (bytes=0-99 -> 206). A Range request must not reach the EdgeWorker; if it does, passed through.

CSP

RouteExpected EdgeWorker behavior
/csp-nonce.htmlInjected tags carry the page's nonce (strict-dynamic policy, no connect-src: nothing else restricted).
/csp-strict.htmlInjected, but the browser blocks the SDK (script-src) and collection (connect-src via default-src). Candidate for the warn log.
/csp-allow.htmlInjected and working: SDK host in script-src, collection host in connect-src, nonce for the init script.
/csp-script-src-elem.htmlThe nonce from script-src-elem is used, not the one in script-src.
/csp-multi.htmlTwo enforced policies with different nonces: the first nonce is reused, the second policy blocks. Known limitation.
/csp-report-only.htmlReport-Only nonce is reused (no enforced policy). Nothing is blocked.
/csp-meta.htmlCSP in a <meta> tag, no header: injected, browser blocks the SDK, and the EdgeWorker cannot see the policy.

Requests

RouteExpected EdgeWorker behavior
/echoAny method. JSON of the request as the origin received it: look for host, x-ts-edge-subrequest, true-client-ip, pragma, accept-encoding.
/echo.htmlSame as /echo but as an HTML page, so it goes through injection: see what the sub-request carried.
/method.htmlAny method returns a 200 HTML page naming it. GET injected; others must not reach the EdgeWorker (if they do: passed through).
/form.htmlForms: POST rendering a page, POST with post-redirect-get, and a file upload (1 MB sub-request body limit).
/form-renderPOST: 200 HTML rendered from the POST. Injected only if the rule sends POST to the EdgeWorker.
/form-prgPOST: 303 to /utf8.html?posted=1. The redirect passes through; the following GET is injected.
/uploadPOST/PUT any size: text/plain with the byte count. Over 1 MB through the EdgeWorker fails (if the rule lets uploads in).
/iframe-parent.htmlParent injected; the child (Sec-Fetch-Dest: iframe) not. Open in a browser.
/iframe-child.htmlNo tags when loaded as a frame; injected when opened directly.
/fetch-parent.htmlPage fetch()es /utf8.html and reports whether the fetched copy has the loader tag: it must not.
/prefetch-parent.htmlLink prefetch of /utf8.html and a Speculation Rules prerender of /prerender-target.html. Check the log for Sec-Purpose.
/prerender-target.htmlShows whether it was prerendered. If the SDK ran before activation, collection happened for an unseen page.

Flaky origin

RouteExpected EdgeWorker behavior
/flaky.html?fail=2: the first N requests to this URL get a 503 HTML page, then 200. Reset with a new query.
/ghost400.html?fail=1: the first N requests get a 400 with Server: AkamaiGHost (what the retry logic looks for), then 200.

Log

RouteExpected EdgeWorker behavior
/logJSON: the last 200 requests this origin received, newest first.
/log.htmlThe same as a table.
/log/clearClears the log and the flaky counters.
/This index: injected like any page.

Request scenarios (curl, through the edge)

Add -H 'Pragma: akamai-x-ew-debug' to see the EdgeWorker's debug headers. Then check /log.html for what the origin received.

# page load, injected
curl -si https://EDGE/utf8.html | grep -c 'id="tsd-loader"'          # expect 1
# not page loads: no EdgeWorker (rule) or no tags
curl -si -H 'Sec-Fetch-Mode: cors' -H 'Sec-Fetch-Dest: empty' https://EDGE/utf8.html
curl -si -H 'Sec-Fetch-Dest: iframe' https://EDGE/utf8.html
curl -si -H 'HX-Request: true' https://EDGE/utf8.html
curl -si -H 'Range: bytes=0-99' https://EDGE/range.html            # 206, untouched
curl -sI https://EDGE/utf8.html                                     # HEAD
curl -si -X POST -d q=1 https://EDGE/method.html                   # POST
curl -si -X PATCH https://EDGE/method.html                         # unsupported sub-request verb
curl -si -H 'Accept: application/json' https://EDGE/utf8.html      # no text/html in Accept
# caching (purge first): fill with a non-page request, then load as a page: must be injected
curl -s -H 'Sec-Fetch-Mode: cors' https://EDGE/utf8.html >/dev/null; curl -s https://EDGE/utf8.html | grep -c 'id="tsd-loader"'
# parallel loads after a purge: every response exactly one tag
for i in $(seq 20); do curl -s https://EDGE/utf8.html | grep -c 'id="tsd-loader"' & done; wait
# conditional
curl -si -H 'If-None-Match: W/"TAG"' https://EDGE/etag.html
# what the origin saw
curl -s https://EDGE/echo.html | sed -n '//p'

Loader tag: unknown (inline script blocked?)