Manage Dependencies and Caching

Running a package install inside an untrusted sandbox creates a dilemma. The packages have to be downloaded, but giving an agent full Internet access risks data exfiltration or credential theft.

DynoCap solves this by pairing filtered network egress with lockfile-keyed dependency seeds. Each runtime has exactly one package manager. The runtime declares which registry hosts filtered egress may reach and how its package cache is keyed.

This guide shows you how to:

  1. Warm a dependency seed in a one-time sandbox whose egress is restricted to your runtime’s registry.
  2. Reuse that seed in later sandboxes that have no network device.
  3. Invalidate or refresh the seed when your lockfile changes.

Runtimes at a glance

Runtime Package manager Seed key (lockfile) Filtered egress allows Online install Offline install
node:24 npm package-lock.json registry.npmjs.org npm ci npm ci --offline
node:22 npm package-lock.json registry.npmjs.org npm ci npm ci --offline
python:3.14 uv uv.lock pypi.org, files.pythonhosted.org uv sync --frozen uv sync --offline --frozen
go:1.27 Go modules go.sum proxy.golang.org, sum.golang.org go mod download go mod download
shell:1 none no seed only a task repository URL’s host n/a n/a

When --runtime is omitted, the project’s files choose the runtime (package.json → node:24, pyproject.toml → python:3.14, go.mod → go:1.27); anything else gets shell:1. Prepare a runtime once per home before you use it, for example dynocap runtime prepare . --runtime python:3.14.

Some files change the seed key besides the lockfile: .npmrc for npm and uv.toml for uv. The prepared runtime’s digest is also part of the key, so a seed is never restored into a different runtime or version. uv uses the runtime’s Python and never downloads an interpreter. Go runs with GOTOOLCHAIN=local and never switches to another toolchain.


The workflow pattern: “deps” sandbox vs. “worker” sandbox

  1. Warm step: Clone into a sandbox named deps with --network filtered, run the online install, and finish. Finishing publishes the package cache as a seed.
  2. Offline step: Clone again with the default --network none. DynoCap restores the matching seed before your first command, and the offline install needs no network.
Text
+-------------------------------------------------------------+
| 1. WARM SEED (deps)                                         |
| dynocap clone ./project deps --runtime python:3.14 \        |
|   --network filtered                                        |
| dynocap exec deps -- uv sync --frozen                       |
| dynocap finish deps   ---> Saves a seed keyed by uv.lock    |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
| 2. OFFLINE TASKS (worker)                                   |
| dynocap clone ./project worker --runtime python:3.14        |
| dynocap exec worker -- uv sync --offline --frozen           |
| (Installs from the restored seed, zero network)             |
+-------------------------------------------------------------+

Step 1: Warm the dependency seed

Make sure your project contains its runtime’s lockfile, such as package-lock.json, uv.lock or go.sum. Then clone it with filtered networking:

Shell
dynocap clone ./my-project deps --runtime node:24 --network filtered

Why --network filtered? Under filtered mode, DynoCap connects a virtio-net interface whose host forwarder answers DNS only for the runtime’s registry hosts and, for a repository URL, that URL’s host. It forwards only HTTPS to those names. Every other address and port is dropped.

Run the online install:

Shell
dynocap exec deps --timeout 300 -- npm ci

Finish the sandbox:

Shell
dynocap finish deps

Finishing archives the runtime’s package cache (for example /work/npm-cache/_cacache, /work/uv-cache or /work/go/pkg/mod/cache/download) into ~/.dynocap/dependency-cache/<key>.tar on the host. A seed is only published if the lockfile is unchanged at finish. It is also skipped if any command pointed the package manager at another registry or cache, for example through npm_config_*, UV_INDEX*, GOPROXY or GOFLAGS, or a --registry or --index argument.

Clean up the finished record:

Shell
dynocap rm deps

Step 2: Run worker tasks completely offline

Clone your project again for your agent or test runner, leaving the network at its default (none):

Shell
dynocap clone ./my-project worker --runtime node:24

If the lockfile matches a seed, DynoCap unpacks it into the guest before clone returns. Run the offline install:

Shell
dynocap exec worker -- npm ci --offline

Then run your tests or let your coding agent work:

Shell
dynocap exec worker -- npm test

Which tasks publish a seed

A seed is the package cache a networked install produced, and later offline sandboxes trust it. DynoCap therefore publishes a seed only from a task where:

  • every command was one of the runtime’s install commands: npm ci or npm install for node, uv sync for python, go mod download for go (a plain sh -c "npm ci" from the SDK counts; anything with shell syntax does not);
  • no command pointed the package manager at another registry or cache.

A task that runs anything else, such as npm test, a shell, or a script, never publishes or republishes a seed, because that code could rewrite the cache. Keep the deps sandbox to the install alone.

Seeds are keyed by the task’s source (its directory or repository URL) as well as the lockfile, so a seed never crosses into another project.

A seed is only as trustworthy as the install that produced it: packages’ install scripts ran with network access. npm re-checks each package against the lockfile’s integrity hashes when it installs offline; uv and Go reuse their caches as they are.


Cache invalidation and updates

  • If you change dependencies: a lockfile change produces a different key, so later clones find no matching seed.
  • To update the seed: re-run Step 1 with --network filtered.
  • Location and size: seeds live in ~/.dynocap/dependency-cache/. They are capped at 2 GiB in total, and the least recently restored seeds are evicted first. You can inspect or delete them safely:
    Shell
    ls -lh ~/.dynocap/dependency-cache

Troubleshooting

A registry name does not resolve in a --network none sandbox

The sandbox has no network device. Use the offline form of the install: npm ci --offline, uv sync --offline --frozen, or go mod download after a seed was restored.

Download blocked during an install in filtered mode

Filtered egress allows HTTPS only to the hosts in the table above. Dependencies from private Git repositories, a custom registry (Artifactory, GitHub Packages, a private PyPI index) or a Go GOPROXY other than proxy.golang.org are blocked. In those cases use --network nat, which is unrestricted. A seed is still not published when a command overrides the registry.

This page is also plain Markdown, for agents and for you.