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
50 changes: 9 additions & 41 deletions bin/deploy.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# Deploy extensions and bridge from baudbot source to agent runtime.
# Deploy extensions/skills/runtime scripts from baudbot source to agent runtime.
#
# Run as admin:
# ~/baudbot/bin/deploy.sh
Expand Down Expand Up @@ -69,7 +69,6 @@ log() { bb_log "$1"; }

# Security-critical files — deployed read-only (chmod a-w)
PROTECTED_EXTENSIONS=(tool-guard.ts tool-guard.test.mjs)
PROTECTED_BRIDGE_FILES=(security.mjs security.test.mjs)
EXPERIMENTAL_EXTENSIONS=(agentmail email-monitor)

# ── Stage source to temp dir (readable by agent) ────────────────────────────
Expand All @@ -81,7 +80,6 @@ trap 'rm -rf "$STAGE_DIR"' EXIT
if [ "$DRY_RUN" -eq 0 ]; then
cp -r --no-preserve=ownership "$BAUDBOT_SRC/pi/extensions" "$STAGE_DIR/extensions"
cp -r --no-preserve=ownership "$BAUDBOT_SRC/pi/skills" "$STAGE_DIR/skills"
cp -r --no-preserve=ownership "$BAUDBOT_SRC/slack-bridge" "$STAGE_DIR/slack-bridge"
cp --no-preserve=ownership "$BAUDBOT_SRC/start.sh" "$STAGE_DIR/start.sh"
mkdir -p "$STAGE_DIR/bin"
for script in harden-permissions.sh redact-logs.sh prune-session-logs.sh; do
Expand Down Expand Up @@ -241,42 +239,6 @@ else
log "would seed: memory/*.md (only if missing)"
fi

# ── Slack Bridge ─────────────────────────────────────────────────────────────

echo "Deploying slack-bridge..."

BRIDGE_SRC="$STAGE_DIR/slack-bridge"
BRIDGE_DEST="$BAUDBOT_HOME/runtime/slack-bridge"

if [ "$DRY_RUN" -eq 0 ]; then
as_agent bash -c "
mkdir -p '$BRIDGE_DEST'
# Unlock protected files before bulk copy
for pf in ${PROTECTED_BRIDGE_FILES[*]}; do
[ -f '$BRIDGE_DEST/\$pf' ] && chmod u+w '$BRIDGE_DEST/\$pf' 2>/dev/null || true
done
cp -r '$BRIDGE_SRC/.' '$BRIDGE_DEST/'
"

# Lock protected files read-only
for pf in "${PROTECTED_BRIDGE_FILES[@]}"; do
if as_agent test -f "$BRIDGE_DEST/$pf"; then
as_agent chmod a-w "$BRIDGE_DEST/$pf"
log "✓ $pf (read-only)"
fi
done

# Agent-modifiable files stay writable
if as_agent test -f "$BRIDGE_DEST/bridge.mjs"; then
as_agent chmod u+w "$BRIDGE_DEST/bridge.mjs"
log "✓ bridge.mjs"
fi

log "✓ node_modules/ + package files"
else
log "would copy: slack-bridge/"
fi

# ── Runtime bin (utility scripts + start.sh) ─────────────────────────────────

echo "Deploying runtime scripts..."
Expand Down Expand Up @@ -414,11 +376,17 @@ VEOF
echo ' \"source_sha\": \"$GIT_SHA\",'
echo ' \"files\": {'
first=1
for dir in '$BAUDBOT_HOME/.pi/agent/extensions' '$BAUDBOT_HOME/.pi/agent/skills' '$BAUDBOT_HOME/runtime/slack-bridge' '$BAUDBOT_HOME/runtime/bin'; do
for dir in '$BAUDBOT_HOME/.pi/agent/extensions' '$BAUDBOT_HOME/.pi/agent/skills' '/opt/baudbot/current/slack-bridge' '$BAUDBOT_HOME/runtime/bin'; do
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

the manifest generation now includes /opt/baudbot/current/slack-bridge, but bin/security-audit.sh and bin/doctor.sh still check for files at ~/runtime/slack-bridge. this path mismatch will cause security audits to fail when looking for security.mjs and security.test.mjs

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/deploy.sh
Line: 379

