fix(release): exclude tests from skill payloads (#230)

* fix(release): exclude tests from skill payloads

* fix(release): normalize test path filtering

* fix(release): prefer GitHub artifacts for non-OpenClaw installs

* fix(release): keep legacy ClawHub publishing

* fix(release): address skill packaging review feedback

* chore(skills): bump release versions

* feat(skills): surface recommended platforms

* docs(skills): add signed release verification

* fix(skills): normalize PR version bumps

---------

Co-authored-by: David Abutbul <David.a@prompt.security>
This commit is contained in:
David Abutbul
2026-05-14 14:38:58 +03:00
committed by GitHub
parent 0e503c3d5a
commit 1e48a955cc
54 changed files with 1645 additions and 269 deletions
+12 -3
View File
@@ -184,15 +184,23 @@ jobs:
# Build skill entry for index # Build skill entry for index
SKILL_DATA=$(jq -c --arg tag "$TAG" ' SKILL_DATA=$(jq -c --arg tag "$TAG" '
. as $skill |
def object_or_empty($value): def object_or_empty($value):
if ($value | type) == "object" then $value else {} end; if ($value | type) == "object" then $value else {} end;
def object_field($name): def object_field($name):
object_or_empty(.[$name]?); object_or_empty($skill[$name]?);
def platform_meta: def platform_meta:
(.platform as $platform ($skill.platform as $platform
| if ($platform | type) == "string" then object_or_empty(.[$platform]?) | if ($platform | type) == "string" then object_or_empty($skill[$platform]?)
else {} else {}
end); 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, id: .name,
name: .name, name: .name,
@@ -200,6 +208,7 @@ jobs:
description: .description, description: .description,
emoji: (platform_meta.emoji // object_field("openclaw").emoji // object_field("hermes").emoji // object_field("nanoclaw").emoji // object_field("picoclaw").emoji // "📦"), 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"), 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, trust: .trust.level,
tag: $tag tag: $tag
} }
+182 -50
View File
@@ -375,6 +375,26 @@ jobs:
failures=0 failures=0
mkdir -p dist/dry-run 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 while IFS= read -r skill_dir; do
json_path="${skill_dir}/skill.json" json_path="${skill_dir}/skill.json"
md_path="${skill_dir}/SKILL.md" md_path="${skill_dir}/SKILL.md"
@@ -477,8 +497,17 @@ jobs:
temp_sbom_file="$(mktemp)" temp_sbom_file="$(mktemp)"
jq -r '.sbom.files[].path' "${json_path}" > "${temp_sbom_file}" jq -r '.sbom.files[].path' "${json_path}" > "${temp_sbom_file}"
while IFS= read -r file; do while IFS= read -r raw_file; do
[ -z "${file}" ] && continue [ -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}" full_path="${skill_dir}/${file}"
if [ -f "${full_path}" ]; then if [ -f "${full_path}" ]; then
mkdir -p "${inner_dir}/$(dirname "${file}")" mkdir -p "${inner_dir}/$(dirname "${file}")"
@@ -504,6 +533,11 @@ jobs:
# --- Create zip preserving directory structure --- # --- Create zip preserving directory structure ---
zip_name="${skill_name}-v${version}.zip" zip_name="${skill_name}-v${version}.zip"
(cd "${staging_dir}" && zip -qr "${OLDPWD}/${out_assets}/${zip_name}" .) (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 --- # --- Clean up test artifacts from source directory ---
if [ -d "${skill_dir}/advisories" ]; then if [ -d "${skill_dir}/advisories" ]; then
@@ -515,8 +549,14 @@ jobs:
# --- Generate checksums.json via jq --- # --- Generate checksums.json via jq ---
files_json="{}" files_json="{}"
while IFS= read -r file; do while IFS= read -r raw_file; do
[ -z "${file}" ] && continue [ -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}" full_path="${skill_dir}/${file}"
if [ -f "${full_path}" ]; then if [ -f "${full_path}" ]; then
sha256="$(sha256sum "${full_path}" | awk '{print $1}')" sha256="$(sha256sum "${full_path}" | awk '{print $1}')"
@@ -615,6 +655,8 @@ jobs:
version: ${{ steps.parse.outputs.version }} version: ${{ steps.parse.outputs.version }}
skill_path: ${{ steps.parse.outputs.skill_path }} skill_path: ${{ steps.parse.outputs.skill_path }}
publishable: ${{ steps.publishable.outputs.publishable }} publishable: ${{ steps.publishable.outputs.publishable }}
openclaw_skill: ${{ steps.publishable.outputs.openclaw_skill }}
publish_clawhub: ${{ steps.publishable.outputs.publish_clawhub }}
steps: steps:
- name: Parse tag - name: Parse tag
id: parse id: parse
@@ -686,22 +728,35 @@ jobs:
echo "SKILL.md version validated: $MD_VERSION" echo "SKILL.md version validated: $MD_VERSION"
fi fi
else else
echo "No SKILL.md found, skipping frontmatter validation" echo "::error::Missing required SKILL.md: $SKILL_PATH/SKILL.md"
exit 1
fi fi
- name: Detect publishability - name: Detect publishability and install defaults
id: publishable id: publishable
run: | run: |
SKILL_PATH="${{ steps.parse.outputs.skill_path }}" 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 PUBLISHABLE=true
if [ "$INTERNAL" = "true" ]; then if [ "$INTERNAL" = "true" ]; then
PUBLISHABLE=false 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 fi
echo "internal=${INTERNAL}" >> $GITHUB_OUTPUT echo "internal=${INTERNAL}" >> $GITHUB_OUTPUT
echo "openclaw_skill=${OPENCLAW_SKILL}" >> $GITHUB_OUTPUT
echo "publish_clawhub=${PUBLISH_CLAWHUB}" >> $GITHUB_OUTPUT
echo "publishable=${PUBLISHABLE}" >> $GITHUB_OUTPUT echo "publishable=${PUBLISHABLE}" >> $GITHUB_OUTPUT
- name: Setup Node - name: Setup Node
@@ -788,6 +843,26 @@ jobs:
mkdir -p release-assets 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 --- # --- Stage SBOM files preserving directory structure ---
STAGING_DIR="$(mktemp -d)" STAGING_DIR="$(mktemp -d)"
INNER_DIR="$STAGING_DIR/$SKILL_NAME" INNER_DIR="$STAGING_DIR/$SKILL_NAME"
@@ -795,8 +870,16 @@ jobs:
TEMPFILE="$(mktemp)" TEMPFILE="$(mktemp)"
jq -r '.sbom.files[].path' "$SKILL_PATH/skill.json" > "$TEMPFILE" jq -r '.sbom.files[].path' "$SKILL_PATH/skill.json" > "$TEMPFILE"
while IFS= read -r file; do while IFS= read -r raw_file; do
[ -z "$file" ] && continue [ -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" FULL_PATH="$SKILL_PATH/$file"
if [ -f "$FULL_PATH" ]; then if [ -f "$FULL_PATH" ]; then
mkdir -p "$INNER_DIR/$(dirname "$file")" mkdir -p "$INNER_DIR/$(dirname "$file")"
@@ -812,11 +895,22 @@ jobs:
# --- Create zip preserving directory structure --- # --- Create zip preserving directory structure ---
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip" ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
(cd "$STAGING_DIR" && zip -qr "$OLDPWD/release-assets/$ZIP_NAME" .) (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 --- # --- Generate checksums.json via jq ---
FILES_JSON="{}" FILES_JSON="{}"
while IFS= read -r file; do while IFS= read -r raw_file; do
[ -z "$file" ] && continue [ -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" FULL_PATH="$SKILL_PATH/$file"
if [ -f "$FULL_PATH" ]; then if [ -f "$FULL_PATH" ]; then
SHA256=$(sha256sum "$FULL_PATH" | awk '{print $1}') SHA256=$(sha256sum "$FULL_PATH" | awk '{print $1}')
@@ -946,6 +1040,71 @@ jobs:
echo "EOF" echo "EOF"
} >> $GITHUB_OUTPUT } >> $GITHUB_OUTPUT
- name: Build quick install instructions
id: install
run: |
set -euo pipefail
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
VERSION="${{ steps.parse.outputs.version }}"
REPO="${{ github.repository }}"
TAG="${{ github.ref_name }}"
{
echo "quick_install<<INSTALL_EOF"
if [ "${{ steps.publishable.outputs.publish_clawhub }}" = "true" ] && [ "${{ steps.publishable.outputs.openclaw_skill }}" = "true" ]; then
cat <<EOF
### Quick Install
**Via ClawHub (recommended):**
\`\`\`bash
npx clawhub@latest install ${SKILL_NAME}
\`\`\`
**If you already have \`clawsec-suite\` installed:**
Ask your agent to pull \`${SKILL_NAME}\` from the ClawSec catalog and it will handle setup and verification automatically.
EOF
else
cat <<EOF
### Quick Install
**GitHub release artifact (recommended):**
Ask your agent to read the published skill instructions from this GitHub release and follow them:
https://github.com/${REPO}/releases/download/${TAG}/SKILL.md
Or download them locally:
\`\`\`bash
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/SKILL.md
\`\`\`
EOF
fi
cat <<EOF
**Manual download with verification:**
\`\`\`bash
# 1. Download the release archive, checksums, and signing material
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/${SKILL_NAME}-v${VERSION}.zip
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/checksums.json
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/checksums.sig
curl -sLO https://github.com/${REPO}/releases/download/${TAG}/signing-public.pem
# 2. Verify the checksums manifest signature (Ed25519)
openssl base64 -d -A -in checksums.sig -out checksums.sig.bin
openssl pkeyutl -verify -rawin -pubin -inkey signing-public.pem -sigfile checksums.sig.bin -in checksums.json
# 3. Verify archive checksum from the signed manifest
echo "\$(jq -r '.archive.sha256' checksums.json) ${SKILL_NAME}-v${VERSION}.zip" | sha256sum -c
# 4. Extract (creates ${SKILL_NAME}/ directory)
unzip ${SKILL_NAME}-v${VERSION}.zip
\`\`\`
EOF
echo "INSTALL_EOF"
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with: with:
@@ -957,34 +1116,7 @@ jobs:
${{ steps.changelog.outputs.changelog }} ${{ steps.changelog.outputs.changelog }}
### Quick Install ${{ steps.install.outputs.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
```
### Verification ### Verification
@@ -1061,28 +1193,28 @@ jobs:
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }} CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
steps: steps:
- name: Check if publishable - name: Check if publishable
if: needs.release-tag.outputs.publishable != 'true' if: needs.release-tag.outputs.publish_clawhub != 'true'
run: | run: |
echo "Skill marked as internal, skipping ClawHub publish" echo "Skill is not eligible for ClawHub publishing; skipping"
exit 0 exit 0
- name: Checkout - name: Checkout
if: needs.release-tag.outputs.publishable == 'true' if: needs.release-tag.outputs.publish_clawhub == 'true'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node - 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 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with: with:
node-version: 20 node-version: 20
- name: Install clawhub CLI - 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} run: npm install -g clawhub@${CLAWHUB_CLI_VERSION}
- name: Patch clawhub publish payload workaround - name: Patch clawhub publish payload workaround
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms. # 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: | run: |
node <<'NODE' node <<'NODE'
const { execSync } = require("node:child_process"); const { execSync } = require("node:child_process");
@@ -1125,7 +1257,7 @@ jobs:
NODE NODE
- name: Login to ClawHub - 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: | run: |
set -euo pipefail set -euo pipefail
SITE=${CLAWHUB_SITE:-https://clawhub.ai} SITE=${CLAWHUB_SITE:-https://clawhub.ai}
@@ -1136,7 +1268,7 @@ jobs:
clawhub login --token "$CLAWHUB_TOKEN" --site "$SITE" --no-input clawhub login --token "$CLAWHUB_TOKEN" --site "$SITE" --no-input
- name: Guard duplicate ClawHub version - 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: | run: |
set -euo pipefail set -euo pipefail
SITE=${CLAWHUB_SITE:-https://clawhub.ai} SITE=${CLAWHUB_SITE:-https://clawhub.ai}
@@ -1166,7 +1298,7 @@ jobs:
fi fi
- name: Publish to ClawHub - 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: | run: |
set -euo pipefail set -euo pipefail
SITE=${CLAWHUB_SITE:-https://clawhub.ai} SITE=${CLAWHUB_SITE:-https://clawhub.ai}
@@ -1240,7 +1372,7 @@ jobs:
id: publishable id: publishable
run: | run: |
SKILL_PATH="${{ steps.parse.outputs.skill_path }}" 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 if [ "$INTERNAL" = "true" ]; then
echo "::error::Skill is marked internal and cannot be published to ClawHub" echo "::error::Skill is marked internal and cannot be published to ClawHub"
+21 -1
View File
@@ -2,16 +2,19 @@ import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ArrowRight } from 'lucide-react'; import { ArrowRight } from 'lucide-react';
import type { SkillMetadata } from '../types'; import type { SkillMetadata } from '../types';
import { getPlatformDescriptor } from '../utils/advisoryPlatforms';
interface SkillCardProps { interface SkillCardProps {
skill: SkillMetadata; skill: SkillMetadata;
} }
export const SkillCard: React.FC<SkillCardProps> = ({ skill }) => { export const SkillCard: React.FC<SkillCardProps> = ({ skill }) => {
const platforms = Array.isArray(skill.platforms) ? skill.platforms.slice(0, 4) : [];
return ( return (
<Link <Link
to={`/skills/${skill.id}`} to={`/skills/${skill.id}`}
className="group block bg-clawd-800 border border-clawd-700 rounded-xl p-5 hover:border-clawd-accent/30 hover:bg-clawd-800/80 transition-all duration-200" className="group block bg-clawd-800 border border-clawd-700 rounded-lg p-5 hover:border-clawd-accent/30 hover:bg-clawd-800/80 transition-all duration-200"
> >
<div className="flex items-center gap-3 mb-3"> <div className="flex items-center gap-3 mb-3">
<span className="text-2xl">{skill.emoji || '📦'}</span> <span className="text-2xl">{skill.emoji || '📦'}</span>
@@ -27,6 +30,23 @@ export const SkillCard: React.FC<SkillCardProps> = ({ skill }) => {
{skill.description} {skill.description}
</p> </p>
{platforms.length > 0 && (
<div className="flex flex-wrap gap-1.5 mb-4" aria-label="Recommended platforms">
{platforms.map((platform) => {
const descriptor = getPlatformDescriptor(platform);
return (
<span
key={platform}
className={`text-[11px] leading-none px-2 py-1 rounded-md ${descriptor.classes}`}
>
{descriptor.label}
</span>
);
})}
</div>
)}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{/* Category badge - hidden for now, uncomment when we have multiple categories {/* Category badge - hidden for now, uncomment when we have multiple categories
<span className="text-xs text-gray-500 bg-clawd-700 px-2 py-1 rounded"> <span className="text-xs text-gray-500 bg-clawd-700 px-2 py-1 rounded">
+44 -31
View File
@@ -4,41 +4,19 @@ import { ArrowLeft, Copy, Check, Download, ExternalLink, FileText, Shield } from
import Markdown from 'react-markdown'; import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import { Footer } from '../components/Footer'; 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 { defaultMarkdownComponents } from '../utils/markdownComponents';
import { stripFrontmatter } from '../utils/markdownHelpers.mjs'; 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 isProbablyHtmlDocument = (text: string): boolean => {
const start = text.trimStart().slice(0, 200).toLowerCase(); const start = text.trimStart().slice(0, 200).toLowerCase();
return start.startsWith('<!doctype html') || start.startsWith('<html'); return start.startsWith('<!doctype html') || start.startsWith('<html');
}; };
const isPlatformMetadataObject = (value: unknown): value is SkillPlatformMetadata => {
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
const maybe = value as Record<string, unknown>;
return 'emoji' in maybe || 'category' in maybe || 'triggers' in maybe;
};
const resolvePlatformMetadata = (skill: SkillJson): SkillPlatformMetadata => {
const platform = skill.platform;
if (
typeof platform === 'string' &&
(PLATFORM_METADATA_KEYS as readonly string[]).includes(platform)
) {
const platformBlock = skill[platform as (typeof PLATFORM_METADATA_KEYS)[number]];
if (isPlatformMetadataObject(platformBlock)) return platformBlock;
}
for (const key of PLATFORM_METADATA_KEYS) {
const fallbackBlock = skill[key];
if (isPlatformMetadataObject(fallbackBlock)) return fallbackBlock;
}
return {};
};
export const SkillDetail: React.FC = () => { export const SkillDetail: React.FC = () => {
const { skillId } = useParams<{ skillId: string }>(); const { skillId } = useParams<{ skillId: string }>();
const [skillData, setSkillData] = useState<SkillJson | null>(null); const [skillData, setSkillData] = useState<SkillJson | null>(null);
@@ -147,10 +125,29 @@ export const SkillDetail: React.FC = () => {
setTimeout(() => setCopied(null), 2000); setTimeout(() => setCopied(null), 2000);
}; };
const installCommand = skillData const releaseTag = skillData ? `${skillData.name}-v${skillData.version}` : '';
? `npx clawhub@latest install ${skillData.name}` 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(() => { const releasePageUrl = useMemo(() => {
if (!skillData) return ''; if (!skillData) return '';
@@ -160,7 +157,7 @@ export const SkillDetail: React.FC = () => {
const [owner, repo] = url.pathname.split('/').filter(Boolean); const [owner, repo] = url.pathname.split('/').filter(Boolean);
if (owner && repo) { if (owner && repo) {
const repoBase = `${url.origin}/${owner}/${repo.replace(/\\.git$/, '')}`; const repoBase = `${url.origin}/${owner}/${repo.replace(/\\.git$/, '')}`;
return `${repoBase}/releases/tag/${skillData.name}-v${skillData.version}`; return `${repoBase}/releases/tag/${releaseTag}`;
} }
} }
} catch { } catch {
@@ -168,10 +165,10 @@ export const SkillDetail: React.FC = () => {
} }
return skillData.homepage; return skillData.homepage;
}, [skillData]); }, [releaseTag, skillData]);
const platformMetadata = useMemo( const platformMetadata = useMemo(
() => (skillData ? resolvePlatformMetadata(skillData) : null), () => (skillData ? resolveSkillPlatformMetadata(skillData) : null),
[skillData] [skillData]
); );
@@ -221,6 +218,18 @@ export const SkillDetail: React.FC = () => {
<h1 className="text-3xl font-bold text-white mb-1">{skillData.name}</h1> <h1 className="text-3xl font-bold text-white mb-1">{skillData.name}</h1>
<div className="flex items-center gap-3 text-sm"> <div className="flex items-center gap-3 text-sm">
<span className="text-gray-500 font-mono">v{skillData.version}</span> <span className="text-gray-500 font-mono">v{skillData.version}</span>
{recommendedPlatforms.slice(0, 4).map((platform) => {
const descriptor = getPlatformDescriptor(platform);
return (
<span
key={platform}
className={`text-xs px-2 py-0.5 rounded-md ${descriptor.classes}`}
>
{descriptor.label}
</span>
);
})}
{/* Category badge - hidden for now, uncomment when we have multiple categories {/* Category badge - hidden for now, uncomment when we have multiple categories
<span className="text-gray-500 bg-clawd-800 px-2 py-0.5 rounded"> <span className="text-gray-500 bg-clawd-800 px-2 py-0.5 rounded">
{platformMetadata?.category || 'utility'} {platformMetadata?.category || 'utility'}
@@ -254,6 +263,10 @@ export const SkillDetail: React.FC = () => {
<Download size={20} /> <Download size={20} />
Quick Install Quick Install
</h2> </h2>
<div>
<p className="text-sm font-medium text-white">{installLabel}</p>
<p className="text-sm text-gray-400">{installHelp}</p>
</div>
<div className="bg-clawd-800 rounded-lg p-3 sm:p-4 flex items-center justify-between gap-2 sm:gap-4"> <div className="bg-clawd-800 rounded-lg p-3 sm:p-4 flex items-center justify-between gap-2 sm:gap-4">
<code className="text-gray-200 font-mono text-xs sm:text-sm overflow-x-auto break-all min-w-0 flex-1"> <code className="text-gray-200 font-mono text-xs sm:text-sm overflow-x-auto break-all min-w-0 flex-1">
{installCommand} {installCommand}
@@ -71,3 +71,14 @@ if [[ "$CANONICAL_FPR" != "$DOC_EXPECTED_FPR" ]]; then
fi fi
echo "All signing key references are consistent: $CANONICAL_FPR" 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."
+12 -3
View File
@@ -169,15 +169,23 @@ EOF
# Build skill entry for index # Build skill entry for index
SKILL_DATA=$(jq -c --arg tag "$TAG" ' SKILL_DATA=$(jq -c --arg tag "$TAG" '
. as $skill |
def object_or_empty($value): def object_or_empty($value):
if ($value | type) == "object" then $value else {} end; if ($value | type) == "object" then $value else {} end;
def object_field($name): def object_field($name):
object_or_empty(.[$name]?); object_or_empty($skill[$name]?);
def platform_meta: def platform_meta:
(.platform as $platform ($skill.platform as $platform
| if ($platform | type) == "string" then object_or_empty(.[$platform]?) | if ($platform | type) == "string" then object_or_empty($skill[$platform]?)
else {} else {}
end); 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, id: .name,
name: .name, name: .name,
@@ -185,6 +193,7 @@ EOF
description: .description, description: .description,
emoji: (platform_meta.emoji // object_field("openclaw").emoji // object_field("hermes").emoji // object_field("nanoclaw").emoji // object_field("picoclaw").emoji // "📦"), 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"), 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 tag: $tag
} }
' "$SKILL_JSON") ' "$SKILL_JSON")
+5
View File
@@ -1,5 +1,10 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: claw-release 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. description: Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
metadata: {"openclaw":{"emoji":"🚀","category":"utility","internal":true}} 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 - 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 - 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 ## Quick Reference
| Release Type | Command | Tag Format | | Release Type | Command | Tag Format |
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "claw-release", "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.", "description": "Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -1,5 +1,13 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: clawsec-clawhub-checker 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. description: ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
clawdis: 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 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 ## Usage
Run the enhanced installer directly from this skill: Run the enhanced installer directly from this skill:
+1 -11
View File
@@ -1,6 +1,6 @@
{ {
"name": "clawsec-clawhub-checker", "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.", "description": "ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.",
"author": "abutbul", "author": "abutbul",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -52,16 +52,6 @@
"path": "CHANGELOG.md", "path": "CHANGELOG.md",
"required": true, "required": true,
"description": "Version history and release notes" "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"
} }
] ]
}, },
+5
View File
@@ -1,5 +1,10 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: clawsec-feed 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. 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 homepage: https://clawsec.prompt.security
metadata: {"openclaw":{"emoji":"📡","category":"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 ## Deploy ClawSec Feed
Installation steps: Installation steps:
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "clawsec-feed", "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.", "description": "Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
+5
View File
@@ -1,5 +1,10 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: clawsec-nanoclaw 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 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 - Alerts to supply chain attacks in dependencies
- Provides actionable remediation steps - Provides actionable remediation steps
- Zero false positives (curated feed only) - Zero false positives (curated feed only)
## Release Artifact Verification
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
```bash
set -euo pipefail
SKILL_NAME="clawsec-nanoclaw"
VERSION="0.0.5"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"
curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"
curl -fsSL "$BASE/SKILL.md" -o "$TMP_DIR/SKILL.md"
curl -fsSL "$BASE/skill.json" -o "$TMP_DIR/skill.json"
ACTUAL_PUBKEY_SHA256="$(openssl pkey -pubin -in "$TMP_DIR/signing-public.pem" -outform DER | shasum -a 256 | awk '{print $1}')"
if [ "$ACTUAL_PUBKEY_SHA256" != "$RELEASE_PUBKEY_SHA256" ]; then
echo "ERROR: signing-public.pem fingerprint mismatch" >&2
exit 1
fi
openssl base64 -d -A -in "$TMP_DIR/checksums.sig" -out "$TMP_DIR/checksums.sig.bin"
openssl pkeyutl -verify -rawin -pubin \
-inkey "$TMP_DIR/signing-public.pem" \
-sigfile "$TMP_DIR/checksums.sig.bin" \
-in "$TMP_DIR/checksums.json" >/dev/null
hash_file() {
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
else
sha256sum "$1" | awk '{print $1}'
fi
}
verify_manifest_file() {
asset="$1"
path="$2"
expected="$(jq -r --arg asset "$asset" '.files[$asset].sha256 // empty' "$TMP_DIR/checksums.json")"
if [ -z "$expected" ]; then
echo "ERROR: checksums.json missing $asset" >&2
exit 1
fi
actual="$(hash_file "$path")"
if [ "$actual" != "$expected" ]; then
echo "ERROR: checksum mismatch for $asset" >&2
exit 1
fi
}
expected_archive="$(jq -r '.archive.sha256 // empty' "$TMP_DIR/checksums.json")"
if [ -z "$expected_archive" ]; then
echo "ERROR: checksums.json missing archive.sha256" >&2
exit 1
fi
actual_archive="$(hash_file "$TMP_DIR/$ZIP_NAME")"
if [ "$actual_archive" != "$expected_archive" ]; then
echo "ERROR: archive checksum mismatch" >&2
exit 1
fi
verify_manifest_file "SKILL.md" "$TMP_DIR/SKILL.md"
verify_manifest_file "skill.json" "$TMP_DIR/skill.json"
echo "Signed release manifest, archive, SKILL.md, and skill.json verified."
```
Only install or extract the archive after this verification succeeds.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "clawsec-nanoclaw", "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", "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", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
+5
View File
@@ -1,5 +1,10 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: clawsec-scanner 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. 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 homepage: https://clawsec.prompt.security
clawdis: clawdis:
+1 -21
View File
@@ -1,6 +1,6 @@
{ {
"name": "clawsec-scanner", "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.", "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", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -93,26 +93,6 @@
"path": "hooks/clawsec-scanner-hook/handler.ts", "path": "hooks/clawsec-scanner-hook/handler.ts",
"required": false, "required": false,
"description": "OpenClaw hook handler for periodic vulnerability scanning" "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"
} }
] ]
}, },
+5
View File
@@ -1,5 +1,10 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: clawtributor name: clawtributor
version: 0.0.5 version: 0.0.6
description: Community incident reporting for AI agents. Contribute to collective security by reporting threats. description: Community incident reporting for AI agents. Contribute to collective security by reporting threats.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
metadata: {"openclaw":{"emoji":"🤝","category":"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 ## What Clawtributor Does
### Community-Driven Security Reporting ### Community-Driven Security Reporting
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "clawtributor", "name": "clawtributor",
"version": "0.0.5", "version": "0.0.6",
"description": "Community incident reporting for AI agents. Contribute to collective security by reporting threats.", "description": "Community incident reporting for AI agents. Contribute to collective security by reporting threats.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -1,5 +1,13 @@
# Changelog # 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 ## [0.1.0] - 2026-04-21
- Added mandatory release verification gate guidance before install: `checksums.json`, `checksums.sig`, and pinned signing public-key fingerprint. - Added mandatory release verification gate guidance before install: `checksums.json`, `checksums.sig`, and pinned signing public-key fingerprint.
+81 -33
View File
@@ -1,6 +1,6 @@
--- ---
name: hermes-attestation-guardian 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. description: Hermes-only runtime security attestation and drift detection skill for operator-managed Hermes infrastructure.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
hermes: hermes:
@@ -15,42 +15,90 @@ IMPORTANT SCOPE:
- This skill targets Hermes infrastructure only (CLI/Gateway/profile-managed deployments). - This skill targets Hermes infrastructure only (CLI/Gateway/profile-managed deployments).
- This skill is not an OpenClaw runtime hook package. - 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 ## Goal
Generate deterministic Hermes posture attestations, verify them with fail-closed integrity checks, and compare baseline drift using stable severity mapping. 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 ## 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. When installing from community sources, configure Hermes guard to use signature-aware trust (trusted signer fingerprint allowlist) rather than source-name-only trust. Unknown signer fingerprints should stay on community policy, and invalid signatures must remain blocked.
+1 -41
View File
@@ -1,6 +1,6 @@
{ {
"name": "hermes-attestation-guardian", "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.", "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", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -80,46 +80,6 @@
"path": "scripts/setup_advisory_check_cron.mjs", "path": "scripts/setup_advisory_check_cron.mjs",
"required": true, "required": true,
"description": "Optional recurring schedule setup for Hermes guarded advisory checks" "description": "Optional recurring schedule setup for Hermes guarded advisory checks"
},
{
"path": "test/attestation_schema.test.mjs",
"required": false,
"description": "Schema and determinism tests"
},
{
"path": "test/attestation_diff.test.mjs",
"required": false,
"description": "Diff and severity mapping tests"
},
{
"path": "test/attestation_cli.test.mjs",
"required": false,
"description": "Generator/verifier CLI behavior tests"
},
{
"path": "test/setup_attestation_cron.test.mjs",
"required": false,
"description": "Hermes-only cron setup tests"
},
{
"path": "test/setup_advisory_check_cron.test.mjs",
"required": false,
"description": "Hermes-only guarded advisory cron setup tests"
},
{
"path": "test/feed_verification.test.mjs",
"required": false,
"description": "Advisory feed signature/checksum verification behavior tests"
},
{
"path": "test/guarded_skill_verify.test.mjs",
"required": false,
"description": "Advisory-aware guarded verification gate behavior tests"
},
{
"path": "test/hermes_attestation_sandbox_regression.sh",
"required": false,
"description": "Sandboxed end-to-end regression harness for install and verification paths"
} }
] ]
}, },
@@ -1,5 +1,13 @@
# Changelog # 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 ## [0.0.1-beta1] - 2026-05-10
- Added baseline skill metadata, frontmatter, and implementation specification. - Added baseline skill metadata, frontmatter, and implementation specification.
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: hermes-traffic-guardian 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. description: Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
author: 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. 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 ## Scope
Builders should use this skill as the Hermes landing zone for runtime traffic monitoring: Builders should use this skill as the Hermes landing zone for runtime traffic monitoring:
+1 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "hermes-traffic-guardian", "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.", "description": "Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -49,11 +49,6 @@
"path": "scripts/.gitkeep", "path": "scripts/.gitkeep",
"required": false, "required": false,
"description": "Placeholder for lifecycle, status, and attestation export scripts" "description": "Placeholder for lifecycle, status, and attestation export scripts"
},
{
"path": "test/.gitkeep",
"required": false,
"description": "Placeholder for unit and integration tests"
} }
] ]
}, },
@@ -1,5 +1,13 @@
# Changelog # 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 ## [0.0.1-beta1] - 2026-05-10
- Added baseline skill metadata, frontmatter, and implementation specification. - Added baseline skill metadata, frontmatter, and implementation specification.
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: nanoclaw-traffic-guardian 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. description: NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
author: 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. 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 ## Scope
Builders should use this skill as the NanoClaw landing zone for runtime traffic monitoring: Builders should use this skill as the NanoClaw landing zone for runtime traffic monitoring:
+1 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "nanoclaw-traffic-guardian", "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.", "description": "NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -54,11 +54,6 @@
"path": "mcp-tools/.gitkeep", "path": "mcp-tools/.gitkeep",
"required": false, "required": false,
"description": "Placeholder for container-side MCP tool definitions" "description": "Placeholder for container-side MCP tool definitions"
},
{
"path": "test/.gitkeep",
"required": false,
"description": "Placeholder for unit and integration tests"
} }
] ]
}, },
@@ -1,5 +1,10 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: openclaw-audit-watchdog 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. 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 homepage: https://clawsec.prompt.security
metadata: 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 ## Operational requirements
Required runtime: Required runtime:
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "openclaw-audit-watchdog", "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.", "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", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -1,5 +1,13 @@
# Changelog # 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 ## [0.0.1-beta1] - 2026-05-10
- Added baseline skill metadata, frontmatter, and implementation specification. - Added baseline skill metadata, frontmatter, and implementation specification.
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: openclaw-traffic-guardian 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. description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
author: 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. 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 ## Scope
Builders should use this skill as the OpenClaw landing zone for runtime traffic monitoring: Builders should use this skill as the OpenClaw landing zone for runtime traffic monitoring:
+1 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "openclaw-traffic-guardian", "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.", "description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -53,11 +53,6 @@
"path": "hooks/openclaw-traffic-guardian-hook/.gitkeep", "path": "hooks/openclaw-traffic-guardian-hook/.gitkeep",
"required": false, "required": false,
"description": "Placeholder for optional OpenClaw hook integration" "description": "Placeholder for optional OpenClaw hook integration"
},
{
"path": "test/.gitkeep",
"required": false,
"description": "Placeholder for unit and integration tests"
} }
] ]
}, },
@@ -1,5 +1,13 @@
# Changelog # 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 ## [0.0.1] - 2026-04-26
### Added ### Added
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: picoclaw-security-guardian 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. description: Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
author: prompt-security author: prompt-security
@@ -18,6 +18,86 @@ picoclaw:
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`. 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 ## Goal
Provide Picoclaw with the same support-matrix security capabilities ClawSec tracks for mature platform modules: Provide Picoclaw with the same support-matrix security capabilities ClawSec tracks for mature platform modules:
+2 -22
View File
@@ -1,6 +1,6 @@
{ {
"name": "picoclaw-security-guardian", "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.", "description": "Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -70,31 +70,11 @@
"path": "scripts/check_advisories.mjs", "path": "scripts/check_advisories.mjs",
"required": true, "required": true,
"description": "Check Picoclaw-relevant advisories from a signed/verified feed state" "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": { "picoclaw": {
"emoji": "\ud83e\udd90", "emoji": "🦐",
"category": "security", "category": "security",
"requires": { "requires": {
"bins": [ "bins": [
@@ -1,5 +1,13 @@
# Changelog # 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 ## [0.0.1] - 2026-04-26
### Added ### Added
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: picoclaw-self-pen-testing 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. description: Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
author: 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. 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 ## Scope
This skill only performs local, read-only posture-review analysis against an existing Picoclaw posture profile. This skill only performs local, read-only posture-review analysis against an existing Picoclaw posture profile.
+1 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "picoclaw-self-pen-testing", "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.", "description": "Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -45,11 +45,6 @@
"path": "scripts/self_pen_test.mjs", "path": "scripts/self_pen_test.mjs",
"required": true, "required": true,
"description": "Run posture-review checks on a profile" "description": "Run posture-review checks on a profile"
},
{
"path": "test/self_pen_test.test.mjs",
"required": false,
"description": "Finding classification tests"
} }
] ]
}, },
@@ -1,5 +1,13 @@
# Changelog # 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 ## [0.0.1-beta1] - 2026-05-10
- Added baseline skill metadata, frontmatter, and implementation specification. - Added baseline skill metadata, frontmatter, and implementation specification.
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: picoclaw-traffic-guardian 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. description: Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
author: 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. 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 ## Scope
Builders should use this skill as the Picoclaw landing zone for runtime traffic monitoring: Builders should use this skill as the Picoclaw landing zone for runtime traffic monitoring:
+1 -6
View File
@@ -1,6 +1,6 @@
{ {
"name": "picoclaw-traffic-guardian", "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.", "description": "Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -49,11 +49,6 @@
"path": "scripts/.gitkeep", "path": "scripts/.gitkeep",
"required": false, "required": false,
"description": "Placeholder for lifecycle, status, and profile export scripts" "description": "Placeholder for lifecycle, status, and profile export scripts"
},
{
"path": "test/.gitkeep",
"required": false,
"description": "Placeholder for unit and integration tests"
} }
] ]
}, },
+5
View File
@@ -1,5 +1,10 @@
# Changelog # 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. 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/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+81 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: soul-guardian 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 description: Drift detection + baseline integrity guard for agent workspace files with automatic alerting support
homepage: https://clawsec.prompt.security homepage: https://clawsec.prompt.security
metadata: {"openclaw":{"emoji":"👻","category":"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 - Network behavior: none by default
- Trust model: any scheduling is opt-in, but restore mode intentionally overwrites drifted files - 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) ## Quick Start (3 Steps)
### Step 1: Initialize baselines ### Step 1: Initialize baselines
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "soul-guardian", "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.", "description": "Drift detection and baseline integrity guard for agent workspace prompt files. Auto-restore critical files with tamper-evident audit logging.",
"author": "prompt-security", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
+2
View File
@@ -78,6 +78,7 @@ export interface SkillMetadata {
description: string; description: string;
emoji: string; emoji: string;
category: string; category: string;
platforms?: AdvisoryPlatformSlug[];
tag: string; tag: string;
} }
@@ -129,6 +130,7 @@ export interface SkillJson {
description: string; description: string;
}>; }>;
}; };
platforms?: AdvisoryPlatformSlug[];
platform?: CorePlatformSlug | (string & {}); platform?: CorePlatformSlug | (string & {});
openclaw?: SkillPlatformMetadata | null; openclaw?: SkillPlatformMetadata | null;
hermes?: SkillPlatformMetadata | null; hermes?: SkillPlatformMetadata | null;
+54 -3
View File
@@ -12,12 +12,50 @@ Example:
import hashlib import hashlib
import json import json
import re
import sys import sys
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path, PurePosixPath, PureWindowsPath
from validate_skill import validate_skill 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: def calculate_sha256(file_path: Path) -> str:
"""Calculate SHA256 hash of a file.""" """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", []) sbom_files = skill_data.get("sbom", {}).get("files", [])
for file_entry in sbom_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 full_path = skill_path / file_rel_path
if full_path.exists(): 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 # Always include skill.json
files_to_checksum.append(("skill.json", skill_json_path)) files_to_checksum.append(("skill.json", skill_json_path))
+57
View File
@@ -0,0 +1,57 @@
import {
CORE_PLATFORM_SLUGS,
type AdvisoryPlatformSlug,
type CorePlatformSlug,
type SkillJson,
type SkillPlatformMetadata,
} from '../types';
export const SKILL_PLATFORM_METADATA_KEYS = CORE_PLATFORM_SLUGS;
const normalizePlatformSlug = (platform: string) => platform.trim().toLowerCase();
export const isSkillPlatformMetadataObject = (value: unknown): value is SkillPlatformMetadata => {
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
const maybe = value as Record<string, unknown>;
return 'emoji' in maybe || 'category' in maybe || 'triggers' in maybe;
};
export const getRecommendedSkillPlatforms = (skill: SkillJson): AdvisoryPlatformSlug[] => {
const platforms = new Set<string>();
if (Array.isArray(skill.platforms)) {
for (const platform of skill.platforms) {
if (typeof platform === 'string' && platform.trim()) {
platforms.add(normalizePlatformSlug(platform));
}
}
}
if (typeof skill.platform === 'string' && skill.platform.trim()) {
platforms.add(normalizePlatformSlug(skill.platform));
}
for (const key of SKILL_PLATFORM_METADATA_KEYS) {
if (isSkillPlatformMetadataObject(skill[key])) {
platforms.add(key);
}
}
return [...platforms] as AdvisoryPlatformSlug[];
};
export const resolveSkillPlatformMetadata = (skill: SkillJson): SkillPlatformMetadata => {
for (const platform of getRecommendedSkillPlatforms(skill)) {
if ((SKILL_PLATFORM_METADATA_KEYS as readonly string[]).includes(platform)) {
const platformBlock = skill[platform as CorePlatformSlug];
if (isSkillPlatformMetadataObject(platformBlock)) return platformBlock;
}
}
for (const key of SKILL_PLATFORM_METADATA_KEYS) {
const fallbackBlock = skill[key];
if (isSkillPlatformMetadataObject(fallbackBlock)) return fallbackBlock;
}
return {};
};