Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Handler/action annotation security now shares `processInvalidAccess()` with route middleware. Behavior is unchanged.

### Fixed

- Settings you set on the `cbcsrf` module itself were silently overwritten by the `csrf` key of the cbsecurity settings, even when you never set that key, because cbsecurity copied its defaults over them. Now only the keys you explicitly set in `cbsecurity.csrf` are applied, and they win over the `cbcsrf` module settings. Every other key keeps the `cbcsrf` value. `cbsecurity.csrf` now shows the values that are in effect.

## [3.8.0] - 2026-09-21

### Changed
Expand Down
38 changes: 36 additions & 2 deletions models/CBSecurity.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ component threadsafe singleton accessors="true" {
}

function onDIComplete(){
// What the user explicitly set in cbsecurity.csrf, captured before the defaults fill the gaps
var explicitCsrf = {};
if ( structKeyExists( variables.settings, "csrf" ) && isStruct( variables.settings.csrf ) ) {
explicitCsrf = duplicate( variables.settings.csrf );
}

// Default level-1 settings
variables.settings.append( variables.DEFAULT_SETTINGS, false );
// Default level-2 settings
Expand Down Expand Up @@ -197,14 +203,42 @@ component threadsafe singleton accessors="true" {
log.info( "+ Basic Auth Validator Detected -> User Service set to BasicAuthUserService" );
}

// cbcsrf settings incorporation
variables.moduleSettings.cbcsrf.settings.append( variables.settings.csrf, true );
// cbcsrf settings incorporation. Precedence, highest first:
// 1. Settings explicitly set in cbsecurity.csrf
// 2. Settings of the cbcsrf module (its own overrides or its defaults)
var cbcsrfSettings = variables.moduleSettings.cbcsrf.settings;
cbcsrfSettings.append(
resolveCsrfSettings( cbsecurityCsrf = explicitCsrf, cbcsrfSettings = cbcsrfSettings ),
true
);
// Make cbsecurity.csrf show the values that are really in effect
for ( var csrfKey in variables.DEFAULT_SETTINGS.csrf ) {
if ( structKeyExists( cbcsrfSettings, csrfKey ) ) {
variables.settings.csrf[ csrfKey ] = cbcsrfSettings[ csrfKey ];
}
}
// DBLogger Configuration
variables.dbLogger.configure();
// Log it
log.info( "√ CBSecurity Services started and configured." );
}

/**
* Work out the settings for the cbcsrf module. The keys the user explicitly set in cbsecurity.csrf win, every
* other key keeps the value the cbcsrf module already has, so its own overrides are never replaced by
* cbsecurity defaults.
*
* @cbsecurityCsrf The keys the user explicitly set in cbsecurity.csrf
* @cbcsrfSettings The current settings of the cbcsrf module
*
* @return The cbcsrf settings to use
*/
struct function resolveCsrfSettings( required struct cbsecurityCsrf, required struct cbcsrfSettings ){
var resolved = duplicate( arguments.cbcsrfSettings );
resolved.append( arguments.cbsecurityCsrf, true );
return resolved;
}

/**
* Get the default rule settings structure
*/
Expand Down
80 changes: 80 additions & 0 deletions test-harness/tests/specs/unit/CsrfSettingsSpec.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Which settings win between the cbsecurity `csrf` key and the cbcsrf module settings
*/
component extends="coldbox.system.testing.BaseModelTest" model="cbsecurity.models.CBSecurity" {

function beforeAll(){
super.beforeAll();
// The defaults that both modules share
variables.defaults = {
enableAutoVerifier : false,
verifyExcludes : [],
rotationTimeout : 30,
enableEndpoint : false,
cacheStorage : "CacheStorage@cbstorages",
enableAuthTokenRotator : true
};
}

function run(){
describe( "cbcsrf settings precedence", function(){
beforeEach( function(){
setup();
} );

it( "leaves the cbcsrf settings alone when cbsecurity.csrf sets nothing", function(){
var cbcsrf = duplicate( defaults );
cbcsrf.rotationTimeout = 99;
cbcsrf.enableEndpoint = true;

var resolved = model.resolveCsrfSettings( {}, cbcsrf );

expect( resolved.rotationTimeout ).toBe( 99 );
expect( resolved.enableEndpoint ).toBeTrue();
} );

it( "applies the keys the user set in cbsecurity.csrf over the defaults", function(){
var resolved = model.resolveCsrfSettings( { rotationTimeout : 77 }, duplicate( defaults ) );

expect( resolved.rotationTimeout ).toBe( 77 );
expect( resolved.enableEndpoint ).toBeFalse();
} );

it( "lets an explicit cbsecurity.csrf key win over a cbcsrf override", function(){
var cbcsrf = duplicate( defaults );
cbcsrf.rotationTimeout = 99;

var resolved = model.resolveCsrfSettings( { rotationTimeout : 77 }, cbcsrf );

expect( resolved.rotationTimeout ).toBe( 77 );
} );

it( "keeps cbcsrf overrides for keys cbsecurity did not set", function(){
var cbcsrf = duplicate( defaults );
cbcsrf.rotationTimeout = 99;
cbcsrf.enableEndpoint = true;

var resolved = model.resolveCsrfSettings( { rotationTimeout : 77 }, cbcsrf );

expect( resolved.rotationTimeout ).toBe( 77 );
expect( resolved.enableEndpoint ).toBeTrue();
} );

it( "applies complex values from cbsecurity.csrf", function(){
var cbcsrf = duplicate( defaults );
cbcsrf.verifyExcludes = [ "stripe" ];

var resolved = model.resolveCsrfSettings( { verifyExcludes : [ "other" ] }, cbcsrf );

expect( resolved.verifyExcludes ).toBe( [ "other" ] );
} );

it( "does not change the settings it is given", function(){
var cbcsrf = duplicate( defaults );
model.resolveCsrfSettings( { rotationTimeout : 77 }, cbcsrf );
expect( cbcsrf.rotationTimeout ).toBe( 30 );
} );
} );
}

}
Loading