Comment:
the manifest generation now includes `/opt/baudbot/current/slack-bridge`, but `bin/security-audit.sh` and `bin/doctor.sh` still check for files at `~/runtime/slack-bridge`. this path mismatch will cause security audits to fail when looking for `security.mjs` and `security.test.mjs`

How can I resolve this? If you propose a fix, please make it concise.

if [ -d \"\$dir\" ]; then
Comment thread
sentry[bot] marked this conversation as resolved.
while IFS= read -r f; do
hash=\$(sha256sum \"\$f\" | cut -d' ' -f1)
rel=\"\${f#$BAUDBOT_HOME/}\"
if [[ \"\$f\" == "$BAUDBOT_HOME/"* ]]; then
rel=\"\${f#$BAUDBOT_HOME/}\"
elif [[ \"\$f\" == \"/opt/baudbot/current/\"* ]]; then
rel=\"release/\${f#/opt/baudbot/current/}\"
else
rel=\"\$f\"
fi
[ \"\$first\" -eq 1 ] && first=0 || echo ','
printf ' \"%s\": \"%s\"' \"\$rel\" \"\$hash\"
done < <(find \"\$dir\" -type f -not -path '*/node_modules/*' -not -name '*.log' | sort)
Expand Down
9 changes: 5 additions & 4 deletions bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,14 @@ else
fi
fi

if [ -d "$BAUDBOT_HOME/runtime/slack-bridge" ] && [ -f "$BAUDBOT_HOME/runtime/slack-bridge/bridge.mjs" ]; then
pass "slack bridge deployed"
BRIDGE_DIR="$BAUDBOT_CURRENT_LINK/slack-bridge"
if [ -d "$BRIDGE_DIR" ] && [ -f "$BRIDGE_DIR/bridge.mjs" ]; then
pass "slack bridge deployed ($BRIDGE_DIR)"
else
if [ "$IS_ROOT" -ne 1 ] && [ -d "$BAUDBOT_HOME/runtime" ]; then
if [ "$IS_ROOT" -ne 1 ] && { [ -d "$BAUDBOT_CURRENT_LINK" ] || [ -e "$BAUDBOT_CURRENT_LINK" ]; }; then
warn "cannot verify slack bridge files as non-root (run: sudo baudbot doctor)"
else
fail "slack bridge not deployed (run: baudbot deploy)"
fail "slack bridge not deployed (expected: $BRIDGE_DIR; run: sudo baudbot update)"
fi
fi

Expand Down
34 changes: 19 additions & 15 deletions bin/security-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,13 @@ else
ok "~/.pi/agent/skills/ is a real directory"
fi

# Check runtime bridge exists
BRIDGE_DIR="$BAUDBOT_CURRENT_LINK/slack-bridge"
# shellcheck disable=SC2088
if [ -d "$BAUDBOT_HOME/runtime/slack-bridge" ]; then
ok "Runtime bridge directory exists"
if [ -d "$BRIDGE_DIR" ]; then
ok "Release bridge directory exists ($BRIDGE_DIR)"
else
finding "WARN" "~/runtime/slack-bridge/ not found" \
"Run: deploy.sh"
finding "WARN" "release bridge directory not found" \
"Expected: $BRIDGE_DIR (run: sudo baudbot update)"
fi

# Check version stamp exists
Expand All @@ -251,10 +251,14 @@ if [ -f "$MANIFEST_FILE" ]; then
for critical_file in \
".pi/agent/extensions/tool-guard.ts" \
".pi/agent/extensions/tool-guard.test.mjs" \
"runtime/slack-bridge/security.mjs" \
"runtime/slack-bridge/security.test.mjs"; do
"release/slack-bridge/security.mjs" \
"release/slack-bridge/security.test.mjs"; do

full_path="$BAUDBOT_HOME/$critical_file"
if [[ "$critical_file" == release/* ]]; then
full_path="$BAUDBOT_CURRENT_LINK/${critical_file#release/}"
else
full_path="$BAUDBOT_HOME/$critical_file"
fi
if [ ! -f "$full_path" ]; then
finding "WARN" "Missing critical file: $critical_file" "Run deploy.sh"
missing=$((missing + 1))
Expand Down Expand Up @@ -649,17 +653,17 @@ if [ "$DEEP" -eq 1 ]; then
fi

# Check that bridge security.mjs exists and is tested
if [ -f "$BAUDBOT_HOME/runtime/slack-bridge/security.mjs" ]; then
ok "Bridge security module exists (runtime)"
if [ -f "$BAUDBOT_HOME/runtime/slack-bridge/security.test.mjs" ]; then
ok "Bridge security tests exist (runtime)"
if [ -f "$BRIDGE_DIR/security.mjs" ]; then
ok "Bridge security module exists (release)"
if [ -f "$BRIDGE_DIR/security.test.mjs" ]; then
ok "Bridge security tests exist (release)"
else
finding "WARN" "No tests for bridge security module in runtime" \
"Run deploy.sh to copy from source"
finding "WARN" "No tests for bridge security module in release" \
"Run: sudo baudbot update"
fi
else
finding "WARN" "Bridge security module not found" \
"Expected in ~/runtime/slack-bridge/security.mjs — run deploy.sh"
"Expected in $BRIDGE_DIR/security.mjs — run: sudo baudbot update"
fi
echo ""

Expand Down
16 changes: 8 additions & 8 deletions bin/security-audit.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ trap cleanup EXIT
setup_base() {
local home="$1"
rm -rf "$home"
mkdir -p "$home/.config" "$home/.ssh" "$home/.pi" "$home/baudbot/slack-bridge" "$home/baudbot/.git"
mkdir -p "$home/.config" "$home/.ssh" "$home/.pi" "$home/opt/baudbot/current/slack-bridge" "$home/baudbot/.git"

# Secrets file
echo "SLACK_BOT_TOKEN=xoxb-test" > "$home/.config/.env"
Expand All @@ -38,8 +38,8 @@ setup_base() {
echo -e "[user]\n\tname = test\n\temail = test@test.com" > "$home/.gitconfig"

# Bridge security module
echo "// security" > "$home/baudbot/slack-bridge/security.mjs"
echo "// tests" > "$home/baudbot/slack-bridge/security.test.mjs"
echo "// security" > "$home/opt/baudbot/current/slack-bridge/security.mjs"
echo "// tests" > "$home/opt/baudbot/current/slack-bridge/security.test.mjs"

# Audit log (fallback location)
mkdir -p "$home/logs"
Expand All @@ -50,7 +50,7 @@ setup_base() {
run_audit() {
local home="$1"
shift
BAUDBOT_HOME="$home" bash "$SCRIPT" "$@" 2>&1 || true
BAUDBOT_HOME="$home" BAUDBOT_RELEASE_ROOT="$home/opt/baudbot" bash "$SCRIPT" "$@" 2>&1 || true
}

expect_contains() {
Expand Down Expand Up @@ -183,7 +183,7 @@ echo ""
echo "Test: missing bridge security module"
HOME8="$TMPDIR/no-bridge-sec"
setup_base "$HOME8"
rm -f "$HOME8/baudbot/slack-bridge/security.mjs"
rm -f "$HOME8/opt/baudbot/current/slack-bridge/security.mjs"

output=$(run_audit "$HOME8")
expect_contains "reports missing security module" "$output" "Bridge security module not found"
Expand All @@ -195,7 +195,7 @@ echo ""
echo "Test: missing bridge tests"
HOME9="$TMPDIR/no-bridge-tests"
setup_base "$HOME9"
rm -f "$HOME9/baudbot/slack-bridge/security.test.mjs"
rm -f "$HOME9/opt/baudbot/current/slack-bridge/security.test.mjs"

output=$(run_audit "$HOME9")
expect_contains "reports missing tests" "$output" "No tests for bridge security"
Expand Down Expand Up @@ -224,7 +224,7 @@ HOME11="$TMPDIR/exitcode"
setup_base "$HOME11"
echo "SLACK_ALLOWED_USERS=U12345" >> "$HOME11/.config/.env"
set +e
BAUDBOT_HOME="$HOME11" bash "$SCRIPT" >/dev/null 2>&1
BAUDBOT_HOME="$HOME11" BAUDBOT_RELEASE_ROOT="$HOME11/opt/baudbot" bash "$SCRIPT" >/dev/null 2>&1
code=$?
set -e
if [ "$code" -le 2 ]; then
Expand All @@ -239,7 +239,7 @@ HOME11b="$TMPDIR/exitcode-crit"
setup_base "$HOME11b"
chmod 644 "$HOME11b/.config/.env"
set +e
BAUDBOT_HOME="$HOME11b" bash "$SCRIPT" >/dev/null 2>&1
BAUDBOT_HOME="$HOME11b" BAUDBOT_RELEASE_ROOT="$HOME11b/opt/baudbot" bash "$SCRIPT" >/dev/null 2>&1
code=$?
set -e
if [ "$code" -eq 2 ]; then
Expand Down
20 changes: 20 additions & 0 deletions bin/update-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,25 @@ EOF
chmod 644 "$release_dir/baudbot-release.json"
}

install_release_bridge_dependencies() {
local release_dir="$1"
local bridge_dir="$release_dir/slack-bridge"

if [ ! -d "$bridge_dir" ] || [ ! -f "$bridge_dir/package.json" ]; then
log "slack-bridge package.json missing; skipping bridge dependency install"
return 0
fi

log "installing production Slack bridge dependencies in release"
rm -rf "$bridge_dir/node_modules"

if [ -f "$bridge_dir/package-lock.json" ]; then
(cd "$bridge_dir" && npm ci --omit=dev)
else
(cd "$bridge_dir" && npm install --omit=dev)
fi
}

publish_release() {
local checkout="$1"
local repo_url="$2"
Expand Down Expand Up @@ -235,6 +254,7 @@ publish_release() {

verify_git_free_release "$STAGING_DIR" || die "staged release contains .git"
write_release_metadata "$STAGING_DIR" "$repo_url" "$branch"
install_release_bridge_dependencies "$STAGING_DIR"

# Release snapshots are immutable artifacts (files read-only).
# Keep directories writable for release pruning/cleanup workflows.
Expand Down
4 changes: 2 additions & 2 deletions pi/skills/control-agent/startup-cleanup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fi
# then Socket Mode when SLACK_BOT_TOKEN + SLACK_APP_TOKEN are present.
# If neither mode is configured, skip bridge startup.
BRIDGE_SCRIPT=""
if [ -f "$HOME/runtime/slack-bridge/broker-bridge.mjs" ] && varlock run --path "$HOME/.config/" -- sh -c '
if [ -f "/opt/baudbot/current/slack-bridge/broker-bridge.mjs" ] && varlock run --path "$HOME/.config/" -- sh -c '
test -n "$SLACK_BROKER_URL" &&
test -n "$SLACK_BROKER_WORKSPACE_ID" &&
test -n "$SLACK_BROKER_SERVER_PRIVATE_KEY" &&
Expand All @@ -102,7 +102,7 @@ fi
# Start fresh slack-bridge
echo "Starting slack-bridge ($BRIDGE_SCRIPT) with PI_SESSION_ID=$MY_UUID..."
tmux new-session -d -s slack-bridge \
"unset PKG_EXECPATH; export PATH=\$HOME/.varlock/bin:\$HOME/opt/node-v22.14.0-linux-x64/bin:\$PATH && export PI_SESSION_ID=$MY_UUID && cd ~/runtime/slack-bridge && exec varlock run --path ~/.config/ -- node $BRIDGE_SCRIPT"
"unset PKG_EXECPATH; export PATH=\$HOME/.varlock/bin:\$HOME/opt/node-v22.14.0-linux-x64/bin:\$PATH && export PI_SESSION_ID=$MY_UUID && cd /opt/baudbot/current/slack-bridge && exec varlock run --path ~/.config/ -- node $BRIDGE_SCRIPT"

# Wait for bridge to come up
sleep 3
Expand Down
4 changes: 2 additions & 2 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,12 @@ fi
echo "=== Setting up runtime directories ==="
# The agent runs from deployed copies, not from the source repo directly.
# Source: ~/baudbot/ (typically not readable by agent in hardened default installs)
# Runtime: ~/.pi/agent/extensions/, ~/.pi/agent/skills/, ~/runtime/slack-bridge/
# Runtime: ~/.pi/agent/extensions/, ~/.pi/agent/skills/, ~/runtime/
sudo -u baudbot_agent bash -c '
mkdir -p ~/.pi/agent/extensions
mkdir -p ~/.pi/agent/skills
mkdir -p ~/.pi/agent/memory
mkdir -p ~/runtime/slack-bridge
mkdir -p ~/runtime
'

echo "=== Installing extension dependencies ==="
Expand Down
11 changes: 6 additions & 5 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
# Run as: sudo -u baudbot_agent ~/runtime/start.sh
#
# The agent runs entirely from deployed copies — no source repo access needed:
# ~/.pi/agent/extensions/ ← pi extensions
# ~/.pi/agent/skills/ ← operational skills
# ~/runtime/slack-bridge/ ← bridge process
# ~/runtime/bin/ ← utility scripts
# ~/.pi/agent/extensions/ ← pi extensions
# ~/.pi/agent/skills/ ← operational skills
# /opt/baudbot/current/slack-bridge/ ← bridge process
# ~/runtime/bin/ ← utility scripts
#
# To update, admin edits source and runs deploy.sh.

Expand Down Expand Up @@ -82,11 +82,12 @@ elif [ -n "${SLACK_BOT_TOKEN:-}" ] && [ -n "${SLACK_APP_TOKEN:-}" ]; then
fi

if [ -n "$BRIDGE_SCRIPT" ]; then
RELEASE_BRIDGE="/opt/baudbot/current/slack-bridge"
tmux kill-session -t slack-bridge 2>/dev/null || true
echo "Starting Slack bridge ($BRIDGE_SCRIPT)..."
tmux new-session -d -s slack-bridge \
"export PATH=$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:\$PATH && \
cd ~/runtime/slack-bridge && \
cd $RELEASE_BRIDGE && \
while true; do \
varlock run --path ~/.config/ -- node $BRIDGE_SCRIPT; \
echo '⚠️ Bridge exited (\$?), restarting in 5s...'; \
Expand Down
7 changes: 4 additions & 3 deletions test/security-audit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function setupFixture(homeDir) {
fs.mkdirSync(path.join(homeDir, ".config"), { recursive: true });
fs.mkdirSync(path.join(homeDir, ".ssh"), { recursive: true });
fs.mkdirSync(path.join(homeDir, ".pi/agent"), { recursive: true });
fs.mkdirSync(path.join(homeDir, "runtime/slack-bridge"), { recursive: true });
fs.mkdirSync(path.join(homeDir, "opt/baudbot/current/slack-bridge"), { recursive: true });
fs.mkdirSync(path.join(homeDir, "baudbot/.git/hooks"), { recursive: true });
fs.mkdirSync(path.join(homeDir, "logs"), { recursive: true });

Expand All @@ -29,8 +29,8 @@ function setupFixture(homeDir) {
path.join(homeDir, ".pi/agent/baudbot-version.json"),
JSON.stringify({ short: "testsha", deployed_at: "2026-01-01T00:00:00Z" }),
);
fs.writeFileSync(path.join(homeDir, "runtime/slack-bridge/security.mjs"), "// security\n");
fs.writeFileSync(path.join(homeDir, "runtime/slack-bridge/security.test.mjs"), "// tests\n");
fs.writeFileSync(path.join(homeDir, "opt/baudbot/current/slack-bridge/security.mjs"), "// security\n");
fs.writeFileSync(path.join(homeDir, "opt/baudbot/current/slack-bridge/security.test.mjs"), "// tests\n");
fs.writeFileSync(path.join(homeDir, "logs/commands.log"), "");
}

Expand All @@ -43,6 +43,7 @@ function runAudit(homeDir, args = []) {
BAUDBOT_HOME: homeDir,
BAUDBOT_SRC: path.join(homeDir, "baudbot"),
BAUDBOT_AGENT_USER: "baudbot_agent",
BAUDBOT_RELEASE_ROOT: path.join(homeDir, "opt/baudbot"),
},
});

Expand Down