aboutsummaryrefslogtreecommitdiffstats
Commit message (Collapse)AuthorAgeFilesLines
* refusals: an import left unusedtv3 days1-1/+1
| | | | | Since judge_held moved to access::rules (1140), swarm::refusals uses ReadDoc no more; clippy warned.
* login: logins by key capped per usertv3 days9-27/+148
| | | | | | | | | | | | 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.
* dashboard: log out of this browser, and say what can't betv3 days4-5/+48
| | | | | | | | | | | | | | The dashboard had no way to log out; only the login page did. It now shows the user beside the connection, and "log out" where the page got in by its session: that ends this browser's session (DELETE /auth/session), other sessions of the user going on, and opens the login page. Where it got in by what it can't end, the browser's password or the proxy's, it says so instead (the token answer now names `login`, how the user proved it). After logging out, a page still let in by the browser's password says so too. A list of a user's sessions, to end one by one, is in future-work.md.
* auth: no password prompt over the dashboard's own requeststv3 days2-5/+10
| | | | | | | | | | | | | Opening a dashboard through its login page showed a 401 on /auth/login and the browser's password prompt. The page asked for its token by the browser's password first (POST /auth/login), with none to send, and the 401 carried a Basic challenge, which a browser answers with its prompt. The page now asks by its session first (POST /auth/session), then by the browser's password or the proxy's. And a 401 to a page's own requests (Sec-Fetch-Dest: empty: fetch, EventSource) carries no challenge: a page has its login of its own. Navigations and clients without the header (curl) are still challenged.
* simtest: forged changes, aborts of expired remakes, envelopes of another swarmtv3 days1-0/+185
| | | | | | | | | | | | | | Simulations backing what the review's fixes changed, beyond their unit tests: * a_change_forged_under_another_members_actor (1145): a change made under b's actor at the seq of b's last change, judged by a: refused as forged, b not refused as a whole, b's next write taken. * an_expired_remake_aborted_by_any_member (1150): under the policy, a coordinator gone before it decides; b aborts its remake once it expired a grace period ago, c takes the abort, nothing refused. * a_strict_swarm (1155): a value alice signed for another swarm, replacing the one there, is refused as such.
* screen: waiting changes building on a refused one refused at oncetv3 days1-25/+90
| | | | | | | | | | | A change waiting for a dependency that hadn't come stayed waiting when another of its dependencies was refused meanwhile, until the bound pushed it out. Changes building on one refused before were refused one generation per batch. Building on a refused change is now followed through at once, before judging and again after (refuse_building): whatever waits on a change refused in the batch, or on one of those, is refused then and there.
* docs: a signed null keeps the prev chain goingtv3 days1-1/+4
| | | | | | Review 3.1: a signed null stays as an envelope, so the value written after it names it in prev and an envelope from before the deletion can't be put back. api.md now says so.
* screen: a batch Automerge refuses names its changestv3 days1-2/+41
| | | | | | | | | | | | Review section 7: Automerge takes the changes judged in a batch all or none, so one it won't take (a fork of an actor's changes, under a policy without signatures) fails the whole batch, and every batch carrying it. The log said who sent the message (`Failed to process sync message`, `Delta download failed`, with the peer) but not what failed. The error now names the changes it was given, the first eight, each with its seq and the node it says made it, so whoever sends the offending one shows in the log beside what it sent.
* future-work: decisions by their votes, where a token is handedtv3 days0-0/+0
| | | | | | | | | | | From the review's list of what to carry on (section 8): * A remake's commit judged by the votes the register holds at the decision's dependencies, now that votes are their members' own under signatures required: the middle ground to counting again, which review-rejections.md rejected for its cost. * The hand-over target is the member's word: show it, and ask, where its host is none the member's entry otherwise names.
* docs: what the review found that staystv3 days2-5/+22
| | | | | | | | | | | | | | | | 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.
* login: the session cookie Secure behind a TLS proxytv3 days2-2/+58
| | | | | | | | | | | Review finding 12, browsers: the session cookie had no Secure flag. Behind a TLS proxy, the token it carries went over plain HTTP if someone typed http://. POST /auth/session now sets Secure when the request came over TLS, as the proxy says (X-Forwarded-Proto, or Forwarded's proto, of the first hop). A client saying so itself only keeps its own cookie off plain HTTP. operations.md's nginx example sets the header.
* dashboard: a content security policy, framed by nonetv3 days2-5/+59
| | | | | | | | | | | | | | | | | | Review finding 12, browsers: the dashboard holds a bearer token in memory and edits the document, but had no content security policy and could be framed by any page of the same site (another port on the same host, anything sharing a domain behind a proxy), acting with its user's token. The dashboard and the login page now come with a policy that runs only their own inline script (by its hash, computed when first served), loads nothing from elsewhere, and lets no page frame them (frame-ancestors 'none', X-Frame-Options DENY). Fetches stay open to any origin, as the members' dashboards are wherever the member list says. The browser check (check.js) loads the page from a file, without the header, so the policy wants one look in a browser.
* envelope: a signed value names its swarmtv3 days13-16/+52
| | | | | | | | | | | | 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 days3-6/+22
| | | | | | | | | 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.
* screen, signatures: the oldest waiting go, not the newesttv3 days2-9/+69
| | | | | | | | | | | | | | Review finding 9: once 4096 signatures waited for their changes, new ones were dropped, and the waiting changes were cut at 10 000 keeping the oldest. A member could send junk once (signatures of changes that never come, changes no signature will vouch for) and fill both for good. Every honest signature arriving before its change was then lost, so under signatures required its changes waited for the actor's next signature: a stall for quiet actors. Both now keep the newest: the oldest go. A share per sender, so a member sending junk without pause can't push out what others sent either, is in future-work.md.
* dashboard: hand over a token reading the member list onlytv3 days4-10/+11
| | | | | | | | | | | | | 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 days3-10/+32
| | | | | | | | | | | | 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 days4-3/+113
| | | | | | | | | | | | | | | | 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 days7-25/+88
| | | | | | | | | | | | | | | | | 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 days2-4/+53
| | | | | | | | | | | | | 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.
* state: a document swap keeps the signatures of the changes it keepstv3 days2-3/+26
| | | | | | | | | | | | | Review 2.2: swap_document forgot every signature. After dropping changes taken unjudged (or a rejoin into the same epoch), the node could vouch for none of the changes it kept: under signatures required, those building on an actor waited for that actor's next signature, and for an actor that stopped writing, forever. Signatures::kept_for keeps the signatures of the changes the new document holds, checks each actor's latest again, and keeps those waiting for their changes. This node's replayed changes are signed at once, as before.
* screen: a signature vouches for its change and what it builds ontv3 days5-14/+116
| | | | | | | | | | | | | | | 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.
* simtest: the forged write refused by whichever node judges it firsttv3 days1-4/+9
| | | | | | | a_swarm_judging_every_change waited for c's forged write to be refused by a, b and d each. The first to judge it tells c, which drops it before the others may have it: wait for a refusal on any of them, then for c to drop it.
* simtest: refused by whichever node judges firsttv3 days1-10/+18
| | | | | | | | In a_strict_swarm and sealed_values, c's forged writes were waited for to be refused by a and by b. Whichever judges them first tells c, which drops them before the other may have judged them (it waits for their signature, or never gets them): wait for a refusal on either, then for c to drop them.
* sync: a member refused as a whole, synced with again once it changed documenttv3 days4-5/+37
| | | | | | | | | | | | 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.
* remake: replays judged before they go intv3 days5-2/+122
| | | | | | | | | | | | | | | A switching or rejoining node replays late writes as fresh changes naming each write's user, and nothing judged them before they left: a replay every node would refuse (a revoked capability, a second envelope at a signed path, readers changed) was refused everywhere, and with it everything the node wrote after it. Under a policy, replay_newest now judges each group it commits as every node will (rules::judge_held, in the new epoch); one they would refuse is taken back (forked at the heads before it, same actor) and its paths quarantined with the reason. replay_newest takes the epoch. Review finding 3 (remake replays).
* access: a node judges its own changes before they leavetv3 days7-61/+204
| | | | | | | | | | | | | | | | | | | | 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).
* remake: replay from the quarantine where the user may writetv3 days3-16/+51
| | | | | | | | | | POST /admin/quarantine/{id}/replay needs write on _continuum, which never covers user data, and replayed every path of the entry as the user who asked: every node refused the user data in it. It now replays only the paths the user may write and keeps the rest in the entry (left), for one who may; 403 if the user may write none. Review finding 3 (quarantine replays).
* access: forgetting and revoking as the API letstv3 days2-6/+49
| | | | | | | | | | | | | | 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 days2-0/+86
| | | | | | | | | | | | 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: signed values in before the next is judgedtv3 days1-2/+73
| | | | | | | | | | | Under a strict policy the rules read user data: the value at a signed place (prev) and what holds one. A batch judged at its frontier missed a signed write earlier in the same batch, so two successive signed writes at one path received together were refused on some nodes and taken on others. A change at, within or above a signed path now goes in before the next is judged. Review finding 2.
* access: judged by the policy written undertv3 days3-3/+43
| | | | | | | | | | 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 days2-1/+9
| | | | | | | | | | 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.
* access: an epoch's root is never droppedtv3 days4-4/+54
| | | | | | | | | | | | | | | A REFUSALS naming the swarm's first change made the node receiving it judge that change again in a document that isn't empty, refuse it as "a change without dependencies that isn't the epoch's root", and drop it with everything built on it: its whole document. The first epoch's root is now the first change a document holds, as it comes to an empty one or as it is in one; a change judged again on a peer's word is never refused for having no dependencies; and drop_taken refuses to drop an epoch's root, or anything that would leave nothing kept, logging an error instead. Review finding 1 (critical).
* dashboard: flashes below labelstv3 days1-3/+5
| | | | Flashes are drawn beneath the nodes' labels.
* dashboard: message flashestv3 days2-33/+99
| | | | | messages: off / dots / flash: a message expands at its own speed and leaves an afterglow fading out in the direction it went.
* simtest: members against the rules, and a policy unknowntv3 days1-0/+102
| | | | | | | | A member writing past its API's checks is refused, told, drops its change and is taken again; a node under a policy it doesn't know takes no writes and syncs on. Auth plan M6 (tests).
* dashboard: a token handed over to another member's dashboardtv3 days6-12/+62
| | | | | | | | 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)tv3 days16-38/+462
| | | | | | | | | 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).
* access: members refused as a wholetv3 days11-6/+168
| | | | | | | | | With signatures required, a member whose own change broke a rule is refused as a whole: sent nothing, shown in /peers (refused), in stats (sync.refusedPeers) and on the dashboard, until a change of its is taken again or the epoch changes. Auth plan M6 (diverged standing).
* auth: the session cookie lets in reads onlytv3 days4-12/+33
| | | | | | | | The cookie lets in GET, HEAD and the session's own requests; anything changing something brings the token itself, which no page of another site or port can make a browser send. The dashboard's writes send it. Auth plan M3 (browsers).
* access: refused oncetv3 days5-17/+203
| | | | | | | | A refused change is remembered by its hash: offered again it is refused without decoding, judging or logging, so is one built on it, and a peer offering nothing else is answered later each time, up to a minute. Auth plan M6 (re-sending).
* docs: trust as it standstv3 days1-32/+60
| | | | | | | trust.md: no policy, a policy, strict, sealed values; what members are trusted with at each step, and the logins. Auth plan (docs).
* auth: a login page and browser sessionstv3 days9-20/+292
| | | | | | | | | GET /login without authentication: log in by password or with a token (from a login by key). POST/DELETE /auth/session keep the token as an HttpOnly, SameSite=Strict cookie; the dashboard sends a browser without a user there and renews the session while open. Auth plan M2/M3 (browsers).
* docs: reading keys and declarationstv3 days2-0/+20
| | | | | | | Clients checking signatures or sealing read the registry and _continuum/access: operators grant reading them. Auth plan M7, M8 (docs).
* simtest: sealed valuestv3 days1-1/+81
| | | | | | | A value sealed for its declared readers goes everywhere; one sealed for a stranger too, or plain, gets nothing past the others. Auth plan M8.
* scripts: sealing and opening in the reference clienttv3 days2-5/+71
| | | | | | | continuum-signed seal and open, with age, for the readers declared or CONTINUUM_READERS. Auth plan M8.
* access: values sealed for their readerstv3 days8-3/+531
| | | | | | | | | | cs1.<age file>: a value encrypted with age to its readers' SSH keys, which no node decrypts. _continuum/access/sealed declares paths whose values must be sealed, for named readers or anyone; nodes check the recipients by age's key tags, at the API and on receipt. Auth plan M8, reworked: sealed values instead of containers, readers as users only (nodes hold no keys).
* simtest: a strict swarmtv3 days1-1/+154
| | | | | | | | Signed values everywhere, carried over by a remake and replaced after it; a member writing the registry or a signed path plain gets nothing past the others. Auth plan M7.