Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion bin/ci/smoke-agent-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ wait_for_control_socket() {

probe_rpc_get_message() {
local socket_path="$1"
sudo -u "$AGENT_USER" python3 - "$socket_path" <<'PY'
local attempts=8
local delay_seconds=1

for ((i = 1; i <= attempts; i++)); do
if sudo -u "$AGENT_USER" python3 - "$socket_path" <<'PY'
import json
import socket
import sys
Expand Down Expand Up @@ -103,6 +107,15 @@ try:
finally:
client.close()
PY
then
return 0
fi

log "rpc probe attempt ${i}/${attempts} failed; retrying in ${delay_seconds}s"
sleep "$delay_seconds"
done

return 1
}

main() {
Expand Down
8 changes: 8 additions & 0 deletions bin/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,14 @@ deploy_runtime_asset_entry() {

bb_manifest_for_each RUNTIME_ASSET_MANIFEST deploy_runtime_asset_entry

# Clean up legacy bridge runtime path; bridge now runs from /opt release only.
if [ "$DRY_RUN" -eq 0 ]; then
as_agent bash -c "rm -rf '$BAUDBOT_HOME/runtime/slack-bridge'"
log "✓ removed legacy runtime/slack-bridge"
else
log "would remove: runtime/slack-bridge (legacy path)"
fi

# ── Memory Seeds ─────────────────────────────────────────────────────────────

MEMORY_SEED_DIR="$STAGE_DIR/skills/control-agent/memory"
Expand Down
20 changes: 10 additions & 10 deletions pi/extensions/tool-guard.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const BASH_DENY_RULES = [
{ id: "chmod-baudbot-source", pattern: new RegExp(`chmod\\b.*${escapeRegex(BAUDBOT_SOURCE_DIR)}`), label: "chmod on baudbot source repo", severity: "block" },
{ id: "chown-baudbot-source", pattern: new RegExp(`chown\\b.*${escapeRegex(BAUDBOT_SOURCE_DIR)}`), label: "chown on baudbot source repo", severity: "block" },
{ id: "tee-baudbot-source", pattern: new RegExp(`tee\\s+.*${escapeRegex(BAUDBOT_SOURCE_DIR)}/`), label: "tee write to baudbot source repo", severity: "block" },
{ id: "chmod-runtime-security", pattern: /chmod\b.*\/(\.pi\/agent\/extensions\/tool-guard|runtime\/slack-bridge\/security)\./, label: "chmod on protected runtime security file", severity: "block" },
{ id: "chmod-runtime-security", pattern: /chmod\b.*\/(\.pi\/agent\/extensions\/tool-guard|runtime\/slack-bridge\/security|opt\/baudbot\/current\/slack-bridge\/security)\./, label: "chmod on protected runtime security file", severity: "block" },
// Credential exfiltration
{ id: "env-exfil-curl", pattern: /\benv\b.*\|\s*(curl|wget|nc)\b/, label: "Piping environment to network tool", severity: "block" },
{ id: "cat-env-curl", pattern: /cat\s+.*\.env.*\|\s*(curl|wget|nc)\b/, label: "Exfiltrating .env via network", severity: "block" },
Expand Down Expand Up @@ -77,8 +77,8 @@ function isAllowedWritePath(filePath) {
const PROTECTED_RUNTIME_FILES = [
`${AGENT_HOME}/.pi/agent/extensions/tool-guard.ts`,
`${AGENT_HOME}/.pi/agent/extensions/tool-guard.test.mjs`,
`${AGENT_HOME}/runtime/slack-bridge/security.mjs`,
`${AGENT_HOME}/runtime/slack-bridge/security.test.mjs`,
`/opt/baudbot/current/slack-bridge/security.mjs`,
`/opt/baudbot/current/slack-bridge/security.test.mjs`,
];

function isProtectedPath(filePath) {
Expand Down Expand Up @@ -307,7 +307,7 @@ describe("tool-guard: source repo protection (bash)", () => {
assert.equal(checkBashCommand(`chmod a+w ${AGENT_HOME}/.pi/agent/extensions/tool-guard.ts`).blocked, true);
});
it("blocks chmod on runtime security.mjs", () => {
assert.equal(checkBashCommand(`chmod 777 ${AGENT_HOME}/runtime/slack-bridge/security.mjs`).blocked, true);
assert.equal(checkBashCommand("chmod 777 /opt/baudbot/current/slack-bridge/security.mjs").blocked, true);
});
});

Expand Down Expand Up @@ -358,8 +358,8 @@ describe("tool-guard: workspace confinement (allow-list)", () => {
it(`allows write to ${AGENT_HOME}/.pi/agent/skills/new-skill/SKILL.md`, () => {
assert.equal(checkWritePath(`${AGENT_HOME}/.pi/agent/skills/new-skill/SKILL.md`), false);
});
it(`allows write to ${AGENT_HOME}/runtime/slack-bridge/bridge.mjs`, () => {
assert.equal(checkWritePath(`${AGENT_HOME}/runtime/slack-bridge/bridge.mjs`), false);
it("blocks write to /opt/baudbot/current/slack-bridge/bridge.mjs", () => {
assert.equal(checkWritePath("/opt/baudbot/current/slack-bridge/bridge.mjs"), true);
});

// BLOCKED: outside agent home
Expand Down Expand Up @@ -430,13 +430,13 @@ describe("tool-guard: protected runtime security files", () => {
assert.equal(checkWritePath(`${AGENT_HOME}/.pi/agent/extensions/tool-guard.test.mjs`), true);
});
it("blocks write to runtime security.mjs", () => {
assert.equal(checkWritePath(`${AGENT_HOME}/runtime/slack-bridge/security.mjs`), true);
assert.equal(checkWritePath("/opt/baudbot/current/slack-bridge/security.mjs"), true);
});
it("blocks write to runtime security.test.mjs", () => {
assert.equal(checkWritePath(`${AGENT_HOME}/runtime/slack-bridge/security.test.mjs`), true);
assert.equal(checkWritePath("/opt/baudbot/current/slack-bridge/security.test.mjs"), true);
});
it("allows write to runtime bridge.mjs (agent-modifiable)", () => {
assert.equal(checkWritePath(`${AGENT_HOME}/runtime/slack-bridge/bridge.mjs`), false);
it("blocks write to runtime bridge.mjs (immutable release path)", () => {
assert.equal(checkWritePath("/opt/baudbot/current/slack-bridge/bridge.mjs"), true);
});
it("allows write to runtime non-security extensions", () => {
assert.equal(checkWritePath(`${AGENT_HOME}/.pi/agent/extensions/auto-name.ts`), false);
Expand Down
6 changes: 3 additions & 3 deletions pi/extensions/tool-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ const BASH_DENY_RULES: DenyRule[] = [
] : []),
{
id: "chmod-runtime-security",
pattern: /chmod\b.*\/(\.pi\/agent\/extensions\/tool-guard|runtime\/slack-bridge\/security)\./,
pattern: /chmod\b.*\/(\.pi\/agent\/extensions\/tool-guard|runtime\/slack-bridge\/security|opt\/baudbot\/current\/slack-bridge\/security)\./,
label: "chmod on protected runtime security file",
severity: "block" as const,
tier: "high" as const,
Expand Down Expand Up @@ -311,8 +311,8 @@ function isAllowedWritePath(filePath: string): boolean {
const PROTECTED_RUNTIME_FILES = [
`${AGENT_HOME}/.pi/agent/extensions/tool-guard.ts`,
`${AGENT_HOME}/.pi/agent/extensions/tool-guard.test.mjs`,
`${AGENT_HOME}/runtime/slack-bridge/security.mjs`,
`${AGENT_HOME}/runtime/slack-bridge/security.test.mjs`,
`/opt/baudbot/current/slack-bridge/security.mjs`,
`/opt/baudbot/current/slack-bridge/security.test.mjs`,
];

function isProtectedPath(filePath: string): boolean {
Expand Down
17 changes: 14 additions & 3 deletions pi/skills/control-agent/startup-cleanup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ set -euo pipefail
# processes and causes `varlock run` to treat subcommands as Node module paths).
unset PKG_EXECPATH 2>/dev/null || true

RUNTIME_NODE_HELPER="$HOME/runtime/bin/lib/runtime-node.sh"
if [ -r "$RUNTIME_NODE_HELPER" ]; then
# shellcheck source=bin/lib/runtime-node.sh
source "$RUNTIME_NODE_HELPER"
fi

SOCKET_DIR="$HOME/.pi/session-control"

if [ $# -eq 0 ]; then
Expand Down Expand Up @@ -123,20 +129,25 @@ fi
# The tmux session stays alive independently of this script (same pattern as
# sentry-agent). If the bridge crashes, the loop restarts it after 5 seconds.
echo "Starting slack-bridge ($BRIDGE_SCRIPT) via tmux..."
NODE_BIN_DIR="$HOME/opt/node/bin"
NODE_BIN_DIR="${NODE_BIN_DIR:-$HOME/opt/node/bin}"
if command -v bb_resolve_runtime_node_bin_dir >/dev/null 2>&1; then
NODE_BIN_DIR="$(bb_resolve_runtime_node_bin_dir "$HOME")"
fi
if [ ! -d "$NODE_BIN_DIR" ]; then
# Fallback: resolve versioned node dir
NODE_BIN_DIR="$(echo "$HOME"/opt/node-v*-linux-x64/bin | awk '{print $1}')"
fi

tmux new-session -d -s "$BRIDGE_TMUX_SESSION" "\
unset PKG_EXECPATH; \
export PATH=\$HOME/.varlock/bin:$NODE_BIN_DIR:\$PATH; \
export PATH=$NODE_BIN_DIR:\$PATH; \
export PI_SESSION_ID=$MY_UUID; \
cd $BRIDGE_DIR; \
while true; do \
echo \"[\$(date -Is)] bridge: starting $BRIDGE_SCRIPT\" >> $BRIDGE_LOG_FILE; \
varlock run --path \$HOME/.config/ -- node $BRIDGE_SCRIPT >> $BRIDGE_LOG_FILE 2>&1; \
for v in \$(env | grep ^SLACK_BROKER_ | cut -d= -f1 || true); do unset \$v; done; \
set -a; source \$HOME/.config/.env; set +a; \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The script sources .env without error handling. If the file is missing or invalid, the bridge starts without required environment variables, causing an infinite crash loop.
Severity: CRITICAL

Suggested Fix

Ensure the script exits if sourcing the .env file fails. This can be achieved by adding set -e to the beginning of the tmux command string or by checking the exit code of the source command, for example: source \$HOME/.config/.env || exit 1.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: pi/skills/control-agent/startup-cleanup.sh#L149

Potential issue: The script sources the `$HOME/.config/.env` file within a `tmux`
command that does not have `set -e` enabled. If the `source` command fails because the
file is missing, unreadable, or contains syntax errors, the script will not exit.
Instead, it will proceed to launch the Node.js bridge script without the required
environment variables. The bridge script is designed to exit immediately if these
variables are not present. Since this logic is inside a `while true` loop, this will
result in an infinite crash loop, preventing the Slack integration from functioning and
consuming system resources.

Did we get this right? 👍 / 👎 to inform future reviews.

node $BRIDGE_SCRIPT >> $BRIDGE_LOG_FILE 2>&1; \
exit_code=\$?; \
echo \"[\$(date -Is)] bridge: exited with code \$exit_code, restarting in 5s\" >> $BRIDGE_LOG_FILE; \
sleep 5; \
Expand Down