aboutsummaryrefslogtreecommitdiffstats
path: root/docs/access.md
Commit message (Collapse)AuthorAgeFilesLines
* access: a shallow read shows only the way through where the user reads partlytv42 hours1-1/+3
| | | | | | | | | | | | | Review 4, F4: `?shallow=true` kept every entry the user may at least pass through, and a shallow read inlines short scalars' values with their types and sizes. A scalar at a level a `*` passes through (a capability on `data/*/public`, an entry `data/count: 42`) showed its value though no capability covers it; before `*`, only a scalar where a capability expected a map did. Now an entry the user may read only partly is shown only as the way through it is: a map, with no more than its type (not its size, the number of its keys); anything else there (a scalar, a list, which a deep read leaves out too) isn't shown. Entries it may read whole are as they were.
* api: a stream opened with a token ends with ittv47 hours1-1/+4
| | | | | | | | | | | | | | | | | | | | A token was checked when a request came, and a stream answered to it (the dashboard's events, a subscription's links and messages, the store's streams) then went on for as long as the client kept it open: past the token's expiry, past its user's tokens being revoked, its user disabled or removed, its session ended. Now a middleware inside `login::bearer` (`login::streams_end`) ends a streaming answer (`text/event-stream`, `application/x-ndjson`) to a request with a token at the token's deadline, its expiry, or as soon as the document no longer takes it (`token::still_taken`, the checks `verify` makes of the document, now its own function): looked at when a change touches protocol data (the root, a switch, among it), and when a session ends on the node itself in a swarm without a registry (`AppState::session_ended`). No polling: a timer for the expiry, and the changes as they come. The client reconnects with the token it has then; a refreshed token is a new one, so a stream lasts a token's lifetime at most (12 hours by default). `futures` is a dependency of every build now (for `take_until`), not only of libp2p's and Tor's.
* access: ending a single sessiontv47 hours1-1/+5
| | | | | | | | | | | | | | | | | | | | Logging out of the dashboard (`DELETE /auth/session`) only cleared the cookie: the session's tokens stayed taken until they expired, by every node. Ending them took `POST /auth/logout`, which ends all of a user's sessions. Now `DELETE /auth/session` ends the session of the token it comes with (the cookie, or a bearer token): where the swarm has a registry, it is recorded as `_continuum/sessions/<user>/<session>` (`registry::session_path`), its value until when the session would have lasted, and every node refuses the session's tokens once the change reaches it (`token::Refused::Ended`): the page's own, its narrow tokens, and one handed to another member's dashboard, all refreshed from it and keeping its ID. The rules take the entry from the user itself or one who manages users, as a logout. A remake's snapshot leaves out entries past that time, a user's map where none is left: their tokens are expired anyway. Without a registry the document can't record it: the node ending it refuses the session's tokens itself (`AppState::ended_sessions`, until it restarts), others take them until they expire, as a logout there is refused.
* dashboard: a subscribed node's own figures when it is in focustv2 days1-1/+2
| | | | | | | | | | | | | | | A node in focus showed this node's `/peers` entry for it and the links it reports: what this node knows of it, never what it says of itself, though the page subscribes to it. Now a subscribed node in focus shows its own figures first (`Node:`), as this node's are (its `/admin/stats`), then this node's entry for it (`as this node sees it`) and its links, each a branch. A link in focus shows each end's paths to the other as that end has them: this node's, and a subscribed node's from its `/peers`. The page reads them from the node every 2 s while it is in focus (`readFigures`, `figuresOf`), with its narrow token, which may look at a node as any user with a capability may; pages of other origins may now call `/admin/stats` and `/peers` (`cors::open`), as they do a node's links and messages. Where they can't be read, the box says why.
* dashboard: a subscribed node's message written out, read from it when showntv2 days1-1/+2
| | | | | | | | | | A subscribed node's messages come without being written out (their stream leaves the text out: up to 8 KiB each, 500 at a time), so the figures box said only when, both ends, type and size of one. Now the node answers for one message by its number, `GET /admin/dashboard/messages/own/{seq}` (`{"text": …}`, `404` once it isn't kept), which pages of other origins may call as they do its streams (`cors::open`), and the page reads it when the message is shown, with its narrow token, and keeps it on the message. Where it can't be read, the box says why.
* access: what a node holds alone by `_continuum/local/<node ID>/<name>`tv2 days1-4/+10
| | | | | | | | | | | | | | | | Setting a node's log filter took `write` on `_continuum`, all of the protocol data: whoever could change one node's logging could change the swarm's members, remakes and users too. Now what a node holds alone has paths of its own for capabilities, under `_continuum/local/<node ID>/`: `log` (`/admin/log`), `config` (`/admin/config`) and `debug` (`/debug`, always `write`), `read` to look and `write` to act (`api::access::needed`, which takes the node's ID). So `_continuum/local/<node ID>` is everything on one node, `_continuum/local/*/log` the log filter on every node, and holders of `_continuum` keep it all. Nothing is stored there: the store refuses writes under `_continuum/local`, and the rules refuse a change touching it, as for epoch metadata. The quarantine stays with `_continuum`: its entries are everyone's writes, which a node-local capability mustn't show.
* access: `*` for any one key in a capability's pathtv2 days1-3/+5
| | | | | | | | | | | | | | | | | | A capability's path covered by prefix only, so what is the same at every key of a map (each node's log filter, say, at `_continuum/local/<id>/log` from the next patch on) took a capability per key. Now a `*` segment stands for any one key, as it already does in signed paths and sealed places (`envelope`, `sealed`, which now share one `agree`): * covering (`Capability::covers`): the pattern no longer than the path, and agreeing with it segment by segment; * listings (`reaches`): pattern and path agreeing on the segments both have, so every key on the way shows, nothing past where the pattern differs; * narrowing (`Capability::holds`, for a token's limit narrowed again and a refresh's): at or below, a `*` only under a `*`: a limit naming one key never widens to all. A `*` first is outside protocol data, so it never covers `_continuum`, as `[]` doesn't. A key that is `*` itself can't be named alone. Every node judges changes by capabilities, so all must read them alike: they run the same version.
* access: keep an SSH key's commenttv2 days1-2/+2
| | | | | | | | A key's line was stored as the key alone (`ssh-ed25519 <base64>`), whatever comment it came with, both from `PUT /auth/keys/<name>` and from `--bootstrap-keys`. Now its comment is kept after it (`PublicKey::kept_line`), the spaces between its words made one. The comment checks nothing: every use of a key parses the line, and the parse reads the key alone.
* dashboard: keys edited, the last removed with a password, failures kepttv2 days1-2/+4
| | | | | | | | | | | | | | | | | | | | | The credentials dialog listed a user's keys whole, to add one or remove one but the last, and any failure replaced the dialog with the error, the form lost. * A key is shown short: its type, the first and last four characters of what tells it apart (an Ed25519 key's base64 after the part all of them share), its comment; the whole line in its tooltip. * A key can be edited, its name and its line: saved under the name, a new name written first and the old one removed. * A user with a password on this node (`--htpasswd`) may remove its last key: it logs in by the password. GET /auth/keys says so (`password`), and DELETE /auth/keys/{id} takes it then; the rules no longer keep the last key on every node (no other node can tell who has a password where; under a strict policy the signed entry keeps one still). * What fails says why beside where it was asked (the add form, the edited row, the removed one), which stays as it was, to be set right and tried again.
* docs: signing what is sent, and which --sign-every to choosetv2 days1-3/+3
| | | | | | | | | api.md's Signed changes says what sent, all and none do, what each costs, and which to choose: sent for nearly every swarm, all where each change should carry its own proof, none to save signatures where only protocol data and listed paths need them; and that signatures travel with their changes, a message carrying none taking none. access.md no longer says signatures come on their own within a second.
* roots: a peer contradicting --genesis-root refused at the handshaketv3 days1-2/+3
| | | | | | | | | Review 3, R5: a node given its root (--genesis-root) refused a peer naming another only change by change while its document was empty, each with an entry among the refused; the handshake went through, as there was no root of its own yet to compare with. The root given is now compared too: a peer naming another is refused at the handshake, logged once, as one whose document starts from another root.
* state: refused writes limited for the node tootv3 days1-1/+3
| | | | | | | | | | | | Review 3, R5: --refused-writes-per-minute bounds each user, but the cost it guards, a document copy under the write lock, is the node's: ten users, or ten accounts of one, are ten times the budget. The node now takes six times a user's limit within a minute in all (refused::NODE_FACTOR); past it, every user with a write refused within the minute is answered 429 untried, until the oldest leave it. A user none of whose writes was refused writes on, so honest writers aren't held up by others' refusals.
* screen: judged after the policy is gone, tootv3 days1-1/+4
| | | | | | | | | | | | | | Review 3, R4: whether a batch was judged followed the receiving document's policy now (and whether the batch writes one). After the policy was removed, or replaced by a version the node doesn't know, changes written under it and received late went in unjudged, while nodes that had them earlier judged them; REFUSALS gave up likewise. Screening now remembers that the document held a policy, known or not (Screening::policed), and judges what it receives while it does, each change by the policy at its dependencies; REFUSALS judges then too (not under a policy the node doesn't know, as before). Not across a restart, which access.md says.
* docs: remakes and signed valuestv3 days1-0/+3
| | | | | | | | Review 3, R3, the replace side: a replace or import remake writes its content as it is, at signed places too, so under a strict policy a member majority can put plain values where envelopes were. access.md says that remakes are outside the rule; keeping envelopes at signed places in every kind is postponed to future-work.md.
* remake: a strict remake of a strict swarm isn't followedtv3 days1-1/+2
| | | | | | | | | | | | | | | Review 3, R3: 1186 kept honest nodes from proposing or voting for a strict remake of a swarm strict already, but a majority of members, each with keys of its own, could still commit one, and switch and catchup followed it without asking what kind of remake made the epoch: the registry, policy and bans of a strict swarm changing by a member majority, the honest minority following. A node now refuses to switch to such an epoch (its document's policy strict, the remake's kind strict), and catchup refuses to rejoin one whose chain of epochs holds a strict remake after the swarm was strict (as the old document is, or once a remake since made it so). access.md says so.
* roots: a joiner takes a root once its seeds named ittv3 days1-2/+4
| | | | | | | | | | | | | | | | Review 3, R2: the root pin was set by the first HELLO_REPLY, and the first sync, a round trip later, took that root; a peer naming another afterwards was refused as another history. So the seed that answered first decided, not "none while they disagree", and a malicious seed makes itself fastest. A node joining with an empty document of the first epoch now takes no root (RootPin::Awaiting, roots left for later) until each of its seeds named one, or ROOT_WAIT (10 s) passed since the first did; then the one they name, none while they differ, as before. With --genesis-root it takes that one alone, at once. access.md, the option's help and example.toml say so; switching roots later, while the joiner holds nothing of its own, is postponed.
* screen: signatures required as the policy a change came under saystv3 days1-1/+2
| | | | | | | | | | | | | | | | | | | | | Review 3, R1: what a node received was vouched for only while its own document's policy required signatures. A joiner's first sync, and every node's message bringing `required`, went in by the rules alone: a malicious first peer could plant changes under other members' actors at their next seqs, their real ones then failing as duplicates for good. The signatures a message carries are always offered now, and the screen always has them: a change waits for one where the policy at the heads it is judged at requires it (rules::Reads::required, read once per heads), whatever the receiving document holds. Changes written under a policy not requiring signatures wait for none, as on every other node; the check for forged changes runs always. Changes waiting for a signature are judged again whenever signatures come. The test of signatures required now takes the history into an empty document; a new one takes changes written under a policy not requiring them without any.
* state: limit refused writes per usertv3 days1-1/+4
| | | | | | | | | | A write every node would refuse is rolled back by forking the document at the heads before it, under the write lock, at a cost that grows with the document's history. A user could ask for such writes in a loop. Count each user's refused writes over the last minute. Past --refused-writes-per-minute (default 10), its writes are answered 429 without being tried, until the oldest refusal leaves the minute.
* roots: an epoch's root pinned, and named in every handshaketv3 days1-0/+10
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Review 2, N7, the care it raised: a joining node's empty document took as the first epoch's root whatever dependency-less change came first, from whichever peer it joined through; the first epoch's ID is all zeros, so nothing pinned it. A remake's epoch has its ID, the snapshot change's hash, which the rules hold its root to. * Every node names its epoch's root in HELLO and HELLO_REPLY (the epoch's ID for a remake's, its genesis change for the first; wire version 45). A node whose document starts from another root of the same epoch refuses the handshake, logging it: another history under the swarm's ID, a forged root or a genesis started again. * A joining node with an empty document takes as its root only the one it was given (--genesis-root, new), else the one its peers name, and none while they name different ones (access::screen::RootPin, kept in Screening across documents): one peer hands it another root only if it is all the joiner hears, or never, with --genesis-root. A root refused is listed with the refused, what builds on it with it. * The genesis node logs the root (`The swarm's root`); GET /admin/epochs shows it (`root`). A genesis started again under a remembered swarm ID used to merge with the nodes of it still running; they now refuse it (another root), and the docs say to join them with seeds instead. The epoch's identity stays as it is (generation 0 all zeros): records and epoch metadata name it so, and every comparison of epochs with them.
* screen: what a node receives judged, policy or nottv3 days1-5/+6
| | | | | | | | | | | | | | | | | | | | | Review 2, N7: a node screened received changes only while its own document had a policy it enforces. A joiner's empty document has none, so its first sync, as much history as the node it joins through chose, was applied unjudged, and under signatures required unvouched; the same for any node while the policy's own change arrived with later ones. Sync messages, deltas and downloaded changes are now always screened, each change judged by the policy at its dependencies (1135), so the verdicts are the swarm's. While neither the document nor the changes hold a policy (none touches a "policy" key: inspect::touch_key), only an epoch's root is judged and nothing is placed: bench_screening, after 1188, measured placing everything at about as much again as applying (564 ms + 543 ms for 100 000 changes), judging by the rules at about five times (+2.5 s), which a document with a policy pays anyway. access.md no longer lists the joiner's first sync, nor nodes agreeing only as the policy reaches them (judged at the dependencies, they agree), as left to do; changes::load_delta is for tests now.
* store: a write inside a list checked where the rules judge ittv3 days1-1/+3
| | | | | | | | | | | | | | Review 2, N9: the API checked a write's capability on the path asked (servers/list/3/name), the rules on the list it is in (servers/list): a user whose capability lay inside a list had every such write pass the API and refused by the node's own judgement (403, after rolling the document back). inspect::placed places a write's path in the document by the rule the inspector places operations by (Place::child: cut after the first list or text on the way), and the store's writes check capabilities there. One rule for both; a test checks placed against the paths the inspector finds in the change such a write makes.
* strict: no strict remake of a swarm strict alreadytv3 days1-1/+3
| | | | | | | | | | | | | Review 2, N2: nothing checked that a strict remake's base wasn't strict already. In a strict swarm the registry, the policy and the bans change only as a user who may change them signs; a strict remake committed by a majority of members, each with keys of its own, could drop any of them (unban, remove managers, empty userSigned and the sealed declarations), and the honest minority would follow. A node now takes a strict remake (proposing or voting) only in a swarm not strict yet: turning strict is done once, by a remake; after it, by signatures.
* refusals: a big drop waits for a second member only where there is onetv3 days1-4/+6
| | | | | | | | | | | | Review 2, N8: a node with one neighbour (a swarm of two, a leaf) never hears a second member name the change: its big drop was held for good, an error logged at every REFUSALS. It now waits only where it has another neighbour than the one telling it; with one, one word is all there is. access.md also says what the wait is: it guards against one peer, not two members saying the same (any node with the swarm key registers), and never makes a node drop what its own rules take.
* remake: aborts written as the user who asks; no abort by timetv3 days1-6/+5
| | | | | | | | | | | | | | | | | Review 2, N3: 1150 let any member write an abort decision once a remake had expired a grace period ago, judged by the change's own time, the writer's word. A member could race the coordinator's legitimate commit with an abort dated as it liked; both taken, Automerge shows one, and nodes that acted on the commit split from those that didn't. The two writers of such aborts are operator requests whose endpoints already need write on _continuum/remakes: aborting an expired remake (DELETE /admin/remakes/{id}) and superseding one (POST /admin/remakes?force=supersede). Both now commit as the user who asks (the proposal and its vote with them, for a supersede), which the rules take as one who may write the decision. The clause judging an abort by its time goes, and with it the time the rules carried. An expired remake is still aborted through any member, by an operator.
* strict: a manifest serves one remake, at its basetv3 days1-1/+2
| | | | | | | | | | | | | | Review 2, N1: a strict manifest wasn't bound to its base. Any member could propose an old, legitimately signed one again at the current heads; nodes would verify it and vote, and every value at a signed place changed or added since (a ban, a new user, a rotated key, a changed userSigned or sealed declaration) would be deleted, nothing after the base being left to replay. The manifest now names the epoch and the heads it was made at, and a node takes it (proposing or voting) only for a remake of that epoch with exactly that base. GET /admin/strict-manifest makes it so; continuum-signed strict proposes at those heads already.
* docs: what a junk logout doestv3 days1-1/+5
| | | | | | | | | Review 2, N11: a value at _continuum/logouts/<name> that isn't a time (read as damaged: every token revoked), or one far ahead, refuses every token of the user, new ones too, until it is replaced. Any member can write it (finding 10). The rules can't see the value (only that it is written), so access.md says it, and how it is set right: the user logs out again by password, or one who manages users revokes its tokens.
* remake: no --follow-strict-remakes; a strict remake failing says whotv3 days1-5/+6
| | | | | | | | | | | | | | | | | A strict remake takes a majority of members whose --strict-keys take its manifest's signer: no node votes for one without, so --follow-strict-remakes (1173) goes. A node that doesn't vote still switches once the others commit, as with any remake. So that an operator can tell why one fails: a strict remake aborted for want of a majority (the voting period over, or a majority out of reach) says in its reason (GET /admin/remakes/{id}, and the coordinator's log) which members didn't vote, and that a member votes for one only with its manifest signed by a key of its --strict-keys. Each of those logs why itself (`Not voting for a strict remake`). The sim a_strict_remake_too_few_take: only a takes the manifest; the remake aborts, naming b and c.
* remake: turning a running swarm strict, keeping what an operator signedtv3 days1-2/+35
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Until a swarm is strict, the user a change names is its node's word, so any member could have planted registry entries, policy, bans or user-signed values as any user, plain or signed by a key it put there itself; writing the strict policy trusted all of it as it was. A remake of a new kind, `strict`, turns a swarm strict instead: * Its content is a manifest (access::strict): the swarm, the strict policy, and each value kept at the places values are signed, by the SHA-256 of its JSON, signed by an operator (ssh-keygen -Y sign -n continuum-strict). * Every node builds the next epoch as compaction does, keeping at the signed places only the values the manifest names, as they were at the base, and writing its policy (RemakeKind::protocol, the one kind that changes protocol data). Writes after the base reach the new epoch only by replays, judged by the strict policy. * A node votes for one only with its manifest signed by one of --strict-keys, keys given out of the document; without any, for none (logged once, `Not voting for a strict remake`), unless --follow-strict-remakes. A coordinator proposes only what it would vote for (403). Once a majority commits one, every node switches, as with any remake: refusing to switch would only cut a node off. * GET /admin/strict-manifest gives the manifest keeping everything held now, the values for review and the heads they are at; continuum-signed strict shows them, signs once confirmed and proposes at those heads. The sim a_swarm_turning_strict_through_a_remake: a member plants a registry entry as a manager; the operator's manifest leaves it out; a and b, given the key, commit; c, without, follows; the entry is gone and the policy strict everywhere.
* users: a user can't remove its own last keytv3 days1-1/+2
| | | | | | | | A user managing its own keys (1168) could remove the last, and then neither sign nor log in by key. It can't now: DELETE /auth/keys/{id} of the last answers 409, the rules take a user's change of its own keys only with one left after it, and under a strict policy its own entry signed only with one key at least. One who may manage users still may.
* refusals: a drop taking much waits for a second member, and showstv3 days1-1/+5
| | | | | | | | | | | | | | | | | | A node told (REFUSALS) that it holds a change the swarm's rules refuse, and whose own rules refuse it too, dropped it and everything built on it on one peer's word, however much that was; its own writes among them went to the quarantine, the others' nowhere. * A drop taking more than 100 changes, or more than ten and a tenth of the document, now waits until a second member names the same change: the node's rules and one peer's word could both be off. Logged as an error, counted in /admin/stats (sync.dropsHeld). * The others' changes dropped are listed with the refused (GET /admin/refused, "taken unjudged, dropped"), for operators to see. The cheap half of asking the others (future-work.md, Telling a malicious change from a wrong policy).
* users: a user manages its own keystv3 days1-1/+6
| | | | | | | | | | | | | | | | | Only a user who may manage users could change a user's keys: a user couldn't add a new laptop's key, or remove a lost one. A user now manages its own keys, whatever else it may do: * GET /auth/keys: its keys, its entry, and the envelope holding it under a strict policy (`signed`), to sign against. * PUT /auth/keys/{id} with an authorized_keys line, DELETE /auth/keys/{id}: one change, as the user. The rules take a write of _continuum/users/<name>/keys/… from the user it names. * Under a strict policy, the user signs its entry with only its keys changed, with a key it holds already (envelope::check takes that from the user itself), PUT to its own entry even without write on the registry; continuum-signed key-add and key-remove do so.
* login: logins by key capped per usertv3 days1-3/+5
| | | | | | | | | | | | Review 6: a node remembers the challenges used until they expire, 10 000 at most, and refused every login by key once full. One user holding a key could make 10 000 logins within five minutes and keep everyone else out. A user may now log in by key at most --key-logins-per-user times (100) on a node within a challenge's lifetime, past which that user waits and others don't; the 10 000 stay as the cap for all users together. The lifetime, five minutes until now, is --challenge-lifetime.
* docs: what the review found that staystv3 days1-4/+18
| | | | | | | | | | | | | | | | Review findings 10 and 12, and 3.1/3.3: what stays as it is, said. * Any member can log any user out: the user a logout names is its node's word, and a logout is no deciding path, even under strict. * Used challenges are remembered in memory: after a restart, one captured within its five minutes could be taken once more. * A registry turns strict as it is: whatever it holds then counts, plain values and envelopes, checked or not. Look it over first. * A value that isn't an envelope starts its chain anew (prev empty). * A strict registry whose only manager lost its keys changes no more: keep two managers, or two keys. * The handed-over token stays in the address bar and history where the other dashboard never loads, for its five minutes.
* envelope: a signed value names its swarmtv3 days1-1/+2
| | | | | | | | | | | | Review 3.1, part of finding 12: a signed value's payload bound path, user, time and prev, but not the swarm. A user with the same name and key in two swarms could have an envelope replayed from one into the other, wherever the path holds no envelope yet (prev ""). The payload now names the swarm (`swarm`), and a node takes an envelope only for its own. /auth/whoami says the swarm, and continuum-signed and the example in api.md sign it. Wire version 44: nodes before this refuse such envelopes, and those after refuse the old ones.
* token: a banned node's tokens aren't takentv3 days1-3/+3
| | | | | | | | | Review finding 11: token::verify checked only that the issuer is a member, and a ban leaves the member entry. A compromised node's tokens stayed valid after it was banned, until it was forgotten too. A token issued by a banned node is now refused (BannedIssuer), as soon as the ban reaches the node judging it.
* dashboard: hand over a token reading the member list onlytv3 days1-1/+1
| | | | | | | | | | | | | Review finding 8, second part: opening another member's dashboard handed it a token reading everything (READ_ALL). A member writes its own dashboard URL, so a malicious one received a read-everything token under the operator's identity: /admin/config, /admin/changes, /admin/log and the rest, on every node the user reaches. The token handed over now reads the member list only (READ_MEMBERS, as sent to subscribed nodes), which is enough to draw the map; the user logs in at the other dashboard to see more. With 1151 it lasts five minutes and isn't refreshed past them.
* login: a token asked to live less ends its session with ittv3 days1-2/+3
| | | | | | | | | | | | Review finding 8, first part: a refreshed token's lifetime came from the refresh request, and it kept the session's end. So the five-minute read-all token a dashboard hands to another member's refreshed into a 12-hour one, renewable for seven days. A token asked to live less (`lifetime`, at login or refresh) now ends its session with it: nobody refreshes it past its end, its holder included, and a session begun from it (POST /auth/session) ends then too.
* rules: an expired remake aborted by any membertv3 days1-2/+5
| | | | | | | | | | | | | | | | Under a policy, a remake's decision was taken from its coordinator only. A node aborting an expired remake whose coordinator is gone (DELETE /admin/remakes, after abortableAt) was refused by its own judgement (403), so such a remake could never be decided. The decision is now also taken from a user who may write it (the protocol writers), and an abort from any member once the remake expired a grace period ago (abortableAt, by the coordinator's grace period in the proposal). The rules have no clock of their own, so that is judged by the time the change says it was made: the writer's word, so a member could abort a remake early, as it could ask its coordinator to, but never commit one. Before then, an abort stays a request the coordinator applies.
* docs: a member's entry, as the rules have ittv3 days1-3/+4
| | | | | | | Review 1.3: access.md said a user who may write _continuum/members may only remove another member's entry, but the rule lets them write any of it (addresses, dashboard). Those are operators, who may set an entry right through the store, so the doc now says what the rule does.
* rules: a carried remake record taken from the coordinator its snapshot namestv3 days1-1/+2
| | | | | | | | | | | | | | | | | Review finding 7, second part: a record carried into a new epoch whole (proposal and decision in one change) was taken from any member, for any remake ID. A member could write a forged record, decision included, for a fresh ID in a single change. The snapshot's epoch metadata now names the remake's coordinator, as the quorum built it. The carried-record exception holds only in a later epoch, only for the remake that made it, and only from the coordinator its snapshot names, where there is no record yet. Wire version 43: the snapshot, and so the epoch ID, changes. The review suggested carrying the record inside the snapshot instead. The snapshot can't hold the decision, which names the snapshot's own hash. So the snapshot vouches for who may carry the record instead.
* rules: protocol maps judged by what is done to themtv3 days1-2/+7
| | | | | | | | | | | | | Review finding 7, first part: the protocol-map rules looked at paths, not actions. Any member could delete _continuum/members, /votes or /remakes whole, or rewrite another remake's proposal (its coordinator) and then write its decision. What holds the members' entries, votes and remake records, and a remake's record itself, are now made by a member only where there is none; a proposal is written only where there is none. Replacing or removing any of them is left to a user who may write there (the protocol writers). An abort asked for stays any member's.
* screen: a signature vouches for its change and what it builds ontv3 days1-2/+4
| | | | | | | | | | | | | | | Review finding 5: vouching by (actor, seq) isn't bound to a hash. A member can make a change under another member's actor at a seq that member's signature covers. The change is then taken as vouched, and if it breaks a rule, the honest member is refused as a whole and logged as the one whose own change broke the rules. A signature now vouches for its change and the changes of its actor it builds on: in the document, as before; among received changes, those it reaches through the batch's dependencies. A received change under an actor at a seq whose change the document holds (a signature verified up to it) is a fork: refused as forged, never as its actor's node's, not told back in REFUSALS, and logged with the peer that brought it.
* sync: a member refused as a whole, synced with again once it changed documenttv3 days1-2/+3
| | | | | | | | | | | | A node refusing a member as a whole sent it nothing, and lifted that only when a change of the member's was taken again. But a member that dropped what was refused (REFUSALS) starts its sync sessions over, and a fresh session sends no changes until the other side answers: the two waited for each other, and the member stayed refused. The refusal now notes the member's run and epoch_seq when it began; a member with another document since (it dropped what was refused, or rejoined) is synced with again.
* access: a node judges its own changes before they leavetv3 days1-0/+6
| | | | | | | | | | | | | | | | | | | | 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).
* access: forgetting and revoking as the API letstv3 days1-2/+3
| | | | | | | | | | | | | | Two writes the API lets a user make were refused by the rules: * DELETE /peers/{pk}, with write on _continuum/forgotten alone, writes the forget record and removes the member's entry in one change; the rules wanted write on the entry. Removing an entry is now also taken in a change forgetting that member, from a user who may forget it. * DELETE /admin/users/{name}/tokens by a user who manages users writes _continuum/logouts/<name>; the rules wanted write there. One who may write _continuum/users may now write anyone's logouts entry. Review finding 3 (DELETE /peers, revoking tokens).
* access: maps made on the way judged by what they holdtv3 days1-0/+2
| | | | | | | | | | | | A write makes the maps on its way where there are none (store::write_path); the rules judged each made map as a write at its own path, so a user whose capability lies below a map not made yet (write on servers/eu, a first PUT at servers/eu/0) had its first write taken by its node and refused by every other. A map made where there was none, in a change writing within it, is now judged by what is written within it. Review finding 3 (parent maps created on the way).
* access: judged by the policy written undertv3 days1-1/+3
| | | | | | | | | | The rules read the policy at the change's dependencies only for strict mode, so history from before the policy was judged by today's rules when a late node caught up, or when REFUSALS had it judged again. A change written while the document had no policy, or one this node doesn't know, is now judged by none. Review finding 6.
* access: a forgotten node cancels its own recordtv3 days1-0/+1
| | | | | | | | | | A node forgotten swarm-wide while alive cancels its forget record with a change naming no user; the rules judged _continuum/forgotten by the registry alone, so every node refused the cancellation and everything the node wrote after it. A node may now write its own _continuum/forgotten/<id>, as it may its member entry. Review finding 4.
* dashboard: a token handed over to another member's dashboardtv4 days1-1/+3
| | | | | | | | Opening another member's dashboard passes a token reading only, for five minutes, in the URL's fragment; its login page begins a session with it. Tokens may be asked for with a shorter lifetime. Auth plan M3 (hand-off).
* swarm: changes taken unjudged, dropped (REFUSALS)tv4 days1-1/+12
| | | | | | | | | A node refusing changes a peer brought tells it which broke a rule; the peer judges them itself, and if its rules refuse them too, drops them with all built on them (its document forked without them, a fresh actor), quarantining its own writes among them. Wire version 42. Auth plan M6 (rejoin on taint).