Thesis: A WAF on CloudFront only protects traffic that hits CloudFront. A Function URL with auth type NONE is still a parallel front door unless the function itself rejects callers that did not come through the distribution.
What we shipped first
The form posted to a branded intake host. CloudFront sat in front with a CloudFront-scoped web ACL. Rate limits and managed rules looked green in the console. We treated that as “intake is behind WAF.”
The Function URL under the distribution stayed public. Auth type stayed NONE so CloudFront could call it. Anyone who knew or guessed the raw URL could POST past the WAF. CORS did not stop a non-browser client.
The working shape
Keep CloudFront as the marketed path. Give the distribution a custom origin header only it can set. The function fails closed when that header is missing or wrong.
Browser
│ HTTPS → intake.example.com
▼
CloudFront + WAF
│ custom_header: X-Origin-Secret
▼
Function URL (auth NONE)
│
├── via CloudFront → secret present → 200
└── direct URL call → no secret → 403
Checklist
- After you attach WAF to CloudFront, probe the raw Function URL from outside the distribution.
- Require a shared origin secret (or equivalent) on the function when the marketed host is CloudFront.
- Do not treat CORS as a substitute for that check.
- Confirm the marketed CSP connect-src still names only the branded host.
Try the lab
Probe CloudFront and the raw Function URL in a simulated shell, fix the origin check, prove it with the project test. New here? help. Stuck? help next.
Full page: Open this lab · All labs
Related: WAF and Lambda Function URLs: put CloudFront in front; SSH closed until we need break-glass. How we deliver: methodology.
Engineering commentary only — not audit, legal, or certification advice.