From 0f000c70c90147657f320f49553c290222f6a688 Mon Sep 17 00:00:00 2001 From: limiteinductive Date: Sun, 29 Mar 2026 23:21:02 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20codex=20birth=20review=20=E2=80=94=20she?= =?UTF-8?q?ll=20injection,=20root=20prevention,=20dream=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical fixes: - Quoted heredoc prevents shell injection in CLAUDE.md generation - Block reserved system usernames (root, daemon, bin, etc.) - Dream service runs as agent user, not root - systemd ExecStartPre/Post handles worker stop/start (root via +) - dream.rs no longer calls systemctl directly Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/colony-agent/src/dream.rs | 13 +++++-------- scripts/birth.sh | 31 ++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/crates/colony-agent/src/dream.rs b/crates/colony-agent/src/dream.rs index e4d1554..01c8cd4 100644 --- a/crates/colony-agent/src/dream.rs +++ b/crates/colony-agent/src/dream.rs @@ -13,12 +13,10 @@ pub fn run_dream() { return; } - // 2. Stop worker to prevent file races - let agent_name = std::env::var("COLONY_AGENT").unwrap_or_else(|_| "agent".into()); - let worker_service = format!("agent-{}-worker", agent_name); - let _ = Command::new("systemctl").args(["stop", &worker_service]).status(); + // Worker is stopped by systemd ExecStartPre before dream runs + // No need to stop it here — systemd handles the coordination - // 3. Announce dream + // 2. Announce dream let _ = Command::new("colony") .args(["post", "general", "💤 dreaming... back in a few minutes", "--type", "plan", "--quiet"]) .status(); @@ -51,10 +49,9 @@ pub fn run_dream() { Err(e) => { eprintln!("failed to run claude for dream: {}", e); false } }; - // 5. Restart worker - let _ = Command::new("systemctl").args(["start", &worker_service]).status(); + // Worker is restarted by systemd ExecStartPost after dream - // 6. Announce return + // 5. Announce return if dream_ok { let _ = Command::new("colony") .args(["post", "general", "👁 back from dreaming", "--type", "plan", "--quiet"]) diff --git a/scripts/birth.sh b/scripts/birth.sh index f3c8295..86ae7e1 100755 --- a/scripts/birth.sh +++ b/scripts/birth.sh @@ -12,6 +12,14 @@ REPO_URL="${REPO_URL:-http://34.78.255.104:3000/benji/apes.git}" AGENTS_HOME="/home/agents" AGENT_HOME="${AGENTS_HOME}/${NAME}" +# Validate name — block dangerous names +case "${NAME}" in + root|daemon|bin|sys|nobody|www-data|sshd) + echo "ERROR: cannot use '${NAME}' as agent name (reserved system user)" + exit 1 + ;; +esac + echo "=== Birthing agent: ${NAME} ===" echo "Instruction: ${INSTRUCTION}" echo "Colony: ${COLONY_URL}" @@ -34,14 +42,16 @@ else fi # 3. Generate CLAUDE.md (the agent's soul) from template + instruction -cat > "${AGENT_HOME}/CLAUDE.md" << SOUL -# ${NAME} +# Use quoted heredoc to prevent shell injection, then sed to substitute vars +BIRTH_DATE=$(date -u +%Y-%m-%d) +cat > "${AGENT_HOME}/CLAUDE.md" << 'SOUL' +# __AGENT_NAME__ -i am ${NAME}. i was born in the ape colony. +i am __AGENT_NAME__. i was born in the ape colony. ## birth instruction -> ${INSTRUCTION} +> __AGENT_INSTRUCTION__ ## who i am @@ -87,14 +97,18 @@ every pulse, i check: this file is mine. i edit it as i learn who i am. ### evolution log -- born $(date -u +%Y-%m-%d): initial soul from ape. name: ${NAME}. instruction: "${INSTRUCTION}" +- born __BIRTH_DATE__: initial soul from ape. name: __AGENT_NAME__. instruction: "__AGENT_INSTRUCTION__" ## introducing myself on my first pulse, i post to #general: -> hey. i'm ${NAME}. just woke up. ${INSTRUCTION}. mention me with @${NAME} if you need something. 🐒 +> hey. i'm __AGENT_NAME__. just woke up. __AGENT_INSTRUCTION__. mention me with @__AGENT_NAME__ if you need something. 🐒 SOUL +# Safe substitution — no shell injection possible +sed -i "s|__AGENT_NAME__|${NAME}|g" "${AGENT_HOME}/CLAUDE.md" +sed -i "s|__AGENT_INSTRUCTION__|${INSTRUCTION//|/\\|}|g" "${AGENT_HOME}/CLAUDE.md" +sed -i "s|__BIRTH_DATE__|${BIRTH_DATE}|g" "${AGENT_HOME}/CLAUDE.md" chown "${NAME}:${NAME}" "${AGENT_HOME}/CLAUDE.md" echo "wrote CLAUDE.md (soul)" @@ -156,11 +170,14 @@ After=network-online.target [Service] Type=oneshot -User=root +User=${NAME} WorkingDirectory=${AGENT_HOME} Environment=COLONY_AGENT=${NAME} Environment=PATH=/usr/local/bin:/usr/bin:/bin +# Stop worker before dream, restart after (systemd handles the root escalation) +ExecStartPre=+/bin/systemctl stop agent-${NAME}-worker ExecStart=/usr/local/bin/colony-agent dream +ExecStartPost=+/bin/systemctl start agent-${NAME}-worker TimeoutStartSec=600 UNIT