Skip to main content

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>;