BoxLang 🚀 A New JVM Dynamic Language Learn More...

OriginGuard

v1.0.1 Modules

OriginGuard

OriginGuard Logo

OriginGuard protects ColdBox applications from cross-site request forgery (CSRF). It does not use tokens, hidden form fields, or session storage.

OriginGuard checks whether a request that changes data came from an allowed origin. It reads the Sec-Fetch-Site, Origin, and Referer headers that browsers add to requests. A page from another site cannot change these headers. OriginGuard follows the same algorithm as Go 1.25 http.CrossOriginProtection, designed by Filippo Valsorda.

An origin is the combination of a scheme, host, and port. The scheme is the http or https part of a URL.

Why not tokens?

Token-based CSRF protection works, but it needs session data, token creation and rotation, hidden form fields, and special AJAX handling. A form can also fail after its token expires. Header-based protection does not need those parts. Modern browsers send Sec-Fetch-Site. Older browsers usually send Origin or Referer, so OriginGuard checks those headers when Sec-Fetch-Site is absent.

How the decision works

For each unsafe request, OriginGuard checks five rules in order. An unsafe request uses a method other than GET, HEAD, or OPTIONS. OriginGuard stops at the first rule that applies.

Step Signal Verdict
1The Origin is in allowedOrigins Allow the trusted origin
2Sec-Fetch-Site is presentAllow only same-origin or none; reject same-site and cross-site
3Sec-Fetch-Site is absent, but Origin is presentAllow if it matches the request host or allowedOrigins
4Origin is also absent, but Referer is presentUse the same comparison as step 3
5All three browser headers are absentAllow because command-line tools, TestBox, and server clients cannot create browser-based CSRF requests

Rule 5 keeps API clients and scheduled jobs working. These clients do not run in a browser, so they cannot make a browser send a cross-site request with the user's cookies.

Requirements

  • ColdBox 7+
  • Lucee 5+, Adobe ColdFusion 2023+, or BoxLang 1+ (BoxLang runs in CFML-compatibility mode)
  • Zero runtime dependencies

Installation

box install originguard

Quick start: interceptor mode

Install the module. OriginGuard protects every unsafe request with no configuration. A ColdBox interceptor runs this check before the request handler. A rejected request receives a built-in 403 response. AJAX requests receive JSON. Other requests receive HTML. OriginGuard does not intercept events on an errors handler, so error pages can still render.

Configuration narrows the default protection or allows trusted cross-origin requests.

Scoping with secureList and whiteList

If you have used cbsecurity's security rules, this will look familiar. Two settings, both case-insensitive regex patterns matched against the ColdBox event:

  • secureList lists the events to protect. It defaults to "*", which means every event.
  • whiteList lists events to exclude. A whiteList match overrides a secureList match.
// config/Coldbox.cfc
moduleSettings = {
    originguard = {
        // Protect every event. This line repeats the default value.
        secureList = "*",
        // Exclude the payment webhook because the payment service sends it from another site.
        whiteList  = "^checkout:api\.webhook$"
    }
};

Patterns are case-insensitive regular expressions. OriginGuard does not anchor them automatically. This matches cbsecurity behavior. For example, "admin" also matches main.adminIndex. Start a pattern with ^ when it must match from the beginning.

Goal Pattern
Everything*
Root (non-module) events only^[^:]+$
One module^admin:
One handler^admin:users\.
One exact action^admin:users\.delete$
Two modules^admin:,^api:
Nothing (firewall off)""

"*" is a special value that means every event. It is not a wildcard pattern. Every other value is a regular expression.

Both settings accept a comma list ("^admin:,^api:") or an array ([ "^admin:", "^api:" ]). Use an array when a pattern contains a comma, such as a {1,3} quantifier. A comma list would split that pattern into two values.

If the application already receives traffic, start in monitor mode. This follows the staged rollout in the Fetch Metadata guidance. Monitor requests first, then enable blocking.

// Log requests that would be blocked, but allow them to continue.
moduleSettings = {
    originguard = {
        mode = "monitor"
    }
};

Run monitor mode for a few days. Look for OriginGuard monitor: warnings. Some valid workflows send cross-origin requests. Examples include SAML SSO posts, payment service returns, and embedded widgets. Add trusted senders to allowedOrigins, or add their events to whiteList. Then remove mode = "monitor" to block rejected requests.

Custom denial page

Set denialEvent to your own handler to control the response for a blocked request:

moduleSettings = {
    originguard = {
        denialEvent = "myapp:errors.onOriginFailure"
    }
};

The handler receives two values in ColdBox's private request collection, named prc. prc.originBlockedEvent contains the blocked event. prc.originBlockReason contains the reason, such as sec-fetch-site:cross-site. Return HTTP 403 from the handler.

Service mode

Use service mode when each action needs its own response to a rejected request. For example, an action may render a form again or log the result and continue. Set secureList = "" to turn off the firewall, then call the verifier directly.

component {

    property name="originVerifier" inject="OriginVerifier@originguard";
    property name="guardSettings"  inject="coldbox:modulesettings: originguard";

    function doLogin( event, rc, prc ){
        var verdict = originVerifier.verify( event, guardSettings );
        if ( !verdict.allowed ) {
            // Handle the rejected request here.
            return relocate( "main.login" );
        }
        // Continue with the action.
    }
}

verify() returns { allowed : boolean, reason : string }. Use allowed to make decisions. Use reason only in logs.

All settings

