# Security Implementation (https://jackin.tailrocks.com/reference/security-implementation/)



This document describes jackin's security implementation from a contributor perspective. It covers the threat model, the specific functions that enforce security invariants, the external tools bundled in the construct image, and the high-autonomy launch flags used for all five agent runtimes.

## Threat model [#threat-model]

| Threat                           | Defended?                | Where in code                                                                                                                                                                                     | Residual risk                                                                        |
| -------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Agent reads/writes files on host | Yes                      | Mount scoping in <RepoFile path="crates/jackin-config/src/mounts.rs">crates/jackin-config/src/mounts.rs</RepoFile>                                                                                | Explicitly mounted rw paths                                                          |
| Credential theft via symlink     | Yes                      | `reject_symlink` in <RepoFile path="crates/jackin-runtime/src/instance/auth.rs">crates/jackin-runtime/src/instance/auth.rs</RepoFile>                                                             | TOCTOU window between symlink check and file use                                     |
| Container escape                 | Out of scope             | Docker hardening tracked in [Docker runtime hardening contract](/reference/roadmap/docker-runtime-hardening-contract/) and [smolvm backend research](/reference/roadmap/smolvm-backend-research/) | Shared kernel; future microVM work addresses this                                    |
| Agent exfiltration over network  | Needs policy             | Tracked in [Network egress policy](/reference/roadmap/network-egress-policy/)                                                                                                                     | Open egress by default today                                                         |
| Supply chain (capsule binary)    | SHA256 + signed manifest | <RepoFile path="crates/jackin-image/src/capsule_binary.rs">crates/jackin-image/src/capsule\_binary.rs</RepoFile>                                                                                  | See [signed releases](/reference/roadmap/security-threat-model-and-signed-releases/) |

## Security functions [#security-functions]

All six functions live in <RepoFile path="crates/jackin-runtime/src/instance/auth.rs">crates/jackin-runtime/src/instance/auth.rs</RepoFile> and are called during credential provisioning at every `jackin load` / `jackin console` launch.

### `reject_symlink` (line 925) [#reject_symlink-line-925]

Uses `symlink_metadata` (lstat, not stat — the call does not follow the symlink) to detect symlinks at `path` before any write or chmod. An agent-controlled role-state directory is mounted read-write into the container; a compromised role can plant a symlink between launches to redirect credential writes to arbitrary host paths. `reject_symlink` is called unconditionally before every write path and at the top of every `provision_*` function. No-ops on `ENOENT`. Returns an operator-readable error naming the path and suggesting the operator remove the symlink and retry.

### `repair_permissions` (line 1012) [#repair_permissions-line-1012]

Tightens existing credential files to `0o600` on Unix via `set_permissions`. Uses `symlink_metadata` (lstat) before calling `set_permissions` so it never chmods through a symlink. Errors (permission denied, stat failure) are logged via `eprintln!` rather than returned — callers are mid-Sync and must not abort the launch, but a silent chmod failure on a credential file is a security regression that must surface.

### `write_private_bytes` / `write_private_file` (lines 951, 947) [#write_private_bytes--write_private_file-lines-951-947]

Atomically writes bytes with `0o600` permissions. Calls `reject_symlink` at entry. Uses `tempfile::NamedTempFile` (opens with `O_EXCL` internally, so a pre-planted symlink at the temp path is impossible because `O_EXCL` fails on any existing path) with a random suffix, sets permissions on the temp file before rename. Closes the TOCTOU window completely: no intermediate state where a world-readable file exists at the destination path.

### `create_private_file_if_absent` (line 986) [#create_private_file_if_absent-line-986]

Race-free skeleton seeding via `O_CREAT|O_EXCL`. Used to seed `{}` into `account_json` before the Claude CLI runs so the downstream consumer never sees a missing file. Returns `Ok(())` on `EEXIST` without touching existing content. The `O_EXCL` flag prevents any race between a missing-file check and the create.

### `host_home_is_real` (line 231) [#host_home_is_real-line-231]

Gates host-binary shellouts (`gh auth token`, macOS `security` keychain) on whether `host_home` matches the value returned by `directories::BaseDirs::new().home_dir()`. Keeps integration tests hermetic (they pass temp dirs as `host_home`) while allowing the real `gh` and `security` binaries in production runs. Without this gate, tests would shell out to the operator's installed `gh` and contaminate results.

