Skip to content

Security: codegeist-ai/codegeist-os

Security

docs/security.md

Security Requirements

The dedicated AI identities are security boundaries, not merely different home directories. The implementation must combine identity separation, least privilege, isolation, mediation, audit, revocation, and recovery.

Many approval, audit, accelerator, speech, rollback, and recovery requirements remain future work. The repository currently implements and tests the bootstrap controls plus mandatory model acquisition and bounded local inference in the x86_64 installed target; these must not be confused with the complete Version 1 security design.

Implemented Controls

  • The x86_64 VM declares codegeist-ai as a locked, non-login system user with no wheel membership, disables sudo and SSH, and gives the account private 0700 state and workspace directories.
  • Codegeist is present through /etc/codegeist/release but is not installed in the global system PATH. The bootstrap exposes no human desktop launcher.
  • The automated test boots through UEFI, runs --version and --show-config under the AI UID in an empty AI-owned workspace, and applies PrivateNetwork, NoNewPrivileges, ProtectSystem, ProtectHome, and a restricted address-family set to that test-only unit.
  • The reusable VM has no configured login credential or forwarded port. The root backdoor used by runNixOSTest exists only in the generated test machine, not in images.x86_64.vm.
  • task start-vnc adds an unauthenticated host-side QEMU display bound to 127.0.0.1:5900 by default. It is local development tooling, not a guest service or release capability, and must not be bound to an external interface. Its graphics-enabled runner keeps tty0 primary on VNC and uses state separate from the serial headless runner.
  • task test-installer adds the same localhost-only, unauthenticated display to the automated installer driver and opens TigerVNC in view-only mode. The fixed binding cannot be overridden by a task variable, closes with the test VM, and does not alter the production ISO.
  • task record-installer-demo connects a view-only client to that same fixed display on an isolated host X server. Its full recording and logs remain under ignored target/; only the curated installer GIF is repository-owned, and it is replaced only after the graphical E2E succeeds.
  • Future feature demos follow docs/feature-demos.md: capture only synthetic or approved public content, keep raw recordings and review artifacts ignored, and inspect every committed GIF for credentials, private paths, notifications, personal data, and unrelated host content before publication.
  • The x86_64 GNOME Calamares ISO carries Codegeist, Geist, and llama.cpp but does not expose the CLIs in the live user's PATH and embeds no GGUF bytes. Calamares downloads the 1.1 GB GGUF directly to /var/lib/codegeist/model.gguf; download failure fails installation and no checksum verification is performed. Before partitioning, a dedicated Local AI model page discloses the model version, variant, size, and source. The production derivation does not depend on the installer test's credentials or host-side driver.
  • The installer smoke boots the exact production ISO, lets Calamares use the production model URL, installs to a blank disk, and verifies the human-selected hostname and account through GDM. The test account and password are fixtures, not production credentials. Its fixed-resolution, bounded OCR and emulated keyboard are host-side test behavior; timing logs contain recognized UI text and must remain under ignored target/ or Nix test outputs.
  • The Calamares-generated target starts native llama.cpp from pinned Nixpkgs with context 4096 and the downloaded GGUF. The graphical installation test proves the configured llama.cpp-to-Geist request path.
  • Calamares creates a private <username> group and uses it as the primary group for both the human and <username>-geist. The Geist account is a locked system user with a nologin shell, private 0700 state home, no own group, and no administrative supplementary group. The human home is 0750; PAM uses umask 007, and the internal Geist command uses 0007.
  • Only the human's geist package is placed in its PATH. geist derives the paired username from the real caller, joins all arguments into one prompt, and invokes an immutable internal codegeist ask script through a NOPASSWD:NOSETENV sudo rule fixed to the paired user and group. The wrapper preserves the caller's working directory, routes LOG_FILE and CODEGEIST_SESSION_DIRECTORY to private /var/lib/geist/<username> state, disables Spring Shell history, and selects the immutable OS provider configuration. Direct codegeist, arbitrary passwordless commands, and interactive Geist login remain unavailable.
  • The installed target implements CPU inference through native llama.cpp. The end-to-end test completes GNOME login, starts GNOME Console, and obtains a visible real offline answer through geist under the locked paired identity. It does not separately probe private state modes or loopback API access. Speech, accelerator control, approval, audit, rollback, and recovery services remain unimplemented.

Version 1 Security Posture

The first integration milestone is deliberately not a general-purpose AI agent:

  • The human GNOME user, bootstrap AI user, and installed paired Geist user are separate operating-system identities.
  • Both AI identities are non-root, non-login, and absent from administrative groups. The bootstrap identity remains limited to private state and an empty smoke-test workspace. The paired Geist identity deliberately shares the human's private primary group and therefore has broader group-mode filesystem access.
  • Native llama.cpp binds its OpenAI-compatible API to 127.0.0.1:11434 with no API authentication or local-UID filtering. The gpt-5-mini alias matches Codegeist 0.5.0's fixed OpenAI model name.
  • The installed model is the one fixed by the OS artifact pin, not a hardware- ranked or human-selected variant. llama.cpp reads it directly with runtime context 4096 because the current installation test has 6 GiB of RAM.
  • The end-to-end ask smoke runs offline under the locked paired Geist identity against the installed model after Calamares downloaded it through the production URL. The smoke has no alternate model source, mirror, or firmware override.
  • Future accelerator device access must be granted only to the active restricted inference or speech worker through a trusted resource controller, not directly to the AI or human account.
  • Future speech requests must be accepted only from the configured human desktop UID over a permission-restricted Unix socket with peer-credential verification. The AI, inference, worker, and unrelated UIDs must not invoke the broker or runtime APIs.
  • Future Version 1 speech input is limited to one explicitly staged WAV or FLAC file per operation. Live microphone capture is not part of Version 1.
  • Diarization uses anonymous labels local to one file. Speaker identification, voiceprint enrollment, persistent speaker embeddings, and voice cloning are prohibited in Version 1.
  • geist is the only human-facing AI launcher. It is one-shot and has no TUI, Docker, desktop control, MCP integration, or privileged action path, but the shared private group is a broad filesystem grant rather than a file broker.
  • The installer does not inventory hardware, rank compatible variants, offer model selection, or import an external package. It always downloads and installs the single OS-pinned GGUF. Hardware-aware selection, generic catalog handling, external import, and lifecycle management remain future model-manager actions requiring human control.

The current Codegeist CLI includes local file and shell tools and does not yet implement a permission broker. Running it through geist changes the Unix UID but does not sandbox those tools: the process can read or modify every human file whose group permissions allow it, including paths outside the current project. Invoking geist is explicit human initiation, not per-tool approval. Do not place credentials or sensitive data in group-readable or group-writable files when this boundary is enabled.

Least Privilege

  • Create the AI user as a separate non-root UID with no default administrative group membership or unrestricted sudo path.
  • Deny capabilities by default and grant only named operations required for an approved use case.
  • Keep grants narrow in target, action, data exposure, and duration.
  • Prevent the model, prompts, downloaded content, and tool output from changing authorization policy directly.
  • Separate policy decisions from model inference and treat model output as untrusted input.
  • Reserve catalog updates, hardware compatibility decisions, model ranking, payload import, and persistent model deletion for non-AI administrative code.
  • Keep the Geist sudo rule fixed to the immutable internal Ask wrapper, exact paired user and group, NOPASSWD, and NOSETENV. Do not allow a shell, caller-selected executable, environment-preserving command, or alternate run-as identity through that rule.

Filesystem Access

  • Give each AI user a separate state home and private runtime directories.
  • Deny the bootstrap AI identity all human homes. The paired Geist account is the explicit exception: it shares the human's private primary group and can access every path granted to that group.
  • Keep the human home at 0750, use login umask 007, and use internal Geist umask 0007. This makes newly created human and Geist files group-writable but does not retroactively change existing modes or override applications that deliberately create 0600 or 0700 data.
  • Preserve the invoking workspace, but force Codegeist's LOG_FILE to /var/lib/geist/<username>/logs/codegeist.log inside the private 0700 Geist home. Do not require or silently relax a human workspace merely to initialize application logging.
  • Treat the current paired-group access as an accepted broad exception. A future broker should replace it with explicit per-file or per-directory shares.
  • Define ownership, write limits, conflict handling, deletion protection, and revocation for every shared path.
  • Avoid following attacker-controlled links or mount changes across boundaries.
  • A non-model broker must use held directory descriptors and descriptor-relative openat2 resolution, verify the opened input is a caller-owned regular file with one link, and obtain a read lease that excludes concurrent writers. It records metadata before and after copying, rejects changes, and validates and hashes only the completed broker-owned copy before giving workers read-only access.
  • Reject symlinks, hard-link surprises, special files, unexpected containers, unknown request fields, and files outside configured size and duration limits.
  • Create an unnamed result with O_TMPFILE through the held caller-owned output directory descriptor, copy and revalidate the result on that filesystem, sync it, set caller ownership and bounded mode, and publish it with descriptor- relative linkat(AT_EMPTY_PATH) only after intermediate cleanup. Accept only a bounded single-component basename with no slash, . or ..; reject an existing destination or an output filesystem without the required primitive.
  • Delete the sealed source copy, normalized audio, tensors, embeddings, and staging files before success is reported and on every failure path. Version 1 has no broker-side source retention option.

