Security¶
Purpose¶
This document describes AKC’s defense-in-depth security model: tenant isolation, capability boundaries, and where enforcement is hard vs best-effort. It spans the Python akc package (CLI, compile loop, runtime) and the Rust crates (akc_executor, akc_ingest, akc_protocol).
Related docs
- runtime-execution.md — runtime routing (
subprocess,http, coordination), adapter behavior, and policy gates forakc runtime. - oss-security-requirements.md — CI/release security and correctness gates, verifier, and evidence expectations.
- architecture.md — end-to-end system shape.
Code map (where to read this in the repo)¶
| Area | Primary locations |
|---|---|
| Compile-time sandbox selection | src/akc/execute/factory.py (create_sandbox_executor), src/akc/execute/strong.py, src/akc/execute/dev.py |
| Python executors | src/akc/compile/executors.py (SubprocessExecutor, DockerExecutor) |
| Rust bridge / protocol payloads | src/akc/compile/rust_bridge.py, src/akc/compile/execute/rust_executor.py (RustExecutor) |
| Tenant-scoped secrets helper | src/akc/execute/secrets.py (SecretsScopeConfig) |
| Rust executor implementation | rust/crates/akc_executor/ (bin akc-exec), rust/crates/akc_protocol/ |
| Rust ingestion CLI | rust/crates/akc_ingest/ (bin akc-ingest) |
| MCP ingest client (optional) | src/akc/ingest/connectors/mcp/ (ingest-mcp extra; stdio subprocess + streamable HTTP) |
| Runtime HTTP + subprocess policy | src/akc/runtime/http_execute.py, src/akc/runtime/adapters/local_depth.py, src/akc/runtime/action_routing.py |
Status: enforced vs best-effort¶
AKC uses multiple isolation layers depending on lane, sandbox mode, and platform. This document is explicit about what is:
- Enforced: AKC rejects the request or the OS/runtime prevents the action.
- Best-effort: AKC attempts containment/limits, but the host OS may not strictly enforce it.
Assets and trust boundaries¶
AKC treats these as untrusted inputs:
- Generated or externally supplied artifacts executed during the Plan → Generate → Execute → Repair loop
- Ingested documents/messages/APIs that may contain malicious payloads
- Runtime bundles, IR, and operational contracts that drive execution routing (policy must gate mutating or subprocess behavior)
AKC treats these as trusted (when authenticated/authorized by the caller):
- Tenant identity and run metadata (
tenant_id,run_id,repo_idwhere applicable) - The policy layer that evaluates limits and allowlists before execution
- The per-tenant filesystem namespace and runtime configuration
Threat model (high level)¶
The main security goals are:
- Prevent escape from the execution sandbox to the host filesystem or host environment (strength depends on lane; see compile vs runtime sections).
- Prevent cross-tenant data access (no cross-tenant reads/writes, caching, or indexing).
- Constrain resource usage (CPU time, memory, wall-clock time, stdout/stderr size).
We assume the attacker can:
- Provide malicious inputs to ingestion and execution
- Attempt path traversal, symlink attacks, and environment manipulation within the sandbox
- Attempt denial-of-service (resource exhaustion) or data exfiltration
Tenant isolation guarantees (hard requirement)¶
Every execution request must include:
tenant_id(string; Rust bridge additionally restricts characters to alphanumerics,-,_)run_id(uuid-like or equivalent; validated for safe directory names in Python executors)
Workspace layout (Python executors)
Subprocess and Docker executors namespace work under a configurable work_root:
- Scope directory:
{work_root}/{tenant_id}/{repo_id}/ - Optional per-run directory when
run_idis set:{work_root}/{tenant_id}/{repo_id}/{run_id}/
For akc compile, work_root defaults to the project outputs base: <outputs_root>/<tenant_id>/<repo_id>/ (see src/akc/cli/compile.py), not a fictional ./.akc/tenants/... path. If you override --work-root, that path becomes the root for executor namespacing.
No cross-tenant caching:
- extracted docs/messages
- embeddings/index shards
- build artifacts
- WASM modules (unless keyed by tenant_id + a content hash)
Policy evaluation happens before execution/ingestion:
- Network is denied in Rust
akc-exec: requests that setcapabilities.network=trueare rejected (network_capability_denied). The Python compile/runtime surfaces may expose separate “allow network” flags for non-Rust paths; they do not override Rust’s hard denial insideakc-exec. - Command execution in the Rust process lane is allowlisted (via
AKC_EXEC_ALLOWLIST; default is extremely restrictive). The PythonSubprocessExecutorused in--sandbox devdoes not use this allowlist; it relies on controller-chosen commands plus cwd/env/output limits (see Compile loop: execution surfaces). - Filesystem policy is validated in Rust; enforcement strength depends on backend/platform (see process lane sections).
- Resource limits are applied, but memory enforcement is best-effort outside Linux cgroups/Windows Jobs.
Capability system¶
Execution and ingestion APIs use an explicit capability model:
- The caller provides what is allowed (inputs, mounts, limits).
- The Rust executor enforces what is actually accessible by default denying:
- network (enforced:
capabilities.network=trueis always rejected inakc-exec) - dangerous environment variable injection (enforced in Rust: denylist, with optional prefix allowlist via
AKC_EXEC_ENV_ALLOW_PREFIXES) - ambient filesystem access (enforced on Linux
bwrapbackend; best-effort on native/macOS)
Capabilities must be:
- logged by correlation id (tenant/run) without logging sensitive payloads
- validated before any process starts or any untrusted code runs
Secrets scoping (per-tenant)¶
AKC supports tenant-scoped secrets injection into the execution environment via SecretsScopeConfig (src/akc/execute/secrets.py).
The injector follows a configurable host-env convention (defaults shown):
- Host environment variables expected:
AKC_SECRET_{tenant_id}_{secret_name} - Secrets injected into the child environment as:
AKC_SECRET_{secret_name}
Only secrets whose tenant_id matches the active request scope are injected; other tenants’ keys are ignored. Optional allowed_secret_names restricts which names may be passed through (empty tuple means “any name allowed for that tenant prefix”).
The akc compile CLI currently wires secrets_scope=None; programmatic use via SandboxFactoryConfig / StrongSandboxExecutor can supply a SecretsScopeConfig. Treat injected values as sensitive (do not log raw secrets).
Compile loop: execution surfaces¶
akc compile chooses an Executor implementation as follows (see src/akc/cli/compile.py):
-
--use-rust-exec
UsesRustExecutordirectly with--rust-exec-lane process|wasmand--rust-exec-mode cli|pyo3. This bypassescreate_sandbox_executorand talks toakc-exec(or theakc_rustextension) for both process and WASM lanes. The CLI may setallow_networkon the Rust config from--rust-allow-networkor--sandbox-allow-network, butakc-execstill hard-deniescapabilities.network=true(policy denial). Use Docker or dev subprocess paths if you need the Python-side network toggle to take effect. -
Otherwise —
create_sandbox_executor(src/akc/execute/factory.py): --sandbox dev(default) —DevSandboxExecutor→SubprocessExecutor: Pythonsubprocesswith tenant-scoped cwd underwork_root, sanitized env, best-effort network hygiene (proxy env cleared when network is disabled), rlimits, and stdout/stderr caps. Not a Linux mount namespace; not RustAKC_EXEC_ALLOWLIST.--sandbox strong—StrongSandboxExecutorwrapping either:DockerExecutor(Python, invokes thedockerCLI): default for--strong-lane-preference docker, or forautowhen Docker is on PATH. This is the “strong Docker lane” described below.RustExecutorwith lanewasm: for--strong-lane-preference wasm, or forautowhen Docker is unavailable but the Rust surface is present.
Strong lane defaults (memory, stdout/stderr caps, Docker flags) are aligned with SandboxStrongConfig in src/akc/execute/strong.py and CLI defaults in src/akc/cli/compile.py.
Execution sandbox: two lanes (defense-in-depth)¶
AKC’s Rust executor supports two isolation strategies and chooses per task. The Python compile flow may use Docker (strong) or Rust WASM / Rust process (--use-rust-exec) instead of the dev subprocess path.
Strong Docker lane (default for CLI --sandbox strong)¶
The default strong lane in the CLI is Docker via DockerExecutor, not the Rust process backend. When Docker is selected, AKC assembles docker run with the following defaults:
--network none--memory 1073741824(--sandbox-memory-mb 1024)--pids-limit 256(--docker-pids-limit 256)--user 65532:65532unless--docker-useris supplied--tmpfs /tmpunless one or more--docker-tmpfsflags are supplied--read-only--security-opt no-new-privileges--cap-drop ALL- stdout/stderr capture capped at
2048 KiBeach (--sandbox-stdout-max-kb,--sandbox-stderr-max-kb)
Optional Docker hardening flags exposed by the CLI:
--docker-user--docker-tmpfs(repeatable)--docker-seccomp-profile--docker-apparmor-profile--docker-ulimit-nofile--docker-ulimit-nproc--docker-cpus
Docker hardening preflight is fail-closed when Docker-specific controls are requested. AKC rejects the run before launch when:
- Docker-only flags are used outside
--sandbox strong --strong-lane-preference wasmis selected with Docker-only flags--strong-lane-preference autowould fall back away from Docker while Docker-only hardening is configured- tmpfs, user, seccomp/AppArmor identifiers, or ulimits are malformed
- an absolute
--docker-seccomp-profilepath does not exist or is not a file --docker-apparmor-profileis requested on a host without AppArmor support
Docker strong guarantees by host/runtime:
| Scope | Enforced by AKC | Enforced by runtime | Best-effort / platform notes |
|---|---|---|---|
| Lane selection | Docker-specific hardening is rejected unless Docker strong can actually apply it | N/A | auto is only acceptable when no Docker-specific control would be dropped |
| Command shape | AKC validates and assembles --user, --tmpfs, --security-opt, --ulimit, --read-only, --cap-drop ALL deterministically |
Docker receives the exact flags AKC emits | None once launch begins |
| Network | --sandbox-allow-network defaults to deny |
--network none blocks container egress |
Any allowed-network exception must come from policy/config, not fallback behavior |
| Rootfs and temp writes | --read-only and tmpfs mounts are requested by default |
Docker enforces read-only rootfs and writable tmpfs paths | Runtime semantics depend on the container runtime's Linux kernel boundary |
| Privilege reduction | Non-root user default, no-new-privileges, cap-drop ALL |
Docker/kernel enforce the configured user and privilege flags | Images that assume root may fail to start; this is expected, not a silent downgrade |
| Seccomp | AKC validates identifier syntax and absolute-path existence | Docker applies the default profile or the configured seccomp=... profile |
Effective syscall filtering is runtime-defined; verify on the target Docker Engine/kernel |
| AppArmor | AKC fails closed if profile is requested on an unsupported host | Docker applies apparmor=... only on Linux hosts with AppArmor enabled |
Not portable to Docker Desktop/macOS/Windows hosts |
| Resource limits | AKC validates requested values | Docker applies memory, PID, CPU, and ulimit controls | Exact enforcement quality depends on the host runtime/cgroup environment |
| Output caps | AKC captures stdout/stderr with configured byte caps | N/A | Large output can still consume runtime resources before capture is truncated |
Operational guidance:
- Prefer
--strong-lane-preference dockerfor production hardening. It fails closed when Docker is unavailable. - Use
--strong-lane-preference autoonly during migration when WASM fallback is acceptable and no Docker-specific hardening flag is required. - Prefer Linux Docker Engine for production attestations. Docker Desktop on macOS/Windows still receives the same flags, but enforcement happens inside the Linux VM and AppArmor is generally unavailable.
Lane A: WASM execution (portable, capability-based)¶
When feasible, generated guest code runs as WebAssembly using:
- Wasmtime
- WASI Preview 1 (preview1)
The host defines a narrow interface for:
- passing input blobs
- returning structured results
- emitting events/log records
Default restrictions:
- filesystem is denied (no preopened directories)
- network is denied (no host networking exposed)
WASM filesystem contract:
- explicit preopens are required for any guest filesystem access
- no implicit host filesystem exposure is allowed
allowed_read_pathsis rejected for WASM lane (usepreopen_dirs)allowed_write_pathsmust be an explicit subset ofpreopen_dirs- top-level
akc compileexposes WASM filesystem flags: --wasm-preopen-dir <ABS_DIR>(repeatable)--wasm-allow-write-dir <ABS_DIR>(repeatable, subset of preopens)- these flags require explicit WASM lane selection and are not applied to docker/process lanes
WASM path-normalization controls (CLI):
--wasm-fs-normalize-existing-pathscanonicalizes existingpreopen_dirsandallowed_write_pathsbefore subset validation and request emission.--wasm-fs-normalization-profile strict|relaxedcontrols unresolved paths:strict(default): fail closed on missing/unresolvable paths.relaxed: keep unresolved paths as-is and defer final enforcement to Rust runtime.
Recommended profile guidance:
- production / policy-enforced runs: use normalization with strict profile.
- Example:
akc compile --sandbox strong --strong-lane-preference wasm --wasm-fs-normalize-existing-paths --wasm-fs-normalization-profile strict ... - developer convenience / migration: use normalization with relaxed profile when path aliases or symlink spellings are common and strict canonicalization would cause friction.
- Example:
akc compile --sandbox strong --strong-lane-preference wasm --wasm-fs-normalize-existing-paths --wasm-fs-normalization-profile relaxed ...
Limits enforced by the host:
- wall-clock timeout: elapsed-time deadline via Wasmtime epoch interruption on supported platforms
- CPU budgets: deterministic fuel cap from
cpu_fuelwhen provided - maximum stdout/stderr bytes: in-memory capped pipes
- memory limits: explicit Wasmtime linear memory cap when
memory_bytesis provided
Deterministic WASM limits contract:
wall_time_ms:- when set, AKC arms a real elapsed-time deadline using Wasmtime epoch interruption.
- on timeout, execution traps with
WASM_TIMEOUTinstead of being inferred from fuel exhaustion. - Windows remains fail-closed for strict/prod compile runs because this guarantee is not supported there.
cpu_fuel:- when set, applied as an explicit fuel budget with deterministic clamp:
cpu_fuel_budget = clamp(cpu_fuel, 1, 2_000_000_000)
cpu_fuelis independent ofwall_time_ms; when both are set, the first boundary reached wins:- elapsed-time deadline ->
WASM_TIMEOUT - CPU/fuel budget ->
WASM_CPU_FUEL_EXHAUSTED
- elapsed-time deadline ->
- this keeps timeout and CPU exhaustion policy-friendly and unambiguous.
memory_bytes:- when set, applied as a Wasmtime store linear-memory limit (fail-closed on limit exceed).
- when unset, no explicit linear-memory cap is requested by AKC.
stdout_max_bytes/stderr_max_bytes:- each stream is captured with a deterministic in-memory cap.
- default per-stream cap is
1 MiBwhen not specified (Rust constantWASM_DEFAULT_STDIO_MAX_BYTES).
Deterministic WASM error semantics (stable for policy/tests):
- WASM lane emits a machine-readable first stderr line:
AKC_WASM_ERROR code=<CODE> exit_code=<N> message=<TEXT>- Stable error codes and exit codes:
- timeout:
WASM_TIMEOUT-> exit code124 - cpu/fuel exhaustion:
WASM_CPU_FUEL_EXHAUSTED-> exit code137 - memory limit exceeded:
WASM_MEMORY_LIMIT_EXCEEDED-> exit code138 - unsupported platform capability:
WASM_UNSUPPORTED_PLATFORM_CAPABILITY-> exit code78 - Windows note:
- wall-time limit requests in WASM lane are currently unsupported and return
WASM_UNSUPPORTED_PLATFORM_CAPABILITYwith exit code78instead of degrading.
WASM platform capability matrix:
| Platform | Supported guarantees | Unsupported controls | Remediation guidance |
|---|---|---|---|
| Linux | Engine availability probe, capability-based preopens, deterministic wall-time and CPU fuel budgeting, linear memory cap, stdout/stderr caps | None beyond the lane-wide contract (allowed_read_paths is unsupported for WASM) |
Preferred target for strict/prod WASM runs |
| macOS | Engine availability probe, capability-based preopens, deterministic wall-time and CPU fuel budgeting, linear memory cap, stdout/stderr caps | None beyond the lane-wide contract (allowed_read_paths is unsupported for WASM) |
Supported for strict/prod WASM runs when the Rust surface is installed |
| Windows | Engine availability probe, capability-based preopens, CPU fuel budgeting, linear memory cap, stdout/stderr caps | Wall-time enforcement for WASM compile/test stages | Use Linux/macOS for strict/prod WASM runs, or switch to the process/Docker lane |
WASM preflight behavior:
- Compile performs a preflight before execution when WASM is explicitly requested or selected.
- Engine availability is checked first:
- missing
akc-exec/akc_rustfails fast with remediation instead of falling through to a later runtime failure. - In enforced/strict profiles, unsupported platform guarantees fail closed:
- Windows + WASM compile runs are rejected up front because compile stages require bounded wall-time execution.
- Outside enforced/strict profiles, AKC still returns
WASM_UNSUPPORTED_PLATFORM_CAPABILITYwith the stable first-line marker rather than silently broadening access.
WASM compile CLI controls:
--sandbox-cpu-fuel <N>sets an explicit CPU fuel budget for Rust/WASM execution.- must be
> 0 - pairs cleanly with
--sandbox strong --strong-lane-preference wasm - deterministic behavior:
- binding CPU limit ->
WASM_CPU_FUEL_EXHAUSTED - binding wall-time deadline ->
WASM_TIMEOUT
- binding CPU limit ->
Lane B: OS process sandbox (real OS semantics)¶
When tasks require real OS semantics, generated code may run via an OS sandboxed child process in Rust (akc-exec, process lane). Separately, --sandbox dev uses Python SubprocessExecutor (see Compile loop); that path is weaker and intended for local development.
Core isolation controls (Rust process lane):
- dedicated per-run working directory
- constrained environment (env scrubbing; optional
AKC_EXEC_ENV_ALLOW_PREFIXESfor request keys) - wall-clock timeout with process-tree termination
- stdout/stderr byte caps (post-collection clamp)
- memory limits (best-effort on Unix; stronger options on Linux/Windows)
- restricted filesystem view (strong on Linux
bwrap, best-effort on native/macOS)
Process lane: what is enforced per platform/backend¶
The process lane has multiple backends selected by AKC_EXEC_BACKEND:
native(portable; default)bwrap(Linux only; requires Bubblewrap)docker(not implemented in Rust; requests are denied — production Docker isolation for compile is via PythonDockerExecutor, not this backend)
Linux + AKC_EXEC_BACKEND=bwrap (strongest process-lane isolation)¶
Enforced
- Network off: Bubblewrap runs with
--unshare-net. - Filesystem namespace: the child sees only:
- the per-tenant/run workspace mounted read-write
- explicitly allowlisted host paths mounted read-only or read-write (as requested)
- minimal runtime trees mounted read-only (e.g.
/usr,/bin,/lib*) to run dynamically linked binaries /tmpis atmpfs- Environment scrubbing:
env_clear+ minimalPATH+AKC_TENANT_ID/AKC_RUN_ID+ filtered request env. - Command allowlist: program must be in
AKC_EXEC_ALLOWLIST(default: onlyechofor dev/tests). - Timeout cleanup: on Unix, the executor kills the whole process group on wall-time timeout.
Best-effort
- Memory limits:
- Unix
RLIMIT_ASis applied when supported, but is not a perfect RSS cap and may be partially enforced. - On Linux runners, an optional cgroup v2 memory limiter may be created (auto-enabled in CI best-effort; controlled via
AKC_EXEC_CGROUPV2). - Stdout/stderr caps: output is clamped after capture (large outputs still cost host resources during execution).
macOS + AKC_EXEC_BACKEND=native (developer-friendly, best-effort FS isolation)¶
macOS does not use Bubblewrap. The native backend focuses on tenant/run workspace layout and policy validation.
Enforced
- Network off: same as Linux—requests with network capability enabled are rejected.
- Per-tenant/run working directory:
cwdis forced within the tenant/run workspace; relative traversal via..is rejected. - Environment scrubbing:
env_clear+ minimalPATH+AKC_TENANT_ID/AKC_RUN_ID+ filtered request env. - Command allowlist: program must be in
AKC_EXEC_ALLOWLIST(default: onlyechofor dev/tests). - Timeout cleanup: on Unix, the executor kills the whole process group on wall-time timeout.
Best-effort / limitations
- Filesystem isolation: the native backend does not create a new filesystem namespace (no chroot/container).
- The request’s
fs_policyis validated and, in the native backend, is limited to paths within the tenant/run workspace. - However, a spawned program can still attempt to open arbitrary absolute paths on the host (e.g.
/etc/hosts) because the OS view is shared. Treat native/macOS as workspace containment + policy validation, not a hard filesystem sandbox. - Memory limits:
RLIMIT_ASis attempted; some macOS setups returnEINVAL/ENOSYSand the executor proceeds without failing the run.
Windows + AKC_EXEC_BACKEND=native (Job Objects for tree kill + memory caps)¶
Windows uses the native backend, and attempts to create a per-exec Job Object and assign the spawned process to it.
Enforced (when Job assignment succeeds)
- Process-tree cleanup: on timeout, the executor terminates the Job, which terminates the entire process tree.
- Memory limit: if
limits.memory_bytesis provided, the Job memory limit is set for the whole Job.
Enforced (even without a Job)
- Network off: requests with network capability enabled are rejected.
- Per-tenant/run working directory:
cwdis forced within the tenant/run workspace; relative traversal via..is rejected. - Environment scrubbing:
env_clear+ minimalPATH+AKC_TENANT_ID/AKC_RUN_ID+ filtered request env. - Command allowlist: program must be in
AKC_EXEC_ALLOWLIST(default: onlyechofor dev/tests).
Best-effort / limitations
- Job assignment can fail (e.g., if the runner is already in a Job that disallows nested Jobs). In that case:
- timeout kill falls back to best-effort
Child::kill() - full process-tree kill semantics are not guaranteed
- Filesystem isolation: Windows native does not create a filesystem namespace; it relies on workspace containment + policy validation.
- CPU throttling and priority: optional best-effort policies can be applied via:
AKC_EXEC_WIN_CPU_RATE_PERCENT(Job CPU hard-cap when supported)AKC_EXEC_WIN_PRIORITY_CLASS(process priority class)
Linux + AKC_EXEC_BACKEND=native¶
Same semantics as macOS native: no filesystem namespace. Prefer bwrap on Linux when you need enforceable filesystem isolation.
Runtime execution (akc runtime)¶
The long-running runtime uses adapters and policy distinct from the compile-time executor stack. In particular:
LocalDepthRuntimeAdaptercan runsubprocessandhttproutes when enabled by bundle metadata and runtime policy (allowlisted argv basenames, bounded HTTP viaakc.runtime.http_execute, minimal env).NativeRuntimeAdapterstubs most actions but runs realcoordination.stepwork when coordination is present.
Authoritative routing, gates, and evidence paths are documented in runtime-execution.md. Treat runtime as policy-gated: never assume subprocess or outbound HTTP is allowed without explicit bundle + envelope configuration.
Ingestion safety model¶
Ingestion normalizes untrusted content into deterministic chunk records suitable for indexing.
- Python connectors parse and chunk in-process; treat inputs as untrusted data (not trusted code).
- Optional Rust path:
akc-ingestvia CLI/pyo3 (akc compile/ ingest flags such as--use-rust-ingest-docs) produces normalized records consumed by Python.
Isolation principles:
- ingestion should be treated as parsing untrusted payloads (no direct execution of document contents as code)
- tenant-scoped output records include
tenant_id - normalization is deterministic (stable ordering and stable ids) to reduce the chance of adversarial non-determinism affecting downstream compilation
MCP ingest connector (ingest-mcp extra)¶
akc ingest --connector mcp runs a Model Context Protocol client against servers you configure (stdio subprocess or streamable HTTP). Treat this as high trust in the configured command or remote endpoint:
- Stdio: AKC spawns the configured process with the merged environment (including secrets referenced via
${VAR}expansion in config). That process is arbitrary code with the user’s privileges; only run allowlisted binaries and minimize inherited secrets. - HTTP: Non-loopback URLs must use HTTPS (enforced in the connector). For local servers, prefer binding to 127.0.0.1 and follow MCP guidance on Origin validation and DNS rebinding for streamable HTTP.
- Tenant isolation: emitted
Documentmetadata still includestenant_idlike other connectors; do not point multiple tenants at the same MCP server unless you enforce isolation on the server side.
Incremental skips use listing metadata (for example meta.etag) when present; without server hints, sources are re-fetched each run.
AKC as MCP server (mcp-serve extra / akc mcp serve)¶
akc mcp serve runs a read-only Model Context Protocol server (FastMCP) so MCP hosts (for example IDEs) can query indexed knowledge and inspect compile run metadata.
- Tenant isolation: tools require an explicit
tenant_id. Use--allow-tenant/AKC_MCP_ALLOWED_TENANTSto restrict which tenants the process will serve. - Scoped secrets: optional
AKC_MCP_TOOL_TOKEN(or--tool-token) forces clients to passtool_tokenon each tool call (stdio-friendly shared secret). When--http-bearer-token/AKC_MCP_HTTP_BEARER_TOKENis set for streamable HTTP or SSE, FastMCP enforces bearer auth at the transport and does not requiretool_token. - Network exposure: HTTP transports default to 127.0.0.1. Do not expose an unauthenticated server to untrusted networks; prefer SSH tunnels or a reverse proxy with strong auth for remote access.
- Data exposure:
akc_query_indexreturns document snippets (truncated);akc_list_recent_runs/akc_get_compile_statusread the tenant operations index and manifest pointers—treat the MCP host as having read access to whatever paths your outputs root contains.
Observability and logging¶
We record structured events for executor/ingest calls with:
tenant_id,run_id(and related scope fields)- selected lane (
process/wasm) and surface (clivspyo3where applicable) - requested limits (timeout/memory/stdout/stderr caps)
- outcome (
ok, exit code, timeout flag, policy denial)
Python’s rust_bridge emits compact JSON log lines for bridge operations; Rust uses akc_protocol observability helpers.
Logging requirements:
- Always include tenant/run ids for correlation.
- Never log secrets, raw large payloads, or cross-tenant information.
- Prefer redaction/sampling for high-volume events.
Non-goals¶
This model is defense-in-depth. It does not claim absolute safety against all possible sandbox escape vulnerabilities, but it:
- minimizes the attack surface
- enforces strict capabilities and resource budgets where the selected lane supports them
- creates audit trails for incident investigation
Continuous assurance (CI jobs, verifier gates, supply-chain checks) is summarized in oss-security-requirements.md.