@@ -42,40 +42,54 @@ func NewHandler(store domain.UnitManagement, blobStore storage.UnitStore, rbacMa
4242}
4343
4444// resolveUnitIdentifier resolves a unit identifier (name or UUID) to its UUID
45+ // SECURITY: This function validates org ownership for all unit identifiers,
46+ // including direct UUIDs, to prevent IDOR attacks.
4547func (h * Handler ) resolveUnitIdentifier (ctx context.Context , identifier string ) (string , error ) {
4648 // URL-decode first (Echo params may be URL-encoded)
4749 decoded , err := domain .DecodeURLPath (identifier )
4850 if err != nil {
4951 return "" , err
5052 }
51-
53+
5254 normalized := domain .DecodeUnitID (decoded )
53-
54- // If already a UUID, return as-is
55+
56+ // Get org from context - required for security validation
57+ orgCtx , ok := domain .OrgFromContext (ctx )
58+ if ! ok {
59+ return "" , fmt .Errorf ("organization context required" )
60+ }
61+
62+ // If already a UUID, VALIDATE it belongs to the org (SECURITY FIX)
5563 if domain .IsUUID (normalized ) {
64+ // If resolver is not available, we cannot validate - fail secure
65+ if h .resolver == nil {
66+ return "" , fmt .Errorf ("cannot validate unit ownership: resolver not available" )
67+ }
68+
69+ belongs , err := h .resolver .UnitBelongsToOrg (ctx , normalized , orgCtx .OrgID )
70+ if err != nil {
71+ return "" , fmt .Errorf ("failed to verify unit ownership: %w" , err )
72+ }
73+ if ! belongs {
74+ // Don't reveal that the unit exists in another org
75+ return "" , fmt .Errorf ("unit not found" )
76+ }
5677 return normalized , nil
5778 }
58-
79+
5980 // If resolver is not available, return normalized name (will fail at repository layer)
6081 if h .resolver == nil {
6182 return normalized , nil
6283 }
63-
64- // Get org from context for resolution
65- orgCtx , ok := domain .OrgFromContext (ctx )
66- if ! ok {
67- // No org context, return normalized name
68- return normalized , nil
69- }
70-
84+
7185 // Resolve name to UUID using the identifier resolver
72- // Note: ResolveUnit signature is (ctx, identifier, orgID)
86+ // Note: ResolveUnit already validates org scope for name-based lookups
7387 uuid , err := h .resolver .ResolveUnit (ctx , normalized , orgCtx .OrgID )
7488 if err != nil {
7589 // If resolution fails, return error
7690 return "" , err
7791 }
78-
92+
7993 return uuid , nil
8094}
8195
0 commit comments