Credentials

  • Never expose human keyrings, SSH or signing agents, browser sessions, cloud credentials, private keys, or reusable administrator tokens to the AI user.
  • The paired group cannot enforce that requirement for a credential stored with group read permission. Credential stores and secret files must force 0600 or stricter modes and must not rely only on the human login umask.
  • Prefer narrowly scoped brokered operations over handing credentials to a model process.
  • Bind temporary credentials to an operation, target, and short lifetime.
  • Make revocation possible without recreating the human account or reinstalling the system.

Devices

  • Deny cameras, microphones, input devices, removable storage, raw disks, and other sensitive devices by default.
  • Assign accelerators explicitly to the active inference or speech worker and account for driver, DMA, firmware, device-memory residue, denial-of-service, and cross-process isolation risks.
  • Display or record active device grants and revoke them without granting root to the AI user.
  • Keep microphone devices denied even though file-based STT is enabled.
  • Use the Intel iGPU for the human GNOME session on the reference profile. Do not claim service-UID isolation as hardware tenant isolation on an RTX A2000, which has no MIG partitioning.
  • Returning A2000 allocation to the idle baseline does not prove device-memory sanitization. Until an evidenced reset or scrub boundary exists, treat all sequential A2000 inference and speech workers as one accelerator trust domain and make no confidentiality claim between them.
  • Treat each Intel, AMD, NVIDIA, Qualcomm, GPU, NPU, x86_64, or aarch64 profile as unverified until its OS driver, device isolation, telemetry, cleanup, and recovery gates pass.

Processes And Resources

  • Prevent inspection, tracing, signaling, or modification of human-user and privileged processes.
  • Constrain process count, memory, CPU, storage, and accelerator usage so the AI workload cannot make the system unrecoverable.
  • Isolate temporary files, IPC, service sockets, environment variables, and session buses.
  • Define deterministic behavior for crashes, resource exhaustion, and runaway workloads.
  • Serialize accelerator-backed Model Runner, STT, diarization, and TTS through a trusted accelerator resource controller. The root-owned controller launches transient cgroups, grants profile-specific devices only to the active unit, and permits one atomic backend/model-variant lease at a time. Inactive service UIDs have no static accelerator group membership.
  • Clear service-owned accelerator work and release model allocations on completion, cancellation, timeout, or service failure.
  • Treat 10 GiB as a provisional A2000-profile acceptance ceiling until the physical or passthrough gate measures it, not as a current result, universal requirement, or hardware partition. Use the maximum observed by a pinned NVML sampler running at no more than 10 ms intervals and runtime allocator high-water marks, and require at least 2 GiB below GPU-reported usable memory. On startup, reconcile recorded state against actual transient units, cgroups, device grants, processes, and NVML allocation. Block new leases after controller or worker failure until that evidence proves return to the idle baseline.
  • Store operation data on size-limited tmpfs, disable broker and worker core dumps and hibernation, and use no persistent plaintext swap. Scavenge abandoned operation directories before the broker accepts requests after restart.

Network Access

  • Deny unrestricted network access by default.
  • Grant destination, protocol, direction, and duration explicitly for approved workloads.
  • Treat remote responses, downloaded models, prompts, and tool instructions as untrusted content.
  • Make active network grants visible and revocable, and log policy decisions without recording secrets or unnecessary personal data.
  • Deny all network address families to future speech workers. Disable pyannote telemetry with PYANNOTE_METRICS_ENABLED=0 in addition to network denial.
  • Current llama.cpp listens only on 127.0.0.1:11434, which prevents remote access but permits every local UID to connect.
  • Test denied egress from the service contexts while the human desktop retains normal connectivity; taking the whole test machine offline is not sufficient evidence of service isolation.
  • Calamares is the only current model acquisition path. It downloads one pinned GGUF URL and fails installation on download failure. It does not verify the payload checksum. llama.cpp reads that file directly with context 4096.

Human Approval And Privilege

  • Require human approval for access to personal data, credentials, sensitive devices, persistent system changes, and elevated operations.
  • Present the exact action, target, data exposure, and side effects before approval.
  • Do not transform one approval into a reusable shell, token, group membership, or unlimited policy exemption.
  • Keep a non-AI path for denial, cancellation, and emergency shutdown.

