@objectstack/lint@17.3.0. Measured on objectstack-ai/hotcrm at ff43321, four injections into ONE site — src/profiles/sales-manager.profile.ts, policy opportunity_private_owner_only on crm_opportunity, whose shipped using is is_private == false || owner_id == current_user.id. Same run, same command (objectstack lint --json).
Measured
using written at that site |
lint |
finding |
billing_address.country == "US" (not pushdownable) |
exit 1 |
rls-predicate-unenforceable at error — CONTROL |
is_private == = false (unparseable) |
exit 1 |
rls-predicate-unparseable at error — CONTROL |
is_private_nope == false || owner_id == current_user.id |
exit 0 |
none |
is_private == false || owner_id == current_user.nope |
exit 0 |
none |
Baseline for all four: passed:true · errors:0 · warnings:17 · suggestions:12. The two silent rows moved nothing.
So the RLS rules cover the shape of a predicate (does it parse, can it be lowered) and not its references — neither the field names nor the current_user variables are resolved against anything.
Why the two silent rows are the expensive ones
Both fail closed, which is the opposite of a cosmetic defect:
- An unknown field is what a column RENAME leaves behind. The policy referencing it stops narrowing the way it was authored to, and the layer denies.
current_user.* is pre-resolved from a fixed set (hotcrm's own guard names id, organization_id, positions, org_user_ids, email). An unknown one compiles to nothing and the policy fails closed — every holder of the permission set loses the object entirely.
A rename that silently converts an authored narrowing into a deny-all is exactly the class the two shipped rules were written for; it just happens to be the half they do not check. Note this is the same field-existence asymmetry one surface over that #16108 records for security-fls-unqualified-key (unqualified key reported, qualified-but-dangling key silent).
How it was found
objectstack-ai/hotcrm#1625 retires local test assertions proven duplicate of platform rules. hotcrm's test/authorization-coverage.test.ts carries two sweeps — every predicate is pushdownable and names real fields and the first field of a "using" predicate exists (the engine drops the policy otherwise) — which red on both silent rows above. Meanwhile rls-predicate-unparseable fires on a shape those sweeps accept (their clause regex reads is_private == = false as field/op/rhs). ⇒ Neither side subsumes the other, so both assertions were kept rather than retired, and the two rule ids were reported as non-retirable.
Dedupe ran before filing — #16108, #6778, #3539, #13639 read; none covers this. Unassigned.
@objectstack/lint@17.3.0. Measured onobjectstack-ai/hotcrmatff43321, four injections into ONE site —src/profiles/sales-manager.profile.ts, policyopportunity_private_owner_onlyoncrm_opportunity, whose shippedusingisis_private == false || owner_id == current_user.id. Same run, same command (objectstack lint --json).Measured
usingwritten at that sitebilling_address.country == "US"(not pushdownable)rls-predicate-unenforceableat error — CONTROLis_private == = false(unparseable)rls-predicate-unparseableat error — CONTROLis_private_nope == false || owner_id == current_user.idis_private == false || owner_id == current_user.nopeBaseline for all four:
passed:true · errors:0 · warnings:17 · suggestions:12. The two silent rows moved nothing.So the RLS rules cover the shape of a predicate (does it parse, can it be lowered) and not its references — neither the field names nor the
current_uservariables are resolved against anything.Why the two silent rows are the expensive ones
Both fail closed, which is the opposite of a cosmetic defect:
current_user.*is pre-resolved from a fixed set (hotcrm's own guard namesid,organization_id,positions,org_user_ids,email). An unknown one compiles to nothing and the policy fails closed — every holder of the permission set loses the object entirely.A rename that silently converts an authored narrowing into a deny-all is exactly the class the two shipped rules were written for; it just happens to be the half they do not check. Note this is the same field-existence asymmetry one surface over that #16108 records for
security-fls-unqualified-key(unqualified key reported, qualified-but-dangling key silent).How it was found
objectstack-ai/hotcrm#1625retires local test assertions proven duplicate of platform rules. hotcrm'stest/authorization-coverage.test.tscarries two sweeps —every predicate is pushdownable and names real fieldsandthe first field of a "using" predicate exists (the engine drops the policy otherwise)— which red on both silent rows above. Meanwhilerls-predicate-unparseablefires on a shape those sweeps accept (their clause regex readsis_private == = falseas field/op/rhs). ⇒ Neither side subsumes the other, so both assertions were kept rather than retired, and the two rule ids were reported as non-retirable.Dedupe ran before filing — #16108, #6778, #3539, #13639 read; none covers this. Unassigned.