A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
atcr.io
docker
container
atproto
go
1# Hold Crew Access Control
2
3## Overview
4
5ATCR 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.
6
7## Current Implementation
8
9### Records in Hold's PDS
10
11**Captain record** - Hold ownership (single record at `io.atcr.hold.captain/self`):
12```json
13{
14 "$type": "io.atcr.hold.captain",
15 "owner": "did:plc:alice123",
16 "public": false,
17 "deployedAt": "2025-10-14T...",
18 "region": "iad",
19 "provider": "fly.io"
20}
21```
22
23**Crew records** - Access control (one per member at `io.atcr.hold.crew/{rkey}`):
24```json
25{
26 "$type": "io.atcr.hold.crew",
27 "member": "did:plc:bob456",
28 "role": "admin",
29 "permissions": ["blob:read", "blob:write"],
30 "addedAt": "2025-10-14T..."
31}
32```
33
34### Authorization Logic
35
36Write authorization follows this priority:
37
38```
39isAuthorizedWrite(userDID):
40 1. If userDID == captain.owner → ALLOW
41 2. If crew record exists for userDID → ALLOW
42 3. Default → DENY
43```
44
45Read authorization depends on `HOLD_PUBLIC` setting:
46- **Public hold** (`HOLD_PUBLIC=true`): Anonymous + all authenticated users can read
47- **Private hold** (`HOLD_PUBLIC=false`): Requires crew membership for reads
48
49### Configuration
50
51```bash
52# Access control environment variables
53HOLD_PUBLIC=false # Require authentication for reads
54HOLD_ALLOW_ALL_CREW=false # Only explicit crew members can write
55```
56
57### Crew Management
58
59Crew records are managed by the hold captain (owner) using standard ATProto operations on the hold's embedded PDS:
60
61**Add crew member:**
62```bash
63# Via hold's PDS (requires captain's OAuth)
64atproto put-record \
65 --pds https://hold.example.com \
66 --collection io.atcr.hold.crew \
67 --rkey "{memberDID}" \
68 --value '{
69 "$type": "io.atcr.hold.crew",
70 "member": "did:plc:bob456",
71 "role": "admin",
72 "permissions": ["blob:read", "blob:write"],
73 "addedAt": "2025-10-14T12:00:00Z"
74 }'
75```
76
77**Remove crew member:**
78```bash
79atproto delete-record \
80 --pds https://hold.example.com \
81 --collection io.atcr.hold.crew \
82 --rkey "{memberDID}"
83```
84
85**List crew members:**
86```bash
87# Via XRPC
88GET https://hold.example.com/xrpc/com.atproto.repo.listRecords?repo={holdDID}&collection=io.atcr.hold.crew
89```
90
91## Authentication Flow
92
93```
941. User pushes image to atcr.io/alice/myapp
95
962. AppView gets service token from alice's PDS:
97 GET /xrpc/com.atproto.server.getServiceAuth?aud={holdDID}
98 Response: { "token": "..." }
99
1003. AppView calls hold with service token:
101 POST /xrpc/io.atcr.hold.initiateUpload
102 Authorization: Bearer {serviceToken}
103
1044. Hold validates service token:
105 - Checks token is from alice's PDS
106 - Extracts alice's DID from token
107
1085. Hold checks crew membership:
109 - Queries its own PDS: com.atproto.repo.getRecord
110 - Collection: io.atcr.hold.crew
111 - Record key: alice's DID
112
1136. If crew record found → allow upload
114 Else → deny with 403 Forbidden
115```
116
117**Trust model:** "Trust but verify"
118- User OAuth'd to AppView (proves identity)
119- Service token from user's PDS (proves AppView is acting on behalf of user)
120- Crew record in hold's PDS (proves user has access to this hold)
121
122## Use Cases
123
124### 1. Personal Hold (Private)
125
126```bash
127# Owner only
128HOLD_PUBLIC=false
129HOLD_ALLOW_ALL_CREW=false
130# No additional crew records needed - captain has implicit access
131```
132
133### 2. Team Hold (Shared)
134
135```bash
136# Multiple team members
137HOLD_PUBLIC=false
138HOLD_ALLOW_ALL_CREW=false
139
140# Captain adds crew members:
141# - did:plc:alice (admin)
142# - did:plc:bob (member)
143# - did:plc:charlie (member)
144```
145
146### 3. Public Hold (Community)
147
148```bash
149# Allow any authenticated user (TODO: Implement HOLD_ALLOW_ALL_CREW)
150HOLD_PUBLIC=true
151HOLD_ALLOW_ALL_CREW=true
152```
153
154## Planned Features
155
156### Pattern-Based Access Control
157
158**Status:** Planned but not yet implemented.
159
160**Concept:** Allow crew records with pattern matching instead of explicit DIDs:
161
162```json
163{
164 "$type": "io.atcr.hold.crew",
165 "memberPattern": "*.example.com",
166 "role": "write"
167}
168```
169
170**Use cases:**
171- `"*"` - Allow all authenticated users
172- `"*.company.com"` - Allow all users from company domain
173- `"*.community.social"` - Allow all community members
174
175**Implementation needed:**
176- Add `memberPattern` field to crew record schema (make `member` optional)
177- Add handle resolution (DID → handle lookup)
178- Add pattern matching logic
179- Update authorization to check patterns
180
181### Barred List (Access Revocation)
182
183**Status:** Planned but not yet implemented.
184
185**Concept:** Explicit deny list that overrides crew membership:
186
187```json
188{
189 "$type": "io.atcr.hold.crew.barred",
190 "member": "did:plc:former-employee",
191 "reason": "No longer with company",
192 "barredAt": "2025-10-13T12:00:00Z"
193}
194```
195
196**Priority:** Barred list checked before crew list.
197
198### HOLD_ALLOW_ALL_CREW
199
200**Status:** Environment variable exists but full implementation pending.
201
202**Concept:** Automatically create/manage wildcard crew record via env var:
203
204```bash
205HOLD_ALLOW_ALL_CREW=true # Creates crew record with memberPattern: "*"
206```
207
208**Implementation needed:**
209- Auto-create wildcard crew record on startup if env=true
210- Auto-delete wildcard crew record if env changes to false
211- Use well-known rkey "allow-all" for managed record
212
213## Architecture Notes
214
215### Why Hold's Embedded PDS?
216
217**Key insight:** Crew records are **shared data** about the hold, not user-specific data.
218
219**Benefits:**
220- **Self-contained**: Hold is independent ATProto actor
221- **Portable**: Hold can move without coordinating with user PDSs
222- **Discoverable**: Query hold's PDS to see who has access
223- **Standard**: Uses normal ATProto sync endpoints (subscribeRepos, getRecord, listRecords)
224
225**Comparison:**
226- **User's PDS**: Stores user-specific data (manifests, sailor profile)
227- **Hold's PDS**: Stores hold-specific data (captain, crew, configuration)
228- Clear separation of concerns
229
230### Security Considerations
231
2321. **Public Records**: Crew records are public (anyone can see who has access to a hold)
2332. **Service Tokens**: Hold trusts user's PDS to issue valid service tokens
2343. **DID-Based**: Crew membership is DID-based (permanent), not handle-based
2354. **Captain Control**: Only captain can modify crew records (via OAuth to hold's PDS)
236
237## Future Improvements
238
2391. **Crew management UI** - Web interface for adding/removing crew members
2402. **Pattern-based matching** - Implement `memberPattern` field
2413. **Barred list** - Implement access revocation
2424. **Role-based permissions** - Fine-grained permissions beyond read/write
2435. **Temporary access** - Time-limited crew membership (`expiresAt` field)
2446. **Audit logging** - Track access grants/denials
245
246## References
247
248- [EMBEDDED_PDS.md](./EMBEDDED_PDS.md) - Embedded PDS architecture details
249- [BYOS.md](./BYOS.md) - BYOS deployment and usage
250- [ATProto Lexicon Spec](https://atproto.com/specs/lexicon)