feat(523): forge-connection tokens are stored sealed under SECRET_KEY, like the backup S3 secret
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 56s
CI & Build / integration (push) Successful in 1m8s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 36s

The column becomes token_sealed (migration 0122 seals existing rows with a
frozen copy of the v1 key), every reader opens it through connection_token,
a row this instance cannot open is skipped with a warning, and Settings asks
for that token again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 18:08:11 -04:00
co-authored by Claude Opus 5.5
parent 3008d29f89
commit 07bc51b890
10 changed files with 232 additions and 16 deletions
+4 -2
View File
@@ -16,7 +16,9 @@ class ForgeConnection(Base, TimestampMixin):
`host` is derived from `base_url` at write time and stored because it is
the lookup key; the service layer keeps the two in step. The token is a
secret: to_dict never includes it, and no route may return it.
secret: it is stored sealed (services/secrets.py — read it through
services.forge_connections.connection_token), to_dict never includes it,
and no route may return it.
"""
__tablename__ = "forge_connections"
@@ -31,7 +33,7 @@ class ForgeConnection(Base, TimestampMixin):
kind: Mapped[str] = mapped_column(Text, nullable=False)
base_url: Mapped[str] = mapped_column(Text, nullable=False)
host: Mapped[str] = mapped_column(Text, nullable=False)
token: Mapped[str] = mapped_column(Text, nullable=False)
token_sealed: Mapped[str] = mapped_column(Text, nullable=False)
def to_dict(self) -> dict:
return {