feat(security): per-tenant isolation for spend, secrets, strict mode#337
Merged
Conversation
Closes 3 multi-tenant isolation gaps flagged by PR #326's regression suite. The 3 #[ignore]'d tests now pass. * Per-tenant budget: SpendTracker grows check_tenant_budget(tenant,...) backed by per-tenant in-memory caches and per-tenant JSONL journals at spend/<tenant>/<YYYY-MM>.jsonl. record_tenant no longer also accumulates into the global counter, so a tenant overspend cannot trip the global budget for other tenants. * Per-tenant secrets: SecretBackend::get takes (tenant, name). All three backends (LocalEncrypted, Env, File) resolve <tenant>/<name> first and fall back to the legacy flat layout for global secrets so single-tenant deployments need no migration. * strict_tenant flag: new [security] strict_tenant config field plus tenant_required_middleware that returns HTTP 400 with a missing_tenant body when the flag is on and no tenant id can be resolved (X-Tenant-ID header, JWT claim, or virtual key). * Tenant id source priority: VirtualKeyContext > JWT claim > X-Tenant-ID header. Header is only consulted when no authenticated tenant exists, so a client cannot impersonate a JWT tenant. API breaking changes (all internal except SecretBackend trait): - SecretBackend::get: now takes &str tenant before &str name. - SpendTracker grows check_tenant_budget plus DEFAULT_TENANT bucket. - SpendTracker::record_tenant stops also calling self.record(...). Tests: 1345 nextest, fmt+clippy clean. The 3 previously #[ignore]'d multi_tenant_isolation tests now pass alongside the 4 already-passing ones (7 active, 1 still ignored for ToolSpikeDetector from PR #308). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Mutation testing (PR diff sample)Informational — never blocks merge. Full matrix runs on main.
Legend: clean (no survivors), missed (inspect artifact), timed-out (25 min cap reached). Artifact: mutants-pr-results-52b2664853a86d444469cf3f504709a97c88c701. |
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.
Summary
Closes 3 multi-tenant isolation gaps flagged by PR #326's regression suite. The 3 `#[ignore]`'d tests now pass.
API breaking changes (all internal except `SecretBackend` trait)
Test plan
Origin
This commit was authored by background agent `a6ab9ef6391d6f6f5` on 2026-04-28 but the agent hit org usage cap before opening the PR. Commit content unchanged from `2fe3c1c`; cherry-picked onto current main (which already includes #326's regression suite).
🤖 Generated with Claude Code