handleAttachmentDownload streams one attachment's bytes. It is a RAW chi route (not Huma, not bearer): the capability TOKEN authorizes the download, so the URL can be handed to a sandboxed tool without leaking the agent's credential — the same model as the HITL magic-link. The token binds message+in
(w http.ResponseWriter, r *http.Request)
| 215 | // keyed by agent id), so a valid token can only stream the exact attachment it |
| 216 | // was minted for. |
| 217 | func (s *Server) handleAttachmentDownload(w http.ResponseWriter, r *http.Request) { |
| 218 | // Per-IP rate limit FIRST. This raw capability-token route sits OUTSIDE the |
| 219 | // Huma rate-limit middleware, and each accepted hit runs GetAgent + |
| 220 | // GetMessage + a full MIME re-parse — so throttle before any of that work to |
| 221 | // bound token-replay and index/message probing. Keyed by client IP (no bearer |
| 222 | // here); shares the per-IP convention of the registration/feedback limiters. |
| 223 | if s.deps.DownloadLimit != nil { |
| 224 | ok, retryAfter, limit, remaining, reset := s.deps.DownloadLimit(clientIP(r)) |
| 225 | w.Header().Set("RateLimit-Limit", strconv.Itoa(limit)) |
| 226 | w.Header().Set("RateLimit-Remaining", strconv.Itoa(remaining)) |
| 227 | w.Header().Set("RateLimit-Reset", strconv.Itoa(reset)) |
| 228 | if !ok { |
| 229 | secs := int(retryAfter.Round(time.Second).Seconds()) |
| 230 | if secs < 1 { |
| 231 | secs = 1 |
| 232 | } |
| 233 | w.Header().Set("Retry-After", strconv.Itoa(secs)) |
| 234 | writeRawError(w, r, http.StatusTooManyRequests, "rate_limited", "rate limit exceeded", |
| 235 | map[string]any{"retry_after_seconds": secs}) |
| 236 | return |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | email := identity.NormalizeEmail(chi.URLParam(r, "email")) |
| 241 | id := chi.URLParam(r, "id") |
| 242 | index, err := strconv.Atoi(chi.URLParam(r, "index")) |
| 243 | if err != nil || index < 0 { |
| 244 | http.Error(w, "invalid attachment index", http.StatusBadRequest) |
| 245 | return |
| 246 | } |
| 247 | token := r.URL.Query().Get("token") |
| 248 | if token == "" { |
| 249 | http.Error(w, "token query parameter required", http.StatusUnauthorized) |
| 250 | return |
| 251 | } |
| 252 | if s.deps.AttachmentStore == nil || s.deps.GetAgent == nil || s.deps.GetMessage == nil { |
| 253 | http.Error(w, "attachment download unavailable", http.StatusInternalServerError) |
| 254 | return |
| 255 | } |
| 256 | // Capability check: the token must authorize exactly this message+index. |
| 257 | if !s.deps.AttachmentStore.VerifyDownload(token, id, index) { |
| 258 | http.Error(w, "invalid or expired download token", http.StatusForbidden) |
| 259 | return |
| 260 | } |
| 261 | // Bind the message to the path agent (GetMessage is keyed by agent id), so a |
| 262 | // token can't be replayed against a path naming a different agent. |
| 263 | ag, err := s.deps.GetAgent(r.Context(), email) |
| 264 | if err != nil || ag == nil { |
| 265 | http.Error(w, "agent not found", http.StatusNotFound) |
| 266 | return |
| 267 | } |
| 268 | msg, err := s.deps.GetMessage(r.Context(), id, ag.ID) |
| 269 | if err != nil || msg == nil { |
| 270 | http.Error(w, "message not found", http.StatusNotFound) |
| 271 | return |
| 272 | } |
| 273 | att, ok := mailparse.AttachmentAt(msg.RawMessage, index) |
| 274 | if !ok { |
nothing calls this directly
no test coverage detected