Skip to content

Public global helpers (env, model, findAll, …) are URL-invokable as controller actions — protectedControllerMethods is never populated #2844

Description

@mlibbe

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

  1. Any standard Wheels 4.0 app with the default [controller]/[action] route.
  2. Request a URL whose action segment is a global helper name, e.g. GET /<anyController>/env.
  3. → 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions