GRASP-08: Private repositories
Purpose: to provide private repositories through an authenticated, read-restricted service with a service-wide whitelist of npubs, that can later become public.
This is an optional extension to GRASP-01.
The service-wide whitelist contains configured pubkeys. To enable repository synchronization, it also contains every owner npub from the NIP-11 documents of relays listed by accepted repository announcements in the recursive maintainer set.
An operator would spin up many private grasp services, one per group of private collaborators.
Private Git Relay List
Clients can discover a user's private repositories using a NIP-51 standard list with kind 10318. Each item is a g tag containing the WebSocket URL of a relay that serves private NIP-34 events to the user. The relay MAY be a GRASP service or a non-GRASP relay.
All g tags in this list MUST be private items: they MUST be encoded as a JSON array, encrypted to the event author using NIP-44 as specified by NIP-51, and stored in the event's content. The event's public tags array MUST NOT contain private Git relay URLs.
yaml
{
"kind": 10318,
"content": nip44_encrypt([
["g", "wss://<grasp-host>"],
["g", "wss://<private-relay-host>"]
]),
"tags": []
}Supporting clients SHOULD fetch and decrypt this list from the user's normal relays, then query each listed relay for repository announcements available to the user.
Repository Privacy
A repository is private if any kind 30617 announcement in its recursive maintainer set includes the tag ["private", "true"].
For a private repository, clients MUST publish announcements and related events only to the repository relays listed in the announcements' relays tags. Clients MUST NOT publish these events to any user's NIP-65 inbox, outbox or fallback relays. The encrypted kind 10318 list is not a repository-related event and MAY be published to the user's normal relays.
Nostr Relay
MUST require successful NIP-42 authentication with a whitelisted pubkey before accepting or serving any events. MUST use auth-required: when authentication is missing and restricted: when the authenticated pubkey is not whitelisted.
Git Smart HTTP Service
Every GET or POST request MUST include a repository-scoped NIP-98 credential in the HTTP Authorization header:
text
Authorization: Nostr <base64-event>The credential MUST be a valid kind 27235 event whose pubkey is in the service-wide whitelist. It uses NIP-98 encoding with the following Git-specific validation rules:
- the
utag MUST equal the canonical absolute repository URL without a query or trailing slash; - the
methodtag MUST beGET; - the same event MUST be accepted for GET and POST requests to that repository's root,
info/refs,git-upload-packandgit-receive-packendpoints; - a
payloadtag MUST NOT be required or validated; and - repeated use of the event ID MUST NOT be rejected during its validity window; and
created_atMUST be within 60 seconds of the server's current time.
These rules intentionally replace NIP-98's exact request URL and method checks so Git can reuse one credential throughout a Smart HTTP operation.
A valid credential proves identity but does not grant repository write permission. Pushes MUST still satisfy all GRASP-01 authorization requirements.
Every GET or POST request for a private repository without valid authorization MUST receive a 401 Unauthorized response with an empty body and a WWW-Authenticate challenge using the Nostr scheme and a method="GET" parameter. The challenge MAY include an implementation-defined realm parameter. MUST NOT respond with 403 Forbidden or 404 Not Found in these cases.
After validating the credential and whitelist membership, MUST use normal GRASP-01 responses.
MUST include Authorization in Access-Control-Allow-Headers when serving CORS responses.
Whitelist membership controls access only; authorized users and the server can copy and republish all events and git data they receive. Clients SHOULD NOT republish this data to relays other than the specified repository relays.