From 349243e7c4d8bfeb93848ac9f9518e645a6a0a21 Mon Sep 17 00:00:00 2001 From: Claude CoWork Date: Thu, 7 May 2026 11:29:49 -0700 Subject: [PATCH 1/2] OPS-405: bake codex runtime defaults into image, layered with ConfigMap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds defaults/codex-config.toml carrying a sensible baseline for the codex variant: sandbox_mode = "danger-full-access" # pod is the security boundary approval_policy = "on-failure" # no per-command prompts [projects."/home/codex/workspace"] trust_level = "trusted" Why these values: the apk8s pod itself is the security sandbox (non-root user, restricted RBAC, PVC isolation). Codex's internal bubblewrap layer is redundant in this deployment AND was failing on `bwrap: No permissions to create new namespace` because most hardened k8s clusters block unprivileged user-namespace cloning. Disabling Codex's inner sandbox eliminates the per-command escalation that OPS-405 calls out as noisy. Dockerfile copies defaults/-config.toml into /etc/-defaults/ during build (only the file matching the AGENT arg gets installed). Entrypoint now layers two sources into ${AGENT_CONFIG_DIR}: Layer 1: /etc/-defaults/ — image baseline (this commit) Layer 2: /etc/-config/ — apk8s ConfigMap (existing, wins) Per-deployment tweaks still go in the apk8s ConfigMap; this baseline just means a fresh pod without a ConfigMap is still functional. Note on full fix scope: OPS-405's apk8s ConfigMap update remains a separate Nate-action — the live pods today have a ConfigMap mounted, which means this image-default doesn't reach them until either the pods are rebuilt without their ConfigMap or the ConfigMap content is updated to match this baseline. The values here can be copied into the apk8s ConfigMap directly. Co-Authored-By: Claude Opus 4.7 (1M context) --- Dockerfile | 11 +++++++++++ bin/entrypoint.sh | 15 +++++++++++++-- defaults/codex-config.toml | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 defaults/codex-config.toml diff --git a/Dockerfile b/Dockerfile index 88f6248..44e265f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -219,6 +219,17 @@ RUN groupadd -g 1000 ${AGENT} \ && mkdir -p /home/${AGENT}/.config /home/${AGENT}/workspace \ && chown -R ${AGENT}:${AGENT} /home/${AGENT} +# Per-agent default config baseline. Copied into /etc/-defaults/ +# at build time; the entrypoint applies these BEFORE the ConfigMap +# overlay at /etc/-config/, so a deployment without a ConfigMap +# still gets sensible runtime config and the ConfigMap only needs to +# carry the deltas. +RUN mkdir -p "/etc/${AGENT}-defaults" +COPY defaults/ /etc/defaults-staging/ +RUN if [ -f "/etc/defaults-staging/${AGENT}-config.toml" ]; then \ + cp "/etc/defaults-staging/${AGENT}-config.toml" "/etc/${AGENT}-defaults/config.toml"; \ + fi && rm -rf /etc/defaults-staging + # Entrypoint + bash profile. COPY --chmod=0755 bin/entrypoint.sh /usr/local/bin/entrypoint.sh COPY --chown=${AGENT}:${AGENT} profile/.bashrc /home/${AGENT}/.bashrc diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index eef43ac..5801e06 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -97,10 +97,21 @@ claude) ;; esac -# Sync managed config from a ConfigMap mounted at /etc/-config/. +# Layer 1 — image-baked defaults at /etc/-defaults/. +# Provide sensible defaults so a deployment without a ConfigMap still +# gets a working runtime config. The ConfigMap overlay below wins on +# any key it also sets. Today this carries the Codex sandbox/approval +# baseline (see defaults/codex-config.toml; OPS-405). +AGENT_DEFAULTS_DIR="/etc/${AGENT}-defaults" +if [ -d "${AGENT_DEFAULTS_DIR}" ]; then + cp -fL "${AGENT_DEFAULTS_DIR}/." "${AGENT_CONFIG_DIR}/" 2>/dev/null || true + chmod -R u+w "${AGENT_CONFIG_DIR}" 2>/dev/null || true +fi + +# Layer 2 — managed config from a ConfigMap mounted at /etc/-config/. # The ConfigMap (apk8s repo) is the source of truth for model/MCP config; # in-pod edits get blown away on restart. Stakater Reloader restarts the -# pod when the ConfigMap changes. +# pod when the ConfigMap changes. Per-deployment overrides go here. if [ -d "${AGENT_CONFIG_SOURCE}" ]; then # cp -L follows symlinks (configmap mounts are symlink farms). cp -fL "${AGENT_CONFIG_SOURCE}/." "${AGENT_CONFIG_DIR}/" 2>/dev/null || true diff --git a/defaults/codex-config.toml b/defaults/codex-config.toml new file mode 100644 index 0000000..c9c03e1 --- /dev/null +++ b/defaults/codex-config.toml @@ -0,0 +1,37 @@ +# Default Codex CLI runtime config for the codex-shell pod. +# +# Layered with /etc/codex-config (apk8s ConfigMap) at entrypoint time — +# image defaults are applied first, then the ConfigMap overlay wins on +# any key it sets. So this file is the baseline; per-deployment tweaks +# go in apk8s. +# +# Why these values: +# +# - `sandbox_mode = "danger-full-access"`: the pod itself is the +# security boundary (non-root user, restricted RBAC, PVC isolation). +# Codex's internal bubblewrap layer is redundant in this deployment +# and was failing on `bwrap: No permissions to create new namespace` +# in apk8s pods that don't allow unprivileged user namespaces (most +# hardened k8s clusters). Disabling the inner sandbox means commands +# no longer escalate-on-bwrap-failure for every read. +# +# - `approval_policy = "on-failure"`: with the inner sandbox off, no +# sandbox-failure escalations happen. The user only gets prompted +# when a command genuinely fails. Combined with full-access this is +# functionally "no per-command prompts" — appropriate for a trusted +# agent pod, not for an unrestricted user shell. +# +# - The `[projects."/home/codex/workspace"]` trust entry mirrors what +# the live config already had (per OPS-405 description) and makes +# the trust explicit at image-default level. +# +# To tighten later (e.g., re-enable inner sandbox once unprivileged +# user-namespace-clone is enabled at the kubelet/sysctl level), set +# `sandbox_mode = "workspace-write"` in the apk8s ConfigMap; this +# baseline doesn't need to change. + +sandbox_mode = "danger-full-access" +approval_policy = "on-failure" + +[projects."/home/codex/workspace"] +trust_level = "trusted" From b9e4c6e0c4dfd3510e0bac0ea5f943a1744737bf Mon Sep 17 00:00:00 2001 From: Claude CoWork Date: Thu, 7 May 2026 13:02:27 -0700 Subject: [PATCH 2/2] OPS-405: harden config-copy on both layers (cp -afL + smoke check) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex review (CHANGES_REQUESTED): cp -fL without -R skipped subdirectories, so the baked defaults wouldn't actually land in the runtime config dir. Same root cause as OPS-406 (codex-shell#10). Applies the hardened pattern from #10 to BOTH config-copy layers: Layer 1 — image defaults (/etc/-defaults/): cp -afL with FATAL exit on failure + smoke check that catches silent permission/path failures. Layer 2 — ConfigMap overlay (/etc/-config/): Same pattern. Will rebase cleanly on top of #10 (or vice versa) since the changes are textually identical. Both layers now fail loudly instead of silently masking missing config — same defense-in-depth as the OPS-406 fix. Co-Authored-By: Claude Opus 4.7 (1M context) --- bin/entrypoint.sh | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 5801e06..42f165d 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -102,20 +102,51 @@ esac # gets a working runtime config. The ConfigMap overlay below wins on # any key it also sets. Today this carries the Codex sandbox/approval # baseline (see defaults/codex-config.toml; OPS-405). +# +# cp -afL: -a recurses + preserves attributes, -L dereferences symlinks. +# Failures exit FATAL rather than being masked — same pattern as the +# ConfigMap overlay below (OPS-406, codex-shell#10). AGENT_DEFAULTS_DIR="/etc/${AGENT}-defaults" if [ -d "${AGENT_DEFAULTS_DIR}" ]; then - cp -fL "${AGENT_DEFAULTS_DIR}/." "${AGENT_CONFIG_DIR}/" 2>/dev/null || true + if ! cp -afL "${AGENT_DEFAULTS_DIR}/." "${AGENT_CONFIG_DIR}/"; then + echo "FATAL: failed to sync image defaults from ${AGENT_DEFAULTS_DIR} to ${AGENT_CONFIG_DIR}" >&2 + exit 1 + fi chmod -R u+w "${AGENT_CONFIG_DIR}" 2>/dev/null || true + + # Smoke check: if the defaults dir has any files, at least one must + # have landed in the destination. Catches silent permission/path + # failures that would otherwise mask a non-functional baseline. + if [ -n "$(find "${AGENT_DEFAULTS_DIR}" -mindepth 1 -print -quit 2>/dev/null)" ] \ + && [ -z "$(find "${AGENT_CONFIG_DIR}" -mindepth 1 -print -quit 2>/dev/null)" ]; then + echo "FATAL: defaults sync ran but ${AGENT_CONFIG_DIR} is empty" >&2 + exit 1 + fi fi # Layer 2 — managed config from a ConfigMap mounted at /etc/-config/. # The ConfigMap (apk8s repo) is the source of truth for model/MCP config; # in-pod edits get blown away on restart. Stakater Reloader restarts the # pod when the ConfigMap changes. Per-deployment overrides go here. +# +# cp -afL: -a recurses + preserves attributes, -L dereferences the +# symlink farm that ConfigMap mounts use. The previous `cp -fL` skipped +# subdirectories entirely and silently dropped managed config (OPS-406). if [ -d "${AGENT_CONFIG_SOURCE}" ]; then - # cp -L follows symlinks (configmap mounts are symlink farms). - cp -fL "${AGENT_CONFIG_SOURCE}/." "${AGENT_CONFIG_DIR}/" 2>/dev/null || true + if ! cp -afL "${AGENT_CONFIG_SOURCE}/." "${AGENT_CONFIG_DIR}/"; then + echo "FATAL: failed to sync managed config from ${AGENT_CONFIG_SOURCE} to ${AGENT_CONFIG_DIR}" >&2 + exit 1 + fi chmod -R u+w "${AGENT_CONFIG_DIR}" 2>/dev/null || true + + # Smoke check: if the ConfigMap mount has any files, at least one + # must have landed in the destination. Catches silent + # permission/path failures that previously masked stale config. + if [ -n "$(find "${AGENT_CONFIG_SOURCE}" -mindepth 1 -print -quit 2>/dev/null)" ] \ + && [ -z "$(find "${AGENT_CONFIG_DIR}" -mindepth 1 -print -quit 2>/dev/null)" ]; then + echo "FATAL: managed config sync ran but ${AGENT_CONFIG_DIR} is empty" >&2 + exit 1 + fi fi # Pull nprodromou/agent-config for the canonical Nate-org instructions