moduleSettings = {
    originguard = {
        // Set this to false to turn off OriginGuard's origin checks.
        enabled        = true,
        // Origins that may send cross-origin requests. Include a scheme to trust only that scheme.
        // A host without a scheme trusts HTTP and HTTPS. An empty array trusts only your own host.
        allowedOrigins = [],
        // Read X-Forwarded-Host when a trusted reverse proxy replaces the Host header.
        trustUpstream  = false,
        // Events to protect. Use a comma list or array of case-insensitive regular expressions.
        // Start a pattern with "^" to match from the beginning. "*" protects every event.
        // An empty value turns off the firewall for service mode.
        secureList     = "*",
        // Events to exclude from secureList. A whiteList match always takes priority.
        whiteList      = "",
        // HTTP methods that do not need an origin check.
        safeMethods    = "GET,HEAD,OPTIONS",
        // "block" rejects requests. "monitor" logs requests without rejecting them.
        mode           = "block",
        // The event that renders the response for a blocked request.
        denialEvent    = "originguard:errors.onBlocked"
    }
};

Important behavior

  • An allowed origin overrides Sec-Fetch-Site. OriginGuard trusts an entry in allowedOrigins even when the browser reports cross-site. Include a scheme, such as "https://partner.com", to trust only that scheme. This matches Go's AddTrustedOrigin. A host-only entry such as "partner.com" trusts HTTP and HTTPS.
  • Configure trusted reverse proxies. Set trustUpstream = true when a trusted reverse proxy replaces the Host header. OriginGuard then uses the first value in X-Forwarded-Host. Do this only when a proxy you control sets the header because clients can also send it.
  • OriginGuard does not anchor regular expressions. The pattern "admin" matches main.adminIndex, not only the admin module. Start the pattern with ^ to match from the beginning. This matches cbsecurity behavior.
  • OriginGuard checks both HTTP method values. The firewall skips an origin check only when the real request method and ColdBox's reported method are both safe. MethodGuard also removes an unsafe _method value from a safe request.
  • Older browsers use other headers. When Sec-Fetch-Site is absent, OriginGuard checks Origin and then Referer.
  • Valid cross-origin POST requests need an exception. SAML SSO, 3-D Secure payment returns, and embedded widgets may send a browser POST from another site. Add the sender to allowedOrigins or add the event to whiteList. Use monitor mode first when adding OriginGuard to an existing app.

MethodGuard and unsafe _method values

OriginGuard also installs an interceptor named MethodGuard. It has no settings and cannot be turned off.

The problem

On ColdBox 7 through 8.1, event.getHTTPMethod() reads _method from request data:

return getValue( "_method", CGI.REQUEST_METHOD );

HTML forms support only GET and POST. A form can send a POST with _method=DELETE to request a DELETE route. That use is valid. The problem is that these ColdBox versions also trust _method on a GET request. For example:

<img src="https://your-app.com/admin/tags/1/delete?_method=DELETE">

The browser sends a real GET from another site and includes the signed-in user's cookies. ColdBox reads _method=DELETE, checks DELETE against allowedMethods, and may run the delete action. This bug caused a real record to be deleted during development.

Declaring this.allowedMethods does not stop this attack unless _method is corrected first.

The fix

MethodGuard removes _method when a GET, HEAD, or OPTIONS request claims an unsafe method. getHTTPMethod() then returns the real request method. ColdBox checks that method against allowedMethods and returns 405 through the handler's onInvalidHTTPMethod action.

A POST with _method=DELETE remains valid, so normal delete forms keep working.

The enabled, secureList, and mode settings affect only origin checks. They do not affect MethodGuard. MethodGuard writes a warning log entry each time it removes _method.

Routing limit

ColdBox's RoutingService reads the method before any interceptor runs. MethodGuard cannot correct that earlier routing decision.

This limit does not affect routes that use only the URL path. These routes do not use .withVerbs() or action structs.

If a route uses .withVerbs() or an action struct, it may select a DELETE action before MethodGuard runs. Declare this.allowedMethods on every action that changes data. ColdBox will then reject the corrected GET before the action runs.

This limit applies only before ColdBox 8.2. Starting with 8.2, the router uses the corrected getHTTPMethod() and does not route from the unsafe value.

When MethodGuard can be removed

MethodGuard is a temporary fix for COLDBOX-1406. The ColdBox maintainers fixed the bug for ColdBox 8.2. Starting with that version, getHTTPMethod() reads _method only on a POST and accepts only PUT, PATCH, or DELETE. ColdBox 7 and 8.1 do not contain the fix. The maintainers do not plan to add it to version 7.

OriginGuard supports ColdBox 7 and later. Keep MethodGuard until OriginGuard requires ColdBox 8.2 or later. On ColdBox 8.2, MethodGuard repeats the framework check but does not change the result. It also logs the attempt, while ColdBox ignores the unsafe value without logging it. Applications do not call MethodGuard directly, so removing it later will not change the application API.

Contributing and tests

See CONTRIBUTING.md. To run the suite locally:

box run-script install:dependencies
box run-script start:lucee5     # or start:2023, start:boxlang-cfml, ...
# open http://127.0.0.1:60299/tests/runner.cfm

License

Apache License, Version 2.0.

Changelog

This file records important changes to OriginGuard.

The format is based on Keep a Changelog, and the project uses Semantic Versioning.


[Unreleased]

[1.0.1] => 2026-09-11

Changed

  • Rewrote project comments, documentation, test descriptions, and response text in plain English. No security rules or public interfaces changed.
  • Updated the documentation for COLDBOX-1406. ColdBox 8.2 fixes the bug, but versions 7 and 8.1 do not contain the fix. Keep MethodGuard until OriginGuard requires ColdBox 8.2 or later. MethodGuard is not needed on ColdBox 8.2, but it still logs unsafe _method values that ColdBox ignores without logging.

[1.0.0] => 2026-07-14

Initial release.

$ box install originguard

No collaborators yet.
     
  • {{ getFullDate("2026-07-14T23:18:32Z") }}
  • {{ getFullDate("2026-09-13T07:39:16Z") }}
  • 436
  • 70