errors.py python
103 lines 3.6 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 32 days ago
1 """Exit-code contract and exception types for the Muse CLI."""
2
3 from __future__ import annotations
4
5 import enum
6
7
8 class ExitCode(enum.IntEnum):
9 """Standardised CLI exit codes.
10
11 0 — success
12 1 — user error (bad arguments, invalid input)
13 2 — repo-not-found / config invalid
14 3 — server / internal error
15 4 — requested item not found
16 5 — remote communication error
17 6 — attestation required but unavailable (Phase 7 — air.required=true rejection)
18 """
19
20 SUCCESS = 0
21 USER_ERROR = 1
22 REPO_NOT_FOUND = 2
23 INTERNAL_ERROR = 3
24 NOT_FOUND = 4
25 REMOTE_ERROR = 5
26 ATTESTATION_REQUIRED = 6
27
28
29 class MuseCLIError(Exception):
30 """Base exception for Muse CLI errors."""
31
32 def __init__(self, message: str, exit_code: ExitCode = ExitCode.INTERNAL_ERROR) -> None:
33 super().__init__(message)
34 self.exit_code = exit_code
35
36
37 class RepoNotFoundError(MuseCLIError):
38 """Raised when the current directory is not a Muse repository."""
39
40 def __init__(self, message: str = "Not a Muse repository. Run `muse init`.") -> None:
41 super().__init__(message, exit_code=ExitCode.REPO_NOT_FOUND)
42
43
44 #: Canonical public alias matching the name specified.
45 MuseNotARepoError = RepoNotFoundError
46
47
48 class UntrustedRepositoryError(PermissionError):
49 """Raised when a ``.muse/`` directory is owned by a different user.
50
51 This is the Muse equivalent of CVE-2022-24765: an attacker-owned repository
52 in a shared-filesystem location (e.g. ``/tmp``, a Docker bind-mount, a
53 multi-user home directory) could inject malicious configuration or hooks that
54 execute under the victim's UID.
55
56 Escape hatches (bypass ownership check):
57 - Set ``MUSE_SAFE_DIRS`` environment variable to a colon-separated list of
58 absolute paths that are trusted regardless of ownership.
59 - Add paths via ``muse trust add <path>`` which writes to
60 ``~/.muse/config.toml`` under ``[security] safe_dirs``.
61
62 Root (uid == 0) bypasses ownership checks entirely since root can already
63 read and write any file regardless of ownership.
64 """
65
66 def __init__(self, path: str, owner_uid: int, current_uid: int) -> None:
67 self.repo_path = path
68 self.owner_uid = owner_uid
69 self.current_uid = current_uid
70 message = (
71 f"Untrusted repository at {path!r}: owned by UID {owner_uid}, "
72 f"but running as UID {current_uid}. "
73 "This could be a security risk (CVE-2022-24765 equivalent). "
74 f"To trust this repository, run: muse trust add {path}"
75 )
76 super().__init__(message)
77
78
79 class HubFingerprintMismatchError(Exception):
80 """Raised when a hub's TLS certificate fingerprint changes unexpectedly.
81
82 TOFU (Trust On First Use) pinning stores the server's certificate fingerprint
83 on the first connection. If a subsequent connection presents a different
84 fingerprint, this exception is raised to alert the user to a possible
85 man-in-the-middle attack or certificate rotation.
86
87 To re-pin after a legitimate certificate change, run:
88 muse trust hub-reset <hostname>
89 """
90
91 def __init__(self, hostname: str, stored: str, actual: str) -> None:
92 self.hostname = hostname
93 self.stored_fingerprint = stored
94 self.actual_fingerprint = actual
95 message = (
96 f"Hub fingerprint mismatch for {hostname!r}!\n"
97 f" Stored: {stored}\n"
98 f" Actual: {actual}\n"
99 "This may indicate a man-in-the-middle attack or a legitimate "
100 "certificate rotation.\n"
101 f"If you expected this change, run: muse trust hub-reset {hostname}"
102 )
103 super().__init__(message)
File History 2 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 32 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 32 days ago