From 1e48a955ccf7f9a4b5d4aaedfbb9f47779348595 Mon Sep 17 00:00:00 2001 From: David Abutbul Date: Thu, 14 May 2026 14:38:58 +0300 Subject: [PATCH] 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 --- .github/workflows/deploy-pages.yml | 15 +- .github/workflows/skill-release.yml | 232 ++++++++++++++---- components/SkillCard.tsx | 22 +- pages/SkillDetail.tsx | 75 +++--- scripts/ci/verify_signing_key_consistency.sh | 11 + scripts/populate-local-skills.sh | 15 +- skills/claw-release/CHANGELOG.md | 5 + skills/claw-release/SKILL.md | 82 ++++++- skills/claw-release/skill.json | 2 +- skills/clawsec-clawhub-checker/CHANGELOG.md | 8 + skills/clawsec-clawhub-checker/SKILL.md | 82 ++++++- skills/clawsec-clawhub-checker/skill.json | 12 +- skills/clawsec-feed/CHANGELOG.md | 5 + skills/clawsec-feed/SKILL.md | 82 ++++++- skills/clawsec-feed/skill.json | 2 +- skills/clawsec-nanoclaw/CHANGELOG.md | 5 + skills/clawsec-nanoclaw/SKILL.md | 82 ++++++- skills/clawsec-nanoclaw/skill.json | 2 +- skills/clawsec-scanner/CHANGELOG.md | 5 + skills/clawsec-scanner/SKILL.md | 2 +- skills/clawsec-scanner/skill.json | 22 +- skills/clawtributor/CHANGELOG.md | 5 + skills/clawtributor/SKILL.md | 82 ++++++- skills/clawtributor/skill.json | 2 +- .../hermes-attestation-guardian/CHANGELOG.md | 8 + skills/hermes-attestation-guardian/SKILL.md | 114 ++++++--- skills/hermes-attestation-guardian/skill.json | 42 +--- skills/hermes-traffic-guardian/CHANGELOG.md | 8 + skills/hermes-traffic-guardian/SKILL.md | 82 ++++++- skills/hermes-traffic-guardian/skill.json | 7 +- skills/nanoclaw-traffic-guardian/CHANGELOG.md | 8 + skills/nanoclaw-traffic-guardian/SKILL.md | 82 ++++++- skills/nanoclaw-traffic-guardian/skill.json | 7 +- skills/openclaw-audit-watchdog/CHANGELOG.md | 5 + skills/openclaw-audit-watchdog/SKILL.md | 82 ++++++- skills/openclaw-audit-watchdog/skill.json | 2 +- skills/openclaw-traffic-guardian/CHANGELOG.md | 8 + skills/openclaw-traffic-guardian/SKILL.md | 82 ++++++- skills/openclaw-traffic-guardian/skill.json | 7 +- .../picoclaw-security-guardian/CHANGELOG.md | 8 + skills/picoclaw-security-guardian/SKILL.md | 82 ++++++- skills/picoclaw-security-guardian/skill.json | 24 +- skills/picoclaw-self-pen-testing/CHANGELOG.md | 8 + skills/picoclaw-self-pen-testing/SKILL.md | 82 ++++++- skills/picoclaw-self-pen-testing/skill.json | 7 +- skills/picoclaw-traffic-guardian/CHANGELOG.md | 8 + skills/picoclaw-traffic-guardian/SKILL.md | 82 ++++++- skills/picoclaw-traffic-guardian/skill.json | 7 +- skills/soul-guardian/CHANGELOG.md | 5 + skills/soul-guardian/SKILL.md | 82 ++++++- skills/soul-guardian/skill.json | 2 +- types.ts | 2 + utils/package_skill.py | 57 ++++- utils/skillPlatforms.ts | 57 +++++ 54 files changed, 1645 insertions(+), 269 deletions(-) create mode 100644 utils/skillPlatforms.ts diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 71ad8f8..5ab398f 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -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 } diff --git a/.github/workflows/skill-release.yml b/.github/workflows/skill-release.yml index 5890ba8..ef3c6c0 100644 --- a/.github/workflows/skill-release.yml +++ b/.github/workflows/skill-release.yml @@ -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<> "$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" diff --git a/components/SkillCard.tsx b/components/SkillCard.tsx index 406f019..4d98327 100644 --- a/components/SkillCard.tsx +++ b/components/SkillCard.tsx @@ -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 = ({ skill }) => { + const platforms = Array.isArray(skill.platforms) ? skill.platforms.slice(0, 4) : []; + return (
{skill.emoji || '📦'} @@ -27,6 +30,23 @@ export const SkillCard: React.FC = ({ skill }) => { {skill.description}

+ {platforms.length > 0 && ( +
+ {platforms.map((platform) => { + const descriptor = getPlatformDescriptor(platform); + + return ( + + {descriptor.label} + + ); + })} +
+ )} +
{/* Category badge - hidden for now, uncomment when we have multiple categories diff --git a/pages/SkillDetail.tsx b/pages/SkillDetail.tsx index 5a04562..febae04 100644 --- a/pages/SkillDetail.tsx +++ b/pages/SkillDetail.tsx @@ -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(' { - if (!value || typeof value !== 'object' || Array.isArray(value)) return false; - const maybe = value as Record; - 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(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 = () => {

{skillData.name}

v{skillData.version} + {recommendedPlatforms.slice(0, 4).map((platform) => { + const descriptor = getPlatformDescriptor(platform); + + return ( + + {descriptor.label} + + ); + })} {/* Category badge - hidden for now, uncomment when we have multiple categories {platformMetadata?.category || 'utility'} @@ -254,6 +263,10 @@ export const SkillDetail: React.FC = () => { Quick Install +
+

{installLabel}

+

{installHelp}

+
{installCommand} diff --git a/scripts/ci/verify_signing_key_consistency.sh b/scripts/ci/verify_signing_key_consistency.sh index 541bf66..7ab57af 100755 --- a/scripts/ci/verify_signing_key_consistency.sh +++ b/scripts/ci/verify_signing_key_consistency.sh @@ -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." diff --git a/scripts/populate-local-skills.sh b/scripts/populate-local-skills.sh index 100a8d1..55b5d21 100755 --- a/scripts/populate-local-skills.sh +++ b/scripts/populate-local-skills.sh @@ -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") diff --git a/skills/claw-release/CHANGELOG.md b/skills/claw-release/CHANGELOG.md index b545a6c..533e606 100644 --- a/skills/claw-release/CHANGELOG.md +++ b/skills/claw-release/CHANGELOG.md @@ -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/), diff --git a/skills/claw-release/SKILL.md b/skills/claw-release/SKILL.md index 743fc0c..6d9862e 100644 --- a/skills/claw-release/SKILL.md +++ b/skills/claw-release/SKILL.md @@ -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 | diff --git a/skills/claw-release/skill.json b/skills/claw-release/skill.json index 7315d1c..3b7a574 100644 --- a/skills/claw-release/skill.json +++ b/skills/claw-release/skill.json @@ -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", diff --git a/skills/clawsec-clawhub-checker/CHANGELOG.md b/skills/clawsec-clawhub-checker/CHANGELOG.md index a189377..07a7ea6 100644 --- a/skills/clawsec-clawhub-checker/CHANGELOG.md +++ b/skills/clawsec-clawhub-checker/CHANGELOG.md @@ -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/), diff --git a/skills/clawsec-clawhub-checker/SKILL.md b/skills/clawsec-clawhub-checker/SKILL.md index db39aec..7d173c9 100644 --- a/skills/clawsec-clawhub-checker/SKILL.md +++ b/skills/clawsec-clawhub-checker/SKILL.md @@ -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: diff --git a/skills/clawsec-clawhub-checker/skill.json b/skills/clawsec-clawhub-checker/skill.json index 9624a06..152e70a 100644 --- a/skills/clawsec-clawhub-checker/skill.json +++ b/skills/clawsec-clawhub-checker/skill.json @@ -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" } ] }, diff --git a/skills/clawsec-feed/CHANGELOG.md b/skills/clawsec-feed/CHANGELOG.md index f60b42a..4d771d8 100644 --- a/skills/clawsec-feed/CHANGELOG.md +++ b/skills/clawsec-feed/CHANGELOG.md @@ -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/), diff --git a/skills/clawsec-feed/SKILL.md b/skills/clawsec-feed/SKILL.md index cf40ecc..18d09bc 100644 --- a/skills/clawsec-feed/SKILL.md +++ b/skills/clawsec-feed/SKILL.md @@ -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: diff --git a/skills/clawsec-feed/skill.json b/skills/clawsec-feed/skill.json index ce297d6..ba2d9c8 100644 --- a/skills/clawsec-feed/skill.json +++ b/skills/clawsec-feed/skill.json @@ -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", diff --git a/skills/clawsec-nanoclaw/CHANGELOG.md b/skills/clawsec-nanoclaw/CHANGELOG.md index 7fc88a8..7f06410 100644 --- a/skills/clawsec-nanoclaw/CHANGELOG.md +++ b/skills/clawsec-nanoclaw/CHANGELOG.md @@ -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/), diff --git a/skills/clawsec-nanoclaw/SKILL.md b/skills/clawsec-nanoclaw/SKILL.md index 3986f12..afeda05 100644 --- a/skills/clawsec-nanoclaw/SKILL.md +++ b/skills/clawsec-nanoclaw/SKILL.md @@ -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. diff --git a/skills/clawsec-nanoclaw/skill.json b/skills/clawsec-nanoclaw/skill.json index 330e479..20fadb2 100644 --- a/skills/clawsec-nanoclaw/skill.json +++ b/skills/clawsec-nanoclaw/skill.json @@ -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", diff --git a/skills/clawsec-scanner/CHANGELOG.md b/skills/clawsec-scanner/CHANGELOG.md index 6053214..cd6075d 100644 --- a/skills/clawsec-scanner/CHANGELOG.md +++ b/skills/clawsec-scanner/CHANGELOG.md @@ -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/), diff --git a/skills/clawsec-scanner/SKILL.md b/skills/clawsec-scanner/SKILL.md index afc49d5..04a4fce 100644 --- a/skills/clawsec-scanner/SKILL.md +++ b/skills/clawsec-scanner/SKILL.md @@ -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: diff --git a/skills/clawsec-scanner/skill.json b/skills/clawsec-scanner/skill.json index bb58a73..8616a1b 100644 --- a/skills/clawsec-scanner/skill.json +++ b/skills/clawsec-scanner/skill.json @@ -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" } ] }, diff --git a/skills/clawtributor/CHANGELOG.md b/skills/clawtributor/CHANGELOG.md index 87a2664..cfec233 100644 --- a/skills/clawtributor/CHANGELOG.md +++ b/skills/clawtributor/CHANGELOG.md @@ -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/), diff --git a/skills/clawtributor/SKILL.md b/skills/clawtributor/SKILL.md index 658aaa1..9e74302 100644 --- a/skills/clawtributor/SKILL.md +++ b/skills/clawtributor/SKILL.md @@ -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 diff --git a/skills/clawtributor/skill.json b/skills/clawtributor/skill.json index a189bc7..32b59c0 100644 --- a/skills/clawtributor/skill.json +++ b/skills/clawtributor/skill.json @@ -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", diff --git a/skills/hermes-attestation-guardian/CHANGELOG.md b/skills/hermes-attestation-guardian/CHANGELOG.md index e2602a1..5ee7f32 100644 --- a/skills/hermes-attestation-guardian/CHANGELOG.md +++ b/skills/hermes-attestation-guardian/CHANGELOG.md @@ -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. diff --git a/skills/hermes-attestation-guardian/SKILL.md b/skills/hermes-attestation-guardian/SKILL.md index 002f640..53bee5d 100644 --- a/skills/hermes-attestation-guardian/SKILL.md +++ b/skills/hermes-attestation-guardian/SKILL.md @@ -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. diff --git a/skills/hermes-attestation-guardian/skill.json b/skills/hermes-attestation-guardian/skill.json index 0ef3ee7..6472586 100644 --- a/skills/hermes-attestation-guardian/skill.json +++ b/skills/hermes-attestation-guardian/skill.json @@ -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" } ] }, diff --git a/skills/hermes-traffic-guardian/CHANGELOG.md b/skills/hermes-traffic-guardian/CHANGELOG.md index 9eded47..776829a 100644 --- a/skills/hermes-traffic-guardian/CHANGELOG.md +++ b/skills/hermes-traffic-guardian/CHANGELOG.md @@ -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. diff --git a/skills/hermes-traffic-guardian/SKILL.md b/skills/hermes-traffic-guardian/SKILL.md index 23fd4ef..a7ae340 100644 --- a/skills/hermes-traffic-guardian/SKILL.md +++ b/skills/hermes-traffic-guardian/SKILL.md @@ -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: diff --git a/skills/hermes-traffic-guardian/skill.json b/skills/hermes-traffic-guardian/skill.json index 00467c9..3a6308b 100644 --- a/skills/hermes-traffic-guardian/skill.json +++ b/skills/hermes-traffic-guardian/skill.json @@ -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" } ] }, diff --git a/skills/nanoclaw-traffic-guardian/CHANGELOG.md b/skills/nanoclaw-traffic-guardian/CHANGELOG.md index 992cc15..3987cc5 100644 --- a/skills/nanoclaw-traffic-guardian/CHANGELOG.md +++ b/skills/nanoclaw-traffic-guardian/CHANGELOG.md @@ -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. diff --git a/skills/nanoclaw-traffic-guardian/SKILL.md b/skills/nanoclaw-traffic-guardian/SKILL.md index 92bdd62..777f49b 100644 --- a/skills/nanoclaw-traffic-guardian/SKILL.md +++ b/skills/nanoclaw-traffic-guardian/SKILL.md @@ -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: diff --git a/skills/nanoclaw-traffic-guardian/skill.json b/skills/nanoclaw-traffic-guardian/skill.json index b0c1da4..f513ab5 100644 --- a/skills/nanoclaw-traffic-guardian/skill.json +++ b/skills/nanoclaw-traffic-guardian/skill.json @@ -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" } ] }, diff --git a/skills/openclaw-audit-watchdog/CHANGELOG.md b/skills/openclaw-audit-watchdog/CHANGELOG.md index b71d889..1112dbe 100644 --- a/skills/openclaw-audit-watchdog/CHANGELOG.md +++ b/skills/openclaw-audit-watchdog/CHANGELOG.md @@ -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/), diff --git a/skills/openclaw-audit-watchdog/SKILL.md b/skills/openclaw-audit-watchdog/SKILL.md index 5ba6065..a245c68 100644 --- a/skills/openclaw-audit-watchdog/SKILL.md +++ b/skills/openclaw-audit-watchdog/SKILL.md @@ -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: diff --git a/skills/openclaw-audit-watchdog/skill.json b/skills/openclaw-audit-watchdog/skill.json index adcce37..a4b5a5a 100644 --- a/skills/openclaw-audit-watchdog/skill.json +++ b/skills/openclaw-audit-watchdog/skill.json @@ -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", diff --git a/skills/openclaw-traffic-guardian/CHANGELOG.md b/skills/openclaw-traffic-guardian/CHANGELOG.md index b117a4b..fcf693d 100644 --- a/skills/openclaw-traffic-guardian/CHANGELOG.md +++ b/skills/openclaw-traffic-guardian/CHANGELOG.md @@ -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. diff --git a/skills/openclaw-traffic-guardian/SKILL.md b/skills/openclaw-traffic-guardian/SKILL.md index 0d3ece1..5601c8f 100644 --- a/skills/openclaw-traffic-guardian/SKILL.md +++ b/skills/openclaw-traffic-guardian/SKILL.md @@ -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: diff --git a/skills/openclaw-traffic-guardian/skill.json b/skills/openclaw-traffic-guardian/skill.json index 050fe53..767164a 100644 --- a/skills/openclaw-traffic-guardian/skill.json +++ b/skills/openclaw-traffic-guardian/skill.json @@ -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" } ] }, diff --git a/skills/picoclaw-security-guardian/CHANGELOG.md b/skills/picoclaw-security-guardian/CHANGELOG.md index a15b7aa..e1c2d18 100644 --- a/skills/picoclaw-security-guardian/CHANGELOG.md +++ b/skills/picoclaw-security-guardian/CHANGELOG.md @@ -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 diff --git a/skills/picoclaw-security-guardian/SKILL.md b/skills/picoclaw-security-guardian/SKILL.md index 7088d1e..ede2dc1 100644 --- a/skills/picoclaw-security-guardian/SKILL.md +++ b/skills/picoclaw-security-guardian/SKILL.md @@ -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: diff --git a/skills/picoclaw-security-guardian/skill.json b/skills/picoclaw-security-guardian/skill.json index d692cf9..a8183c6 100644 --- a/skills/picoclaw-security-guardian/skill.json +++ b/skills/picoclaw-security-guardian/skill.json @@ -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": [ diff --git a/skills/picoclaw-self-pen-testing/CHANGELOG.md b/skills/picoclaw-self-pen-testing/CHANGELOG.md index dae4324..99fae0b 100644 --- a/skills/picoclaw-self-pen-testing/CHANGELOG.md +++ b/skills/picoclaw-self-pen-testing/CHANGELOG.md @@ -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 diff --git a/skills/picoclaw-self-pen-testing/SKILL.md b/skills/picoclaw-self-pen-testing/SKILL.md index 55c2c3c..abed8ad 100644 --- a/skills/picoclaw-self-pen-testing/SKILL.md +++ b/skills/picoclaw-self-pen-testing/SKILL.md @@ -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. diff --git a/skills/picoclaw-self-pen-testing/skill.json b/skills/picoclaw-self-pen-testing/skill.json index bfb5405..00762c8 100644 --- a/skills/picoclaw-self-pen-testing/skill.json +++ b/skills/picoclaw-self-pen-testing/skill.json @@ -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" } ] }, diff --git a/skills/picoclaw-traffic-guardian/CHANGELOG.md b/skills/picoclaw-traffic-guardian/CHANGELOG.md index ae7a651..409ec03 100644 --- a/skills/picoclaw-traffic-guardian/CHANGELOG.md +++ b/skills/picoclaw-traffic-guardian/CHANGELOG.md @@ -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. diff --git a/skills/picoclaw-traffic-guardian/SKILL.md b/skills/picoclaw-traffic-guardian/SKILL.md index e43bac8..ff467b1 100644 --- a/skills/picoclaw-traffic-guardian/SKILL.md +++ b/skills/picoclaw-traffic-guardian/SKILL.md @@ -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: diff --git a/skills/picoclaw-traffic-guardian/skill.json b/skills/picoclaw-traffic-guardian/skill.json index a602124..2460729 100644 --- a/skills/picoclaw-traffic-guardian/skill.json +++ b/skills/picoclaw-traffic-guardian/skill.json @@ -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" } ] }, diff --git a/skills/soul-guardian/CHANGELOG.md b/skills/soul-guardian/CHANGELOG.md index 62cb9e0..18426ca 100644 --- a/skills/soul-guardian/CHANGELOG.md +++ b/skills/soul-guardian/CHANGELOG.md @@ -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/), diff --git a/skills/soul-guardian/SKILL.md b/skills/soul-guardian/SKILL.md index e12de2b..5478a63 100644 --- a/skills/soul-guardian/SKILL.md +++ b/skills/soul-guardian/SKILL.md @@ -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 diff --git a/skills/soul-guardian/skill.json b/skills/soul-guardian/skill.json index ebd6e80..34f0350 100644 --- a/skills/soul-guardian/skill.json +++ b/skills/soul-guardian/skill.json @@ -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", diff --git a/types.ts b/types.ts index 2f3411e..174150b 100644 --- a/types.ts +++ b/types.ts @@ -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; diff --git a/utils/package_skill.py b/utils/package_skill.py index 0ffce34..661b26f 100644 --- a/utils/package_skill.py +++ b/utils/package_skill.py @@ -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)) diff --git a/utils/skillPlatforms.ts b/utils/skillPlatforms.ts new file mode 100644 index 0000000..f02e4b3 --- /dev/null +++ b/utils/skillPlatforms.ts @@ -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; + return 'emoji' in maybe || 'category' in maybe || 'triggers' in maybe; +}; + +export const getRecommendedSkillPlatforms = (skill: SkillJson): AdvisoryPlatformSlug[] => { + const platforms = new Set(); + + 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 {}; +};