diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 14515f43..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
@@ -40,6 +37,17 @@ jobs:
- name: Build
run: go build -v ./cmd/rampart
+ - name: Installer scripts are synced
+ if: runner.os == 'Linux'
+ run: |
+ cmp -s install.sh docs/install
+ cmp -s install.sh docs/install.sh
+ cmp -s install.sh scripts/install.sh
+ sh -n install.sh
+ sh -n docs/install
+ sh -n docs/install.sh
+ sh -n scripts/install.sh
+
- name: OpenClaw plugin regression tests
if: runner.os != 'Windows'
run: |
@@ -65,11 +73,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@v6
+
+ - 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
@@ -77,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 6cf7e529..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,33 +12,88 @@ jobs:
docker:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v5
-
- - uses: docker/setup-qemu-action@v4
+ - uses: actions/checkout@v6
- 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:
images: ghcr.io/peg/rampart
tags: |
type=semver,pattern={{version}}
- type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref_name, '-') }}
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-') }}
- - uses: docker/build-push-action@v7
+ - name: Compute build metadata
+ id: build
+ run: |
+ echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
+ echo "commit=${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
+ echo "date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
+
+ - name: Build local image for smoke test
+ uses: docker/build-push-action@v7
with:
context: .
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
+
+ - name: Smoke test local Docker image
+ env:
+ IMAGE: rampart:release-smoke
+ VERSION: ${{ steps.build.outputs.version }}
+ run: |
+ set -euo pipefail
+
+ docker run --rm "$IMAGE" version | tee /tmp/rampart-docker-version.txt
+ grep -F "rampart ${VERSION}" /tmp/rampart-docker-version.txt
+
+ cid="$(docker run -d -p 19090:9090 "$IMAGE")"
+ 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
+
+ - 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 }}
+ run: |
+ set -euo pipefail
+ docker buildx imagetools inspect "$IMAGE" | tee /tmp/rampart-docker-manifest.txt
+ grep -F 'linux/amd64' /tmp/rampart-docker-manifest.txt
+ grep -F 'linux/arm64' /tmp/rampart-docker-manifest.txt
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 --
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6029fe53..84a94eb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,18 @@ 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.
+- **Installer surfaces are canonical again** — `install.sh`, `docs/install`, `docs/install.sh`, and `scripts/install.sh` are byte-for-byte synced, with CI checks to prevent future drift.
+
+### Changed
+
+- **1.0 launch metadata is aligned** — The embedded OpenClaw plugin manifest, runtime export, package metadata, landing structured data, docs homepage, support matrix, and roadmap now use final `1.0.0` launch language instead of stale RC labels.
+- **Release docs point at the live package channels** — Homebrew examples use `peg/tap/rampart`, binary download docs describe the actual archive formats, and Docker docs describe stable/minor/prerelease tags accurately.
+
## [1.0.0-rc.2] - 2026-05-04
### Fixed
diff --git a/Dockerfile b/Dockerfile
index 0107a0d7..7c1c3eef 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,15 +1,32 @@
-# Build stage
-FROM golang:1.24-bookworm AS build
+# Build stage. Build on the native runner platform and cross-compile for the
+# requested image platform so linux/arm64 releases don't depend on slow QEMU
+# emulation for the Go build itself.
+ARG BUILDPLATFORM
+FROM --platform=$BUILDPLATFORM golang:1.25.9-bookworm AS build
WORKDIR /src
+
+ARG TARGETOS
+ARG TARGETARCH
+ARG VERSION=dev
+ARG COMMIT=unknown
+ARG DATE=unknown
+
COPY go.mod go.sum ./
RUN go mod download
COPY . .
-RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /rampart ./cmd/rampart
+RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} go build -trimpath \
+ -ldflags="-s -w \
+ -X github.com/peg/rampart/internal/build.versionFromLDFlags=${VERSION} \
+ -X github.com/peg/rampart/internal/build.Commit=${COMMIT} \
+ -X github.com/peg/rampart/internal/build.Date=${DATE}" \
+ -o /rampart ./cmd/rampart
# Runtime stage — distroless for minimal attack surface
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /rampart /rampart
USER nonroot:nonroot
+ENV HOME=/tmp
+WORKDIR /tmp
EXPOSE 9090
ENTRYPOINT ["/rampart"]
-CMD ["serve", "--bind", "0.0.0.0:9090"]
+CMD ["serve", "--addr", "0.0.0.0", "--port", "9090"]
diff --git a/Makefile b/Makefile
index 25cf5b67..e0004caf 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS = -s -w \
- -X github.com/peg/rampart/internal/build.Version=$(VERSION) \
+ -X github.com/peg/rampart/internal/build.versionFromLDFlags=$(VERSION) \
-X github.com/peg/rampart/internal/build.Commit=$(COMMIT) \
-X github.com/peg/rampart/internal/build.Date=$(DATE)
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/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/cmd/rampart/cli/setup_openclaw_plugin_test.go b/cmd/rampart/cli/setup_openclaw_plugin_test.go
index 4cd4fa7c..49d70151 100644
--- a/cmd/rampart/cli/setup_openclaw_plugin_test.go
+++ b/cmd/rampart/cli/setup_openclaw_plugin_test.go
@@ -9,10 +9,10 @@ import (
func TestExtractOpenClawPluginRuntimeVersion(t *testing.T) {
got := extractOpenClawPluginRuntimeVersion(`export const id = "rampart";
-export const version = "1.0.0-rc.2";
+export const version = "1.0.0";
`)
- if got != "1.0.0-rc.2" {
- t.Fatalf("runtime version = %q, want 1.0.0-rc.2", got)
+ if got != "1.0.0" {
+ t.Fatalf("runtime version = %q, want 1.0.0", got)
}
}
diff --git a/docs-site/features/mcp-proxy.md b/docs-site/features/mcp-proxy.md
index 80215f1f..59179503 100644
--- a/docs-site/features/mcp-proxy.md
+++ b/docs-site/features/mcp-proxy.md
@@ -43,8 +43,7 @@ In your agent's MCP config (Claude Desktop, Cursor, etc.):
```d2
direction: right
-client: "MCP Client
-(Claude Desktop, Cursor…)" {shape: oval}
+client: "MCP Client\\n(Claude Desktop, Cursor…)" {shape: oval}
rampart: "rampart mcp" {style.border-radius: 8}
engine: "Policy Engine" {
style.fill: "#1d3320"; style.stroke: "#2ea043"; style.font-color: "#3fb950"; style.border-radius: 8
@@ -128,7 +127,7 @@ rampart mcp -- npx -y @modelcontextprotocol/server-postgres postgres://localhost
### 1. Install Rampart
```bash
-brew tap peg/rampart && brew install rampart
+brew install peg/tap/rampart
```
### 2. Create a Policy
diff --git a/docs-site/features/policy-engine.md b/docs-site/features/policy-engine.md
index f4db61ab..3872020f 100644
--- a/docs-site/features/policy-engine.md
+++ b/docs-site/features/policy-engine.md
@@ -14,11 +14,9 @@ direction: down
call: "Tool Call" {shape: oval}
-match: "Match policies
-by tool type" {shape: diamond}
+match: "Match policies\\nby tool type" {shape: diamond}
-rules: "Evaluate rules
-top-to-bottom" {shape: diamond}
+rules: "Evaluate rules\\ntop-to-bottom" {shape: diamond}
deny: "Denied" {
style.fill: "#2d1b1b"
@@ -52,8 +50,7 @@ allow: "Allowed" {
style.border-radius: 6
}
-default: "Default Action
-(allow or deny)" {
+default: "Default Action\\n(allow or deny)" {
style.border-radius: 6
style.stroke-dash: 4
}
diff --git a/docs-site/features/semantic-verification.md b/docs-site/features/semantic-verification.md
index 2bc1c462..292a7743 100644
--- a/docs-site/features/semantic-verification.md
+++ b/docs-site/features/semantic-verification.md
@@ -129,8 +129,7 @@ policies:
direction: right
agent: "AI Agent" {shape: oval}
-rampart: "Rampart
-Policy Engine" {
+rampart: "Rampart\\nPolicy Engine" {
style.fill: "#1d3320"; style.stroke: "#2ea043"; style.font-color: "#3fb950"; style.border-radius: 8
}
@@ -146,10 +145,8 @@ verify: {
style.stroke-dash: 4
style.border-radius: 8
- redact: "Redact secrets
-from command" {style.border-radius: 6}
- llm: "LLM
-(gpt-4o-mini / Haiku / Ollama)" {style.border-radius: 6}
+ redact: "Redact secrets\\nfrom command" {style.border-radius: 6}
+ llm: "LLM\\n(gpt-4o-mini / Haiku / Ollama)" {style.border-radius: 6}
redact -> llm: "sanitized command"
}
diff --git a/docs-site/getting-started/installation.md b/docs-site/getting-started/installation.md
index 928826b9..8c92b948 100644
--- a/docs-site/getting-started/installation.md
+++ b/docs-site/getting-started/installation.md
@@ -23,7 +23,7 @@ description: "Install Rampart on Windows, macOS, or Linux. Get the security laye
**Homebrew (recommended):**
```bash
- brew tap peg/rampart && brew install rampart
+ brew install peg/tap/rampart
```
**One-liner:**
@@ -36,7 +36,7 @@ description: "Install Rampart on Windows, macOS, or Linux. Get the security laye
The fastest way to install Rampart:
```bash
-brew tap peg/rampart && brew install rampart
+brew install peg/tap/rampart
```
This installs the `rampart` binary.
@@ -60,15 +60,15 @@ go install github.com/peg/rampart/cmd/rampart@latest
Download pre-built binaries from [GitHub Releases](https://github.com/peg/rampart/releases).
-Binaries are available for Linux (amd64/arm64) as `.tar.gz` and macOS (amd64/arm64) as `.zip`:
+Binaries are available for Linux and macOS (amd64/arm64) as `.tar.gz` archives. Windows builds are published as `.zip` archives:
```bash
# Example: Linux amd64
tar xzf rampart_*_linux_amd64.tar.gz
sudo mv rampart /usr/local/bin/
-# Example: macOS (unzip, then move)
-unzip rampart_*_darwin_arm64.zip
+# Example: macOS arm64
+tar xzf rampart_*_darwin_arm64.tar.gz
sudo mv rampart /usr/local/bin/
```
@@ -77,7 +77,7 @@ sudo mv rampart /usr/local/bin/
Multi-arch container image (amd64 + arm64), built on distroless for minimal attack surface:
```bash
-docker run ghcr.io/peg/rampart:latest serve --addr 0.0.0.0 --port 9090
+docker run --rm -p 9090:9090 ghcr.io/peg/rampart:latest
```
Or use with docker-compose. First, create a policy file (e.g. `mkdir policies && rampart init > policies/rampart.yaml`):
@@ -97,7 +97,7 @@ volumes:
rampart-audit:
```
-Available tags: `latest`, `0.9.12`, `0.9`. `latest` always points to the current stable release. Pin to a specific version tag for reproducibility. Images are published on [GitHub Container Registry](https://github.com/peg/rampart/pkgs/container/rampart).
+Available tags include full versions such as `1.0.0`, minor versions such as `1.0`, and `latest` for the current stable release. Prereleases use their full tag, for example `1.0.0-rc.3`, and do not move `latest`. Pin to a specific version tag for reproducibility. Images are published on [GitHub Container Registry](https://github.com/peg/rampart/pkgs/container/rampart).
## Build from Source
diff --git a/docs-site/getting-started/quickstart.md b/docs-site/getting-started/quickstart.md
index e4bb9bbe..76896320 100644
--- a/docs-site/getting-started/quickstart.md
+++ b/docs-site/getting-started/quickstart.md
@@ -32,7 +32,7 @@ Before you dive in, skim the [integration support matrix](support-matrix.md) if
=== "Homebrew"
```bash
- brew tap peg/rampart && brew install rampart
+ brew install peg/tap/rampart
```
## One-Command Setup
diff --git a/docs-site/getting-started/support-matrix.md b/docs-site/getting-started/support-matrix.md
index ab6ac4b3..81cda8cd 100644
--- a/docs-site/getting-started/support-matrix.md
+++ b/docs-site/getting-started/support-matrix.md
@@ -46,13 +46,13 @@ Use this page as the canonical support contract for Rampart's main integration s
Native plugin
rampart setup openclaw |
Required |
First-class plugin approvals / native approval UI |
- Recommended RC baseline |
+ Recommended |
| OpenClaw 2026.4.29 - 2026.5.1 |
Native plugin
rampart setup openclaw |
Required |
- Native plugin startup/interception; approval delivery was not the RC baseline |
+ Native plugin startup/interception; approval delivery was not the launch baseline |
Supported |
@@ -90,7 +90,7 @@ Use this page as the canonical support contract for Rampart's main integration s
- **Claude Code** → best overall native path
- **Codex CLI** → best CLI path when you want strong coverage
-- **OpenClaw >= 2026.5.2** → best OpenClaw path for the 1.0 RC baseline; plugin + native approval UI
+- **OpenClaw >= 2026.5.2** → best OpenClaw path for 1.0; plugin + native approval UI
- **Cline** → good supported path, but less polished approval UX than Claude Code
## Degraded behavior notes
diff --git a/docs-site/getting-started/troubleshooting.md b/docs-site/getting-started/troubleshooting.md
index fb1fc712..36419d8f 100644
--- a/docs-site/getting-started/troubleshooting.md
+++ b/docs-site/getting-started/troubleshooting.md
@@ -165,7 +165,7 @@ rampart test --tool read "/etc/shadow"
```bash
openclaw plugins list
-# Should show: rampart v0.9.12 ✓ active
+# Should show: rampart 1.0.0 ✓ active
rampart doctor
# Should show: ✓ OpenClaw plugin: installed (before_tool_call hook active)
diff --git a/docs-site/getting-started/tutorial.md b/docs-site/getting-started/tutorial.md
index 11298854..10d8d5f2 100644
--- a/docs-site/getting-started/tutorial.md
+++ b/docs-site/getting-started/tutorial.md
@@ -38,7 +38,7 @@ Let's set it up.
=== "Homebrew"
```bash
- brew tap peg/rampart && brew install rampart
+ brew install peg/tap/rampart
```
Verify:
diff --git a/docs-site/guides/agent-install.md b/docs-site/guides/agent-install.md
index 50b1df01..e11c6212 100644
--- a/docs-site/guides/agent-install.md
+++ b/docs-site/guides/agent-install.md
@@ -32,7 +32,7 @@ Follow these steps in order. Each step is independently verifiable.
=== "Homebrew (macOS)"
```bash
- brew tap peg/rampart && brew install rampart
+ brew install peg/tap/rampart
```
Verify the install:
diff --git a/docs-site/guides/securing-claude-code.md b/docs-site/guides/securing-claude-code.md
index 2dd679c3..cf4b4522 100644
--- a/docs-site/guides/securing-claude-code.md
+++ b/docs-site/guides/securing-claude-code.md
@@ -22,7 +22,7 @@ The goal is not to disable Claude Code automation. The goal is to add guardrails
1. Install Rampart.
```bash
-brew tap peg/rampart && brew install rampart
+brew install peg/tap/rampart
```
2. Wire Rampart into Claude Code.
diff --git a/docs-site/index.html b/docs-site/index.html
index 8f37f3be..af37ba06 100644
--- a/docs-site/index.html
+++ b/docs-site/index.html
@@ -27,7 +27,7 @@
"url": "https://rampart.sh",
"applicationCategory": "SecurityApplication",
"operatingSystem": "Linux, macOS, Windows",
- "softwareVersion": "1.0.0-rc.2",
+ "softwareVersion": "1.0.0",
"offers": {
"@type": "Offer",
"price": "0",
@@ -1112,7 +1112,7 @@
-
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-site/index.md b/docs-site/index.md
index a4739b3c..1a511693 100644
--- a/docs-site/index.md
+++ b/docs-site/index.md
@@ -78,7 +78,7 @@ Rampart also scans tool **responses** — if your agent reads a file containing
```bash
# Install
-brew tap peg/rampart && brew install rampart
+brew install peg/tap/rampart
# Claude Code
rampart setup claude-code
@@ -131,19 +131,19 @@ intercept: {
mcp: "MCP Proxy"
}
-engine: "YAML Policy Engine\n<10μs" {
+engine: "YAML Policy Engine\\n<10μs" {
style.fill: "#1d3320"
style.stroke: "#2ea043"
style.font-color: "#3fb950"
style.border-radius: 8
}
-verify: "rampart-verify\n(optional sidecar)" {
+verify: "rampart-verify\\n(optional sidecar)" {
style.stroke-dash: 4
style.border-radius: 8
}
-audit: "Audit Trail\nhash-chained" {
+audit: "Audit Trail\\nhash-chained" {
style.border-radius: 8
}
@@ -205,13 +205,13 @@ verify -> outcomes.approval
[:octicons-arrow-right-24: See all integration guides](integrations/index.md)
-## What's New in v1.0.0-rc.2
+## What's New in v1.0
-- **Prerelease update checks are sane** — `rampart doctor` no longer suggests downgrading from the 1.0 RC line to the older stable `v0.9.22` release.
-- **OpenClaw 2026.5.2 is the RC baseline** — Rampart now 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)
+- **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.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 release-candidate 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 RC pass.
+- **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.
### v0.9.22
diff --git a/docs-site/integrations/index.md b/docs-site/integrations/index.md
index 0e48c7cf..4a7db350 100644
--- a/docs-site/integrations/index.md
+++ b/docs-site/integrations/index.md
@@ -58,12 +58,10 @@ start: "Your agent" {shape: oval}
q: "Integration method?" {shape: diamond}
-hooks: "rampart setup claude-code
-rampart setup cline" {
+hooks: "rampart setup claude-code\\nrampart setup cline" {
style.fill: "#1d3320"; style.stroke: "#2ea043"; style.font-color: "#3fb950"; style.border-radius: 6
}
-shim: "rampart setup openclaw
-native plugin on current builds" {
+shim: "rampart setup openclaw\\nnative plugin on current builds" {
style.fill: "#1d3320"; style.stroke: "#2ea043"; style.font-color: "#3fb950"; style.border-radius: 6
}
mcp: "rampart mcp --" {
@@ -75,25 +73,18 @@ wrap: "rampart wrap --" {
preload: "rampart preload --" {
style.fill: "#1d3320"; style.stroke: "#2ea043"; style.font-color: "#3fb950"; style.border-radius: 6
}
-api: "HTTP API / SDK
-localhost:9090" {
+api: "HTTP API / SDK\\nlocalhost:9090" {
style.fill: "#1d3320"; style.stroke: "#2ea043"; style.font-color: "#3fb950"; style.border-radius: 6
}
start -> q
-q -> hooks: "Claude Code or Cline
-(native hooks, lowest overhead)"
-q -> shim: "OpenClaw
-(native plugin on supported builds)"
-q -> mcp: "Cursor, Claude Desktop
-or any MCP-compatible client"
-q -> wrap: "Any CLI agent
-with \$SHELL support"
-q -> preload: "Any CLI agent
-without \$SHELL or native hooks"
-q -> api: "Custom / Python agent
-or CI pipeline"
+q -> hooks: "Claude Code or Cline\\n(native hooks, lowest overhead)"
+q -> shim: "OpenClaw\\n(native plugin on supported builds)"
+q -> mcp: "Cursor, Claude Desktop\\nor any MCP-compatible client"
+q -> wrap: "Any CLI agent\\nwith \$SHELL support"
+q -> preload: "Any CLI agent\\nwithout \$SHELL or native hooks"
+q -> api: "Custom / Python agent\\nor CI pipeline"
```
!!! tip "Start with the simplest method"
diff --git a/docs-site/integrations/openclaw.md b/docs-site/integrations/openclaw.md
index e0c0695e..0a66ab26 100644
--- a/docs-site/integrations/openclaw.md
+++ b/docs-site/integrations/openclaw.md
@@ -12,11 +12,11 @@ When `rampart serve` is healthy, every supported tool call — exec, read, write
For sensitive tools, the recommended operating assumption is simple: if Rampart policy service is unavailable, treat that as a broken state and fix it before trusting approval-path tests. By default the plugin blocks sensitive tools such as `exec` and `write` when `rampart serve` is unavailable; lower-risk tools (`read`, `web_fetch`, `web_search`, `image`) are explicitly configured fail-open and can be tightened with `plugins.entries.rampart.config.failOpenTools`.
!!! info "Version requirements"
- - **OpenClaw >= 2026.5.2**: Preferred RC baseline. Supports explicit plugin startup activation plus first-class plugin approvals on the shared `/approve` / native approval path.
- - **OpenClaw 2026.4.29 - 2026.5.1**: Supported for native plugin startup/interception; plugin approval delivery was not the RC baseline.
+ - **OpenClaw >= 2026.5.2**: Recommended 1.0 path. Supports explicit plugin startup activation plus first-class plugin approvals on the shared `/approve` / native approval path.
+ - **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 RC baseline on**: OpenClaw 2026.5.2
+ - **Verified 1.0 launch dogfood on**: OpenClaw 2026.5.6
`rampart setup openclaw` auto-detects your version and uses the right method.
@@ -157,7 +157,7 @@ Or check plugin status directly:
```bash
openclaw plugins list
-# rampart v0.9.18 active
+# rampart v1.0.0 active
```
## Troubleshooting
diff --git a/docs-site/reference/architecture.md b/docs-site/reference/architecture.md
index 855586eb..f268235e 100644
--- a/docs-site/reference/architecture.md
+++ b/docs-site/reference/architecture.md
@@ -29,19 +29,19 @@ intercept: {
mcp: "MCP Proxy"
}
-engine: "YAML Policy Engine\n<10μs" {
+engine: "YAML Policy Engine\\n<10μs" {
style.fill: "#1d3320"
style.stroke: "#2ea043"
style.font-color: "#3fb950"
style.border-radius: 8
}
-verify: "rampart-verify\n(optional sidecar)" {
+verify: "rampart-verify\\n(optional sidecar)" {
style.stroke-dash: 4
style.border-radius: 8
}
-audit: "Audit Trail\nhash-chained" {
+audit: "Audit Trail\\nhash-chained" {
style.border-radius: 8
}
diff --git a/docs-site/reference/owasp-mapping.md b/docs-site/reference/owasp-mapping.md
index 27eb905b..dc1bef2b 100644
--- a/docs-site/reference/owasp-mapping.md
+++ b/docs-site/reference/owasp-mapping.md
@@ -13,7 +13,7 @@ This page maps Rampart's capabilities to the [OWASP Top 10 for Agentic Applicati
| ASI02 | **Tool Misuse and Exploitation** | Every tool call (exec, read, write, fetch, MCP) is evaluated against YAML policies before execution. `default_action: deny` enforces least-privilege. Parameter validation, command pattern matching, and approval workflows for sensitive operations. This is Rampart's core function. | ✅ Covered |
| ASI03 | **Identity and Privilege Abuse** | `agent_depth` conditions limit sub-agent privilege escalation. Self-modification protection blocks agents from running `rampart allow`/`rampart block`. User separation prevents agents from accessing policies/audit. Does not manage agent credentials, OAuth tokens, or delegated permissions — over-scoped keys are outside Rampart's scope. | ⚠️ Partial |
| ASI04 | **Agentic Supply Chain Vulnerabilities** | Community policy SHA-256 verification detects tampering after registry publication. `rampart mcp scan` auto-generates policy from MCP server tool definitions. Project-local policies enforce deny-wins (a project policy can tighten but not loosen global policy). Does not verify tool provenance at source, inspect dependency trees, or provide SBOM/AIBOM. | ⚠️ Partial |
-| ASI05 | **Unexpected Code Execution (RCE)** | Shell command normalization, interpreter one-liner blocking (`python3 -c`, `node -e`, `perl -e`), LD_PRELOAD cascade for subprocess interception, and pattern matching catch common injected code patterns before they run. Does not inspect code executed inside allowed interpreters (e.g., `python3 script.py`), cannot handle all obfuscation variants, and LD_PRELOAD cascade does not apply in native-hook mode (Claude Code, Cline). See [Threat Model — Known Gaps](../threat-model/). | ⚠️ Partial |
+| ASI05 | **Unexpected Code Execution (RCE)** | Shell command normalization, interpreter one-liner blocking (`python3 -c`, `node -e`, `perl -e`), LD_PRELOAD cascade for subprocess interception, and pattern matching catch common injected code patterns before they run. Does not inspect code executed inside allowed interpreters (e.g., `python3 script.py`), cannot handle all obfuscation variants, and LD_PRELOAD cascade does not apply in native-hook mode (Claude Code, Cline). See [Threat Model — Known Gaps](threat-model.md). | ⚠️ Partial |
| ASI06 | **Memory & Context Poisoning** | Response scanning (`response_matches`) blocks credentials and known-bad patterns in tool responses before they enter the agent's context window. Does not protect persistent memory stores, RAG databases, embeddings, or conversation history — Rampart operates at the tool call layer, not the memory layer. | ⚠️ Partial |
| ASI07 | **Insecure Inter-Agent Communication** | Not addressed. Rampart operates at the agent-to-OS boundary; it has no visibility into messages passed between agents in a multi-agent system. Does not provide mutual authentication, message signing, anti-replay, or encryption for agent-to-agent channels. Note: *tool calls* from sub-agents are evaluated by the same policy engine, and `agent_depth` conditions limit sub-agent nesting depth — but these address sub-agent containment, not communication security. | ❌ Not covered |
| ASI08 | **Cascading Failures** | Fail-open design prevents Rampart from becoming a single point of failure (a crashed Rampart doesn't lock out the system). `call_count` rate limiting throttles runaway agents. Webhook notifications alert on anomalies in real time. Does not prevent agent-to-agent cascade in multi-agent systems. | ⚠️ Partial |
diff --git a/docs-site/reference/threat-model.md b/docs-site/reference/threat-model.md
index e298ef9b..d58bebfd 100644
--- a/docs-site/reference/threat-model.md
+++ b/docs-site/reference/threat-model.md
@@ -1,6 +1,6 @@
# Threat Model
-> Last reviewed: 2026-04-30 | Applies to: v1.0.0-rc.2+
+> Last reviewed: 2026-04-30 | Applies to: v1.0.0
Rampart is a policy engine for AI agents — not a sandbox, not a hypervisor, not a full isolation boundary. This document describes what Rampart protects against, what it doesn't, and why.
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index bb4835c4..cfdaeaff 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -45,15 +45,15 @@ What's coming next for Rampart. Priorities shift based on feedback — [open an
## Current Focus
-### `v1.0.0-rc.2`
+### `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 RC baseline for native plugin approvals.
-- Keep `rampart doctor`, setup output, and docs aligned so users can answer "am I protected, how, and what breaks if serve is down?" without reading source.
+- 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 the RC
+### After 1.0
- Collect feedback from real OpenClaw, Claude Code, Cline, Codex, and MCP users.
-- Fix any RC integration bugs without widening the public API unless the tradeoff is explicit.
-- Promote to `v1.0.0` only when the support matrix and threat model still match real-world behavior.
+- Fix integration bugs without widening the public API unless the tradeoff is explicit.
+- Keep the support matrix and threat model honest as agent runtimes evolve.
## Future / v2.x
diff --git a/docs/index.html b/docs/index.html
index 8f37f3be..af37ba06 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -27,7 +27,7 @@
"url": "https://rampart.sh",
"applicationCategory": "SecurityApplication",
"operatingSystem": "Linux, macOS, Windows",
- "softwareVersion": "1.0.0-rc.2",
+ "softwareVersion": "1.0.0",
"offers": {
"@type": "Offer",
"price": "0",
@@ -1112,7 +1112,7 @@
-
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/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/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..5676b55b 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",
@@ -11,7 +11,7 @@
"security",
"plugin"
],
- "license": "MIT",
+ "license": "Apache-2.0",
"openclaw": {
"extensions": [
"./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