Skip to content

Commit b5e9bd3

Browse files
Haofeiclaude
andcommitted
Align capability taxonomy with REIR's authoritative categories
The Phase 1 taxonomy had invented names (fs.read, http.client, crypto.*, time.now, random.secure, network.connect/listen) that diverge from REIR's authoritative reir::CapabilityCategory. Such a category passes review here but degrades to an unrecognized `Extension` in the REIR bundle, so the REIR policy layer would misclassify it (e.g. filesystem access not seen as filesystem). Mirror REIR's category names exactly (filesystem.*, time.read, random.read, object_storage.*, queue.*, etc.), and add `taxonomy_is_subset_of_reir` which fails if any canonical category isn't recognized by reir::CapabilityCategory — so the two taxonomies can't drift again. Shipped packages already used aligned names, so the gate and all REIR output are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6513a28 commit b5e9bd3

1 file changed

Lines changed: 69 additions & 29 deletions

File tree

src/capability.rs

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
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 {
2126
pub 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+
3040
use 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.
3444
pub 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

5882
const 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

Comments
 (0)