AWS Lambda@Edge
Questi contenuti non sono ancora disponibili nella tua lingua.
The Prosopo Protect Lambda@Edge bundle runs on Amazon CloudFront’s viewer-request trigger. It executes Protect’s decision on every request and either forwards to your origin (allow), blocks it with a branded interstitial (block), or serves a captcha challenge (challenge).
Two-part install: read this first
Section titled Two-part install: read this firstA production Prosopo Protect integration is two independent pieces working together. Deploying only one leaves your site unprotected:
- Part 1: the Lambda@Edge bundle (server-side). Runs on every CloudFront viewer request, executes the verdict, and blocks bad traffic before it reaches your origin. This is
lambda-edge.js, deployed as a Lambda function and attached to a CloudFront behaviour. - Part 2: the Protect JS bundle (client-side). Loaded on every HTML page users visit. Creates the session cookie, collects the bot-signal telemetry the risk engine scores, and shows the captcha overlay when the edge returns a challenge. This is a single
<script>tag in your HTML.
Without the Lambda, no request is scored. Without the JS bundle, no user ever gets a session cookie, so every /api/* and /forms/* request returns 401 no-session forever. The verify commands under Part 1 test the Lambda path only, so a green Lambda with no JS bundle deployed looks like it’s working while every real user still fails on their first XHR.
Deploy the two parts in either order, but ship both before rollout. The sections below are organised under the same two headings.
What you get
Section titled What you getPart 1 (server-side) is delivered as a file:
lambda-edge.js: the Lambda code, delivered per site and per environment. Self-contained, ~100 KB. Same file for every customer; per-site config is baked in at build time.
Part 2 (client-side) is hosted by us. You add one <script> tag pointing at https://js.protect.prosopo.io/protect.bundle.js. Nothing to download or bundle yourself. We publish new versions to that URL; your pages pick them up on their next reload without a redeploy from you.
Prerequisites
Section titled Prerequisites- An AWS account with permission to create Lambda functions in
us-east-1(Lambda@Edge functions must live there), publish versions of that function, and attach viewer-request triggers to your CloudFront distributions. - A CloudFront distribution serving your origin.
- The AWS CLI or the AWS Console.
- Your Prosopo account manager has:
- Registered your site key on the Protect dashboard with your intended
defaultPathType, endpoint rules, IP-category rules, and interstitial branding. See Portal settings checklist, which applies here too. - Issued you a bundle scoped to your site.
- Registered your site key on the Protect dashboard with your intended
Part 1: the Lambda@Edge bundle (server-side)
Section titled Part 1: the Lambda@Edge bundle (server-side)1a. DNS: protect.<your-domain> as DNS-only
Section titled 1a. DNS: protect.<your-domain> as DNS-onlyThe bundle talks to Protect at a hostname you own, typically protect.<your-domain>. Create a CNAME record on your DNS pointing that hostname at Prosopo:
protect.<your-domain> CNAME protect.prosopo.ioDo not put this record behind a proxy layer (CloudFront, Cloudflare, or similar). Prosopo’s risk engine derives bot-signal value from the raw TLS handshake. If a proxy terminates the browser’s TLS at its edge and re-opens a fresh connection to Prosopo, the JA4 signals collapse to a single value per proxy stack and risk scoring degrades to noise. This subdomain must be DNS-only.
1b. Coordinate cert issuance with your account manager
Section titled 1b. Coordinate cert issuance with your account managerProsopo issues the TLS certificate for your protect.<your-domain> subdomain via Let’s Encrypt on our infrastructure. This is a two-step handoff:
-
You: create the CNAME record. Verify it’s live:
Terminal window dig protect.<your-domain># ANSWER SECTION should show a CNAME to protect.prosopo.io# and an A record resolving directly to our edge. -
Us: email your account manager once DNS is propagated. We issue the certificate on our end (~30 seconds), then confirm back. Attempting to deploy the Lambda before we’ve issued the cert results in TLS handshake failures on every access-check call, and the Lambda fails open. No request is actually scored.
Once the cert is in place it auto-renews on our end every 60 days.
The bundle you receive from Prosopo is built against your CNAMEd hostname, so you don’t need to configure the URL yourself. If you rotate the CNAMEd hostname, request a fresh bundle.
1c. Deploy the Lambda
Section titled 1c. Deploy the LambdaUpload the bundle to Lambda, publish a version, and attach the versioned ARN to the CloudFront behaviour you want to protect.
zip lambda-edge.zip lambda-edge.js
# First deploy: create the functionaws lambda create-function \ --region us-east-1 \ --function-name prosopo-protect-viewer-request \ --runtime nodejs22.x \ --handler lambda-edge.handler \ --memory-size 256 \ --timeout 5 \ --role arn:aws:iam::<account-id>:role/<lambda-edge-execution-role> \ --zip-file fileb://lambda-edge.zip
# Every deploy after that: update the codeaws lambda update-function-code \ --region us-east-1 \ --function-name prosopo-protect-viewer-request \ --zip-file fileb://lambda-edge.zip
# Publish a version. Lambda@Edge only accepts versioned ARNs.aws lambda publish-version \ --region us-east-1 \ --function-name prosopo-protect-viewer-requestThe publish-version response includes a FunctionArn ending in :<version-number>. In your CloudFront distribution’s Behaviors tab, edit the behaviour you want to protect and add a Function association:
- Event type: Viewer request
- Function type: Lambda@Edge
- Function ARN: the versioned ARN from
publish-version.
CloudFront propagates to every edge location in 2 to 5 minutes. Repeat this attach step for each behaviour that should be protected.
Your account manager will let you know when a new bundle is available. Run update-function-code plus publish-version and point the behaviour at the new versioned ARN. CloudFront won’t pick up $LATEST.
Execution role
Section titled Execution roleThe function’s role needs both lambda.amazonaws.com and edgelambda.amazonaws.com as trusted principals, plus the AWSLambdaBasicExecutionRole managed policy for CloudWatch Logs:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] }, "Action": "sts:AssumeRole" } ]}1d. Verify the Lambda
Section titled 1d. Verify the LambdaOnce the trigger is attached and CloudFront has propagated, test through the distribution’s domain.
DIST=https://your-distribution.cloudfront.net
# 1. HTML: Protect should return "allow" (bootstrap-passthrough) and# CloudFront forwards to your origin. Expected: 200, with# x-prosopo-request-id in the response headers.curl -sD - -H "Accept: text/html" -H "User-Agent: Mozilla/5.0" "$DIST/" | head
# 2. JSON, no cookie: Protect returns 401. Expected: 401,# x-prosopo-status: no-session, x-prosopo-request-id.curl -sD - -H "Accept: application/json" "$DIST/api/anything" | head
# 3. POST /forms/* with Accept: text/html and no cookie: same 401.# Closes the "spoof Accept: text/html on a POST" bot pattern.curl -sD - -H "Accept: text/html" -X POST "$DIST/forms/anything" | headIf command 1 returns anything other than 200, you may be hitting your own origin’s bot protection, not Prosopo’s. AWS WAF, CloudFront’s own managed rules, and similar tools reliably block curl regardless of what headers you set. Distinguish:
- If the response has
x-prosopo-*headers, the 4xx is from Prosopo. Checkx-prosopo-statusandx-prosopo-decision. See Troubleshooting. - If the response has
server: awselband nox-prosopo-*headers, the 4xx is from your origin’s own bot protection. Verify with a real browser instead.
X-Prosopo-Request-Id is echoed on every response Prosopo returns and written to Protect’s verdict audit log. CloudFront’s own x-amz-cf-id is the default request ID, so the same value shows up in CloudFront access logs and Protect’s audit log.
Lambda verified? Move to Part 2.
Part 2: the Protect JS bundle (client-side)
Section titled Part 2: the Protect JS bundle (client-side)This part is mandatory. Without the bundle no session cookie is ever set, and the Lambda returns 401 no-session on every /api/* or /forms/* call in perpetuity. The Lambda verify commands under Part 1 still pass without the bundle; real user traffic doesn’t.
2a. Add the script tag to every HTML page
Section titled 2a. Add the script tag to every HTML pageAdd this snippet to every HTML page your site serves. A shared header or footer template is the usual spot:
<script> window.prosopo_config = { site_key: "<your-site-key>", protect_url: "https://protect.<your-domain>" };</script><script src="https://js.protect.prosopo.io/protect.bundle.js" async></script>Both tags are required. The first sets window.prosopo_config before the second loads; the bundle reads that config to know which Protect API to talk to. Skipping the first tag causes the bundle to fall back to the CDN’s own origin (js.protect.prosopo.io) as its API URL, which returns 405 on the CORS preflight for /api/protect/init and hangs the bundle’s patched fetch.
On first page load the bundle:
- Calls
POST https://protect.<your-domain>/api/protect/init. - Receives a
prosopo_sessioncookie scoped to.<your-domain>. - Loads a per-session telemetry runtime script that collects the bot-signal data used for risk scoring.
Every subsequent request from that browser carries the cookie, and the Lambda looks up a real verdict on /api/* or /forms/* calls.
2b. Gate fast form submissions and link clicks (optional)
Section titled 2b. Gate fast form submissions and link clicks (optional)Users can click Submit before the bundle finishes creating the session. The browser fires a full-page POST with no cookie and gets a 401. The security is correct but the UX is bad.
Opt any form or link into automatic held-until-session-ready behaviour by adding the data-prosopo-gate attribute:
<form action="/forms/search.php" method="post" data-prosopo-gate> <!-- ... --></form>
<nav data-prosopo-gate> <a href="/section-a/">Section A</a> <a href="/section-b/">Section B</a></nav>The bundle intercepts marked forms and same-origin links in the capture phase, holds up to 8 seconds waiting for the session, then releases with the cookie in place. The submit button flips disabled and aria-busy while held so the user gets visual feedback. Ctrl/Cmd-click, middle-click, target="_blank", download attributes, hash-only links, and cross-origin links pass through untouched.
Attributes on an ancestor cover every descendant form or link, so <nav data-prosopo-gate> gates every link inside.
2c. Verify the bundle
Section titled 2c. Verify the bundleOpen your site in a real browser with DevTools on the Network tab. On first page load you should see:
- A
POSTtohttps://protect.<your-domain>/api/protect/initreturning200and aSet-Cookie: prosopo_session=…header. - A subsequent
GET https://protect.<your-domain>/api/protect/t/<jti>fetching the per-session telemetry runtime. - The
prosopo_sessioncookie set withDomain=.<your-domain>for later requests.
If the init call goes to https://js.protect.prosopo.io/api/protect/init and returns 405, the window.prosopo_config block is missing from the snippet. See Troubleshooting.
Configuring proxy, VPN and datacenter blocking
Section titled Configuring proxy, VPN and datacenter blockingBlocking policy lives on your Prosopo dashboard, not in the bundle. Two independent rule sources feed the no-cookie edge path:
- Site settings: recognised IP categories are
tor,datacenter,proxy,vpn,abuser,mobile, andcrawler. Configure per-category verdicts (allow,challenge, orblock). - Tiered access rules: support the full rule set of IP CIDR, ASN, IP category, country, User-Agent substring, and JA4 TLS fingerprint. Rules can be scoped to your site or applied globally by Prosopo.
Either source firing blocks the request at the edge. When both match, the more restrictive verdict wins (Block > Challenge > Allow). Policy changes take effect on the next request, no redeploy needed.
Constraints
Section titled Constraints- Region: functions must be created in
us-east-1. - Memory: 256 MB recommended. Higher tiers cost more but give proportionally more CPU.
- Time: the viewer-request handler is capped at 5 seconds. Protect’s API calls typically return in under 40 ms (under 2 ms for cached lookups) and are budgeted to a 500 ms hard timeout, with fail-open on transient errors.
- CloudWatch logs: written to the region closest to the viewer that served the request, not
us-east-1. Check the matching region when debugging.
Rotating credentials
Section titled Rotating credentialsEach bundle is scoped to a specific site’s authentication token. Rotating the token means requesting a fresh bundle from Prosopo and redeploying it via the Deploy the Lambda flow. The old bundle keeps working until you swap the ARN on your CloudFront behaviour.
Troubleshooting
Section titled TroubleshootingCommon failure modes with their causes are on the dedicated Troubleshooting page.
Learn