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 — defence 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"

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()"

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"

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}"

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}"

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)

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}"

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"

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)

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)

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

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 parametrised 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 ]

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"

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

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()}"

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 authorisation 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

UPDATE on the audit chain table is denied at the SQLite authorisation 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")

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"

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"

"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" )
What these tests do and do not prove

A passing test proves that one specific scenario behaves as the test author intended. It does not prove the absence of bugs, nor does it prove correctness against attacks that were not yet imagined when the test was written. We list canonical tests here so that any security reviewer can verify our claims directly and so that we can be held to a specific bar in conversation. This is evidence of intent and rigour, not a proof of soundness. We commission third-party security review in the V2 timeframe, and we will publish findings.