Skip to content

Instantly share code, notes, and snippets.

@johnf
Last active January 7, 2026 05:29
Show Gist options
  • Select an option

  • Save johnf/4e4e950dde48489d13de01273eca9273 to your computer and use it in GitHub Desktop.

Select an option

Save johnf/4e4e950dde48489d13de01273eca9273 to your computer and use it in GitHub Desktop.
OAC header injection
/**
* 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