Summary
application.wheels.protectedControllerMethods is initialized to an empty string and never populated, so any public, non-$-prefixed framework helper mixed onto a controller can be invoked as a controller action directly from a URL. Helpers with a required argument (most visibly env()) throw a hard 500 when hit this way; others (model(), findAll(), redirectTo(), etc.) execute unintended code paths.
This is reachable by unauthenticated traffic (crawlers/scanners routinely probe URLs), so it's both an error-noise and a minor hardening concern.
Affected version
Reproduced on main (also present in the 4.0.2 release).
Root cause
vendor/wheels/events/onapplicationstart.cfc (lines 344–346 on main) describes the intended behavior and even declares the allow-list variable, but the population logic is missing — the protected list is simply set to empty:
// Add all public controller / view methods to a list of methods that you should not be allowed to call as a controller action from the url.
local.allowedGlobalMethods = "get,set,mapper"; // declared, never used
application.$wheels.protectedControllerMethods = ""; // never populated
$callAction() in vendor/wheels/controller/processing.cfc (lines 131–140 on main) is the only gate, and it relies on that list:
public void function $callAction(required string action) {
if (Left(arguments.action, 1) == "$" || ListFindNoCase(application.wheels.protectedControllerMethods, arguments.action)) {
Throw(type = "Wheels.ActionNotAllowed", ...);
}
try {
if (StructKeyExists(this, arguments.action) && IsCustomFunction(this[arguments.action])) {
$invoke(method = arguments.action); // <-- invokes ANY public custom-function member
}
...
Because the only protection is the $-prefix check and the (empty) protectedControllerMethods list, every public helper mixed in via Global.cfc is an invocable "action." The env() helper (vendor/wheels/Global.cfc:597, public any function env(required string name, any defaultValue = "")) is the clearest failure: a request routing to action=env calls it with no name, producing:
expression Error: The parameter [name] to function [env] is required but was not passed in.
Reproduction
- Any standard Wheels 4.0 app with the default
[controller]/[action] route.
- Request a URL whose action segment is a global helper name, e.g.
GET /<anyController>/env.
- → HTTP 500,
The parameter [name] to function [env] is required but was not passed in.
(GET /<anyController>/model, /findAll, etc. similarly dispatch into framework helpers rather than 404ing.)
Expected behavior
A request to an action that is actually a framework/global helper — not a user-defined controller action — should not be dispatched. It should fall through to the missing-action / 404 path, the same as any other non-existent action.
Suggested fix
Populate protectedControllerMethods in onapplicationstart.cfc as the comment intends — i.e. with the public framework helper names mixed onto controllers (the set exposed via Global.cfc / the view+controller mixins), minus any that are legitimately allowed as actions. The orphaned local.allowedGlobalMethods = "get,set,mapper" suggests the original intent was to derive the protected set from the controller's public method surface and exclude an allow-list.
A minimal interim guard is to at least include the helpers that take required args (e.g. env) so they can't 500; the complete fix is to restore the full list so no global helper is URL-invokable.
Happy to open a PR if a maintainer can confirm the intended source of truth for the helper list (derive-from-metadata vs. a static list).
Summary
application.wheels.protectedControllerMethodsis initialized to an empty string and never populated, so any public, non-$-prefixed framework helper mixed onto a controller can be invoked as a controller action directly from a URL. Helpers with arequiredargument (most visiblyenv()) throw a hard 500 when hit this way; others (model(),findAll(),redirectTo(), etc.) execute unintended code paths.This is reachable by unauthenticated traffic (crawlers/scanners routinely probe URLs), so it's both an error-noise and a minor hardening concern.
Affected version
Reproduced on
main(also present in the 4.0.2 release).Root cause
vendor/wheels/events/onapplicationstart.cfc(lines 344–346 onmain) describes the intended behavior and even declares the allow-list variable, but the population logic is missing — the protected list is simply set to empty:$callAction()invendor/wheels/controller/processing.cfc(lines 131–140 onmain) is the only gate, and it relies on that list:Because the only protection is the
$-prefix check and the (empty)protectedControllerMethodslist, every public helper mixed in viaGlobal.cfcis an invocable "action." Theenv()helper (vendor/wheels/Global.cfc:597,public any function env(required string name, any defaultValue = "")) is the clearest failure: a request routing toaction=envcalls it with noname, producing:Reproduction
[controller]/[action]route.GET /<anyController>/env.The parameter [name] to function [env] is required but was not passed in.(
GET /<anyController>/model,/findAll, etc. similarly dispatch into framework helpers rather than 404ing.)Expected behavior
A request to an action that is actually a framework/global helper — not a user-defined controller action — should not be dispatched. It should fall through to the missing-action / 404 path, the same as any other non-existent action.
Suggested fix
Populate
protectedControllerMethodsinonapplicationstart.cfcas the comment intends — i.e. with the public framework helper names mixed onto controllers (the set exposed viaGlobal.cfc/ the view+controller mixins), minus any that are legitimately allowed as actions. The orphanedlocal.allowedGlobalMethods = "get,set,mapper"suggests the original intent was to derive the protected set from the controller's public method surface and exclude an allow-list.A minimal interim guard is to at least include the helpers that take required args (e.g.
env) so they can't 500; the complete fix is to restore the full list so no global helper is URL-invokable.Happy to open a PR if a maintainer can confirm the intended source of truth for the helper list (derive-from-metadata vs. a static list).