gabriel / muse public
feat BREAKING security feat/security-controls #1 / 2
gabriel · 152 days ago · Apr 9, 2026 · Diff

feat(security): ownership checks, TOFU pinning, Ed25519 provenance v2

- repo.py: CVE-2022-24765 equivalent — UntrustedRepositoryError when .muse/ is owned by a different UID; escape via MUSE_SAFE_DIRS or 'muse trust add <path>' - identity.py: refuse to load private keys with world-readable perms or foreign ownership - errors.py: UntrustedRepositoryError + HubFingerprintMismatchError - provenance.py: payload v2 (binds committed_at timestamp), Ed25519 verify support, sign_commit_record helper - verify.py: Ed25519 signature verification (replaces HMAC) - commit.py: wires provenance signing into commit path - trust.py: 'muse trust' CLI — add/remove/list safe dirs, hub-list/hub-reset for TOFU fingerprint management - app.py / config.py: register trust command, safe_dirs config helpers - ast_parser.py: code intelligence updates - 34 tests covering all of the above (all green)

sha256:a50b74a10891416f2f142efb6d67bac4c7761786d4ccec023c37ec2ea6d08053 sha
+112 ~117 −1 symbols
sha256:b9e1654ab6f50332c26f3bb18de6c9b957c0716ac5724b10eb43d4797c9d9549 snapshot
+112
symbols added
~117
symbols modified
−1
symbol removed
0
dead code introduced
Semantic Changes 230 symbols
~ muse/cli/commands/trust.py .py 9 symbols added
+ _run_add function function _run_add L108–115
+ _run_hub_list function function _run_hub_list L145–160
+ _run_hub_reset function function _run_hub_reset L163–172
+ _run_list function function _run_list L132–142
+ _run_remove function function _run_remove L118–129
+ annotations import import annotations L27–27
+ argparse import import argparse L29–29
+ sys import import sys L30–30
+ register function function register L33–105
~ tests/test_core_provenance.py .py 26 symbols added
+ TestProvenancePayloadV2 class class TestProvenancePayloadV2 L50–94
+ test_different_committed_at_different_payload method method test_different_committed_at_different_payload L75–80
+ test_empty_committed_at_still_works method method test_empty_committed_at_still_works L82–88
+ test_payload_includes_committed_at method method test_payload_includes_committed_at L64–73
+ test_payload_is_64_hex_chars method method test_payload_is_64_hex_chars L90–94
+ test_payload_version_prefix method method test_payload_version_prefix L51–62
+ TestTimestampSignatureBinding class class TestTimestampSignatureBinding L97–166
+ test_round_trip_sign_verify_all_fields method method test_round_trip_sign_verify_all_fields L123–154
+ test_sign_commit_record_without_committed_at_still_verifiable method method test_sign_commit_record_without_committed_at_still_verifiable L156–166
+ test_signature_detects_timestamp_mutation method method test_signature_detects_timestamp_mutation L98–112
+ test_signature_validates_with_same_timestamp method method test_signature_validates_with_same_timestamp L114–121
+ _gen_key function function _gen_key L27–28
+ _pub_bytes function function _pub_bytes L31–32
+ Ed25519PrivateKey import import Ed25519PrivateKey L14–14
+ Encoding import import Encoding L15–15
+ PROVENANCE_PAYLOAD_VERSION import import PROVENANCE_PAYLOAD_VERSION L17–17
+ PublicFormat import import PublicFormat L15–15
+ _PROV_VERSION_PREFIX import import _PROV_VERSION_PREFIX L17–17
+ annotations import import annotations L9–9
+ base64 import import base64 L11–11
+ provenance_payload import import provenance_payload L17–17
+ pytest import import pytest L13–13
+ sign_commit_ed25519 import import sign_commit_ed25519 L17–17
+ sign_commit_record import import sign_commit_record L17–17
+ verify_commit_ed25519 import import verify_commit_ed25519 L17–17
+ test_provenance_payload_version_is_2 function function test_provenance_payload_version_is_2 L40–42
~ tests/test_security_hub_trust.py .py 22 symbols added
+ _fake_fingerprint function function _fake_fingerprint L34–35
+ _patch_cert_fp function function _patch_cert_fp L38–40
+ HTTP_NO_TLS_SENTINEL import import HTTP_NO_TLS_SENTINEL L19–19
+ HubFingerprintMismatchError import import HubFingerprintMismatchError L18–18
+ HubTrustRecord import import HubTrustRecord L19–19
+ HubTrustStore import import HubTrustStore L19–19
+ _normalise_hostname import import _normalise_hostname L19–19
+ annotations import import annotations L11–11
+ check_and_pin import import check_and_pin L19–19
+ load_hub_trust_store import import load_hub_trust_store L19–19
+ patch import import patch L14–14
+ pathlib import import pathlib L13–13
+ pytest import import pytest L16–16
+ remove_hub_record import import remove_hub_record L19–19
+ test_fingerprint_mismatch_raises function function test_fingerprint_mismatch_raises L85–110
+ test_first_connection_pins_fingerprint function function test_first_connection_pins_fingerprint L48–65
+ test_http_stores_sentinel function function test_http_stores_sentinel L113–129
+ test_hub_reset_cli function function test_hub_reset_cli L166–198
+ test_hub_trust_list_cli function function test_hub_trust_list_cli L142–163
+ test_normalise_hostname_http_with_port function function test_normalise_hostname_http_with_port L137–139
+ test_normalise_hostname_https function function test_normalise_hostname_https L132–134
+ test_second_connection_increments_count function function test_second_connection_increments_count L68–82
+ _generate_pem_key function function _generate_pem_key L30–37
+ _write_key function function _write_key L40–43
+ Ed25519PrivateKey import import Ed25519PrivateKey L16–16
+ 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
+ os import import os L10–10
+ patch import import patch L13–13
+ pathlib import import pathlib L11–11
+ pytest import import pytest L15–15
+ 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
~ tests/test_security_ownership.py .py 19 symbols added
+ _make_repo function function _make_repo L24–28
+ UntrustedRepositoryError import import UntrustedRepositoryError L16–16
+ _check_repo_ownership import import _check_repo_ownership L17–17
+ annotations import import annotations L8–8
+ find_repo_root import import find_repo_root L17–17
+ os import import os L10–10
+ patch import import patch L12–12
+ pathlib import import pathlib L11–11
+ pytest import import pytest L14–14
+ test_error_message_contains_fix_command function function test_error_message_contains_fix_command L239–267
+ test_global_config_safe_dirs function function test_global_config_safe_dirs L199–236
+ test_muse_safe_dirs_env function function test_muse_safe_dirs_env L90–117
+ test_muse_safe_dirs_multi function function test_muse_safe_dirs_multi L120–148
+ test_root_bypasses_ownership function function test_root_bypasses_ownership L78–87
+ test_trust_add_cli function function test_trust_add_cli L151–166
+ test_trust_list_empty function function test_trust_list_empty L185–196
+ test_trust_remove_cli function function test_trust_remove_cli L169–182
+ test_trusted_own_repo function function test_trusted_own_repo L36–43
+ test_untrusted_other_uid function function test_untrusted_other_uid L46–75
~ muse/cli/commands/commit.py .py 1 symbol modified
~ run
~ muse/cli/config.py .py 7 symbols added
+ _GLOBAL_CONFIG_FILE variable variable _GLOBAL_CONFIG_FILE L1091–1091
+ _GLOBAL_MUSE_DIR variable variable _GLOBAL_MUSE_DIR L1090–1090
+ _load_global_config function function _load_global_config L1094–1115
+ _save_global_config function function _save_global_config L1118–1164
+ add_global_safe_dir function function add_global_safe_dir L1175–1190
+ get_global_safe_dirs function function get_global_safe_dirs L1167–1172
+ remove_global_safe_dir function function remove_global_safe_dir L1193–1206
~ muse/core/errors.py .py 2 symbols added, 2 symbols modified
+ UntrustedRepositoryError class class UntrustedRepositoryError L46–74
+ __init__ method method __init__ L64–74
~ muse/core/identity.py .py 1 symbol added, 1 symbol modified
+ _check_key_file_permissions function function _check_key_file_permissions L450–500
~ muse/core/provenance.py .py 2 symbols added, 2 symbols modified
+ PROVENANCE_PAYLOAD_VERSION variable variable PROVENANCE_PAYLOAD_VERSION L80–80
+ _PROV_VERSION_PREFIX variable variable _PROV_VERSION_PREFIX L81–81
~ muse/core/repo.py .py 3 symbols added, 2 symbols modified
+ _check_repo_ownership function function _check_repo_ownership L241–282
+ _is_repo_trusted function function _is_repo_trusted L208–238
+ UntrustedRepositoryError import import UntrustedRepositoryError L38–38
~ muse/core/verify.py .py 1 symbol modified
~ muse/plugins/code/ast_parser.py .py 1 symbol added, 2 symbols modified
+ _parse_headings_regex method method _parse_headings_regex L740–838

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:a50b74a10891416f2f142efb6d67bac4c7761786d4ccec023c37ec2ea6d08053 --body "your comment"