Skip to content

URL env switch to production/maintenance 500s and silently reverts — bare $location() undefined at onapplicationstart.cfc:488 when redirectAfterReload auto-enables #3054

Closed

Description

@bpamiri

What happens

?reload=production&password=<secret> — the exact flow documented in the security-hardening guide — never completes. The gate and switch code engage correctly, but the new application's cold start throws, returns a 500, and the next request reverts to the file-configured environment.

Reproduction (Lucee 7 docker harness, develop @ f668c50)

  1. Demo app in development with set(reloadPassword="smokepw").
  2. curl -s -o /dev/null -w "%{http_code} %{redirect_url}" 'http://localhost:60007/?reload=production&password=smokepw' → 302 http://localhost:60007/?reload=production&password=smokepw (params correctly preserved per app template: reload redirect strips the environment-switch params, making ?reload=<env> a silent no-op #3030).
  3. Follow the redirect once: → HTTP 500 with the production "Something went wrong" page.
  4. wheels_security.log shows the switch DID engage: Environment switched to 'production' via URL from <ip> + Reload accepted from <ip> (environment: production).
  5. wheels-errors.log shows: No matching function [$LOCATION] found (reproduced twice, timestamps matching the switch attempts).
  6. Next request: app cold-starts again back in development (/wheels/info?format=json → 200; no HSTS from SecurityHeaders). The switch never survives.

Control: ?reload=testing&password=smokepw switches cleanly end-to-end (302 → app serves in testing, /wheels/info 404s). This isolates the root cause to production/maintenance targets.

Root cause

  1. vendor/wheels/events/init/orm.cfm auto-flips redirectAfterReload = true when the environment is production or maintenance (if (ListFindNoCase("production,maintenance", application.$wheels.environment)) { application.$wheels.redirectAfterReload = true; }).
  2. With redirectAfterReload=true and url.reload present (always true on the env-switch cold start, because $buildRedirectUrl deliberately preserves reload+password for switches), vendor/wheels/events/onapplicationstart.cfc line 488 runs:
    $location(url = local.url, addToken = false);
  3. onapplicationstart.cfc is component { with no extends and no mixins — $location() (defined in vendor/wheels/Global.cfc line 326) is not in scope. Every other helper call in this file goes through application.wo.$X(...); line 488 is the lone bare call. Lucee throws No matching function [$LOCATION] found, the application start aborts, and the engine discards the half-started app.

Why CI hasn't caught it

The non-dev smoke probes (#3044) boot directly into production/testing via environment.cfm; they never exercise the URL switch INTO production. The #3030/#3031 specs cover gate/flag resolution, not the post-switch cold start with redirectAfterReload=true.

Suggested fix

Change line 488 to application.wo.$location(url = local.url, addToken = false); (matching every other call in the file).

Acceptance criteria

  • From development with a reloadPassword set, ?reload=production&password=<secret> ends with the app serving in production (e.g. /wheels/info 404, SecurityHeaders emits the HSTS auto-default), no 500 during the transition.
  • Same for ?reload=maintenance.
  • wheels-errors.log contains no No matching function [$LOCATION] entries during the flow.
  • The redirectAfterReload=true + ?reload=true combination (explicitly set in development) also completes without throwing.
  • Regression spec covering the redirectAfterReload code path in onapplicationstart.cfc (the bare-call-vs-application.wo discrepancy is exactly the class of bug a structural spec could guard, like BareCfabortGuardSpec).

Evidence gathered during the security-hardening guide behavioral audit (Lucee 7 + Adobe 2023 docker harness).


Reported via guides: deployment/production-config.mdx, deployment/security-hardening.mdx

Activity

  1. wheels-bot commented on Jun 12, 2026

    @wheels-bot
    Contributor

    Wheels Bot — Triage

    Classified as bug.

    The report describes observable wrong behavior with a clear "what happened / what I expected": ?reload=production&password=<secret> 500s and silently reverts to the file-configured environment, with wheels-errors.log showing No matching function [$LOCATION] found. Verified against the tree: vendor/wheels/events/onapplicationstart.cfc is declared component { (no extends, no mixins), yet line 488 makes a bare $location(url = local.url, addToken = false) call. Every other helper call in that file (26 occurrences) goes through application.wo.$X(...), so $location() is genuinely out of scope at that call site — the reporter's root-cause analysis checks out.

    Suspected layer

    config — see .ai/wheels/security/ for the environment-switch / reload-hardening flow that exercises this path (reloadPassword, ?reload=<env>, redirectAfterReload). The defect itself lives in the application-start/environment-reload code at vendor/wheels/events/onapplicationstart.cfc.

    Fix sketch

    Single-file, mechanical: change vendor/wheels/events/onapplicationstart.cfc:488 from the bare $location(...) call to application.wo.$location(...), matching the established convention used by all 26 other helper calls in the file. The trigger is the redirectAfterReload=true (auto-enabled for production/maintenance by vendor/wheels/events/init/orm.cfm) + url.reload present branch. A structural regression guard (in the spirit of BareCfabortGuardSpec) catching bare in-scope helper calls in non-mixin event CFCs would prevent recurrence — that spec work belongs to propose-fix.

    Confidence: high

    The report pinpoints the exact line, the suspected layer is unambiguous, and the fix is a one-line change matching surrounding convention — independently verified by reading the file. No auto-downgrade rule strictly fires: the edit lands in vendor/wheels/events/**, not in vendor/wheels/security/**, middleware/**, a migration, or the DI container (auth has already succeeded by this redirect step), and the fix is uniform across all engines rather than engine-divergent.

    <!-- wheels-bot:triage:3054 -->
    <!-- wheels-bot:triage-class:bug -->
    <!-- wheels-bot:triage-confidence:high -->

  2. wheels-bot commented on Jun 12, 2026

    @wheels-bot
    Contributor

    Wheels Bot — Fix proposed

    Draft PR: #3055

    Spec: vendor/wheels/tests/specs/security/EventHelperScopeGuardSpec.cfc — failing → passing
    Implementation: vendor/wheels/events/onapplicationstart.cfc (line 488: $location(...) → application.wo.$location(...))

    The fix matches your root-cause analysis exactly: the lone bare $location() call in a no-helper-scope component {. The new structural guard (in the spirit of BareCfabortGuardSpec) fails the suite if any non-mixin event CFC ever again calls an out-of-scope framework helper without the application.wo. receiver. Local run: wheels.tests.specs.security went from 1 fail (pinpointing /events/onapplicationstart.cfc:488 ($location)) to 289 pass, 0 fail, 0 error on Lucee 7 + SQLite.

    A human review is required before merge. The Reviewer will weigh in shortly. Doc updates (MDX guides, .ai/wheels/, CLAUDE.md) are handled separately by bot-update-docs.yml.

  3. added a commit that references this issue on Jun 12, 2026
    10687fb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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