fix: redact identity secrets through a public hook and mask issued agm_ tokens
ci / gitleaks (push) Successful in 8s
ci / docs-check (push) Failing after 30s
ci / lint (push) Successful in 42s
ci / test-integration (push) Successful in 46s
ci / security (push) Failing after 27s
ci / test-mcp (push) Successful in 1m16s
ci / test-unit (push) Successful in 2m17s
ci / build-and-push (push) Successful in 17s
ci / gitleaks (push) Successful in 8s
ci / docs-check (push) Failing after 30s
ci / lint (push) Successful in 42s
ci / test-integration (push) Successful in 46s
ci / security (push) Failing after 27s
ci / test-mcp (push) Successful in 1m16s
ci / test-unit (push) Successful in 2m17s
ci / build-and-push (push) Successful in 17s
Identity gains a redact field that the executor honours, so the OAuth resolver no longer needs an executor subclass overriding private internals. Opaque agm_ tokens issued by our authorization server are now masked by the secret patterns wherever they appear in content.
This commit is contained in:
1 parent
e02cede9f2
commit
ce5ae6caa5
8 files changed
+37
-38
No files matched your search
@@ -18,7 +18,6 @@ import dataclasses
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mcp.server.auth.provider import AccessToken
|
||||
@@ -26,14 +25,12 @@ from mcp.server.auth.provider import AccessToken
|
||||
from aegis_gitea_mcp.audit import AuditLogger, get_audit_logger
|
||||
from aegis_gitea_mcp.auth import STATIC_CLIENT_ID, IdentityResolver
|
||||
from aegis_gitea_mcp.errors import ToolError
|
||||
from aegis_gitea_mcp.gitea import GiteaExecutor
|
||||
from aegis_gitea_mcp.gitea.redaction import normalize_secrets
|
||||
from aegis_gitea_mcp.identity import Identity
|
||||
from aegis_gitea_mcp.oauth.store import OAuthStore, OAuthStoreError, SessionRecord
|
||||
from aegis_gitea_mcp.oauth.upstream import GiteaUpstream, UpstreamError, UpstreamInvalidGrant
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aegis_gitea_mcp.gitea.executor import _Call
|
||||
pass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -47,26 +44,6 @@ OAUTH_ONLY = (
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SessionIdentity(Identity):
|
||||
"""An OAuth identity that also names session secrets to redact from results.
|
||||
|
||||
Attributes:
|
||||
redact: Further secrets of the session (the Gitea refresh token) that must
|
||||
never appear in a result. Excluded from ``repr`` and comparisons.
|
||||
"""
|
||||
|
||||
redact: tuple[str, ...] = field(default=(), repr=False, compare=False)
|
||||
|
||||
|
||||
class OAuthGiteaExecutor(GiteaExecutor):
|
||||
"""Executor that also redacts the session secrets carried by a :class:`SessionIdentity`."""
|
||||
|
||||
def _secrets(self, identity: Identity, call: _Call) -> tuple[str, ...]:
|
||||
extra = identity.redact if isinstance(identity, SessionIdentity) else ()
|
||||
return normalize_secrets([*super()._secrets(identity, call), *extra])
|
||||
|
||||
|
||||
class OAuthIdentityResolver:
|
||||
"""Maps a verified access token to the identity a tool call runs as.
|
||||
|
||||
@@ -120,7 +97,8 @@ class OAuthIdentityResolver:
|
||||
if self._needs_refresh(session):
|
||||
session = await self._refresh(session_id)
|
||||
redact = (session.gitea_refresh_token,) if session.gitea_refresh_token else ()
|
||||
return SessionIdentity(
|
||||
# The executor redacts ``Identity.redact`` (the Gitea refresh token) from results.
|
||||
return Identity(
|
||||
login=session.login, mode="oauth", token=session.gitea_access_token, redact=redact
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user