Skip to content

rnorth/tsuba

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tsuba logo

tsuba

Run any command inside an ephemeral Docker container, with optional egress filtering.

$ tsuba -- pi

The supplied command runs inside a one-shot Docker container built from tsuba's curated Ubuntu base (git, gh, docker CLI, jq, ripgrep, build tools, mise, …). The working directory is mounted at the same absolute path, the container runs as your host user (non-root, matching UID/GID), and outbound HTTP/HTTPS is filtered through a mitmproxy sidecar according to a policy in your config file.

Philosophy

Containment, not just isolation. Every filesystem operation, every shell command, every subprocess your program spawns happens inside a container that is created on invocation and destroyed when the program exits.

Fail-closed network. A config with no networkPolicies means no outbound HTTP/HTTPS, not "everything allowed." tsuba warns you at startup so you can add policies if you want them.

Generic. tsuba wraps any program; there is no special integration with the wrapped tool.

Quick start

git clone https://github.com/rnorth/tsuba ~/code/tsuba
cd ~/code/tsuba/tsuba
npm install
npm run build
npm link    # installs `tsuba` globally

mkdir -p ~/.config/tsuba
cat > ~/.config/tsuba/config.yaml <<'YAML'
networkPolicies:
  - host: api.github.com
    policies:
      - action: ALLOW
        path: /repos/.*
        method: GET
YAML

tsuba -- pi

On first run, tsuba builds its curated base image — takes a few minutes, then it's cached for every subsequent invocation.

Language runtimes

The curated image ships mise but no language runtimes. Keep your project's runtime versions in a checked-in .mise.toml and bring them in at the start of each tsuba session:

# .mise.toml
[tools]
node = "22"
python = "3.12"
tsuba -- bash -c "mise install && pi"

Container teardown is total — mise install results don't persist across invocations.

Configuration

~/.config/tsuba/config.yaml:

image: <docker image>          # optional; defaults to tsuba's curated base image
networkPolicies:               # optional; missing/empty == default-deny
  - host: <hostname>
    policies:
      - action: ALLOW | DENY
        path: <regex>          # Python re.fullmatch against the request path
        method: GET | POST | ... | "*"

See docs/container-image.md for the curated tool set, mise integration, and the bring-your-own-base contract; docs/container-configuration.md for config file details.

See docs/egress-control.md for the full policy semantics and limitations.

Development

Two independent modules:

tsuba/
├── tsuba/    # The CLI (Node/TS) — run `npm test` from inside
└── proxy/      # The mitmproxy egress sidecar (Python + Docker image)

See docs/architecture.md for an overview and links to the feature docs.

cd tsuba && npm test           # CLI + integration tests
sh scripts/test-proxy.sh         # proxy tests (Python/pytest)

About

Run any command inside an ephemeral Docker container, with optional egress filtering

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages