A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
73
fork

Configure Feed

Select the types of activity you want to include in your feed.

Hold Crew Access Control#

Overview#

ATCR uses crew-based access control for hold (storage) services. Crew records are stored in the hold's embedded PDS (not the owner's or user's PDS), making the hold a self-contained ATProto actor with its own access control.

Current Implementation#

Records in Hold's PDS#

Captain record - Hold ownership (single record at io.atcr.hold.captain/self):

{
  "$type": "io.atcr.hold.captain",
  "owner": "did:plc:alice123",
  "public": false,
  "deployedAt": "2025-10-14T...",
  "region": "iad",
  "provider": "fly.io"
}

Crew records - Access control (one per member at io.atcr.hold.crew/{rkey}):

{
  "$type": "io.atcr.hold.crew",
  "member": "did:plc:bob456",
  "role": "admin",
  "permissions": ["blob:read", "blob:write"],
  "addedAt": "2025-10-14T..."
}

Authorization Logic#

Write authorization follows this priority:

isAuthorizedWrite(userDID):
  1. If userDID == captain.owner → ALLOW
  2. If crew record exists for userDID → ALLOW
  3. Default → DENY

Read authorization depends on HOLD_PUBLIC setting:

  • Public hold (HOLD_PUBLIC=true): Anonymous + all authenticated users can read
  • Private hold (HOLD_PUBLIC=false): Requires crew membership for reads

Configuration#

# Access control environment variables
HOLD_PUBLIC=false  # Require authentication for reads
HOLD_ALLOW_ALL_CREW=false  # Only explicit crew members can write

Crew Management#

Crew records are managed by the hold captain (owner) using standard ATProto operations on the hold's embedded PDS:

Add crew member:

# Via hold's PDS (requires captain's OAuth)
atproto put-record \
  --pds https://hold.example.com \
  --collection io.atcr.hold.crew \
  --rkey "{memberDID}" \
  --value '{
    "$type": "io.atcr.hold.crew",
    "member": "did:plc:bob456",
    "role": "admin",
    "permissions": ["blob:read", "blob:write"],
    "addedAt": "2025-10-14T12:00:00Z"
  }'

Remove crew member:

atproto delete-record \
  --pds https://hold.example.com \
  --collection io.atcr.hold.crew \
  --rkey "{memberDID}"

List crew members:

# Via XRPC
GET https://hold.example.com/xrpc/com.atproto.repo.listRecords?repo={holdDID}&collection=io.atcr.hold.crew

Authentication Flow#

1. User pushes image to atcr.io/alice/myapp

2. AppView gets service token from alice's PDS:
   GET /xrpc/com.atproto.server.getServiceAuth?aud={holdDID}
   Response: { "token": "..." }

3. AppView calls hold with service token:
   POST /xrpc/io.atcr.hold.initiateUpload
   Authorization: Bearer {serviceToken}

4. Hold validates service token:
   - Checks token is from alice's PDS
   - Extracts alice's DID from token

5. Hold checks crew membership:
   - Queries its own PDS: com.atproto.repo.getRecord
   - Collection: io.atcr.hold.crew
   - Record key: alice's DID

6. If crew record found → allow upload
   Else → deny with 403 Forbidden

Trust model: "Trust but verify"

  • User OAuth'd to AppView (proves identity)
  • Service token from user's PDS (proves AppView is acting on behalf of user)
  • Crew record in hold's PDS (proves user has access to this hold)

Use Cases#

1. Personal Hold (Private)#

# Owner only
HOLD_PUBLIC=false
HOLD_ALLOW_ALL_CREW=false
# No additional crew records needed - captain has implicit access

2. Team Hold (Shared)#

# Multiple team members
HOLD_PUBLIC=false
HOLD_ALLOW_ALL_CREW=false

# Captain adds crew members:
# - did:plc:alice (admin)
# - did:plc:bob (member)
# - did:plc:charlie (member)

3. Public Hold (Community)#

# Allow any authenticated user (TODO: Implement HOLD_ALLOW_ALL_CREW)
HOLD_PUBLIC=true
HOLD_ALLOW_ALL_CREW=true

Planned Features#

Pattern-Based Access Control#

Status: Planned but not yet implemented.

Concept: Allow crew records with pattern matching instead of explicit DIDs:

{
  "$type": "io.atcr.hold.crew",
  "memberPattern": "*.example.com",
  "role": "write"
}

Use cases:

  • "*" - Allow all authenticated users
  • "*.company.com" - Allow all users from company domain
  • "*.community.social" - Allow all community members

Implementation needed:

  • Add memberPattern field to crew record schema (make member optional)
  • Add handle resolution (DID → handle lookup)
  • Add pattern matching logic
  • Update authorization to check patterns

Barred List (Access Revocation)#

Status: Planned but not yet implemented.

Concept: Explicit deny list that overrides crew membership:

{
  "$type": "io.atcr.hold.crew.barred",
  "member": "did:plc:former-employee",
  "reason": "No longer with company",
  "barredAt": "2025-10-13T12:00:00Z"
}

Priority: Barred list checked before crew list.

HOLD_ALLOW_ALL_CREW#

Status: Environment variable exists but full implementation pending.

Concept: Automatically create/manage wildcard crew record via env var:

HOLD_ALLOW_ALL_CREW=true  # Creates crew record with memberPattern: "*"

Implementation needed:

  • Auto-create wildcard crew record on startup if env=true
  • Auto-delete wildcard crew record if env changes to false
  • Use well-known rkey "allow-all" for managed record

Architecture Notes#

Why Hold's Embedded PDS?#

Key insight: Crew records are shared data about the hold, not user-specific data.

Benefits:

  • Self-contained: Hold is independent ATProto actor
  • Portable: Hold can move without coordinating with user PDSs
  • Discoverable: Query hold's PDS to see who has access
  • Standard: Uses normal ATProto sync endpoints (subscribeRepos, getRecord, listRecords)

Comparison:

  • User's PDS: Stores user-specific data (manifests, sailor profile)
  • Hold's PDS: Stores hold-specific data (captain, crew, configuration)
  • Clear separation of concerns

Security Considerations#

  1. Public Records: Crew records are public (anyone can see who has access to a hold)
  2. Service Tokens: Hold trusts user's PDS to issue valid service tokens
  3. DID-Based: Crew membership is DID-based (permanent), not handle-based
  4. Captain Control: Only captain can modify crew records (via OAuth to hold's PDS)

Future Improvements#

  1. Crew management UI - Web interface for adding/removing crew members
  2. Pattern-based matching - Implement memberPattern field
  3. Barred list - Implement access revocation
  4. Role-based permissions - Fine-grained permissions beyond read/write
  5. Temporary access - Time-limited crew membership (expiresAt field)
  6. Audit logging - Track access grants/denials

References#