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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { User, Bot, Copy, Check, Lock } from 'lucide-react';
import { Footer } from '../components/Footer';

const FILE_NAMES = ['SOUL.md', 'AGENTS.md', 'USER.md', 'TOOLS.md', 'IDENTITY.md', 'HEARTBEAT.md', 'MEMORY.md'];
const PLATFORM_NAMES = ['OpenClaw', 'NanoClaw'];
const PLATFORM_NAMES = ['OpenClaw', 'NanoClaw', 'Hermes'];
const FILE_LOCK_REVEAL_DELAY_MS = 1600;

export const Home: React.FC = () => {
Expand Down Expand Up @@ -97,7 +97,7 @@ export const Home: React.FC = () => {
agents
</h2>
<p className="text-lg md:text-xl text-gray-400 leading-relaxed">
A complete security skill suite for OpenClaw and NanoClaw agents. Protect your{' '}
A complete security skill suite for OpenClaw, NanoClaw, and Hermes agents. Protect your{' '}
<code
key={currentFileIndex}
className="px-2 py-1 rounded text-clawd-accent inline-block align-baseline relative text-base"
Expand Down
124 changes: 124 additions & 0 deletions scripts/hermes_attestation_sandbox_regression.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
set -euo pipefail

# Sandbox regression test for hermes-attestation-guardian using an isolated Docker Hermes instance.
#
# Usage:
# scripts/hermes_attestation_sandbox_regression.sh
#
# Optional env overrides:
# IMAGE=python:3.11-slim
# HERMES_AGENT_SRC=/home/davida/.hermes/hermes-agent
# SKILL_SRC=/home/davida/clawsec/skills/hermes-attestation-guardian
# WELL_KNOWN_PORT=8765

IMAGE="${IMAGE:-python:3.11-slim}"
HERMES_AGENT_SRC="${HERMES_AGENT_SRC:-$HOME/.hermes/hermes-agent}"
SKILL_SRC="${SKILL_SRC:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/skills/hermes-attestation-guardian}"
WELL_KNOWN_PORT="${WELL_KNOWN_PORT:-8765}"

if ! command -v docker >/dev/null 2>&1; then
echo "ERROR: docker is required." >&2
exit 1
fi
if [[ ! -d "$HERMES_AGENT_SRC" ]]; then
echo "ERROR: HERMES_AGENT_SRC not found: $HERMES_AGENT_SRC" >&2
exit 1
fi
if [[ ! -d "$SKILL_SRC" ]]; then
echo "ERROR: SKILL_SRC not found: $SKILL_SRC" >&2
exit 1
fi

echo "[sandbox] image=$IMAGE"
echo "[sandbox] hermes-agent-src=$HERMES_AGENT_SRC"
echo "[sandbox] skill-src=$SKILL_SRC"

docker run --rm \
-e HOME=/tmp/hermes-sandbox-home \
-e HERMES_HOME=/tmp/hermes-sandbox-home \
-v "$HERMES_AGENT_SRC":/opt/hermes-agent:ro \
-v "$SKILL_SRC":/opt/skill-src:ro \
"$IMAGE" bash -lc "
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update >/dev/null
apt-get install -y --no-install-recommends openssl ca-certificates curl nodejs npm >/dev/null

cp -a /opt/hermes-agent /tmp/hermes-agent-src
python -m pip install --no-cache-dir /tmp/hermes-agent-src >/tmp/pip-install.log 2>&1
mkdir -p \"\$HOME\"

echo \"INSIDE_HOME=\$HOME\"
echo \"INSIDE_HERMES_HOME=\$HERMES_HOME\"

mkdir -p /tmp/well/.well-known/skills/hermes-attestation-guardian
cp -a /opt/skill-src/. /tmp/well/.well-known/skills/hermes-attestation-guardian/
python3 - <<'PY'
import os,json
root='/tmp/well/.well-known/skills'
sk='hermes-attestation-guardian'
base=os.path.join(root,sk)
files=[]
for dp,_,fns in os.walk(base):
for fn in fns:
files.append(os.path.relpath(os.path.join(dp,fn),base).replace('\\\\','/'))
idx={'generated_at':'2026-04-16T00:00:00Z','skills':[{'name':sk,'version':'0.0.1','description':'sandbox feature test','path':f'.well-known/skills/{sk}','files':sorted(files)}]}
with open(os.path.join(root,'index.json'),'w') as f: json.dump(idx,f)
PY
python3 -m http.server $WELL_KNOWN_PORT --directory /tmp/well >/tmp/http.log 2>&1 &
HPID=\$!
sleep 1

INSTALL_OUT=\$(hermes skills install \"well-known:http://127.0.0.1:$WELL_KNOWN_PORT/.well-known/skills/hermes-attestation-guardian\" --yes 2>&1)
echo \"\$INSTALL_OUT\"

echo \"\$INSTALL_OUT\" | grep -q \"Verdict: SAFE\"
echo \"\$INSTALL_OUT\" | grep -q \"Decision: ALLOWED\"
Comment on lines +73 to +77
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hermes skills install success currently depends on grepping logs for Verdict: SAFE and Decision: ALLOWED (not documented as part of the contract), so can we assert a stable machine-readable signal from INSTALL_OUT instead of parsing prose?

Finding type: Logical Bugs | Severity: 🟢 Low


Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents:

Before applying, verify this suggestion against the current code. In
scripts/hermes_attestation_sandbox_regression.sh around lines 73-77 (inside the docker
run bash -lc block), the sandbox determines install success by grepping INSTALL_OUT for
the literal strings “Verdict: SAFE” and “Decision: ALLOWED”. Refactor this to
use a stable, machine-readable success signal: first check the hermes CLI for an
existing option/contract (e.g., a JSON output or a documented flag) and assert on that;
if none exists, change the logic to assert on the hermes skills install command exit
code and then verify the skill was actually installed by checking for the expected
installed directory/files under the configured HERMES_HOME. Remove the brittle prose
greps so the test doesn’t fail when wording changes.


SKILL_DIR=\"\$HERMES_HOME/skills/hermes-attestation-guardian\"
mkdir -p \"\$HERMES_HOME/security/attestations\"
echo \"alpha\" > /tmp/watch.txt
echo \"anchor-v1\" > /tmp/anchor.pem
cat > /tmp/policy.json <<EOF
{\"watch_files\": [\"/tmp/watch.txt\"], \"trust_anchor_files\": [\"/tmp/anchor.pem\"]}
EOF

node \"\$SKILL_DIR/scripts/generate_attestation.mjs\" --output \"\$HERMES_HOME/security/attestations/current.json\" --policy /tmp/policy.json --generated-at 2026-04-16T00:00:00.000Z --write-sha256 >/tmp/generate.log
DIGEST=\$(cut -d\" \" -f1 \"\$HERMES_HOME/security/attestations/current.json.sha256\")
node \"\$SKILL_DIR/scripts/verify_attestation.mjs\" --input \"\$HERMES_HOME/security/attestations/current.json\" --expected-sha256 \"\$DIGEST\" >/tmp/verify-ok.log

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out /tmp/sign.key >/dev/null 2>&1
openssl pkey -in /tmp/sign.key -pubout -out /tmp/sign.pub.pem >/dev/null 2>&1
openssl dgst -sha256 -sign /tmp/sign.key -out /tmp/current.sig \"\$HERMES_HOME/security/attestations/current.json\"
node \"\$SKILL_DIR/scripts/verify_attestation.mjs\" --input \"\$HERMES_HOME/security/attestations/current.json\" --signature /tmp/current.sig --public-key /tmp/sign.pub.pem >/tmp/verify-sig.log

cp \"\$HERMES_HOME/security/attestations/current.json\" \"\$HERMES_HOME/security/attestations/baseline.json\"
BASE_SHA=\$(sha256sum \"\$HERMES_HOME/security/attestations/baseline.json\" | cut -d\" \" -f1)
echo \"beta\" > /tmp/watch.txt
echo \"anchor-v2\" > /tmp/anchor.pem
node \"\$SKILL_DIR/scripts/generate_attestation.mjs\" --output \"\$HERMES_HOME/security/attestations/current.json\" --policy /tmp/policy.json --generated-at 2026-04-16T00:10:00.000Z >/tmp/generate-drift.log
set +e
DRIFT_OUT=\$(node \"\$SKILL_DIR/scripts/verify_attestation.mjs\" --input \"\$HERMES_HOME/security/attestations/current.json\" --baseline \"\$HERMES_HOME/security/attestations/baseline.json\" --baseline-expected-sha256 \"\$BASE_SHA\" --fail-on-severity critical 2>&1)
DRIFT_CODE=\$?
set -e
[ \"\$DRIFT_CODE\" -ne 0 ]
echo \"\$DRIFT_OUT\" | grep -Eq \"WATCHED_FILE_DRIFT|TRUST_ANCHOR_MISMATCH\"

node \"\$SKILL_DIR/scripts/setup_attestation_cron.mjs\" --every 6h --print-only > /tmp/cron-preview.log
grep -q \"Preflight review:\" /tmp/cron-preview.log
grep -q \"# >>> hermes-attestation-guardian >>>\" /tmp/cron-preview.log

echo \"=== SANDBOX FEATURE TEST SUMMARY ===\"
echo \"install_safe_allowed=PASS\"
echo \"generate_with_policy=PASS\"
echo \"verify_expected_sha=PASS\"
echo \"verify_signature=PASS\"
echo \"baseline_drift_fail_closed=PASS\"
echo \"scheduler_preview=PASS\"

kill \$HPID >/dev/null 2>&1 || true
wait \$HPID 2>/dev/null || true
"

echo "[sandbox] completed successfully"
11 changes: 11 additions & 0 deletions skills/hermes-attestation-guardian/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog

## [0.0.1] - 2026-04-15

- Implemented deterministic Hermes attestation generator CLI (`scripts/generate_attestation.mjs`).
- Implemented fail-closed verifier CLI with schema, canonical digest, expected checksum, and optional detached signature checks (`scripts/verify_attestation.mjs`).
- Implemented meaningful baseline diff engine with stable severity mapping for risky toggle regressions, feed verification regressions, trust anchor drift, and watched file drift (`lib/diff.mjs`).
- Implemented Hermes-only cron setup helper with print-only default and managed-block apply mode (`scripts/setup_attestation_cron.mjs`).
- Added shared attestation library for canonicalization, schema validation, digest generation, and policy parsing (`lib/attestation.mjs`).
- Expanded tests for schema determinism, diff behavior, generator/verifier fail-closed behavior, and cron helper Hermes-only output.
- Updated metadata/docs to match actual implemented behavior and ClawSec release pipeline expectations.
45 changes: 45 additions & 0 deletions skills/hermes-attestation-guardian/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# hermes-attestation-guardian

Hermes-only security attestation and drift detection skill.

Status: implemented (v0.0.1), Hermes-only.

## What it does

- Generates deterministic Hermes runtime posture attestations.
- Verifies attestation schema + canonical digest with fail-closed semantics.
- Optionally verifies detached signatures using a provided public key.
- Fails closed on baseline diffing unless baseline authenticity is verified (trusted digest and/or detached signature).
- Restricts attestation output writes to Hermes attestation scope (`$HERMES_HOME/security/attestations`).
- Compares baseline vs current attestations with stable severity classification.
- Provides an optional Hermes-oriented cron setup helper (print-only by default).

## Scope boundaries

In scope:
- Hermes environment posture snapshots
- deterministic baseline diffing
- fail-closed verification semantics
- Hermes optional scheduling helper

Out of scope / unsupported (v0.0.1):
- OpenClaw runtime hooks (unsupported)
- destructive auto-remediation
- automatic rollback of runtime configuration

## Quickstart

```bash
node scripts/generate_attestation.mjs
node scripts/verify_attestation.mjs --input ~/.hermes/security/attestations/current.json
node scripts/setup_attestation_cron.mjs --every 6h --print-only
```

## Tests

```bash
node test/attestation_schema.test.mjs
node test/attestation_diff.test.mjs
node test/attestation_cli.test.mjs
node test/setup_attestation_cron.test.mjs
```
96 changes: 96 additions & 0 deletions skills/hermes-attestation-guardian/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
name: hermes-attestation-guardian
version: 0.0.1
description: Hermes-only runtime security attestation and drift detection skill for operator-managed Hermes infrastructure.
homepage: https://clawsec.prompt.security
clawdis:
emoji: "🛡️"
requires:
bins: [node]
---

# Hermes Attestation Guardian

IMPORTANT SCOPE:
- This skill targets Hermes infrastructure only (CLI/Gateway/profile-managed deployments).
- This skill is not an OpenClaw runtime hook package.

## Goal

Generate deterministic Hermes posture attestations, verify them with fail-closed integrity checks, and compare baseline drift using stable severity mapping.

## Commands

```bash
# Generate attestation (default output: ~/.hermes/security/attestations/current.json)
node scripts/generate_attestation.mjs

# Generate with explicit policy + deterministic timestamp
node scripts/generate_attestation.mjs \
--policy ~/.hermes/security/attestation-policy.json \
--generated-at 2026-04-15T18:00:00.000Z \
--write-sha256

# Verify schema + canonical digest
node scripts/verify_attestation.mjs --input ~/.hermes/security/attestations/current.json

# Verify with baseline diff (baseline must be authenticated)
node scripts/verify_attestation.mjs \
--input ~/.hermes/security/attestations/current.json \
--baseline ~/.hermes/security/attestations/baseline.json \
--baseline-expected-sha256 <trusted-baseline-sha256> \
--fail-on-severity high

# Optional detached signature verification
node scripts/verify_attestation.mjs \
--input ~/.hermes/security/attestations/current.json \
--signature ~/.hermes/security/attestations/current.json.sig \
--public-key ~/.hermes/security/keys/attestation-public.pem

# Preview scheduler config without mutating user schedule state
node scripts/setup_attestation_cron.mjs --every 6h --print-only

# Apply managed scheduler block
node scripts/setup_attestation_cron.mjs --every 6h --apply
```

## Attestation payload (implemented)

The generator emits:
- schema_version, platform, generated_at
- generator metadata (skill + node version)
- host metadata (hostname/platform/arch)
- posture.runtime (gateway enabled flags + risky toggles)
- posture.feed_verification status (verified|unverified|unknown)
- posture.integrity watched_files and trust_anchors (existence + sha256)
- digests.canonical_sha256 over a stable canonical JSON representation

## Fail-closed behavior

Verifier exits non-zero when:
- schema validation fails
- canonical digest algorithm is unsupported or digest binding mismatches
- expected file sha256 mismatches (if configured)
- detached signature verification fails (if configured)
- baseline is provided without authenticated trust binding (`--baseline-expected-sha256` and/or baseline signature + public key)
- baseline authenticity or baseline schema/digest validation fails
- baseline diff highest severity is at/above `--fail-on-severity` (default: critical)

Severity messages are emitted as INFO / WARNING / CRITICAL style lines.

## Side effects

- `generate_attestation.mjs` writes one JSON file (and optional `.sha256`) under `$HERMES_HOME/security/attestations`.
- `verify_attestation.mjs` is read-only.
- `setup_attestation_cron.mjs` is read-only unless `--apply` is provided.
- `setup_attestation_cron.mjs --apply` rewrites only the current user managed schedule block delimited by:
- `# >>> hermes-attestation-guardian >>>`
- `# <<< hermes-attestation-guardian <<<`

## Notes

- Default output root is `~/.hermes/security/attestations/`.
- No destructive remediation actions (delete/restore/quarantine) are implemented.
- Operator policy file is optional JSON with:
- `watch_files`: list of file paths
- `trust_anchor_files`: list of file paths
Loading
Loading