An AWS Lambda@Edge origin-request handler that signs article text with invisible provenance markers at the CDN edge. Every HTML page served through your CloudFront distribution gets sentence-level Encypher markers embedded before the response reaches the reader, with zero changes to your origin server.
-
Article detection. The handler fetches the page from your origin and runs an eight-level detection chain (article tag, CMS classes, Schema.org microdata, JSON-LD, paragraph clusters) to locate the article region.
-
Signing. The visible text is hashed and sent to the Encypher signing API, which returns an embedding plan mapping invisible VS marker sequences to codepoint positions in the text.
-
Marker embedding. The embedding plan is applied to the HTML by splicing marker bytes at the exact byte offsets corresponding to each codepoint position. The original HTML structure is preserved; only invisible Unicode variation selectors are added.
-
Caching. Embedding plans are cached in the Lambda container's in-process memory (1 hour TTL). A negative cache (5 minutes) skips pages with no article content or alignment failures. Provisioning records are cached for 24 hours. Cache entries are pruned periodically to bound memory growth.
-
Fail-open. Any error at any stage returns the original HTML unmodified. Readers always receive a valid page.
- Node.js 18 or later (Lambda@Edge supports Node.js 18.x and 20.x)
- An AWS account with a CloudFront distribution
- Lambda function created in us-east-1 (Lambda@Edge requirement)
Lambda@Edge does not support runtime environment variables. Open
src/handler.mjs and set the module-level constants before packaging:
const ENCYPHER_API_URL = 'https://api.encypher.com'; // do not change
const ARTICLE_SELECTOR = ''; // optional CSS selector override, e.g. '.article-body'
const MIN_TEXT_LENGTH = 50; // minimum visible characters to trigger signing
const MAX_TEXT_LENGTH = 51200; // maximum characters sent to the signing API
const ENCYPHER_API_KEY = ''; // optional: Bearer token for authenticated endpointsARTICLE_SELECTOR accepts a tag name (article), a class (.entry-content),
or an attribute selector ([itemprop=articleBody]). Leave it empty to use
automatic detection.
npm run packageThis produces function.zip containing all source files in src/.
- Region: us-east-1 (required for Lambda@Edge)
- Runtime: Node.js 18.x or 20.x
- Handler:
handler.handler - Upload
function.zip
Lambda@Edge requires a numbered version (not $LATEST).
In the AWS console: Actions -> Publish new version.
Or via CLI:
aws lambda publish-version \
--function-name encypher-edge-provenance \
--region us-east-1Note the version ARN returned (e.g.
arn:aws:lambda:us-east-1:123456789:function:encypher-edge-provenance:1).
In your CloudFront distribution:
- Open the behaviour for the paths you want to sign (typically
*) - Under "Function associations", add a Lambda@Edge association:
- Event type: Origin request
- Lambda function ARN: paste the versioned ARN from step 3
Save the distribution and wait for the deployment to complete (Status: Deployed). Then invalidate the cache so new requests run through the handler:
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/*"Navigate to https://yourdomain.com/.well-known/encypher-verify to
confirm the domain is provisioned. A 200 response with domain_token
and org_id fields indicates successful setup.
Response headers on signed pages:
X-Encypher-Provenance: active- page was signedX-Encypher-Org: <org_id>- your Encypher organisation IDX-Encypher-Provenance: skipped:*- page was not signed (asset, no article content, quota exceeded, or processing error)
- The handler runs as an origin-request trigger, not viewer-request. This allows it to generate a full response with a body, which is required for marker injection. Viewer-request triggers cannot return a body for pass-through responses.
- Lambda@Edge functions are replicated globally from us-east-1 but invoked at the nearest edge location to the viewer.
- The in-process cache is per-container. Cold starts or high concurrency result in cache misses and extra API calls. This is safe: the signing API is idempotent for the same content hash.
- Lambda@Edge has a 30-second execution timeout for origin-request
triggers. The handler uses Node.js global
fetch(Node 18+) with no explicit timeout, so the origin fetch and API calls together must complete within that window.