fix(policy): fail-close empty scope and deny reserved actions - #3414
Merged
Conversation
Move AuthorizationSpec into a directory so --filter=Authorization discovers a bundle. S2/S5/S9 fail on the current Policy.scope, production InvalidCollection fall-through, and authorize(action=scope). S6/S7/S8 name the DI identity, guest "", and production 403 contracts. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com>
Policy.scope() returns a no-rows Policy instead of whereIn("id", []).
policyScope() does the same after production InvalidCollection, so a
bad collection never reaches whereIn. authorize() and can() skip
Invoke for init and scope and take the existing deny path.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Peter Amiri <peter@alurium.com>
S8 authorize() denial in production sets 403 via $header and left that status on the runner request. Reset with the same $header helper after the assert and in afterEach. The 403 contract is unchanged. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Policy default-deny still called
whereIn("id", []). An empty id list can becomeIN ()or match every row if the collection is not a QueryBuilder. ProductionInvalidCollectionstayed silent and then made that samewhereIncall.authorize()Invokedscopeandinitwhen those names arrived asparams.action, which 500s onscope()instead of denying.This PR lands S2, S5, S6, S7, S8, and S9 only. S1, S3, and S4 stay HELD.
Scope
vendor/wheels/Policy.cfcscope()returns a no-rowswheels.Policyand does not callwhereIncurrentUser()returns the init identity (guest is"")count()/findAll()/where()/whereIn()keep that empty chainvendor/wheels/controller/authorization.cfcpolicyScope()returns the empty Policy after production InvalidCollectionauthorize()andcan()skip Invoke for reservedinitandscopevendor/wheels/tests/specs/Authorization/AuthorizationSpec.cfc(moved fromcontroller/so--filter=Authorizationdiscovers a directory)g.$header(statusCode = 200), the same helper$notAuthorized()uses.afterEachdoes the same restore so the runner request cannot leave 403.WhereInSpy.cfcandCurrentUserStub.cfcchangelog.d/policy-hardener-s2-s9.fixed.mdCLI stays out. Cache leftovers stay closed. No
onMissingMethod. Unknown actions still throw. Loose CF boolean grant is unchanged. Status code stays 403.Missing-policy production still calls
whereIn("id", [])on a resolved model class. That path is leftover-closed. It is not S10.Tradeoffs
The empty scope is a
wheels.Policyinstance, not a QueryBuilder$alwaysEmptyflag. That keeps fail-closed out ofwhereInand still answerscount()/findAll().Blast Radius
App policies that inherit
scope()get the same no-rows result they already had.policyScope()on an in-flight chain still throwsWheels.Policy.InvalidCollectionin development and testing. Production now returns zero rows instead of callingwhereInon a bad collection.authorize(action="scope")andauthorize(action="init")becomeWheels.NotAuthorized(403 in production) instead of a missing-argument 500.S8 still proves production deny is 403. It no longer leaves that status on the TestBox HTTP request (LuCLI at
03cc18fareportedTest HTTP status: 403, no Results line, CLI 1179/0).Desk S1–S9
onMissingMethod.scope()does not callwhereInwith an empty id list. SpywhereInCallsis 0 andcount()is 0.catch(any)on DIcurrentUseris wrong-user failover. A broken resolver can still hit the authenticator. Not catch-to-guest. Unflipped.authorize()/can()still grant on CF loose boolean"yes"/"true".whereInCallsis 0. A struct withoutwhereIndoes not throw.$currentUserForPolicy()reads the DIcurrentUserservice (id=9001) and the authenticatorSessionStrategy.currentUser()(id=4242).Policy.init()with no user.currentUser()is"", not an object.authorize()deny sets status 403 and does not return the record. Spec andafterEachrestore 200 via$header.authorize(action="scope")andauthorize(action="init")throwWheels.NotAuthorized.can()also skips those names. Specs are authorize()-only.Verification
LuCLI at
03cc18fa76754575099abaaab80167ddc5882562. Test HTTP status 403. No Results line. CLI 1179/0. Cause was S8 leaving 403 on the runner.This head
ae0b9851429616e18468ff86ecc16775baa7650arestores 200 after that assert. Agent-localwheels test --core --ci --filter=Authorizationcrashed before results (Lucee missingincludeon/workspace/public/Application.cfc). Counts from the babysit LuCLI run belong here.No closer keywords. Do not merge.