-
Notifications
You must be signed in to change notification settings - Fork 93
feat(hermes-attestation-guardian): harden attestation verification and drift controls #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
davida-ps
merged 9 commits into
prompt-security:main
from
abutbul:skill/hermes-attestation-guardian-hardening-v0.0.1
Apr 16, 2026
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
18fefe4
feat(hermes-attestation-guardian): harden attestation verification an…
abutbul 65b83d7
docs(wiki): add human-friendly claim mapping for hermes attestation g…
abutbul 1f1dde4
docs(wiki): expand hermes attestation claim narratives and archive draft
abutbul 2386539
fix(attestation): address Baz review findings for schema and verifier
abutbul 37b4544
fix(attestation): reject broken symlink output paths
abutbul ced2464
docs(attestation): pass clean community install guard without force
abutbul 69368e9
fix(attestation): harden writes and fail-closed config parsing
davida-ps a08a0a7
feat(ui): add Hermes to rotating platform text
davida-ps 70e1ea3
test(attestation): add sandboxed Hermes regression runner script
abutbul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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\" | ||
|
|
||
| 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" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hermes skills installsuccess currently depends on grepping logs forVerdict: SAFEandDecision: ALLOWED(not documented as part of the contract), so can we assert a stable machine-readable signal fromINSTALL_OUTinstead of parsing prose?Finding type:
Logical Bugs| Severity: 🟢 LowWant Baz to fix this for you? Activate Fixer
Other fix methods
Prompt for AI Agents: