Repository navigation
fix(cli): keep item content off the private socket and say so - #2256
Merged
Merged
Conversation
Merged
3 tasks done
… bulk jobs included
…event doors closed to the socket
aicayzer
commented
Oct 21, 2026
aicayzer
left a comment
Contributor
Author
There was a problem hiding this comment.
Review: ready to merge
Summary. The private socket was never meant to reach item content: every content route already refuses it, so the fix is a contract statement that says so, a CLI message that names what needs a key, and READMEs that no longer promise more; the server is unchanged and the change is safe to merge.
First draft: 3/5. No blocking issue; 5 smaller ones found, fixed in c8c4a0785, 6e480662a and dc154ee9c.
Found.
- The fixture did not try every door the statement names. Fixed: edges by id, edge bulk, metadata, extensions, tombstones and events now each have a socket refusal and a keyed witness.
- The CLI message and READMEs said item content needs a key, but attachments and bulk-job status are reachable through the socket, on purpose (
blobs/read-operator-key). Fixed: the words list what needs a key and what works without one. - The message named events without a statement behind it. Fixed: events are in the statement and its tests.
- The statement overlapped
search-and-filters/owner-read-boundaryon counts and exports. Fixed: it points there instead. - Prose in
deploy/README.md. Fixed.
Checked. Every item, edge, tag, metadata, extension, search, occurrence and event route answers 401 over the socket and over HTTP with no key; refused requests leave no audit rows; a key minted through the socket does the work and is named in the audit log.
aicayzer
marked this pull request as ready for review
October 21, 2026 09:38
aicayzer
enabled auto-merge (squash)
October 21, 2026 09:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Decision: the private control socket keeps out of item content, so
items taganditems bulk-actionbeing refused over--socketis correct. The bug was in the docs, which said the socket had the same authority as the owner, and in the CLI message, which did not say which commands need a key. This pull request writes that boundary into the contract, holds it with tests, correctsdeploy/README.mdandcore/README.md, and makes the CLI's401message over--socketname which commands need a key and which work without one.Evidence for the decision:
search-and-filters/owner-summaries-authority(GET /items/summaries),items/owner-trash-actionsanditems/owner-trash-cli(trash through summaries), anditems/job-read(job status).search-and-filters/owner-read-boundarykeeps counts and export closed to it. No statement admitted it to an item content door, but none refused it explicitly either. This pull request adds that refusal asitems/local-authority-content.authority: local_processwith no key (middleware/direct-authority.ts). Every content door decides with the key's type map (requireAuth,checkAuth,requireTypeAccess,requireWritableRow,computeTypeFilter), so it answers401when there is no key. Direct authority is admitted only throughdirectAuthorityOnlyandstandingPermission, which no item content route uses.marfaover a fresh local server's socket, pluscurl --unix-socket).401:items get/list/create/update/tag/untag/transition/delete/restore/purge/edges/backrefs/versions/stats,items bulk-action(all six actions,purgeincluded),PUT/PATCH /items/{id}/metadata, every/items/{id}/extensionsdoor,GET/POST /edges,GET/DELETE /edges/{id},POST /edges/bulk,GET /search,GET /occurrencesandexport. Admitted:items summariesand everyitems trashcommand.items createanditems updatedo not work over the socket either, sotagandbulk-actionwere never exceptions.blobs/read-operator-key, andcli/instance.test.tsdownloads a blob through the socket), and bulk-action job status and cancel (items/job-read). The server's own account can already read the data folder, so blob bytes add no reach. The words below say so.keys create), and that key's map limits its reach and its ID names it in the audit log. Bulkpurgestays refused, consistent with the deliberate refusal of a private-socket purge elsewhere: the socket purges only one summary at a time, at its selected version, through the owner trash.The server's behavior does not change. No route gains or loses access.
Changes
items/local-authority-contentinconformance/spec/items.md. When a request carries only private local authority to an item, edge, tag, metadata, extension, tombstone, search or occurrence operation, or toGET /events(other than item summaries, the owner trash, and reading or cancelling a bulk-action job), with a body the operation accepts, the server answers401 unauthorized.eventsis in the statement because the stream carries item and edge content within a type map. Counts and exports are owned bysearch-and-filters/owner-read-boundary, which the statement's reason points to, so no two statements govern the same door.device/local-socket-names-key-needsinconformance/spec/device.md. On a401through--socket, the CLI says which commands need a key and which work through the socket without one.conformance/src/suites/compliance/local-authority-content.test.ts. Each door is tried over the socket, over HTTP with no key, and with the working key as a witness. Writes are checked to have changed nothing. It coversGET /metadata/tags,GET,PATCHandDELETE /edges/{id},POST /edges/bulk(on its own pair of items),PUT /items/{id}/metadata,GET,PUTandDELETE /items/{id}/extensions/{namespace}(seeded first),POST /items/tombstones(a purged row's tombstone, checked unchanged) andGET /events. The bulk-action cases cover all six actions, and one realupdate_tagsjob checks that the socket queued nothing.401message over--socketkeeps its first sentence and adds: "Item properties, edges, tags, search, counts, events and exports need a key.items summaries,items trash, theblobscommands, anditems bulk-action jobanditems bulk-action cancelwork through --socket without one". The--sockethelp text says the same, in the same words.COMMANDS.mdis regenerated.cli/instance.test.ts(real binary, real server socket) now coversitems tagand all sixitems bulk-actionsubcommands, each with a key witness.device/contract.test.tscoversitems taganditems bulk-action tags, and no longer listsblobs downloadorconnectors deliveries bodyas401cases, because the real server admits both. The Rust unit test checks the new sentence.deploy/README.mdno longer says the socket has "the same machine authority" as the owner. It says what needs a key, what works without one, and points tokeys create.core/README.mdsays the same and namesitems list,items tag,items bulk-action tags,eventsandexportas refused.Testing
direct-authority.tsto give the socket a full key. All five failed (expected 200 to be 401and similar), and I then reverted the patch. The CLI scenario, the device test and the Rust unit test each failed on the new sentence before the message changed, and passed after.pnpm typecheck,pnpm lint,pnpm format:check: pass.cargo fmt,cargo clippy -p marfa-cli --all-targets -D warnings: pass.cargo test -p marfa-cli reference(regeneratedCOMMANDS.md): pass.pnpm test:changed: 83 files, 1096 tests pass; 24 files, 238 tests pass.marfa:upserver:compliance/local-authority-content,control-socket,owner-item-read,item-summaries,owner-trash,connector-direct-authority,owner,owner-sign-ins,version-writersandjson-body-doors: 122 tests pass.cli/instanceandcli/item-summaries: 21 tests pass.device/contract: 82 tests pass.spec-formandspec-citations: pass.items tag,items bulk-action tags,items bulk-action purgeanditems createexit5with the new message. Over HTTP with no key,POST /items/{id}/tagsandPOST /items/bulk-actionsanswer401.keys createthrough the socket mints a key, and that key then tags the item and queues a bulk tag job. The audit log names the mintlocal:process("Local command") and names the tag and bulk job by the minted key. The refused socket requests left no audit rows.cargo fmt --check,cargo clippy -p marfa-cli --all-targets -- -D warningsandcargo test -p marfa-cli local::pass.pnpm format:check,pnpm typecheckandpnpm lintpass.COMMANDS.mdwas regenerated withMARFA_WRITE_COMMANDS=1.spec-form,spec-citationsandspec-statementspass (48 tests). The new compliance file passes (9 tests). Against amarfa:upserver and the debug binary,cli/instance.test.tsanddevice/contract.test.tspass (100 tests). A probe over a fresh server's socket confirmedblobs(stores, orphans, bytes, URL, locations) and bulk-action job read and cancel answer200, andGET /eventsanswers401.Notes for reviewers
Review depth: full, it changes what the private socket may do.
400(body validation runs before the handler's key check), while HTTP with no credential gets401before the body is read. The new statement therefore covers requests "with a body the operation takes". This matches how a key without a grant is answered (400before the permission check), and nothing can use the door either way.core/README.mdand feat(config): apply owner settings without restarting #2253 also touchescore/marfa-cli/COMMANDS.md, in different lines. Whichever merges second rebases;COMMANDS.mdis regenerated, not merged by hand.marfa --socketshould say whatdeploy/README.mdnow says: item properties, edges, tags, search, counts, events and exports need a key, whichmarfa --socket PATH keys createmints, while item summaries, the trash, blobs and bulk-action job status and cancel work without one. Also update any page that repeats "the same machine authority without an API key".Checklist