Audit

  • Record capability requests, policy decisions, human approvals, execution outcomes, revocations, and security-relevant failures.
  • Protect audit data from the AI user and separate integrity evidence from model output.
  • Minimize personal and prompt content in logs while preserving enough context for investigation.
  • Define retention, export, review, and clock-integrity requirements before claiming auditability.
  • Record speech artifact versions, opaque operation identifier, anonymous speaker count, timing, and outcome without storing raw audio, transcript content, speaker embeddings, or voiceprints by default.

Catalog And Artifact Supply Chain

  • Accept immutable per-architecture Codegeist releases. The current runtime is llama.cpp from pinned Nixpkgs; the installer model download is deliberately not checksum-verified. The generic shared catalog remains planned.
  • Require the future shared catalog to record the approved logical LLM release, upstream source, training inputs, transformations, every LLM and speech runtime variant, quantization, evaluation, architecture/backend compatibility, resources, licenses, and every released file checksum.
  • Treat SafeTensors, GGUF, release archives, manifests, and converters as untrusted supply-chain inputs until independently verified.
  • Apply the same pin, hash, provenance, license, and offline requirements to every STT, diarization, alignment, VAD, TTS, tokenizer, codec, and voice file.
  • Keep GGUF and speech payload bytes outside production VM and ISO closures. The implemented ISO instead requires Calamares to acquire the exact pinned GGUF and save it in the installed target; that file is deliberate installed-system state, not part of the ISO closure.
  • Permit only Calamares to download the current fixed GGUF. Codegeist, Geist, and llama.cpp receive no selection or download authority. A future model manager exclusively owns hardware-aware catalog selection, external import, update, and deletion.
  • Run the installer smoke outside the Nix build sandbox so Calamares reaches the production model URL through normal QEMU user networking. Do not add a model mirror, firmware source override, or test cache. Generic external bundles remain future model-manager and speech-test inputs.
  • Treat gated model access conditions as additional release terms. Build-time access through a token does not by itself permit redistribution.
  • Acquire gated files only through the isolated prefetch process in docs/artifact-contract.md. Only official bundle-production or test derivations may consume the reviewed files from a credential-free immutable distribution location; production VM/ISO derivations must not reference them.
  • Keep model weights and generated OS images out of Git.
  • Keep Hugging Face, Gitea, GitHub, cache, and signing credentials out of Nix expressions, store paths, logs, images, and release metadata.
  • Do not publish an official model bundle until all base-model, dataset, derivative, and redistribution rights are recorded and accepted. An uncleared bundle does not block artifacts that neither redistribute nor install it.
  • Rebuild and rerun ISO payload-absence, mandatory installer acquisition, installed inference, affected external-bundle, and hardware-profile checks for every update listed in the canonical trigger set in docs/artifact-contract.md.

Rollback, Disablement, And Recovery

  • Provide a clear way to stop AI processes, disable the AI account and services, revoke grants, and block network access.
  • Preserve human login and administrative recovery when AI components fail.
  • Provide a non-AI recovery path when installed model state is missing or corrupt; the current direct-download path is not yet that recovery mechanism.
  • Support rollback of system configuration independently of future model/runtime updates. Model-manager-owned model rollback and deletion remain a separate later contract.
  • Define offline or rescue recovery that does not require the model, network, or normal AI services.
  • Before the first release, test disablement and model-independent recovery from compromised credentials, corrupted transient state, policy failure, and resource exhaustion. Test recovery from an unbootable installed update before an installed-system release.

Public Repository Boundary

Gitea at git.codegeist.ai is the primary write target. GitHub at github.com/codegeist-ai/codegeist-os is a public push mirror of Git refs; issues, pull requests, secrets, permissions, and other platform state are not synchronized. Do not commit credentials, personal data, private host information, restricted model artifacts, signing keys, or security-sensitive deployment state.

Development environments must ultimately trust the Gitea deployment certificate authority and keep Git TLS verification enabled. The project-local .codegeist/Dockerfile currently sets GIT_SSL_NO_VERIFY=true as a temporary exception. This disables certificate verification for every Git HTTPS request in the rebuilt development container, including requests to non-Gitea hosts. It does not authenticate to Gitea and does not affect host-side submodule initialization before the container starts. Remove the exception after the Gitea CA is trusted. Never place a Gitea token in a remote URL, Dockerfile, committed environment, or other tracked file.

There aren't any published security advisories