Skip to content

Repository files navigation

CloudFront Function – Traffic Filter

A CloudFront Function (JS 2.0 runtime) designed to protect a static website hosted on AWS S3 (no PHP). It filters incoming requests before they reach the origin or cache.

CodeQL Quality Gate Status Coverage

What the function does

1. Missing user-agent blocking (404)

Requests with no User-Agent header, an empty value, or whitespace-only value return 404. This check runs first and cannot be bypassed.

2. Always-allow paths

/ads.txt and /robots.txt bypass all further checks and return unchanged (still requires a non-empty user-agent).

3. Security scan blocking (404)

Requests matching automated-scan patterns return 404:

  • URI extensions: .php*, .sql, .bak, .phtml, .phar
  • Common scanner folders: /admin, /wp-admin, /phpmyadmin, /backup, etc.
  • Sensitive paths: /.env, /.git, /ip

4. Malformed Firefox user-agent blocking (404)

Requests with mismatched rv: and firefox/ versions return 404.

5. Bot / scraper blocking

Requests matching known bot/scraper user-agent patterns return 404, on every path.

Blocked patterns include: scrapers (Scrapy, PetalBot, DataForSEO, etc.), old browser tokens (Trident, Presto), and 50+ other known bots/crawlers, matched case-insensitively against the User-Agent header.

6. Pass-through

All other requests are forwarded to the origin unchanged.


Why a CloudFront Function (not Lambda@Edge)?

CloudFront Functions run at every edge location with sub-millisecond startup and are ~6× cheaper than Lambda@Edge. They are the right tool for stateless, CPU-light request manipulation that requires no network I/O, no large runtimes, and no response body streaming. This filter fits that profile exactly: pure string matching, no external calls.

The trade-off is a restricted runtime — no setTimeout, no fetch, no Node.js built-ins. The function is written deliberately to stay within those constraints.


Deployment

Copy the body of function.js into the CloudFront Functions editor in the AWS Console (or deploy via AWS CLI / CDK / Terraform). Associate the function with the viewer request event of your distribution.

Important: remove the export { handler } line before deploying — CloudFront's JS 2.0 runtime does not support ES module export syntax. That line exists solely so Vitest can import the function during testing.


Pre-push validation (Claude Code hook)

A Claude Code PreToolUse hook automatically validates function.js against the live cloudfront-js-2.0 runtime before every git push. It uploads the local code to the DEVELOPMENT stage and runs aws cloudfront test-function, blocking the push if any syntax or runtime error is detected.

Setup

1. Set your function name

echo "Block_Intrusions" > .cloudfront-function-name

2. Configure the test event

Edit test-event.json to match a representative viewer request for your distribution. The default covers a standard GET with a User-Agent header.

3. AWS credentials

Ensure your shell has credentials with at least these permissions:

{
  "Effect": "Allow",
  "Action": [
    "cloudfront:DescribeFunction",
    "cloudfront:UpdateFunction",
    "cloudfront:TestFunction"
  ],
  "Resource": "*"
}

How it works

On each git push Claude Code will:

  1. Fetch the current ETag via aws cloudfront describe-function
  2. Upload local function.js to the DEVELOPMENT stage via aws cloudfront update-function
  3. Run aws cloudfront test-function --stage DEVELOPMENT
  4. Block the push and display the error if the runtime rejects the function

The hook script is at .claude/hooks/cloudfront-pre-push.sh.


Test framework

Tests are written with Vitest.

Vitest was chosen over Jest for this project because:

  • Native ESM support — no Babel transform needed. The function uses export { handler } which works out of the box with "type": "module" in package.json.
  • Zero config — no jest.config.js, no transform pipeline to maintain.
  • Fast — Vitest starts in milliseconds; the full suite runs in under 250 ms.
  • Jest-compatible APIdescribe, it, expect, it.each are identical, so the syntax is familiar.

Running the tests

npm test           # run once
npm run test:watch # watch mode (re-runs on file save)

Test structure

function.test.js covers all behaviours with 117 tests:

Suite What is tested
Always-allow paths /ads.txt; URI trim & lowercase normalisation
Security scan blocking File extensions, scanner folders, sensitive paths
Blocked bots Empty /feed.xml; empty /rss.xml; empty /sitemap.xml; 304 Not Modified on cache headers
Bot patterns 60+ patterns matched case-insensitively
Malformed Firefox UA Mismatched rv: and firefox/ versions blocked
Null / empty UA blocking Missing/empty/whitespace user-agent
Pass-through Normal requests forwarded unchanged

Each test builds a minimal CloudFront event object ({ request: { uri, headers } }) and asserts on the return value — either the original request object (pass-through) or a synthetic response with statusCode, headers, and body.

About

This project holds a single cloudfront function to filter out or modify incoming traffic

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages