Skip to content

feat: add ncrypted action provider for one-time encrypted secrets - #1567

Open
JustinSeidl wants to merge 1 commit into
coinbase:mainfrom
JustinSeidl:feat/ncrypted-action-provider
Open

JustinSeidl wants to merge 1 commit into
coinbase:mainfrom
JustinSeidl:feat/ncrypted-action-provider

Conversation

@JustinSeidl

Copy link
Copy Markdown

Description

Adds ncryptedActionProvider, which lets an agent share secrets (passwords, API keys, tokens, private notes) as one-time, end-to-end encrypted links instead of putting the value in a chat message, tool output or log.

Actions:

  • create_secret: encrypts the value locally and returns a link that opens exactly once. Optional TTL (5 min to 7 days), passphrase, and email delivery.
  • read_secret: opens a link, returns the value, and destroys the secret.
  • secret_status: tells the creator whether the link has been opened.

How it works:

  • Encryption happens in the agent's process (AES-256-GCM, key derived via HKDF-SHA256 from a random 32-byte link key, plus PBKDF2 for an optional passphrase). The key only exists in the link's #fragment, so the ncrypted service stores ciphertext only. The first read deletes it atomically.
  • The first 3 secrets per day are free. After that each costs 0.005 USDC on Base, paid via x402 from the agent's wallet. It uses the same @x402/fetch + registerExactEvmScheme(walletProvider.toSigner()) pattern as the x402 provider, with a registerPolicy cap (maxPaymentUsdc, default 0.01) so a misbehaving server can never charge more. Reading and status checks are free.
  • No new dependencies. The crypto is a small self-contained module (crypto.ts). The tests decrypt vectors produced by the reference implementation to pin wire compatibility.
  • supportsNetwork: Base mainnet only, since payments settle in USDC on Base.

Disclosure: I'm the author of ncrypted. Source and protocol: https://github.com/Seidl-Family/ncrypted (MIT). It's listed in the x402 Bazaar and the MCP Registry (io.github.Seidl-Family/ncrypted).

Tests

Unit tests (ncryptedActionProvider.test.ts, 12 tests):

  • Decrypts SDK-produced vectors, with and without a passphrase. Round-trips; rejects a wrong key, wrong passphrase or missing passphrase.
  • Schemas.
  • create_secret (free path): uploads ciphertext only. The returned link's key decrypts exactly what was uploaded, and the wallet isn't asked to sign.
  • create_secret with emailTo: opts out of the free tier and emails the link.
  • Payment cap: a 402 asking for 1 USDC is refused and the wallet never signs.
  • read_secret, already-read (410) handling, secret_status, supportsNetwork.

pnpm exec jest src/action-providers/ncrypted, eslint, prettier --check and tsc --noEmit all pass.

Real mainnet run through AgentKit (direct action invocation rather than the example chatbots, which need OpenAI and CDP keys I didn't use for this):

Setup: AgentKit.from({ walletProvider: new ViemWalletProvider(<Base mainnet wallet with USDC>),
                      actionProviders: [ncryptedActionProvider()] })
Network: Base mainnet (free tier already used, so this exercised the paid x402 path)

actions via AgentKit: NcryptedActionProvider_create_secret, NcryptedActionProvider_read_secret, NcryptedActionProvider_secret_status
created: VKwNG9E0QD7ENgYr9tW3_g paid: true
status before: pending
read: agentkit mainnet e2e
read again: Error reading secret: This secret has already been read, burned, or has expired.
status after: read

Separately, while testing I noticed that action invocations trigger analytics requests whose rejections aren't handled (Error: HTTP error! status: 400 as an unhandled rejection), which terminates the process on Node 15+. WalletProvider.trackInitialization already catches this (#1408), but the sendAnalyticsEvent(...) call in action-providers/actionDecorator.ts has no .catch. Happy to open a separate PR if useful.

Checklist

  • Added documentation to all relevant README.md files (provider README plus the action table in typescript/agentkit/README.md)
  • Added a changelog entry (.changeset/ncrypted-one-time-secrets.md)

Adds ncryptedActionProvider with create_secret, read_secret and
secret_status. Agents can share passwords and API keys as one-time,
end-to-end encrypted links instead of putting the values in messages.
Encryption is local (AES-256-GCM); the service stores ciphertext only.
The first 3 secrets per day are free, then 0.005 USDC on Base via x402,
paid from the agent's wallet and capped at maxPaymentUsdc per secret.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@github-actions github-actions Bot added documentation Improvements or additions to documentation action provider New action provider typescript labels Oct 16, 2026

This branch has not been deployed

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

Labels

action provider New action provider documentation Improvements or additions to documentation typescript

Development

Successfully merging this pull request may close these issues.

2 participants