-
v1.0.0-rc.2 · release candidate
+
v1.0.0 · launch-ready
Your agent has root.
That’s the problem.
Rampart sits in the execution path. Install it, run rampart quickstart, and it wires the right protection path for Claude Code, Codex, Cline, or OpenClaw before risky tool calls run.
diff --git a/docs/install b/docs/install
index c8dd02bc..86169596 100644
--- a/docs/install
+++ b/docs/install
@@ -1,151 +1,263 @@
#!/bin/sh
-# Rampart installer
-# Usage: curl -fsSL https://rampart.sh/install | bash
-# curl -fsSL https://rampart.sh/install | RAMPART_VERSION=v0.3.0 bash
+# Rampart install script.
#
-# Env vars:
-# RAMPART_VERSION — pin a version (default: latest)
-# RAMPART_INSTALL_DIR — install directory (default: ~/.local/bin)
-# RAMPART_INSTALL_DRY_RUN — set to 1 to print actions without running them
+# Canonical source for the website and legacy installer copies. Keep
+# docs/install, docs/install.sh, and scripts/install.sh byte-for-byte synced
+# with this file.
+# Usage: curl -fsSL https://rampart.sh/install | sh
+# curl -fsSL https://rampart.sh/install | sh -s -- --version v0.1.0
+# curl -fsSL https://rampart.sh/install | sh -s -- --auto-setup
+# RAMPART_INSTALL_DRY_RUN=1 sh install.sh --version v1.0.0
+# RAMPART_VERSION=v1.0.0 RAMPART_INSTALL_DIR=$HOME/.local/bin sh install.sh
set -e
REPO="peg/rampart"
-INSTALL_DIR="${RAMPART_INSTALL_DIR:-$HOME/.local/bin}"
+INSTALL_DIR="${RAMPART_INSTALL_DIR:-}"
+BINARY="rampart"
VERSION="${RAMPART_VERSION:-}"
+AUTO_SETUP="${RAMPART_AUTO_SETUP:-0}"
DRY_RUN="${RAMPART_INSTALL_DRY_RUN:-0}"
-# ── Colors ────────────────────────────────────────────────────────────────────
+# Colors (if terminal supports them).
if [ -t 1 ]; then
- BOLD="\033[1m"; GREEN="\033[32m"; YELLOW="\033[33m"; RED="\033[31m"; RESET="\033[0m"
+ BOLD="\033[1m"
+ GREEN="\033[32m"
+ RED="\033[31m"
+ YELLOW="\033[33m"
+ RESET="\033[0m"
else
- BOLD=""; GREEN=""; YELLOW=""; RED=""; RESET=""
+ BOLD="" GREEN="" RED="" YELLOW="" RESET=""
fi
info() { printf "${GREEN}▸${RESET} %s\n" "$1"; }
-warn() { printf "${YELLOW}▸${RESET} %s\n" "$1" >&2; }
+warn() { printf "${YELLOW}▸${RESET} %s\n" "$1"; }
error() { printf "${RED}✗${RESET} %s\n" "$1" >&2; exit 1; }
-step() { printf "\n${BOLD}%s${RESET}\n" "$1"; }
-dry() { printf "${YELLOW}[dry-run]${RESET} %s\n" "$1"; }
-# ── Downloader ─────────────────────────────────────────────────────────────────
-fetch() { # fetch [dest]
- URL="$1"; DEST="$2"
+fetch() { # fetch [dest]
+ URL="$1"
+ DEST="${2:-}"
if command -v curl >/dev/null 2>&1; then
- if [ -n "$DEST" ]; then curl -fsSL -o "$DEST" "$URL"
- else curl -fsSL "$URL"; fi
+ if [ -n "$DEST" ]; then
+ curl -fsSL -o "$DEST" "$URL"
+ else
+ curl -fsSL "$URL"
+ fi
elif command -v wget >/dev/null 2>&1; then
- if [ -n "$DEST" ]; then wget -qO "$DEST" "$URL"
- else wget -qO- "$URL"; fi
+ if [ -n "$DEST" ]; then
+ wget -qO "$DEST" "$URL"
+ else
+ wget -qO- "$URL"
+ fi
else
error "Neither curl nor wget found. Install one and retry."
fi
}
-# ── OS / Arch ──────────────────────────────────────────────────────────────────
+# Parse args.
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --version) VERSION="$2"; shift 2 ;;
+ --version=*) VERSION="${1#--version=}"; shift ;;
+ --auto-setup) AUTO_SETUP=1; shift ;;
+ --dry-run) DRY_RUN=1; shift ;;
+ *) error "Unknown option: $1" ;;
+ esac
+done
+
+# Detect OS.
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
- linux) OS="linux" ;;
+ linux) OS="linux" ;;
darwin) OS="darwin" ;;
- *) error "Unsupported OS: $(uname -s). Only linux and darwin are supported." ;;
+ *) error "Unsupported OS: $OS (need linux or darwin)" ;;
esac
+# Detect architecture.
ARCH="$(uname -m)"
case "$ARCH" in
- x86_64|amd64) ARCH="amd64" ;;
- aarch64|arm64) ARCH="arm64" ;;
- *) error "Unsupported architecture: $(uname -m). Only amd64 and arm64 are supported." ;;
+ x86_64|amd64) ARCH="amd64" ;;
+ aarch64|arm64) ARCH="arm64" ;;
+ *) error "Unsupported architecture: $ARCH (need amd64 or arm64)" ;;
esac
-info "Platform: ${BOLD}${OS}/${ARCH}${RESET}"
+info "Detected ${BOLD}${OS}/${ARCH}${RESET}"
-# ── Resolve version ────────────────────────────────────────────────────────────
+# Determine version.
if [ -z "$VERSION" ]; then
- info "Fetching latest release..."
- VERSION="$(fetch "https://api.github.com/repos/${REPO}/releases/latest" \
- | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')"
- [ -n "$VERSION" ] || error "Could not determine latest version. Set RAMPART_VERSION=vX.Y.Z and retry."
+ info "Fetching latest version..."
+ VERSION=$(fetch "https://api.github.com/repos/${REPO}/releases/latest" \
+ | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
+ if [ -z "$VERSION" ]; then
+ error "Could not determine latest version. Try: --version v0.1.0"
+ fi
fi
-# Normalise: ensure leading 'v'
+# Normalise version: release tags include a leading "v".
case "$VERSION" in
v*) ;;
*) VERSION="v${VERSION}" ;;
esac
-info "Version: ${BOLD}${VERSION}${RESET}"
+info "Installing ${BOLD}rampart ${VERSION}${RESET}"
-# ── Build URLs ─────────────────────────────────────────────────────────────────
-TARBALL="rampart_${VERSION#v}_${OS}_${ARCH}.tar.gz"
+if [ -z "$INSTALL_DIR" ]; then
+ if [ "$(id -u)" -eq 0 ]; then
+ INSTALL_DIR="/usr/local/bin"
+ elif [ -d "$HOME/.local/bin" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif [ "$DRY_RUN" = "1" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif mkdir -p "$HOME/.local/bin" 2>/dev/null; then
+ INSTALL_DIR="$HOME/.local/bin"
+ else
+ INSTALL_DIR="/usr/local/bin"
+ fi
+fi
+
+# Build download URLs.
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
+TARBALL="rampart_${VERSION#v}_${OS}_${ARCH}.tar.gz"
TARBALL_URL="${BASE_URL}/${TARBALL}"
CHECKSUM_URL="${BASE_URL}/checksums.txt"
if [ "$DRY_RUN" = "1" ]; then
- step "Dry-run — no changes will be made"
- dry "Would download: ${TARBALL_URL}"
- dry "Would install: ${INSTALL_DIR}/rampart"
- dry "Would run: rampart quickstart"
- if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
- dry "Would hint to add ${INSTALL_DIR} to PATH"
- fi
+ info "Dry-run — no changes will be made"
+ info "Would download: ${TARBALL_URL}"
+ info "Would verify: ${CHECKSUM_URL}"
+ info "Would install: ${INSTALL_DIR}/${BINARY}"
exit 0
fi
-# ── Download & extract ─────────────────────────────────────────────────────────
-step "Downloading rampart ${VERSION}..."
-
+# Create temp directory.
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
-fetch "$TARBALL_URL" "${TMP_DIR}/${TARBALL}" \
- || error "Download failed.\nURL: ${TARBALL_URL}\nCheck that ${VERSION} exists: https://github.com/${REPO}/releases"
+# Download archive.
+info "Downloading archive..."
+if ! fetch "$TARBALL_URL" "${TMP_DIR}/${TARBALL}"; then
+ error "Download failed. Check that ${VERSION} exists at:\n ${TARBALL_URL}"
+fi
-# Optional checksum verification
+# Download and verify checksum.
+info "Verifying checksum..."
if fetch "$CHECKSUM_URL" "${TMP_DIR}/checksums.txt" 2>/dev/null; then
+ EXPECTED=$(grep "${TARBALL}" "${TMP_DIR}/checksums.txt" | awk '{print $1}')
if command -v sha256sum >/dev/null 2>&1; then
- HASH_CMD="sha256sum"
+ ACTUAL=$(sha256sum "${TMP_DIR}/${TARBALL}" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
- HASH_CMD="shasum -a 256"
+ ACTUAL=$(shasum -a 256 "${TMP_DIR}/${TARBALL}" | awk '{print $1}')
else
- HASH_CMD=""
+ warn "No sha256sum or shasum found — skipping verification"
+ ACTUAL="$EXPECTED"
fi
- if [ -n "$HASH_CMD" ]; then
- EXPECTED="$(grep "${TARBALL}" "${TMP_DIR}/checksums.txt" | awk '{print $1}')"
- ACTUAL="$($HASH_CMD "${TMP_DIR}/${TARBALL}" | awk '{print $1}')"
- if [ -n "$EXPECTED" ] && [ "$EXPECTED" != "$ACTUAL" ]; then
- error "Checksum mismatch!\n Expected: ${EXPECTED}\n Got: ${ACTUAL}"
- fi
+
+ if [ -z "$EXPECTED" ]; then
+ warn "No checksum entry found for ${TARBALL} — skipping verification"
+ elif [ "$EXPECTED" != "$ACTUAL" ]; then
+ error "Checksum mismatch!\n Expected: ${EXPECTED}\n Got: ${ACTUAL}"
+ else
info "Checksum verified ✓"
fi
+else
+ warn "No checksums.txt found — skipping verification"
fi
tar -xzf "${TMP_DIR}/${TARBALL}" -C "$TMP_DIR"
-# ── Install ────────────────────────────────────────────────────────────────────
-step "Installing to ${INSTALL_DIR}..."
+if [ ! -f "${TMP_DIR}/${BINARY}" ]; then
+ error "Archive did not contain ${BINARY}"
+fi
-mkdir -p "$INSTALL_DIR"
-mv "${TMP_DIR}/rampart" "${INSTALL_DIR}/rampart"
-chmod +x "${INSTALL_DIR}/rampart"
+# Install.
+chmod +x "${TMP_DIR}/${BINARY}"
-info "Installed: ${BOLD}${INSTALL_DIR}/rampart${RESET}"
+if [ ! -d "$INSTALL_DIR" ]; then
+ if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then
+ info "Need sudo to create ${INSTALL_DIR}"
+ sudo mkdir -p "$INSTALL_DIR"
+ fi
+fi
-# PATH hint
-if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
- warn "${INSTALL_DIR} is not in your PATH."
- warn "Add this to your shell profile (~/.bashrc, ~/.zshrc, etc.):"
- printf " ${BOLD}export PATH=\"\$HOME/.local/bin:\$PATH\"${RESET}\n" >&2
- export PATH="${INSTALL_DIR}:${PATH}"
+if [ -w "$INSTALL_DIR" ]; then
+ mv "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
+else
+ info "Need sudo to install to ${INSTALL_DIR}"
+ sudo mv "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
fi
-# ── Quickstart ─────────────────────────────────────────────────────────────────
-step "Running quickstart..."
+info "Installed to ${BOLD}${INSTALL_DIR}/${BINARY}${RESET}"
-if [ -t 0 ] && [ -t 1 ]; then
- rampart quickstart
+# Verify.
+RAMPART_BIN="${INSTALL_DIR}/${BINARY}"
+if [ -x "$RAMPART_BIN" ]; then
+ printf "\n"
+ "$RAMPART_BIN" version 2>/dev/null || true
+
+ if echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
+ printf "\n${GREEN}${BOLD}Ready!${RESET} Run ${BOLD}rampart quickstart${RESET} to get started.\n"
+ else
+ printf "\n${YELLOW}Note:${RESET} ${INSTALL_DIR} may not be in your PATH.\n"
+ printf "Add it: ${BOLD}export PATH=\"${INSTALL_DIR}:\$PATH\"${RESET}\n"
+ printf "Then run: ${BOLD}rampart quickstart${RESET}\n"
+ fi
else
- rampart quickstart --env none 2>/dev/null || true
+ warn "Could not verify installed binary at ${INSTALL_DIR}/${BINARY}"
fi
-printf "\n${GREEN}${BOLD}Done!${RESET} rampart ${VERSION} is installed.\n"
-printf "Docs: ${BOLD}https://docs.rampart.sh${RESET}\n\n"
+# Detect AI agents and suggest setup commands.
+detect_agents_and_suggest() {
+ printf "\n"
+
+ # Detect OpenClaw
+ OPENCLAW_FOUND=0
+ if command -v openclaw >/dev/null 2>&1; then
+ OPENCLAW_FOUND=1
+ elif [ -f "$HOME/.local/bin/openclaw" ] || [ -f "/usr/local/bin/openclaw" ] || [ -f "/usr/bin/openclaw" ]; then
+ OPENCLAW_FOUND=1
+ fi
+
+ # Detect Claude Code (claude CLI)
+ CLAUDE_FOUND=0
+ if command -v claude >/dev/null 2>&1; then
+ CLAUDE_FOUND=1
+ elif [ -f "$HOME/.claude/settings.json" ]; then
+ CLAUDE_FOUND=1
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 1 ] || [ "$CLAUDE_FOUND" -eq 1 ]; then
+ printf "${GREEN}${BOLD}✓ AI agent(s) detected!${RESET}\n\n"
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 1 ]; then
+ if [ "$AUTO_SETUP" = "1" ]; then
+ printf "${GREEN}▸${RESET} Auto-setup: protecting OpenClaw...\n"
+ "$RAMPART_BIN" setup openclaw 2>&1 || printf "${YELLOW} ↳ Auto-setup failed — run manually: rampart setup openclaw${RESET}\n"
+ else
+ printf " Run this to protect your OpenClaw agent:\n"
+ printf " ${BOLD}rampart setup openclaw${RESET}\n"
+ printf "\n"
+ fi
+ fi
+
+ if [ "$CLAUDE_FOUND" -eq 1 ]; then
+ if [ "$AUTO_SETUP" = "1" ]; then
+ printf "${GREEN}▸${RESET} Auto-setup: protecting Claude Code...\n"
+ "$RAMPART_BIN" setup claude-code 2>&1 || printf "${YELLOW} ↳ Auto-setup failed — run manually: rampart setup claude-code${RESET}\n"
+ else
+ printf " Run this to protect Claude Code:\n"
+ printf " ${BOLD}rampart setup claude-code${RESET}\n"
+ printf "\n"
+ fi
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 0 ] && [ "$CLAUDE_FOUND" -eq 0 ]; then
+ printf " To protect an AI agent, run:\n"
+ printf " ${BOLD}rampart setup openclaw${RESET} — for OpenClaw\n"
+ printf " ${BOLD}rampart setup claude-code${RESET} — for Claude Code\n"
+ printf "\n"
+ fi
+}
+
+if [ -x "$RAMPART_BIN" ]; then
+ detect_agents_and_suggest
+fi
diff --git a/docs/install.sh b/docs/install.sh
new file mode 100755
index 00000000..86169596
--- /dev/null
+++ b/docs/install.sh
@@ -0,0 +1,263 @@
+#!/bin/sh
+# Rampart install script.
+#
+# Canonical source for the website and legacy installer copies. Keep
+# docs/install, docs/install.sh, and scripts/install.sh byte-for-byte synced
+# with this file.
+# Usage: curl -fsSL https://rampart.sh/install | sh
+# curl -fsSL https://rampart.sh/install | sh -s -- --version v0.1.0
+# curl -fsSL https://rampart.sh/install | sh -s -- --auto-setup
+# RAMPART_INSTALL_DRY_RUN=1 sh install.sh --version v1.0.0
+# RAMPART_VERSION=v1.0.0 RAMPART_INSTALL_DIR=$HOME/.local/bin sh install.sh
+set -e
+
+REPO="peg/rampart"
+INSTALL_DIR="${RAMPART_INSTALL_DIR:-}"
+BINARY="rampart"
+VERSION="${RAMPART_VERSION:-}"
+AUTO_SETUP="${RAMPART_AUTO_SETUP:-0}"
+DRY_RUN="${RAMPART_INSTALL_DRY_RUN:-0}"
+
+# Colors (if terminal supports them).
+if [ -t 1 ]; then
+ BOLD="\033[1m"
+ GREEN="\033[32m"
+ RED="\033[31m"
+ YELLOW="\033[33m"
+ RESET="\033[0m"
+else
+ BOLD="" GREEN="" RED="" YELLOW="" RESET=""
+fi
+
+info() { printf "${GREEN}▸${RESET} %s\n" "$1"; }
+warn() { printf "${YELLOW}▸${RESET} %s\n" "$1"; }
+error() { printf "${RED}✗${RESET} %s\n" "$1" >&2; exit 1; }
+
+fetch() { # fetch [dest]
+ URL="$1"
+ DEST="${2:-}"
+ if command -v curl >/dev/null 2>&1; then
+ if [ -n "$DEST" ]; then
+ curl -fsSL -o "$DEST" "$URL"
+ else
+ curl -fsSL "$URL"
+ fi
+ elif command -v wget >/dev/null 2>&1; then
+ if [ -n "$DEST" ]; then
+ wget -qO "$DEST" "$URL"
+ else
+ wget -qO- "$URL"
+ fi
+ else
+ error "Neither curl nor wget found. Install one and retry."
+ fi
+}
+
+# Parse args.
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --version) VERSION="$2"; shift 2 ;;
+ --version=*) VERSION="${1#--version=}"; shift ;;
+ --auto-setup) AUTO_SETUP=1; shift ;;
+ --dry-run) DRY_RUN=1; shift ;;
+ *) error "Unknown option: $1" ;;
+ esac
+done
+
+# Detect OS.
+OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
+case "$OS" in
+ linux) OS="linux" ;;
+ darwin) OS="darwin" ;;
+ *) error "Unsupported OS: $OS (need linux or darwin)" ;;
+esac
+
+# Detect architecture.
+ARCH="$(uname -m)"
+case "$ARCH" in
+ x86_64|amd64) ARCH="amd64" ;;
+ aarch64|arm64) ARCH="arm64" ;;
+ *) error "Unsupported architecture: $ARCH (need amd64 or arm64)" ;;
+esac
+
+info "Detected ${BOLD}${OS}/${ARCH}${RESET}"
+
+# Determine version.
+if [ -z "$VERSION" ]; then
+ info "Fetching latest version..."
+ VERSION=$(fetch "https://api.github.com/repos/${REPO}/releases/latest" \
+ | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
+ if [ -z "$VERSION" ]; then
+ error "Could not determine latest version. Try: --version v0.1.0"
+ fi
+fi
+
+# Normalise version: release tags include a leading "v".
+case "$VERSION" in
+ v*) ;;
+ *) VERSION="v${VERSION}" ;;
+esac
+
+info "Installing ${BOLD}rampart ${VERSION}${RESET}"
+
+if [ -z "$INSTALL_DIR" ]; then
+ if [ "$(id -u)" -eq 0 ]; then
+ INSTALL_DIR="/usr/local/bin"
+ elif [ -d "$HOME/.local/bin" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif [ "$DRY_RUN" = "1" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif mkdir -p "$HOME/.local/bin" 2>/dev/null; then
+ INSTALL_DIR="$HOME/.local/bin"
+ else
+ INSTALL_DIR="/usr/local/bin"
+ fi
+fi
+
+# Build download URLs.
+BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
+TARBALL="rampart_${VERSION#v}_${OS}_${ARCH}.tar.gz"
+TARBALL_URL="${BASE_URL}/${TARBALL}"
+CHECKSUM_URL="${BASE_URL}/checksums.txt"
+
+if [ "$DRY_RUN" = "1" ]; then
+ info "Dry-run — no changes will be made"
+ info "Would download: ${TARBALL_URL}"
+ info "Would verify: ${CHECKSUM_URL}"
+ info "Would install: ${INSTALL_DIR}/${BINARY}"
+ exit 0
+fi
+
+# Create temp directory.
+TMP_DIR="$(mktemp -d)"
+trap 'rm -rf "$TMP_DIR"' EXIT
+
+# Download archive.
+info "Downloading archive..."
+if ! fetch "$TARBALL_URL" "${TMP_DIR}/${TARBALL}"; then
+ error "Download failed. Check that ${VERSION} exists at:\n ${TARBALL_URL}"
+fi
+
+# Download and verify checksum.
+info "Verifying checksum..."
+if fetch "$CHECKSUM_URL" "${TMP_DIR}/checksums.txt" 2>/dev/null; then
+ EXPECTED=$(grep "${TARBALL}" "${TMP_DIR}/checksums.txt" | awk '{print $1}')
+ if command -v sha256sum >/dev/null 2>&1; then
+ ACTUAL=$(sha256sum "${TMP_DIR}/${TARBALL}" | awk '{print $1}')
+ elif command -v shasum >/dev/null 2>&1; then
+ ACTUAL=$(shasum -a 256 "${TMP_DIR}/${TARBALL}" | awk '{print $1}')
+ else
+ warn "No sha256sum or shasum found — skipping verification"
+ ACTUAL="$EXPECTED"
+ fi
+
+ if [ -z "$EXPECTED" ]; then
+ warn "No checksum entry found for ${TARBALL} — skipping verification"
+ elif [ "$EXPECTED" != "$ACTUAL" ]; then
+ error "Checksum mismatch!\n Expected: ${EXPECTED}\n Got: ${ACTUAL}"
+ else
+ info "Checksum verified ✓"
+ fi
+else
+ warn "No checksums.txt found — skipping verification"
+fi
+
+tar -xzf "${TMP_DIR}/${TARBALL}" -C "$TMP_DIR"
+
+if [ ! -f "${TMP_DIR}/${BINARY}" ]; then
+ error "Archive did not contain ${BINARY}"
+fi
+
+# Install.
+chmod +x "${TMP_DIR}/${BINARY}"
+
+if [ ! -d "$INSTALL_DIR" ]; then
+ if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then
+ info "Need sudo to create ${INSTALL_DIR}"
+ sudo mkdir -p "$INSTALL_DIR"
+ fi
+fi
+
+if [ -w "$INSTALL_DIR" ]; then
+ mv "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
+else
+ info "Need sudo to install to ${INSTALL_DIR}"
+ sudo mv "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
+fi
+
+info "Installed to ${BOLD}${INSTALL_DIR}/${BINARY}${RESET}"
+
+# Verify.
+RAMPART_BIN="${INSTALL_DIR}/${BINARY}"
+if [ -x "$RAMPART_BIN" ]; then
+ printf "\n"
+ "$RAMPART_BIN" version 2>/dev/null || true
+
+ if echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
+ printf "\n${GREEN}${BOLD}Ready!${RESET} Run ${BOLD}rampart quickstart${RESET} to get started.\n"
+ else
+ printf "\n${YELLOW}Note:${RESET} ${INSTALL_DIR} may not be in your PATH.\n"
+ printf "Add it: ${BOLD}export PATH=\"${INSTALL_DIR}:\$PATH\"${RESET}\n"
+ printf "Then run: ${BOLD}rampart quickstart${RESET}\n"
+ fi
+else
+ warn "Could not verify installed binary at ${INSTALL_DIR}/${BINARY}"
+fi
+
+# Detect AI agents and suggest setup commands.
+detect_agents_and_suggest() {
+ printf "\n"
+
+ # Detect OpenClaw
+ OPENCLAW_FOUND=0
+ if command -v openclaw >/dev/null 2>&1; then
+ OPENCLAW_FOUND=1
+ elif [ -f "$HOME/.local/bin/openclaw" ] || [ -f "/usr/local/bin/openclaw" ] || [ -f "/usr/bin/openclaw" ]; then
+ OPENCLAW_FOUND=1
+ fi
+
+ # Detect Claude Code (claude CLI)
+ CLAUDE_FOUND=0
+ if command -v claude >/dev/null 2>&1; then
+ CLAUDE_FOUND=1
+ elif [ -f "$HOME/.claude/settings.json" ]; then
+ CLAUDE_FOUND=1
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 1 ] || [ "$CLAUDE_FOUND" -eq 1 ]; then
+ printf "${GREEN}${BOLD}✓ AI agent(s) detected!${RESET}\n\n"
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 1 ]; then
+ if [ "$AUTO_SETUP" = "1" ]; then
+ printf "${GREEN}▸${RESET} Auto-setup: protecting OpenClaw...\n"
+ "$RAMPART_BIN" setup openclaw 2>&1 || printf "${YELLOW} ↳ Auto-setup failed — run manually: rampart setup openclaw${RESET}\n"
+ else
+ printf " Run this to protect your OpenClaw agent:\n"
+ printf " ${BOLD}rampart setup openclaw${RESET}\n"
+ printf "\n"
+ fi
+ fi
+
+ if [ "$CLAUDE_FOUND" -eq 1 ]; then
+ if [ "$AUTO_SETUP" = "1" ]; then
+ printf "${GREEN}▸${RESET} Auto-setup: protecting Claude Code...\n"
+ "$RAMPART_BIN" setup claude-code 2>&1 || printf "${YELLOW} ↳ Auto-setup failed — run manually: rampart setup claude-code${RESET}\n"
+ else
+ printf " Run this to protect Claude Code:\n"
+ printf " ${BOLD}rampart setup claude-code${RESET}\n"
+ printf "\n"
+ fi
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 0 ] && [ "$CLAUDE_FOUND" -eq 0 ]; then
+ printf " To protect an AI agent, run:\n"
+ printf " ${BOLD}rampart setup openclaw${RESET} — for OpenClaw\n"
+ printf " ${BOLD}rampart setup claude-code${RESET} — for Claude Code\n"
+ printf "\n"
+ fi
+}
+
+if [ -x "$RAMPART_BIN" ]; then
+ detect_agents_and_suggest
+fi
diff --git a/install.sh b/install.sh
index 1e1fd9da..86169596 100755
--- a/install.sh
+++ b/install.sh
@@ -1,15 +1,20 @@
#!/bin/sh
-# Rampart install script
+# Rampart install script.
+#
+# Canonical source for the website and legacy installer copies. Keep
+# docs/install, docs/install.sh, and scripts/install.sh byte-for-byte synced
+# with this file.
# Usage: curl -fsSL https://rampart.sh/install | sh
# curl -fsSL https://rampart.sh/install | sh -s -- --version v0.1.0
# curl -fsSL https://rampart.sh/install | sh -s -- --auto-setup
-# RAMPART_INSTALL_DRY_RUN=1 sh install.sh --version v1.0.0-rc.2
+# RAMPART_INSTALL_DRY_RUN=1 sh install.sh --version v1.0.0
+# RAMPART_VERSION=v1.0.0 RAMPART_INSTALL_DIR=$HOME/.local/bin sh install.sh
set -e
REPO="peg/rampart"
-INSTALL_DIR="/usr/local/bin"
+INSTALL_DIR="${RAMPART_INSTALL_DIR:-}"
BINARY="rampart"
-VERSION=""
+VERSION="${RAMPART_VERSION:-}"
AUTO_SETUP="${RAMPART_AUTO_SETUP:-0}"
DRY_RUN="${RAMPART_INSTALL_DRY_RUN:-0}"
@@ -28,6 +33,26 @@ info() { printf "${GREEN}▸${RESET} %s\n" "$1"; }
warn() { printf "${YELLOW}▸${RESET} %s\n" "$1"; }
error() { printf "${RED}✗${RESET} %s\n" "$1" >&2; exit 1; }
+fetch() { # fetch [dest]
+ URL="$1"
+ DEST="${2:-}"
+ if command -v curl >/dev/null 2>&1; then
+ if [ -n "$DEST" ]; then
+ curl -fsSL -o "$DEST" "$URL"
+ else
+ curl -fsSL "$URL"
+ fi
+ elif command -v wget >/dev/null 2>&1; then
+ if [ -n "$DEST" ]; then
+ wget -qO "$DEST" "$URL"
+ else
+ wget -qO- "$URL"
+ fi
+ else
+ error "Neither curl nor wget found. Install one and retry."
+ fi
+}
+
# Parse args.
while [ $# -gt 0 ]; do
case "$1" in
@@ -60,7 +85,7 @@ info "Detected ${BOLD}${OS}/${ARCH}${RESET}"
# Determine version.
if [ -z "$VERSION" ]; then
info "Fetching latest version..."
- VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
+ VERSION=$(fetch "https://api.github.com/repos/${REPO}/releases/latest" \
| grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
if [ -z "$VERSION" ]; then
error "Could not determine latest version. Try: --version v0.1.0"
@@ -75,16 +100,18 @@ esac
info "Installing ${BOLD}rampart ${VERSION}${RESET}"
-if [ "$(id -u)" -eq 0 ]; then
- INSTALL_DIR="/usr/local/bin"
-elif [ -d "$HOME/.local/bin" ]; then
- INSTALL_DIR="$HOME/.local/bin"
-elif [ "$DRY_RUN" = "1" ]; then
- INSTALL_DIR="$HOME/.local/bin"
-elif mkdir -p "$HOME/.local/bin" 2>/dev/null; then
- INSTALL_DIR="$HOME/.local/bin"
-else
- INSTALL_DIR="/usr/local/bin"
+if [ -z "$INSTALL_DIR" ]; then
+ if [ "$(id -u)" -eq 0 ]; then
+ INSTALL_DIR="/usr/local/bin"
+ elif [ -d "$HOME/.local/bin" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif [ "$DRY_RUN" = "1" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif mkdir -p "$HOME/.local/bin" 2>/dev/null; then
+ INSTALL_DIR="$HOME/.local/bin"
+ else
+ INSTALL_DIR="/usr/local/bin"
+ fi
fi
# Build download URLs.
@@ -107,13 +134,13 @@ trap 'rm -rf "$TMP_DIR"' EXIT
# Download archive.
info "Downloading archive..."
-if ! curl -fsSL -o "${TMP_DIR}/${TARBALL}" "$TARBALL_URL"; then
+if ! fetch "$TARBALL_URL" "${TMP_DIR}/${TARBALL}"; then
error "Download failed. Check that ${VERSION} exists at:\n ${TARBALL_URL}"
fi
# Download and verify checksum.
info "Verifying checksum..."
-if curl -fsSL -o "${TMP_DIR}/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then
+if fetch "$CHECKSUM_URL" "${TMP_DIR}/checksums.txt" 2>/dev/null; then
EXPECTED=$(grep "${TARBALL}" "${TMP_DIR}/checksums.txt" | awk '{print $1}')
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "${TMP_DIR}/${TARBALL}" | awk '{print $1}')
@@ -144,6 +171,13 @@ fi
# Install.
chmod +x "${TMP_DIR}/${BINARY}"
+if [ ! -d "$INSTALL_DIR" ]; then
+ if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then
+ info "Need sudo to create ${INSTALL_DIR}"
+ sudo mkdir -p "$INSTALL_DIR"
+ fi
+fi
+
if [ -w "$INSTALL_DIR" ]; then
mv "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
else
diff --git a/internal/plugin/openclaw/index.js b/internal/plugin/openclaw/index.js
index 3b92d31b..1d2a53d8 100644
--- a/internal/plugin/openclaw/index.js
+++ b/internal/plugin/openclaw/index.js
@@ -5,7 +5,7 @@
* Replaces brittle dist-file patching with the official OpenClaw plugin API.
*
* @see https://github.com/peg/rampart
- * @version 0.1.0
+ * @version 1.0.0
*/
import { readFile } from "fs/promises";
@@ -212,7 +212,7 @@ async function auditLog(toolName, params, ctx, outcome, config) {
export const id = "rampart";
export const name = "Rampart";
export const description = "AI agent firewall — YAML policy-as-code for every tool call";
-export const version = "1.0.0-rc.2";
+export const version = "1.0.0";
// OpenClaw runs higher-priority before_tool_call hooks first. Rampart should
// act as the final normal plugin gate so it evaluates the params that will
diff --git a/internal/plugin/openclaw/openclaw.plugin.json b/internal/plugin/openclaw/openclaw.plugin.json
index b56b1b7b..2e92ae30 100644
--- a/internal/plugin/openclaw/openclaw.plugin.json
+++ b/internal/plugin/openclaw/openclaw.plugin.json
@@ -8,7 +8,7 @@
"hook"
]
},
- "version": "1.0.0-rc.2",
+ "version": "1.0.0",
"author": "peg",
"homepage": "https://rampart.sh",
"repository": "https://github.com/peg/rampart",
diff --git a/internal/plugin/openclaw/package.json b/internal/plugin/openclaw/package.json
index d6c22f61..120f7245 100644
--- a/internal/plugin/openclaw/package.json
+++ b/internal/plugin/openclaw/package.json
@@ -1,6 +1,6 @@
{
"name": "rampart",
- "version": "1.0.0-rc.2",
+ "version": "1.0.0",
"description": "Rampart AI agent firewall — OpenClaw native plugin (before_tool_call hook)",
"type": "module",
"main": "index.js",
diff --git a/mkdocs.yml b/mkdocs.yml
index c1db0cf3..86e37f02 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -8,6 +8,12 @@ edit_uri: edit/main/docs-site/
docs_dir: docs-site
+# docs-site/index.html is the standalone rampart.sh landing page source, not a
+# MkDocs page. Exclude it explicitly so docs builds don't warn about the
+# intentional index.md/index.html collision.
+exclude_docs: |
+ index.html
+
theme:
name: material
custom_dir: docs-site/overrides
diff --git a/scripts/install.sh b/scripts/install.sh
index c8dd02bc..86169596 100644
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -1,151 +1,263 @@
#!/bin/sh
-# Rampart installer
-# Usage: curl -fsSL https://rampart.sh/install | bash
-# curl -fsSL https://rampart.sh/install | RAMPART_VERSION=v0.3.0 bash
+# Rampart install script.
#
-# Env vars:
-# RAMPART_VERSION — pin a version (default: latest)
-# RAMPART_INSTALL_DIR — install directory (default: ~/.local/bin)
-# RAMPART_INSTALL_DRY_RUN — set to 1 to print actions without running them
+# Canonical source for the website and legacy installer copies. Keep
+# docs/install, docs/install.sh, and scripts/install.sh byte-for-byte synced
+# with this file.
+# Usage: curl -fsSL https://rampart.sh/install | sh
+# curl -fsSL https://rampart.sh/install | sh -s -- --version v0.1.0
+# curl -fsSL https://rampart.sh/install | sh -s -- --auto-setup
+# RAMPART_INSTALL_DRY_RUN=1 sh install.sh --version v1.0.0
+# RAMPART_VERSION=v1.0.0 RAMPART_INSTALL_DIR=$HOME/.local/bin sh install.sh
set -e
REPO="peg/rampart"
-INSTALL_DIR="${RAMPART_INSTALL_DIR:-$HOME/.local/bin}"
+INSTALL_DIR="${RAMPART_INSTALL_DIR:-}"
+BINARY="rampart"
VERSION="${RAMPART_VERSION:-}"
+AUTO_SETUP="${RAMPART_AUTO_SETUP:-0}"
DRY_RUN="${RAMPART_INSTALL_DRY_RUN:-0}"
-# ── Colors ────────────────────────────────────────────────────────────────────
+# Colors (if terminal supports them).
if [ -t 1 ]; then
- BOLD="\033[1m"; GREEN="\033[32m"; YELLOW="\033[33m"; RED="\033[31m"; RESET="\033[0m"
+ BOLD="\033[1m"
+ GREEN="\033[32m"
+ RED="\033[31m"
+ YELLOW="\033[33m"
+ RESET="\033[0m"
else
- BOLD=""; GREEN=""; YELLOW=""; RED=""; RESET=""
+ BOLD="" GREEN="" RED="" YELLOW="" RESET=""
fi
info() { printf "${GREEN}▸${RESET} %s\n" "$1"; }
-warn() { printf "${YELLOW}▸${RESET} %s\n" "$1" >&2; }
+warn() { printf "${YELLOW}▸${RESET} %s\n" "$1"; }
error() { printf "${RED}✗${RESET} %s\n" "$1" >&2; exit 1; }
-step() { printf "\n${BOLD}%s${RESET}\n" "$1"; }
-dry() { printf "${YELLOW}[dry-run]${RESET} %s\n" "$1"; }
-# ── Downloader ─────────────────────────────────────────────────────────────────
-fetch() { # fetch [dest]
- URL="$1"; DEST="$2"
+fetch() { # fetch [dest]
+ URL="$1"
+ DEST="${2:-}"
if command -v curl >/dev/null 2>&1; then
- if [ -n "$DEST" ]; then curl -fsSL -o "$DEST" "$URL"
- else curl -fsSL "$URL"; fi
+ if [ -n "$DEST" ]; then
+ curl -fsSL -o "$DEST" "$URL"
+ else
+ curl -fsSL "$URL"
+ fi
elif command -v wget >/dev/null 2>&1; then
- if [ -n "$DEST" ]; then wget -qO "$DEST" "$URL"
- else wget -qO- "$URL"; fi
+ if [ -n "$DEST" ]; then
+ wget -qO "$DEST" "$URL"
+ else
+ wget -qO- "$URL"
+ fi
else
error "Neither curl nor wget found. Install one and retry."
fi
}
-# ── OS / Arch ──────────────────────────────────────────────────────────────────
+# Parse args.
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --version) VERSION="$2"; shift 2 ;;
+ --version=*) VERSION="${1#--version=}"; shift ;;
+ --auto-setup) AUTO_SETUP=1; shift ;;
+ --dry-run) DRY_RUN=1; shift ;;
+ *) error "Unknown option: $1" ;;
+ esac
+done
+
+# Detect OS.
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
- linux) OS="linux" ;;
+ linux) OS="linux" ;;
darwin) OS="darwin" ;;
- *) error "Unsupported OS: $(uname -s). Only linux and darwin are supported." ;;
+ *) error "Unsupported OS: $OS (need linux or darwin)" ;;
esac
+# Detect architecture.
ARCH="$(uname -m)"
case "$ARCH" in
- x86_64|amd64) ARCH="amd64" ;;
- aarch64|arm64) ARCH="arm64" ;;
- *) error "Unsupported architecture: $(uname -m). Only amd64 and arm64 are supported." ;;
+ x86_64|amd64) ARCH="amd64" ;;
+ aarch64|arm64) ARCH="arm64" ;;
+ *) error "Unsupported architecture: $ARCH (need amd64 or arm64)" ;;
esac
-info "Platform: ${BOLD}${OS}/${ARCH}${RESET}"
+info "Detected ${BOLD}${OS}/${ARCH}${RESET}"
-# ── Resolve version ────────────────────────────────────────────────────────────
+# Determine version.
if [ -z "$VERSION" ]; then
- info "Fetching latest release..."
- VERSION="$(fetch "https://api.github.com/repos/${REPO}/releases/latest" \
- | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')"
- [ -n "$VERSION" ] || error "Could not determine latest version. Set RAMPART_VERSION=vX.Y.Z and retry."
+ info "Fetching latest version..."
+ VERSION=$(fetch "https://api.github.com/repos/${REPO}/releases/latest" \
+ | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
+ if [ -z "$VERSION" ]; then
+ error "Could not determine latest version. Try: --version v0.1.0"
+ fi
fi
-# Normalise: ensure leading 'v'
+# Normalise version: release tags include a leading "v".
case "$VERSION" in
v*) ;;
*) VERSION="v${VERSION}" ;;
esac
-info "Version: ${BOLD}${VERSION}${RESET}"
+info "Installing ${BOLD}rampart ${VERSION}${RESET}"
-# ── Build URLs ─────────────────────────────────────────────────────────────────
-TARBALL="rampart_${VERSION#v}_${OS}_${ARCH}.tar.gz"
+if [ -z "$INSTALL_DIR" ]; then
+ if [ "$(id -u)" -eq 0 ]; then
+ INSTALL_DIR="/usr/local/bin"
+ elif [ -d "$HOME/.local/bin" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif [ "$DRY_RUN" = "1" ]; then
+ INSTALL_DIR="$HOME/.local/bin"
+ elif mkdir -p "$HOME/.local/bin" 2>/dev/null; then
+ INSTALL_DIR="$HOME/.local/bin"
+ else
+ INSTALL_DIR="/usr/local/bin"
+ fi
+fi
+
+# Build download URLs.
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
+TARBALL="rampart_${VERSION#v}_${OS}_${ARCH}.tar.gz"
TARBALL_URL="${BASE_URL}/${TARBALL}"
CHECKSUM_URL="${BASE_URL}/checksums.txt"
if [ "$DRY_RUN" = "1" ]; then
- step "Dry-run — no changes will be made"
- dry "Would download: ${TARBALL_URL}"
- dry "Would install: ${INSTALL_DIR}/rampart"
- dry "Would run: rampart quickstart"
- if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
- dry "Would hint to add ${INSTALL_DIR} to PATH"
- fi
+ info "Dry-run — no changes will be made"
+ info "Would download: ${TARBALL_URL}"
+ info "Would verify: ${CHECKSUM_URL}"
+ info "Would install: ${INSTALL_DIR}/${BINARY}"
exit 0
fi
-# ── Download & extract ─────────────────────────────────────────────────────────
-step "Downloading rampart ${VERSION}..."
-
+# Create temp directory.
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
-fetch "$TARBALL_URL" "${TMP_DIR}/${TARBALL}" \
- || error "Download failed.\nURL: ${TARBALL_URL}\nCheck that ${VERSION} exists: https://github.com/${REPO}/releases"
+# Download archive.
+info "Downloading archive..."
+if ! fetch "$TARBALL_URL" "${TMP_DIR}/${TARBALL}"; then
+ error "Download failed. Check that ${VERSION} exists at:\n ${TARBALL_URL}"
+fi
-# Optional checksum verification
+# Download and verify checksum.
+info "Verifying checksum..."
if fetch "$CHECKSUM_URL" "${TMP_DIR}/checksums.txt" 2>/dev/null; then
+ EXPECTED=$(grep "${TARBALL}" "${TMP_DIR}/checksums.txt" | awk '{print $1}')
if command -v sha256sum >/dev/null 2>&1; then
- HASH_CMD="sha256sum"
+ ACTUAL=$(sha256sum "${TMP_DIR}/${TARBALL}" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
- HASH_CMD="shasum -a 256"
+ ACTUAL=$(shasum -a 256 "${TMP_DIR}/${TARBALL}" | awk '{print $1}')
else
- HASH_CMD=""
+ warn "No sha256sum or shasum found — skipping verification"
+ ACTUAL="$EXPECTED"
fi
- if [ -n "$HASH_CMD" ]; then
- EXPECTED="$(grep "${TARBALL}" "${TMP_DIR}/checksums.txt" | awk '{print $1}')"
- ACTUAL="$($HASH_CMD "${TMP_DIR}/${TARBALL}" | awk '{print $1}')"
- if [ -n "$EXPECTED" ] && [ "$EXPECTED" != "$ACTUAL" ]; then
- error "Checksum mismatch!\n Expected: ${EXPECTED}\n Got: ${ACTUAL}"
- fi
+
+ if [ -z "$EXPECTED" ]; then
+ warn "No checksum entry found for ${TARBALL} — skipping verification"
+ elif [ "$EXPECTED" != "$ACTUAL" ]; then
+ error "Checksum mismatch!\n Expected: ${EXPECTED}\n Got: ${ACTUAL}"
+ else
info "Checksum verified ✓"
fi
+else
+ warn "No checksums.txt found — skipping verification"
fi
tar -xzf "${TMP_DIR}/${TARBALL}" -C "$TMP_DIR"
-# ── Install ────────────────────────────────────────────────────────────────────
-step "Installing to ${INSTALL_DIR}..."
+if [ ! -f "${TMP_DIR}/${BINARY}" ]; then
+ error "Archive did not contain ${BINARY}"
+fi
-mkdir -p "$INSTALL_DIR"
-mv "${TMP_DIR}/rampart" "${INSTALL_DIR}/rampart"
-chmod +x "${INSTALL_DIR}/rampart"
+# Install.
+chmod +x "${TMP_DIR}/${BINARY}"
-info "Installed: ${BOLD}${INSTALL_DIR}/rampart${RESET}"
+if [ ! -d "$INSTALL_DIR" ]; then
+ if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then
+ info "Need sudo to create ${INSTALL_DIR}"
+ sudo mkdir -p "$INSTALL_DIR"
+ fi
+fi
-# PATH hint
-if ! echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
- warn "${INSTALL_DIR} is not in your PATH."
- warn "Add this to your shell profile (~/.bashrc, ~/.zshrc, etc.):"
- printf " ${BOLD}export PATH=\"\$HOME/.local/bin:\$PATH\"${RESET}\n" >&2
- export PATH="${INSTALL_DIR}:${PATH}"
+if [ -w "$INSTALL_DIR" ]; then
+ mv "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
+else
+ info "Need sudo to install to ${INSTALL_DIR}"
+ sudo mv "${TMP_DIR}/${BINARY}" "${INSTALL_DIR}/${BINARY}"
fi
-# ── Quickstart ─────────────────────────────────────────────────────────────────
-step "Running quickstart..."
+info "Installed to ${BOLD}${INSTALL_DIR}/${BINARY}${RESET}"
-if [ -t 0 ] && [ -t 1 ]; then
- rampart quickstart
+# Verify.
+RAMPART_BIN="${INSTALL_DIR}/${BINARY}"
+if [ -x "$RAMPART_BIN" ]; then
+ printf "\n"
+ "$RAMPART_BIN" version 2>/dev/null || true
+
+ if echo ":${PATH}:" | grep -q ":${INSTALL_DIR}:"; then
+ printf "\n${GREEN}${BOLD}Ready!${RESET} Run ${BOLD}rampart quickstart${RESET} to get started.\n"
+ else
+ printf "\n${YELLOW}Note:${RESET} ${INSTALL_DIR} may not be in your PATH.\n"
+ printf "Add it: ${BOLD}export PATH=\"${INSTALL_DIR}:\$PATH\"${RESET}\n"
+ printf "Then run: ${BOLD}rampart quickstart${RESET}\n"
+ fi
else
- rampart quickstart --env none 2>/dev/null || true
+ warn "Could not verify installed binary at ${INSTALL_DIR}/${BINARY}"
fi
-printf "\n${GREEN}${BOLD}Done!${RESET} rampart ${VERSION} is installed.\n"
-printf "Docs: ${BOLD}https://docs.rampart.sh${RESET}\n\n"
+# Detect AI agents and suggest setup commands.
+detect_agents_and_suggest() {
+ printf "\n"
+
+ # Detect OpenClaw
+ OPENCLAW_FOUND=0
+ if command -v openclaw >/dev/null 2>&1; then
+ OPENCLAW_FOUND=1
+ elif [ -f "$HOME/.local/bin/openclaw" ] || [ -f "/usr/local/bin/openclaw" ] || [ -f "/usr/bin/openclaw" ]; then
+ OPENCLAW_FOUND=1
+ fi
+
+ # Detect Claude Code (claude CLI)
+ CLAUDE_FOUND=0
+ if command -v claude >/dev/null 2>&1; then
+ CLAUDE_FOUND=1
+ elif [ -f "$HOME/.claude/settings.json" ]; then
+ CLAUDE_FOUND=1
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 1 ] || [ "$CLAUDE_FOUND" -eq 1 ]; then
+ printf "${GREEN}${BOLD}✓ AI agent(s) detected!${RESET}\n\n"
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 1 ]; then
+ if [ "$AUTO_SETUP" = "1" ]; then
+ printf "${GREEN}▸${RESET} Auto-setup: protecting OpenClaw...\n"
+ "$RAMPART_BIN" setup openclaw 2>&1 || printf "${YELLOW} ↳ Auto-setup failed — run manually: rampart setup openclaw${RESET}\n"
+ else
+ printf " Run this to protect your OpenClaw agent:\n"
+ printf " ${BOLD}rampart setup openclaw${RESET}\n"
+ printf "\n"
+ fi
+ fi
+
+ if [ "$CLAUDE_FOUND" -eq 1 ]; then
+ if [ "$AUTO_SETUP" = "1" ]; then
+ printf "${GREEN}▸${RESET} Auto-setup: protecting Claude Code...\n"
+ "$RAMPART_BIN" setup claude-code 2>&1 || printf "${YELLOW} ↳ Auto-setup failed — run manually: rampart setup claude-code${RESET}\n"
+ else
+ printf " Run this to protect Claude Code:\n"
+ printf " ${BOLD}rampart setup claude-code${RESET}\n"
+ printf "\n"
+ fi
+ fi
+
+ if [ "$OPENCLAW_FOUND" -eq 0 ] && [ "$CLAUDE_FOUND" -eq 0 ]; then
+ printf " To protect an AI agent, run:\n"
+ printf " ${BOLD}rampart setup openclaw${RESET} — for OpenClaw\n"
+ printf " ${BOLD}rampart setup claude-code${RESET} — for Claude Code\n"
+ printf "\n"
+ fi
+}
+
+if [ -x "$RAMPART_BIN" ]; then
+ detect_agents_and_suggest
+fi
From 76319f08613ead3b6da2b08e2e000846b0321bc2 Mon Sep 17 00:00:00 2001
From: Trevor <70075744+peg@users.noreply.github.com>
Date: Tue, 5 May 2026 19:35:38 -0700
Subject: [PATCH 2/7] ci: smoke test Docker before releases (#304)
Co-authored-by: clap [bot]
---
.github/workflows/ci.yml | 56 +++++++++++++++++++++++++++++++++++-
.github/workflows/docker.yml | 46 ++++++++++++++++++-----------
2 files changed, 84 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f1ae9a40..3d6f23b7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -76,11 +76,65 @@ jobs:
run: go test -bench=. -benchmem ./internal/engine/ ./internal/audit/
- name: Upload coverage
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
name: coverage-${{ matrix.os }}
path: coverage.out
+ docker-smoke:
+ name: docker smoke (amd64 runtime + arm64 build)
+ runs-on: ubuntu-latest
+ needs: test
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: docker/setup-buildx-action@v4
+
+ - name: Build amd64 image
+ run: |
+ set -euo pipefail
+ docker buildx build \
+ --platform linux/amd64 \
+ --load \
+ --build-arg VERSION=ci-smoke \
+ --build-arg COMMIT=${GITHUB_SHA::12} \
+ --build-arg DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
+ -t rampart:ci-smoke \
+ .
+
+ - name: Smoke test amd64 image
+ run: |
+ set -euo pipefail
+
+ docker run --rm rampart:ci-smoke version | tee /tmp/rampart-docker-version.txt
+ grep -F "rampart ci-smoke" /tmp/rampart-docker-version.txt
+
+ cid="$(docker run -d -p 19090:9090 rampart:ci-smoke)"
+ trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT
+
+ for _ in $(seq 1 20); do
+ if curl -fsS http://127.0.0.1:19090/healthz >/dev/null 2>&1; then
+ exit 0
+ fi
+ sleep 1
+ done
+
+ docker logs "$cid"
+ exit 1
+
+ - name: Build arm64 image
+ run: |
+ set -euo pipefail
+ docker buildx build \
+ --platform linux/arm64 \
+ --build-arg VERSION=ci-smoke \
+ --build-arg COMMIT=${GITHUB_SHA::12} \
+ --build-arg DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
+ --output=type=cacheonly \
+ .
+
release-dry-run:
name: goreleaser snapshot (cross-platform build check)
runs-on: ubuntu-latest
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 6eeba897..4fd5838c 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -17,16 +17,8 @@ jobs:
steps:
- uses: actions/checkout@v5
- - uses: docker/setup-qemu-action@v4
-
- uses: docker/setup-buildx-action@v4
- - uses: docker/login-action@v4
- with:
- registry: ghcr.io
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- uses: docker/metadata-action@v6
id: meta
with:
@@ -43,23 +35,22 @@ jobs:
echo "commit=${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
- - uses: docker/build-push-action@v7
+ - name: Build local image for smoke test
+ uses: docker/build-push-action@v7
with:
context: .
- platforms: linux/amd64,linux/arm64
- push: true
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
+ platforms: linux/amd64
+ load: true
+ tags: rampart:release-smoke
build-args: |
VERSION=${{ steps.build.outputs.version }}
COMMIT=${{ steps.build.outputs.commit }}
DATE=${{ steps.build.outputs.date }}
cache-from: type=gha
- cache-to: type=gha,mode=max
- - name: Smoke test Docker image
+ - name: Smoke test local Docker image
env:
- IMAGE: ghcr.io/peg/rampart:${{ steps.build.outputs.version }}
+ IMAGE: rampart:release-smoke
VERSION: ${{ steps.build.outputs.version }}
run: |
set -euo pipefail
@@ -71,7 +62,7 @@ jobs:
trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT
for _ in $(seq 1 20); do
- if curl -fsS http://127.0.0.1:19090/healthz >/dev/null; then
+ if curl -fsS http://127.0.0.1:19090/healthz >/dev/null 2>&1; then
exit 0
fi
sleep 1
@@ -80,6 +71,27 @@ jobs:
docker logs "$cid"
exit 1
+ - uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push multi-arch image
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ build-args: |
+ VERSION=${{ steps.build.outputs.version }}
+ COMMIT=${{ steps.build.outputs.commit }}
+ DATE=${{ steps.build.outputs.date }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
- name: Verify multi-arch manifest
env:
IMAGE: ghcr.io/peg/rampart:${{ steps.build.outputs.version }}
From ed28ece97946468677a69bc237dd73b45e403ce5 Mon Sep 17 00:00:00 2001
From: Trevor <70075744+peg@users.noreply.github.com>
Date: Tue, 5 May 2026 19:45:16 -0700
Subject: [PATCH 3/7] ci: quiet workflow runtime warnings (#305)
Co-authored-by: clap [bot]
---
.github/workflows/ci.yml | 11 ++++-------
.github/workflows/community-policy-ci.yml | 5 +----
.github/workflows/docker.yml | 5 +----
.github/workflows/docs.yml | 5 +----
.github/workflows/release.yml | 5 +----
.github/workflows/render-diagrams.yml | 5 +----
6 files changed, 9 insertions(+), 27 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3d6f23b7..04b3aaaf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,8 +1,5 @@
name: CI
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
-
on:
push:
branches: [main, staging]
@@ -17,11 +14,11 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
+ os: [ubuntu-latest, macos-latest, windows-2022]
go-version: ['1.25.9']
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
@@ -88,7 +85,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
@@ -142,7 +139,7 @@ jobs:
permissions:
contents: read
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
diff --git a/.github/workflows/community-policy-ci.yml b/.github/workflows/community-policy-ci.yml
index cc8d9cb4..357f9f33 100644
--- a/.github/workflows/community-policy-ci.yml
+++ b/.github/workflows/community-policy-ci.yml
@@ -1,8 +1,5 @@
name: Community Policy CI
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
-
on:
pull_request:
paths:
@@ -17,7 +14,7 @@ jobs:
validate:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 4fd5838c..0ebfe407 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -1,8 +1,5 @@
name: Docker
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
-
on:
push:
tags: ['v*']
@@ -15,7 +12,7 @@ jobs:
docker:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 0195da1e..b1c74568 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,8 +1,5 @@
name: Deploy Docs
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
-
on:
workflow_dispatch:
push:
@@ -19,7 +16,7 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index eb97030c..e8be2ca0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,8 +1,5 @@
name: Release
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
-
on:
push:
tags:
@@ -15,7 +12,7 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
diff --git a/.github/workflows/render-diagrams.yml b/.github/workflows/render-diagrams.yml
index 6b2b7576..f079324e 100644
--- a/.github/workflows/render-diagrams.yml
+++ b/.github/workflows/render-diagrams.yml
@@ -1,8 +1,5 @@
name: Render Diagrams
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
-
on:
push:
branches: [main, staging]
@@ -16,7 +13,7 @@ jobs:
render:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v6
- name: Install D2
run: curl -fsSL https://d2lang.com/install.sh | sh -s --
From 0c1c1d1ecde8b740240495e9fdf1a8a8c843fa4a Mon Sep 17 00:00:00 2001
From: "clap [bot]"
Date: Wed, 6 May 2026 18:10:16 +0000
Subject: [PATCH 4/7] docs: finalize 1.0 changelog section
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5edf19c6..84a94eb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [1.0.0] - 2026-05-06
+
### Fixed
- **Docker images now boot and report release metadata** — The Dockerfile uses the current `serve --addr/--port` flags, injects version/commit/date ldflags, aligns its Go toolchain with the release workflow, and sets a writable runtime home for the nonroot distroless container.
From 3845b7d2b7b252322e13a0e8c7142426e90eb335 Mon Sep 17 00:00:00 2001
From: "clap [bot]"
Date: Wed, 6 May 2026 18:25:00 +0000
Subject: [PATCH 5/7] fix: align OpenClaw plugin license metadata
---
internal/plugin/openclaw/embed_test.go | 4 ++++
internal/plugin/openclaw/package.json | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/internal/plugin/openclaw/embed_test.go b/internal/plugin/openclaw/embed_test.go
index b681f8d6..981a20ed 100644
--- a/internal/plugin/openclaw/embed_test.go
+++ b/internal/plugin/openclaw/embed_test.go
@@ -40,6 +40,7 @@ func TestPackageDeclaresInstallHostFloor(t *testing.T) {
}
var pkg struct {
Version string `json:"version"`
+ License string `json:"license"`
OpenClaw struct {
Install struct {
MinHostVersion string `json:"minHostVersion"`
@@ -55,6 +56,9 @@ func TestPackageDeclaresInstallHostFloor(t *testing.T) {
if got, want := pkg.Version, Version(); got != want {
t.Fatalf("package.json version = %q, want Version() %q", got, want)
}
+ if got, want := pkg.License, "Apache-2.0"; got != want {
+ t.Fatalf("package.json license = %q, want %q", got, want)
+ }
}
func TestManifestDeclaresDegradedModeConfig(t *testing.T) {
diff --git a/internal/plugin/openclaw/package.json b/internal/plugin/openclaw/package.json
index 120f7245..5676b55b 100644
--- a/internal/plugin/openclaw/package.json
+++ b/internal/plugin/openclaw/package.json
@@ -11,7 +11,7 @@
"security",
"plugin"
],
- "license": "MIT",
+ "license": "Apache-2.0",
"openclaw": {
"extensions": [
"./index.js"
From 0f8d467fb340e826ad084544c14070fdd9008f68 Mon Sep 17 00:00:00 2001
From: "clap [bot]"
Date: Wed, 6 May 2026 18:36:06 +0000
Subject: [PATCH 6/7] docs: update 1.0 security support metadata
---
SECURITY.md | 6 +++---
docs-site/integrations/openclaw.md | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/SECURITY.md b/SECURITY.md
index 1f583057..ee304f93 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,9 +4,9 @@
| Version | Supported |
|---------|--------------------|
-| 0.7.x | ✅ Current release |
-| 0.6.x | ⚠️ Critical fixes only |
-| < 0.6 | ❌ No longer supported |
+| 1.0.x | ✅ Current release |
+| 0.9.x | ⚠️ Critical fixes only |
+| < 0.9 | ❌ No longer supported |
## Reporting a Vulnerability
diff --git a/docs-site/integrations/openclaw.md b/docs-site/integrations/openclaw.md
index f0224d78..a5e2777f 100644
--- a/docs-site/integrations/openclaw.md
+++ b/docs-site/integrations/openclaw.md
@@ -157,7 +157,7 @@ Or check plugin status directly:
```bash
openclaw plugins list
-# rampart v0.9.18 active
+# rampart v1.0.0 active
```
## Troubleshooting
From d1d08f3d82419bbb0d0670eea69a7d35c5a8de3b Mon Sep 17 00:00:00 2001
From: "clap [bot]"
Date: Wed, 6 May 2026 20:42:02 +0000
Subject: [PATCH 7/7] fix: flag stale OpenClaw plugin prereleases
---
cmd/rampart/cli/doctor.go | 20 ++++++--------------
cmd/rampart/cli/doctor_test.go | 3 +++
docs-site/index.md | 2 +-
docs-site/integrations/openclaw.md | 2 +-
docs/ROADMAP.md | 2 +-
5 files changed, 12 insertions(+), 17 deletions(-)
diff --git a/cmd/rampart/cli/doctor.go b/cmd/rampart/cli/doctor.go
index b1768d11..a9a965f9 100644
--- a/cmd/rampart/cli/doctor.go
+++ b/cmd/rampart/cli/doctor.go
@@ -1635,23 +1635,15 @@ func normalizedReleaseVersion(version string) (string, bool) {
if strings.Contains(version, "-g") {
return "", false
}
- base := strings.SplitN(version, "+", 2)[0]
- version = strings.SplitN(base, "-", 2)[0]
- parts := strings.Split(version, ".")
- if len(parts) != 3 {
+ parsed, ok := parseReleaseVersion(version)
+ if !ok {
return "", false
}
- for _, part := range parts {
- if part == "" {
- return "", false
- }
- for _, r := range part {
- if r < '0' || r > '9' {
- return "", false
- }
- }
+ normalized := fmt.Sprintf("%d.%d.%d", parsed.major, parsed.minor, parsed.patch)
+ if len(parsed.prerelease) > 0 {
+ normalized += "-" + strings.Join(parsed.prerelease, ".")
}
- return version, true
+ return normalized, true
}
func isGoPseudoVersion(version string) bool {
diff --git a/cmd/rampart/cli/doctor_test.go b/cmd/rampart/cli/doctor_test.go
index b27e3696..89191c3f 100644
--- a/cmd/rampart/cli/doctor_test.go
+++ b/cmd/rampart/cli/doctor_test.go
@@ -682,6 +682,9 @@ func TestPluginVersionMatchesBuildVersion(t *testing.T) {
{"0.9.22", "0.9.22", true},
{"0.9.22", "v0.9.23", false},
{"1.0.0-rc.1", "v1.0.0-rc.1", true},
+ {"1.0.0-rc.2", "v1.0.0-rc.3", false},
+ {"1.0.0-rc.2", "v1.0.0", false},
+ {"1.0.0", "v1.0.0-rc.2", false},
{"0.9.22", "v1.0.0-rc.1", false},
{"0.9.22", "v0.9.22-staging-47fa0cf", true},
{"0.9.22", "v0.9.22-33-g47fa0cf", true},
diff --git a/docs-site/index.md b/docs-site/index.md
index 83096289..1a511693 100644
--- a/docs-site/index.md
+++ b/docs-site/index.md
@@ -208,7 +208,7 @@ verify -> outcomes.approval
## What's New in v1.0
- **Update checks are sane** — `rampart doctor` understands the 1.0 release line and no longer suggests downgrading release candidates to the older stable `v0.9.22` release.
-- **OpenClaw 2026.5.4 verified for launch** — Rampart uses OpenClaw's first-class plugin approval path as the single human-approval owner, with Rampart handling policy, audit, and durable allow-always persistence. [Details →](integrations/openclaw.md)
+- **OpenClaw 2026.5.6 verified for launch** — Rampart uses OpenClaw's first-class plugin approval path as the single human-approval owner, with Rampart handling policy, audit, and durable allow-always persistence. [Details →](integrations/openclaw.md)
- **Degraded mode is explicit** — sensitive OpenClaw tools block when `rampart serve` is unavailable, while only configured lower-risk `failOpenTools` may proceed.
- **Setup and doctor are launch-strict** — `rampart setup openclaw` installs the native plugin cleanly, repairs approval-hardening drift, and `rampart doctor` checks plugin state, serve reachability, approval timeout alignment, and version coherence.
- **Matching and bypass regressions are tighter** — shell-wrapper normalization, URL/domain handling, path matching, and OpenClaw plugin approval/degraded-mode tests now cover the hard edges found during the 1.0 RC pass.
diff --git a/docs-site/integrations/openclaw.md b/docs-site/integrations/openclaw.md
index a5e2777f..0a66ab26 100644
--- a/docs-site/integrations/openclaw.md
+++ b/docs-site/integrations/openclaw.md
@@ -16,7 +16,7 @@ For sensitive tools, the recommended operating assumption is simple: if Rampart
- **OpenClaw 2026.4.29 - 2026.5.1**: Supported for native plugin startup/interception; plugin approval delivery was not the launch baseline.
- **OpenClaw 2026.3.28 - 2026.4.28**: Native plugin works for tool enforcement, but Rampart's polished approval path is supported on newer OpenClaw builds.
- **OpenClaw < 2026.3.28**: Legacy shim + bridge — exec-only coverage, requires re-patching after upgrades.
- - **Verified 1.0 launch dogfood on**: OpenClaw 2026.5.4
+ - **Verified 1.0 launch dogfood on**: OpenClaw 2026.5.6
`rampart setup openclaw` auto-detects your version and uses the right method.
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 43cba714..cfdaeaff 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -47,7 +47,7 @@ What's coming next for Rampart. Priorities shift based on feedback — [open an
### `v1.0.0`
- Keep the integration support story boring and evidence-backed: hooks, plugins, preload/wrapper, MCP, and HTTP API should each say what is protected and what happens when policy evaluation is unavailable.
-- Treat OpenClaw `2026.5.2+` as the recommended 1.0 path for native plugin approvals, with launch dogfood verified on OpenClaw `2026.5.4`.
+- Treat OpenClaw `2026.5.2+` as the recommended 1.0 path for native plugin approvals, with launch dogfood verified on OpenClaw `2026.5.6`.
- Keep `rampart doctor`, setup output, plugin metadata, and docs aligned so users can answer "am I protected, how, and what breaks if serve is down?" without reading source.
### After 1.0