fix(pat): close restricted-token paths through workflows, chat, schedules and conversations

A resource restriction was checked on ids in the request, not on what the
addressed row pulls in or belongs to. Closed:

- Workflow writes for tokens restricted on sources, tools or prompts (a graph
  names those inside its nodes), and attaching a workflow to an agent unless
  the token is restricted on workflows too.
- Chat for tools-restricted tokens (chat executes tools; rejected at token
  creation as well), and agent-less chat for tokens restricted on prompts or
  workflows.
- conversation_id on chat: it must belong to the agent being run, or to no
  agent for agent-less chat. Otherwise the server continued, appended to, or
  resumed pending tool calls of another agent's conversation.
- Schedules for tokens restricted on anything but agents; schedule-id routes
  for every restricted token.
- Conversations and analytics for every restricted token, not only
  agent-restricted ones.

Also: create, first publish and adopt return the agent API key masked to a
token without agents:keys; token ids must be canonical UUIDs (urn:uuid: gave
a 500); an expired token is reported as expired; token creation takes a
per-user advisory lock so the cap cannot be raced; admin revoke-sessions
writes a pat_revoked event per token. The UI drops a row whose revoke returns
404 and does not offer a tools restriction next to chat:run.
This commit is contained in:
arc53-machine committed 2026-09-20 19:46:43 +01:00
1 parent b1ff8f516e
commit 94b5924e36
15 files changed
+393 -64

No files matched your search

+12 -4
View File
@@ -9,7 +9,7 @@ from flask_restx import fields, Namespace, Resource
from pydantic import ValidationError as PydanticValidationError
from docsgpt.api import api
from docsgpt.api.pat.rules import filter_listing
from docsgpt.api.pat.rules import filter_listing, mask_agent_key, may_see_agent_keys
from docsgpt.guardrails.config import AgentConfig
from docsgpt.api.user.base import (
copy_agent_image_for_user,
@@ -763,7 +763,9 @@ class CreateAgent(Resource):
except Exception as err:
current_app.logger.error(f"Error creating agent: {err}", exc_info=True)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"id": new_id, "key": key}), 201)
# A token without agents:keys never receives the plaintext agent key.
visible_key = key if may_see_agent_keys(request) else mask_agent_key(key)
return make_response(jsonify({"id": new_id, "key": visible_key}), 201)
@agents_ns.route("/update_agent/<string:agent_id>")
@@ -1307,7 +1309,11 @@ class UpdateAgent(Resource):
"message": "Agent updated successfully",
}
if newly_generated_key:
response_data["key"] = newly_generated_key
response_data["key"] = (
newly_generated_key
if may_see_agent_keys(request)
else mask_agent_key(newly_generated_key)
)
return make_response(jsonify(response_data), 200)
@@ -1811,7 +1817,9 @@ class AdoptAgent(Resource):
)
response_agent = _format_agent_output(new_agent, include_key_masked=False)
response_agent["key"] = new_key
response_agent["key"] = (
new_key if may_see_agent_keys(request) else mask_agent_key(new_key)
)
return make_response(
jsonify({"success": True, "agent": response_agent}), 200
)