33//! Every `[[review.capability_bindings]]` `category` should be one of these
44//! `domain.action` names. A fixed taxonomy is what makes capability review
55//! meaningful and reproducible: reviewers (and the GitHub Action) can reason
6- //! about a stable set of powers (fs / network / database / process / secret …)
6+ //! about a stable set of powers (filesystem / network / database / secret …)
77//! and assign each a default risk, instead of free-form strings that drift.
8+ //!
9+ //! These names mirror REIR's authoritative `reir::CapabilityCategory` so a
10+ //! category that passes review here is recognized by the REIR policy layer
11+ //! rather than degrading to an unrecognized `Extension`. The `taxonomy_is_subset_of_reir`
12+ //! test enforces that alignment.
813
914/// Risk a capability category carries by default, used to rank review effort and
1015/// drive target policy (e.g. denying high-risk powers in a `prod` target).
@@ -21,49 +26,66 @@ pub enum CapabilityRisk {
2126pub struct CapabilityCategory {
2227 /// `domain.action` name, e.g. `database.write`.
2328 pub name : & ' static str ,
24- /// Coarse domain (`network`, `fs`, …) for grouping.
25- pub domain : & ' static str ,
2629 pub risk : CapabilityRisk ,
2730 pub description : & ' static str ,
2831}
2932
33+ impl CapabilityCategory {
34+ /// Coarse domain (`network`, `filesystem`, …) for grouping.
35+ pub fn domain ( & self ) -> & ' static str {
36+ self . name . split ( '.' ) . next ( ) . unwrap_or ( self . name )
37+ }
38+ }
39+
3040use CapabilityRisk :: { High , Low , Medium } ;
3141
32- /// The canonical capability categories. Add new powers here (and a fixture that
33- /// uses them) rather than inventing free-form category strings .
42+ /// The canonical capability categories — a risk-annotated mirror of REIR's
43+ /// `CapabilityCategory`. Add new powers to REIR first, then here .
3444pub const CAPABILITY_CATEGORIES : & [ CapabilityCategory ] = & [
35- cat ( "fs.read" , "fs" , Medium , "Read files or directory contents" ) ,
36- cat ( "fs.write" , "fs" , High , "Create, modify, or delete files" ) ,
37- cat ( "network.client" , "network" , High , "Make outbound network connections" ) ,
38- cat ( "network.connect" , "network" , High , "Open an outbound socket" ) ,
39- cat ( "network.listen" , "network" , High , "Bind and accept inbound connections" ) ,
40- cat ( "network.server" , "network" , High , "Serve requests on a bound address" ) ,
41- cat ( "http.client" , "network" , High , "Make outbound HTTP requests" ) ,
42- cat ( "database.read" , "database" , Medium , "Query a database" ) ,
43- cat ( "database.write" , "database" , High , "Mutate database state" ) ,
44- cat ( "env.read" , "env" , Medium , "Read environment variables" ) ,
45- cat ( "env.write" , "env" , High , "Set environment variables" ) ,
46- cat ( "process.args" , "process" , Low , "Read process arguments" ) ,
47- cat ( "process.spawn" , "process" , High , "Spawn or exec subprocesses" ) ,
48- cat ( "compute.hash" , "compute" , Low , "Compute a (non-secret) hash" ) ,
49- cat ( "crypto.sign" , "crypto" , High , "Produce cryptographic signatures/MACs" ) ,
50- cat ( "crypto.verify" , "crypto" , Medium , "Verify cryptographic signatures/MACs" ) ,
51- cat ( "secret.read" , "secret" , High , "Read secrets or credentials" ) ,
52- cat ( "identity.assume" , "identity" , High , "Assume an identity / capability role" ) ,
53- cat ( "time.now" , "time" , Low , "Read the wall clock" ) ,
54- cat ( "random.secure" , "random" , Low , "Draw cryptographically secure randomness" ) ,
55- cat ( "runtime.native" , "runtime" , High , "Cross an unclassified native boundary" ) ,
45+ cat ( "network.client" , High , "Make outbound network connections" ) ,
46+ cat ( "network.server" , High , "Serve requests on a bound address" ) ,
47+ cat ( "network.public_ingress" , High , "Accept traffic from a public ingress" ) ,
48+ cat ( "network.egress" , High , "Send traffic to an external destination" ) ,
49+ cat ( "filesystem.read" , Medium , "Read files or directory contents" ) ,
50+ cat ( "filesystem.write" , High , "Create, modify, or delete files" ) ,
51+ cat ( "object_storage.read" , Medium , "Read from object storage" ) ,
52+ cat ( "object_storage.write" , High , "Write to object storage" ) ,
53+ cat ( "object_storage.delete" , High , "Delete from object storage" ) ,
54+ cat ( "database.read" , Medium , "Query a database" ) ,
55+ cat ( "database.write" , High , "Mutate database state" ) ,
56+ cat ( "queue.publish" , Medium , "Publish to a message queue" ) ,
57+ cat ( "queue.consume" , Medium , "Consume from a message queue" ) ,
58+ cat ( "secret.read" , High , "Read secrets or credentials" ) ,
59+ cat ( "secret.write" , High , "Write secrets or credentials" ) ,
60+ cat ( "env.read" , Medium , "Read environment variables" ) ,
61+ cat ( "env.write" , High , "Set environment variables" ) ,
62+ cat ( "time.read" , Low , "Read the wall clock" ) ,
63+ cat ( "random.read" , Low , "Draw randomness" ) ,
64+ cat ( "compute.hash" , Low , "Compute a (non-secret) hash" ) ,
65+ cat ( "compute.regex" , Low , "Evaluate a regular expression" ) ,
66+ cat ( "process.args" , Low , "Read process arguments" ) ,
67+ cat ( "process.spawn" , High , "Spawn or exec subprocesses" ) ,
68+ cat ( "identity.assume" , High , "Assume an identity / capability role" ) ,
69+ cat ( "identity.grant" , High , "Grant an identity / capability role" ) ,
70+ cat ( "runtime.native" , High , "Cross an unclassified native boundary" ) ,
71+ cat ( "runtime.unsafe" , High , "Execute unsafe runtime code" ) ,
72+ cat ( "build.execute" , High , "Execute code at build time" ) ,
73+ cat ( "build.network" , High , "Access the network at build time" ) ,
74+ cat ( "container.privileged" , High , "Run a privileged container" ) ,
75+ cat ( "container.host_access" , High , "Access the container host" ) ,
76+ cat ( "k8s.rbac.grant" , High , "Grant Kubernetes RBAC permissions" ) ,
77+ cat ( "storage.persistent_read" , Medium , "Read persistent storage" ) ,
78+ cat ( "storage.persistent_write" , High , "Write persistent storage" ) ,
79+ cat ( "telemetry.emit" , Low , "Emit telemetry" ) ,
5680] ;
5781
5882const fn cat (
5983 name : & ' static str ,
60- domain : & ' static str ,
6184 risk : CapabilityRisk ,
6285 description : & ' static str ,
6386) -> CapabilityCategory {
6487 CapabilityCategory {
6588 name,
66- domain,
6789 risk,
6890 description,
6991 }
@@ -102,7 +124,7 @@ mod tests {
102124 }
103125
104126 #[ test]
105- fn taxonomy_names_are_unique_and_dotted ( ) {
127+ fn taxonomy_names_are_unique_dotted_and_match_domain ( ) {
106128 let mut seen = std:: collections:: BTreeSet :: new ( ) ;
107129 for category in CAPABILITY_CATEGORIES {
108130 assert ! (
@@ -115,6 +137,24 @@ mod tests {
115137 "category {} must be `domain.action`" ,
116138 category. name
117139 ) ;
140+ assert ! ( category. name. starts_with( category. domain( ) ) ) ;
141+ }
142+ }
143+
144+ /// Every canonical category must be recognized by REIR's authoritative
145+ /// `CapabilityCategory` (i.e. not fall back to `Extension`), so a category
146+ /// that passes review here is classified correctly by the REIR policy layer.
147+ #[ test]
148+ fn taxonomy_is_subset_of_reir ( ) {
149+ for category in CAPABILITY_CATEGORIES {
150+ let reir_category = reir:: CapabilityCategory :: try_from ( category. name . to_string ( ) )
151+ . expect ( "conversion is infallible" ) ;
152+ assert ! (
153+ !matches!( reir_category, reir:: CapabilityCategory :: Extension ( _) ) ,
154+ "capability category `{}` is not a canonical REIR category \
155+ (it would degrade to Extension); align it with reir::CapabilityCategory",
156+ category. name
157+ ) ;
118158 }
119159 }
120160}
0 commit comments