···302302 // Support hyphen-encoded DIDs in image paths (e.g., did-plc-abc123/repo:tag)
303303 // OCI reference grammar doesn't allow colons in path components, so DIDs must
304304 // be encoded with hyphens instead: did:plc:abc123 → did-plc-abc123
305305- if decoded, ok := token.DecodeDIDFromHyphens(identityStr); ok {
305305+ if decoded, ok := auth.DecodeDIDFromHyphens(identityStr); ok {
306306 identityStr = decoded
307307 }
308308
+19
pkg/auth/scope.go
···5656 return access, nil
5757}
58585959+// DecodeDIDFromHyphens converts a hyphen-encoded DID back to colon-separated form.
6060+// "did-plc-abc123" → "did:plc:abc123", "did-web-example.com" → "did:web:example.com"
6161+// Returns the decoded DID and true if the input matched, or ("", false) otherwise.
6262+func DecodeDIDFromHyphens(s string) (string, bool) {
6363+ if strings.HasPrefix(s, "did-plc-") {
6464+ return "did:plc:" + strings.TrimPrefix(s, "did-plc-"), true
6565+ }
6666+ if strings.HasPrefix(s, "did-web-") {
6767+ return "did:web:" + strings.TrimPrefix(s, "did-web-"), true
6868+ }
6969+ return "", false
7070+}
7171+5972// ValidateAccess checks if the requested access is allowed for the user
6073// For ATCR, users can only push to repositories under their own handle/DID
6174func ValidateAccess(userDID, userHandle string, access []AccessEntry) error {
···7891 }
79928093 repoOwner := parts[0]
9494+9595+ // Decode hyphen-encoded DIDs (e.g., did-plc-abc123 → did:plc:abc123)
9696+ // Image paths use hyphens because colons are parsed as transport separators
9797+ if decoded, ok := DecodeDIDFromHyphens(repoOwner); ok {
9898+ repoOwner = decoded
9999+ }
8110082101 // Check if user is trying to access their own repository
83102 // They can use either their handle or DID