Skip to content

feat(hub): secret intake links — secure secret submission from chat - #318

Open
zeroasterisk wants to merge 1 commit into
GoogleCloudPlatform:mainfrom
zeroasterisk:feat/secret-intake-link
Open

feat(hub): secret intake links — secure secret submission from chat#318
zeroasterisk wants to merge 1 commit into
GoogleCloudPlatform:mainfrom
zeroasterisk:feat/secret-intake-link

Conversation

@zeroasterisk

@zeroasterisk zeroasterisk commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Problem

When users interact with Scion agents via chat (Telegram, Discord), agents sometimes need secrets. Today users must leave the chat to use the CLI/web UI, or paste secrets in plaintext — a security anti-pattern that has already happened in practice (a raw GitHub PAT was pasted into Telegram in this project's own history).

Solution

Secret intake links: a short-lived, JWT-secured URL that lets users paste a secret directly into the Hub, with a two-step confirmation flow to prevent injection attacks.

User flow

Agent → "I need your GitHub token: https://hub.example.com/intake#<JWT>"
User  → clicks link, pastes secret
Hub   → stores as PENDING, sends confirmation to chat with submitter IP/UA
User  → confirms in chat
Hub   → secret becomes ACTIVE, available to agents

Security model

Protection How
One-time use Intake consumed after submission
Short-lived 15 min TTL default, 1 hour max
Rate limited 5 attempts per intake + per-IP token bucket
JWT signed HS256 with Hub signing key
Pending gate Value not usable until creator confirms in authenticated chat
Tamper evident Submitter IP/UA shown in confirmation
Zero-knowledge URL JWT in fragment — never sent to server in HTTP request

Alternatives considered

  1. Direct paste in chat: What happens today. Secrets end up in chat logs, potentially visible to other users or logged by the platform.
  2. CLI-only workflow: Secure, but breaks the chat UX — users must context-switch to a terminal.
  3. OAuth per-service: More secure for supported services, but high integration cost and doesn't cover arbitrary secrets (API keys, tokens).

We chose intake links as the best tradeoff between security and UX for the chat context.

Reviewability roadmap

This is ~1,865 lines but the core logic is in one file:

  1. Start with pkg/hub/secret_intake.go — the SecretIntakeService: JWT generation, rate limiting, full lifecycle (create → submit → confirm/reject). Follows the existing TelegramLinkService pattern.
  2. pkg/hub/secret_intake_test.go — 12 tests covering the full lifecycle + security edge cases
  3. pkg/hubclient/secret_intake.go — SDK client (thin wrapper)
  4. cmd/hub_secret_intake.go — CLI command scion hub secret intake KEY
  5. web/src/components/pages/secret-intake.ts — Lit component for the intake page
  6. Wiring: server.go (routes), auth.go (exempt submit endpoint — JWT is the auth)

This PR could be split if you'd prefer to review the backend (Go) and frontend (Lit) separately — happy to do that on request.

Risk

  • Crypto surface: JWT signing uses HS256 with the Hub's existing signing key. No new crypto primitives introduced — this reuses the Hub's existing JWT infrastructure.
  • Backward compatible: No changes to existing secret management. Intake links are a new, additive feature.
  • No migration needed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements Secret Intake Links, enabling secure secret submission from chat channels via short-lived, one-time-use URLs. It adds a CLI command, backend handlers, an in-memory SecretIntakeService, and a standalone frontend page for anonymous submission. The review feedback identifies several critical issues: a concurrency data race on SecretIntake fields between handlers and the background cleanup loop; a client-side JWT decoding bug due to missing base64 padding and lack of UTF-8 support; a potential memory-exhaustion DoS vulnerability from a missing size limit on submitted values; a validation gap where scope_id is not verified for project-scoped secrets; loose path matching in the unauthenticated endpoint check; and a bug where trimming whitespace from submitted secrets can corrupt keys with significant whitespace.

Comment thread pkg/hub/secret_intake.go Outdated
Comment thread web/src/components/pages/secret-intake.ts Outdated
Comment thread pkg/hub/secret_intake.go
Comment thread pkg/hub/secret_intake.go
Comment thread pkg/hub/auth.go Outdated
Comment thread web/src/components/pages/secret-intake.ts
@ptone

ptone commented Jun 5, 2026

Copy link
Copy Markdown
Member

could said link basically just bring you to the new secret dialog in the linked project

the secret intake with no login, then confirmation in chat seems like a mild security risk surface

@zeroasterisk

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback! We were going for a more versatile and zero-friction approach (works even if the user isn't near a browser session), but you're right that the no-login surface is unnecessary risk. Switching to a deep-link into the Hub UI with a simplified, focused secret-entry screen. The user must be logged in, the link pre-fills the key/scope/project/type from a JWT, and we'll send a notification to chat when the secret is stored. Dropping the anonymous intake page and the confirmation step.

@zeroasterisk
zeroasterisk force-pushed the feat/secret-intake-link branch from 3e3da3f to 9cbfdc9 Compare June 7, 2026 22:53
@zeroasterisk

Copy link
Copy Markdown
Contributor Author

Round 3 review complete — 3 clean cycles, no new findings. Rebased on upstream/main (resolved 4 merge conflicts in server.go from upstream Discord link service additions). All tests pass. Design simplified per maintainer feedback: authenticated deep-link, no anonymous endpoints, no OTP/confirmation flow. 12 tests.

@zeroasterisk
zeroasterisk force-pushed the feat/secret-intake-link branch from 8d1bf6c to 495021e Compare June 17, 2026 03:49
@zeroasterisk

Copy link
Copy Markdown
Contributor Author

could said link basically just bring you to the new secret dialog in the linked project

the secret intake with no login, then confirmation in chat seems like a mild security risk surface

This is what we did

@zeroasterisk
zeroasterisk force-pushed the feat/secret-intake-link branch from 495021e to c84a492 Compare June 27, 2026 15:19
@zeroasterisk
zeroasterisk force-pushed the feat/secret-intake-link branch from c84a492 to 03fb52c Compare August 3, 2026 06:24
@ptone

ptone commented Aug 3, 2026

Copy link
Copy Markdown
Member

I think I might have come up with a much more elegant approach to this, which would use native bot commands in the chat system. I would allow a user to send secrets directly to the control plane, not via an agent, and it would not be visible to other users in the chat. There then has to be a companion issue about how agents actually receive or retrieve secrets that were created after they were provisioned, but that's at least a separate issue.

@ptone

ptone commented Aug 7, 2026

Copy link
Copy Markdown
Member

#1069

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants