Skip to content

feat(webchat): agent-initiated secure credentials form and authenticated handoff #1

Open

Description

@mrgoonie

Summary

Support agent-initiated, secure credential collection in the embeddable <dewee-chat> widget. When an agent needs an API key, token or client secret, display a native Credentials form or a secure Dewee-hosted form link. Never ask the visitor to paste a secret into chat.

Companion gateway issue: https://github.com/nextlevelbuilder/dewee/issues/931

This repository owns widget rendering, HTTP/SSE protocol and TS/PHP backend adapters. The Dewee gateway owns authorization, credential encryption/storage, request lifecycle, and credential_ref. Do not introduce a second secret vault.

Current architecture (codebase findings)

  • packages/dewee-webchat/src/widget/element.ts: Shadow DOM web component, floating/inline UI, SSE, conversation history.
  • packages/dewee-webchat/src/protocol.ts: shared ChatStreamEvent / HistoryMessage; only generic tool_call and tool_result are supported today.
  • packages/dewee-webchat/src/server/gateway-client.ts: gateway v3 WS tool.call/tool.result are forwarded as generic SSE and folded into history.
  • packages/dewee-webchat/src/server/handler.ts: TypeScript backend (/config, /session, /chat, /history).
  • php/dewee-webchat.php: independent PHP backend with the same protocol.
  • docs/http-contract.md: contract shared by both backends and widget.
  • A signed webchat visitor token is not owner/admin authorization for modifying workspace-wide credentials.

Functional requirements

1. Safe agent-to-widget request

  • Consume a server-authorized secret_request / credential-request event from Dewee #931. The payload contains metadata only: request ID, schema version, status, trusted target/destination, purpose, allowed field descriptors, expiry, and optionally a trusted secure-form URL.
  • Add typed contract to protocol.ts. Map only validated gateway data, bound to the correct tenant, user, agent and conversation. Do not let the model supply arbitrary HTML, scripts, destination URLs or phishing fields.
  • Do not treat this as a normal tool result: prevent secret-related raw tool arguments/results from appearing in SSE, transcript/history, UI debug views or observability.

2. Native Credentials form in webchat

  • Render a secure form/card in <dewee-chat> for both floating and inline modes, supporting mobile, accessibility, light/dark themes and existing en/vi localization.
  • Display trustworthy destination, intended access/scope, required/optional fields, masked secret inputs, explicit Save/Connect and Cancel, spinner, success, error, expired and rotate/replace confirmation states.
  • Keep secret values only in the active form's volatile memory, clear on completion/cancel/expiry/unmount. Never persist them in localStorage/sessionStorage, chat messages, history, markdown, clipboard copy, custom widget events, error messages or analytics.
  • Form submission must be a separate HTTPS request to an authorized first-party backend or verified Dewee-hosted form, never send(text) / POST /chat / model prompts.

3. Authorization boundary for public website visitors

Important: This widget accepts anonymous visitors. Their HMAC-signed visitor ID alone cannot authorize writing global workspace credentials (provider, MCP, channel, CLI, etc.).

  • Feature is disabled by default unless the host/gateway provides explicit credential-request capabilities and a trusted identity/authorization integration.
  • Support only server-verified Dewee user identity / account linking and actor-scoped destinations. Credential grants, writes and rotations must be authorized server-side on creation, display, submission and completion.
  • User-scoped credentials may be supported only if the gateway enforces strict per-actor ownership and isolation. Never silently save a visitor's secret into tenant-global settings.
  • If inline authorization cannot be proven in an embedded third-party site, render a first-party authenticated Dewee secure-form handoff URL. Do not collect secrets directly in the untrusted page.
  • Keep operator gateway token exclusively on server. Existing playground-only allowClientCredentials is a separate feature and is not authorization for this flow.

