Last active
January 7, 2026 05:29
-
-
Save johnf/4e4e950dde48489d13de01273eca9273 to your computer and use it in GitHub Desktop.
OAC header injection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * CloudFront OAC Fetch Wrapper | |
| * | |
| * When using CloudFront Origin Access Control (OAC) with Lambda Function URLs, | |
| * the client must include the SHA256 hash of the request body in the | |
| * x-amz-content-sha256 header. This allows CloudFront to properly sign | |
| * POST requests when forwarding to the Lambda Function URL. | |
| * | |
| * This module wraps globalThis.fetch to automatically add this header | |
| * for TanStack Start server function POST requests. | |
| * | |
| * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda.html | |
| */ | |
| /** | |
| * Compute SHA256 hash of a string and return as hex | |
| */ | |
| const computeSha256 = async (body: string): Promise<string> => { | |
| const encoder = new TextEncoder(); | |
| const dataBuffer = encoder.encode(body); | |
| const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer); | |
| const hashArray = Array.from(new Uint8Array(hashBuffer)); | |
| return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); | |
| }; | |
| /** | |
| * Check if a request is a TanStack Start server function POST request | |
| */ | |
| const isServerFunctionPost = (init?: RequestInit): boolean => { | |
| if (init?.method !== 'POST') { | |
| return false; | |
| } | |
| const headers = init?.headers; | |
| if (!headers) { | |
| return false; | |
| } | |
| // Check for x-tsr-serverFn header | |
| if (headers instanceof Headers) { | |
| return headers.get('x-tsr-serverFn') === 'true'; | |
| } | |
| if (Array.isArray(headers)) { | |
| return headers.some(([key, value]) => key.toLowerCase() === 'x-tsr-serverfn' && value === 'true'); | |
| } | |
| // Record<string, string> | |
| const headerRecord = headers as Record<string, string>; | |
| return headerRecord['x-tsr-serverFn'] === 'true' || headerRecord['x-tsr-serverfn'] === 'true'; | |
| }; | |
| /** | |
| * Install the CloudFront OAC fetch wrapper. | |
| * | |
| * This wraps globalThis.fetch to add the x-amz-content-sha256 header | |
| * for POST requests to TanStack Start server functions. This is required | |
| * when using CloudFront Origin Access Control (OAC) with Lambda Function URLs. | |
| * | |
| * IMPORTANT: Call this before Sentry.init() to ensure proper integration. | |
| */ | |
| export const installCloudfrontOacFetch = (): void => { | |
| // Only run on client | |
| if (typeof window === 'undefined') { | |
| return; | |
| } | |
| // Prevent double-installation | |
| if ((globalThis as unknown as { __cloudfrontOacInstalled?: boolean }).__cloudfrontOacInstalled) { | |
| return; | |
| } | |
| const originalFetch = globalThis.fetch; | |
| globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => { | |
| // Only intercept server function POST requests | |
| if (!isServerFunctionPost(init)) { | |
| return originalFetch(input, init); | |
| } | |
| // Get the body and compute hash | |
| const body = init?.body; | |
| if (typeof body !== 'string') { | |
| // FormData or other body types - let them through without hashing | |
| return originalFetch(input, init); | |
| } | |
| const hash = await computeSha256(body); | |
| // Create new headers with the hash | |
| const newHeaders = new Headers(init?.headers); | |
| newHeaders.set('x-amz-content-sha256', hash); | |
| return originalFetch(input, { | |
| ...init, | |
| headers: newHeaders, | |
| }); | |
| }; | |
| (globalThis as unknown as { __cloudfrontOacInstalled?: boolean }).__cloudfrontOacInstalled = true; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment