Security Model
Honest boundary statement
Section titled “Honest boundary statement”jackin’ provides a container boundary today. That is a deliberate proof-of-concept choice, not the long-term ceiling.
Containers are where jackin’ starts because the team building it has years of operational experience with Docker, the mental model is widely understood, and a container boundary is the shortest path to a useful product the operator can actually feel — workspaces, mounts, isolated worktrees, parallel agents, all running locally without an additional virtualization layer to debug. The current version is mostly designed around that boundary because it is what we know how to make ergonomic now.
That doesn’t mean jackin’ has to stop there. Stronger boundaries — microVMs, hypervisor-backed sandboxes, and selectable backends — are explicit research items on the roadmap:
- Docker runtime hardening contract — keep the Docker-first model while making profiles, DinD hardening, resource budgets, network posture, and launch-contract reporting explicit.
- OrbStack isolated machine backend — research a macOS backend that preserves Docker workflows inside an OrbStack isolated machine with explicit shares.
- smolvm backend research — research an open Rust/libkrun VM backend for true VM-per-workload isolation, OCI-image execution, and network-off-by-default operation.
- Selectable sandbox backends — keep Docker, OrbStack, Kubernetes, and later true microVM providers under one backend-selection umbrella without forcing one boundary on every workload.
- Reproducibility & provenance pinning — tighten what gets into the image to begin with.
The plan is to keep iterating on UX, ergonomics, and operator workflows until jackin’ is no longer in the experimenting and figuring it out phase, and then research and add stronger isolation boundaries as a configurable option. We constantly look at adjacent projects (microVM-based agent sandboxes, devcontainer setups, remote sandbox products) and borrow good ideas — see Comparison with Alternatives for a current snapshot.
So the honest framing today is:
- jackin’ is useful when you trust the agent runtime itself but want to limit what it can touch on your machine.
- It is not the right tool to claim hypervisor-level isolation or to contain a fully hostile workload right now.
- If you need the hardest local isolation boundary available today, a microVM-based tool such as
Docker Sandboxesis stronger by design — and we treat those tools as references for what jackin’ should eventually offer as one of the selectable backends.
For the latest plan and current research items, the Roadmap is the source of truth.
Isolation layers
Section titled “Isolation layers”jackin’ security model is built on several layers that stack together:
1. Container isolation
Section titled “1. Container isolation”Each agent runs in its own Docker container with:
- a separate filesystem
- a separate process space
- a separate Docker network
The agent cannot see arbitrary host files unless you mount them.
2. Mount-based access control
Section titled “2. Mount-based access control”The agent can only access host paths you explicitly mount.
# Agent can read/write ~/Projects/my-app# Agent can read but not write ~/reference# Agent cannot see ~/.ssh, ~/.aws, ~/Documents, or other host pathsjackin load agent-smith ~/Projects/my-app --mount ~/reference:/reference:roThis is the core blast-radius control in jackin’.
3. Read-only enforcement
Section titled “3. Read-only enforcement”Mounts marked :ro are enforced by Docker and the kernel. The agent cannot write to those paths, regardless of any per-runtime permission flag.
4. Agent source trust
Section titled “4. Agent source trust”jackin’ uses a trust-on-first-use model for third-party agent sources.
Built-in agents (agent-smith, the-architect) are always trusted. When you load a third-party agent (a namespaced selector like org/agent-name) for the first time, jackin’ prompts you to review and confirm the source before building:
!! Untrusted agent source !!
agent: org/agent-name source: https://github.com/org/jackin-agent-name.git
jackin' has never loaded this agent before. Trusting it means: - Its Dockerfile will be executed during the image build - Arbitrary commands in that Dockerfile will run on your machine - The agent will have access to your mounted workspace filesOnce you confirm, jackin’ remembers the decision and subsequent loads of the same role proceed without prompting.
You can also manage trust from the command line:
# Pre-trust an agent without loading itjackin config trust grant chainargos/the-architect
# Revoke trust (next load will prompt again)jackin config trust revoke chainargos/the-architect
# List all trusted agentsjackin config trust listFor non-interactive environments (CI, automation), use jackin config trust grant <selector> ahead of the first load — that records the trust decision the same way the interactive prompt would.
Branch trust gate
Section titled “Branch trust gate”Loading a role from an unmerged branch (--role-branch) triggers a separate, always-on confirmation prompt — even if the role is already trusted.
The reason: trusting a role records that you reviewed the role’s default branch. An external contributor can open a pull request on that same role repository with a modified Dockerfile that runs arbitrary commands during the image build. If you loaded the branch without a prompt, you could unknowingly execute code you never reviewed, in a context where you expected the behaviour of the trusted default branch.
The branch trust gate shows the role, source repository, and branch name, then asks you to confirm that you have reviewed the diff:
!! Unreviewed branch — verify before proceeding !!
role: the-architect source: https://github.com/jackin-project/jackin-the-architect.git branch: feat/some-pr
This branch has not been merged to the default branch. Its Dockerfile and scripts may differ from the trusted main branch. A malicious contributor could introduce harmful code that runs on your machine during the image build.
Review the branch diff in the role repository before continuing.
Have you reviewed branch "feat/some-pr" and verified it is safe to build? [y/N]This gate cannot be bypassed: there is no --force-branch flag, no config option to skip it, and non-interactive terminals bail with an error rather than defaulting to yes.
The threat model entry for this is listed in the table below.
5. Per-agent network separation
Section titled “5. Per-agent network separation”Each runtime gets its own Docker network. The agent container and its DinD sidecar share that network, but different agents do not share a network by default.
This reduces accidental cross-agent interference.
What agents CAN do
Section titled “What agents CAN do”Inside their boundary, agents have broad autonomy:
- execute arbitrary shell commands
- install packages with
apt,npm,pip, and similar tools - build and run containers through their DinD sidecar
- modify files inside writable mounts
- access the internet
- use
ghif authenticated inside the runtime
What agents CANNOT do
Section titled “What agents CANNOT do”- access files that are not mounted
- modify files on read-only mounts
- directly access your host Docker daemon
- directly see another agent’s container or persisted state
These guarantees are useful, but they are not absolute in the face of a kernel escape or Docker escape vulnerability.
User mapping
Section titled “User mapping”jackin’ remaps the container user to your host UID and GID. That means:
- files created in mounted directories are owned by your host user
- you avoid common permission conflicts after the agent edits files
- the main runtime process does not run as root
What persists and what does not
Section titled “What persists and what does not”Each agent keeps its own session state — conversation history, OAuth credentials forwarded from your host, account metadata, and any tool logins (such as gh) you performed inside the container. That state survives stop/restart, so reconnecting to the same role picks up where it left off.
Several important things do not persist:
- the mutable root filesystem of the runtime container
- packages installed interactively into the running container
- containers and images the agent built inside its own private
Dockerdaemon during the session
This is a deliberate trade-off. jackin’ is biased toward reproducible agent images and explicit workspace mounts, not long-lived mutable sandboxes. Use jackin purge <role> to wipe all of an agent’s persisted state and start fresh.
Current implementation constraints
Section titled “Current implementation constraints”These are the parts of the security story that should be stated bluntly.
Privileged DinD sidecar
Section titled “Privileged DinD sidecar”jackin’ launches a privileged docker:dind sidecar per agent. The sidecar auto-generates TLS certificates at startup and shares client certs with the agent container via a Docker volume. The agent connects over TLS-authenticated TCP (DOCKER_HOST=tcp://...:2376 with DOCKER_TLS_VERIFY=1).
That keeps the agent away from your host Docker daemon and prevents unauthenticated access to the DinD daemon on the per-agent network. But it is still a privileged sidecar, and the auto-generated certificates are session-scoped (not pinned to an external CA).
Open outbound network by default
Section titled “Open outbound network by default”jackin’ does not currently provide per-agent allowlists, denylists, private-network blocking, or request logs for outbound traffic.
If an agent can read a mounted secret and the network path is open, jackin’ cannot stop exfiltration.
Direct bind mounts, not snapshots
Section titled “Direct bind mounts, not snapshots”Mounted files are live host files. If the agent edits a mounted path, the host file changes immediately. There is no snapshot or rollback layer between the runtime and the mount.
Authentication forwarding
Section titled “Authentication forwarding”By default, jackin’ forwards the host’s agent credentials (Claude Code, Codex, Amp, Kimi, OpenCode, and gh) into new agent containers (the sync auth-forward mode). This means agents start pre-authenticated, but it also means OAuth tokens and API keys land on disk inside the agent’s persisted state with owner-only file permissions.
--debug output never prints resolved token values. Command-level diagnostic lines show the command name and arguments; env-var values and command stdout that carry credentials are redacted or suppressed at the source before they reach the terminal.
You can control this behaviour at three scopes (global, per workspace, and per workspace × role × agent) — see Authentication Forwarding. Setting the mode to ignore revokes any previously forwarded credentials and falls back to manual in-container login. The api_key and oauth_token modes inject the credential into the container’s process env at launch only — no credential is written to disk inside the agent’s persisted state.
Credentials obtained inside the runtime remain inside the runtime
Section titled “Credentials obtained inside the runtime remain inside the runtime”Whether credentials were forwarded from the host or obtained via in-container login, they persist in jackin’ state until you purge it.
If you authenticate a tool like gh inside the runtime, that credential becomes readable by that runtime as well.
Threat model
Section titled “Threat model”jackin’ is designed to reduce the most common operator risks:
| Threat | Mitigation | Residual risk |
|---|---|---|
| Agent reads unrelated host files | Only mounted paths are visible | Sensitive data inside mounted paths is still readable |
| Agent edits system files | Runtime filesystem is isolated | Writable host mounts still change immediately |
| Agent reaches host Docker daemon | Agent uses its own DinD sidecar | DinD sidecar itself is privileged |
| Agent interferes with another agent | Separate containers, networks, and state | Shared host kernel remains a common lower layer |
| Agent persists random system changes | Runtime root filesystem is ephemeral | Persisted tool state and mounted files still survive |
| Typosquatted or malicious role repo | Trust-on-first-use prompt before building | User must review the source; trusted flag persists |
| Malicious code in an unmerged PR branch | Branch trust gate always fires for --role-branch | User must confirm they reviewed the branch diff; cannot be skipped |
What jackin’ does NOT protect against
Section titled “What jackin’ does NOT protect against”- Container escape vulnerabilities — containers share the host kernel
- Kernel-level exploits — there is no hypervisor boundary
- Network exfiltration — outbound access is open by default
- Credentials stored inside the runtime — if you log in inside the container, the agent can potentially read what that tool stores
- Host-file mistakes inside writable mounts — if you give an agent write access to a project, it can delete or rewrite that project
Best practices
Section titled “Best practices”- Mount the minimum necessary. Smaller visibility is the biggest win.
- Prefer read-only mounts. If the agent only needs to inspect something, mount it as
:ro. - Do not mount credential directories. Avoid
~/.ssh,~/.aws,~/.gnupg, and similar paths. jackin’ warns and asks for confirmation when it detects these, but avoiding them entirely is the safest approach. - Treat runtime login as sensitive. If you authenticate inside the runtime, assume the agent can read that state.
- Review role repos before loading. The Dockerfile defines what the runtime contains. jackin’ prompts for trust confirmation on first use of third-party agents, but you should inspect the repository yourself before confirming.
- Always review the branch diff before loading a role branch.
--role-branchalways prompts for confirmation, but the prompt is only as useful as your review. Open the pull request, check the Dockerfile and any scripts, and confirm only after you understand what changed. - Split roles by scope. Keep frontend, backend, infra, and review workflows in separate environments when appropriate.
- Use
jackin purgewhen a runtime no longer needs its persisted state. - Keep Docker updated. Container isolation is only as strong as the Docker runtime underneath it.