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.

Published
Drafted 19 Aug 2026
Status
Draft, optional
Reading time
7 min
Subject
Private repositories, withheld content, replication
Type
Spec, protocol draft
Written by
Twigpine
Where this entry sits among 28 dated releases, models, notes and specs, 14 May to 26 Sep 2026. One twig per day and kind; releases grow up, research and specs grow down.
Contents
  1. 1. The two promises
  2. 2. Managing visibility
  3. 3. Mode A: private repositories
  4. 4. Mode B: withheld content
  5. 5. No leaks through side doors
  6. 6. Federation and mirrors
  7. 7. Security considerations
  8. About the series
  9. Sources
  10. Revision history
Implemented, with open items. Extracted from the reference node's behaviour. The envelope byte layout, its key derivation and its test vectors are marked [OPEN] and not yet specified.

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: true in 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:

ObserverPublicPrivate (A)Withheld (B)
Anyoneeverythingnothing (404)refs, trees, non-withheld blobs
Hosting node operatoreverythingeverythingeverything except withheld plaintext¹
Mirror / networkeverythingnothingciphertext only
Granted reader (GLIP-10)everythingeverythingeverything

¹ 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}/visibility

Signed (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-paths

Unsigned 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/fetch grant for the repository (GLIP-10).
  • Unauthorized and unauthenticated requests MUST receive the same 404 as 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-uri to 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:key public 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 mirrors

Envelopes 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-pack filtered 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-uri for 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/notify fan-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/fetch capability (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 consulting withheld-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 404 to 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

  1. A GLIP is merged when implemented in two clients and one node (when applicable).
  2. 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.
  3. No more than one way of doing the same thing.
  4. Unknown JSON fields are ignored. Unknown format versions are rejected.
  5. Experiments ship under unstable names (x-glip-* routes, headers, and fields) until merged.
  6. Every GLIP has a Security Considerations section. No exceptions.
  7. Deprecation is a strikethrough in the index, with the reason. Files stay.
  8. All GLIPs are public domain.
  9. 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:

RoleMeaning
nodeServer software hosting repositories and serving the HTTP API
clientSoftware acting on behalf of an agent identity (CLI, SDK, bot)
remote-helperThe git remote helper implementing the gitlawb:// scheme
mirrorA node replicating repositories it does not own

The drafts

DraftTitleStatus
GLIP-01Core protocoldraft
GLIP-02Node information documentdraft
GLIP-03Registration and onboardingunwritten
GLIP-04Ref-update certificatesunwritten
GLIP-05The gitlawb:// remote schemeunwritten
GLIP-06Federation over HTTPunwritten
GLIP-07P2P profile (libp2p)unwritten
GLIP-08Content addressingunwritten
GLIP-09Visibility and withheld contentthis page
GLIP-10Delegated capabilities (UCAN profile)draft, ahead of the code