AgentDock

Documentation

Security model

The threats Arcotheon considers, what is enforced and tested today, and exactly what is not.

This page says what the reference implementation (SDK and CLI 0.1) actually enforces. Where something is declared but not enforced, it says so. "Tested" means an automated test exercises it, run locally with npm test.

Threats considered#

ThreatDefenceStatus
Tampered download or mirrorArchive SHA-256 and size pinned in a signed catalogue; per-file hashes and the manifest digest checked before extractionEnforced, tested
Forged or substituted packageEd25519 publisher signature over the manifest digest; publisher keys come from the signed catalogueEnforced, tested
Compromised registry hostCatalogue signed by a root key pinned in the client; package documents pinned by hashEnforced, tested
Stale or older catalogue replayed (rollback)Monotonic sequence, remembered across runsEnforced, tested
Frozen catalogue (freeze attack)Catalogue expires; an online client refuses an expired one; offline use warnsEnforced, tested. Needs the maintainers to re-sign before expiry (registry build --refresh)
Compromised publisher keyPer-key retired / revoked status; revoked keys invalidate what they signed; whole-publisher suspendedEnforced, tested
Compromised registry root keyRotation: a catalogue signed by a trusted root may add roots and revoke roots; clients remember itEnforced, tested. A client that never saw the new root must update its pins
Hostile archives: ../, absolute paths, symlinks, hard links, duplicates, decompression bombsStrict tar reader, regular files only, entry and size limitsEnforced, tested
Code running at install or browse timeThe format has no scripts or hooksBy construction, tested
A tool doing more than it declaredPer-tool permissions plus the OS sandbox belowEnforced on Linux with bubblewrap, tested; refused elsewhere
An update (or a dependency update) widening permissionsGrants stored per package; any new permission needs a new approvalEnforced, tested
A bad version already installedrevoked status refuses installs and refuses to run; doctor reports itEnforced, tested
Local tampering after installEvery run re-hashes the installed filesEnforced, tested
Corrupt or poisoned large resourcesSHA-256 of the whole file, shard-by-shard chain, resumed downloads re-verifiedEnforced, tested
Entitled content fetched without entitlementHost authoriser; reference gateway issues short-lived, content-bound, revocable grantsClient side and gateway logic tested; no production entitlement service exists

The tool sandbox#

Tools run through runTool. Before starting a process it checks integrity, revocation and suspension, granted permissions and the input schema. There is no unrestricted fallback: where no enforcing sandbox is available the tool does not run (there are no allowUnsandboxed or partial-sandbox options). On Linux with bubblewrap:

  • Namespaces: user, PID, IPC, UTS, network and cgroup unshared; --die-with-parent --new-session.
  • Syscalls (seccomp-BPF): a filter assembled by Arcotheon denies ptrace, mount, umount2, pivot_root, chroot, unshare, setns, bpf, perf_event_open, io_uring_*, userfaultfd, keyctl, process_vm_*, module loading, reboot, clock and swap calls and others; clone with any CLONE_NEW* flag; and clone3 (libc falls back to clone). Sockets are limited to AF_UNIX, AF_INET and AF_INET6. Other ABIs are killed, and architectures without a table (anything but x86_64 and aarch64) refuse to start. It is a denylist: an allowlist would break arbitrary tools.
  • Filesystem: only system libraries (read-only), the capability's own directory and its dependencies (read-only), the verified-resource cache (read-only, if it declared resources) and the workspace read-only or read-write if it holds that permission. Other capabilities and the Arcotheon state do not exist for it.
  • Executables: /usr/bin, /usr/sbin, /usr/local/bin, /usr/libexec are replaced by empty directories; a tool can start only its interpreter and the programs process:spawn names. Holds for Node, Python and native tools.
  • Network: none. A tool with net:connect has no direct route either: the only way out is a unix socket to a host-side filtering proxy, bridged to loopback inside. The proxy permits only declared hosts and ports (no port means 80 and 443), resolves names itself and dials the address it vetted, refuses names that resolve to private, loopback, link-local, unique-local or IPv4-mapped-private addresses unless the rule names that literal address (defeating DNS rebinding), and supports IPv6 literals. Ignoring the proxy settings gets nothing: tests show direct IPv4, IPv6, private ranges, the metadata address and DNS all fail, and the only interface is an empty loopback.
  • Limits: memory, swap and task ceilings through a transient cgroup-v2 scope (systemd-run --user) when the user session supports it; otherwise RLIMIT_DATA (with a 1 GiB floor, because Node aborts below about 768 MiB under it) and a process ceiling relative to the user's current thread count; ARCOTHEON_DISABLE_CGROUP=1 forces the fallback. CPU time, file size and open files via prlimit; wall-clock timeout; output cap. Tests: a tool allocating past its ceiling dies, a fork attempt stops at the ceiling.
  • Termination and cleanup: the whole process group is killed on timeout, output overflow, abort and crash; scratch directories and proxy sockets are removed (also on process exit and SIGINT/SIGTERM). Tests show no stray processes or directories after a timeout with background children.
  • Environment: empty apart from env:read names. Secrets: only to tools holding secrets:<name>, if the host supplies them.
  • Every result reports what was enforced and by which mechanism.

