Daytona Opencode
Run every OpenCode session in a Daytona sandbox whose vendor API calls are answered by a Veris twin. One line in opencode.json: unmodified code, real hostnames, and a receipt of what the vendor actually received.
0
359
近 7 天 183
35.7
生态多维模型
4 天前
2026-09-30
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@veris-ai/daytona-opencode@0.4.1"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@veris-ai/daytona-opencode@0.4.1"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D @veris-ai/daytona-opencodeOpenCode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
Run code in a Daytona sandbox where calls to
api.stripe.com and the rest of your vendor stack are answered by Veris
twins — stateful, contract-accurate fakes — with the code under test
completely unmodified.
No base-URL overrides, no injected config, no mocking library. Your code keeps its production hostnames, credentials and SDKs; the network layer does the rest. And every run ends with a receipt of what the vendor actually received.
| package | ||
|---|---|---|
@veris-ai/daytona |
a drop-in for @daytona/sdk |
run your own code against twins |
@veris-ai/daytona-opencode |
an OpenCode plugin | every agent session runs in one of these sandboxes |
Each has its own README with installation and usage. This page is about how they work and how to develop them.
@veris-ai/daytona is a library and nothing else: no executable, no runner.
It re-exports @daytona/sdk and replaces one class, Daytona, so that every
sandbox it creates comes up with a twin answering its vendor calls — the
egress credential, the gateway pin, the outbound proxy, the CA bundle, the
canary and the trust variables all live inside create(). Getting code in,
running commands and reading a receipt are the Daytona SDK's own calls
(fs.uploadFile, process.executeCommand) plus sbx.veris. Which run proved
what is the veris CLI's business, which already owns what a receipt means.
Shared skills in OpenCode
Use the canonical @veris-ai/veris-opencode skills package plus one sandbox
plugin after the pending skills 0.7.3 and provider 0.3.0 releases are published:
{
"plugin": [
"@veris-ai/veris-opencode@latest",
"@veris-ai/daytona-opencode@latest"
]
}
The commands are /veris:setup, /veris:build <request> and /veris:fix <request>.
verisSkill reads installed skill resources on the host; application file/bash
tools remain remote. verisTwin identifies the plugin-owned session and controls,
and verisReceipt takes an explicit pre-execution baseline. Skills reuse this
session automatically. Record and pin the resolved published npm versions.
See daytona-opencode/README.md for the capability
contract, separate network/TLS/persistence/sync behavior and release prerequisites.
How it works
Every sandbox is created with two Daytona parameters:
networkAllowList— the Veris gateway's IPv4 address, as one/32, and nothing else. Daytona enforces it at the network layer: a process that ignores the proxy variables cannot dial out at all.outboundProxyUrl— the Veris gateway, over HTTP CONNECT.
Daytona chains them: sandbox traffic reaches Daytona's own proxy, which forwards
it to the gateway, which answers vendor hostnames from the twin and passes
public hosts — package registries, git — through untouched. This is the same
tier @veris-ai/e2b uses.
Why an address and not a hostname list. Measured on Daytona: a
domainAllowList set beside outboundProxyUrl switches Daytona's egress proxy
into TLS inspection. The client is shown a leaf signed by Daytona's own
ephemeral CA, Daytona opens a second TLS session to the gateway, rejects the
Veris-signed leaf it gets back, and every vendor call ends as 502 could not reach upstream host — whatever the sandbox trusts, because the trust decision
that fails is Daytona's, not the client's. networkAllowList beside the same
proxy URL leaves the tunnel untouched, so the client verifies the Veris leaf
itself. The address comes from the control plane (gateway_ips in the egress
credential); an older control plane gets one DNS lookup of the proxy host.
Pinning to the gateway also retires Daytona's 20-domain cap: nothing is
trimmed, and there is no allowOut.
Nothing of ours runs inside the sandbox, so any image works — there is no
snapshot to register, no NET_ADMIN to request, and no environment to thread
through individual commands.
Why this is not a leak. The gateway, not the allowlist, stands between the sandbox and the real vendor: a vendor hostname is intercepted there and answered from the twin, and a process that bypasses the proxy is blocked by Daytona rather than let out — stripping every proxy variable gets a connection reset, not the real vendor. What the gateway passes through is public hosts with their own certificates.
The canary. Before create() resolves, a probe dials a reserved hostname
only the gateway answers, whose body carries the twin id. It proves in one
request that egress is tunnelled, that the credential reached the right twin,
and that trust is wired — and it cannot pass by accident, because outside the
tunnel that host has no listener. It runs again on every receipt(), so a
receipt is never reported from a sandbox whose egress cannot be vouched for.
CA trust. The gateway presents a certificate it forged for the vendor
hostname, signed by the Veris CA, and that certificate reaches the client
directly: Daytona tunnels the CONNECT end to end rather than terminating TLS
itself (measured: the leaf a sandbox sees for api.stripe.com is issued by
Veris Gateway CA). So the Veris CA is load-bearing. A bundle is assembled
inside the sandbox from the distribution's public roots plus ours, requiring
nothing of the image, and the trust variables Daytona does not itself set
(PIP_CERT, CARGO_HTTP_CAINFO, DENO_CERT and a dozen more) point at it.
Daytona then overwrites SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE
and NODE_EXTRA_CA_CERTS with its own bundle, which does not carry the
Veris CA and so cannot validate the chain — see the Limitations entry below for
what that breaks and how each runtime gets the right value back.
Bundled CAs. An SDK that ships its own CA file and loads it by path reads
none of those variables: stripe-python passes verify=stripe.ca_bundle_path,
and its first call fails with "Could not verify Stripe's SSL certificate" in a
sandbox where curl, Node and requests all succeed. sbx.veris .patchBundledCas() appends the Veris CA to the known ones (certifi, pip's
vendored certifi, botocore, stripe, httplib2) — the Daytona-shaped version of
the veris CLI's --patch-bundled-cas. Run it after installing dependencies.
Every sandbox also carries the same patcher as a script at
/tmp/veris-patch-bundled-cas.sh, so whoever installed the dependencies can
run it with no SDK in hand.
On socks_address. The egress credential also carries a SOCKS endpoint,
which is what @veris-ai/e2b uses. Daytona cannot: it accepts only
http/https outbound proxies, which is why the gateway has an HTTP CONNECT
listener.
Limitations
- Requires a Veris control plane that serves an HTTP CONNECT gateway.
Without one,
create()fails atcredential-mintsaying so. - The canary probe and CA install need
curland a POSIX shell in the image. Apython:3.12-slim-style image fails atcreate()withcurl: not foundin thecanaryphase. - Python 3.13+ needs a gateway that mints strict-verifier-safe leaves.
Newer Python verifies with
VERIFY_X509_STRICTand rejects a forged leaf without an Authority Key Identifier. The control plane fix is services-sandbox#1044; a control plane without it fails Python withMissing Authority Key Identifierwhilecurland Node succeed. - Daytona overwrites the CA variables, and its own CA file cannot verify
the gateway's leaf. Inside the sandbox
SSL_CERT_FILE,REQUESTS_CA_BUNDLE,CURL_CA_BUNDLEandNODE_EXTRA_CA_CERTSall point at Daytona's file, which lacks the Veris CA and is root-owned on a read-only mount. Measured with Python'srequests:unable to get local issuer certificatewith the inherited value, 200 withREQUESTS_CA_BUNDLE=/tmp/veris-ca-bundle.crt. So every runtime that reads one of those variables as its only trust source needs the Veris bundle exported per command; a command run without it (the OpenCode plugin's bash tool,daytona ssh) inherits Daytona's value. The SDK serves the right values two ways for whoever runs the command:sbx.veris.getTrustEnv()when you can hand the process an env map, andsbx.veris.trustPrelude()— one line of shellexports — when all you can do is prefix a command line. Node is handled at create time instead (NODE_OPTIONS=--use-openssl-ca, which Daytona leaves alone, reading the system certificate directory the Veris CA is installed into; that install needs passwordless sudo andupdate-ca-certificates, both in Daytona's default image). curl and Python's ownsslreturn 200 under the inherited value; both consult the system directory as well. Node also needsNODE_USE_ENV_PROXY=1, set at create time: Node ignores the proxy variables otherwise, and Daytona blocks a direct dial. - Node SDKs that build their own agent.
NODE_USE_ENV_PROXYreaches only Node's global agents and the globalfetchdispatcher. An SDK that constructs its ownhttp(s).Agentfor keep-alive pooling (stripe-node, the AWS SDK's Node handler, Twilio's client) never sees the proxy, resolves the vendor host itself, and dies on the blocked egress withEAI_AGAIN. Measured with stripe-node 15: the global agent 200, its own agentEAI_AGAIN, its own agent withproxyEnv: process.env200. So every sandbox carries/tmp/veris-node-proxy.cjs, a preload that subclasses both Agent classes to defaultproxyEnvto the process environment, andNODE_OPTIONSnames it with--requirebeside--use-openssl-ca. Both flags are appended to a caller's ownNODE_OPTIONS, never replacing them;verisNodeOptions()builds the value for a command of your own. Clients built on undiciPoolorClienthold their own dispatcher and are not covered. - An SDK that bundles its own CA reads no variable at all. stripe-python
passes
verify=stripe.ca_bundle_path, so the trust variables never reach it and the first Stripe call fails with "Could not verify Stripe's SSL certificate".sbx.veris.patchBundledCas()appends the Veris CA to the bundles listed above; run it after installing dependencies, because that is when they arrive. An SDK outside that list still fails, and its own error names the file to add. github.comgets an empty reply in an environment without a github twin. The platform's route table maps it to thegithubtwin, and the gateway resolves that table for every sandbox rather than only the services the environment deployed — so the gateway forges a leaf forgithub.com(it verifies) and then dials a backend pod that does not exist. Measured:uvcould not fetch a CPython the image lacked.codeload.github.comandraw.githubusercontent.compass through unaffected; an environment that does have the github twin gets the real route. The fix belongs in the gateway (intercept only what the sandbox's environment deployed).- A very long run's receipt is a floor, not a count. The twin's log is read
in pages of 1000 up to a budget; past that the count prints as
≥Nandentry.cappedis true. Below the budget it is exact — the count used to stop silently at the server's default of 50. - Git sync into the sandbox can fail with
Host key verification failed. Inherited from upstream@daytona/opencode; the agent works, but local changes are not pushed in. SettingDAYTONA_SSH_KNOWN_HOSTSis the likely fix. - QUIC/HTTP3 and ECH are not intercepted. The gateway relays TCP; both are
reported in the receipt's
leaksrather than silently omitted.
Layout
| directory | package |
|---|---|
veris-daytona/ |
@veris-ai/daytona |
daytona-opencode/ |
@veris-ai/daytona-opencode, forked from @daytona/opencode 0.192.0 |
Both npm packages sit under the @veris-ai scope, which already says "Veris",
so neither name repeats it. @veris-ai/daytona is the engine integration,
mirroring @veris-ai/e2b; @veris-ai/daytona-opencode is the plugin built on
it. <engine>-opencode scales: a future E2B-backed plugin is
@veris-ai/e2b-opencode.
Two meanings of "sandbox"
Held apart carefully throughout this codebase, because both are in play:
- a Daytona sandbox is the container your code runs in (
sandbox.id) - a Veris twin is the stateful fake answering its vendor calls
(
sandbox.verisSandboxId,VERIS_SANDBOX_ID)
Never write "sandbox" bare in a log line, error, or doc here.
The plugin fork is one import line
@veris-ai/daytona re-exports all of @daytona/sdk and overrides only
Daytona, so the entire behavioural diff against upstream is:
- } from '@daytona/sdk'
+ } from '@veris-ai/daytona'
in daytona/core/session-manager.ts. That one line carries twin provisioning,
the egress credential, the gateway pin, the outbound proxy, CA trust and the
canary — because all of it lives inside create(). Twin teardown rides along
too: @veris-ai/daytona wraps delete() on the sandbox it returns, so the
plugin's existing sandbox.delete() removes the twin with no plugin change at
all. Beyond that the fork adds one tool, registers it, one paragraph in the
system prompt, and a check that the Veris coordinates are set.
@daytona/sdk is a peer dependency, which is load-bearing. The plugin
branches on err instanceof DaytonaNotFoundError to tell "this sandbox is gone,
replace it" from "transient failure, keep the session mapping" — and
tools/bash.ts imports that class from @daytona/sdk directly while
session-manager.ts imports from us. Two copies of the SDK in one tree and
those checks silently return false. tests/unit/exports.test.ts fails loudly
if that ever regresses.
Developing
See CONTRIBUTING.md.
npm install # workspaces link @veris-ai/daytona into the plugin
npm run typecheck
npm test # unit tests, no credentials needed
# live, costs money, needs all three keys:
npm run smoke
License
Apache-2.0. See LICENSE and NOTICE — daytona-opencode/ is
a fork of @daytona/opencode, Copyright Daytona Platforms Inc.
同类生态推荐
Atelier
@konfeature/opencode-atelier
OpenCode plugin that dispatches coding tasks to Atelier sandboxes (self-hosted Kata Containers dev environments).
E2b Opencode
@veris-ai/e2b-opencode
OpenCode plugin that runs every session in an E2B sandbox whose vendor API calls are answered by a Veris twin, with a receipt of what the vendor received.
Seatbelt
opencode-seatbelt
macOS Seatbelt (kernel sandbox) for OpenCode. Stops coding agents from reading secrets, enforced by the OS, not by command matching.