From c2d43c55e79249fbff13e87af2e13f8bf70dbcc1 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Mon, 13 Jul 2026 17:54:55 +0200 Subject: [PATCH] feat(agent): real nix (sandbox=false) for firmware SBOM, replacing nix-portable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nix-portable fell back to proot in the deployment (user namespaces are blocked by the container's default seccomp/apparmor profile, and orca can't relax it), and proot corrupts the nix build's file-permission syscalls — every firmware build failed at `cp: setting permissions … No such file or directory` and fell back to the analysis-only SBOM. Ship a real nix instead and disable its build sandbox (`sandbox = false`): a plain gcc/make firmware build needs no user namespace, so it runs under the locked-down profile with no proot at all. The store ships as a compressed bootstrap tarball (built in a throwaway `nixos/nix` stage) and is seeded onto /nix at first start by docker/agent-entrypoint.sh, so a persistent /nix volume survives redeploys. Seeding and the whole path are best-effort — a broken nix just falls back to analysis-only, never breaking a scan. No agent code change: NixBackend::detect() already prefers the system `nix`. Co-Authored-By: Claude Opus 4.8 --- Dockerfile.agent | 43 ++++++++++++++++++++++++++------------ docker/agent-entrypoint.sh | 19 +++++++++++++++++ 2 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 docker/agent-entrypoint.sh diff --git a/Dockerfile.agent b/Dockerfile.agent index eefe1f7..2b2a060 100644 --- a/Dockerfile.agent +++ b/Dockerfile.agent @@ -13,6 +13,12 @@ RUN --mount=type=secret,id=tramiton_token \ fi && \ CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p compliance-agent +# A throwaway stage that packs a real nix store (store paths + the validity DB) +# into a compressed bootstrap tarball. Only the tarball is copied into the final +# image, so we don't carry a raw /nix copy layer. +FROM nixos/nix:latest AS nixseed +RUN tar -C / -czf /nix-bootstrap.tar.gz nix + FROM debian:bookworm-slim RUN apt-get update && apt-get install -y ca-certificates libssl3 git curl python3 python3-pip npm golang-go php-cli && rm -rf /var/lib/apt/lists/* @@ -40,20 +46,30 @@ RUN pip3 install --break-system-packages semgrep # Install ruff for Python linting RUN pip3 install --break-system-packages ruff -# Install nix-portable (rootless nix) so the firmware-SBOM pipeline can drive a -# tramiton reproducible build (NixBackend). Best-effort: if the download fails, -# the agent falls back to analysis-only firmware SBOMs (never breaks a scan). -# The nix store lives under NP_LOCATION — mount a PERSISTENT volume there in the -# deployment, else every firmware scan re-fetches nixpkgs + cross toolchains. -ARG NIX_PORTABLE_VERSION=v012 -RUN curl -fsSL -o /usr/local/bin/nix-portable \ - "https://github.com/DavHau/nix-portable/releases/download/${NIX_PORTABLE_VERSION}/nix-portable-x86_64" \ - && chmod +x /usr/local/bin/nix-portable \ - || { rm -f /usr/local/bin/nix-portable; echo "WARN: nix-portable install skipped; firmware SBOM uses analysis-only fallback"; } -ENV NP_LOCATION=/data/compliance-scanner -RUN mkdir -p /data/compliance-scanner +# Real nix for the tramiton reproducible-build firmware SBOM. +# +# nix-portable's proot fallback can't run here: user namespaces are blocked by +# the container's default seccomp/apparmor profile, and orca exposes no way to +# relax it. So ship a *real* nix and disable its build sandbox +# (`sandbox = false`) — a plain gcc/make firmware build needs no user namespace, +# so it runs fine under the locked-down profile with no proot involved. +# +# The store is shipped as a bootstrap tarball and seeded onto /nix at first +# start (see docker/agent-entrypoint.sh), so a persistent /nix volume survives +# redeploys. A missing/broken nix just falls back to the analysis-only SBOM. +COPY --from=nixseed /nix-bootstrap.tar.gz /opt/nix-bootstrap.tar.gz +ENV PATH="/nix/var/nix/profiles/default/bin:${PATH}" +RUN mkdir -p /etc/nix && printf '%s\n' \ + 'experimental-features = nix-command flakes' \ + 'sandbox = false' \ + 'build-users-group =' \ + 'substituters = https://cache.nixos.org' \ + 'trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=' \ + > /etc/nix/nix.conf COPY --from=builder /app/target/release/compliance-agent /usr/local/bin/compliance-agent +COPY docker/agent-entrypoint.sh /usr/local/bin/agent-entrypoint.sh +RUN chmod +x /usr/local/bin/agent-entrypoint.sh # Copy documentation for the help chat assistant COPY --from=builder /app/README.md /app/README.md @@ -65,5 +81,6 @@ RUN mkdir -p /data/compliance-scanner/ssh EXPOSE 3001 3002 -ENTRYPOINT ["compliance-agent"] +# Seeds /nix (fresh volume) from the bootstrap tarball, then runs the agent. +ENTRYPOINT ["/usr/local/bin/agent-entrypoint.sh"] diff --git a/docker/agent-entrypoint.sh b/docker/agent-entrypoint.sh new file mode 100644 index 0000000..07fcc91 --- /dev/null +++ b/docker/agent-entrypoint.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Seed the nix store on first start, then run the agent. +# +# The firmware-SBOM pipeline drives a real `nix` build (tramiton NixBackend). +# The image ships the store as a bootstrap tarball rather than baking /nix, so a +# persistent /nix volume (mounted empty on first deploy) gets populated once and +# then survives redeploys. Seeding is best-effort: if it fails, the agent still +# starts and firmware SBOMs fall back to analysis-only. +if [ ! -e /nix/store ]; then + echo "agent-entrypoint: seeding /nix store from image bootstrap..." + mkdir -p /nix + if tar -C / -xzf /opt/nix-bootstrap.tar.gz; then + echo "agent-entrypoint: /nix store seeded." + else + echo "agent-entrypoint: WARN nix seed failed; firmware SBOM will use analysis-only fallback." + fi +fi + +exec compliance-agent "$@" -- 2.54.0