What each operating system gives you#

Linux (bwrap + userns)macOSWindows
Install, verify, resolve, updateyesyesyes
Run toolsenforced (above)refused unless ARCOTHEON_EXPERIMENTAL_SEATBELT=1refused
Native backendbubblewrapSeatbelt profile (sandbox-exec): written, not validated on a real Macnone
  • macOS: the Seatbelt backend generates a deny-by-default profile (paths, executables, no network) and is implemented but has never run on a Mac; it is off by default. When on, it has no network allowlist (tools needing net:connect are refused), and no memory or process limits (reported as none).
  • Windows: there is no native backend. AppContainer plus a job object needs a native helper (for example a small Rust program calling CreateAppContainerProfile, CreateProcess with a restricted token and job limits) that was neither built nor tested. Run Arcotheon inside WSL2 with bubblewrap, where the Linux guarantees apply. explainNoSandbox() tells the user exactly this.
  • If host-level enforcement needs a kernel feature that is missing (user namespaces, cgroup v2 delegation, prlimit), the tool fails closed with the specific reason, or (for memory/process limits only) uses the weaker fallback and reports it.

Known limitations#

  • A denylist seccomp filter and namespaces are strong but not perfect: a kernel or bubblewrap vulnerability could still allow escape. This is defence in depth against honest mistakes and ordinary malware, not a guarantee against a kernel exploit.
  • A capability with net:connect and fs:read workspace can read the workspace and send it to its allowed hosts. That is what the permission means; the prompt says so. The proxy filters by name and port, not content (no TLS inspection).
  • gpu is not provided to tools.
  • Whoever can write to state.json can change grants; protect the store directory.

Supply chain of the registry#

  • Git is the source of truth. CODEOWNERS routes registry/publishers, registry/status and the pinned root keys to the maintainers; GitHub branch protection for main must be turned on by an owner (it has not been configured here).
  • Entries are immutable: the build fails if a published version's content changes.
  • Keys. Signing keys may live in a file (mode 0600), an environment variable, or outside this process: with --signer-cmd and --key-id Arcotheon hands the message to a command (an HSM or KMS wrapper) and never sees the private key. The keys used for the sample registry are development keys kept outside the repository. No production trust root has been created. The procedure is in production signing.
  • Publisher identity. A publisher can be marked trusted only with a recorded identity verification (who checked and when); the build rejects it otherwise. Sarja's record is "first-party", since Sarja operates the registry.
  • CI: the workflow is manual and read-only and has not been run. Nothing is published from CI.
  • The directory at agdc.dev serves the same files and the CLI re-verifies signatures on the client, so the website is not a trust anchor.

Reporting#

Report vulnerabilities privately through GitHub security advisories on the repository.