Repository navigation
Description
Summary
The security-hardening and production-config guides — and the framework's own boot warning — describe a reload-password contract that the dispatch gate does not implement. Three drifts, one root cause (the onRequestStart reload gate predates the #2082 hardening work, which all landed in the cold-start path only):
- Empty
reloadPassworddoes NOT disable plain reload. Withset(reloadPassword=""), any anonymous client can?reload=trueand trigger a fullapplicationStop()restart. Only the environment-switch leg is disabled when empty (onapplicationstart.cfc:188, scoped by fix(config): require non-empty reload password for environment switching #2082). An internet-reachable app with an empty password can be restart-DoS'd in a loop. - The boot warning asserts the opposite.
onapplicationstart.cfc:372-374logs: "reloadPassword is empty — URL-based environment switching and application reload are disabled until a password is set". The reload half of that sentence is false. - Wrong-password attempts against a warm app are invisible and unthrottled. With a password set,
?reload=true&password=wrongpwreturns HTTP 200 (the page is served normally — there is also no 403, contrary to the guide). The attempt produces nowheels_security.logentry and no rate-limit count: the logging + 5-attempts-per-5-minutes lockout (onapplicationstart.cfc:160-178, 219) only execute during application cold start; the warm-path gate inpublic/Application.cfc:268-288falls through silently first. Brute-forcing the reload password against a running app is unmonitored.
Repro (develop @ f668c50, Lucee 7 docker harness)
# stage harness configs, then:
printf '<cfscript>\nset(reloadPassword="");\n</cfscript>\n' >> config/settings.cfm
docker run -d -t --memory=3g -p 60007:60007 -v "$PWD":/wheels-test-suite wheels-test-lucee7:v1.0.1
# 1. empty password: anonymous restart succeeds
curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost:60007/?reload=true' # -> 302, Location: /
# proof of restart: a route appended to config/routes.cfm before the curl goes 404 -> 200
# (also reproduced in production env: production-config audit saw 302 + /smokeprobe4 404->200)
# 2. with a password set: wrong password is silent
# set(reloadPassword="smokepw") instead, restart container, then:
curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost:60007/?reload=true&password=wrongpw' # -> 200 (no 403, no reload)
grep -c 'rejected' logs/wheels_security.log # -> no new entry for the warm-path attemptRoot cause
The reload gate in public/Application.cfc:268-288 explicitly fires when !Len(application.wheels.reloadPassword) (... || !Len(...) || secureCompare(...)), and on mismatch falls through to normal serving with no logging, no 403, no rate-limit bump. All the hardening (#2082: non-empty-password requirement; rejected/accepted logging at onapplicationstart.cfc:219/227; per-IP rate limit at :160-178) lives only in the cold-start path. The boot warning text was written as if #2082 covered plain reload too.
Proposed direction
Decision needed first — should an empty reloadPassword disable URL reload entirely (matching the warning + both guides), or remain a dev convenience? Then:
- If "empty = disabled": gate
?reload=trueon a non-empty password inpublic/Application.cfc(mirror the env-switch leg); update guides to match. - Either way: log warm-path wrong-password attempts to
wheels_security.logwith the source IP and count them against the same per-IP rate limit as the cold-start path. - Fix the boot warning text at
onapplicationstart.cfc:372-374to state exactly what is disabled. - Update
deployment/security-hardening.mdx("If it's empty, both are disabled" + the nonexistent 403) anddeployment/production-config.mdx("Empty disables ?reload= entirely", settings comment + checklist item 2).
Acceptance
- Boot warning text, both guides, and gate behavior agree on what an empty
reloadPasswordmeans. - If "empty = disabled":
?reload=truewith empty password serves the page normally without restarting (harness: no 302, no cold start). - Wrong-password attempts against a warm app are logged with source IP and feed the per-IP rate limit (parity with cold-start), or the docs explicitly scope logging to cold start.
- Specs cover: empty-password warm reload, wrong-password warm reload (log + rate-limit), correct-password warm reload.
Reported by the 2026-06-11 guide behavioral audit (P1 batch 1): security-hardening verifier (claims reload-empty-disables-claim-FALSE [both], reload-403-claim-FALSE, reload-attempts-logged-with-ip) and production-config verifier (claim empty-reloadpw-disables-reload); the env-config verifier independently confirmed the boot-warning text. Findings catalog: docs/superpowers/audits/2026-06-guide-audit-findings.md.
Activity
wheels-bot commentedon Jun 12, 2026
Wheels Bot — Triage
Classified as bug.
The report describes observable wrong behavior backed by repro steps and exact line references, and the defect reproduces in isolation: the warm-path reload gate at
public/Application.cfc:269-280short-circuits on\!Len(application.wheels.reloadPassword), so an emptyreloadPasswordlets anonymous?reload=truetrigger a restart, and on a wrong-password mismatch the warm path falls through to normal serving with nowheels_security.logentry and no per-IP rate-limit bump. The boot warning atvendor/wheels/events/onapplicationstart.cfcseparately asserts the opposite. (The embedded "empty = disabled vs dev convenience" question is a design trade-off, but the logging/rate-limit gap and the misstated warning are defects regardless of how that is decided — hencebug, notframework-design.)Suspected layer
config— the reload-password contract is aconfig/settings.cfmsetting (set(reloadPassword=...)); ground the security reasoning in.ai/wheels/security/(logging, rate-limit,$secureCompareparity with the cold-start path).Fix sketch
Two framework files plus docs. In
public/Application.cfc(the warm-path gate, ~lines 269-280): mirror the env-switch leg so an emptyreloadPasswordno longer satisfies the reload condition, and on a wrong-password mismatch log the attempt towheels_security.logwith source IP and count it against the same per-IP rate limit used in the cold-start path. Invendor/wheels/events/onapplicationstart.cfc(~lines 372-374): correct the boot-warning text to state exactly what an empty password disables. Then reconciledeployment/security-hardening.mdx(the "both are disabled" + 403 claims) anddeployment/production-config.mdxso the warning, both guides, and the gate agree. The hardening logic (#2082) already exists in the cold-start path — the work is extending it to the warm path.Confidence:
mediumAuto-downgraded from high: the fix touches a security/auth reload gate (DoS surface, brute-force logging/rate-limiting), and the "empty password = disabled vs dev convenience" semantics is a real trade-off that must be decided before the gate change lands.
<!-- wheels-bot:triage:3062 -->
<!-- wheels-bot:triage-class:bug -->
<!-- wheels-bot:triage-confidence:medium -->
wheels-bot commentedon Jun 12, 2026
Wheels Bot — Fix on hold for human review
The proposed fix touches a sensitive area (
vendor/wheelsreload-password gate / security logging / rate-limiting — password + auth code) and the bot's safety net requires a human in the loop before any code is written.Two reasons reinforce the hold:
public/Application.cfc:268-288), which performs$secureComparepassword matching with a direct restart-DoS and brute-force surface, plus the requestedwheels_security.log+ per-IP rate-limit parity work. These are exactly the categories the safety net reserves for human authorship.reloadPassworddisable URL reload entirely (matching the warning + both guides), or remain a dev convenience?" The triage comment likewise flagged this as a trade-off that must be settled before the gate change lands. The correct gate behavior — and therefore the spec — cannot be written until a maintainer picks the contract.A maintainer should decide the empty-password contract, then either author the fix directly or re-task the bot with the decision recorded on this issue.
docs(web/guides): correct reload-password behavior claims in security…
docs(web/guides): correct caching guide — cache categories, finder-ca…
docs(web/guides): correct environments guide to audited config load o…
docs(web/guides): correct production-config claims and stale source c…
docs(web/guides): correct debug-panel guide against live framework be…
fix(public): empty reloadPassword disables URL reload; log and rate-l…
Merge remote-tracking branch 'origin/develop' into peter/issue-3060-d…
fix(cli): reload and packages add no longer claim onApplicationStart …
feat(events): dev-mode debug-bar notice when ?reload=true is refused (#…
docs(guides): correct stale 4.1 statements on error email, reload, up…