gabriel / muse public
patch dev
AI Agent gabriel · 149 days ago · Apr 25, 2026 · Diff

security: audit doc line-by-line pass — all test checklist items complete

auth.py: - run_keygen now rejects duplicate identity without --force (guards both human and agent entries by checking load_identity before proceeding)

tests/test_hd_keygen_unified.py: - Remove 8 failing PEM-asserting tests (test_pem_written, test_pem_mode_600, test_agent_pem_*, test_agent_key_different_from_human_key, etc.) - Add hermetic keychain patching to _keygen and _setup_operator helpers - Replace with: test_no_pem_written, test_hd_path_in_identity_toml, test_agent_no_pem_written, test_agent_hd_path_in_identity_toml, fingerprint-based key-distinctness tests - Remove test_generate_hd_keypair_exists (pinned vestigial function)

tests/test_cmd_auth_keygen_hd.py: - Replace test_pem_file_written / test_pem_is_valid_ed25519_key with test_derive_hd_public_info_* (no disk writes) - test_second_keygen_succeeds_without_force → test_second_keygen_without_force_rejected - Remove stale key_path fields from IdentityEntry literals - Remove stale load_pem_private_key import

tests/test_agent_signing.py: - Delete TestLoadPrivateKeyFromPem class - Remove stale key_path fields from identity entries - Remove Encoding/NoEncryption/PrivateFormat imports

tests/test_core_keychain.py: - Remove stale key_path fields from IdentityEntry test data

tests/test_security_key_permissions.py: - DELETED — tested _load_private_key_from_path permission checks (PEM arch)

tests/test_security_no_pem_on_disk.py: - NEW — NP-1 through NP-5: keygen writes no PEM, resolve needs no keys/, security-check passes, fresh install is clean

tests/test_resolve_signing_identity_keychain_path.py: - NEW — KC-1 through KC-7: full keychain→derive→sign→verify chain, graceful None returns, determinism

docs/key-material-security-audit.md: - Current State section updated to reflect phases 1-7 complete - H5 marked Fixed (Phase 5); M1 marked Fixed (Phase 6) - Test Checklist fully updated; Files to Delete table shows blockers

sha256:794449e7457b82dc5e2c97854618810e77aa6d13267acffe12cb9f85daeb2a17 sha
+62 ~42 −42 symbols
sha256:dd045f90da933d42fa0d6501cfc2e9f3c33c29a37c28e0f1a0a92907610b77cc snapshot
+62
symbols added
~42
symbols modified
−42
symbols removed
0
dead code introduced
Semantic Changes 146 symbols
+ TestDeterministic class class TestDeterministic L176–214
+ test_KC_6_same_mnemonic_same_key method method test_KC_6_same_mnemonic_same_key L177–191
+ test_KC_7_different_hd_paths_different_keys method method test_KC_7_different_hd_paths_different_keys L193–214
+ TestFullChain class class TestFullChain L87–127
+ test_KC_1_returns_signing_identity method method test_KC_1_returns_signing_identity L88–99
+ test_KC_2_derived_key_signs_verifiable_message method method test_KC_2_derived_key_signs_verifiable_message L101–127
+ TestGracefulNone class class TestGracefulNone L135–168
+ test_KC_3_no_mnemonic_returns_none method method test_KC_3_no_mnemonic_returns_none L136–146
+ test_KC_4_no_identity_entry_returns_none method method test_KC_4_no_identity_entry_returns_none L148–154
+ test_KC_5_no_hd_path_returns_none method method test_KC_5_no_hd_path_returns_none L156–168
+ _AGENT_HD_PATH variable variable _AGENT_HD_PATH L35–35
+ _HD_PATH variable variable _HD_PATH L34–34
+ _HUB variable variable _HUB L33–33
+ _MNEMONIC variable variable _MNEMONIC L29–32
+ _fingerprint function function _fingerprint L76–79
+ _save_human_entry function function _save_human_entry L66–73
+ Ed25519PrivateKey import import Ed25519PrivateKey L23–23 ← moved from tests/test_security_key_permissions.py
+ annotations import import annotations L18–18
+ derive_hd_public_info import import derive_hd_public_info L26–26
+ mnemonic_to_seed import import mnemonic_to_seed L27–27
+ pathlib import import pathlib L20–20
+ pytest import import pytest L22–22
+ resolve_signing_identity import import resolve_signing_identity L25–25
+ save_identity import import save_identity L25–25
+ isolated_env function function isolated_env L44–56
+ keychain_mnemonic function function keychain_mnemonic L60–63
~ tests/test_security_no_pem_on_disk.py .py 23 symbols added
+ TestFreshInstallNoPem class class TestFreshInstallNoPem L201–208
+ test_NP_5_fresh_keys_dir_has_no_pem method method test_NP_5_fresh_keys_dir_has_no_pem L202–208
+ TestNoPemAfterAgentKeygen class class TestNoPemAfterAgentKeygen L101–112
+ test_NP_2_no_pem_in_keys_dir method method test_NP_2_no_pem_in_keys_dir L102–112
+ TestNoPemAfterHumanKeygen class class TestNoPemAfterHumanKeygen L72–93
+ test_NP_1_no_pem_in_keys_dir method method test_NP_1_no_pem_in_keys_dir L73–80
+ test_NP_1b_hd_path_written_instead method method test_NP_1b_hd_path_written_instead L82–93
+ TestResolveNoPemRead class class TestResolveNoPemRead L120–175
+ test_NP_3_no_keys_dir_read method method test_NP_3_no_keys_dir_read L121–148
+ test_NP_3b_works_even_if_keys_dir_empty method method test_NP_3b_works_even_if_keys_dir_empty L150–175
+ TestSecurityCheckNoPem class class TestSecurityCheckNoPem L183–193
+ test_NP_4_security_check_passes_after_keygen method method test_NP_4_security_check_passes_after_keygen L184–193
+ _HOSTNAME variable variable _HOSTNAME L24–24
+ _HUB variable variable _HUB L23–23
+ _MNEMONIC variable variable _MNEMONIC L25–28
+ _patch_env function function _patch_env L38–57
+ _pem_files function function _pem_files L60–64
+ CliRunner import import CliRunner L21–21
+ annotations import import annotations L14–14 ← moved from tests/test_security_key_permissions.py
+ json import import json L17–17
+ pathlib import import pathlib L16–16 ← moved from tests/test_security_key_permissions.py
+ pytest import import pytest L19–19 ← moved from tests/test_security_key_permissions.py
+ runner variable variable runner L30–30
~ tests/test_security_key_permissions.py .py 19 symbols removed
_generate_pem_key function function _generate_pem_key L30–37
_write_key function function _write_key L40–43
Ed25519PrivateKey import import Ed25519PrivateKey L16–16 → moved to tests/test_resolve_signing_identity_keychain_path.py
Encoding import import Encoding L17–17
NoEncryption import import NoEncryption L17–17
PrivateFormat import import PrivateFormat L17–17
_load_private_key_from_path import import _load_private_key_from_path L23–23
annotations import import annotations L8–8 → moved to tests/test_security_no_pem_on_disk.py
os import import os L10–10
patch import import patch L13–13
pathlib import import pathlib L11–11 → moved to tests/test_security_no_pem_on_disk.py
pytest import import pytest L15–15 → moved to tests/test_security_no_pem_on_disk.py
stat import import stat L12–12
test_key_0600_loads function function test_key_0600_loads L51–57
test_key_0640_refused function function test_key_0640_refused L71–79
test_key_0644_refused function function test_key_0644_refused L60–68
test_key_wrong_owner_refused function function test_key_wrong_owner_refused L82–116
test_root_skips_ownership_check function function test_root_skips_ownership_check L119–151
test_warning_includes_chmod_command function function test_warning_includes_chmod_command L154–164
~ docs/key-material-security-audit.md .md 4 symbols added, 4 symbols removed, 15 symbols modified
Files to Delete (Post-Phases 2–4) section Files to Delete (Post-Phases 2–4) L260–271
table@L262 section table@L262 L262–271
table@L103 section table@L103 L103–110
table@L113 section table@L113 L113–119
+ Files to Delete (blocked on runrecover / runrotate migration) section Files to Delete (blocked on runrecover / runrotate migration) L259–270
+ table@L261 section table@L261 L261–270
+ table@L104 section table@L104 L104–111
+ table@L114 section table@L114 L114–120
~ py
~ py
~ py
~ HIGH
~ MEDIUM
~ muse/cli/commands/auth.py .py 1 symbol modified
~ tests/test_agent_signing.py .py 9 symbols removed, 5 symbols modified
TestLoadPrivateKeyFromPem class class TestLoadPrivateKeyFromPem L361–375
_make_pem method method _make_pem L362–364
test_empty_bytes_returns_none method method test_empty_bytes_returns_none L374–375
test_invalid_pem_returns_none method method test_invalid_pem_returns_none L371–372
test_valid_pem_returns_key method method test_valid_pem_returns_key L366–369
Encoding import import Encoding L28–28
NoEncryption import import NoEncryption L28–28
PrivateFormat import import PrivateFormat L28–28
load_private_key_from_pem import import load_private_key_from_pem L44–44
~ tests/test_cmd_auth_keygen_hd.py .py 3 symbols added, 4 symbols removed, 9 symbols modified
test_pem_file_written method method test_pem_file_written L241–249
test_pem_is_valid_ed25519_key method method test_pem_is_valid_ed25519_key L251–261
test_second_keygen_succeeds_without_force method method test_second_keygen_succeeds_without_force L402–411
load_pem_private_key import import load_pem_private_key L69–69
+ test_derive_hd_public_info_returns_pub_b64_and_fingerprint method method test_derive_hd_public_info_returns_pub_b64_and_fingerprint L238–247
+ test_no_pem_written_by_derive_hd_public_info method method test_no_pem_written_by_derive_hd_public_info L249–259
+ test_second_keygen_without_force_rejected method method test_second_keygen_without_force_rejected L400–409
~ tests/test_hd_keygen_unified.py .py 6 symbols added, 6 symbols removed, 7 symbols modified
test_agent_pem_at_expected_path method method test_agent_pem_at_expected_path L260–266
test_agent_pem_mode_600 method method test_agent_pem_mode_600 L268–275
test_pem_mode_600 method method test_pem_mode_600 L162–169
test_pem_written method method test_pem_written L155–160
test_generate_hd_keypair_exists method method test_generate_hd_keypair_exists L136–138
load_pem_private_key import import load_pem_private_key L20–20
+ test_agent_hd_path_in_identity_toml method method test_agent_hd_path_in_identity_toml L288–300
+ test_agent_no_pem_written method method test_agent_no_pem_written L278–286
+ test_hd_path_in_identity_toml method method test_hd_path_in_identity_toml L176–186
+ test_no_pem_written method method test_no_pem_written L167–174
+ _kc_store variable variable _kc_store L60–60
+ _patch_keychain function function _patch_keychain L63–70
← Older Oldest on dev
All commits
Newer → Latest on dev

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:794449e7457b82dc5e2c97854618810e77aa6d13267acffe12cb9f85daeb2a17 --body "your comment"