fix(security): C2 from #169 — reject spoofed source_id in activity.Report
Cherry-picks the one genuinely new fix from #169 after confirming the rest of that PR is already covered on main (C1/C3/C5 by wsAuth group, C6 by #94+#119 SSRF blocklist, C4 ownership by existing WHERE filter). Pre-existing middleware (WorkspaceAuth on /workspaces/:id/* sub-routes) proves the caller owns the :id path param. But the body field source_id was never validated — a workspace authenticated for its own /activity endpoint could still attribute logs to a different workspace by setting source_id=<foreign UUID>. Rejected with 403 now. No schema change, no new middleware. 4-line handler delta. Closes the only real gap in #169; #169 itself will be closed as superseded. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a0552214a
commit
25bbfd3bfc
@ -329,7 +329,18 @@ func (h *ActivityHandler) Report(c *gin.Context) {
|
||||
if reqBody == nil {
|
||||
reqBody = body.Metadata
|
||||
}
|
||||
// C2 (from #169) — source_id spoof defense. WorkspaceAuth middleware
|
||||
// already proves the caller owns :id, but that check doesn't cover the
|
||||
// body field. Without this guard, workspace A authenticated for its own
|
||||
// /activity endpoint could still set source_id=<workspace B's UUID> in
|
||||
// the payload and attribute the log to B. Reject any body where
|
||||
// source_id is non-empty AND differs from the authenticated workspace.
|
||||
// Empty source_id falls through to the default-to-self branch below.
|
||||
sourceID := body.SourceID
|
||||
if sourceID != "" && sourceID != workspaceID {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "source_id must match authenticated workspace"})
|
||||
return
|
||||
}
|
||||
if sourceID == "" {
|
||||
sourceID = workspaceID
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user