GLIP-09: Visibility and withheld content
What non-public means on a network where anyone can run a node: two privacy tiers, the side channels they close, and how withheld content replicates.
Contents
This GLIP defines what non-public means on a network where anyone can run a node. It specifies two distinct privacy tiers with different trust assumptions, the rules that keep non-public activity from leaking through side channels, and how withheld content replicates as ciphertext.
Status note: extracted from gitlawb-node v0.x behavior. Open decisions are marked [OPEN].
1. The two promises
A repository is in exactly one visibility tier:
- Public — the default (
is_public: truein GLIP-01 §4.1). Everything is readable by anyone. - Private (mode A) — policy privacy: the node enforces authorization on every read and hides the repository's existence (GLIP-01 §5.2). This promise is only as strong as the node operator; the protocol cannot make a node keep a secret from itself. Software presenting mode A to users MUST NOT describe it as end-to-end private.
- Withheld (mode B) — cryptographic privacy for selected content: refs, commit/tree structure, and non-withheld blobs are public, but the blobs at designated paths are available only as ciphertext, encrypted to an owner-controlled recipient set. Mirrors and network observers hold bytes they cannot read.
The tiers answer different threats:
| Observer | Public | Private (A) | Withheld (B) |
|---|---|---|---|
| Anyone | everything | nothing (404) | refs, trees, non-withheld blobs |
| Hosting node operator | everything | everything | everything except withheld plaintext¹ |
| Mirror / network | everything | nothing | ciphertext only |
| Granted reader (GLIP-10) | everything | everything | everything |
¹ Holds iff encryption happens client-side before push. [OPEN] The reference implementation's encryption point (client vs node) must be pinned here, because it decides the operator column. If the node encrypts, mode B protects against mirrors and observers but not the hosting operator, and this table MUST say so.
2. Managing visibility
GET|PUT|DELETE /api/v1/repos/{owner}/{repo}/visibilitySigned (owner only). PUT sets the mode and, for mode B, the withheld path set and recipient set. Visibility state is repository metadata, not history: changing it does not rewrite objects already fetched by others (see §7).
The withheld path set of a mode-B repository is public:
GET /{owner}/{repo}/withheld-pathsUnsigned read. Mirrors use this to decide replication strategy (§6); clients use it to explain missing content to users instead of failing opaquely.
3. Mode A: private repositories
- Every git and API read of a mode-A repository MUST require a signature from the owner DID or from a DID holding a
git/fetchgrant for the repository (GLIP-10). - Unauthorized and unauthenticated requests MUST receive the same
404as a nonexistent repository — same status, same body shape, and implementations SHOULD avoid measurable timing differences (GLIP-01 §9). - Mode A repositories get no public bundle URIs. A node MAY advertise
bundle-urito an authorized fetcher, and if it does the URI MUST be single-use-scoped or short-TTL (the reference implementation uses short-TTL presigned object-storage URLs).
4. Mode B: withheld content
4.1 Envelope format
A withheld blob is stored and served as an encryption envelope:
- Content encrypted with XChaCha20-Poly1305 under a per-blob (or per-repo-generation) content key.
- The content key is wrapped once per recipient using X25519, with each recipient's X25519 key derived from their Ed25519
did:keypublic key. The recipient set is therefore a list of DIDs. - The envelope is addressed by the git object id of the plaintext blob, so trees and packs reference withheld content by its real oid and history stays intact.
[OPEN] The exact envelope byte layout, the Ed25519→X25519 derivation, and the key-wrap construction must be specified byte-for-byte with test vectors before this GLIP leaves draft — this is signed/encrypted material and falls under the GLIP-01 §8 canonicalization rule. Vectors ship in vectors/ with this GLIP.
4.2 Serving
GET /{owner}/{repo}/encrypted-blobs — list withheld oids
GET /{owner}/{repo}/encrypted-blob/{oid} — one envelope
GET /{owner}/{repo}/encrypted-blobs/replicate — bulk transfer for mirrorsEnvelopes are servable to anyone — the recipient wrapping is the access control. Plaintext of a withheld blob MUST NOT be served on any route, including git-upload-pack.
4.3 Git transfer of mode-B repositories
- The node MUST exclude withheld blobs from packs it builds (
upload-packfiltered path). Clients see the repository as a promisor-style partial clone: trees reference oids the pack does not contain, and the client obtains them (as envelopes) via §4.2 or tolerates their absence. - The node MUST NOT advertise an unfiltered
bundle-urifor a mode-B repository. Filtered baseline bundles are permitted.
5. No leaks through side doors
Gating repository content is necessary but not sufficient. Normative blanket rule:
A node MUST NOT emit any event, feed entry, certificate reference, gossip message, content-addressed object, artifact, or search/index entry derived from a mode-A repository on any surface readable by parties not authorized to read that repository.
Specific consequences:
- Ref-update events (GLIP-01 §6): pushes to mode-A repositories MUST NOT be published to the gossip topic (GLIP-07), the events feed, or
sync/notifyfan-out (GLIP-06). Mode-B pushes are public events (the structure is public). - Feeds: visibility filtering MUST be fail-closed — an event whose repository's visibility cannot be determined is dropped, not shown. All feed surfaces (REST, GraphQL, anything added later) MUST share one gate so they cannot drift.
- Content-addressed gateway (GLIP-08): the same object can exist in a public and a non-public repository. Rule:
GET /ipfs/{cid}MUST serve an object iff it is reachable from at least one public repository on the node. Reachability from a non-public repository contributes nothing, in either direction. - Certificates (GLIP-04): certs for mode-A pushes exist for the owner's benefit and MUST be served only to authorized readers of that repository.
6. Federation and mirrors
- Mode A does not federate. Nodes MUST NOT announce, enumerate, or sync mode-A repositories. The single exception: the owner MAY grant a specific node a
git/fetchcapability (GLIP-10), making it a chosen replica — extending the trust-the-operator promise to an operator the owner picked. Such replicas inherit every obligation in this GLIP. - Mode B federates as ciphertext. Mirrors replicate structure via git transfer (§4.3) and envelopes via
encrypted-blobs/replicate. A mirror decides its mode by first consultingwithheld-paths: empty → plain mirror; non-empty → partial/promisor mirror plus envelope replication. This gives withheld content the network's durability without extending its readability: availability from strangers, readability from keys.
7. Security considerations
- Revocation is prospective only. Removing a recipient stops them reading future content keys; everything they could already fetch, they may have. Un-sharing is not un-publishing. This is equally true of centralized forges; implementations SHOULD surface it in UX rather than imply otherwise.
- Visibility downgrades (public → private) do not recall objects already replicated, cached in bundles, pinned via GLIP-08, or fetched by any client. Nodes MUST stop serving on downgrade, and MUST treat previously-emitted events for now-private repositories as unrecallable.
- Metadata of mode B is public by design: paths, file sizes (approximately, via envelope size), commit cadence, and author DIDs are all visible. Owners needing to hide that something exists need mode A, not mode B.
- Traffic analysis of envelope fetches reveals which recipients read what and when; mirrors reduce this only if readers spread requests.
- Existence-hiding uniformity: every route touching a repository — including this GLIP's visibility and encrypted-blob routes — MUST give the mode-A-consistent
404to unauthorized callers; one forgotten route defeats all the others.
About the series
GLIPs document what may be implemented by node and client software on the Twigpine network, so that anyone can implement the protocol in any language. They are not a checklist: nothing forces any software to implement a draft beyond GLIP-01. Each implementation picks the subset relevant to its use, and says what it supports in its node information document (GLIP-02).
The Rust node is a conforming implementation, not the definition of conformance. Where the node and a GLIP disagree, the GLIP and its test vectors decide.
Rules
- A GLIP is merged when implemented in two clients and one node (when applicable).
- GLIPs beyond GLIP-01 are optional. Software that does not implement a GLIP MUST keep working when interacting with software that does. Backwards-incompatible proposals are rejected.
- No more than one way of doing the same thing.
- Unknown JSON fields are ignored. Unknown format versions are rejected.
- Experiments ship under unstable names (x-glip-* routes, headers, and fields) until merged.
- Every GLIP has a Security Considerations section. No exceptions.
- Deprecation is a strikethrough in the index, with the reason. Files stay.
- All GLIPs are public domain.
- Other rules will be made up when necessary.
Roles
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL are to be read as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in capitals. Requirements are addressed to roles:
| Role | Meaning |
|---|---|
node | Server software hosting repositories and serving the HTTP API |
client | Software acting on behalf of an agent identity (CLI, SDK, bot) |
remote-helper | The git remote helper implementing the gitlawb:// scheme |
mirror | A node replicating repositories it does not own |
The drafts
| Draft | Title | Status |
|---|---|---|
| GLIP-01 | Core protocol | draft |
| GLIP-02 | Node information document | draft |
| GLIP-03 | Registration and onboarding | unwritten |
| GLIP-04 | Ref-update certificates | unwritten |
| GLIP-05 | The gitlawb:// remote scheme | unwritten |
| GLIP-06 | Federation over HTTP | unwritten |
| GLIP-07 | P2P profile (libp2p) | unwritten |
| GLIP-08 | Content addressing | unwritten |
| GLIP-09 | Visibility and withheld content | this page |
| GLIP-10 | Delegated capabilities (UCAN profile) | draft, ahead of the code |