trust.py python
172 lines 6.5 KB
Raw
sha256:0e3697f29ae6c62f39818a19861d4db6276465de16481fadf866eb93cbf4aae4 test: validate staging push and MP workflow for aaronrene Human 17 days ago
1 """muse trust — manage trusted repository paths and pinned hub fingerprints.
2
3 Security model
4 --------------
5 Muse performs an ownership check on every discovered ``.muse/`` directory
6 (CVE-2022-24765 equivalent). If the ``.muse/`` directory is owned by a
7 different UID, Muse refuses to operate on it and raises
8 :class:`~muse.core.errors.UntrustedRepositoryError`.
9
10 For shared-filesystem environments (Docker bind-mounts, CI agents, multi-user
11 workstations), specific paths can be added to the trust list so Muse operates
12 normally without re-checking ownership.
13
14 Trust sources (in evaluation order)
15 ------------------------------------
16 1. ``MUSE_SAFE_DIRS`` environment variable — colon-separated absolute paths.
17 Useful for ephemeral CI containers.
18 2. ``~/.muse/config.toml`` ``[security] safe_dirs`` — persistent trust list.
19 Managed by ``muse trust add`` / ``muse trust remove``.
20
21 Hub fingerprint pinning (TOFU)
22 -------------------------------
23 ``muse trust hub-list`` and ``muse trust hub-reset`` manage the TOFU
24 (Trust On First Use) fingerprint store at ``~/.muse/hub_trust.toml``.
25 """
26
27 from __future__ import annotations
28
29 import argparse
30 import sys
31
32
33 def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
34 """Register the ``muse trust`` namespace and its subcommands."""
35 trust_parser = subparsers.add_parser(
36 "trust",
37 help="Manage trusted repository paths and pinned hub fingerprints.",
38 description=__doc__,
39 formatter_class=argparse.RawDescriptionHelpFormatter,
40 )
41 trust_subs = trust_parser.add_subparsers(dest="trust_command", metavar="TRUST_COMMAND")
42 trust_subs.required = True
43
44 # muse trust add <path>
45 add_p = trust_subs.add_parser(
46 "add",
47 help="Add a path to the trusted directory list (~/.muse/config.toml).",
48 description=(
49 "Add PATH to the [security] safe_dirs list in ~/.muse/config.toml.\n"
50 "Muse will skip the ownership check for this repository.\n\n"
51 "Example:\n"
52 " muse trust add /shared/workspaces/myrepo"
53 ),
54 formatter_class=argparse.RawDescriptionHelpFormatter,
55 )
56 add_p.add_argument("path", help="Repository path to trust (absolute or relative).")
57 add_p.set_defaults(func=_run_add)
58
59 # muse trust remove <path>
60 remove_p = trust_subs.add_parser(
61 "remove",
62 help="Remove a path from the trusted directory list.",
63 description=(
64 "Remove PATH from the [security] safe_dirs list in ~/.muse/config.toml."
65 ),
66 formatter_class=argparse.RawDescriptionHelpFormatter,
67 )
68 remove_p.add_argument("path", help="Repository path to remove from the trust list.")
69 remove_p.set_defaults(func=_run_remove)
70
71 # muse trust list
72 list_p = trust_subs.add_parser(
73 "list",
74 help="Print all currently trusted paths.",
75 description="Print all paths in the [security] safe_dirs list.",
76 )
77 list_p.set_defaults(func=_run_list)
78
79 # muse trust hub-list
80 hub_list_p = trust_subs.add_parser(
81 "hub-list",
82 help="List all pinned hub fingerprints (TOFU store).",
83 description=(
84 "Print all hub fingerprints stored in ~/.muse/hub_trust.toml.\n"
85 "Each entry shows: hostname, SHA-256 fingerprint, first seen, verified count."
86 ),
87 formatter_class=argparse.RawDescriptionHelpFormatter,
88 )
89 hub_list_p.set_defaults(func=_run_hub_list)
90
91 # muse trust hub-reset <hostname>
92 hub_reset_p = trust_subs.add_parser(
93 "hub-reset",
94 help="Remove a pinned hub fingerprint so the next connection re-pins (TOFU reset).",
95 description=(
96 "Remove the stored fingerprint for HOSTNAME from ~/.muse/hub_trust.toml.\n"
97 "The next connection to this hub will re-pin with TOFU.\n\n"
98 "Use this after a legitimate certificate rotation to acknowledge the change."
99 ),
100 formatter_class=argparse.RawDescriptionHelpFormatter,
101 )
102 hub_reset_p.add_argument("hostname", help="Hub hostname (e.g. 'musehub.ai' or 'localhost:10003').")
103 hub_reset_p.set_defaults(func=_run_hub_reset)
104
105 trust_parser.set_defaults(func=lambda args: trust_parser.print_help() or sys.exit(0))
106
107
108 def _run_add(args: argparse.Namespace) -> None:
109 """Add a path to the global safe_dirs list."""
110 from muse.cli.config import add_global_safe_dir
111 import os
112 abs_path = os.path.abspath(args.path)
113 add_global_safe_dir(abs_path)
114 print(f"Trusted path added: {abs_path}")
115 print(f"Muse will skip the ownership check for: {abs_path}")
116
117
118 def _run_remove(args: argparse.Namespace) -> None:
119 """Remove a path from the global safe_dirs list."""
120 from muse.cli.config import remove_global_safe_dir, get_global_safe_dirs
121 import os
122 abs_path = os.path.abspath(args.path)
123 before = get_global_safe_dirs()
124 remove_global_safe_dir(abs_path)
125 after = get_global_safe_dirs()
126 if abs_path in before and abs_path not in after:
127 print(f"Trusted path removed: {abs_path}")
128 else:
129 print(f"Path not in trust list: {abs_path}")
130
131
132 def _run_list(args: argparse.Namespace) -> None:
133 """Print all trusted paths."""
134 from muse.cli.config import get_global_safe_dirs
135 dirs = get_global_safe_dirs()
136 if not dirs:
137 print("No trusted directories configured.")
138 print("Add paths with: muse trust add <path>")
139 return
140 print("Trusted directories (~/.muse/config.toml):")
141 for d in dirs:
142 print(f" {d}")
143
144
145 def _run_hub_list(args: argparse.Namespace) -> None:
146 """Print all pinned hub fingerprints."""
147 from muse.core.hub_trust import load_hub_trust_store
148 store = load_hub_trust_store()
149 if not store:
150 print("No hub fingerprints pinned.")
151 print("Fingerprints are pinned automatically on first connection.")
152 return
153 print("Pinned hub fingerprints (~/.muse/hub_trust.toml):")
154 for hostname, record in sorted(store.items()):
155 print(
156 f" {hostname}\n"
157 f" fingerprint: {record['fingerprint']}\n"
158 f" first_seen: {record['first_seen']}\n"
159 f" verified_count: {record['verified_count']}"
160 )
161
162
163 def _run_hub_reset(args: argparse.Namespace) -> None:
164 """Remove a hub fingerprint so the next connection re-pins."""
165 from muse.core.hub_trust import remove_hub_record
166 hostname = args.hostname
167 removed = remove_hub_record(hostname)
168 if removed:
169 print(f"Hub fingerprint reset for: {hostname}")
170 print("The next connection will re-pin with TOFU.")
171 else:
172 print(f"No pinned fingerprint found for: {hostname}")
File History 1 commit
sha256:0e3697f29ae6c62f39818a19861d4db6276465de16481fadf866eb93cbf4aae4 test: validate staging push and MP workflow for aaronrene Human 17 days ago