From 76f5aef17adda9c443eec3e231d76ad5e275a6e1 Mon Sep 17 00:00:00 2001 From: Jeff Gonzalez Date: Mon, 7 Sep 2026 14:29:13 -0400 Subject: [PATCH] =?UTF-8?q?marmalade:=20reviewed=20=E2=80=94=20ssh=20is=20?= =?UTF-8?q?needed=20nowhere,=20and=20write=20access=20nowhere=20either?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every operation is HTTPS on 443. Measured that partial clone with the blob filter works over gitea's smart HTTP, so list never needed ssh; I had built and measured it over ssh out of habit. The tool writes only to the working tree — a fetched document, a lock, a facet — and never commits or pushes, so it needs no write credential anywhere, including this repository. Same reason cart gives for an agent committing and not pushing: the consequences of a push land on people it cannot experience. write:repository should never appear on a token this tool holds. The exception is published check, which needs an identity that is not the owner's. That is not an ssh problem and not solvable by scoping — it is the command's point. Anonymous or a reader credential, both HTTPS, and checked-as-owner-proves-nothing when neither is held. One untested assumption, and it is load-bearing: whether a read-scoped PAT authenticates git over HTTPS and not only the API and raw files. Those are different paths in gitea and only the second was ever measured. If it needs a broader scope, that is the sole argument for keeping ssh for list. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris --- .loom/cart/current/claude-marmalade.md | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/.loom/cart/current/claude-marmalade.md b/.loom/cart/current/claude-marmalade.md index 0883f65..5b82eb0 100644 --- a/.loom/cart/current/claude-marmalade.md +++ b/.loom/cart/current/claude-marmalade.md @@ -226,3 +226,87 @@ audience and the audience is not always everyone.* *Not an apology. **A thing to point a test at**, and the reason the log carries a belief rather than a conclusion.* + +--- + +## Reviewed: what needs ssh, and what needs write + +**Short answer: ssh, nothing. Write access, nowhere — including here.** + +### Every operation, and what it actually needs + +| | what it does | transport | credential | +|---|---|---|---| +| **`external list`** | *partial clone, `ls-tree` the published dir* | **git over HTTPS** | *read PAT* | +| **`external add`** | *`GET` the raw file, record URL + `ETag`* | **HTTPS** | *read PAT* | +| **`external check`** | *conditional `GET`, `If-None-Match`* | **HTTPS** | *read PAT* | +| **`published check`** | *`GET` our own published file* | **HTTPS** | ***not ours*** | +| **everything it writes** | *files in the working tree* | **none** | **none** | + +**Measured just now: `git clone --filter=blob:none --depth=1 --no-checkout` over +`https://` on gitea** — *filter accepted, 124KB, `ls-tree` lists the published +directory.* **I had built and measured `list` over ssh out of habit, and it never +needed it.** + +### The tool never writes over the network, so it needs no write credential + +**Including in this repository.** *`loomctl` puts files in the working tree — a +fetched document, a lock, a facet — and stops.* **Committing and pushing are yours, +under your own credentials.** + +> *That is the same reason `cart` gives for an agent committing and not pushing:* +> **the consequences of a push land on people it cannot experience.** *A tool that +> can push is a tool that can publish, and publishing is supposed to be an act +> somebody performs.* + +**So `write:repository` should never appear on any token this tool is given**, *and +a token that has it is a token we would rather not hold.* + +### The one thing a single PAT cannot do + +**`published check` needs an identity that is not the owner's, and yours is +yours.** *That is not an ssh problem and it is not solvable by scoping — **it is +the command's entire point.*** + +**Two workable identities, both HTTPS:** + +- **anonymous** — *the right answer when the audience is everyone* +- **a second credential belonging to the actual audience** — *a reader account, or + a token from somebody in the group you published for* + +*And if you hold neither, the honest output is **"checked as owner, proves +nothing"**, which is still worth printing.* + +### One thing untested, and it is the load-bearing one + +**Whether a gitea PAT scoped `read:repository` authenticates *git over HTTPS*, not +just the API and raw files.** *They are different paths in gitea, and the deleted +token was only ever measured against the second.* **Measured: an unauthenticated +HTTPS clone of the private repository returns `Failed to authenticate user`**, *so +something is required; nobody has confirmed that a read PAT is enough.* + +**First thing to run against the next token.** *If it turns out git-over-HTTPS +needs a broader scope than raw HTTP, that is an argument for keeping ssh for `list` +after all* — **and it would be the only argument.** + +### An aside that is too neat not to record + +**`:2222` cost me three tool calls and a guess, and the tool does not need it.** +*Every transport here is HTTPS on 443.* **The fact that bit me is a fact for +people cloning repositories, not for `loomctl`** — *which is its own small lesson +about who a published document is for.* + +### Implementation note: keep the token out of the URL + +**Do not build `https://@host/…`.** *git writes the remote into +`.git/config`, and it turns up in logs and in `git remote -v` afterwards.* + +**Pass it per-invocation instead:** + +``` +git -c http.extraHeader="Authorization: token " clone --filter=blob:none … +``` + +*Nothing persisted, nothing in the repository.* **Which is the same rule as the +credential file: the mistake we would most regret is the one that happens by +accident.**