resolveOwnedAgent authenticates the caller, loads the agent by address, and verifies ownership — the shared front half of every per-agent operation. It mirrors the legacy resolveAgentForUser behavior: a missing or non-owned agent is reported as 403 (the legacy surface does not distinguish the two, a
(ctx context.Context, address string)
| 144 | // or non-owned agent is reported as 403 (the legacy surface does not |
| 145 | // distinguish the two, and preserving that is a Slice-1 non-goal to change). |
| 146 | func (s *Server) resolveOwnedAgent(ctx context.Context, address string) (*identity.AgentIdentity, error) { |
| 147 | p, err := s.requirePrincipal(ctx) |
| 148 | if err != nil { |
| 149 | return nil, err |
| 150 | } |
| 151 | if s.deps.GetAgent == nil { |
| 152 | return nil, NewError(http.StatusInternalServerError, "internal_error", "agent lookup unavailable") |
| 153 | } |
| 154 | ag, err := s.deps.GetAgent(ctx, identity.NormalizeEmail(address)) |
| 155 | if err != nil || ag == nil || ag.UserID != p.User.ID { |
| 156 | return nil, NewError(http.StatusForbidden, "forbidden", "agent not found") |
| 157 | } |
| 158 | // Hard scope ceiling (Slice 5a): an agent-scoped credential is pinned to a |
| 159 | // single agent. Even though the owner owns this agent, a credential bound |
| 160 | // to a DIFFERENT agent must not act here. Account-scoped credentials pass. |
| 161 | // This is the one choke point for every per-agent operation. |
| 162 | if p.Scope == identity.ScopeAgent && p.AgentID != ag.ID { |
| 163 | return nil, NewError(http.StatusForbidden, "forbidden", |
| 164 | "this agent-scoped credential is bound to a different agent") |
| 165 | } |
| 166 | return ag, nil |
| 167 | } |
no test coverage detected