From dd72a38a92277ddb408e50af6798787a2a637e25 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 26 Oct 2026 18:12:39 -0500 Subject: [PATCH] docs: record human chat verification-key entry and qualification Document both signing-key guards and the failure-preserving real-auth evidence. Preserve catalog creator attribution in this companion so the implementation and companion stay under the review file limit. Co-Authored-By: Paperclip --- doc/plans/2026-10-13-connection-api-parity.md | 10 ++++++++++ maps/agent-access-map/app-permissions.md | 4 ++-- maps/agent-access-map/chat-channels.md | 6 +++--- maps/feature-map/chat-channels.md | 2 ++ .../services/tool-access/catalog/default-access.ts | 13 ++++--------- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/doc/plans/2026-10-13-connection-api-parity.md b/doc/plans/2026-10-13-connection-api-parity.md index 76644564035..9dedac1ba54 100644 --- a/doc/plans/2026-10-13-connection-api-parity.md +++ b/doc/plans/2026-10-13-connection-api-parity.md @@ -33,6 +33,8 @@ Binding and audience authorization checks field presence, including `null`, `fal ## Saved authentication and delegation +Generic chat configure/reconnect treats signing/webhook secrets, private keys, client secrets and project secrets as human credential entry. A delegated request returns the saved endpoint's addressed handoff before provider work or vault mutation. Verified provider provisioning can still save provider-selected credentials. Authorized bot-token and API-key entry remains delegated. Generic tool-connection updates cannot replace channel credential references and bypass the provider setup boundary. + Agent setup responses retain their browser fields and add `requiredUserAction` where a human step is needed. It identifies the addressed user, saved resource, setup destination, reason and continuation. Chat handoffs reuse the saved endpoint and existing connection-intent delivery. AI login handoffs reopen the saved sign-in intent in the existing credential UI. Completion is based on server state; the UI does not ask a human to certify setup success. Generic app OAuth returns the saved setup destination without creating agent-bound provider state; the human browser starts the existing session-bound authorization flow. Its `expiresAt` is null until that human step creates an expiring provider session. Slack/GitHub provider callbacks preserve human subject and state checks. Agent-created GitHub setup binds its browser session when the addressed human opens it. Identity callbacks validate the current human and session before consuming state, and revalidate authority before saving identity. Wrong-user callbacks leave the rightful user's flow intact. Continuation rechecks task state and current authority after a yielding run; cancelled tasks and revoked access cannot be revived by a late provider result. @@ -197,3 +199,11 @@ Security review found that tool-connection activity repeated a full run capture Implementation `e5b8070d2f` creates one management evaluator, shares decisions by task within the bounded page, and passes the current request's read context into each probe. The passing regression proves two capability decisions and at most two run captures, the same 50 visible events, no private event content, and a fresh 403 after revoking audit permission (`/tmp/parity-tool-activity-green.log`). No authority is cached across requests. **82 API/OpenAPI tests passed** in fresh PostgreSQL `parity_review_35`: 55 assembled-server actual-auth scenarios and 27 contracts (`/tmp/parity-tool-activity-full-green.log`). Server typecheck/build passed (`/tmp/parity-tool-activity-types.log`, `/tmp/parity-tool-activity-build.log`). The earlier red result is retained as the baseline; no full local sweep, live provider verification, or empirical model success claim is added. + +### 2026-10-26: delegated verification-key entry and CI rate assertions + +Security review found that generic Slack reconnect accepted an agent-chosen signing key while verification reused the saved bot token. A clean actual-key regression reproduced HTTP 200 instead of a human handoff (`/tmp/parity-chat-signing-red-fresh.log`). Source review exposed a second path: actual-JWT generic tool-connection PATCH accepted a replacement signing-key secret reference (`/tmp/parity-chat-binding-red.log`). Both paths are now guarded before persistence. Configure/reconnect returns the saved addressed credential-entry handoff; the generic credential-reference update fails with 403. Tests confirm unchanged refs and the original vaulted signing value, human-session setup, permitted agent bot-token repair, and normal policy/rate checks on test messages. + +The first diagnostic used `configure` on an already configured endpoint and returned the expected lifecycle 409 rather than exercising reconnect (`/tmp/parity-chat-signing-red.log`). Reusing that database then hit the known Slack identity fixture collision (`/tmp/parity-chat-signing-red-reconnect.log`). Fresh databases established the two security baselines above. The first full post-fix run passed all 55 authenticated cases but failed an OpenAPI assertion that expected `anyOf` instead of the emitted `oneOf` (`/tmp/parity-chat-signing-full-green.log`). The final clean PostgreSQL `parity_review_41` run passed all **82 API/OpenAPI tests** (`/tmp/parity-chat-signing-final-full.log`). Eight focused native receipt protections also passed in `/tmp/parity-chat-signing-green.log`. Server typecheck/build passed (`/tmp/parity-chat-signing-types.log`, `/tmp/parity-chat-signing-build.log`). No live provider claim is added. + +Linux CI run `40415124314` failed two existing rate-admission assertions because persisted admissions now also record the bucket identity used by paired caller/target checks (`/tmp/parity-e5-linux-failure.log`). The local baseline reproduced both failures (`/tmp/parity-rate-bucket-red.log`). Updating the expected shape first used the wrong fixture field (`/tmp/parity-rate-bucket-green.log`); using the actual actor identity then passed all **49 policy service tests** (`/tmp/parity-rate-bucket-final-green.log`). No production rate behavior changed for this correction. The companion preserves the catalog creator-attribution follow-up so the implementation review stays at 99 changed files. Both PRs must ship together. Fresh current-head Linux CI and reviews remain required; no full local test-sweep pass is claimed. diff --git a/maps/agent-access-map/app-permissions.md b/maps/agent-access-map/app-permissions.md index 007bec9cb96..716fbb8a8c7 100644 --- a/maps/agent-access-map/app-permissions.md +++ b/maps/agent-access-map/app-permissions.md @@ -466,9 +466,9 @@ Gaps: This individual operation has source review but no recorded HTTP verificat API: `present` — `PATCH /api/tool-connections/{connectionId}`; JSON REST. Agent access: `available` — agent REST and default native `search_api` / `call_api`. -Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). Resolved resources and every affected target are checked; personal ownership remains the responsible user. +Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). Resolved resources and every affected target are checked; personal ownership remains the responsible user. Agents cannot replace channel credential references through this generic operation; provider setup owns credential validation and human verification-key entry. Evidence: `verified` — [route](../../server/src/routes/tool-access.ts); reviewed in the working tree based on the source commit above. [Authenticated fixtures](../../server/src/__tests__/connection-management.integration.test.ts) passed with database persistence and actual bearer/session middleware. -Verification: `passed` — deterministic provider fixtures / local assembled server; no live provider qualification. +Verification: `passed` — deterministic provider fixtures / local assembled server, including actual-JWT denial of a Slack signing-key reference replacement. Ordinary connection configuration remains permitted. No live provider qualification. Gaps: Provider fixtures do not qualify live provider setup or availability. ### `delete-tool-connections` diff --git a/maps/agent-access-map/chat-channels.md b/maps/agent-access-map/chat-channels.md index f8c408a1e01..b7d1a5da259 100644 --- a/maps/agent-access-map/chat-channels.md +++ b/maps/agent-access-map/chat-channels.md @@ -265,10 +265,10 @@ Gaps: This individual operation has source review but no recorded HTTP verificat ### `post-chat-endpoints-setup` API: `present` — `POST /api/chat-endpoints/{endpointId}/setup`; JSON REST. -Agent access: `available` — agent REST and default native `search_api` / `call_api`. -Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). Resolved resources and every affected target are checked; personal ownership remains the responsible user. +Agent access: `available` — agent REST and default native `search_api` / `call_api`. Agent-supplied signing/webhook secrets, private keys, client secrets, and project secrets return a saved human credential-entry handoff. Authorized write-only bot-token and API-key setup remains delegated. +Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). The service rejects delegated verification-key entry before provider work or vault writes. Human sessions and verified provider handoffs retain credential entry; agent configuration uses the responsible user's current authority. Evidence: `verified` — [route](../../server/src/routes/chat-channels.ts); reviewed in the working tree based on the source commit above. [Authenticated fixtures](../../server/src/__tests__/connection-management.integration.test.ts) passed with database persistence and actual bearer/session middleware. -Verification: `passed` — deterministic provider fixtures / local assembled server; no live provider qualification. +Verification: `passed` — actual key/JWT configure/reconnect requests return the addressed handoff with unchanged secret refs and vault signing value. A human session configures Slack; an agent can repair its bot token and send a policy-governed test message. Native setup receipts are protected. No live provider qualification. Gaps: Provider fixtures do not qualify live provider setup or availability. ### `post-chat-endpoints-setup-secret` diff --git a/maps/feature-map/chat-channels.md b/maps/feature-map/chat-channels.md index 6fc4e31659e..5407bf1f3df 100644 --- a/maps/feature-map/chat-channels.md +++ b/maps/feature-map/chat-channels.md @@ -29,6 +29,8 @@ Message the configured bot/address from its external provider and follow the cor ### `endpoint-management` +Delegated setup can save ordinary bot tokens and API keys. Provider verification keys require the addressed human credential-entry step or verified provider provisioning. Generic setup returns the saved handoff without changing those keys, and generic connection updates cannot replace channel credential references. The actual-auth Slack fixture covers both guards and permitted bot-token repair; live provider repair remains separately qualified. + Open `/apps/chat/:endpointId/settings` and the endpoint’s other available tabs; identity confirmation uses `/chat-identity/confirm`. ### `outbound-agentmail` diff --git a/server/src/services/tool-access/catalog/default-access.ts b/server/src/services/tool-access/catalog/default-access.ts index c9a459dfafc..408a33d9b14 100644 --- a/server/src/services/tool-access/catalog/default-access.ts +++ b/server/src/services/tool-access/catalog/default-access.ts @@ -1,3 +1,4 @@ +import { connectionActorUserId } from "../actor.js"; import { lockManagedAppPolicies, lockToolAssignmentAgents } from "../assignment-locks.js"; import type { ActorInfo, ToolAccessMutationDb } from "../types.js"; import { and, eq, inArray } from "drizzle-orm"; @@ -111,9 +112,7 @@ export function createCatalogDefaultAccess(db: Db) { ? (input.actor.actorId ?? null) : null, createdByUserId: - input.actor?.actorType === "user" - ? (input.actor.actorId ?? null) - : null, + connectionActorUserId(input.actor), }); } else if (resetToRecommendedDefaults) { // Removing an app may retain its profile row when another record still @@ -163,9 +162,7 @@ export function createCatalogDefaultAccess(db: Db) { ? (input.actor.actorId ?? null) : null, createdByUserId: - input.actor?.actorType === "user" - ? (input.actor.actorId ?? null) - : null, + connectionActorUserId(input.actor), }); } @@ -299,9 +296,7 @@ export function createCatalogDefaultAccess(db: Db) { ? (input.actor.actorId ?? null) : null, createdByUserId: - input.actor?.actorType === "user" - ? (input.actor.actorId ?? null) - : null, + connectionActorUserId(input.actor), }) .returning(); results.push(toPolicy(created));