diff options
| author | tv <tv@krebsco.de> | 2026-11-08 09:44:28 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-08 14:29:01 +0100 |
| commit | e63984e99bef66cb05d7bcf781812ea5e6276fa8 (patch) | |
| tree | aad499e46a8b63ebc2c329e5984ef48e45dc6a0a /src/swarm | |
| parent | 4ecb4ee5c0a49fc6d78ae06e21f6bb1571e290cb (diff) | |
access: a node judges its own changes before they leave
The API checks the path a request asks for; the rules judge everything
the change does. Where they disagreed, the honest node's change was
refused by every other node, and with signatures required the node was
refused as a whole. The plan had both gates run the same judgement.
Under a policy, each change a node makes (its own writes and those
through the API) is now judged by rules::judge as receivers will judge
it, before it is signed or sent; one every node would refuse is rolled
back (the document forked at the heads before it, same actor: the
change never left) and the request refused with 403 (StoreError::
Refused). rules::judge_held, judging a change the document holds,
moves from swarm::refusals to access::rules for both.
The simulations that play a member past its own checks write with a
helper that bypasses them (forge).
Review finding 3 (the same judgement at both gates).
Diffstat (limited to 'src/swarm')
| -rw-r--r-- | src/swarm/refusals.rs | 53 |
1 files changed, 1 insertions, 52 deletions
diff --git a/src/swarm/refusals.rs b/src/swarm/refusals.rs index fd857fa..30facb4 100644 --- a/src/swarm/refusals.rs +++ b/src/swarm/refusals.rs @@ -66,7 +66,7 @@ pub(crate) async fn received(state: &AppState, peer: &str, message: RefusalsMess let mut refused = None; let mut taken = Vec::new(); for hash in changes { - match judge_held(&replica.doc, replica.epoch, &hash) { + match crate::access::rules::judge_held(&replica.doc, replica.epoch, &hash) { Some(Err(why)) => { refused = Some((hash, why)); break; @@ -90,54 +90,3 @@ pub(crate) async fn received(state: &AppState, peer: &str, message: RefusalsMess warn!(peer = %peer, changes = ?taken, "A peer refuses changes our rules take: its policy or ours is off"); } } - -/// `hash`, which `doc` (of `epoch`) holds, judged by its rules now: why they refuse it, -/// if they do; None if `doc` doesn't hold it. -fn judge_held( - doc: &crate::document::Document, - epoch: Epoch, - hash: &ChangeHash, -) -> Option<Result<(), String>> { - let change = doc.get_change_by_hash(hash)?; - // An epoch's root is what the document is: never refused, whatever a peer says. - if change.deps().is_empty() { - return Some(Ok(())); - } - let decoded = change.decode(); - let ops = crate::document::inspect::Inspector::new(doc) - .ops(std::slice::from_ref(&decoded)) - .pop() - .unwrap_or_default(); - let paths = ops.iter().map(|op| op.path.clone()).collect(); - let committer = doc - .get_author_for_actor(change.actor_id()) - .and_then(|author| { - std::str::from_utf8(author.as_bytes()) - .ok() - .map(str::to_owned) - }); - let user = change - .message() - .and_then(crate::document::replica::user_of) - .map(str::to_owned); - let deps = change.deps().to_vec(); - let judged = crate::access::rules::judge( - doc, - epoch, - &crate::access::rules::Change { - hash: *hash, - deps: &deps, - seen: &deps, - committer: committer.as_deref(), - user: user.as_deref(), - paths: &paths, - ops: &ops, - }, - ); - Some(match judged { - Ok(()) => Ok(()), - Err(crate::access::rules::Refusal::Rule(why)) => Err(why), - // Its dependencies are in the document: it is. - Err(crate::access::rules::Refusal::Unready) => Ok(()), - }) -} |
