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.
- The x86_64 VM declares
codegeist-aias a locked, non-login system user with nowheelmembership, disables sudo and SSH, and gives the account private0700state and workspace directories. - Codegeist is present through
/etc/codegeist/releasebut is not installed in the global system PATH. The bootstrap exposes no human desktop launcher. - The automated test boots through UEFI, runs
--versionand--show-configunder the AI UID in an empty AI-owned workspace, and appliesPrivateNetwork,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
runNixOSTestexists only in the generated test machine, not inimages.x86_64.vm. task start-vncadds an unauthenticated host-side QEMU display bound to127.0.0.1:5900by 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 keepstty0primary on VNC and uses state separate from the serial headless runner.task test-installeradds 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-democonnects a view-only client to that same fixed display on an isolated host X server. Its full recording and logs remain under ignoredtarget/; 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 anologinshell, private0700state home, no own group, and no administrative supplementary group. The human home is0750; PAM uses umask007, and the internal Geist command uses0007. - Only the human's
geistpackage is placed in its PATH.geistderives the paired username from the real caller, joins all arguments into one prompt, and invokes an immutable internalcodegeist askscript through aNOPASSWD:NOSETENVsudo rule fixed to the paired user and group. The wrapper preserves the caller's working directory, routesLOG_FILEandCODEGEIST_SESSION_DIRECTORYto private/var/lib/geist/<username>state, disables Spring Shell history, and selects the immutable OS provider configuration. Directcodegeist, 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
geistunder 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.
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:11434with no API authentication or local-UID filtering. Thegpt-5-minialias matches Codegeist0.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
asksmoke 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.
geistis 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.
- Create the AI user as a separate non-root UID with no default administrative
group membership or unrestricted
sudopath. - 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, andNOSETENV. Do not allow a shell, caller-selected executable, environment-preserving command, or alternate run-as identity through that rule.
- 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 umask007, and use internal Geist umask0007. This makes newly created human and Geist files group-writable but does not retroactively change existing modes or override applications that deliberately create0600or0700data. - Preserve the invoking workspace, but force Codegeist's
LOG_FILEto/var/lib/geist/<username>/logs/codegeist.loginside the private0700Geist 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
openat2resolution, 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_TMPFILEthrough 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- relativelinkat(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.
- 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
0600or 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.
- 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.
- 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.
- 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=0in 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.
- 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.
- 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.
- 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.
- 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.
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.