Skip to content

Instantly share code, notes, and snippets.

@ngerakines
Last active January 28, 2026 22:55
Show Gist options
  • Select an option

  • Save ngerakines/f50c17a2c2d50d67c0276aa54147ab22 to your computer and use it in GitHub Desktop.

Select an option

Save ngerakines/f50c17a2c2d50d67c0276aa54147ab22 to your computer and use it in GitHub Desktop.

Proposal: Access-Controlled Blobs via Signed URLs

Summary

This proposal introduces a mechanism for gating access to blob content within ATProtocol. While blobs are traditionally public byte arrays accessible by anyone through a content identifier (CID), this extension enables access control by requiring signed URLs for protected blobs.

Motivation

As ATProtocol evolves to support richer user experiences, there is a growing need to handle sensitive or access-restricted media (e.g., paid content, private media, or community-gated assets). This mechanism provides a flexible, cryptographically-verifiable way to authorize blob access without introducing heavyweight permissions infrastructure.

Design Overview

  1. Blob Request (Unauthorized)

    A client attempts to fetch a protected blob directly using an HTTP GET to the blob’s CID endpoint.

    The server responds with HTTP 401 Unauthorized, indicating the blob is access-restricted.

  2. Signed Access URL Generation

    The client sends a request to the XRPC method com.atproto.repo.signBlob, including:

    • The CID of the requested blob
    • A request signed by the client’s DID key

    The server verifies the signature and returns a signed URL granting time-limited access. This URL includes:

    • signature - a server-generated HMAC of the URL (without the signature query string parameter)
    • nonce - to prevent replay attacks
    • notAfter (optional) - an expiry timestamp
    • did (optional) - the identity that was granted the signed URL
  3. Blob Request (Authorized)

    The client uses the signed URL to make a new HTTP GET request.

    If the signature and query parameters validate, the server responds with HTTP 200 OK and the blob content.

Benefits

  • Compatible with current blob retrieval process
  • Leverages existing cryptographic identity infrastructure (DIDs, keypairs)
  • No need for persistent ACLs or session state
  • Enables short-lived, revocable blob access
  • Maintains compatibility with decentralized, content-addressed storage systems

Considerations

  • The implementation must ensure nonces are single-use or time-bound to prevent replay
  • Optionally, servers may impose rate limits or usage caps based on DID or IP

Next Steps

  • Formalize com.atproto.repo.signBlob input/output schema
  • Define URL signature format and validation algorithm
  • Implement support in PDSs and compatible blob gateways

Lexicon

com.atproto.repo.signBlob

{
  "lexicon": 1,
  "id": "com.atproto.repo.signBlob",
  "defs": {
    "main": {
      "type": "procedure",
      "description": "Generates a signed URL for accessing a gated blob",
      "parameters": {
        "type": "params",
        "properties": {
          "blob": {
            "type": "string",
            "description": "The CID (Content Identifier) of the blob to access"
          }
        },
        "required": ["blob"]
      },
      "input": {
        "encoding": "application/json",
        "schema": {
          "type": "object",
          "properties": {
            "blob": {
              "type": "string",
              "description": "The blob that is being accessed."
            }
          },
          "required": ["blob"]
        }
      },
      "output": {
        "encoding": "application/json",
        "schema": {
          "type": "object",
          "properties": {
            "url": {
              "type": "string",
              "description": "Signed URL including query parameters like signature, notAfter, did, and nonce"
            }
          },
          "required": ["url"]
        }
      },
      "errors": [
        {
          "name": "InvalidSignature",
          "description": "The provided signature is invalid or cannot be verified"
        },
        {
          "name": "BlobNotFound",
          "description": "The requested blob does not exist"
        },
        {
          "name": "UnauthorizedBlob",
          "description": "The blob is not public and access is not permitted for the requester"
        }
      ]
    }
  }
}

Transparent Proxy for Access-Controlled Blobs

To enable access control without requiring deep changes to existing PDS implementations, a transparent HTTP proxy can be introduced in front of the PDS. This proxy intercepts all blob retrieval requests and enforces signed URL verification for gated content.

Role of the Proxy

The proxy acts as a gatekeeper, providing the following benefits:

  • Keeps blob access enforcement decoupled from the PDS internals
  • Enables enforcement of signed URL logic without modifying blob storage or PDS APIs
  • Maintains compatibility with existing blob fetching logic on the client side

How It Works

  1. Intercepting Blob Requests

    • The proxy listens for incoming GET /xrpc/com.atproto.sync.getBlob or equivalent blob-serving endpoints.
    • If the blob is not gated, the proxy passes the request through transparently to the PDS.
    • If the blob is gated, the proxy checks for a valid signed URL.
  2. Validating Signed URLs

    The proxy parses the following query parameters:

    • signature: A server-signed token (e.g., HMAC or JWT) encoding access details
    • notAfter: An expiration timestamp
    • did: The identity that requested the blob
    • nonce: Used to prevent replay attacks

    The proxy verifies:

    • That the signature is valid for the expected blob, DID, and nonce
    • That the notAfter timestamp has not expired
    • (Optionally) That the nonce hasn’t already been used
  3. Serving or Rejecting the Request

    • If all checks pass, the proxy fetches the blob from the PDS and returns it to the client.
    • If validation fails, the proxy responds with an appropriate HTTP error (e.g., 401 Unauthorized, 403 Forbidden, or 410 Gone for expired URLs).

Advantages

  • Deployable without modifying upstream PDS software
  • Centralizes access control logic
  • Compatible with CDN caching (with signed URL support)
  • Easier to audit and update policies over time

Implementation Tips

  • The proxy can be implemented using any modern HTTP server or middleware (e.g., NGINX with Lua, Envoy, Node.js/Express, or Rust with Hyper or Axum)
  • Signed URLs should be reasonably short-lived (e.g., 60m-120m) and bound to specific blob CIDs
  • Consider logging access attempts for auditing or rate-limiting

Management

An additional Lexicon like com.atproto.repo.manageBlobAccess could be used to manage access to blobs. It includes the ability to:

  • Grant access to a DID (optionally with an expiration time)
  • List currently authorized DIDs
  • Revoke access for a specific DID

com.atproto.repo.grantBlobAccess

Input: DID to grant access to with optional access duration.

com.atproto.repo.revokeBlobAccess

Input: DID to revoke access of

com.atproto.repo.listBlobAccess

Input: Optional list of DIDs to include in paginated list of DIDs that have access

Output: Cursor paginated list of DIDs that have access

@ngerakines

Copy link
Copy Markdown
Author

Additional thoughts and considerations.

  • An AppView making a HTTP HEAD request to test access control and validity of blobs could be a cheap and safe pattern to encourage
  • Adding some sort of hint to records and types that include blobs could be helpful to media intensive app-views

@cboscolo

Copy link
Copy Markdown

This is intriguing.

Can you say a bit more about:

A request signed by the client’s DID key

Is there a standard way to sign things when using did:plc and and key managed by BSky PDS?

@mikestaub

Copy link
Copy Markdown

I think this is doable, but it feels like a stop-gap solution. My intuition tells me that a more powerful solution would be to combine cedar policies with UCAN tokens, but that would require more research.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment