fix: audit refused and failed mcp write calls, scope key assignment to token
Every early return from a write-tool handler skipped both the tool's own LogCall and transport.go's gated LogCall (which only fires for reads), so a blocked mutation attempt left no audit trail. registerSDKTool now routes every write-tool error through LogDenied (fan-out and tag-scope refusals, by gate name) or LogFailure (everything else), keeping the successful-write path logging its own resolved server count exactly as before. Also close a live scope gap surfaced while reviewing this: POST /api/keys/:id/assign called services.AssignKey with an unscoped GetServer lookup, so a tag-restricted token could assign a key to a server outside its restriction. The handler now resolves the target through GetServerScoped first, matching its sibling revoke route, and the route is recorded in serverScopedRoutes.
This commit is contained in:
@@ -623,6 +623,7 @@ func deleteKey(c *gin.Context) {
|
||||
// @Router /keys/{id}/assign [post]
|
||||
func assignKey(c *gin.Context) {
|
||||
keyID := c.Param("id")
|
||||
instanceID := auth.InstanceID(c)
|
||||
var body struct {
|
||||
ServerID string `json:"server_id" binding:"required"`
|
||||
}
|
||||
@@ -631,7 +632,12 @@ func assignKey(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
a, err := services.AssignKey(auth.InstanceID(c), keyID, body.ServerID)
|
||||
if _, err := services.GetServerScoped(instanceID, body.ServerID, auth.ServerScope(c)); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "server not found"})
|
||||
return
|
||||
}
|
||||
|
||||
a, err := services.AssignKey(instanceID, keyID, body.ServerID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@@ -50,6 +50,16 @@ var serverScopedRoutes = map[string]bool{
|
||||
// assignment on a server outside its scope.
|
||||
"DELETE /api/keys/:id/assign/:serverId": true,
|
||||
|
||||
// assignKey resolves body.ServerID through GetServerScoped before calling
|
||||
// services.AssignKey, which itself uses the unscoped GetServer — so a
|
||||
// restricted token can no longer assign a key to a server outside its
|
||||
// scope by naming it in the request body. This route's path carries
|
||||
// neither "server" nor "console", so it is not swept in by
|
||||
// serverTouchingRoutes and this entry is not boot-enforced; it is kept
|
||||
// here anyway as the record of a considered decision, matching its
|
||||
// sibling revoke route.
|
||||
"POST /api/keys/:id/assign": true,
|
||||
|
||||
// Creating a server has no server to filter yet.
|
||||
"POST /api/servers": false,
|
||||
// The agent's own enrolment routes authenticate as the agent, not as a
|
||||
|
||||
Reference in New Issue
Block a user