GHSA-7R7F-9XPJ-JMR7
Vulnerability from github – Published: 2025-10-13 13:33 – Updated: 2025-10-13 13:33Summary
When using filter authorization, two edge cases could cause the policy compiler/authorizer to generate a permissive filter:
-
Bypass policies whose condition can never pass at runtime were compiled as
OR(AND(condition, compiled_policies), NOT(condition)). If the condition could never be true at runtime, theNOT(condition)branch evaluated truthy and the overall expression became permissive. -
Runtime policy scenarios that reduce to “no checks are applicable” (an empty SAT scenario) were treated as an empty clause and dropped instead of being treated as
false, which could again produce an overly broad (permissive) filter.
These bugs could allow reads to return records that should have been excluded by policy.
Impact
Projects that rely on filter-based authorization and define:
bypass ... do ... endblocks whose condition(s) are only resolvable at runtime and can never pass in a given request context, or- runtime checks that simplify to an empty scenario for a clause
may unintentionally generate a permissive query filter, potentially returning unauthorized data.
Actions primarily affected: reads guarded by filter policies. Non-filter (e.g., hard forbid) policies are not impacted.
Technical details
This patch corrects two behaviors:
-
Ash.Policy.Policy.compile_policy_expression/1now treats bypass blocks asAND(condition_expression, compiled_policies)instead ofOR(AND(...), NOT(condition_expression)). This removes the permissiveNOT(condition)escape hatch when a bypass condition never passes. -
Ash.Policy.Authorizernow treats empty SAT scenarios (scenario == %{}) asfalse, ensuring impossible scenarios do not collapse into a no-op and inadvertently widen the filter. The reducer also normalizesnil→falseconsistently when buildingauto_filterfragments.
Relevant changes are in:
lib/ash/policy/policy.ex(bypass compilation)lib/ash/policy/authorizer/authorizer.ex(scenario handling / auto_filter normalization)- Tests added:
test/policy/filter_condition_test.exs(RuntimeFalsyCheck,RuntimeBypassResource) validate the corrected behavior.
Workarounds
- Avoid
bypasspolicies whose conditions are only decidable at runtime and may be perpetually false in some contexts; prefer explicitauthorize_if/forbid_ifblocks withoutbypassfor those cases. - Add an explicit final
forbid_if always()guard for sensitive reads as a belt-and-suspenders fallback until user can upgrade. - Where feasible, replace runtime-unknown checks with strict/compile-time checks or restructure to avoid empty SAT scenarios.
How to tell if user is affected
User is likely affected if ALL of the following are true:
- Uses filter authorization; and
- Defines
bypassblock withaccess_type :runtimewithout any policies after it; or - Defines
bypassblocks whose conditions are evaluated at runtime (e.g., checks withstrict_check/3returning:unknownand a runtimecheck/4that may never succeed in some contexts) without any policies after it
A quick sanity test is to issue a read expected to return no rows under such a bypass or runtime-falsy condition and verify it indeed returns []. The included test bypass works with filter policies demonstrates the corrected, non-permissive behavior.
{
"affected": [
{
"package": {
"ecosystem": "Hex",
"name": "ash"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.6.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-48043"
],
"database_specific": {
"cwe_ids": [
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2025-10-13T13:33:22Z",
"nvd_published_at": "2025-10-10T16:15:52Z",
"severity": "HIGH"
},
"details": "### Summary\n\nWhen using **filter** authorization, two edge cases could cause the policy compiler/authorizer to generate a permissive filter:\n\n1. **Bypass policies whose condition can never pass at runtime** were compiled as\n `OR(AND(condition, compiled_policies), NOT(condition))`.\n If the condition could never be true at runtime, the `NOT(condition)` branch evaluated truthy and the overall expression became permissive.\n\n2. **Runtime policy scenarios that reduce to \u201cno checks are applicable\u201d** (an empty SAT scenario) were treated as an empty clause and dropped instead of being treated as **`false`**, which could again produce an overly broad (permissive) filter.\n\nThese bugs could allow reads to return records that should have been excluded by policy.\n\n### Impact\n\nProjects that rely on **filter-based authorization** and define:\n\n* `bypass ... do ... end` blocks whose condition(s) are only resolvable at runtime and can never pass in a given request context, **or**\n* runtime checks that simplify to an **empty** scenario for a clause\n\nmay unintentionally generate a permissive query filter, potentially returning unauthorized data.\n\n*Actions primarily affected:* reads guarded by filter policies. Non-filter (e.g., hard forbid) policies are not impacted.\n\n### Technical details\n\nThis patch corrects two behaviors:\n\n* **`Ash.Policy.Policy.compile_policy_expression/1`** now treats **bypass** blocks as\n `AND(condition_expression, compiled_policies)`\n instead of `OR(AND(...), NOT(condition_expression))`. This removes the permissive `NOT(condition)` escape hatch when a bypass condition never passes.\n\n* **`Ash.Policy.Authorizer`** now treats **empty SAT scenarios** (`scenario == %{}`) as **`false`**, ensuring impossible scenarios do not collapse into a no-op and inadvertently widen the filter. The reducer also normalizes `nil` \u2192 `false` consistently when building `auto_filter` fragments.\n\nRelevant changes are in:\n\n* `lib/ash/policy/policy.ex` (bypass compilation)\n* `lib/ash/policy/authorizer/authorizer.ex` (scenario handling / auto_filter normalization)\n* Tests added: `test/policy/filter_condition_test.exs` (`RuntimeFalsyCheck`, `RuntimeBypassResource`) validate the corrected behavior.\n\n### Workarounds\n\n* Avoid `bypass` policies whose conditions are only decidable at runtime and may be perpetually false in some contexts; prefer explicit `authorize_if`/`forbid_if` blocks without `bypass` for those cases.\n* Add an explicit **final `forbid_if always()`** guard for sensitive reads as a belt-and-suspenders fallback until user can upgrade.\n* Where feasible, replace runtime-unknown checks with strict/compile-time checks or restructure to avoid empty SAT scenarios.\n\n### How to tell if user is affected\n\nUser is likely affected if ALL of the following are true:\n\n* Uses **filter authorization**; and\n* Defines `bypass` block with `access_type :runtime` without any policies after it; or\n* Defines `bypass` blocks whose conditions are evaluated at runtime (e.g., checks with `strict_check/3` returning `:unknown` and a runtime `check/4` that may never succeed in some contexts) without any policies after it\n\nA quick sanity test is to issue a read expected to return **no** rows under such a bypass or runtime-falsy condition and verify it indeed returns `[]`. The included test `bypass works with filter policies` demonstrates the corrected, non-permissive behavior.",
"id": "GHSA-7r7f-9xpj-jmr7",
"modified": "2025-10-13T13:33:22Z",
"published": "2025-10-13T13:33:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/ash-project/ash/security/advisories/GHSA-7r7f-9xpj-jmr7"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-48043"
},
{
"type": "WEB",
"url": "https://github.com/ash-project/ash/commit/66d81300065b970da0d2f4528354835d2418c7ae"
},
{
"type": "PACKAGE",
"url": "https://github.com/ash-project/ash"
},
{
"type": "WEB",
"url": "https://github.com/ash-project/ash/releases/tag/v3.6.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Ash Framework: Filter authorization misapplies impossible bypass/runtime policies"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.