agent_capability_token/error.rs
1//! Error types for ACT encoding, decoding and verification.
2
3/// Failures encountered while decoding or verifying an ACT.
4///
5/// Variants are deliberately specific so callers can distinguish an expired
6/// token from a forged one in logs and metrics. A verifier that collapses these
7/// into a single "invalid token" outcome loses the ability to alarm on forgery
8/// while staying quiet about ordinary expiry.
9#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum ActError {
12 /// The encoded token exceeded [`crate::MAX_ACT_BYTES`].
13 ///
14 /// Checked before any parsing, so a hostile payload cannot force
15 /// unbounded allocation.
16 #[error("token is {actual} bytes, exceeding the {limit}-byte maximum")]
17 TooLarge {
18 /// Size of the rejected input.
19 actual: usize,
20 /// The configured ceiling.
21 limit: usize,
22 },
23
24 /// The outer envelope was not well-formed CBOR, or did not match the
25 /// envelope schema.
26 #[error("envelope decode failed: {0}")]
27 EnvelopeDecode(String),
28
29 /// The claims payload inside the envelope was not well-formed CBOR, or did
30 /// not match the claims schema.
31 #[error("claims decode failed: {0}")]
32 ClaimsDecode(String),
33
34 /// The envelope declared a format version this build does not implement.
35 ///
36 /// Carried explicitly so a rollout can distinguish "forged" from "newer
37 /// than me", which are operationally opposite situations.
38 #[error("unsupported ACT format version {found}, this build supports {supported}")]
39 UnsupportedVersion {
40 /// Version declared by the envelope.
41 found: u64,
42 /// Version this build implements.
43 supported: u64,
44 },
45
46 /// The envelope declared a signature algorithm this build does not
47 /// implement.
48 #[error("unsupported signature algorithm {0:?}, this build implements Ed25519")]
49 UnsupportedAlgorithm(String),
50
51 /// The signature field was absent.
52 #[error("envelope carries no signature")]
53 MissingSignature,
54
55 /// The signature was present but not 64 bytes.
56 #[error("signature is {0} bytes, expected exactly 64")]
57 MalformedSignature(usize),
58
59 /// A configured trusted key was not a valid Ed25519 public key.
60 ///
61 /// Distinguished from a verification failure because it is a
62 /// misconfiguration on the verifier's side, not a problem with the token.
63 #[error("trusted key at index {index} is not a valid Ed25519 key: {reason}")]
64 MalformedTrustedKey {
65 /// Position in the supplied key set.
66 index: usize,
67 /// Underlying reason.
68 reason: String,
69 },
70
71 /// No trusted keys were supplied, so no token could ever verify.
72 #[error("at least one trusted issuer key is required")]
73 NoTrustedKeys,
74
75 /// The signature did not validate against any trusted key.
76 #[error("signature did not validate against any of {tried} trusted keys")]
77 SignatureInvalid {
78 /// Number of keys attempted.
79 tried: usize,
80 },
81
82 /// `exp` is at or before the current time.
83 #[error("token expired at {expired_at} (now {now})")]
84 Expired {
85 /// The `exp` claim.
86 expired_at: i64,
87 /// Time used for the comparison.
88 now: i64,
89 },
90
91 /// `nbf` is after the current time.
92 #[error("token not valid until {valid_from} (now {now})")]
93 NotYetValid {
94 /// The `nbf` claim.
95 valid_from: i64,
96 /// Time used for the comparison.
97 now: i64,
98 },
99
100 /// `iss` did not match the expected issuer.
101 #[error("issuer mismatch: expected {expected:?}, found {found:?}")]
102 IssuerMismatch {
103 /// Issuer the verifier requires.
104 expected: String,
105 /// Issuer the token carries.
106 found: String,
107 },
108
109 /// `aud` did not include the expected audience.
110 #[error("audience mismatch: expected {expected:?}, found {found:?}")]
111 AudienceMismatch {
112 /// Audience the verifier requires.
113 expected: String,
114 /// Audiences the token carries.
115 found: Vec<String>,
116 },
117
118 /// A required scope was absent from `scope`.
119 #[error("missing required scope {0:?}")]
120 MissingScope(String),
121
122 /// A scope did not match the `service:resource:action` grammar.
123 #[error("scope {value:?} is malformed: {reason}")]
124 MalformedScope {
125 /// The offending scope string.
126 value: String,
127 /// Why it was rejected.
128 reason: &'static str,
129 },
130
131 /// A structurally required claim was empty.
132 #[error("claim {0} must not be empty")]
133 EmptyClaim(&'static str),
134
135 /// `exp` was not after `nbf`, so the token has no valid window.
136 #[error("token validity window is empty: nbf {nbf} is not before exp {exp}")]
137 EmptyValidityWindow {
138 /// The `nbf` claim.
139 nbf: i64,
140 /// The `exp` claim.
141 exp: i64,
142 },
143
144 /// Serializing claims or an envelope failed.
145 #[error("encode failed: {0}")]
146 Encode(String),
147}
148
149/// Result alias for ACT operations.
150pub type ActResult<T> = Result<T, ActError>;