### `parse_gh_hosts_yml` (line 346) [#parse_gh_hosts_yml-line-346]

Uses `serde_yaml_ng` to parse `~/.config/gh/hosts.yml`. Returns `None` on any malformed input, including a document that has a `github.com` block but no `oauth_token` field, or one where `oauth_token` is empty/whitespace. Partial results are never returned: silently accepting half-parsed scalars would land bogus credentials in the container's `hosts.yml` and surface as unrelated authentication failures mid-session.

## External security tools [#external-security-tools]

Both tools are installed in the construct image and active in every container session by default.

### shellfirm [#shellfirm]

Intercepts dangerous shell commands at the shell level before execution, blocking `rm -rf /`, `chmod 777`, `curl | bash`, and similar patterns. CI prepares the pinned binary before Docker Buildx runs, stages it at `docker/construct/prebuilt/shellfirm`, and <RepoFile path="docker/construct/Dockerfile">docker/construct/Dockerfile</RepoFile> copies that binary into the image at line 5. Shell hook wired in <RepoFile path="docker/construct/zshrc">docker/construct/zshrc</RepoFile> and <RepoFile path="docker/construct/fish-config.fish">docker/construct/fish-config.fish</RepoFile>.

Protects against both accidental operator mistakes and agent-generated destructive commands. Disable per-session by setting `JACKIN_DISABLE_SHELLFIRM=1` in the container environment.

### tirith [#tirith]

Security scanner that validates container runtime state against a policy. Guards against configuration drift and unexpected privilege escalation during a session. Downloaded in <RepoFile path="docker/construct/Dockerfile">docker/construct/Dockerfile</RepoFile> at line 61 via a SHA256-verified curl + `tar` extraction. Shell hook wired in the same shell config files as shellfirm.

Disable per-session by setting `JACKIN_DISABLE_TIRITH=1` in the container environment.

## High-autonomy launch flags [#high-autonomy-launch-flags]

jackin' launches each agent with flags that remove tool-approval prompts. These are defined in <RepoFile path="docker/runtime/entrypoint.sh">docker/runtime/entrypoint.sh</RepoFile> and applied at container start before the agent binary runs.

| Agent    | Configuration                                                                                                                                                 | Where                                                                                                                        |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Claude   | `--dangerously-skip-permissions` CLI flag + `skipDangerousModePermissionPrompt: true` in settings JSON                                                        | line 41                                                                                                                      |
| Codex    | `--dangerously-bypass-approvals-and-sandbox` CLI flag                                                                                                         | line 47                                                                                                                      |
| Amp      | `--dangerously-allow-all` CLI flag                                                                                                                            | line 53                                                                                                                      |
| Kimi     | `--yolo` CLI flag (auto-approves all tool actions)                                                                                                            | line 58                                                                                                                      |
| OpenCode | `OPENCODE_CONFIG_CONTENT='{"permission":"allow"}'` env var + `~/.config/opencode/opencode.json` written with `"permission": "allow"` by capsule runtime setup | line 64 + <RepoFile path="crates/jackin-capsule/src/runtime_setup.rs">crates/jackin-capsule/src/runtime\_setup.rs</RepoFile> |

When any of these configurations are active: jackin' still enforces mount scoping, network isolation, and the host Docker socket exclusion. The flags remove tool-approval prompts but do not remove auth prompts, user questions, plan confirmation, model/account issues, runtime updates, or terminal-level failures. Operators who need the approval prompts restored can rebuild the derived image without the high-autonomy flags by customising the entrypoint hook.

## Container boundary contract [#container-boundary-contract]

The operator is responsible for what they mount. Explicitly mounted read-write paths are accessible to the agent — this is intentional and documented in the [Mounts guide](/guides/mounts/). The security model defends against the agent escaping the defined mount scope, not against the agent using what the operator chose to expose.

The YOLO flags above reduce friction at the cost of prompt-level damage prevention. The remaining guarantees (mount scoping, network isolation, no host Docker socket) hold regardless of flag state.
