mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-01 15:52:26 +03:00
fix(release): exclude tests from skill payloads (#230)
* fix(release): exclude tests from skill payloads * fix(release): normalize test path filtering * fix(release): prefer GitHub artifacts for non-OpenClaw installs * fix(release): keep legacy ClawHub publishing * fix(release): address skill packaging review feedback * chore(skills): bump release versions * feat(skills): surface recommended platforms * docs(skills): add signed release verification * fix(skills): normalize PR version bumps --------- Co-authored-by: David Abutbul <David.a@prompt.security>
This commit is contained in:
@@ -184,15 +184,23 @@ jobs:
|
||||
|
||||
# Build skill entry for index
|
||||
SKILL_DATA=$(jq -c --arg tag "$TAG" '
|
||||
. as $skill |
|
||||
def object_or_empty($value):
|
||||
if ($value | type) == "object" then $value else {} end;
|
||||
def object_field($name):
|
||||
object_or_empty(.[$name]?);
|
||||
object_or_empty($skill[$name]?);
|
||||
def platform_meta:
|
||||
(.platform as $platform
|
||||
| if ($platform | type) == "string" then object_or_empty(.[$platform]?)
|
||||
($skill.platform as $platform
|
||||
| if ($platform | type) == "string" then object_or_empty($skill[$platform]?)
|
||||
else {}
|
||||
end);
|
||||
def platform_list:
|
||||
([]
|
||||
+ (if ($skill.platforms | type) == "array" then $skill.platforms else [] end)
|
||||
+ (if ($skill.platform | type) == "string" then [$skill.platform] else [] end)
|
||||
+ (["openclaw", "hermes", "nanoclaw", "picoclaw"] | map(select((object_field(.) | length) > 0))))
|
||||
| map(select(type == "string") | ascii_downcase)
|
||||
| unique;
|
||||
{
|
||||
id: .name,
|
||||
name: .name,
|
||||
@@ -200,6 +208,7 @@ jobs:
|
||||
description: .description,
|
||||
emoji: (platform_meta.emoji // object_field("openclaw").emoji // object_field("hermes").emoji // object_field("nanoclaw").emoji // object_field("picoclaw").emoji // "📦"),
|
||||
category: (platform_meta.category // object_field("openclaw").category // object_field("hermes").category // object_field("nanoclaw").category // object_field("picoclaw").category // "utility"),
|
||||
platforms: platform_list,
|
||||
trust: .trust.level,
|
||||
tag: $tag
|
||||
}
|
||||
|
||||
@@ -375,6 +375,26 @@ jobs:
|
||||
failures=0
|
||||
mkdir -p dist/dry-run
|
||||
|
||||
normalize_release_path() {
|
||||
local path="$1"
|
||||
path="${path//\\//}"
|
||||
while [[ "$path" == ./* ]]; do
|
||||
path="${path#./}"
|
||||
done
|
||||
while [[ "$path" == *//* ]]; do
|
||||
path="${path//\/\//\/}"
|
||||
done
|
||||
if [[ -z "$path" || "$path" == /* || "$path" == [A-Za-z]:* || "$path" == ".." || "$path" == ../* || "$path" == */.. || "$path" == */../* ]]; then
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
|
||||
is_test_release_path() {
|
||||
local lower="${1,,}"
|
||||
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
|
||||
}
|
||||
|
||||
while IFS= read -r skill_dir; do
|
||||
json_path="${skill_dir}/skill.json"
|
||||
md_path="${skill_dir}/SKILL.md"
|
||||
@@ -477,8 +497,17 @@ jobs:
|
||||
temp_sbom_file="$(mktemp)"
|
||||
jq -r '.sbom.files[].path' "${json_path}" > "${temp_sbom_file}"
|
||||
|
||||
while IFS= read -r file; do
|
||||
[ -z "${file}" ] && continue
|
||||
while IFS= read -r raw_file; do
|
||||
[ -z "${raw_file}" ] && continue
|
||||
if ! file="$(normalize_release_path "${raw_file}")"; then
|
||||
echo "::error file=${json_path}::SBOM references unsafe file path: ${raw_file}"
|
||||
failures=$((failures + 1))
|
||||
continue
|
||||
fi
|
||||
if is_test_release_path "${file}"; then
|
||||
echo " [Dry-run] Skipping test-only release file: ${file}"
|
||||
continue
|
||||
fi
|
||||
full_path="${skill_dir}/${file}"
|
||||
if [ -f "${full_path}" ]; then
|
||||
mkdir -p "${inner_dir}/$(dirname "${file}")"
|
||||
@@ -504,6 +533,11 @@ jobs:
|
||||
# --- Create zip preserving directory structure ---
|
||||
zip_name="${skill_name}-v${version}.zip"
|
||||
(cd "${staging_dir}" && zip -qr "${OLDPWD}/${out_assets}/${zip_name}" .)
|
||||
if unzip -Z1 "${out_assets}/${zip_name}" | grep -Eiq '(^|/)(test|tests)/'; then
|
||||
echo "::error::Dry-run release archive contains test-only files: ${zip_name}"
|
||||
unzip -Z1 "${out_assets}/${zip_name}" | grep -Ei '(^|/)(test|tests)/' || true
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
# --- Clean up test artifacts from source directory ---
|
||||
if [ -d "${skill_dir}/advisories" ]; then
|
||||
@@ -515,8 +549,14 @@ jobs:
|
||||
|
||||
# --- Generate checksums.json via jq ---
|
||||
files_json="{}"
|
||||
while IFS= read -r file; do
|
||||
[ -z "${file}" ] && continue
|
||||
while IFS= read -r raw_file; do
|
||||
[ -z "${raw_file}" ] && continue
|
||||
if ! file="$(normalize_release_path "${raw_file}")"; then
|
||||
continue
|
||||
fi
|
||||
if is_test_release_path "${file}"; then
|
||||
continue
|
||||
fi
|
||||
full_path="${skill_dir}/${file}"
|
||||
if [ -f "${full_path}" ]; then
|
||||
sha256="$(sha256sum "${full_path}" | awk '{print $1}')"
|
||||
@@ -615,6 +655,8 @@ jobs:
|
||||
version: ${{ steps.parse.outputs.version }}
|
||||
skill_path: ${{ steps.parse.outputs.skill_path }}
|
||||
publishable: ${{ steps.publishable.outputs.publishable }}
|
||||
openclaw_skill: ${{ steps.publishable.outputs.openclaw_skill }}
|
||||
publish_clawhub: ${{ steps.publishable.outputs.publish_clawhub }}
|
||||
steps:
|
||||
- name: Parse tag
|
||||
id: parse
|
||||
@@ -686,22 +728,35 @@ jobs:
|
||||
echo "SKILL.md version validated: $MD_VERSION"
|
||||
fi
|
||||
else
|
||||
echo "No SKILL.md found, skipping frontmatter validation"
|
||||
echo "::error::Missing required SKILL.md: $SKILL_PATH/SKILL.md"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Detect publishability
|
||||
- name: Detect publishability and install defaults
|
||||
id: publishable
|
||||
run: |
|
||||
SKILL_PATH="${{ steps.parse.outputs.skill_path }}"
|
||||
INTERNAL=$(jq -r '.openclaw.internal // false' "$SKILL_PATH/skill.json")
|
||||
INTERNAL=$(jq -r 'if (.openclaw | type) == "object" then (.openclaw.internal // false) else false end' "$SKILL_PATH/skill.json")
|
||||
|
||||
OPENCLAW_SKILL=false
|
||||
if jq -e '(.openclaw | type == "object") and ((.openclaw | length) > 0)' "$SKILL_PATH/skill.json" >/dev/null; then
|
||||
OPENCLAW_SKILL=true
|
||||
fi
|
||||
|
||||
PUBLISHABLE=true
|
||||
if [ "$INTERNAL" = "true" ]; then
|
||||
PUBLISHABLE=false
|
||||
echo "Skill marked internal=true; will skip ClawHub publish."
|
||||
echo "Skill marked internal=true; will skip ClawHub publishing."
|
||||
fi
|
||||
|
||||
PUBLISH_CLAWHUB=false
|
||||
if [ "$PUBLISHABLE" = "true" ]; then
|
||||
PUBLISH_CLAWHUB=true
|
||||
fi
|
||||
|
||||
echo "internal=${INTERNAL}" >> $GITHUB_OUTPUT
|
||||
echo "openclaw_skill=${OPENCLAW_SKILL}" >> $GITHUB_OUTPUT
|
||||
echo "publish_clawhub=${PUBLISH_CLAWHUB}" >> $GITHUB_OUTPUT
|
||||
echo "publishable=${PUBLISHABLE}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Node
|
||||
@@ -788,6 +843,26 @@ jobs:
|
||||
|
||||
mkdir -p release-assets
|
||||
|
||||
normalize_release_path() {
|
||||
local path="$1"
|
||||
path="${path//\\//}"
|
||||
while [[ "$path" == ./* ]]; do
|
||||
path="${path#./}"
|
||||
done
|
||||
while [[ "$path" == *//* ]]; do
|
||||
path="${path//\/\//\/}"
|
||||
done
|
||||
if [[ -z "$path" || "$path" == /* || "$path" == [A-Za-z]:* || "$path" == ".." || "$path" == ../* || "$path" == */.. || "$path" == */../* ]]; then
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$path"
|
||||
}
|
||||
|
||||
is_test_release_path() {
|
||||
local lower="${1,,}"
|
||||
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
|
||||
}
|
||||
|
||||
# --- Stage SBOM files preserving directory structure ---
|
||||
STAGING_DIR="$(mktemp -d)"
|
||||
INNER_DIR="$STAGING_DIR/$SKILL_NAME"
|
||||
@@ -795,8 +870,16 @@ jobs:
|
||||
TEMPFILE="$(mktemp)"
|
||||
jq -r '.sbom.files[].path' "$SKILL_PATH/skill.json" > "$TEMPFILE"
|
||||
|
||||
while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
while IFS= read -r raw_file; do
|
||||
[ -z "$raw_file" ] && continue
|
||||
if ! file="$(normalize_release_path "$raw_file")"; then
|
||||
echo "::error file=$SKILL_PATH/skill.json::SBOM references unsafe file path: $raw_file"
|
||||
exit 1
|
||||
fi
|
||||
if is_test_release_path "$file"; then
|
||||
echo "Skipping test-only release file: $file"
|
||||
continue
|
||||
fi
|
||||
FULL_PATH="$SKILL_PATH/$file"
|
||||
if [ -f "$FULL_PATH" ]; then
|
||||
mkdir -p "$INNER_DIR/$(dirname "$file")"
|
||||
@@ -812,11 +895,22 @@ jobs:
|
||||
# --- Create zip preserving directory structure ---
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
(cd "$STAGING_DIR" && zip -qr "$OLDPWD/release-assets/$ZIP_NAME" .)
|
||||
if unzip -Z1 "release-assets/$ZIP_NAME" | grep -Eiq '(^|/)(test|tests)/'; then
|
||||
echo "::error::Release archive contains test-only files: $ZIP_NAME"
|
||||
unzip -Z1 "release-assets/$ZIP_NAME" | grep -Ei '(^|/)(test|tests)/' || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Generate checksums.json via jq ---
|
||||
FILES_JSON="{}"
|
||||
while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
while IFS= read -r raw_file; do
|
||||
[ -z "$raw_file" ] && continue
|
||||
if ! file="$(normalize_release_path "$raw_file")"; then
|
||||
continue
|
||||
fi
|
||||
if is_test_release_path "$file"; then
|
||||
continue
|
||||
fi
|
||||
FULL_PATH="$SKILL_PATH/$file"
|
||||
if [ -f "$FULL_PATH" ]; then
|
||||
SHA256=$(sha256sum "$FULL_PATH" | awk '{print $1}')
|
||||
@@ -946,6 +1040,71 @@ jobs:
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build quick install instructions
|
||||
id: install
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
|
||||
VERSION="${{ steps.parse.outputs.version }}"
|
||||
REPO="${{ github.repository }}"
|
||||
TAG="${{ github.ref_name }}"
|
||||
|
||||
{
|
||||
echo "quick_install<<INSTALL_EOF"
|
||||
|
||||
if [ "${{ steps.publishable.outputs.publish_clawhub }}" = "true" ] && [ "${{ steps.publishable.outputs.openclaw_skill }}" = "true" ]; then
|
||||
cat <<EOF
|
||||
### Quick Install
|
||||
|
||||
**Via ClawHub (recommended):**
|
||||
\`\`\`bash
|
||||
npx clawhub@latest install ${SKILL_NAME}
|
||||
\`\`\`
|
||||
|
||||
**If you already have \`clawsec-suite\` installed:**
|
||||
Ask your agent to pull \`${SKILL_NAME}\` from the ClawSec catalog and it will handle setup and verification automatically.
|
||||
EOF
|
||||
else
|
||||
cat <<EOF
|
||||
### Quick Install
|
||||
|
||||
**GitHub release artifact (recommended):**
|
||||
Ask your agent to read the published skill instructions from this GitHub release and follow them:
|
||||
|
||||
https://github.com/${REPO}/releases/download/${TAG}/SKILL.md
|
||||
|
||||
Or download them locally:
|
||||
\`\`\`bash
|
||||
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/SKILL.md
|
||||
\`\`\`
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
**Manual download with verification:**
|
||||
\`\`\`bash
|
||||
# 1. Download the release archive, checksums, and signing material
|
||||
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/${SKILL_NAME}-v${VERSION}.zip
|
||||
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/checksums.json
|
||||
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/checksums.sig
|
||||
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/signing-public.pem
|
||||
|
||||
# 2. Verify the checksums manifest signature (Ed25519)
|
||||
openssl base64 -d -A -in checksums.sig -out checksums.sig.bin
|
||||
openssl pkeyutl -verify -rawin -pubin -inkey signing-public.pem -sigfile checksums.sig.bin -in checksums.json
|
||||
|
||||
# 3. Verify archive checksum from the signed manifest
|
||||
echo "\$(jq -r '.archive.sha256' checksums.json) ${SKILL_NAME}-v${VERSION}.zip" | sha256sum -c
|
||||
|
||||
# 4. Extract (creates ${SKILL_NAME}/ directory)
|
||||
unzip ${SKILL_NAME}-v${VERSION}.zip
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
echo "INSTALL_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
@@ -957,34 +1116,7 @@ jobs:
|
||||
|
||||
${{ steps.changelog.outputs.changelog }}
|
||||
|
||||
### Quick Install
|
||||
|
||||
**Via clawhub (recommended):**
|
||||
```bash
|
||||
npx clawhub@latest install ${{ steps.parse.outputs.skill_name }}
|
||||
```
|
||||
|
||||
**If you already have `clawsec-suite` installed:**
|
||||
Ask your agent to pull `${{ steps.parse.outputs.skill_name }}` from the ClawSec catalog and it will handle setup and verification automatically.
|
||||
|
||||
**Manual download with verification:**
|
||||
```bash
|
||||
# 1. Download the release archive, checksums, and signing material
|
||||
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/${{ steps.parse.outputs.skill_name }}-v${{ steps.parse.outputs.version }}.zip
|
||||
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/checksums.json
|
||||
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/checksums.sig
|
||||
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/signing-public.pem
|
||||
|
||||
# 2. Verify the checksums manifest signature (Ed25519)
|
||||
openssl base64 -d -A -in checksums.sig -out checksums.sig.bin
|
||||
openssl pkeyutl -verify -rawin -pubin -inkey signing-public.pem -sigfile checksums.sig.bin -in checksums.json
|
||||
|
||||
# 3. Verify archive checksum from the signed manifest
|
||||
echo "$(jq -r '.archive.sha256' checksums.json) ${{ steps.parse.outputs.skill_name }}-v${{ steps.parse.outputs.version }}.zip" | sha256sum -c
|
||||
|
||||
# 4. Extract (creates ${{ steps.parse.outputs.skill_name }}/ directory)
|
||||
unzip ${{ steps.parse.outputs.skill_name }}-v${{ steps.parse.outputs.version }}.zip
|
||||
```
|
||||
${{ steps.install.outputs.quick_install }}
|
||||
|
||||
### Verification
|
||||
|
||||
@@ -1061,28 +1193,28 @@ jobs:
|
||||
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Check if publishable
|
||||
if: needs.release-tag.outputs.publishable != 'true'
|
||||
if: needs.release-tag.outputs.publish_clawhub != 'true'
|
||||
run: |
|
||||
echo "Skill marked as internal, skipping ClawHub publish"
|
||||
echo "Skill is not eligible for ClawHub publishing; skipping"
|
||||
exit 0
|
||||
|
||||
- name: Checkout
|
||||
if: needs.release-tag.outputs.publishable == 'true'
|
||||
if: needs.release-tag.outputs.publish_clawhub == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
if: needs.release-tag.outputs.publishable == 'true'
|
||||
if: needs.release-tag.outputs.publish_clawhub == 'true'
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install clawhub CLI
|
||||
if: needs.release-tag.outputs.publishable == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
run: npm install -g clawhub@${CLAWHUB_CLI_VERSION}
|
||||
|
||||
- name: Patch clawhub publish payload workaround
|
||||
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms.
|
||||
if: needs.release-tag.outputs.publishable == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const { execSync } = require("node:child_process");
|
||||
@@ -1125,7 +1257,7 @@ jobs:
|
||||
NODE
|
||||
|
||||
- name: Login to ClawHub
|
||||
if: needs.release-tag.outputs.publishable == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
|
||||
@@ -1136,7 +1268,7 @@ jobs:
|
||||
clawhub login --token "$CLAWHUB_TOKEN" --site "$SITE" --no-input
|
||||
|
||||
- name: Guard duplicate ClawHub version
|
||||
if: needs.release-tag.outputs.publishable == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
|
||||
@@ -1166,7 +1298,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Publish to ClawHub
|
||||
if: needs.release-tag.outputs.publishable == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
|
||||
@@ -1240,7 +1372,7 @@ jobs:
|
||||
id: publishable
|
||||
run: |
|
||||
SKILL_PATH="${{ steps.parse.outputs.skill_path }}"
|
||||
INTERNAL=$(jq -r '.openclaw.internal // false' "$SKILL_PATH/skill.json")
|
||||
INTERNAL=$(jq -r 'if (.openclaw | type) == "object" then (.openclaw.internal // false) else false end' "$SKILL_PATH/skill.json")
|
||||
|
||||
if [ "$INTERNAL" = "true" ]; then
|
||||
echo "::error::Skill is marked internal and cannot be published to ClawHub"
|
||||
|
||||
@@ -2,16 +2,19 @@ import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import type { SkillMetadata } from '../types';
|
||||
import { getPlatformDescriptor } from '../utils/advisoryPlatforms';
|
||||
|
||||
interface SkillCardProps {
|
||||
skill: SkillMetadata;
|
||||
}
|
||||
|
||||
export const SkillCard: React.FC<SkillCardProps> = ({ skill }) => {
|
||||
const platforms = Array.isArray(skill.platforms) ? skill.platforms.slice(0, 4) : [];
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/skills/${skill.id}`}
|
||||
className="group block bg-clawd-800 border border-clawd-700 rounded-xl p-5 hover:border-clawd-accent/30 hover:bg-clawd-800/80 transition-all duration-200"
|
||||
className="group block bg-clawd-800 border border-clawd-700 rounded-lg p-5 hover:border-clawd-accent/30 hover:bg-clawd-800/80 transition-all duration-200"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span className="text-2xl">{skill.emoji || '📦'}</span>
|
||||
@@ -27,6 +30,23 @@ export const SkillCard: React.FC<SkillCardProps> = ({ skill }) => {
|
||||
{skill.description}
|
||||
</p>
|
||||
|
||||
{platforms.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mb-4" aria-label="Recommended platforms">
|
||||
{platforms.map((platform) => {
|
||||
const descriptor = getPlatformDescriptor(platform);
|
||||
|
||||
return (
|
||||
<span
|
||||
key={platform}
|
||||
className={`text-[11px] leading-none px-2 py-1 rounded-md ${descriptor.classes}`}
|
||||
>
|
||||
{descriptor.label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Category badge - hidden for now, uncomment when we have multiple categories
|
||||
<span className="text-xs text-gray-500 bg-clawd-700 px-2 py-1 rounded">
|
||||
|
||||
+44
-31
@@ -4,41 +4,19 @@ import { ArrowLeft, Copy, Check, Download, ExternalLink, FileText, Shield } from
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { Footer } from '../components/Footer';
|
||||
import type { SkillJson, SkillChecksums, SkillPlatformMetadata } from '../types';
|
||||
import type { SkillJson, SkillChecksums } from '../types';
|
||||
import { getPlatformDescriptor } from '../utils/advisoryPlatforms';
|
||||
import { defaultMarkdownComponents } from '../utils/markdownComponents';
|
||||
import { stripFrontmatter } from '../utils/markdownHelpers.mjs';
|
||||
import { getRecommendedSkillPlatforms, resolveSkillPlatformMetadata } from '../utils/skillPlatforms';
|
||||
|
||||
const PLATFORM_METADATA_KEYS = ['openclaw', 'hermes', 'nanoclaw', 'picoclaw'] as const;
|
||||
const RELEASE_REPO_URL = 'https://github.com/prompt-security/clawsec';
|
||||
|
||||
const isProbablyHtmlDocument = (text: string): boolean => {
|
||||
const start = text.trimStart().slice(0, 200).toLowerCase();
|
||||
return start.startsWith('<!doctype html') || start.startsWith('<html');
|
||||
};
|
||||
|
||||
const isPlatformMetadataObject = (value: unknown): value is SkillPlatformMetadata => {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
||||
const maybe = value as Record<string, unknown>;
|
||||
return 'emoji' in maybe || 'category' in maybe || 'triggers' in maybe;
|
||||
};
|
||||
|
||||
const resolvePlatformMetadata = (skill: SkillJson): SkillPlatformMetadata => {
|
||||
const platform = skill.platform;
|
||||
if (
|
||||
typeof platform === 'string' &&
|
||||
(PLATFORM_METADATA_KEYS as readonly string[]).includes(platform)
|
||||
) {
|
||||
const platformBlock = skill[platform as (typeof PLATFORM_METADATA_KEYS)[number]];
|
||||
if (isPlatformMetadataObject(platformBlock)) return platformBlock;
|
||||
}
|
||||
|
||||
for (const key of PLATFORM_METADATA_KEYS) {
|
||||
const fallbackBlock = skill[key];
|
||||
if (isPlatformMetadataObject(fallbackBlock)) return fallbackBlock;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
export const SkillDetail: React.FC = () => {
|
||||
const { skillId } = useParams<{ skillId: string }>();
|
||||
const [skillData, setSkillData] = useState<SkillJson | null>(null);
|
||||
@@ -147,10 +125,29 @@ export const SkillDetail: React.FC = () => {
|
||||
setTimeout(() => setCopied(null), 2000);
|
||||
};
|
||||
|
||||
const installCommand = skillData
|
||||
? `npx clawhub@latest install ${skillData.name}`
|
||||
const releaseTag = skillData ? `${skillData.name}-v${skillData.version}` : '';
|
||||
const skillInstructionsUrl = releaseTag
|
||||
? `${RELEASE_REPO_URL}/releases/download/${releaseTag}/SKILL.md`
|
||||
: '';
|
||||
|
||||
const recommendedPlatforms = useMemo(
|
||||
() => (skillData ? getRecommendedSkillPlatforms(skillData) : []),
|
||||
[skillData]
|
||||
);
|
||||
|
||||
const isOpenClawSkill = recommendedPlatforms.includes('openclaw');
|
||||
|
||||
const installCommand = skillData
|
||||
? isOpenClawSkill
|
||||
? `npx clawhub@latest install ${skillData.name}`
|
||||
: `curl -sLO ${skillInstructionsUrl}`
|
||||
: '';
|
||||
|
||||
const installLabel = isOpenClawSkill ? 'Via ClawHub' : 'Via SKILL.md instructions';
|
||||
const installHelp = isOpenClawSkill
|
||||
? 'Recommended for OpenClaw-compatible skills.'
|
||||
: 'Pull the published instruction file and follow the platform-specific setup steps.';
|
||||
|
||||
const releasePageUrl = useMemo(() => {
|
||||
if (!skillData) return '';
|
||||
|
||||
@@ -160,7 +157,7 @@ export const SkillDetail: React.FC = () => {
|
||||
const [owner, repo] = url.pathname.split('/').filter(Boolean);
|
||||
if (owner && repo) {
|
||||
const repoBase = `${url.origin}/${owner}/${repo.replace(/\\.git$/, '')}`;
|
||||
return `${repoBase}/releases/tag/${skillData.name}-v${skillData.version}`;
|
||||
return `${repoBase}/releases/tag/${releaseTag}`;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -168,10 +165,10 @@ export const SkillDetail: React.FC = () => {
|
||||
}
|
||||
|
||||
return skillData.homepage;
|
||||
}, [skillData]);
|
||||
}, [releaseTag, skillData]);
|
||||
|
||||
const platformMetadata = useMemo(
|
||||
() => (skillData ? resolvePlatformMetadata(skillData) : null),
|
||||
() => (skillData ? resolveSkillPlatformMetadata(skillData) : null),
|
||||
[skillData]
|
||||
);
|
||||
|
||||
@@ -221,6 +218,18 @@ export const SkillDetail: React.FC = () => {
|
||||
<h1 className="text-3xl font-bold text-white mb-1">{skillData.name}</h1>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<span className="text-gray-500 font-mono">v{skillData.version}</span>
|
||||
{recommendedPlatforms.slice(0, 4).map((platform) => {
|
||||
const descriptor = getPlatformDescriptor(platform);
|
||||
|
||||
return (
|
||||
<span
|
||||
key={platform}
|
||||
className={`text-xs px-2 py-0.5 rounded-md ${descriptor.classes}`}
|
||||
>
|
||||
{descriptor.label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
{/* Category badge - hidden for now, uncomment when we have multiple categories
|
||||
<span className="text-gray-500 bg-clawd-800 px-2 py-0.5 rounded">
|
||||
{platformMetadata?.category || 'utility'}
|
||||
@@ -254,6 +263,10 @@ export const SkillDetail: React.FC = () => {
|
||||
<Download size={20} />
|
||||
Quick Install
|
||||
</h2>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">{installLabel}</p>
|
||||
<p className="text-sm text-gray-400">{installHelp}</p>
|
||||
</div>
|
||||
<div className="bg-clawd-800 rounded-lg p-3 sm:p-4 flex items-center justify-between gap-2 sm:gap-4">
|
||||
<code className="text-gray-200 font-mono text-xs sm:text-sm overflow-x-auto break-all min-w-0 flex-1">
|
||||
{installCommand}
|
||||
|
||||
@@ -71,3 +71,14 @@ if [[ "$CANONICAL_FPR" != "$DOC_EXPECTED_FPR" ]]; then
|
||||
fi
|
||||
|
||||
echo "All signing key references are consistent: $CANONICAL_FPR"
|
||||
|
||||
while IFS= read -r skill_md; do
|
||||
while IFS= read -r doc_fpr; do
|
||||
if [[ "$doc_fpr" != "$CANONICAL_FPR" ]]; then
|
||||
echo "ERROR: $skill_md RELEASE_PUBKEY_SHA256 ($doc_fpr) != canonical fingerprint ($CANONICAL_FPR)" >&2
|
||||
exit 1
|
||||
fi
|
||||
done < <(awk -F'"' '/RELEASE_PUBKEY_SHA256=/{print $2}' "$skill_md")
|
||||
done < <(find skills -mindepth 2 -maxdepth 2 -name SKILL.md -print | sort)
|
||||
|
||||
echo "All skill doc RELEASE_PUBKEY_SHA256 references match the canonical signing key."
|
||||
|
||||
@@ -169,15 +169,23 @@ EOF
|
||||
|
||||
# Build skill entry for index
|
||||
SKILL_DATA=$(jq -c --arg tag "$TAG" '
|
||||
. as $skill |
|
||||
def object_or_empty($value):
|
||||
if ($value | type) == "object" then $value else {} end;
|
||||
def object_field($name):
|
||||
object_or_empty(.[$name]?);
|
||||
object_or_empty($skill[$name]?);
|
||||
def platform_meta:
|
||||
(.platform as $platform
|
||||
| if ($platform | type) == "string" then object_or_empty(.[$platform]?)
|
||||
($skill.platform as $platform
|
||||
| if ($platform | type) == "string" then object_or_empty($skill[$platform]?)
|
||||
else {}
|
||||
end);
|
||||
def platform_list:
|
||||
([]
|
||||
+ (if ($skill.platforms | type) == "array" then $skill.platforms else [] end)
|
||||
+ (if ($skill.platform | type) == "string" then [$skill.platform] else [] end)
|
||||
+ (["openclaw", "hermes", "nanoclaw", "picoclaw"] | map(select((object_field(.) | length) > 0))))
|
||||
| map(select(type == "string") | ascii_downcase)
|
||||
| unique;
|
||||
{
|
||||
id: .name,
|
||||
name: .name,
|
||||
@@ -185,6 +193,7 @@ EOF
|
||||
description: .description,
|
||||
emoji: (platform_meta.emoji // object_field("openclaw").emoji // object_field("hermes").emoji // object_field("nanoclaw").emoji // object_field("picoclaw").emoji // "📦"),
|
||||
category: (platform_meta.category // object_field("openclaw").category // object_field("hermes").category // object_field("nanoclaw").category // object_field("picoclaw").category // "utility"),
|
||||
platforms: platform_list,
|
||||
tag: $tag
|
||||
}
|
||||
' "$SKILL_JSON")
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.3] - 2026-05-14
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
All notable changes to the Claw Release skill will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: claw-release
|
||||
version: 0.0.2
|
||||
version: 0.0.3
|
||||
description: Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"🚀","category":"utility","internal":true}}
|
||||
@@ -26,6 +26,86 @@ Internal tool for releasing skills and managing the ClawSec catalog.
|
||||
- Side effects: creates commits, tags, pushes to remote, and publishes GitHub Releases
|
||||
- Trust model: run only from a trusted checkout with a clean working tree and maintainer approval
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="claw-release"
|
||||
VERSION="0.0.3"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Release Type | Command | Tag Format |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claw-release",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"description": "Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.4] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
All notable changes to the ClawSec ClawHub Checker will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-clawhub-checker
|
||||
version: 0.0.3
|
||||
version: 0.0.4
|
||||
description: ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.
|
||||
homepage: https://clawsec.prompt.security
|
||||
clawdis:
|
||||
@@ -45,6 +45,86 @@ Optional preflight check (validates local paths and prints recommended command):
|
||||
node ~/.openclaw/skills/clawsec-clawhub-checker/scripts/setup_reputation_hook.mjs
|
||||
```
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="clawsec-clawhub-checker"
|
||||
VERSION="0.0.4"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Usage
|
||||
|
||||
Run the enhanced installer directly from this skill:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-clawhub-checker",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.",
|
||||
"author": "abutbul",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -52,16 +52,6 @@
|
||||
"path": "CHANGELOG.md",
|
||||
"required": true,
|
||||
"description": "Version history and release notes"
|
||||
},
|
||||
{
|
||||
"path": "test/reputation_check.test.mjs",
|
||||
"required": false,
|
||||
"description": "Test suite for reputation checking functionality"
|
||||
},
|
||||
{
|
||||
"path": "test/setup_reputation_hook.test.mjs",
|
||||
"required": false,
|
||||
"description": "Regression coverage for setup preflight behavior"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.7] - 2026-05-14
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
All notable changes to the ClawSec Feed skill will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-feed
|
||||
version: 0.0.6
|
||||
version: 0.0.7
|
||||
description: Security advisory feed package for OpenClaw-related threats and vulnerabilities. The upstream feed is updated daily; local automation is handled by clawsec-suite or the operator.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"📡","category":"security"}}
|
||||
@@ -81,6 +81,86 @@ Once you have this skill file, proceed to **[Deploy ClawSec Feed](#deploy-clawse
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="clawsec-feed"
|
||||
VERSION="0.0.7"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Deploy ClawSec Feed
|
||||
|
||||
Installation steps:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-feed",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"description": "Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.5] - 2026-05-14
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
All notable changes to the ClawSec NanoClaw compatibility skill will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-nanoclaw
|
||||
version: 0.0.4
|
||||
version: 0.0.5
|
||||
description: Use when checking for security vulnerabilities in NanoClaw skills, before installing new skills, or when asked about security advisories affecting the bot
|
||||
---
|
||||
|
||||
@@ -198,3 +198,83 @@ See [INSTALL.md](./INSTALL.md) for setup and [docs/](./docs/) for advanced usage
|
||||
- Alerts to supply chain attacks in dependencies
|
||||
- Provides actionable remediation steps
|
||||
- Zero false positives (curated feed only)
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="clawsec-nanoclaw"
|
||||
VERSION="0.0.5"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-nanoclaw",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"description": "ClawSec security suite for NanoClaw - Advisory feed monitoring, MCP tools for vulnerability checking, and Ed25519 signature verification for containerized WhatsApp bot agents",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.3] - 2026-05-13
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
All notable changes to the ClawSec Scanner will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-scanner
|
||||
version: 0.0.2
|
||||
version: 0.0.3
|
||||
description: Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific DAST hook execution testing for OpenClaw hooks.
|
||||
homepage: https://clawsec.prompt.security
|
||||
clawdis:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-scanner",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"description": "Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific DAST hook execution testing for OpenClaw hooks.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -93,26 +93,6 @@
|
||||
"path": "hooks/clawsec-scanner-hook/handler.ts",
|
||||
"required": false,
|
||||
"description": "OpenClaw hook handler for periodic vulnerability scanning"
|
||||
},
|
||||
{
|
||||
"path": "test/dependency_scanner.test.mjs",
|
||||
"required": false,
|
||||
"description": "Unit tests for dependency scanning (npm audit, pip-audit)"
|
||||
},
|
||||
{
|
||||
"path": "test/cve_integration.test.mjs",
|
||||
"required": false,
|
||||
"description": "Integration tests for CVE database API queries"
|
||||
},
|
||||
{
|
||||
"path": "test/sast_engine.test.mjs",
|
||||
"required": false,
|
||||
"description": "Unit tests for SAST analysis (Semgrep, Bandit)"
|
||||
},
|
||||
{
|
||||
"path": "test/dast_harness.test.mjs",
|
||||
"required": false,
|
||||
"description": "DAST harness tests for real hook execution and malicious-input failure detection"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.6] - 2026-05-14
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
All notable changes to Clawtributor will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawtributor
|
||||
version: 0.0.5
|
||||
version: 0.0.6
|
||||
description: Community incident reporting for AI agents. Contribute to collective security by reporting threats.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"🤝","category":"security"}}
|
||||
@@ -44,6 +44,86 @@ I will keep reports local unless you explicitly approve submission.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="clawtributor"
|
||||
VERSION="0.0.6"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## What Clawtributor Does
|
||||
|
||||
### Community-Driven Security Reporting
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawtributor",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"description": "Community incident reporting for AI agents. Contribute to collective security by reporting threats.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.1] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
## [0.1.0] - 2026-04-21
|
||||
|
||||
- Added mandatory release verification gate guidance before install: `checksums.json`, `checksums.sig`, and pinned signing public-key fingerprint.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: hermes-attestation-guardian
|
||||
version: 0.1.0
|
||||
version: 0.1.1
|
||||
description: Hermes-only runtime security attestation and drift detection skill for operator-managed Hermes infrastructure.
|
||||
homepage: https://clawsec.prompt.security
|
||||
hermes:
|
||||
@@ -15,42 +15,90 @@ IMPORTANT SCOPE:
|
||||
- This skill targets Hermes infrastructure only (CLI/Gateway/profile-managed deployments).
|
||||
- This skill is not an OpenClaw runtime hook package.
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="hermes-attestation-guardian"
|
||||
VERSION="0.1.1"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Goal
|
||||
|
||||
Generate deterministic Hermes posture attestations, verify them with fail-closed integrity checks, and compare baseline drift using stable severity mapping.
|
||||
|
||||
## Mandatory release verification gate (before install)
|
||||
|
||||
Before treating any release install instructions as valid, verify all three inputs:
|
||||
|
||||
1) `checksums.json`
|
||||
2) `checksums.sig`
|
||||
3) pinned signing public-key fingerprint
|
||||
|
||||
```bash
|
||||
BASE="https://github.com/prompt-security/clawsec/releases/download/hermes-attestation-guardian-v0.1.0"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP/signing-public.pem"
|
||||
|
||||
[ -s "$TMP/checksums.json" ] || { echo "ERROR: missing checksums.json" >&2; exit 1; }
|
||||
[ -s "$TMP/checksums.sig" ] || { echo "ERROR: missing checksums.sig" >&2; exit 1; }
|
||||
|
||||
EXPECTED_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP/signing-public.pem" -outform DER | sha256sum | awk '{print $1}')"
|
||||
[ "$ACTUAL_PUBKEY_SHA256" = "$EXPECTED_PUBKEY_SHA256" ] || {
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
openssl base64 -d -A -in "$TMP/checksums.sig" -out "$TMP/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin -inkey "$TMP/signing-public.pem" \
|
||||
-sigfile "$TMP/checksums.sig.bin" -in "$TMP/checksums.json" >/dev/null
|
||||
```
|
||||
|
||||
## Hermes guard trust policy note
|
||||
|
||||
When installing from community sources, configure Hermes guard to use signature-aware trust (trusted signer fingerprint allowlist) rather than source-name-only trust. Unknown signer fingerprints should stay on community policy, and invalid signatures must remain blocked.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hermes-attestation-guardian",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "Hermes-only runtime security attestation and drift detection skill. Generates deterministic posture artifacts, verifies integrity fail-closed, and classifies baseline drift severity.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -80,46 +80,6 @@
|
||||
"path": "scripts/setup_advisory_check_cron.mjs",
|
||||
"required": true,
|
||||
"description": "Optional recurring schedule setup for Hermes guarded advisory checks"
|
||||
},
|
||||
{
|
||||
"path": "test/attestation_schema.test.mjs",
|
||||
"required": false,
|
||||
"description": "Schema and determinism tests"
|
||||
},
|
||||
{
|
||||
"path": "test/attestation_diff.test.mjs",
|
||||
"required": false,
|
||||
"description": "Diff and severity mapping tests"
|
||||
},
|
||||
{
|
||||
"path": "test/attestation_cli.test.mjs",
|
||||
"required": false,
|
||||
"description": "Generator/verifier CLI behavior tests"
|
||||
},
|
||||
{
|
||||
"path": "test/setup_attestation_cron.test.mjs",
|
||||
"required": false,
|
||||
"description": "Hermes-only cron setup tests"
|
||||
},
|
||||
{
|
||||
"path": "test/setup_advisory_check_cron.test.mjs",
|
||||
"required": false,
|
||||
"description": "Hermes-only guarded advisory cron setup tests"
|
||||
},
|
||||
{
|
||||
"path": "test/feed_verification.test.mjs",
|
||||
"required": false,
|
||||
"description": "Advisory feed signature/checksum verification behavior tests"
|
||||
},
|
||||
{
|
||||
"path": "test/guarded_skill_verify.test.mjs",
|
||||
"required": false,
|
||||
"description": "Advisory-aware guarded verification gate behavior tests"
|
||||
},
|
||||
{
|
||||
"path": "test/hermes_attestation_sandbox_regression.sh",
|
||||
"required": false,
|
||||
"description": "Sandboxed end-to-end regression harness for install and verification paths"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
## [0.0.1-beta1] - 2026-05-10
|
||||
|
||||
- Added baseline skill metadata, frontmatter, and implementation specification.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: hermes-traffic-guardian
|
||||
version: 0.0.1-beta1
|
||||
version: 0.0.1-beta2
|
||||
description: Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -15,6 +15,86 @@ hermes:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="hermes-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Scope
|
||||
|
||||
Builders should use this skill as the Hermes landing zone for runtime traffic monitoring:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hermes-traffic-guardian",
|
||||
"version": "0.0.1-beta1",
|
||||
"version": "0.0.1-beta2",
|
||||
"description": "Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -49,11 +49,6 @@
|
||||
"path": "scripts/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for lifecycle, status, and attestation export scripts"
|
||||
},
|
||||
{
|
||||
"path": "test/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for unit and integration tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
## [0.0.1-beta1] - 2026-05-10
|
||||
|
||||
- Added baseline skill metadata, frontmatter, and implementation specification.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: nanoclaw-traffic-guardian
|
||||
version: 0.0.1-beta1
|
||||
version: 0.0.1-beta2
|
||||
description: NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -14,6 +14,86 @@ nanoclaw:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="nanoclaw-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Scope
|
||||
|
||||
Builders should use this skill as the NanoClaw landing zone for runtime traffic monitoring:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nanoclaw-traffic-guardian",
|
||||
"version": "0.0.1-beta1",
|
||||
"version": "0.0.1-beta2",
|
||||
"description": "NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -54,11 +54,6 @@
|
||||
"path": "mcp-tools/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for container-side MCP tool definitions"
|
||||
},
|
||||
{
|
||||
"path": "test/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for unit and integration tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.5] - 2026-05-14
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-audit-watchdog
|
||||
version: 0.1.4
|
||||
version: 0.1.5
|
||||
description: Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Runs deep audits, creates or updates a recurring cron job, and sends formatted reports to configured recipients.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata:
|
||||
@@ -65,6 +65,86 @@ Continue below for standalone installation instructions.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="openclaw-audit-watchdog"
|
||||
VERSION="0.1.5"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Operational requirements
|
||||
|
||||
Required runtime:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openclaw-audit-watchdog",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Creates or updates an unattended cron job and sends formatted reports to configured recipients.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
## [0.0.1-beta1] - 2026-05-10
|
||||
|
||||
- Added baseline skill metadata, frontmatter, and implementation specification.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-traffic-guardian
|
||||
version: 0.0.1-beta1
|
||||
version: 0.0.1-beta2
|
||||
description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -15,6 +15,86 @@ clawdis:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="openclaw-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Scope
|
||||
|
||||
Builders should use this skill as the OpenClaw landing zone for runtime traffic monitoring:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openclaw-traffic-guardian",
|
||||
"version": "0.0.1-beta1",
|
||||
"version": "0.0.1-beta2",
|
||||
"description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -53,11 +53,6 @@
|
||||
"path": "hooks/openclaw-traffic-guardian-hook/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for optional OpenClaw hook integration"
|
||||
},
|
||||
{
|
||||
"path": "test/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for unit and integration tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
## [0.0.1] - 2026-04-26
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: picoclaw-security-guardian
|
||||
version: 0.0.1
|
||||
version: 0.0.2
|
||||
description: Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -18,6 +18,86 @@ picoclaw:
|
||||
|
||||
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`.
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="picoclaw-security-guardian"
|
||||
VERSION="0.0.2"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Goal
|
||||
|
||||
Provide Picoclaw with the same support-matrix security capabilities ClawSec tracks for mature platform modules:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picoclaw-security-guardian",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -70,31 +70,11 @@
|
||||
"path": "scripts/check_advisories.mjs",
|
||||
"required": true,
|
||||
"description": "Check Picoclaw-relevant advisories from a signed/verified feed state"
|
||||
},
|
||||
{
|
||||
"path": "test/profile.test.mjs",
|
||||
"required": false,
|
||||
"description": "Profile generation and path-safety tests"
|
||||
},
|
||||
{
|
||||
"path": "test/drift.test.mjs",
|
||||
"required": false,
|
||||
"description": "Drift severity tests"
|
||||
},
|
||||
{
|
||||
"path": "test/supply_chain.test.mjs",
|
||||
"required": false,
|
||||
"description": "Checksum and required-signature verification tests"
|
||||
},
|
||||
{
|
||||
"path": "test/picoclaw_security_guardian_sandbox_regression.sh",
|
||||
"required": false,
|
||||
"description": "Isolated Docker/Picoclaw install regression harness using Picoclaw find_skills/install_skill and skill-loader validation for pre-release checks"
|
||||
}
|
||||
]
|
||||
},
|
||||
"picoclaw": {
|
||||
"emoji": "\ud83e\udd90",
|
||||
"emoji": "🦐",
|
||||
"category": "security",
|
||||
"requires": {
|
||||
"bins": [
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
## [0.0.1] - 2026-04-26
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: picoclaw-self-pen-testing
|
||||
version: 0.0.1
|
||||
version: 0.0.2
|
||||
description: Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -18,6 +18,86 @@ picoclaw:
|
||||
|
||||
Purpose: keep Picoclaw posture-review checks isolated from the broader guardian package so moderation-sensitive checks can be versioned/published independently.
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="picoclaw-self-pen-testing"
|
||||
VERSION="0.0.2"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Scope
|
||||
|
||||
This skill only performs local, read-only posture-review analysis against an existing Picoclaw posture profile.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picoclaw-self-pen-testing",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -45,11 +45,6 @@
|
||||
"path": "scripts/self_pen_test.mjs",
|
||||
"required": true,
|
||||
"description": "Run posture-review checks on a profile"
|
||||
},
|
||||
{
|
||||
"path": "test/self_pen_test.test.mjs",
|
||||
"required": false,
|
||||
"description": "Finding classification tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
### Changed
|
||||
- Re-release skill payload metadata after excluding test-only files from release SBOMs and archives.
|
||||
|
||||
## [0.0.1-beta1] - 2026-05-10
|
||||
|
||||
- Added baseline skill metadata, frontmatter, and implementation specification.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: picoclaw-traffic-guardian
|
||||
version: 0.0.1-beta1
|
||||
version: 0.0.1-beta2
|
||||
description: Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -15,6 +15,86 @@ picoclaw:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="picoclaw-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Scope
|
||||
|
||||
Builders should use this skill as the Picoclaw landing zone for runtime traffic monitoring:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picoclaw-traffic-guardian",
|
||||
"version": "0.0.1-beta1",
|
||||
"version": "0.0.1-beta2",
|
||||
"description": "Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -49,11 +49,6 @@
|
||||
"path": "scripts/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for lifecycle, status, and profile export scripts"
|
||||
},
|
||||
{
|
||||
"path": "test/.gitkeep",
|
||||
"required": false,
|
||||
"description": "Placeholder for unit and integration tests"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.6] - 2026-05-14
|
||||
|
||||
### Security
|
||||
- Added explicit signed release artifact verification instructions for standalone installs, including `checksums.json`, `checksums.sig`, `signing-public.pem`, archive hash verification, and `SKILL.md`/`skill.json` checksum checks.
|
||||
|
||||
All notable changes to soul-guardian will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: soul-guardian
|
||||
version: 0.0.5
|
||||
version: 0.0.6
|
||||
description: Drift detection + baseline integrity guard for agent workspace files with automatic alerting support
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"👻","category":"security"}}
|
||||
@@ -22,6 +22,86 @@ Protects your agent's core files (SOUL.md, AGENTS.md, etc.) from unauthorized ch
|
||||
- Network behavior: none by default
|
||||
- Trust model: any scheduling is opt-in, but restore mode intentionally overwrites drifted files
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="soul-guardian"
|
||||
VERSION="0.0.6"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
|
||||
|
||||
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
|
||||
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
|
||||
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
|
||||
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
|
||||
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
|
||||
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
|
||||
|
||||
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
|
||||
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
|
||||
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
|
||||
openssl pkeyutl -verify -rawin -pubin \
|
||||
-inkey "$TMP_DIR/signing-public.pem" \
|
||||
-sigfile "$TMP_DIR/checksums.sig.bin" \
|
||||
-in "$TMP_DIR/checksums.json" >/dev/null
|
||||
|
||||
hash_file() {
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
else
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
verify_manifest_file() {
|
||||
asset="$1"
|
||||
path="$2"
|
||||
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected" ]; then
|
||||
echo "ERROR: checksums.json missing $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual="$(hash_file "$path")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "ERROR: checksum mismatch for $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
|
||||
if [ -z "$expected_archive" ]; then
|
||||
echo "ERROR: checksums.json missing archive.sha256" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
|
||||
if [ "$actual_archive" != "$expected_archive" ]; then
|
||||
echo "ERROR: archive checksum mismatch" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
|
||||
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
|
||||
|
||||
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
|
||||
```
|
||||
|
||||
Only install or extract the archive after this verification succeeds.
|
||||
|
||||
## Quick Start (3 Steps)
|
||||
|
||||
### Step 1: Initialize baselines
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soul-guardian",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"description": "Drift detection and baseline integrity guard for agent workspace prompt files. Auto-restore critical files with tamper-evident audit logging.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -78,6 +78,7 @@ export interface SkillMetadata {
|
||||
description: string;
|
||||
emoji: string;
|
||||
category: string;
|
||||
platforms?: AdvisoryPlatformSlug[];
|
||||
tag: string;
|
||||
}
|
||||
|
||||
@@ -129,6 +130,7 @@ export interface SkillJson {
|
||||
description: string;
|
||||
}>;
|
||||
};
|
||||
platforms?: AdvisoryPlatformSlug[];
|
||||
platform?: CorePlatformSlug | (string & {});
|
||||
openclaw?: SkillPlatformMetadata | null;
|
||||
hermes?: SkillPlatformMetadata | null;
|
||||
|
||||
+54
-3
@@ -12,12 +12,50 @@ Example:
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
|
||||
from validate_skill import validate_skill
|
||||
|
||||
_TEST_PATH_RE = re.compile(r"(^|/)(test|tests)/", re.IGNORECASE)
|
||||
|
||||
|
||||
def normalize_release_path(path: str) -> str:
|
||||
"""Normalize a skill SBOM path for release packaging.
|
||||
|
||||
Paths must remain relative POSIX paths inside the skill directory. Test
|
||||
filtering and checksum keys use this normalized form so local packaging and
|
||||
the GitHub release workflow apply the same policy.
|
||||
"""
|
||||
raw_path = str(path)
|
||||
windows_path = PureWindowsPath(raw_path)
|
||||
if windows_path.is_absolute() or windows_path.drive:
|
||||
raise ValueError(f"unsafe SBOM path: {path}")
|
||||
|
||||
normalized = raw_path.replace("\\", "/")
|
||||
while normalized.startswith("./"):
|
||||
normalized = normalized[2:]
|
||||
while "//" in normalized:
|
||||
normalized = normalized.replace("//", "/")
|
||||
|
||||
pure = PurePosixPath(normalized)
|
||||
if (
|
||||
not normalized
|
||||
or pure.is_absolute()
|
||||
or normalized == "."
|
||||
or ".." in pure.parts
|
||||
):
|
||||
raise ValueError(f"unsafe SBOM path: {path}")
|
||||
|
||||
return pure.as_posix()
|
||||
|
||||
|
||||
def is_test_release_path(path: str) -> bool:
|
||||
"""Return True for root or nested test/test(s) release paths."""
|
||||
return bool(_TEST_PATH_RE.search(path))
|
||||
|
||||
|
||||
def calculate_sha256(file_path: Path) -> str:
|
||||
"""Calculate SHA256 hash of a file."""
|
||||
@@ -72,10 +110,23 @@ def package_skill(skill_path: str, output_dir: str = None) -> tuple[Path | None,
|
||||
sbom_files = skill_data.get("sbom", {}).get("files", [])
|
||||
|
||||
for file_entry in sbom_files:
|
||||
file_rel_path = file_entry["path"]
|
||||
try:
|
||||
file_rel_path = normalize_release_path(file_entry["path"])
|
||||
except ValueError as exc:
|
||||
print(f"[ERROR] {exc}")
|
||||
return None, None
|
||||
if is_test_release_path(file_rel_path):
|
||||
print(f" Skipping test-only release file: {file_rel_path}")
|
||||
continue
|
||||
full_path = skill_path / file_rel_path
|
||||
if full_path.exists():
|
||||
files_to_checksum.append((file_rel_path, full_path))
|
||||
resolved_full_path = full_path.resolve()
|
||||
try:
|
||||
resolved_full_path.relative_to(skill_path)
|
||||
except ValueError:
|
||||
print(f"[ERROR] SBOM file escapes skill directory: {file_rel_path}")
|
||||
return None, None
|
||||
files_to_checksum.append((file_rel_path, resolved_full_path))
|
||||
|
||||
# Always include skill.json
|
||||
files_to_checksum.append(("skill.json", skill_json_path))
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
CORE_PLATFORM_SLUGS,
|
||||
type AdvisoryPlatformSlug,
|
||||
type CorePlatformSlug,
|
||||
type SkillJson,
|
||||
type SkillPlatformMetadata,
|
||||
} from '../types';
|
||||
|
||||
export const SKILL_PLATFORM_METADATA_KEYS = CORE_PLATFORM_SLUGS;
|
||||
|
||||
const normalizePlatformSlug = (platform: string) => platform.trim().toLowerCase();
|
||||
|
||||
export const isSkillPlatformMetadataObject = (value: unknown): value is SkillPlatformMetadata => {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
||||
const maybe = value as Record<string, unknown>;
|
||||
return 'emoji' in maybe || 'category' in maybe || 'triggers' in maybe;
|
||||
};
|
||||
|
||||
export const getRecommendedSkillPlatforms = (skill: SkillJson): AdvisoryPlatformSlug[] => {
|
||||
const platforms = new Set<string>();
|
||||
|
||||
if (Array.isArray(skill.platforms)) {
|
||||
for (const platform of skill.platforms) {
|
||||
if (typeof platform === 'string' && platform.trim()) {
|
||||
platforms.add(normalizePlatformSlug(platform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof skill.platform === 'string' && skill.platform.trim()) {
|
||||
platforms.add(normalizePlatformSlug(skill.platform));
|
||||
}
|
||||
|
||||
for (const key of SKILL_PLATFORM_METADATA_KEYS) {
|
||||
if (isSkillPlatformMetadataObject(skill[key])) {
|
||||
platforms.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return [...platforms] as AdvisoryPlatformSlug[];
|
||||
};
|
||||
|
||||
export const resolveSkillPlatformMetadata = (skill: SkillJson): SkillPlatformMetadata => {
|
||||
for (const platform of getRecommendedSkillPlatforms(skill)) {
|
||||
if ((SKILL_PLATFORM_METADATA_KEYS as readonly string[]).includes(platform)) {
|
||||
const platformBlock = skill[platform as CorePlatformSlug];
|
||||
if (isSkillPlatformMetadataObject(platformBlock)) return platformBlock;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of SKILL_PLATFORM_METADATA_KEYS) {
|
||||
const fallbackBlock = skill[key];
|
||||
if (isSkillPlatformMetadataObject(fallbackBlock)) return fallbackBlock;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
Reference in New Issue
Block a user