Security properties — the receipts
Every defensive claim on the homepage maps to a specific test in the open-source repository. This page is the index. Each entry shows the claim, the canonical test file, and the assertion line that proves it.
Architectural enforcement — defense by deletion
The strongest claim MahaGuardian makes is that bypass paths do not exist in the codebase. These tests assert it directly: the function cannot be imported because it has been removed.
The agent has no alternate read path into the vault. Bypass functions deleted.
tests/test_vault_phase3.py :: TestVaultStructuralEnforcement
assert not hasattr(_vault_mod, "_vault_read"), \
"_vault_read still exists — FIX 2 incomplete"
View on GitHub →
enforce() delegates to the canonical pipeline (TLP check, param scan, item lookup). It cannot be bypassed because the bypass calls are mocked and asserted-called.
tests/test_enforce_integration.py :: TestEnforceCallsCanonicalFunctions
assert mock_tlp.called, "enforce() must call check_tlp()"
View on GitHub →
Legacy partition-access enforcement and its error type have been removed entirely. The attack surface is gone.
tests/test_attacks.py :: test_cross_agent_vault_access
assert not hasattr(enforcer_module, "enforce_partition_access"), \
"enforce_partition_access must not exist after SM-001 removal"
View on GitHub →
Guardian API is bound to localhost only. The owner's device is the only host that can reach it.
tests/test_attacks.py :: test_guardian_api_not_accessible_externally
assert GUARDIAN_HOST == "127.0.0.1", \
f"GUARDIAN_HOST must be 127.0.0.1 (localhost only), got {GUARDIAN_HOST}"
View on GitHub →
LLM API keys are never written to disk in plaintext. A filesystem scan after vault operations finds nothing.
tests/test_attacks.py :: test_llm_key_not_in_agent_filesystem
assert secret_value.encode() not in content, \
f"Plaintext LLM key found in {fpath}"
View on GitHub →
Signed rules — SOUL.lock integrity
Agent rules are ed25519-signed by the owner's device. Tampering — direct rewrite, schema corruption, missing signature, hash mismatch — is detected before the rule is loaded.
Direct rewrite of a signed SOUL.lock file is detected by signature + hash verification at load time.
tests/test_attacks.py :: test_soul_lock_direct_write_blocked
# ATTACK: direct write to SOUL.lock
soul_path.write_text("[meta]\nagent = 'evil'\n", encoding="utf-8")
# DEFENCE: verify_soul catches the tamper
with pytest.raises(SOULTamperError):
verify_soul(soul_path, pub_key)
View on GitHub →
All SOUL.lock error paths return the same generic message. An attacker cannot probe internal state from error responses.
tests/test_soul_security.py :: test_all_invalid_soul_errors_use_same_message_prefix
assert msg.startswith("SOUL.lock validation failed"), \
f"Unexpected error format: {msg!r}"
View on GitHub →
Schema validation timing is uniform between valid and invalid inputs. No timing oracle.
tests/test_soul_security.py :: test_valid_vs_invalid_schema_timing_similar
assert abs(avg_valid - avg_invalid) < 0.005, \
f"Timing gap: valid={avg_valid:.4f}s, invalid={avg_invalid:.4f}s"
View on GitHub →
Token enforcement
Guardian Access Tokens are scoped, time-limited, and cryptographically bound to the agent's mTLS certificate. Permissions are derived from the vault at issuance, not from the request.
Tokens are bound to the agent's mTLS certificate. Presenting a token with the wrong certificate is rejected.
tests/test_attacks.py :: test_token_agent_mismatch
# Issue token bound to cert A; present with cert B
with pytest.raises(TokenAgentMismatchError):
verify_token(token_str, cert_b)
View on GitHub →
Expired tokens are rejected at the verification boundary, not at the application layer.
tests/test_attacks.py :: test_expired_token_rejected
with pytest.raises(TokenExpiredError):
verify_token(token_str, agent_cert)
View on GitHub →
Token permissions come from the vault at issuance, not from caller-supplied request fields. Caller cannot elevate by lying.
tests/test_attacks.py :: test_token_permissions_come_from_vault_not_request
View on GitHub →
Cross-matter isolation
The core product claim. An agent working on Engagement A cannot reach Engagement B's data, even when the same human operator is involved in both. Twelve parametrized scenarios exercise allow / deny / elevate across six agent profiles.
Twelve-case truth table: every combination of agent profile × data key produces the expected allow / deny / elevate outcome end-to-end.
tests/test_integration_truth_table.py :: TestIntegrationTruthTable.test_truth_table
TRUTH_TABLE = [
("director_overview", "client_count", "allow"),
("director_asst_a", "v2g_profit_split", "deny"), # partition deny
("financial_analyst_a", "client_count", "elevate"), # human approval
("external_agent_a", "client_count", "deny"), # TLP: GREEN + RESTRICTED
# ... 8 more cases
]
View on GitHub →
Cross-partition access at the HTTP API returns 403 with the generic access_denied message. No information leak about whether the resource exists.
tests/test_attacks.py :: test_cross_partition_via_api
assert response.status_code == 403
assert response.json()["detail"] == "access_denied"
View on GitHub →
Confused-deputy via tool params is caught. An agent with company-a credentials cannot smuggle a company-b path through tool arguments.
tests/test_attacks.py :: test_tool_params_confused_deputy_via_api
# agent_id="alpha" with company-a token, but params reference company-b
"params": {"path": "/vault/company-b/secrets.txt"}
# Expected: 403 — confused-deputy detected by partition scan of params
assert response.status_code == 403
View on GitHub →
Injection of control characters (commas, equals) in payment recipient fields is rejected before reaching accounting. Audit log integrity preserved.
tests/test_attacks.py :: test_recipient_injection_neutralized_at_api
# recipient="Shop,amount=-9999.0" — control char injection
assert response.status_code != 200, \
f"Injection recipient was accepted! {response.json()}"
View on GitHub →
Audit chain integrity
Every enforcement decision is appended to a hash-chained SQLite log keyed with an HMAC. Tampering is detectable; UPDATE and DELETE are blocked at the database authorization layer.
Tampering with any field in a logged decision is detected at verification time. The chain breaks.
tests/test_audit_chain.py :: test_verify_detects_tamper_in_decision
# Direct SQL: flip a logged ALLOW to DENY
conn.execute("UPDATE audit_chain SET decision='DENY' WHERE rowid=1")
assert chain.verify() is False
View on GitHub →
UPDATE on the audit chain table is denied at the SQLite authorization layer, not just by application policy.
tests/test_audit_chain.py :: test_append_only_update_blocked
conn.set_authorizer(
lambda code, *_: sqlite3.SQLITE_DENY if code == sqlite3.SQLITE_UPDATE
else sqlite3.SQLITE_OK
)
with pytest.raises(Exception):
conn.execute("UPDATE audit_chain SET decision='DENY' WHERE rowid=1")
View on GitHub →
ELEVATE decisions (human-approval gate) are logged with the same fields as ALLOW and DENY, in chain order.
tests/test_audit_chain.py :: test_elevate_entry_logged
assert entries[0]["decision"] == "ELEVATE"
View on GitHub →
Side-channel resistance
An attacker probing the enforcement boundary cannot learn whether a resource exists by comparing error messages or response timing.
"Key not found" and "access denied" expose the same safe message at the caller boundary.
tests/test_anti_probing.py :: test_not_found_and_denied_have_same_safe_message
assert e_not_found.value.safe_message \
== e_denied.value.safe_message \
== "access_denied"
View on GitHub →
"Key not found" and "access denied" have comparable response times. No timing oracle.
tests/test_anti_probing.py :: test_not_found_vs_denied_timing_comparable
assert diff_ms < 50, (
f"Timing oracle detected: mean not_found={mean_nf*1000:.2f}ms, "
f"mean denied={mean_denied*1000:.2f}ms, diff={diff_ms:.2f}ms"
)
View on GitHub →