Skip to content
    ↑↓ select↵ openesc close
    veris-ai

    Daytona Opencode

    @veris-ai/daytona-opencode·v0.4.1·Sandbox & Infra

    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.

    GitHub stars

    0

    Monthly installs

    359

    183 in 7 days

    Composite score

    35.7

    Multi-signal model

    Last commit

    4 days ago

    2026-09-30

    Install and configure

    opencode.json

    Writes to this project's opencode.json — applies to this repository only.

    opencode.json

    {
      "$schema": "https://opencode.ai/config.json",
      "plugin": ["@veris-ai/daytona-opencode@0.4.1"]
    }

    OpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.

    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 at credential-mint saying so.
    • The canary probe and CA install need curl and a POSIX shell in the image. A python:3.12-slim-style image fails at create() with curl: not found in the canary phase.
    • Python 3.13+ needs a gateway that mints strict-verifier-safe leaves. Newer Python verifies with VERIFY_X509_STRICT and rejects a forged leaf without an Authority Key Identifier. The control plane fix is services-sandbox#1044; a control plane without it fails Python with Missing Authority Key Identifier while curl and 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_BUNDLE and NODE_EXTRA_CA_CERTS all point at Daytona's file, which lacks the Veris CA and is root-owned on a read-only mount. Measured with Python's requests: unable to get local issuer certificate with the inherited value, 200 with REQUESTS_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, and sbx.veris.trustPrelude() — one line of shell exports — 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 and update-ca-certificates, both in Daytona's default image). curl and Python's own ssl return 200 under the inherited value; both consult the system directory as well. Node also needs NODE_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_PROXY reaches only Node's global agents and the global fetch dispatcher. An SDK that constructs its own http(s).Agent for 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 with EAI_AGAIN. Measured with stripe-node 15: the global agent 200, its own agent EAI_AGAIN, its own agent with proxyEnv: process.env 200. So every sandbox carries /tmp/veris-node-proxy.cjs, a preload that subclasses both Agent classes to default proxyEnv to the process environment, and NODE_OPTIONS names it with --require beside --use-openssl-ca. Both flags are appended to a caller's own NODE_OPTIONS, never replacing them; verisNodeOptions() builds the value for a command of your own. Clients built on undici Pool or Client hold 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.com gets an empty reply in an environment without a github twin. The platform's route table maps it to the github twin, and the gateway resolves that table for every sandbox rather than only the services the environment deployed — so the gateway forges a leaf for github.com (it verifies) and then dials a backend pod that does not exist. Measured: uv could not fetch a CPython the image lacked. codeload.github.com and raw.githubusercontent.com pass 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 ≥N and entry.capped is 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. Setting DAYTONA_SSH_KNOWN_HOSTS is the likely fix.
    • QUIC/HTTP3 and ECH are not intercepted. The gateway relays TCP; both are reported in the receipt's leaks rather 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.

    Similar plugins