feat(pat): enforce scopes and resource restrictions, deny by default

A central rule table maps each route and method to the scope a token needs;
a route that is not listed cannot be called with a token, and a test fails
when a registered route is left unclassified. Token management, admin, team
management, sign-in, device pairing and OAuth handshakes are never token
reachable, and a token never carries the admin role.

A token restricted to specific agents, sources, prompts, tools or workflows
is held to its allowlist: ids are checked wherever a route carries them,
listings are filtered, creation is refused, and routes whose rows cannot be
tied to the allowlist are closed. Agent import checks the resolved target.
This commit is contained in:
arc53-machine committed 2026-09-19 23:39:58 +01:00
1 parent 3a74aa23f0
commit 82ec6cba0e
10 files changed
+1114 -4

No files matched your search

+18 -1
View File
@@ -22,8 +22,11 @@ from docsgpt.api.devices import devices_bp # noqa: E402
from docsgpt.api.internal.routes import internal # noqa: E402
from docsgpt.api.oidc import oidc_bp # noqa: E402
from docsgpt.api.oidc.denylist import is_denied as oidc_session_denied # noqa: E402
from docsgpt.api.pat.routes import pat_ns # noqa: E402
from docsgpt.api.pat.rules import authorize as authorize_pat # noqa: E402
from docsgpt.api.pat.tokens import is_pat # noqa: E402
from docsgpt.api.scim import scim_bp # noqa: E402
from docsgpt.api.user.authz import resolve_roles # noqa: E402
from docsgpt.api.user.authz import ROLE_USER, resolve_roles # noqa: E402
from docsgpt.api.user.routes import user # noqa: E402
from docsgpt.api.connector.routes import connector # noqa: E402
from docsgpt.api.v1 import v1_bp # noqa: E402
@@ -109,6 +112,8 @@ app.register_blueprint(v1_bp)
# first app and raise "add_url_rule can no longer be called".
if admin_ns not in api.namespaces:
api.add_namespace(admin_ns)
if pat_ns not in api.namespaces:
api.add_namespace(pat_ns)
app.config.update(
UPLOAD_FOLDER="inputs",
CELERY_BROKER_URL=settings.CELERY_BROKER_URL,
@@ -317,6 +322,18 @@ def authenticate_request():
request.decoded_token = None
elif "error" in decoded_token:
return jsonify(decoded_token), 401
elif is_pat(decoded_token):
# Scopes and resource restrictions are enforced here, centrally and
# deny by default (docsgpt/api/pat/rules.py). A token never carries
# admin, whatever its owner holds, and the session denylist does not
# apply: the token lookup already excludes revoked tokens and
# deactivated users.
denied = authorize_pat(request, decoded_token)
if denied is not None:
body, status = denied
return jsonify(body), status
decoded_token["roles"] = [ROLE_USER]
request.decoded_token = decoded_token
elif settings.AUTH_TYPE == "oidc" and oidc_session_denied(decoded_token):
# Back-channel logout / SCIM deactivation revoked this session.
return (