BoxLang 🚀 A New JVM Dynamic Language Learn More...
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.
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.
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 |
|---|---|---|
| 1 | The Origin is in allowedOrigins
| Allow the trusted origin |
| 2 | Sec-Fetch-Site is
present | Allow only
same-origin or none; reject
same-site and cross-site
|
| 3 | Sec-Fetch-Site is absent, but
Origin is present | Allow if
it matches the request host or allowedOrigins
|
| 4 | Origin is also absent, but
Referer is present | Use the same comparison as step 3 |
| 5 | All three browser headers are absent | Allow 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.
box install originguard
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.
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.
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.
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.
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"
}
};
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.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."admin" matches
main.adminIndex, not only the admin
module. Start the pattern with ^ to match from the
beginning. This matches cbsecurity behavior._method value from a safe request.Sec-Fetch-Site is absent, OriginGuard checks
Origin and then Referer.allowedOrigins or add the event to
whiteList. Use monitor mode first when adding
OriginGuard to an existing app._method valuesOriginGuard also installs an interceptor named MethodGuard. It has no settings and cannot be turned off.
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.
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.
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.
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.
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
Apache License, Version 2.0.
This file records important changes to OriginGuard.
The format is based on Keep a Changelog, and the project uses Semantic Versioning.
_method values
that ColdBox ignores without logging.Initial release.
$
box install originguard