P2 slice 4: access
Agent tokens (wb_ and 64 hex digits), one book and one name each, kept only as SHA-256 in a chain log and in the OS keyring; made, listed and revoked only through the admin socket (`wanderbooks token ...`). The door checks them, scopes cover every tool, every call is in the access log (fail closed), and the owner passphrase is an Argon2id hash. Setup's terminal test waits for its prompts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
1 parent
a298d154b6
commit
d1f3ad891a
28 files changed
+5504
-116
No files matched your search
@@ -27,6 +27,9 @@ pub struct Knobs {
|
||||
pub queue_wait: Duration,
|
||||
/// How long a command waits for the books to open.
|
||||
pub open_wait: Duration,
|
||||
/// Keep agents' tokens in files in the root's `run/`, not the OS
|
||||
/// keyring (tests: tokens.rs INVARIANT 3). `file_keyring=1`.
|
||||
pub file_keyring: bool,
|
||||
}
|
||||
|
||||
impl Default for Knobs {
|
||||
@@ -39,6 +42,7 @@ impl Default for Knobs {
|
||||
init_wait: Duration::from_secs(5),
|
||||
queue_wait: Duration::from_secs(60),
|
||||
open_wait: Duration::from_secs(120),
|
||||
file_keyring: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +58,14 @@ impl Knobs {
|
||||
let (name, value) = pair
|
||||
.split_once('=')
|
||||
.ok_or_else(|| format!("{FLAG}: {pair:?} isn't name=milliseconds"))?;
|
||||
if name == "file_keyring" {
|
||||
k.file_keyring = match value {
|
||||
"1" => true,
|
||||
"0" => false,
|
||||
_ => return Err(format!("{FLAG}: file_keyring is 1 or 0")),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
let ms: u64 = value
|
||||
.parse()
|
||||
.map_err(|_| format!("{FLAG}: {value:?} isn't a number of milliseconds"))?;
|
||||
@@ -90,6 +102,9 @@ impl Knobs {
|
||||
parts.push(format!("{name}={}", now.as_millis()));
|
||||
}
|
||||
}
|
||||
if self.file_keyring {
|
||||
parts.push("file_keyring=1".into());
|
||||
}
|
||||
(!parts.is_empty()).then(|| parts.join(","))
|
||||
}
|
||||
|
||||
@@ -137,13 +152,23 @@ mod tests {
|
||||
let args = k.args();
|
||||
assert_eq!(args[0], FLAG);
|
||||
assert_eq!(Knobs::parse(&args[1]).unwrap(), k);
|
||||
let keyring = Knobs::parse("file_keyring=1,backoff=5").unwrap();
|
||||
assert!(keyring.file_keyring);
|
||||
assert_eq!(Knobs::parse(&keyring.args()[1]).unwrap(), keyring);
|
||||
assert!(!Knobs::parse("file_keyring=0").unwrap().file_keyring);
|
||||
let one = Knobs::parse("close_after=1").unwrap();
|
||||
assert_eq!(one.to_arg().as_deref(), Some("close_after=1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bad_knob_is_refused() {
|
||||
for bad in ["close_after", "close_after=x", "nope=1", "close_after=-1"] {
|
||||
for bad in [
|
||||
"close_after",
|
||||
"close_after=x",
|
||||
"nope=1",
|
||||
"close_after=-1",
|
||||
"file_keyring=yes",
|
||||
] {
|
||||
assert!(Knobs::parse(bad).is_err(), "{bad}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user