Skip to content

fix: anti-pattern-11 url shadowing in $buildRedirectUrl broke every Adobe reload (#3053) - #3057

Merged
bpamiri merged 1 commit into
developfrom
peter/issue-3053-buildredirecturl-url-shadowing
Jun 12, 2026
Merged

fix: anti-pattern-11 url shadowing in $buildRedirectUrl broke every Adobe reload (#3053)#3057
bpamiri merged 1 commit into
developfrom
peter/issue-3053-buildredirecturl-url-shadowing

Conversation

@bpamiri

@bpamiri bpamiri commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Fixes #3053.

Root cause — CLAUDE.md Anti-Pattern 11 (reserved scope names)

#3036 (a6df6be) added unscoped URL-scope reads (StructKeyExists(url, "reload"), IsBoolean(url.reload), ...) inside public/Application.cfc::$buildRedirectUrl(), which has always declared a string local named url. On Adobe CF, unscoped name resolution finds local.url before the URL scope, so the new checks dereferenced a string and threw dereference a scalar variable of type class java.lang.String — HTTP 500 before applicationStop(). Every password-gated reload (?reload=true&password=...), URL environment switch (?reload=<env>&password=...), and wheels reload (drives the same URL) was broken on Adobe since the merge. Lucee was unaffected (the url scope always wins there).

Changes

  1. Rename local.urllocal.redirectPath in $buildRedirectUrl() across all four same-lineage copies: public/Application.cfc, cli/lucli/templates/app/public/Application.cfc, examples/starter-app/public/Application.cfc, examples/tweet/public/Application.cfc. Full-file audit found no other local/argument named after a reserved scope in any copy (checked onRequestStart and $handleRestartAppRequest too — they read the url scope but declare no shadowing locals).
  2. Spec guard: ReloadEnvironmentSwitchParitySpec gains a fifth it-block per copy that pins local.redirectPath and fails if any local.url / var url declaration (or use) reappears anywhere in these files — line-anchored scan, comment lines skipped (anti-pattern 14).
  3. Probe-gap closure: tools/ci/smoke-env.sh probe 6 — "authorized reload restarts cleanly": with SMOKE_RELOAD_PASSWORD set, GET /?reload=true&password=$SMOKE_RELOAD_PASSWORD must answer 302 (not 5xx, not 200); SKIP log line when unset. Wired SMOKE_RELOAD_PASSWORD: "wheels-dev" (the CI app's reloadPassword from config/settings.cfm) into both smoke-env.yml matrix legs. This probe turns the Adobe CF: every password reload and URL env switch returns HTTP 500 — local.url shadows the url scope in $buildRedirectUrl (#3036 regression) #3053 failure class into a red CI leg the moment an Adobe smoke leg exists (smoke-env: add an Adobe engine leg so the #3029 failure class is CI-gated #3047).

RED → GREEN evidence (local docker harness, sqlite)

Adobe 2023 (wheels-test-adobe2023:v1.0.1), development env

Probe develop f668c50 (RED) after fix (GREEN)
/?reload=true&password=smokepw 500dereference a scalar variable ... java.lang.String, stack BUILDREDIRECTURL <- HANDLERESTARTAPPREQUEST 302 Location: / (params stripped), app restarts, root 200 after
/?reload=testing&password=smokepw 500 (same error) 302 preserving reload+password; chain terminates; /wheels/info flips 200 → 404 (switch applied)
  • Targeted spec bundle on Adobe: wheels.tests.specs.cli.ReloadEnvironmentSwitchParitySpec20/20 pass (5 it-blocks × 4 copies, includes the new guard).
  • smoke-env.sh vs Adobe in testing: without SMOKE_RELOAD_PASSWORD → probes 1–5 PASS, probe 6 SKIP, exit 0. With SMOKE_RELOAD_PASSWORD=smokepwall 6 PASS (probe 6: 302), exit 0. On unfixed develop probe 6 would have got the 500 → FAIL.

Lucee 7 control (wheels-test-lucee7:v1.0.0)

  • /?reload=true&password=smokepw302 Location: /, root 200 after restart (unchanged behavior).
  • /?reload=testing&password=smokepw302 preserving params, chain terminates, /wheels/info → 404 (switch applied, no loop).
  • smoke-env.sh in testing: probe 6 SKIP without the var (exit 0), all 6 PASS with it (exit 0).
  • Full core suite (sqlite): 4402 pass / 0 errors / 18 skipped; the only 12 failures are the pre-existing tolerated internal.testClientSpec container artifacts.

Spec guard RED check

Injected local.url = "spec-red-probe"; into the tweet copy → parity bundle 19 pass / 1 fail, failing spec named exactly never shadows the url scope with a local named url in examples/tweet/public/Application.cfc. Reverted → 20/20.

Notes

  • bash -n tools/ci/smoke-env.sh clean; probe 6 is last in the script because it genuinely restarts the app.
  • Acceptance items for adobe2025/BoxLang ride on the same single-lineage code path; the structural spec runs on every engine in the matrix.

🤖 Generated with Claude Code

…3053)

PR #3036 added unscoped URL-scope reads (StructKeyExists(url, "reload"),
url.reload, ...) inside $buildRedirectUrl(), which had always declared a
string local named url. On Adobe CF unscoped name resolution finds the
local before the URL scope, so every password-gated reload, URL
environment switch, and 'wheels reload' dereferenced a string and
returned HTTP 500 before applicationStop() — CLAUDE.md anti-pattern #11
(reserved scope names). Lucee was unaffected because the url scope
always wins there.

- Rename local.url to local.redirectPath in all four same-lineage
  copies of public/Application.cfc (repo demo app, CLI app template,
  starter-app and tweet examples); full-file audit found no other
  reserved-scope locals or arguments.
- ReloadEnvironmentSwitchParitySpec gains a fifth it-block per copy
  that pins the rename and fails if any local/var named url reappears
  in these files (line-anchored scan, comment lines skipped).
- Probe-gap closure: tools/ci/smoke-env.sh probe 6 asserts an
  authorized reload (correct password) answers 302, not 5xx/200.
  Opt-in via SMOKE_RELOAD_PASSWORD; SKIPs when unset. Wired into both
  smoke-env.yml matrix legs with the CI app's reloadPassword. This is
  the probe that would have caught #3053 on an Adobe leg.

Fixes #3053

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer

TL;DR: This PR fixes the Adobe-only HTTP 500 on every password-gated reload (#3053) by renaming the reserved-scope local local.urllocal.redirectPath in $buildRedirectUrl() across all four same-lineage copies of public/Application.cfc, adds a structural spec guard pinning the rename, and closes the probe gap with an opt-in smoke probe asserting an authorized reload answers 302. The diagnosis is correct (CLAUDE.md anti-pattern #11 — unscoped name resolution finds local.url before the URL scope on Adobe CF), the fix is minimal and complete, and the RED→GREEN evidence on both Adobe 2023 and Lucee 7 is thorough. Verdict: comment — one non-blocking test-hardening nit below; everything else checks out.

Tests

  • Guard doesn't cover argument declarations named urlvendor/wheels/tests/specs/cli/ReloadEnvironmentSwitchParitySpec.cfc:254-255:

    reFindNoCase("local\.url[^a-z0-9_]", line & " ") > 0
    || reFindNoCase("var[ #Chr(9)#]+url[ #Chr(9)#=;]", line & " ") > 0

    This catches local.url and var url declarations, but a future signature change like public string function $buildRedirectUrl(string url) would shadow the unscoped URL-scope reads just as fatally (the arguments scope also precedes the url scope in unscoped resolution) without tripping the guard. The spec comment at line 234 says "No local/var declaration (or any other use) named url" — the parenthetical slightly overstates what the regex matches. Suggested follow-up (fine in this PR or a later one): add a third pattern for parameter declarations on function-bearing lines, or simply tighten the comment to match the implemented coverage. Non-blocking — the PR body's manual audit confirmed no such parameter exists today.

What I verified (and found clean)

  • Correctness: The rename is applied consistently in all four copies (public/Application.cfc:469-543, CLI template, starter-app, tweet); grep -nE 'local\.url[^a-zA-Z0-9_]|var[ \t]+url[ \t=;]' across all four files returns nothing. The intentional unscoped URL-scope reads (StructKeyExists(url, "reload") at public/Application.cfc:447,504) are untouched and correctly not flagged by the new guard's patterns.
  • Cross-engine: The new spec it-block follows the exact prior-art pattern of the existing four (IIFE loop-variable capture at lines 75/268, contains operator, ## escapes in string literals per the CLAUDE.md test gotcha). No Left(str, 0) risk (empty lines are skipped before Left(line, 2)), the for loop is not inside a finally (Lucee 7 invariant #12), and ListToArray(content, Chr(10), true) preserves empty lines so reported offender line numbers stay accurate.
  • Smoke probe: tools/ci/smoke-env.sh:93-102 mirrors the existing probe 4/5 style (set -u script, explicit 000 handling, password masked as *** in FAIL output), is correctly opt-in and last (the restart can't perturb earlier probes). SMOKE_RELOAD_PASSWORD: "wheels-dev" in .github/workflows/smoke-env.yml:47 matches set(reloadPassword = "wheels-dev") at config/settings.cfm:29, and the workflow's paths filters (public/**, tools/ci/smoke-env.sh, the workflow itself) guarantee both matrix legs exercise the new probe on this PR.
  • Docs: Changelog fragment present at changelog.d/3053-buildredirecturl-url-shadowing.fixed.md (correct <slug>.fixed.md form, no direct CHANGELOG.md edit). The shadowing class is already documented as CLAUDE.md anti-pattern #11, so no further doc update is required.
  • Commits: Single commit efdd2bfe7fix: type from the allowlist, subject ≤ 100 chars, body explains the why, DCO sign-off present.
  • Security: No injection surface (the probe URL uses a non-secret CI password by design, acknowledged in the workflow comment); the spec is read-only structural scanning.

@bpamiri
bpamiri merged commit ff65858 into develop Jun 12, 2026
8 checks passed
@bpamiri
bpamiri deleted the peter/issue-3053-buildredirecturl-url-shadowing branch June 12, 2026 04:22
bpamiri added a commit that referenced this pull request Jun 12, 2026
…ites from behavioral audit (#3068)

Audit of deployment/production-config.mdx against develop (Lucee 7 + Adobe 2023
harness) found seven docs-wrong claims and a batch of stale source citations.
All corrections re-verified against the current develop head (post-#3057/#3058
line shifts).

- Empty reloadPassword does NOT disable ?reload= — only URL env-switching;
  a bare ?reload=true still restarts the app unauthenticated (refs #3062)
- wheels dbmigrate latest -> wheels migrate latest (no dbmigrate verb)
- csrfStore defaults to "session" unconditionally; cookie storage is an
  explicit opt-in (checklist 5 parenthetical was false)
- flash storage selection cite: orm.cfm:57-64, not security.cfm:49-55
- redirectAfterReload cite: orm.cfm:26/:52-54 (also flips for maintenance),
  not security.cfm:43-45
- wheels doctor scope: structure/files/permissions/datasource only — it does
  not audit the production checklist items
- Refresh all stale line refs (settings cascade, env-switch resolver,
  migrate-down gate, settings table, dotenv step 5, secure compare,
  rate limit, maintenance page, boot warning, auto-migrate gate)

verify:docs passes (3 tagged blocks, 0 failed).

Signed-off-by: Peter Amiri <peter@alurium.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
bpamiri added a commit that referenced this pull request Jun 12, 2026
…ve case-sensitive filesystems (#3071)

The reload gate in onRequestStart() and the shared-application-name branch
in onSessionStart() dispatch their handlers through $simpleLock with
componentReference = "application" — a component PATH that Global.cfc's
$invoke hands to cfinvoke. On case-sensitive filesystems Adobe CF resolves
CFC names by exact case then all-lowercase, so the lowercase literal never
matches Application.cfc and every authorized reload
(?reload=true&password=...) returns HTTP 500 "Could not find the ColdFusion
component or interface application" — in EVERY environment, development
included. Lucee resolves case-insensitively and was unaffected, and macOS
bind mounts are case-insensitive, which is why #3057's local verification
and the Lucee smoke legs stayed green while the #3051 Adobe smoke legs
(Linux runners) caught it.

Fix: case-exact "Application" literal in all four same-lineage copies
(repo demo app, CLI app template, starter-app and tweet examples), pinned
by a sixth it-block in ReloadEnvironmentSwitchParitySpec (line-anchored,
comment-skipping, case-sensitive scan).

Verified: pre-fix 500 reproduced on a case-sensitive Docker volume in
testing, production AND development; post-fix all six smoke probes pass in
testing + production on the same volume, case-insensitive Adobe control
unaffected, Lucee control green, full Lucee core suite at baseline
(4409 pass / 12 tolerated testClientSpec artifacts).

Signed-off-by: Peter Amiri <peter@alurium.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adobe CF: every password reload and URL env switch returns HTTP 500 — local.url shadows the url scope in $buildRedirectUrl (#3036 regression)

1 participant