4. Backend and gateway integration

  • Extend /api/dewee/* contract with secure request status and submit/cancel or hosted-form handoff, as supported by Dewee #931.
  • Enforce strict binding to tenant, actor, agent, conversation, credential target and request ID; user authorization, TTL, single-use/idempotency, anti-replay, CSRF/origin controls, allowed gateway host and destination, safe redirects, no-store, rate limits and redacted errors.
  • Never expose gateway secret/token to browser. Never include entered values in SSE/WS frames, generic tool results, chat callbacks (onChat), logs, metrics, error payloads or URLs.
  • Keep TypeScript handler and PHP backend fully equivalent. No insecure fallback on unsupported PHP deployments.
  • Restore pending/resolved/cancelled/expired cards in history without restoring field contents. Avoid duplicate cards from event replay.
  • On success, Dewee stores the secret encrypted and returns only sanitized status / scoped credential_ref; notify/resume the originating agent session once.

Acceptance criteria

  • Agent-triggered credential request renders secure form in floating and inline embedded webchat.
  • Authorized users can submit credentials without those values entering chat/LLM context/session history.
  • Unauthenticated visitor cannot create/rotate/write workspace-global credentials.
  • When inline auth is unavailable, offer authenticated first-party Dewee form link with expiry, one-use, identity binding; forwarded/expired links fail closed.
  • Correct agent/session gets only sanitized completion status / credential_ref; no duplicate continuation.
  • Reload restores only request metadata/status, never secrets.
  • TS and PHP backends implement equivalent status, submit and rejection behavior.
  • Tests cover actor/tenant isolation, unauthorized visitor, wrong conversation, expired/replay/forwarded request, CSRF, forged tool event, field validation, cancellation, and multi-secret forms.
  • Regression tests prove no secret in SSE, tool arguments/results, history, custom events, logs, analytics callbacks or browser storage.
  • Update docs/http-contract.md, package docs, PHP docs, examples, i18n and test suites.

Implementation sequence

  1. Agree on the safe credential-request schema and authorized submit/handoff APIs from dewee#931.
  2. Extend TS gateway adapter, HTTP contract and history redaction.
  3. Add native Shadow DOM card and trusted hosted-form fallback.
  4. Mirror PHP adapter; verify security tests and docs.

Out of scope: passwords or MFA recovery codes for Dewee accounts, arbitrary agent-generated forms, and any second credential store inside the widget.

Activity

  1. added
    enhancementNew feature or request
    securitySecurity, identity, credentials and secret handling
    ai-handleAI-assisted implementation ready
    on Oct 10, 2026
  2. self-assigned this
    on Nov 8, 2026
  3. mrgoonie commented on Nov 8, 2026

    @mrgoonie
    ContributorAuthor

    The current Gateway tool and public ingress do not expose a verified, actor- and conversation-bound credential request event or authorized handoff to this widget. The required contract and runtime/CCP work are tracked in nextlevelbuilder/dewee#2233. The existing external-channel link path cannot be used to grant anonymous website visitors workspace credential authority. Implementation can resume when that contract is available.

  4. mrgoonie commented on Nov 8, 2026

    @mrgoonie
    ContributorAuthor

    Wire contract for credential requests, implemented in nextlevelbuilder/dewee#2233 (off by default, signed-in visitors only). The widget can build against this.

    Negotiation

    • Send credential-requests in X-Dewee-Webchat-Features on chat, history and the routes below.
    • The widget config gains credential_requests: boolean. Show nothing when it is false or the visitor is anonymous.

    SSE event

    • {"type":"credential_request","credential_request": view}. The raw tool frame is never forwarded; the view is rebuilt from the stored request.

    View (schema_version: "webchat-credential-request.v1")

    {id, schema_version, status, title, purpose, target{type,mode},
     fields[{key,label,kind,required}], expires_at, agent_key,
     completion: "hosted", resumable}
    
    • A view never carries a value, token, link or session key.
    • A pending request past expires_at reads expired.

    Public routes (POST /api/public/webchat/v1/{integrationId}/…; same headers as chat)

    • Body for all three: {conversation_id, credential_request_id, identity_session, agent?}.
    • credential-request → 200 {credential_request: view}. Poll it while the hosted form is open.
    • credential-request-handoff → 200 {credential_request_id, url, expires_at}, no-store.
      • Open url in a new tab and never persist it.
      • Each call replaces the previous link.
      • Limited to 10 per visitor per 10 min.
    • credential-request-cancel → 200 {credential_request: view}.

    Resume and history

    • Resume: chat with credential_request_id and an empty message, once the status is completed.
      • 409 credential_request_not_ready: still pending, keep polling.
      • 409 credential_request_resumed: already resumed, ignore.
    • History: history returns credential_requests[] (oldest first). Restore cards from it; the composed resume turn is never in messages.

    Errors (by details.reason)

    • 403 credential_requests_unavailable: hide the card.
    • 404 credential_request_not_found: drop the card.
    • 429 rate_limited.

    The widget never collects the secret; the hosted form does, and it requires a signed-in workspace admin.

    Refs nextlevelbuilder/dewee#2233


    Generated by Claude Code

  5. added
    in progressClaimed; implementation underway
    and removed
    blockedWaiting for a documented dependency
    on Nov 8, 2026
  6. mrgoonie commented on Nov 8, 2026

    @mrgoonie
    ContributorAuthor

    PRs #45 and #47 are merged on main: the widget now renders the value-free card, opens only the configured Dewee HTTPS form origin, supports status/cancel/resume and restores metadata without storing the token. Node/Workers and PHP implement the same CCP contract; legacy direct-Gateway streams/history suppress credential-tool payloads. The feature is off unless the host explicitly configures it and the visitor has verified identity.

    The CCP config capability reflects its default agent. A signed-in integration may bind a different agent, so the adapters declare the host capability for the verified request and let the Gateway authorize the actual agent on every operation instead of rejecting it based on the default-agent flag. Gateway/CCP remain the authority.

    Validation: independent security review; Windows full suite 404 passed/5 skipped, typecheck/build/bundler passed; clean Linux focused suite 51 passed; both PR and post-merge CI passed. Surface parity: Node/Workers, PHP, widget and docs updated; Gateway and trace/CLI rationale in nextlevelbuilder/dewee#2240. Public docs follow-up: nextlevelbuilder/dewee-web-v2#133.

    Keep this issue open for a deployed signed-in end-to-end journey, including completion, cancellation, replay/expiry and history recovery. The latest successful Gateway and CCP staging deployments predate the required Gateway commit; no deployed support is claimed yet.

  7. added
    blockedWaiting for a documented dependency
    and removed
    in progressClaimed; implementation underway
    on Nov 8, 2026
  8. mrgoonie commented on Nov 8, 2026

    @mrgoonie
    ContributorAuthor

    Gateway and CCP staging deployments have now succeeded at e8cb2a16, which includes the actor-bound credential-request handoff. The currently observed staging.agentkit.best webchat config reports backend version 0.4.1 and does not include credentialFormOrigin; that integration has not been shown to run the beta.4 backend or to have the opt-in and verified identity required for this feature.

    A staging host running the new backend, explicit trusted form origin and verified signed-in identity is still needed for the hosted-form journey, cancellation, expiry/replay and history recovery checks. Keep this issue open until those checks are demonstrated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ai-handleAI-assisted implementation readyblockedWaiting for a documented dependencyenhancementNew feature or requestsecuritySecurity, identity, credentials and secret handling

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions