You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
do_orm_execute tripwire requiring a visibility decision #815
Makes forgetting a visibility decision a loud failure at the query, complementing the traversal-site failure from #814.
Scope
The listener as specified in the epic — three sets, one subtraction:
involved — guarded entities this statement touches.
decided — entities that brought a with_loader_criteria. The reached path.
asserted — entities the caller declared it will assert_permission on instead. The named path.
The opt-out is entity-scoped, not statement-scoped. Loader criteria propagate to lazy loads; execution options do not — a filter follows the data downstream, a promise dies with the statement that made it.
Also lands the system principal for the worker.
Acceptance criteria
select(ScoreSet) with no visibility decision raises UnscopedQueryError.
select(ScoreSet).options(with_loader_criteria(ScoreSet, clause)) succeeds and filters.
Eager-loading calibrations with criteria on ScoreSet only raises, naming ScoreCalibration.
select(func.count()).select_from(ScoreCalibration) with no decision raises; with criteria it returns the visibility-correct count.
.execution_options(visibility_asserted=(ScoreSet,)) excuses only ScoreSet, and the exemption does not propagate to lazy loads from that result.
Queries touching no guarded entity are unaffected.
A regression test asserts the tripwire still fires, so a SQLAlchemy upgrade that breaks the private-API access fails CI rather than silently disarming enforcement.
Worker jobs carry a system principal whose clause evaluates to true() and attach it like any other caller; none use a raw opt-out.
Notes
Private API risk.statement._with_options and LoaderCriteriaOption._all_mappers() are not public API, and LoaderCriteriaOption lives in sqlalchemy.orm.util. A version bump could break the tripwire, and it would break open. The regression test is a hard requirement, not a nice-to-have.
Not planned — superseded by the revision of #808 to Postgres row-level security.
The tripwire existed to make a forgotten with_loader_criteria a loud failure. With enforcement in the database there is no per-query visibility decision to forget, so there is nothing for it to guard.
It is also worth recording why this is a net safety improvement rather than a lateral move: the listener depended on statement._with_options and LoaderCriteriaOption._all_mappers(), neither of which is public API. A SQLAlchemy upgrade could have broken it, and it would have broken open — enforcement silently disabled with tests still green. The regression test in the original spec was a mitigation, not a guarantee.
The equivalent risk under RLS is a misconfigured role, which is silent in the same way. That is covered by the startup enforcement assertion instead, which checks rolsuper, inherited rolbypassrls, NOINHERIT, and policy bodies at boot.
Activity
bencap commentedon Aug 7, 2026
Not planned — superseded by the revision of #808 to Postgres row-level security.
The tripwire existed to make a forgotten
with_loader_criteriaa loud failure. With enforcement in the database there is no per-query visibility decision to forget, so there is nothing for it to guard.It is also worth recording why this is a net safety improvement rather than a lateral move: the listener depended on
statement._with_optionsandLoaderCriteriaOption._all_mappers(), neither of which is public API. A SQLAlchemy upgrade could have broken it, and it would have broken open — enforcement silently disabled with tests still green. The regression test in the original spec was a mitigation, not a guarantee.The equivalent risk under RLS is a misconfigured role, which is silent in the same way. That is covered by the startup enforcement assertion instead, which checks
rolsuper, inheritedrolbypassrls,NOINHERIT, and policy bodies at boot.