mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 13:38:03 +03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1d1824f86 | |||
| d7312d7429 | |||
| cb58e588c5 |
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"name": "global-skills",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/clawtributor"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hermes-skills",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/hermes-attestation-guardian",
|
||||
"./skills/hermes-traffic-guardian"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nano-claw-skills",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/clawsec-nanoclaw",
|
||||
"./skills/nanoclaw-traffic-guardian"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "open-claw-skills",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/clawsec-clawhub-checker",
|
||||
"./skills/clawsec-feed",
|
||||
"./skills/clawsec-scanner",
|
||||
"./skills/clawsec-suite",
|
||||
"./skills/openclaw-audit-watchdog",
|
||||
"./skills/openclaw-traffic-guardian",
|
||||
"./skills/soul-guardian"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pico-claw-skills",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/picoclaw-security-guardian",
|
||||
"./skills/picoclaw-self-pen-testing",
|
||||
"./skills/picoclaw-traffic-guardian"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "repo-internal-skills",
|
||||
"source": "./",
|
||||
"skills": [
|
||||
"./skills/claw-release"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -111,8 +111,12 @@ jobs:
|
||||
run: node scripts/test-nvd-ghsa-consolidation-workflow.mjs
|
||||
- name: NVD + GHSA Pipeline Dry Run
|
||||
run: node scripts/test-nvd-ghsa-pipeline-dry-run.mjs
|
||||
- name: Skill Release Workflow Tests
|
||||
run: node scripts/test-skill-release-workflow.mjs
|
||||
- name: Skill Release Tooling Tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for test_file in scripts/test-skill-*.mjs; do
|
||||
node "$test_file"
|
||||
done
|
||||
- name: Deploy Pages Advisory Checksums Tests
|
||||
run: node scripts/test-deploy-pages-checksums.mjs
|
||||
- name: GitHub Traffic Archive Tests
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
- name: Build project
|
||||
run: npm run build
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
||||
|
||||
@@ -1069,7 +1069,7 @@ jobs:
|
||||
--event workflow_dispatch \
|
||||
--limit 50 \
|
||||
--json databaseId,createdAt,headSha \
|
||||
--jq --arg since "$DISPATCHED_AT" --arg sha "$EXPECTED_HEAD_SHA" '
|
||||
| jq -r --arg since "$DISPATCHED_AT" --arg sha "$EXPECTED_HEAD_SHA" '
|
||||
map(select(.createdAt >= $since and .headSha == $sha))
|
||||
| sort_by(.createdAt)
|
||||
| last
|
||||
|
||||
@@ -84,6 +84,6 @@ jobs:
|
||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -7,6 +7,9 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'skills/**'
|
||||
- '.github/workflows/skill-release.yml'
|
||||
- 'scripts/ci/**'
|
||||
- 'scripts/test-skill-*.mjs'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -35,6 +38,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Verify signing key consistency (repo + docs)
|
||||
run: ./scripts/ci/verify_signing_key_consistency.sh
|
||||
|
||||
@@ -229,6 +237,12 @@ jobs:
|
||||
|
||||
echo "Validated ${checked_skills} bumped skill(s): version parity and changelog release notes are present."
|
||||
|
||||
- name: Validate npx skills install docs
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: node scripts/ci/validate_skill_install_docs.mjs --base "$BASE_SHA" --head "$HEAD_SHA"
|
||||
|
||||
release:
|
||||
if: github.event_name == 'pull_request'
|
||||
needs: validate-pr-version-sync
|
||||
@@ -241,6 +255,21 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install SkillSpector
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m venv /tmp/skillspector-venv
|
||||
. /tmp/skillspector-venv/bin/activate
|
||||
git clone --depth 1 https://github.com/NVIDIA/SkillSpector.git /tmp/skillspector
|
||||
make -C /tmp/skillspector install
|
||||
echo "/tmp/skillspector-venv/bin" >> "$GITHUB_PATH"
|
||||
skillspector --help >/dev/null
|
||||
|
||||
- name: Generate test signing key for dry-run
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -399,6 +428,50 @@ jobs:
|
||||
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
|
||||
}
|
||||
|
||||
generate_skillspector_report() {
|
||||
local skill_dir="$1"
|
||||
local report_path="$2"
|
||||
|
||||
set +e
|
||||
skillspector scan "${skill_dir}" --no-llm --format markdown --output "${report_path}"
|
||||
local status=$?
|
||||
set -e
|
||||
|
||||
if [ ! -s "${report_path}" ]; then
|
||||
echo "::error file=${skill_dir}::SkillSpector did not produce a report."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "${status}" -ne 0 ]; then
|
||||
echo "::warning file=${report_path}::SkillSpector returned exit code ${status}; report is included for review."
|
||||
fi
|
||||
}
|
||||
|
||||
add_release_asset_checksum() {
|
||||
local out_assets="$1"
|
||||
local asset="$2"
|
||||
local file_path="${out_assets}/${asset}"
|
||||
local sha256
|
||||
local size
|
||||
local tmp_json
|
||||
|
||||
if [ ! -s "${file_path}" ]; then
|
||||
echo "::error file=${file_path}::Required release trust artifact is missing or empty."
|
||||
return 1
|
||||
fi
|
||||
|
||||
sha256="$(sha256sum "${file_path}" | awk '{print $1}')"
|
||||
size="$(stat -c%s "${file_path}" 2>/dev/null || stat -f%z "${file_path}")"
|
||||
tmp_json="$(mktemp)"
|
||||
jq \
|
||||
--arg key "${asset}" \
|
||||
--arg sha "${sha256}" \
|
||||
--argjson sz "${size}" \
|
||||
'.files += {($key): {sha256: $sha, size: $sz, path: $key}}' \
|
||||
"${out_assets}/checksums.json" > "${tmp_json}"
|
||||
mv "${tmp_json}" "${out_assets}/checksums.json"
|
||||
}
|
||||
|
||||
while IFS= read -r skill_dir; do
|
||||
json_path="${skill_dir}/skill.json"
|
||||
md_path="${skill_dir}/SKILL.md"
|
||||
@@ -622,6 +695,58 @@ jobs:
|
||||
continue
|
||||
fi
|
||||
|
||||
# --- Generate release trust packet and include it in signed checksums ---
|
||||
node scripts/ci/generate_skill_release_trust_packet.mjs \
|
||||
"${skill_dir}" \
|
||||
"${out_assets}" \
|
||||
--repository "${{ github.repository }}" \
|
||||
--tag "${tag}" \
|
||||
--source-ref "${HEAD_SHA}"
|
||||
|
||||
# --- Generate SkillSpector report ---
|
||||
if ! generate_skillspector_report "${skill_dir}" "${out_assets}/skillspector-report.md"; then
|
||||
failures=$((failures + 1))
|
||||
rm -rf "${staging_dir}"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! add_release_asset_checksum "${out_assets}" "skill-card.md"; then
|
||||
failures=$((failures + 1))
|
||||
rm -rf "${staging_dir}"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! add_release_asset_checksum "${out_assets}" "permissions.json"; then
|
||||
failures=$((failures + 1))
|
||||
rm -rf "${staging_dir}"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! add_release_asset_checksum "${out_assets}" "install.md"; then
|
||||
failures=$((failures + 1))
|
||||
rm -rf "${staging_dir}"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! add_release_asset_checksum "${out_assets}" "skillspector-report.md"; then
|
||||
failures=$((failures + 1))
|
||||
rm -rf "${staging_dir}"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! jq -e . "${out_assets}/checksums.json" >/dev/null 2>&1; then
|
||||
echo "::error::Generated checksums.json is invalid JSON after adding release trust artifacts."
|
||||
failures=$((failures + 1))
|
||||
rm -rf "${staging_dir}"
|
||||
echo "::endgroup::"
|
||||
continue
|
||||
fi
|
||||
|
||||
# --- Copy skill.json and root-level docs alongside the zip ---
|
||||
cp "${json_path}" "${out_assets}/skill.json"
|
||||
if [ -f "${skill_dir}/SKILL.md" ]; then
|
||||
@@ -652,6 +777,56 @@ jobs:
|
||||
|
||||
echo "Release dry-run completed successfully for ${dry_run_count} changed skill(s)."
|
||||
|
||||
simulate-tag-release-build:
|
||||
if: github.event_name == 'pull_request'
|
||||
needs: validate-pr-version-sync
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install SkillSpector
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m venv /tmp/skillspector-venv
|
||||
. /tmp/skillspector-venv/bin/activate
|
||||
git clone --depth 1 https://github.com/NVIDIA/SkillSpector.git /tmp/skillspector
|
||||
make -C /tmp/skillspector install
|
||||
echo "/tmp/skillspector-venv/bin" >> "$GITHUB_PATH"
|
||||
skillspector --help >/dev/null
|
||||
|
||||
- name: Simulate tag release build
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p dist/tag-release-simulation
|
||||
|
||||
for skill_json in skills/*/skill.json; do
|
||||
skill_dir="${skill_json%/skill.json}"
|
||||
skill_name="$(basename "${skill_dir}")"
|
||||
echo "::group::Simulate tag release build for ${skill_name}"
|
||||
node scripts/ci/simulate_skill_tag_release.mjs \
|
||||
"${skill_dir}" \
|
||||
"dist/tag-release-simulation/${skill_name}" \
|
||||
--repository "${{ github.repository }}" \
|
||||
--source-ref "${{ github.event.pull_request.head.sha }}"
|
||||
jq -e '.simulated_version | test("^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9]+)?$")' \
|
||||
"dist/tag-release-simulation/${skill_name}/simulation-summary.json" >/dev/null
|
||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/checksums.json"
|
||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/checksums.sig"
|
||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/signing-public.pem"
|
||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/skillspector-report.md"
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
release-tag:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
@@ -771,6 +946,19 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Validate npx skills install docs
|
||||
run: node scripts/ci/validate_skill_install_docs.mjs --skills "${{ steps.parse.outputs.skill_path }}"
|
||||
|
||||
- name: Install SkillSpector
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 -m venv /tmp/skillspector-venv
|
||||
. /tmp/skillspector-venv/bin/activate
|
||||
git clone --depth 1 https://github.com/NVIDIA/SkillSpector.git /tmp/skillspector
|
||||
make -C /tmp/skillspector install
|
||||
echo "/tmp/skillspector-venv/bin" >> "$GITHUB_PATH"
|
||||
skillspector --help >/dev/null
|
||||
|
||||
- name: Sign embedded advisory feed and verify
|
||||
if: hashFiles(format('skills/{0}/advisories/feed.json', steps.parse.outputs.skill_name)) != ''
|
||||
uses: ./.github/actions/sign-and-verify
|
||||
@@ -870,6 +1058,49 @@ jobs:
|
||||
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
|
||||
}
|
||||
|
||||
generate_skillspector_report() {
|
||||
local skill_dir="$1"
|
||||
local report_path="$2"
|
||||
|
||||
set +e
|
||||
skillspector scan "${skill_dir}" --no-llm --format markdown --output "${report_path}"
|
||||
local status=$?
|
||||
set -e
|
||||
|
||||
if [ ! -s "${report_path}" ]; then
|
||||
echo "::error file=${skill_dir}::SkillSpector did not produce a report."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "${status}" -ne 0 ]; then
|
||||
echo "::warning file=${report_path}::SkillSpector returned exit code ${status}; report is included for review."
|
||||
fi
|
||||
}
|
||||
|
||||
add_release_asset_checksum() {
|
||||
local asset="$1"
|
||||
local file_path="release-assets/${asset}"
|
||||
local sha256
|
||||
local size
|
||||
local tmp_json
|
||||
|
||||
if [ ! -s "${file_path}" ]; then
|
||||
echo "::error file=${file_path}::Required release trust artifact is missing or empty."
|
||||
return 1
|
||||
fi
|
||||
|
||||
sha256="$(sha256sum "${file_path}" | awk '{print $1}')"
|
||||
size="$(stat -c%s "${file_path}" 2>/dev/null || stat -f%z "${file_path}")"
|
||||
tmp_json="$(mktemp)"
|
||||
jq \
|
||||
--arg key "${asset}" \
|
||||
--arg sha "${sha256}" \
|
||||
--argjson sz "${size}" \
|
||||
'.files += {($key): {sha256: $sha, size: $sz, path: $key}}' \
|
||||
release-assets/checksums.json > "${tmp_json}"
|
||||
mv "${tmp_json}" release-assets/checksums.json
|
||||
}
|
||||
|
||||
# --- Stage SBOM files preserving directory structure ---
|
||||
STAGING_DIR="$(mktemp -d)"
|
||||
INNER_DIR="$STAGING_DIR/$SKILL_NAME"
|
||||
@@ -971,6 +1202,32 @@ jobs:
|
||||
files: $files
|
||||
}' > "release-assets/checksums.json"
|
||||
|
||||
# --- Generate release trust packet and include it in signed checksums ---
|
||||
node scripts/ci/generate_skill_release_trust_packet.mjs \
|
||||
"$SKILL_PATH" \
|
||||
release-assets \
|
||||
--repository "${{ github.repository }}" \
|
||||
--tag "$TAG" \
|
||||
--source-ref "$TAG"
|
||||
|
||||
# --- Generate SkillSpector report ---
|
||||
generate_skillspector_report "$SKILL_PATH" "release-assets/skillspector-report.md"
|
||||
|
||||
test -s release-assets/skill-card.md
|
||||
test -s release-assets/permissions.json
|
||||
test -s release-assets/install.md
|
||||
test -s release-assets/skillspector-report.md
|
||||
|
||||
add_release_asset_checksum "skill-card.md"
|
||||
add_release_asset_checksum "permissions.json"
|
||||
add_release_asset_checksum "install.md"
|
||||
add_release_asset_checksum "skillspector-report.md"
|
||||
|
||||
if ! jq -e . "release-assets/checksums.json" >/dev/null 2>&1; then
|
||||
echo "::error::Generated checksums.json is invalid JSON after adding release trust artifacts."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Copy skill.json and root-level docs alongside the zip ---
|
||||
cp "$SKILL_PATH/skill.json" release-assets/skill.json
|
||||
if [ -f "$SKILL_PATH/SKILL.md" ]; then
|
||||
@@ -1062,6 +1319,26 @@ jobs:
|
||||
{
|
||||
echo "quick_install<<INSTALL_EOF"
|
||||
|
||||
cat <<EOF
|
||||
### Agent Skills CLI
|
||||
|
||||
**Codex global install:**
|
||||
\`\`\`bash
|
||||
npx skills add ${REPO} --skill ${SKILL_NAME} --agent codex --global --yes
|
||||
\`\`\`
|
||||
|
||||
**OpenClaw global install:**
|
||||
\`\`\`bash
|
||||
npx skills add ${REPO} --skill ${SKILL_NAME} --agent openclaw --global --yes
|
||||
\`\`\`
|
||||
|
||||
**Update an installed skill:**
|
||||
\`\`\`bash
|
||||
npx skills update ${SKILL_NAME}
|
||||
\`\`\`
|
||||
|
||||
EOF
|
||||
|
||||
if [ "${{ steps.publishable.outputs.publish_clawhub }}" = "true" ] && [ "${{ steps.publishable.outputs.openclaw_skill }}" = "true" ]; then
|
||||
cat <<EOF
|
||||
### Quick Install
|
||||
@@ -1396,6 +1673,9 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Validate npx skills install docs
|
||||
run: node scripts/ci/validate_skill_install_docs.mjs --skills "${{ steps.parse.outputs.skill_path }}"
|
||||
|
||||
- name: Install clawhub CLI
|
||||
run: npm install -g clawhub@${CLAWHUB_CLI_VERSION}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ ClawSec is a **complete security skill suite for AI agent platforms**. It provid
|
||||
| clawsec-nanoclaw | NanoClaw | Yes | Yes | Yes | Yes | No |
|
||||
| clawsec-scanner | OpenClaw | Yes | No | Yes | Yes | No |
|
||||
| clawsec-suite | OpenClaw | Yes | Yes | No | Yes | No |
|
||||
| clawtributor | OpenClaw | Yes | No | No | No | No |
|
||||
| clawtributor | All core platforms | No | No | No | No | No |
|
||||
| hermes-attestation-guardian | Hermes | Yes (signed advisory feed verification) | Yes | No | Limited (advisory preflight gating only; no artifact signature/provenance install verification) | No |
|
||||
| hermes-traffic-guardian | Hermes | No | Planned posture export only | No | No | Spec baseline |
|
||||
| nanoclaw-traffic-guardian | NanoClaw | No | No | No | No | Spec baseline |
|
||||
|
||||
+40
-5
@@ -1,8 +1,43 @@
|
||||
{
|
||||
"version": "0.0.3",
|
||||
"updated": "2026-06-03T07:38:12Z",
|
||||
"updated": "2026-06-10T08:30:16Z",
|
||||
"description": "Community-driven security advisory feed for ClawSec. Automatically updated with OpenClaw-related CVEs from NVD and community-reported security incidents.",
|
||||
"advisories": [
|
||||
{
|
||||
"id": "CVE-2026-11461",
|
||||
"severity": "medium",
|
||||
"type": "improper_authorization",
|
||||
"nvd_category_id": "CWE-285",
|
||||
"title": "A vulnerability has been found in NousResearch hermes-agent up to 0.12.0. This affects the function ...",
|
||||
"description": "A vulnerability has been found in NousResearch hermes-agent up to 0.12.0. This affects the function resolve_session_by_title of the file hermes_state.py of the component resume Endpoint. Such manipulation of the argument Title leads to authorization bypass. It is possible to launch the attack remotely. The exploit has been disclosed to the public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.",
|
||||
"affected": [
|
||||
"hermes@*"
|
||||
],
|
||||
"platforms": [
|
||||
"hermes"
|
||||
],
|
||||
"action": "Review and update affected components. See NVD for remediation details.",
|
||||
"published": "2026-06-07T22:16:22.547",
|
||||
"references": [
|
||||
"https://gist.github.com/YLChen-007/7951b3dc39193fb675914cc5d8b672fa",
|
||||
"https://gist.github.com/YLChen-007/c2d162e9c8d39584223683cdcba98607",
|
||||
"https://vuldb.com/cve/CVE-2026-11461"
|
||||
],
|
||||
"cvss_score": 6.3,
|
||||
"nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-11461",
|
||||
"exploitability_score": "medium",
|
||||
"exploitability_rationale": "Medium CVSS score (6.3); network accessible",
|
||||
"attack_vector_analysis": {
|
||||
"is_network_accessible": true,
|
||||
"requires_authentication": true,
|
||||
"requires_user_interaction": false,
|
||||
"complexity": "low"
|
||||
},
|
||||
"exploit_detection": {
|
||||
"exploit_available": false,
|
||||
"exploit_sources": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CVE-2026-10548",
|
||||
"severity": "medium",
|
||||
@@ -12216,8 +12251,8 @@
|
||||
"id": "GHSA-jf56-mccx-5f3f",
|
||||
"ghsa_id": "GHSA-jf56-mccx-5f3f",
|
||||
"cve_id": null,
|
||||
"status": "active",
|
||||
"stale": false,
|
||||
"status": "stale",
|
||||
"stale": true,
|
||||
"stale_after_days": 60,
|
||||
"severity": "high",
|
||||
"type": "github_security_advisory",
|
||||
@@ -12260,8 +12295,8 @@
|
||||
"id": "GHSA-gfmx-pph7-g46x",
|
||||
"ghsa_id": "GHSA-gfmx-pph7-g46x",
|
||||
"cve_id": null,
|
||||
"status": "active",
|
||||
"stale": false,
|
||||
"status": "stale",
|
||||
"stale": true,
|
||||
"stale_after_days": 60,
|
||||
"severity": "high",
|
||||
"type": "github_security_advisory",
|
||||
|
||||
@@ -1 +1 @@
|
||||
v+PiWmjIkY6zdIyI9xJX0l0aTy0Azp1+LoZR6qaiDZJnXFuSBX4Sw/x5tMdTb0xSbqdDTJOZwwWI8coPVepzBw==
|
||||
agiAAFvzM1vNHxH2+bGtyeKqFScLWJHnNreBcPpTODUqD0xqFi0cnyP/ZaZX+Rsw1Y9uZ7pGdFdA93pD4lh2BQ==
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"updated": "2026-06-03T07:38:13Z",
|
||||
"updated": "2026-06-10T08:30:16Z",
|
||||
"description": "Provisional ClawSec advisory feed for public GitHub Security Advisories that do not yet have CVE identifiers.",
|
||||
"stale_after_days": 60,
|
||||
"semantics": {
|
||||
@@ -2810,8 +2810,8 @@
|
||||
"id": "GHSA-jf56-mccx-5f3f",
|
||||
"ghsa_id": "GHSA-jf56-mccx-5f3f",
|
||||
"cve_id": null,
|
||||
"status": "active",
|
||||
"stale": false,
|
||||
"status": "stale",
|
||||
"stale": true,
|
||||
"stale_after_days": 60,
|
||||
"severity": "high",
|
||||
"type": "github_security_advisory",
|
||||
@@ -2853,8 +2853,8 @@
|
||||
"id": "GHSA-gfmx-pph7-g46x",
|
||||
"ghsa_id": "GHSA-gfmx-pph7-g46x",
|
||||
"cve_id": null,
|
||||
"status": "active",
|
||||
"stale": false,
|
||||
"status": "stale",
|
||||
"stale": true,
|
||||
"stale_after_days": 60,
|
||||
"severity": "high",
|
||||
"type": "github_security_advisory",
|
||||
|
||||
@@ -1 +1 @@
|
||||
SCkRaPMF6IYDwZuR7/JJXxpB7A7ebuMvLqK827uWX0yfEJr7l2gyLpxvHsEpWJDzE4gchxd5yqJx5qF/yqNwAg==
|
||||
q1EyZ75QcdG2X6FVDkUoAyBtQE3ONA+7k9cmNFmXFgOOuGRPOpSDFUtbSvy86HPqnii26DMoeFJ1hatWJ0lBCQ==
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
ClawSec
|
||||
Security skill suite for AI agents (integrity checks, drift detection, advisory feed).
|
||||
Agent install:
|
||||
Available via clawhub: npx clawhub@latest install clawsec-suite
|
||||
Available via npx skills: npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
||||
OR
|
||||
curl -sL https://clawsec.prompt.security/releases/latest/download/SKILL.md
|
||||
-->
|
||||
@@ -141,7 +141,7 @@
|
||||
ClawSec
|
||||
Security skill suite for AI agents (integrity checks, drift detection, advisory feed).
|
||||
Agent install:
|
||||
Available via clawhub: npx clawhub@latest install clawsec-suite
|
||||
Available via npx skills: npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
||||
OR
|
||||
curl -sL https://clawsec.prompt.security/releases/latest/download/SKILL.md
|
||||
</noscript>
|
||||
|
||||
+14
-2
@@ -13,7 +13,7 @@ export const Home: React.FC = () => {
|
||||
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
||||
const [currentPlatformIndex, setCurrentPlatformIndex] = useState(0);
|
||||
|
||||
const curlCommand = `npx clawhub@latest install clawsec-suite`;
|
||||
const curlCommand = `npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y`;
|
||||
|
||||
// Rotate file names every 2-3 seconds
|
||||
useEffect(() => {
|
||||
@@ -44,7 +44,7 @@ export const Home: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const humanInstruction = `Please install clawsec-suite from clawhubnpx clawhub@latest install clawsec-suite`;
|
||||
const humanInstruction = `Please install clawsec-suite with npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y`;
|
||||
|
||||
const handleCopyCurl = () => {
|
||||
navigator.clipboard.writeText(curlCommand);
|
||||
@@ -285,6 +285,18 @@ export const Home: React.FC = () => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<p className="mt-4 text-center text-xs leading-relaxed text-gray-500">
|
||||
* For harnesses other than OpenClaw, consult the{' '}
|
||||
<a
|
||||
href="https://github.com/prompt-security/clawsec#skill-feature-matrix"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-clawd-accent hover:text-clawd-accent/80 underline underline-offset-2"
|
||||
>
|
||||
README Skill Feature Matrix
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
#!/usr/bin/env node
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const PLATFORM_KEYS = ["openclaw", "nanoclaw", "hermes", "picoclaw"];
|
||||
const KNOWN_AGENT_TYPES = new Set(["codex", "hermes-agent", "openclaw", "universal"]);
|
||||
const PLATFORM_AGENT_ALIASES = new Map([["hermes", "hermes-agent"]]);
|
||||
|
||||
function usage() {
|
||||
return [
|
||||
"Usage: node scripts/ci/generate_skill_release_trust_packet.mjs <skill-dir> <output-dir> [options]",
|
||||
"",
|
||||
"Options:",
|
||||
" --repository <owner/repo> Source repository used in install instructions",
|
||||
" --tag <tag> Release tag for this skill",
|
||||
" --source-ref <ref> Source ref for npx skills examples",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const positional = [];
|
||||
const options = {
|
||||
repository: "prompt-security/clawsec",
|
||||
tag: "",
|
||||
sourceRef: "main",
|
||||
};
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const token = argv[i];
|
||||
if (token === "--repository") {
|
||||
options.repository = argv[++i];
|
||||
} else if (token === "--tag") {
|
||||
options.tag = argv[++i];
|
||||
} else if (token === "--source-ref") {
|
||||
options.sourceRef = argv[++i];
|
||||
} else if (token === "--help" || token === "-h") {
|
||||
console.log(usage());
|
||||
process.exit(0);
|
||||
} else if (token.startsWith("--")) {
|
||||
throw new Error(`Unknown option: ${token}`);
|
||||
} else {
|
||||
positional.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (positional.length !== 2) {
|
||||
throw new Error(usage());
|
||||
}
|
||||
|
||||
return {
|
||||
skillDir: positional[0],
|
||||
outputDir: positional[1],
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
function parseFrontmatter(markdown) {
|
||||
if (!markdown.startsWith("---\n")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const end = markdown.indexOf("\n---", 4);
|
||||
if (end === -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const result = {};
|
||||
const frontmatter = markdown.slice(4, end).split("\n");
|
||||
for (const line of frontmatter) {
|
||||
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
||||
if (match) {
|
||||
result[match[1]] = match[2].replace(/^["']|["']$/g, "").trim();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function asArray(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter((item) => item !== null && item !== undefined).map(String);
|
||||
}
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return [value.trim()];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function unique(values) {
|
||||
return [...new Set(values.filter(Boolean))];
|
||||
}
|
||||
|
||||
function detectPlatform(skill) {
|
||||
for (const key of PLATFORM_KEYS) {
|
||||
if (skill[key] && typeof skill[key] === "object") {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return skill.platform || "agent-skills";
|
||||
}
|
||||
|
||||
function collectDeclaredPlatforms(skill) {
|
||||
const platforms = new Set();
|
||||
if (typeof skill.platform === "string" && skill.platform.trim()) {
|
||||
platforms.add(skill.platform.trim());
|
||||
}
|
||||
if (Array.isArray(skill.platforms)) {
|
||||
for (const platform of skill.platforms) {
|
||||
if (typeof platform === "string" && platform.trim()) {
|
||||
platforms.add(platform.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key of PLATFORM_KEYS) {
|
||||
if (skill[key] && typeof skill[key] === "object") {
|
||||
platforms.add(key);
|
||||
}
|
||||
}
|
||||
return [...platforms];
|
||||
}
|
||||
|
||||
function installAgentForSkill(skill) {
|
||||
const platforms = collectDeclaredPlatforms(skill);
|
||||
if (platforms.length === 0) {
|
||||
return "openclaw";
|
||||
}
|
||||
|
||||
const matchedAgents = new Set();
|
||||
let allPlatformsMatched = true;
|
||||
for (const platform of platforms) {
|
||||
const candidate = PLATFORM_AGENT_ALIASES.get(platform) || platform;
|
||||
if (KNOWN_AGENT_TYPES.has(candidate)) {
|
||||
matchedAgents.add(candidate);
|
||||
} else {
|
||||
allPlatformsMatched = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPlatformsMatched && matchedAgents.size === 1) {
|
||||
return [...matchedAgents][0];
|
||||
}
|
||||
|
||||
return "openclaw";
|
||||
}
|
||||
|
||||
function platformMetadata(skill, platform) {
|
||||
const direct = skill[platform];
|
||||
return direct && typeof direct === "object" ? direct : {};
|
||||
}
|
||||
|
||||
function collectRequiredBinaries(metadata) {
|
||||
const requires = metadata.requires && typeof metadata.requires === "object" ? metadata.requires : {};
|
||||
const bins = asArray(requires.bins);
|
||||
|
||||
for (const [key, value] of Object.entries(requires)) {
|
||||
if (key !== "bins" && typeof value === "string") {
|
||||
bins.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return unique(bins);
|
||||
}
|
||||
|
||||
function collectOptionalBinaries(metadata) {
|
||||
return unique([
|
||||
...asArray(metadata.runtime?.optional_bins),
|
||||
...asArray(metadata.runtime?.optionalBins),
|
||||
]);
|
||||
}
|
||||
|
||||
function collectRequiredEnv(metadata) {
|
||||
const requires = metadata.requires && typeof metadata.requires === "object" ? metadata.requires : {};
|
||||
return unique([
|
||||
...asArray(requires.env),
|
||||
...asArray(metadata.runtime?.required_env),
|
||||
...asArray(metadata.runtime?.requiredEnv),
|
||||
]);
|
||||
}
|
||||
|
||||
function collectOptionalEnv(metadata) {
|
||||
return unique([
|
||||
...asArray(metadata.runtime?.optional_env),
|
||||
...asArray(metadata.runtime?.optionalEnv),
|
||||
]);
|
||||
}
|
||||
|
||||
function stringifyCapabilities(skill, metadata) {
|
||||
const capabilities = metadata.capabilities ?? skill.capabilities ?? {};
|
||||
if (Array.isArray(capabilities)) {
|
||||
return capabilities;
|
||||
}
|
||||
if (capabilities && typeof capabilities === "object") {
|
||||
return Object.entries(capabilities).map(([key, value]) => `${key}: ${String(value)}`);
|
||||
}
|
||||
if (typeof capabilities === "string") {
|
||||
return [capabilities];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function requireField(skill, fieldName) {
|
||||
if (!skill[fieldName] || typeof skill[fieldName] !== "string" || !skill[fieldName].trim()) {
|
||||
throw new Error(`skill.json missing required trust-packet field: ${fieldName}`);
|
||||
}
|
||||
return skill[fieldName].trim();
|
||||
}
|
||||
|
||||
function codeBlock(command) {
|
||||
return ["```bash", command, "```"].join("\n");
|
||||
}
|
||||
|
||||
function buildPermissions({ skill, metadata, platform, generatedAt }) {
|
||||
const execution = metadata.execution && typeof metadata.execution === "object" ? metadata.execution : {};
|
||||
const permissions = {
|
||||
schema_version: "1",
|
||||
generated_at: generatedAt,
|
||||
skill: skill.name,
|
||||
version: skill.version,
|
||||
platform,
|
||||
required_binaries: collectRequiredBinaries(metadata),
|
||||
optional_binaries: collectOptionalBinaries(metadata),
|
||||
required_env: collectRequiredEnv(metadata),
|
||||
optional_env: collectOptionalEnv(metadata),
|
||||
network_egress: execution.network_egress || "Not declared in skill metadata.",
|
||||
persistence: execution.persistence || "Not declared in skill metadata.",
|
||||
automatic_execution: typeof execution.always === "boolean" ? execution.always : "Not declared in skill metadata.",
|
||||
capabilities: stringifyCapabilities(skill, metadata),
|
||||
operator_review: asArray(metadata.operator_review),
|
||||
};
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
function buildSkillCard({ skill, frontmatter, permissions, repository, tag, sourceRef }) {
|
||||
const homepage = skill.homepage || frontmatter.homepage || `https://github.com/${repository}`;
|
||||
const supportRef = `${repository}@${tag || sourceRef}`;
|
||||
const licenseRef = `https://github.com/${repository}/blob/${tag || sourceRef}/LICENSE`;
|
||||
const outputTypes = ["Markdown instructions", "release artifact files"];
|
||||
if (permissions.capabilities.length > 0) {
|
||||
outputTypes.push("local security findings or status reports");
|
||||
}
|
||||
|
||||
return `# Skill Card
|
||||
|
||||
## Description
|
||||
|
||||
The \`${skill.name}\` skill provides this capability: ${skill.description}
|
||||
|
||||
This skill is intended for operator-reviewed security workflows, not unattended production mutation without the review steps declared in the skill instructions.
|
||||
|
||||
## Owner
|
||||
|
||||
prompt-security
|
||||
|
||||
## License/Terms of Use
|
||||
|
||||
${skill.license}
|
||||
|
||||
License reference: ${licenseRef}
|
||||
|
||||
Project homepage: ${homepage}
|
||||
|
||||
## Use Case
|
||||
|
||||
Use this skill for ${permissions.platform} workflows where an agent or operator needs the capability described in \`${skill.name}\`.
|
||||
|
||||
## Deployment Geography for Use
|
||||
|
||||
Global, subject to the operator's local compliance, network, and data-handling requirements.
|
||||
|
||||
## Known Risks and Mitigations
|
||||
|
||||
Risk: The skill may run commands, inspect local files, install hooks, or fetch remote security metadata depending on the workflow.
|
||||
|
||||
Mitigation: Review \`permissions.json\`, \`SKILL.md\`, and the signed \`checksums.json\` before enabling the skill. Keep high-impact actions approval-gated.
|
||||
|
||||
Risk: Security findings and remediation guidance can be incomplete or wrong.
|
||||
|
||||
Mitigation: Treat output as operator guidance. Review proposed removals, installs, configuration changes, and reports before acting.
|
||||
|
||||
## References
|
||||
|
||||
- Source release: ${supportRef}
|
||||
- Skill instructions: SKILL.md
|
||||
- Permission summary: permissions.json
|
||||
- SkillSpector scan: skillspector-report.md
|
||||
- Signed release manifest: checksums.json and checksums.sig
|
||||
|
||||
## Skill Output
|
||||
|
||||
Output type(s): ${outputTypes.join(", ")}
|
||||
|
||||
Output format: Markdown, JSON, shell commands, or local files as documented by the skill.
|
||||
|
||||
Output parameters: See \`SKILL.md\`, \`permissions.json\`, and release checksums for exact files and side effects.
|
||||
|
||||
Other properties: Release assets are covered by signed SHA-256 checksums.
|
||||
|
||||
## Skill Version
|
||||
|
||||
${skill.version}${tag ? ` (${tag})` : ""}
|
||||
|
||||
## Ethical Considerations
|
||||
|
||||
Use this skill only on systems, agents, repositories, and workspaces where you have authorization. Review generated security reports before sharing them because they may contain operational details.
|
||||
`;
|
||||
}
|
||||
|
||||
function buildInstallDoc({ skill, repository, tag, sourceRef }) {
|
||||
const refSuffix = sourceRef && sourceRef !== "main" ? `#${sourceRef}` : "";
|
||||
const source = `${repository}${refSuffix}`;
|
||||
const releaseUrl = tag ? `https://github.com/${repository}/releases/tag/${tag}` : `https://github.com/${repository}`;
|
||||
const agent = installAgentForSkill(skill);
|
||||
|
||||
return `# Install and Update ${skill.name}
|
||||
|
||||
## Install With Agent Skills CLI
|
||||
|
||||
Harness-aware global install:
|
||||
|
||||
${codeBlock(`npx skills add ${source} --skill ${skill.name} --agent ${agent} --global --yes`)}
|
||||
|
||||
Project-local install for compatible agents:
|
||||
|
||||
${codeBlock(`npx skills add ${source} --skill ${skill.name} --yes`)}
|
||||
|
||||
## Update
|
||||
|
||||
Update this skill when installed through the Skills CLI:
|
||||
|
||||
${codeBlock(`npx skills update ${skill.name}`)}
|
||||
|
||||
List installed skills:
|
||||
|
||||
${codeBlock("npx skills list")}
|
||||
|
||||
## Verify Release Artifact
|
||||
|
||||
When installing from a GitHub release instead of the Skills CLI, download the archive, \`checksums.json\`, \`checksums.sig\`, and \`signing-public.pem\` from:
|
||||
|
||||
${releaseUrl}
|
||||
|
||||
Verify \`checksums.json\` before trusting the archive or standalone files.
|
||||
`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const skillDir = path.resolve(args.skillDir);
|
||||
const outputDir = path.resolve(args.outputDir);
|
||||
|
||||
const skillJsonPath = path.join(skillDir, "skill.json");
|
||||
const skillMdPath = path.join(skillDir, "SKILL.md");
|
||||
const [skillJsonRaw, skillMdRaw] = await Promise.all([
|
||||
readFile(skillJsonPath, "utf8"),
|
||||
readFile(skillMdPath, "utf8"),
|
||||
]);
|
||||
|
||||
const skill = JSON.parse(skillJsonRaw);
|
||||
const frontmatter = parseFrontmatter(skillMdRaw);
|
||||
skill.name = requireField(skill, "name");
|
||||
skill.version = requireField(skill, "version");
|
||||
skill.description = requireField(skill, "description");
|
||||
skill.license = requireField(skill, "license");
|
||||
|
||||
const platform = detectPlatform(skill);
|
||||
const metadata = platformMetadata(skill, platform);
|
||||
const generatedAt = new Date().toISOString();
|
||||
const permissions = buildPermissions({ skill, metadata, platform, generatedAt });
|
||||
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
path.join(outputDir, "permissions.json"),
|
||||
`${JSON.stringify(permissions, null, 2)}\n`,
|
||||
),
|
||||
writeFile(
|
||||
path.join(outputDir, "skill-card.md"),
|
||||
buildSkillCard({
|
||||
skill,
|
||||
frontmatter,
|
||||
permissions,
|
||||
repository: args.repository,
|
||||
tag: args.tag,
|
||||
sourceRef: args.sourceRef,
|
||||
}),
|
||||
),
|
||||
writeFile(
|
||||
path.join(outputDir, "install.md"),
|
||||
buildInstallDoc({
|
||||
skill,
|
||||
repository: args.repository,
|
||||
tag: args.tag,
|
||||
sourceRef: args.sourceRef,
|
||||
}),
|
||||
),
|
||||
]);
|
||||
|
||||
console.log(`Generated release trust packet for ${skill.name} in ${outputDir}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,520 @@
|
||||
#!/usr/bin/env node
|
||||
import { createHash } from "node:crypto";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import {
|
||||
cp,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
rm,
|
||||
stat,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const TRUST_ARTIFACTS = [
|
||||
"skill-card.md",
|
||||
"permissions.json",
|
||||
"install.md",
|
||||
"skillspector-report.md",
|
||||
];
|
||||
|
||||
function usage() {
|
||||
return [
|
||||
"Usage: node scripts/ci/simulate_skill_tag_release.mjs <skill-dir> <output-dir> [options]",
|
||||
"",
|
||||
"Options:",
|
||||
" --repository <owner/repo> Source repository used in release metadata",
|
||||
" --source-ref <ref> Source ref used in npx skills examples",
|
||||
" --skillspector-bin <path> SkillSpector executable to run",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const positional = [];
|
||||
const options = {
|
||||
repository: "prompt-security/clawsec",
|
||||
sourceRef: "main",
|
||||
skillspectorBin: "skillspector",
|
||||
};
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const token = argv[i];
|
||||
if (token === "--repository") {
|
||||
options.repository = argv[++i];
|
||||
} else if (token === "--source-ref") {
|
||||
options.sourceRef = argv[++i];
|
||||
} else if (token === "--skillspector-bin") {
|
||||
options.skillspectorBin = argv[++i];
|
||||
} else if (token === "--help" || token === "-h") {
|
||||
console.log(usage());
|
||||
process.exit(0);
|
||||
} else if (token.startsWith("--")) {
|
||||
throw new Error(`Unknown option: ${token}`);
|
||||
} else {
|
||||
positional.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (positional.length !== 2) {
|
||||
throw new Error(usage());
|
||||
}
|
||||
|
||||
return {
|
||||
skillDir: positional[0],
|
||||
outputDir: positional[1],
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
const result = spawnSync(command, args, {
|
||||
encoding: "utf8",
|
||||
...options,
|
||||
});
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
[
|
||||
`Command failed: ${command} ${args.join(" ")}`,
|
||||
result.stdout ? `stdout:\n${result.stdout}` : "",
|
||||
result.stderr ? `stderr:\n${result.stderr}` : "",
|
||||
].filter(Boolean).join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
function runAllowFailure(command, args, options = {}) {
|
||||
return spawnSync(command, args, {
|
||||
encoding: "utf8",
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
function nextSimulatedReleaseVersion(version) {
|
||||
const versionMatch = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9]+))?$/);
|
||||
if (!versionMatch) {
|
||||
throw new Error(`Cannot derive simulated release version from unsupported version: ${version}`);
|
||||
}
|
||||
|
||||
const [, major, minor, patch, prerelease] = versionMatch;
|
||||
if (!prerelease) {
|
||||
return `${major}.${minor}.${Number(patch) + 1}`;
|
||||
}
|
||||
|
||||
const prereleaseMatch = prerelease.match(/^(.*?)(\d+)$/);
|
||||
if (prereleaseMatch) {
|
||||
const [, label, number] = prereleaseMatch;
|
||||
return `${major}.${minor}.${patch}-${label}${Number(number) + 1}`;
|
||||
}
|
||||
|
||||
return `${major}.${minor}.${patch}-${prerelease}1`;
|
||||
}
|
||||
|
||||
function normalizeReleasePath(rawPath) {
|
||||
let releasePath = rawPath.replaceAll("\\", "/");
|
||||
while (releasePath.startsWith("./")) {
|
||||
releasePath = releasePath.slice(2);
|
||||
}
|
||||
while (releasePath.includes("//")) {
|
||||
releasePath = releasePath.replaceAll("//", "/");
|
||||
}
|
||||
|
||||
if (
|
||||
releasePath === "" ||
|
||||
releasePath.startsWith("/") ||
|
||||
/^[A-Za-z]:/.test(releasePath) ||
|
||||
releasePath === ".." ||
|
||||
releasePath.startsWith("../") ||
|
||||
releasePath.endsWith("/..") ||
|
||||
releasePath.includes("/../")
|
||||
) {
|
||||
throw new Error(`Unsafe release path: ${rawPath}`);
|
||||
}
|
||||
|
||||
return releasePath;
|
||||
}
|
||||
|
||||
function isTestReleasePath(releasePath) {
|
||||
const lower = releasePath.toLowerCase();
|
||||
return lower === "test" ||
|
||||
lower === "tests" ||
|
||||
lower.startsWith("test/") ||
|
||||
lower.startsWith("tests/") ||
|
||||
lower.includes("/test/") ||
|
||||
lower.includes("/tests/");
|
||||
}
|
||||
|
||||
async function sha256File(filePath) {
|
||||
const buffer = await readFile(filePath);
|
||||
return createHash("sha256").update(buffer).digest("hex");
|
||||
}
|
||||
|
||||
async function fileSize(filePath) {
|
||||
return (await stat(filePath)).size;
|
||||
}
|
||||
|
||||
async function checksumEntry(filePath, releasePath) {
|
||||
return {
|
||||
sha256: await sha256File(filePath),
|
||||
size: await fileSize(filePath),
|
||||
path: releasePath,
|
||||
};
|
||||
}
|
||||
|
||||
function replaceSkillMarkdownVersion(markdown, version) {
|
||||
if (!markdown.startsWith("---\n")) {
|
||||
throw new Error("SKILL.md is missing YAML frontmatter");
|
||||
}
|
||||
|
||||
const end = markdown.indexOf("\n---", 4);
|
||||
if (end === -1) {
|
||||
throw new Error("SKILL.md frontmatter is not closed");
|
||||
}
|
||||
|
||||
const frontmatter = markdown.slice(0, end);
|
||||
if (!/^version:\s*.+$/m.test(frontmatter)) {
|
||||
throw new Error("SKILL.md frontmatter is missing a version field");
|
||||
}
|
||||
|
||||
return markdown.replace(/^version:\s*.+$/m, `version: ${version}`);
|
||||
}
|
||||
|
||||
async function addSimulatedChangelogEntry(skillDir, version) {
|
||||
const changelogPath = path.join(skillDir, "CHANGELOG.md");
|
||||
if (!existsSync(changelogPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const original = await readFile(changelogPath, "utf8");
|
||||
if (original.includes(`## [${version}] -`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = [
|
||||
`## [${version}] - ${today}`,
|
||||
"",
|
||||
"- Simulated prerelease build for release-pipeline validation.",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
await writeFile(changelogPath, `${entry}${original}`);
|
||||
}
|
||||
|
||||
async function writeJson(filePath, value) {
|
||||
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
||||
}
|
||||
|
||||
async function signFileBase64({ keyPath, inputPath, outputPath, tempRoot }) {
|
||||
const sigBin = path.join(tempRoot, `${path.basename(outputPath)}.bin`);
|
||||
run("openssl", ["pkeyutl", "-sign", "-rawin", "-inkey", keyPath, "-in", inputPath, "-out", sigBin]);
|
||||
run("openssl", ["base64", "-A", "-in", sigBin, "-out", outputPath]);
|
||||
await rm(sigBin, { force: true });
|
||||
}
|
||||
|
||||
async function verifyFileBase64Signature({ publicKeyPath, inputPath, signaturePath, tempRoot }) {
|
||||
const sigBin = path.join(tempRoot, `${path.basename(signaturePath)}.verify.bin`);
|
||||
run("openssl", ["base64", "-d", "-A", "-in", signaturePath, "-out", sigBin]);
|
||||
run("openssl", [
|
||||
"pkeyutl",
|
||||
"-verify",
|
||||
"-rawin",
|
||||
"-pubin",
|
||||
"-inkey",
|
||||
publicKeyPath,
|
||||
"-sigfile",
|
||||
sigBin,
|
||||
"-in",
|
||||
inputPath,
|
||||
]);
|
||||
await rm(sigBin, { force: true });
|
||||
}
|
||||
|
||||
async function createSigningKeyPair(tempRoot) {
|
||||
const keyDir = await mkdtemp(path.join(tempRoot, "signing-"));
|
||||
const privateKeyPath = path.join(keyDir, "private.pem");
|
||||
const publicKeyPath = path.join(keyDir, "public.pem");
|
||||
|
||||
run("openssl", ["genpkey", "-algorithm", "ED25519", "-out", privateKeyPath]);
|
||||
run("openssl", ["pkey", "-in", privateKeyPath, "-pubout", "-out", publicKeyPath]);
|
||||
|
||||
return { privateKeyPath, publicKeyPath };
|
||||
}
|
||||
|
||||
async function signAdvisoryArtifacts(skillDir, tempRoot) {
|
||||
const advisoryDir = path.join(skillDir, "advisories");
|
||||
const feedPath = path.join(advisoryDir, "feed.json");
|
||||
if (!existsSync(feedPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { privateKeyPath, publicKeyPath } = await createSigningKeyPair(tempRoot);
|
||||
const feedSignaturePath = path.join(advisoryDir, "feed.json.sig");
|
||||
const checksumsPath = path.join(advisoryDir, "checksums.json");
|
||||
const checksumsSignaturePath = path.join(advisoryDir, "checksums.json.sig");
|
||||
const publicKeyOutputPath = path.join(advisoryDir, "feed-signing-public.pem");
|
||||
|
||||
await signFileBase64({
|
||||
keyPath: privateKeyPath,
|
||||
inputPath: feedPath,
|
||||
outputPath: feedSignaturePath,
|
||||
tempRoot,
|
||||
});
|
||||
await verifyFileBase64Signature({
|
||||
publicKeyPath,
|
||||
inputPath: feedPath,
|
||||
signaturePath: feedSignaturePath,
|
||||
tempRoot,
|
||||
});
|
||||
|
||||
await writeJson(checksumsPath, {
|
||||
schema_version: "1",
|
||||
algorithm: "sha256",
|
||||
version: "simulation",
|
||||
generated_at: new Date().toISOString(),
|
||||
files: {
|
||||
"advisories/feed.json": await checksumEntry(feedPath, "advisories/feed.json"),
|
||||
"advisories/feed.json.sig": await checksumEntry(feedSignaturePath, "advisories/feed.json.sig"),
|
||||
},
|
||||
});
|
||||
|
||||
await signFileBase64({
|
||||
keyPath: privateKeyPath,
|
||||
inputPath: checksumsPath,
|
||||
outputPath: checksumsSignaturePath,
|
||||
tempRoot,
|
||||
});
|
||||
await verifyFileBase64Signature({
|
||||
publicKeyPath,
|
||||
inputPath: checksumsPath,
|
||||
signaturePath: checksumsSignaturePath,
|
||||
tempRoot,
|
||||
});
|
||||
|
||||
await cp(publicKeyPath, publicKeyOutputPath);
|
||||
}
|
||||
|
||||
async function addReleaseAssetChecksum({ releaseAssetsDir, manifest, asset }) {
|
||||
const filePath = path.join(releaseAssetsDir, asset);
|
||||
if (!existsSync(filePath) || (await fileSize(filePath)) === 0) {
|
||||
throw new Error(`Required release trust artifact is missing or empty: ${filePath}`);
|
||||
}
|
||||
|
||||
manifest.files[asset] = await checksumEntry(filePath, asset);
|
||||
}
|
||||
|
||||
async function stageSbomFiles({ skillDir, innerDir, sbomFiles }) {
|
||||
for (const entry of sbomFiles) {
|
||||
const releasePath = normalizeReleasePath(entry.path);
|
||||
if (isTestReleasePath(releasePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fullPath = path.join(skillDir, releasePath);
|
||||
if (!existsSync(fullPath)) {
|
||||
throw new Error(`SBOM references missing file: ${releasePath}`);
|
||||
}
|
||||
|
||||
const destination = path.join(innerDir, releasePath);
|
||||
await mkdir(path.dirname(destination), { recursive: true });
|
||||
await cp(fullPath, destination);
|
||||
}
|
||||
}
|
||||
|
||||
async function buildFilesManifest({ skillDir, skillJsonPath, sbomFiles }) {
|
||||
const files = {};
|
||||
for (const entry of sbomFiles) {
|
||||
const releasePath = normalizeReleasePath(entry.path);
|
||||
if (isTestReleasePath(releasePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fullPath = path.join(skillDir, releasePath);
|
||||
if (existsSync(fullPath)) {
|
||||
files[releasePath] = await checksumEntry(fullPath, releasePath);
|
||||
}
|
||||
}
|
||||
|
||||
files["skill.json"] = {
|
||||
sha256: await sha256File(skillJsonPath),
|
||||
size: await fileSize(skillJsonPath),
|
||||
};
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
async function runSkillSpector({ skillspectorBin, skillDir, reportPath }) {
|
||||
const result = runAllowFailure(skillspectorBin, [
|
||||
"scan",
|
||||
skillDir,
|
||||
"--no-llm",
|
||||
"--format",
|
||||
"markdown",
|
||||
"--output",
|
||||
reportPath,
|
||||
]);
|
||||
|
||||
if (!existsSync(reportPath) || (await fileSize(reportPath)) === 0) {
|
||||
throw new Error(
|
||||
[
|
||||
"SkillSpector did not produce a report.",
|
||||
result.stdout ? `stdout:\n${result.stdout}` : "",
|
||||
result.stderr ? `stderr:\n${result.stderr}` : "",
|
||||
].filter(Boolean).join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
console.warn(`SkillSpector returned exit code ${result.status}; report is included for review.`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const sourceSkillDir = path.resolve(args.skillDir);
|
||||
const outputDir = path.resolve(args.outputDir);
|
||||
const releaseAssetsDir = path.join(outputDir, "release-assets");
|
||||
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-release-sim-"));
|
||||
|
||||
try {
|
||||
const skillName = path.basename(sourceSkillDir);
|
||||
const tempSkillDir = path.join(tempRoot, skillName);
|
||||
await cp(sourceSkillDir, tempSkillDir, { recursive: true });
|
||||
|
||||
const skillJsonPath = path.join(tempSkillDir, "skill.json");
|
||||
const skillMdPath = path.join(tempSkillDir, "SKILL.md");
|
||||
const skill = JSON.parse(await readFile(skillJsonPath, "utf8"));
|
||||
const originalVersion = skill.version;
|
||||
const simulatedVersion = nextSimulatedReleaseVersion(originalVersion);
|
||||
const tag = `${skillName}-v${simulatedVersion}`;
|
||||
const zipName = `${tag}.zip`;
|
||||
|
||||
skill.version = simulatedVersion;
|
||||
await writeJson(skillJsonPath, skill);
|
||||
await writeFile(
|
||||
skillMdPath,
|
||||
replaceSkillMarkdownVersion(await readFile(skillMdPath, "utf8"), simulatedVersion),
|
||||
);
|
||||
await addSimulatedChangelogEntry(tempSkillDir, simulatedVersion);
|
||||
await signAdvisoryArtifacts(tempSkillDir, tempRoot);
|
||||
|
||||
if (!skill.sbom || !Array.isArray(skill.sbom.files)) {
|
||||
throw new Error(`skill.json missing required release field: sbom.files`);
|
||||
}
|
||||
|
||||
await mkdir(releaseAssetsDir, { recursive: true });
|
||||
|
||||
const stagingDir = await mkdtemp(path.join(tempRoot, "staging-"));
|
||||
const innerDir = path.join(stagingDir, skillName);
|
||||
await mkdir(innerDir, { recursive: true });
|
||||
await stageSbomFiles({
|
||||
skillDir: tempSkillDir,
|
||||
innerDir,
|
||||
sbomFiles: skill.sbom.files,
|
||||
});
|
||||
await cp(skillJsonPath, path.join(innerDir, "skill.json"));
|
||||
|
||||
run("python3", ["scripts/ci/verify_skill_release_import_closure.py", innerDir], {
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
run("zip", ["-qr", path.join(releaseAssetsDir, zipName), "."], {
|
||||
cwd: stagingDir,
|
||||
});
|
||||
|
||||
const zipContents = run("unzip", ["-Z1", path.join(releaseAssetsDir, zipName)]);
|
||||
if (zipContents.split("\n").some((entry) => /(^|\/)(test|tests)\//i.test(entry))) {
|
||||
throw new Error(`Simulated release archive contains test-only files: ${zipName}`);
|
||||
}
|
||||
|
||||
const manifest = {
|
||||
skill: skillName,
|
||||
version: simulatedVersion,
|
||||
generated_at: new Date().toISOString(),
|
||||
repository: args.repository,
|
||||
tag,
|
||||
archive: {
|
||||
filename: zipName,
|
||||
sha256: await sha256File(path.join(releaseAssetsDir, zipName)),
|
||||
size: await fileSize(path.join(releaseAssetsDir, zipName)),
|
||||
url: `https://github.com/${args.repository}/releases/download/${tag}/${zipName}`,
|
||||
},
|
||||
files: await buildFilesManifest({
|
||||
skillDir: tempSkillDir,
|
||||
skillJsonPath,
|
||||
sbomFiles: skill.sbom.files,
|
||||
}),
|
||||
};
|
||||
|
||||
await writeJson(path.join(releaseAssetsDir, "checksums.json"), manifest);
|
||||
|
||||
run(process.execPath, [
|
||||
"scripts/ci/generate_skill_release_trust_packet.mjs",
|
||||
tempSkillDir,
|
||||
releaseAssetsDir,
|
||||
"--repository",
|
||||
args.repository,
|
||||
"--tag",
|
||||
tag,
|
||||
"--source-ref",
|
||||
args.sourceRef,
|
||||
]);
|
||||
|
||||
await runSkillSpector({
|
||||
skillspectorBin: args.skillspectorBin,
|
||||
skillDir: tempSkillDir,
|
||||
reportPath: path.join(releaseAssetsDir, "skillspector-report.md"),
|
||||
});
|
||||
|
||||
for (const artifact of TRUST_ARTIFACTS) {
|
||||
await addReleaseAssetChecksum({ releaseAssetsDir, manifest, asset: artifact });
|
||||
}
|
||||
await writeJson(path.join(releaseAssetsDir, "checksums.json"), manifest);
|
||||
|
||||
await cp(skillJsonPath, path.join(releaseAssetsDir, "skill.json"));
|
||||
await cp(skillMdPath, path.join(releaseAssetsDir, "SKILL.md"));
|
||||
if (existsSync(path.join(tempSkillDir, "README.md"))) {
|
||||
await cp(path.join(tempSkillDir, "README.md"), path.join(releaseAssetsDir, "README.md"));
|
||||
}
|
||||
|
||||
const { privateKeyPath, publicKeyPath } = await createSigningKeyPair(tempRoot);
|
||||
await signFileBase64({
|
||||
keyPath: privateKeyPath,
|
||||
inputPath: path.join(releaseAssetsDir, "checksums.json"),
|
||||
outputPath: path.join(releaseAssetsDir, "checksums.sig"),
|
||||
tempRoot,
|
||||
});
|
||||
await verifyFileBase64Signature({
|
||||
publicKeyPath,
|
||||
inputPath: path.join(releaseAssetsDir, "checksums.json"),
|
||||
signaturePath: path.join(releaseAssetsDir, "checksums.sig"),
|
||||
tempRoot,
|
||||
});
|
||||
await cp(publicKeyPath, path.join(releaseAssetsDir, "signing-public.pem"));
|
||||
|
||||
await writeJson(path.join(outputDir, "simulation-summary.json"), {
|
||||
skill: skillName,
|
||||
original_version: originalVersion,
|
||||
simulated_version: simulatedVersion,
|
||||
tag,
|
||||
release_assets: path.relative(outputDir, releaseAssetsDir),
|
||||
archive: `release-assets/${zipName}`,
|
||||
});
|
||||
|
||||
console.log(`Simulated tag release build for ${skillName}: ${tag}`);
|
||||
} finally {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFile, readdir } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import https from "node:https";
|
||||
import path from "node:path";
|
||||
|
||||
const DEFAULT_REPOSITORY = "prompt-security/clawsec";
|
||||
const DEFAULT_AGENT_TYPES_URL = "https://raw.githubusercontent.com/vercel-labs/skills/main/src/types.ts";
|
||||
const DOC_FILENAMES = ["README.md", "SKILL.md"];
|
||||
const KNOWN_PLATFORM_KEYS = ["openclaw", "nanoclaw", "picoclaw", "hermes"];
|
||||
const PLATFORM_AGENT_ALIASES = new Map([["hermes", "hermes-agent"]]);
|
||||
|
||||
function usage() {
|
||||
return [
|
||||
"Usage: node scripts/ci/validate_skill_install_docs.mjs [options]",
|
||||
"",
|
||||
"Options:",
|
||||
" --root <dir> Repository root. Defaults to current working directory.",
|
||||
" --repository <owner/repo> Expected npx skills source. Defaults to prompt-security/clawsec.",
|
||||
" --base <sha> Base ref for changed-skill detection.",
|
||||
" --head <sha> Head ref for changed-skill detection.",
|
||||
" --skills <dir[,dir...]> Skill directories to validate.",
|
||||
" --all Validate every skill directory with skill.json.",
|
||||
" --agent-types-file <path> Read Vercel AgentType source from a local file.",
|
||||
" --agent-types-url <url> Read Vercel AgentType source from a URL.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = {
|
||||
root: process.cwd(),
|
||||
repository: DEFAULT_REPOSITORY,
|
||||
base: process.env.BASE_SHA || "",
|
||||
head: process.env.HEAD_SHA || "",
|
||||
skillDirs: [],
|
||||
all: false,
|
||||
agentTypesFile: "",
|
||||
agentTypesUrl: DEFAULT_AGENT_TYPES_URL,
|
||||
};
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const token = argv[i];
|
||||
if (token === "--root") {
|
||||
options.root = argv[++i];
|
||||
} else if (token === "--repository") {
|
||||
options.repository = argv[++i];
|
||||
} else if (token === "--base") {
|
||||
options.base = argv[++i];
|
||||
} else if (token === "--head") {
|
||||
options.head = argv[++i];
|
||||
} else if (token === "--skills") {
|
||||
options.skillDirs.push(...argv[++i].split(",").map((item) => item.trim()).filter(Boolean));
|
||||
} else if (token === "--all") {
|
||||
options.all = true;
|
||||
} else if (token === "--agent-types-file") {
|
||||
options.agentTypesFile = argv[++i];
|
||||
} else if (token === "--agent-types-url") {
|
||||
options.agentTypesUrl = argv[++i];
|
||||
} else if (token === "--help" || token === "-h") {
|
||||
console.log(usage());
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw new Error(`Unknown option: ${token}\n${usage()}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
root: path.resolve(options.root),
|
||||
};
|
||||
}
|
||||
|
||||
function fetchText(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(url, (response) => {
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error(`Failed to fetch ${url}: HTTP ${response.statusCode}`));
|
||||
response.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
response.setEncoding("utf8");
|
||||
let body = "";
|
||||
response.on("data", (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
response.on("end", () => resolve(body));
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function readAgentTypeSource(options) {
|
||||
if (options.agentTypesFile) {
|
||||
return readFile(path.resolve(options.agentTypesFile), "utf8");
|
||||
}
|
||||
|
||||
return fetchText(options.agentTypesUrl);
|
||||
}
|
||||
|
||||
function parseAgentTypes(source) {
|
||||
const match = source.match(/export\s+type\s+AgentType\s*=\s*([\s\S]*?);/);
|
||||
if (!match) {
|
||||
throw new Error("Could not find export type AgentType in Vercel skills type source.");
|
||||
}
|
||||
|
||||
const agents = new Set();
|
||||
const agentTypeBody = match[1];
|
||||
for (const agentMatch of agentTypeBody.matchAll(/['"]([^'"]+)['"]/g)) {
|
||||
agents.add(agentMatch[1]);
|
||||
}
|
||||
|
||||
if (agents.size === 0) {
|
||||
throw new Error("Vercel AgentType list was empty.");
|
||||
}
|
||||
|
||||
return agents;
|
||||
}
|
||||
|
||||
async function listAllSkillDirs(root) {
|
||||
const skillsRoot = path.join(root, "skills");
|
||||
const entries = await readdir(skillsRoot, { withFileTypes: true });
|
||||
return entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => `skills/${entry.name}`)
|
||||
.filter((skillDir) => existsSync(path.join(root, skillDir, "skill.json")))
|
||||
.sort();
|
||||
}
|
||||
|
||||
function changedSkillDirs({ root, base, head }) {
|
||||
if (!base || !head) {
|
||||
throw new Error("Provide --skills, --all, or both --base and --head for changed-skill detection.");
|
||||
}
|
||||
|
||||
const result = spawnSync(
|
||||
"git",
|
||||
[
|
||||
"-C",
|
||||
root,
|
||||
"diff",
|
||||
"--name-only",
|
||||
`${base}...${head}`,
|
||||
"--",
|
||||
"skills/*/**",
|
||||
":(exclude)skills/*/test/**",
|
||||
":(exclude)skills/*/tests/**",
|
||||
],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`git diff failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
||||
}
|
||||
|
||||
return [
|
||||
...new Set(
|
||||
result.stdout
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.map((filePath) => filePath.split("/").slice(0, 2).join("/"))
|
||||
.filter((skillDir) => /^skills\/[^/]+$/.test(skillDir)),
|
||||
),
|
||||
].sort();
|
||||
}
|
||||
|
||||
async function readJson(filePath) {
|
||||
return JSON.parse(await readFile(filePath, "utf8"));
|
||||
}
|
||||
|
||||
function collectDeclaredPlatforms(skill) {
|
||||
const platforms = new Set();
|
||||
|
||||
if (typeof skill.platform === "string" && skill.platform.trim()) {
|
||||
platforms.add(skill.platform.trim());
|
||||
}
|
||||
|
||||
if (Array.isArray(skill.platforms)) {
|
||||
for (const platform of skill.platforms) {
|
||||
if (typeof platform === "string" && platform.trim()) {
|
||||
platforms.add(platform.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of KNOWN_PLATFORM_KEYS) {
|
||||
if (skill[key] && typeof skill[key] === "object") {
|
||||
platforms.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return [...platforms];
|
||||
}
|
||||
|
||||
function agentForSkill(skill, agentTypes) {
|
||||
const platforms = collectDeclaredPlatforms(skill);
|
||||
if (platforms.length === 0) {
|
||||
return "openclaw";
|
||||
}
|
||||
|
||||
const matchedAgents = new Set();
|
||||
let allPlatformsMatched = true;
|
||||
|
||||
for (const platform of platforms) {
|
||||
const aliasedPlatform = PLATFORM_AGENT_ALIASES.get(platform) || platform;
|
||||
if (agentTypes.has(aliasedPlatform)) {
|
||||
matchedAgents.add(aliasedPlatform);
|
||||
} else {
|
||||
allPlatformsMatched = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPlatformsMatched && matchedAgents.size === 1) {
|
||||
return [...matchedAgents][0];
|
||||
}
|
||||
|
||||
return "openclaw";
|
||||
}
|
||||
|
||||
function hasRequiredCommand(markdown, { repository, skillName, agent }) {
|
||||
return markdown
|
||||
.split("\n")
|
||||
.map((line) => line.replace(/\s+/g, " ").trim())
|
||||
.filter((line) => line.includes("npx skills add"))
|
||||
.some((line) => {
|
||||
return (
|
||||
line.includes(`npx skills add ${repository}`) &&
|
||||
line.includes(`--skill ${skillName}`) &&
|
||||
(line.includes(`-a ${agent}`) || line.includes(`--agent ${agent}`)) &&
|
||||
(line.includes(" -y") || line.includes(" --yes"))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function validateSkill({ root, skillDir, repository, agentTypes }) {
|
||||
const skillJsonPath = path.join(root, skillDir, "skill.json");
|
||||
const skill = await readJson(skillJsonPath);
|
||||
const skillName = skill.name || path.basename(skillDir);
|
||||
const agent = agentForSkill(skill, agentTypes);
|
||||
const command = `npx skills add ${repository} --skill ${skillName} -a ${agent} -y`;
|
||||
const failures = [];
|
||||
|
||||
for (const filename of DOC_FILENAMES) {
|
||||
const docPath = path.join(root, skillDir, filename);
|
||||
if (!existsSync(docPath)) {
|
||||
failures.push(`Missing required install documentation file: ${path.join(skillDir, filename)}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const markdown = await readFile(docPath, "utf8");
|
||||
if (!hasRequiredCommand(markdown, { repository, skillName, agent })) {
|
||||
failures.push(`Missing required npx skills install command in ${path.join(skillDir, filename)}: ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
skillDir,
|
||||
skillName,
|
||||
agent,
|
||||
failures,
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const agentTypes = parseAgentTypes(await readAgentTypeSource(options));
|
||||
let skillDirs = options.skillDirs;
|
||||
|
||||
if (options.all) {
|
||||
skillDirs = await listAllSkillDirs(options.root);
|
||||
} else if (skillDirs.length === 0) {
|
||||
skillDirs = changedSkillDirs(options);
|
||||
}
|
||||
|
||||
if (skillDirs.length === 0) {
|
||||
console.log("No skill install docs to validate.");
|
||||
return;
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const skillDir of skillDirs) {
|
||||
const skillJsonPath = path.join(options.root, skillDir, "skill.json");
|
||||
if (!existsSync(skillJsonPath)) {
|
||||
console.log(`Skipping removed skill directory: ${skillDir}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(
|
||||
await validateSkill({
|
||||
root: options.root,
|
||||
skillDir,
|
||||
repository: options.repository,
|
||||
agentTypes,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const failures = results.flatMap((result) => result.failures);
|
||||
if (failures.length > 0) {
|
||||
for (const failure of failures) {
|
||||
console.error(`::error::${failure}`);
|
||||
}
|
||||
throw new Error(`Found ${failures.length} npx skills install documentation issue(s).`);
|
||||
}
|
||||
|
||||
for (const result of results) {
|
||||
console.log(`npx skills install docs OK for ${result.skillName}: -a ${result.agent}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -47,6 +47,16 @@ assert.match(
|
||||
/git add "\$FEED_PATH" "\$FEED_SIG_PATH" "\$GHSA_FEED_PATH" "\$GHSA_FEED_SIG_PATH" "\$SKILL_FEED_PATH" "\$SKILL_FEED_SIG_PATH"/,
|
||||
'NVD workflow PR must include both NVD and GHSA feed artifacts',
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/gh run list[\s\S]*--jq --arg/,
|
||||
'CodeQL run lookup must not pass jq CLI flags through gh --jq',
|
||||
);
|
||||
assert.match(
|
||||
workflow,
|
||||
/gh run list[\s\S]*--json databaseId,createdAt,headSha \\\s*\n\s+\| jq -r --arg since "\$DISPATCHED_AT" --arg sha "\$EXPECTED_HEAD_SHA"/,
|
||||
'CodeQL run lookup must filter the gh JSON output with jq variables',
|
||||
);
|
||||
assert.match(
|
||||
ciWorkflow,
|
||||
/name: NVD \+ GHSA Pipeline Dry Run[\s\S]*node scripts\/test-nvd-ghsa-pipeline-dry-run\.mjs/,
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const validator = "scripts/ci/validate_skill_install_docs.mjs";
|
||||
const workflow = await readFile(".github/workflows/skill-release.yml", "utf8");
|
||||
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-install-docs-"));
|
||||
const agentTypesPath = path.join(tempRoot, "vercel-types.ts");
|
||||
|
||||
function runValidator(args) {
|
||||
return spawnSync(
|
||||
process.execPath,
|
||||
[validator, "--root", tempRoot, "--agent-types-file", agentTypesPath, ...args],
|
||||
{
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function writeSkill({ name, metadata, readme, skillMd }) {
|
||||
const skillDir = path.join(tempRoot, "skills", name);
|
||||
await mkdir(skillDir, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(skillDir, "skill.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name,
|
||||
version: "1.0.0",
|
||||
description: `${name} test skill`,
|
||||
license: "AGPL-3.0-or-later",
|
||||
...metadata,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
await writeFile(path.join(skillDir, "README.md"), readme);
|
||||
await writeFile(path.join(skillDir, "SKILL.md"), skillMd);
|
||||
}
|
||||
|
||||
try {
|
||||
await writeFile(
|
||||
agentTypesPath,
|
||||
"export type AgentType = | 'codex' | 'hermes-agent' | 'openclaw' | 'universal';\n",
|
||||
);
|
||||
|
||||
await writeSkill({
|
||||
name: "hermes-example",
|
||||
metadata: { hermes: { category: "security" } },
|
||||
readme: "# Hermes Example\n\n## Installation\n\nMissing the Skills CLI command.\n",
|
||||
skillMd: "---\nname: hermes-example\nversion: 1.0.0\n---\n\n## Installation\n\nMissing the Skills CLI command.\n",
|
||||
});
|
||||
|
||||
const missingHermes = runValidator(["--skills", "skills/hermes-example"]);
|
||||
assert.equal(missingHermes.status, 1, "missing Hermes install docs must fail validation");
|
||||
assert.match(
|
||||
missingHermes.stderr,
|
||||
/npx skills add prompt-security\/clawsec --skill hermes-example -a hermes-agent -y/,
|
||||
"Hermes skills must require the hermes-agent installer target",
|
||||
);
|
||||
|
||||
await writeSkill({
|
||||
name: "hermes-example",
|
||||
metadata: { hermes: { category: "security" } },
|
||||
readme:
|
||||
"# Hermes Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill hermes-example -a hermes-agent -y\n```\n",
|
||||
skillMd:
|
||||
"---\nname: hermes-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill hermes-example -a hermes-agent -y\n```\n",
|
||||
});
|
||||
|
||||
const validHermes = runValidator(["--skills", "skills/hermes-example"]);
|
||||
assert.equal(
|
||||
validHermes.status,
|
||||
0,
|
||||
`valid Hermes install docs should pass\nstdout:\n${validHermes.stdout}\nstderr:\n${validHermes.stderr}`,
|
||||
);
|
||||
|
||||
await writeSkill({
|
||||
name: "codex-example",
|
||||
metadata: { platform: "codex" },
|
||||
readme:
|
||||
"# Codex Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill codex-example -a openclaw -y\n```\n",
|
||||
skillMd:
|
||||
"---\nname: codex-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill codex-example -a openclaw -y\n```\n",
|
||||
});
|
||||
|
||||
const wrongExactTarget = runValidator(["--skills", "skills/codex-example"]);
|
||||
assert.equal(wrongExactTarget.status, 1, "exact AgentType matches must use their matched target");
|
||||
assert.match(
|
||||
wrongExactTarget.stderr,
|
||||
/npx skills add prompt-security\/clawsec --skill codex-example -a codex -y/,
|
||||
"Exact AgentType matches must not fall back to openclaw",
|
||||
);
|
||||
|
||||
await writeSkill({
|
||||
name: "nanoclaw-example",
|
||||
metadata: { platform: "nanoclaw", nanoclaw: { category: "security" } },
|
||||
readme:
|
||||
"# NanoClaw Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a hermes-agent -y\n```\n",
|
||||
skillMd:
|
||||
"---\nname: nanoclaw-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a hermes-agent -y\n```\n",
|
||||
});
|
||||
|
||||
const wrongNanoTarget = runValidator(["--skills", "skills/nanoclaw-example"]);
|
||||
assert.equal(wrongNanoTarget.status, 1, "NanoClaw docs must fail when they use the Hermes target");
|
||||
assert.match(
|
||||
wrongNanoTarget.stderr,
|
||||
/npx skills add prompt-security\/clawsec --skill nanoclaw-example -a openclaw -y/,
|
||||
"NanoClaw skills must install through the openclaw target",
|
||||
);
|
||||
|
||||
await writeSkill({
|
||||
name: "nanoclaw-example",
|
||||
metadata: { platform: "nanoclaw", nanoclaw: { category: "security" } },
|
||||
readme:
|
||||
"# NanoClaw Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a openclaw -y\n```\n",
|
||||
skillMd:
|
||||
"---\nname: nanoclaw-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a openclaw -y\n```\n",
|
||||
});
|
||||
|
||||
const validNano = runValidator(["--skills", "skills/nanoclaw-example"]);
|
||||
assert.equal(
|
||||
validNano.status,
|
||||
0,
|
||||
`valid NanoClaw install docs should pass\nstdout:\n${validNano.stdout}\nstderr:\n${validNano.stderr}`,
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/Validate npx skills install docs/,
|
||||
"Skill release workflow must run the install-doc validator",
|
||||
);
|
||||
} finally {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -2,7 +2,9 @@ import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
const workflowPath = new URL('../.github/workflows/skill-release.yml', import.meta.url);
|
||||
const ciWorkflowPath = new URL('../.github/workflows/ci.yml', import.meta.url);
|
||||
const workflow = await readFile(workflowPath, 'utf8');
|
||||
const ciWorkflow = await readFile(ciWorkflowPath, 'utf8');
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
@@ -10,6 +12,22 @@ assert.match(
|
||||
'Skill release workflow must run when any skill package file changes',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/pull_request:[\s\S]*paths:[\s\S]*- '\.github\/workflows\/skill-release\.yml'[\s\S]*- 'scripts\/ci\/\*\*'/,
|
||||
'Skill release workflow must also run when the release pipeline itself changes',
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
ciWorkflow.includes(` - name: Skill Release Tooling Tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for test_file in scripts/test-skill-*.mjs; do
|
||||
node "$test_file"
|
||||
done`),
|
||||
'CI must run every scripts/test-skill-*.mjs file so new skill release tests are not orphaned',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/git diff --name-only "\$\{BASE_SHA\}\.\.\.\$\{HEAD_SHA\}" --[\s\S]*'skills\/\*\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/test\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/tests\/\*\*'/,
|
||||
@@ -27,3 +45,83 @@ assert.match(
|
||||
/::error file=\$\{skill_dir\}::Changed skill package has no version bump\./,
|
||||
'Skill release validation must emit an explicit missing-version-bump error',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/Install SkillSpector/,
|
||||
'Skill release workflow must install SkillSpector before publishing release evidence',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/Generate SkillSpector report/,
|
||||
'Skill release workflow must generate a SkillSpector report for each released skill',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/Generate release trust packet/,
|
||||
'Skill release workflow must generate skill cards, permission summaries, and npx install instructions',
|
||||
);
|
||||
|
||||
for (const artifact of ['skill-card.md', 'permissions.json', 'install.md', 'skillspector-report.md']) {
|
||||
assert.match(
|
||||
workflow,
|
||||
new RegExp(`release-assets/${artifact.replace('.', '\\.')}`),
|
||||
`Skill release workflow must publish ${artifact} in release assets`,
|
||||
);
|
||||
}
|
||||
|
||||
const escapeRegExp = (literal) => literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
for (const artifact of ['skill-card.md', 'permissions.json', 'install.md', 'skillspector-report.md']) {
|
||||
assert.match(
|
||||
workflow,
|
||||
new RegExp(
|
||||
String.raw`if ! add_release_asset_checksum "\$\{out_assets\}" "${escapeRegExp(artifact)}"; then` +
|
||||
String.raw`[\s\S]*?failures=\$\(\(failures \+ 1\)\)[\s\S]*?continue[\s\S]*?fi`,
|
||||
),
|
||||
`PR dry-run validation must aggregate and continue when ${artifact} cannot be checksummed`,
|
||||
);
|
||||
}
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/add_release_asset_checksum "skill-card\.md"/,
|
||||
'Skill card must be included in the signed checksums manifest',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/add_release_asset_checksum "permissions\.json"/,
|
||||
'Permissions summary must be included in the signed checksums manifest',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/add_release_asset_checksum "install\.md"/,
|
||||
'npx install/update instructions must be included in the signed checksums manifest',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/add_release_asset_checksum "skillspector-report\.md"/,
|
||||
'SkillSpector report must be included in the signed checksums manifest',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/Simulate tag release build/,
|
||||
'Skill release workflow must simulate a tag release build during PR validation',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/simulate_skill_tag_release\.mjs/,
|
||||
'Skill release workflow must call the tag release simulation script',
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
workflow.includes('simulated_version | test("^[0-9]+\\\\.[0-9]+\\\\.[0-9]+(-[a-zA-Z0-9]+)?$")'),
|
||||
'Skill release workflow must accept every prerelease version format that release-skill.sh accepts',
|
||||
);
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { chmod, cp, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-tag-release-sim-"));
|
||||
const fakeSkillspector = path.join(tempRoot, "skillspector");
|
||||
|
||||
async function prereleaseFixture(sourceSkillDir, version, fixtureGroup) {
|
||||
const fixtureDir = path.join(tempRoot, fixtureGroup, path.basename(sourceSkillDir));
|
||||
await cp(sourceSkillDir, fixtureDir, { recursive: true });
|
||||
|
||||
const skillJsonPath = path.join(fixtureDir, "skill.json");
|
||||
const skill = JSON.parse(await readFile(skillJsonPath, "utf8"));
|
||||
skill.version = version;
|
||||
await writeFile(skillJsonPath, `${JSON.stringify(skill, null, 2)}\n`);
|
||||
|
||||
const skillMdPath = path.join(fixtureDir, "SKILL.md");
|
||||
const skillMd = await readFile(skillMdPath, "utf8");
|
||||
await writeFile(skillMdPath, skillMd.replace(/^version:\s*.+$/m, `version: ${version}`));
|
||||
|
||||
return fixtureDir;
|
||||
}
|
||||
|
||||
async function runSimulation({ skillDir, outputDir, expectedOriginal, expectedSimulated, expectedAgent }) {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"scripts/ci/simulate_skill_tag_release.mjs",
|
||||
skillDir,
|
||||
outputDir,
|
||||
"--repository",
|
||||
"prompt-security/clawsec",
|
||||
"--source-ref",
|
||||
"pull-request-head",
|
||||
"--skillspector-bin",
|
||||
fakeSkillspector,
|
||||
],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
result.status,
|
||||
0,
|
||||
`tag release simulation failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
||||
);
|
||||
|
||||
const skillName = path.basename(skillDir);
|
||||
const expectedTag = `${skillName}-v${expectedSimulated}`;
|
||||
const summary = JSON.parse(await readFile(path.join(outputDir, "simulation-summary.json"), "utf8"));
|
||||
assert.equal(summary.skill, skillName);
|
||||
assert.equal(summary.original_version, expectedOriginal);
|
||||
assert.equal(summary.simulated_version, expectedSimulated);
|
||||
assert.equal(summary.tag, expectedTag);
|
||||
|
||||
const releaseAssetsDir = path.join(outputDir, "release-assets");
|
||||
const checksums = JSON.parse(await readFile(path.join(releaseAssetsDir, "checksums.json"), "utf8"));
|
||||
assert.equal(checksums.skill, skillName);
|
||||
assert.equal(checksums.version, expectedSimulated);
|
||||
assert.equal(checksums.tag, expectedTag);
|
||||
assert.equal(checksums.archive.filename, `${expectedTag}.zip`);
|
||||
|
||||
for (const artifact of [
|
||||
"skill-card.md",
|
||||
"permissions.json",
|
||||
"install.md",
|
||||
"skillspector-report.md",
|
||||
"checksums.sig",
|
||||
"signing-public.pem",
|
||||
]) {
|
||||
assert.ok(
|
||||
checksums.files[artifact] || artifact.endsWith(".sig") || artifact === "signing-public.pem",
|
||||
`expected ${artifact} to be represented in the release output`,
|
||||
);
|
||||
const file = await readFile(path.join(releaseAssetsDir, artifact));
|
||||
assert.ok(file.length > 0, `${artifact} should not be empty`);
|
||||
}
|
||||
|
||||
const archive = await readFile(path.join(releaseAssetsDir, `${expectedTag}.zip`));
|
||||
assert.ok(archive.length > 0, "release archive should not be empty");
|
||||
|
||||
const install = await readFile(path.join(releaseAssetsDir, "install.md"), "utf8");
|
||||
assert.match(
|
||||
install,
|
||||
new RegExp(
|
||||
`npx skills add prompt-security/clawsec#pull-request-head --skill ${skillName} --agent ${expectedAgent} --global --yes`,
|
||||
),
|
||||
);
|
||||
assert.match(install, new RegExp(`npx skills update ${skillName}`));
|
||||
}
|
||||
|
||||
try {
|
||||
await writeFile(
|
||||
fakeSkillspector,
|
||||
`#!/usr/bin/env node
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const outputIndex = process.argv.indexOf("--output");
|
||||
if (outputIndex === -1 || !process.argv[outputIndex + 1]) {
|
||||
console.error("missing --output");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
writeFileSync(process.argv[outputIndex + 1], "# Fake SkillSpector Report\\n\\nNo live scan executed in unit test.\\n");
|
||||
`,
|
||||
{ mode: 0o700 },
|
||||
);
|
||||
await chmod(fakeSkillspector, 0o700);
|
||||
|
||||
await runSimulation({
|
||||
skillDir: "skills/clawsec-suite",
|
||||
outputDir: path.join(tempRoot, "stable"),
|
||||
expectedOriginal: "0.1.10",
|
||||
expectedSimulated: "0.1.11",
|
||||
expectedAgent: "openclaw",
|
||||
});
|
||||
|
||||
await runSimulation({
|
||||
skillDir: "skills/hermes-traffic-guardian",
|
||||
outputDir: path.join(tempRoot, "beta"),
|
||||
expectedOriginal: "0.0.1-beta3",
|
||||
expectedSimulated: "0.0.1-beta4",
|
||||
expectedAgent: "hermes-agent",
|
||||
});
|
||||
|
||||
const alphaSkillDir = await prereleaseFixture("skills/picoclaw-self-pen-testing", "0.0.3-alpha1", "alpha-fixture");
|
||||
await runSimulation({
|
||||
skillDir: alphaSkillDir,
|
||||
outputDir: path.join(tempRoot, "alpha"),
|
||||
expectedOriginal: "0.0.3-alpha1",
|
||||
expectedSimulated: "0.0.3-alpha2",
|
||||
expectedAgent: "openclaw",
|
||||
});
|
||||
|
||||
const rcSkillDir = await prereleaseFixture("skills/picoclaw-security-guardian", "0.0.4-rc1", "rc-fixture");
|
||||
await runSimulation({
|
||||
skillDir: rcSkillDir,
|
||||
outputDir: path.join(tempRoot, "rc"),
|
||||
expectedOriginal: "0.0.4-rc1",
|
||||
expectedSimulated: "0.0.4-rc2",
|
||||
expectedAgent: "openclaw",
|
||||
});
|
||||
|
||||
const previewSkillDir = await prereleaseFixture("skills/openclaw-traffic-guardian", "0.0.1-preview", "preview-fixture");
|
||||
await runSimulation({
|
||||
skillDir: previewSkillDir,
|
||||
outputDir: path.join(tempRoot, "preview"),
|
||||
expectedOriginal: "0.0.1-preview",
|
||||
expectedSimulated: "0.0.1-preview1",
|
||||
expectedAgent: "openclaw",
|
||||
});
|
||||
} finally {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const outputDir = await mkdtemp(path.join(tmpdir(), "clawsec-trust-packet-"));
|
||||
|
||||
function runTrustPacket(skillDir, targetDir, tag) {
|
||||
return spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"scripts/ci/generate_skill_release_trust_packet.mjs",
|
||||
skillDir,
|
||||
targetDir,
|
||||
"--repository",
|
||||
"prompt-security/clawsec",
|
||||
"--tag",
|
||||
tag,
|
||||
"--source-ref",
|
||||
"main",
|
||||
],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = runTrustPacket("skills/clawsec-suite", outputDir, "clawsec-suite-v0.1.10");
|
||||
|
||||
assert.equal(
|
||||
result.status,
|
||||
0,
|
||||
`trust packet generator failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
||||
);
|
||||
|
||||
const skillCard = await readFile(path.join(outputDir, "skill-card.md"), "utf8");
|
||||
const permissions = JSON.parse(await readFile(path.join(outputDir, "permissions.json"), "utf8"));
|
||||
const install = await readFile(path.join(outputDir, "install.md"), "utf8");
|
||||
|
||||
assert.match(skillCard, /^# Skill Card/m);
|
||||
assert.match(skillCard, /## License\/Terms of Use/);
|
||||
assert.match(skillCard, /AGPL-3\.0-or-later/);
|
||||
assert.match(skillCard, /skillspector-report\.md/);
|
||||
assert.match(skillCard, /clawsec-suite-v0\.1\.10/);
|
||||
|
||||
assert.equal(permissions.skill, "clawsec-suite");
|
||||
assert.equal(permissions.version, "0.1.10");
|
||||
assert.equal(permissions.platform, "openclaw");
|
||||
assert.deepEqual(
|
||||
permissions.required_binaries,
|
||||
["node", "npx", "openclaw", "curl", "jq", "shasum", "openssl", "unzip"],
|
||||
);
|
||||
assert.match(permissions.network_egress, /signed advisory feed/);
|
||||
assert.match(permissions.persistence, /OpenClaw advisory hook/);
|
||||
assert.ok(Array.isArray(permissions.operator_review));
|
||||
assert.ok(permissions.operator_review.length > 0);
|
||||
|
||||
assert.match(install, /npx skills add prompt-security\/clawsec --skill clawsec-suite --agent openclaw --global --yes/);
|
||||
assert.match(install, /npx skills update clawsec-suite/);
|
||||
|
||||
const hermesOutputDir = path.join(outputDir, "hermes");
|
||||
const hermesResult = runTrustPacket(
|
||||
"skills/hermes-attestation-guardian",
|
||||
hermesOutputDir,
|
||||
"hermes-attestation-guardian-v0.1.4",
|
||||
);
|
||||
assert.equal(
|
||||
hermesResult.status,
|
||||
0,
|
||||
`Hermes trust packet generator failed\nstdout:\n${hermesResult.stdout}\nstderr:\n${hermesResult.stderr}`,
|
||||
);
|
||||
const hermesInstall = await readFile(path.join(hermesOutputDir, "install.md"), "utf8");
|
||||
assert.match(
|
||||
hermesInstall,
|
||||
/npx skills add prompt-security\/clawsec --skill hermes-attestation-guardian --agent hermes-agent --global --yes/,
|
||||
);
|
||||
} finally {
|
||||
await rm(outputDir, { recursive: true, force: true });
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.4] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
- Marked the release helper with top-level internal metadata so compatible installers can hide it from normal agent-facing discovery.
|
||||
|
||||
## [0.0.3] - 2026-05-14
|
||||
|
||||
### Security
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Claw Release
|
||||
|
||||
Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill claw-release -a openclaw -y
|
||||
```
|
||||
@@ -1,9 +1,14 @@
|
||||
---
|
||||
name: claw-release
|
||||
version: 0.0.3
|
||||
version: 0.0.4
|
||||
description: Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"🚀","category":"utility","internal":true}}
|
||||
metadata:
|
||||
internal: true
|
||||
openclaw:
|
||||
emoji: "🚀"
|
||||
category: "utility"
|
||||
internal: true
|
||||
clawdis:
|
||||
emoji: "🚀"
|
||||
requires:
|
||||
@@ -18,6 +23,14 @@ Internal tool for releasing skills and managing the ClawSec catalog.
|
||||
|
||||
---
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill claw-release -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Internal maintainer workflow only.
|
||||
@@ -26,7 +39,6 @@ Internal tool for releasing skills and managing the ClawSec catalog.
|
||||
- Side effects: creates commits, tags, pushes to remote, and publishes GitHub Releases
|
||||
- Trust model: run only from a trusted checkout with a clean working tree and maintainer approval
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
@@ -35,7 +47,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="claw-release"
|
||||
VERSION="0.0.3"
|
||||
VERSION="0.0.4"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claw-release",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.6] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.5] - 2026-06-07
|
||||
|
||||
### Security
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
A `clawsec-suite` companion skill that adds a standalone reputation gate before guarded installs.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-clawhub-checker -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Required runtime: `node`, `clawhub`, `openclaw`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-clawhub-checker
|
||||
version: 0.0.5
|
||||
version: 0.0.6
|
||||
description: ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.
|
||||
homepage: https://clawsec.prompt.security
|
||||
clawdis:
|
||||
@@ -14,6 +14,14 @@ clawdis:
|
||||
|
||||
Adds a reputation gate on top of the `clawsec-suite` guarded installer.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-clawhub-checker -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Required runtime: `node`, `clawhub`, `openclaw`
|
||||
@@ -45,7 +53,6 @@ Optional preflight check (validates local paths and prints recommended command):
|
||||
node ~/.openclaw/skills/clawsec-clawhub-checker/scripts/setup_reputation_hook.mjs
|
||||
```
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-clawhub-checker",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"description": "ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.",
|
||||
"author": "abutbul",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.9] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.8] - 2026-05-24
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence and stay informed about emerging threats.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-feed -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Required runtime for standalone installation: `bash`, `curl`, `jq`, `shasum`, `unzip`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-feed
|
||||
version: 0.0.8
|
||||
version: 0.0.9
|
||||
description: Security advisory feed package for OpenClaw-related threats and vulnerabilities. The upstream feed is updated daily; local automation is handled by clawsec-suite or the operator.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"📡","category":"security"}}
|
||||
@@ -16,6 +16,14 @@ Security advisory feed monitoring for AI agents. Subscribe to community-driven t
|
||||
|
||||
The default `feed.json` is the consolidated agent feed. It includes NVD CVEs, approved community advisories, and provisional GitHub Security Advisories that do not have CVE IDs yet.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-feed -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Required runtime for standalone installation: `bash`, `curl`, `jq`, `shasum`, `unzip`
|
||||
@@ -66,7 +74,6 @@ Standalone installation is a network download workflow. Verify the release sourc
|
||||
Installation Steps:
|
||||
Agents should review scripts before execution!
|
||||
|
||||
|
||||
```bash
|
||||
# Get latest clawsec-feed release tag
|
||||
LATEST_TAG=$(curl -sSL https://api.github.com/repos/prompt-security/ClawSec/releases | \
|
||||
@@ -81,7 +88,6 @@ 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.
|
||||
@@ -90,7 +96,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="clawsec-feed"
|
||||
VERSION="0.0.8"
|
||||
VERSION="0.0.9"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
|
||||
@@ -1,8 +1,43 @@
|
||||
{
|
||||
"version": "0.0.3",
|
||||
"updated": "2026-06-03T07:38:12Z",
|
||||
"updated": "2026-06-10T08:30:16Z",
|
||||
"description": "Community-driven security advisory feed for ClawSec. Automatically updated with OpenClaw-related CVEs from NVD and community-reported security incidents.",
|
||||
"advisories": [
|
||||
{
|
||||
"id": "CVE-2026-11461",
|
||||
"severity": "medium",
|
||||
"type": "improper_authorization",
|
||||
"nvd_category_id": "CWE-285",
|
||||
"title": "A vulnerability has been found in NousResearch hermes-agent up to 0.12.0. This affects the function ...",
|
||||
"description": "A vulnerability has been found in NousResearch hermes-agent up to 0.12.0. This affects the function resolve_session_by_title of the file hermes_state.py of the component resume Endpoint. Such manipulation of the argument Title leads to authorization bypass. It is possible to launch the attack remotely. The exploit has been disclosed to the public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.",
|
||||
"affected": [
|
||||
"hermes@*"
|
||||
],
|
||||
"platforms": [
|
||||
"hermes"
|
||||
],
|
||||
"action": "Review and update affected components. See NVD for remediation details.",
|
||||
"published": "2026-06-07T22:16:22.547",
|
||||
"references": [
|
||||
"https://gist.github.com/YLChen-007/7951b3dc39193fb675914cc5d8b672fa",
|
||||
"https://gist.github.com/YLChen-007/c2d162e9c8d39584223683cdcba98607",
|
||||
"https://vuldb.com/cve/CVE-2026-11461"
|
||||
],
|
||||
"cvss_score": 6.3,
|
||||
"nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-11461",
|
||||
"exploitability_score": "medium",
|
||||
"exploitability_rationale": "Medium CVSS score (6.3); network accessible",
|
||||
"attack_vector_analysis": {
|
||||
"is_network_accessible": true,
|
||||
"requires_authentication": true,
|
||||
"requires_user_interaction": false,
|
||||
"complexity": "low"
|
||||
},
|
||||
"exploit_detection": {
|
||||
"exploit_available": false,
|
||||
"exploit_sources": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CVE-2026-10548",
|
||||
"severity": "medium",
|
||||
@@ -12216,8 +12251,8 @@
|
||||
"id": "GHSA-jf56-mccx-5f3f",
|
||||
"ghsa_id": "GHSA-jf56-mccx-5f3f",
|
||||
"cve_id": null,
|
||||
"status": "active",
|
||||
"stale": false,
|
||||
"status": "stale",
|
||||
"stale": true,
|
||||
"stale_after_days": 60,
|
||||
"severity": "high",
|
||||
"type": "github_security_advisory",
|
||||
@@ -12260,8 +12295,8 @@
|
||||
"id": "GHSA-gfmx-pph7-g46x",
|
||||
"ghsa_id": "GHSA-gfmx-pph7-g46x",
|
||||
"cve_id": null,
|
||||
"status": "active",
|
||||
"stale": false,
|
||||
"status": "stale",
|
||||
"stale": true,
|
||||
"stale_after_days": 60,
|
||||
"severity": "high",
|
||||
"type": "github_security_advisory",
|
||||
|
||||
@@ -1 +1 @@
|
||||
v+PiWmjIkY6zdIyI9xJX0l0aTy0Azp1+LoZR6qaiDZJnXFuSBX4Sw/x5tMdTb0xSbqdDTJOZwwWI8coPVepzBw==
|
||||
agiAAFvzM1vNHxH2+bGtyeKqFScLWJHnNreBcPpTODUqD0xqFi0cnyP/ZaZX+Rsw1Y9uZ7pGdFdA93pD4lh2BQ==
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-feed",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.9",
|
||||
"description": "Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.8] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.7] - 2026-06-07
|
||||
|
||||
### Security
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
ClawSec now supports NanoClaw, a containerized WhatsApp bot powered by Claude agents.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-nanoclaw -a openclaw -y
|
||||
```
|
||||
|
||||
## What Changed
|
||||
|
||||
### Advisory Feed Monitoring
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-nanoclaw
|
||||
version: 0.0.7
|
||||
version: 0.0.8
|
||||
description: Use when checking for security vulnerabilities in NanoClaw skills, before installing new skills, or when asked about security advisories affecting the bot
|
||||
---
|
||||
|
||||
@@ -8,6 +8,14 @@ description: Use when checking for security vulnerabilities in NanoClaw skills,
|
||||
|
||||
Security advisory monitoring that protects your WhatsApp bot from known vulnerabilities in skills and dependencies.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-nanoclaw -a openclaw -y
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
ClawSec provides MCP tools that check installed skills against a curated feed of security advisories. It prevents installation of vulnerable skills, includes exploitability context for triage, and alerts you to issues in existing ones.
|
||||
@@ -201,7 +209,6 @@ See [INSTALL.md](./INSTALL.md) for setup and [docs/](./docs/) for advanced usage
|
||||
- Provides actionable remediation steps
|
||||
- Zero false positives (curated feed only)
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-nanoclaw",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"description": "ClawSec security suite for NanoClaw - Advisory feed monitoring, MCP tools for vulnerability checking, and Ed25519 signature verification for containerized WhatsApp bot agents",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.5] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.4] - 2026-06-07
|
||||
|
||||
### Security
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Clawsec Scanner
|
||||
|
||||
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 static hook inspection for OpenClaw hooks.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-scanner -a openclaw -y
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-scanner
|
||||
version: 0.0.4
|
||||
version: 0.0.5
|
||||
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 static hook inspection for OpenClaw hooks.
|
||||
homepage: https://clawsec.prompt.security
|
||||
clawdis:
|
||||
@@ -20,6 +20,14 @@ Comprehensive security scanner for agent platforms that automates vulnerability
|
||||
- **Unified Reporting**: Consolidated vulnerability reports with severity classification and remediation guidance
|
||||
- **Continuous Monitoring**: OpenClaw hook integration for automated periodic scanning
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-scanner -a openclaw -y
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Multi-Engine Scanning
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-scanner",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"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 static hook inspection for OpenClaw hooks.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.10] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
All notable changes to the ClawSec Suite will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Clawsec Suite
|
||||
|
||||
ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: clawsec-suite
|
||||
version: 0.1.9
|
||||
version: 0.1.10
|
||||
description: ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.
|
||||
homepage: https://clawsec.prompt.security
|
||||
clawdis:
|
||||
@@ -11,6 +11,14 @@ clawdis:
|
||||
|
||||
# ClawSec Suite
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Required runtime: `node`, `npx`, `openclaw`, `curl`, `jq`, `shasum`, `openssl`, `unzip`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawsec-suite",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.10",
|
||||
"description": "ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.7] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
- Marked Clawtributor as a harness-neutral global skill for OpenClaw, NanoClaw, Hermes, and Picoclaw installer grouping.
|
||||
- Removed OpenClaw CLI as a declared runtime requirement because reporting is manual, approval-gated, and not tied to an OpenClaw command path.
|
||||
- Documented Vercel skills installer usage alongside the OpenClaw/ClawHub install path.
|
||||
- Moved local report/state guidance to `~/.clawsec/clawtributor/`.
|
||||
|
||||
## [0.0.6] - 2026-05-14
|
||||
|
||||
### Security
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
|
||||
Community incident reporting for AI agents.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawtributor -a openclaw -y
|
||||
```
|
||||
|
||||
Codex install is also supported:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Reporting is opt-in for every submission
|
||||
@@ -17,6 +31,14 @@ Community incident reporting for AI agents.
|
||||
|
||||
## Quick Install
|
||||
|
||||
Vercel skills installer:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
||||
```
|
||||
|
||||
OpenClaw/ClawHub:
|
||||
|
||||
```bash
|
||||
npx clawhub@latest install clawtributor
|
||||
```
|
||||
|
||||
@@ -1,23 +1,44 @@
|
||||
---
|
||||
name: clawtributor
|
||||
version: 0.0.6
|
||||
description: Community incident reporting for AI agents. Contribute to collective security by reporting threats.
|
||||
version: 0.0.7
|
||||
description: Harness-neutral community incident reporting for AI agents. Contribute to collective security by reporting threats.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"🤝","category":"security"}}
|
||||
platforms:
|
||||
- openclaw
|
||||
- nanoclaw
|
||||
- hermes
|
||||
- picoclaw
|
||||
metadata:
|
||||
global: true
|
||||
openclaw:
|
||||
emoji: "🤝"
|
||||
category: "security"
|
||||
clawdis:
|
||||
emoji: "🤝"
|
||||
requires:
|
||||
bins: [openclaw]
|
||||
---
|
||||
|
||||
# Clawtributor 🤝
|
||||
|
||||
Community incident reporting for AI agents. Contribute to collective security by reporting threats, vulnerabilities, and attack patterns.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawtributor -a openclaw -y
|
||||
```
|
||||
|
||||
Codex install is also supported:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Recommended install path: ClawHub registry (`npx clawhub@latest install clawtributor`)
|
||||
- Side effects: creates local report/state files under `~/.openclaw/`
|
||||
- Recommended install path: harness-native skills installer; use ClawHub for OpenClaw/ClawHub environments (`npx clawhub@latest install clawtributor`)
|
||||
- Side effects: creates local report/state files under `~/.clawsec/clawtributor/`
|
||||
- Network behavior: none unless the user explicitly approves manual submission
|
||||
- Trust model: reporting is opt-in for every submission; sanitize evidence before it leaves the host
|
||||
|
||||
@@ -27,7 +48,13 @@ Community incident reporting for AI agents. Contribute to collective security by
|
||||
|
||||
## Installation
|
||||
|
||||
Install from the registry:
|
||||
Install with your harness-native skills installer. For the Vercel skills installer:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
||||
```
|
||||
|
||||
For OpenClaw/ClawHub environments, install from the registry:
|
||||
|
||||
```bash
|
||||
npx clawhub@latest install clawtributor
|
||||
@@ -44,7 +71,6 @@ 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.
|
||||
@@ -53,7 +79,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="clawtributor"
|
||||
VERSION="0.0.6"
|
||||
VERSION="0.0.7"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
@@ -233,7 +259,7 @@ See [reporting.md](./reporting.md) for the full report format and submission gui
|
||||
|
||||
### Step 1: Prepare report locally
|
||||
|
||||
- Save the report JSON under `~/.openclaw/clawtributor-reports/`
|
||||
- Save the report JSON under `~/.clawsec/clawtributor/reports/`
|
||||
- Keep file permissions private (`chmod 600`)
|
||||
- Confirm the report is sanitized before sharing
|
||||
|
||||
@@ -284,7 +310,7 @@ DO NOT include:
|
||||
|
||||
## State Tracking
|
||||
|
||||
Track submitted reports in `~/.openclaw/clawtributor-state.json`.
|
||||
Track submitted reports in `~/.clawsec/clawtributor/state.json`.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
{
|
||||
"name": "clawtributor",
|
||||
"version": "0.0.6",
|
||||
"description": "Community incident reporting for AI agents. Contribute to collective security by reporting threats.",
|
||||
"version": "0.0.7",
|
||||
"description": "Harness-neutral community incident reporting for AI agents. Contribute to collective security by reporting threats.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"homepage": "https://clawsec.prompt.security",
|
||||
"platforms": [
|
||||
"openclaw",
|
||||
"nanoclaw",
|
||||
"hermes",
|
||||
"picoclaw"
|
||||
],
|
||||
"keywords": [
|
||||
"security",
|
||||
"reporting",
|
||||
"community",
|
||||
"agents",
|
||||
"ai",
|
||||
"global",
|
||||
"harness-neutral",
|
||||
"vulnerability",
|
||||
"contribution"
|
||||
],
|
||||
@@ -36,11 +44,6 @@
|
||||
"openclaw": {
|
||||
"emoji": "🤝",
|
||||
"category": "security",
|
||||
"requires": {
|
||||
"bins": [
|
||||
"openclaw"
|
||||
]
|
||||
},
|
||||
"execution": {
|
||||
"always": false,
|
||||
"persistence": "Stores local report/state files only; no recurring automation is created by default.",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.4] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.1.3] - 2026-05-24
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -4,6 +4,14 @@ Hermes-only attestation, advisory verification, and guarded verification workflo
|
||||
|
||||
Status: implemented (v0.1.0), Hermes-only.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill hermes-attestation-guardian -a hermes-agent -y
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
This skill now covers the full Hermes-side capability set expected from the clawsec-suite parity workstream:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: hermes-attestation-guardian
|
||||
version: 0.1.3
|
||||
version: 0.1.4
|
||||
description: Hermes-only runtime security attestation and drift detection skill for operator-managed Hermes infrastructure.
|
||||
homepage: https://clawsec.prompt.security
|
||||
hermes:
|
||||
@@ -15,6 +15,13 @@ IMPORTANT SCOPE:
|
||||
- This skill targets Hermes infrastructure only (CLI/Gateway/profile-managed deployments).
|
||||
- This skill is not an OpenClaw runtime hook package.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill hermes-attestation-guardian -a hermes-agent -y
|
||||
```
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
@@ -24,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="hermes-attestation-guardian"
|
||||
VERSION="0.1.3"
|
||||
VERSION="0.1.4"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hermes-attestation-guardian",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "Hermes-only runtime security attestation and drift detection skill. Generates deterministic posture artifacts, verifies integrity fail-closed, and classifies baseline drift severity.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta3] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
|
||||
@@ -4,6 +4,14 @@ Baseline skill for Hermes runtime traffic monitoring.
|
||||
|
||||
This package is intentionally a spec scaffold. Builders should add the Hermes-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill hermes-traffic-guardian -a hermes-agent -y
|
||||
```
|
||||
|
||||
## Intended Capability
|
||||
|
||||
- detect outbound secret exfiltration in Hermes HTTP/HTTPS traffic
|
||||
@@ -15,4 +23,3 @@ This package is intentionally a spec scaffold. Builders should add the Hermes-sp
|
||||
## Builder Notes
|
||||
|
||||
Keep runtime ownership in this skill. `hermes-attestation-guardian` should only attest this skill's state, config, and output fingerprints.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: hermes-traffic-guardian
|
||||
version: 0.0.1-beta2
|
||||
version: 0.0.1-beta3
|
||||
description: Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -15,6 +15,13 @@ hermes:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill hermes-traffic-guardian -a hermes-agent -y
|
||||
```
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
@@ -24,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="hermes-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
VERSION="0.0.1-beta3"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
@@ -145,4 +152,3 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
||||
- default blocking
|
||||
- sending traffic to external services
|
||||
- collecting full request/response bodies
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hermes-traffic-guardian",
|
||||
"version": "0.0.1-beta2",
|
||||
"version": "0.0.1-beta3",
|
||||
"description": "Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta3] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
|
||||
@@ -4,6 +4,14 @@ Baseline skill for NanoClaw runtime traffic monitoring.
|
||||
|
||||
This package is intentionally a spec scaffold. Builders should add the NanoClaw-specific host-service, IPC, and MCP implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill nanoclaw-traffic-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Intended Capability
|
||||
|
||||
- detect outbound secret exfiltration in NanoClaw host-managed traffic
|
||||
@@ -15,4 +23,3 @@ This package is intentionally a spec scaffold. Builders should add the NanoClaw-
|
||||
## Builder Notes
|
||||
|
||||
Follow the existing `clawsec-nanoclaw` pattern: host services own privileged operations, while MCP tools expose bounded requests and redacted responses.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: nanoclaw-traffic-guardian
|
||||
version: 0.0.1-beta2
|
||||
version: 0.0.1-beta3
|
||||
description: NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -14,6 +14,13 @@ nanoclaw:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill nanoclaw-traffic-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
@@ -23,7 +30,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="nanoclaw-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
VERSION="0.0.1-beta3"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
@@ -146,4 +153,3 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
||||
- default blocking
|
||||
- sending traffic to external services
|
||||
- exposing raw request/response bodies to the container
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nanoclaw-traffic-guardian",
|
||||
"version": "0.0.1-beta2",
|
||||
"version": "0.0.1-beta3",
|
||||
"description": "NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.7] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.1.6] - 2026-05-16
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill openclaw-audit-watchdog -a openclaw -y
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
The Audit Watchdog provides automated security monitoring for your OpenClaw agent deployments:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-audit-watchdog
|
||||
version: 0.1.6
|
||||
version: 0.1.7
|
||||
description: Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Runs deep audits, creates or updates a recurring cron job, and sends formatted reports to configured recipients.
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata:
|
||||
@@ -29,6 +29,14 @@ clawdis:
|
||||
|
||||
# Prompt Security Audit (openclaw)
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill openclaw-audit-watchdog -a openclaw -y
|
||||
```
|
||||
|
||||
## Installation Options
|
||||
|
||||
You can get openclaw-audit-watchdog in two ways:
|
||||
@@ -65,7 +73,6 @@ 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.
|
||||
@@ -74,7 +81,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="openclaw-audit-watchdog"
|
||||
VERSION="0.1.6"
|
||||
VERSION="0.1.7"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openclaw-audit-watchdog",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"description": "Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Creates or updates an unattended cron job and sends formatted reports to configured recipients.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta3] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
|
||||
@@ -4,6 +4,14 @@ Baseline skill for OpenClaw runtime traffic monitoring.
|
||||
|
||||
This package is intentionally a spec scaffold. Builders should add the OpenClaw-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill openclaw-traffic-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Intended Capability
|
||||
|
||||
- detect outbound secret exfiltration in agent HTTP/HTTPS traffic
|
||||
@@ -15,4 +23,3 @@ This package is intentionally a spec scaffold. Builders should add the OpenClaw-
|
||||
## Builder Notes
|
||||
|
||||
Use `SPEC.md` as the implementation contract. Keep runtime changes opt-in and scoped to the OpenClaw process being monitored.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-traffic-guardian
|
||||
version: 0.0.1-beta2
|
||||
version: 0.0.1-beta3
|
||||
description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -15,6 +15,13 @@ clawdis:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill openclaw-traffic-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
@@ -24,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="openclaw-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
VERSION="0.0.1-beta3"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
@@ -146,4 +153,3 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
||||
- default blocking
|
||||
- sending traffic to external services
|
||||
- collecting full request/response bodies
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openclaw-traffic-guardian",
|
||||
"version": "0.0.1-beta2",
|
||||
"version": "0.0.1-beta3",
|
||||
"description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.4] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.3] - 2026-05-24
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -6,6 +6,14 @@ Status: implemented (v0.0.1), Picoclaw-specific.
|
||||
|
||||
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill picoclaw-security-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Support matrix mapping
|
||||
|
||||
| Skill name | supported platform | security feed | config drift | agent posture-review lane | chain of supply verification |
|
||||
@@ -48,4 +56,3 @@ test/picoclaw_security_guardian_sandbox_regression.sh
|
||||
```
|
||||
|
||||
It uses Docker to publish the skill through a local ClawHub-compatible registry, installs it with Picoclaw's own `find_skills` / `install_skill` flow into an isolated Picoclaw workspace, confirms Picoclaw's skill loader can list/load it, then verifies the installed copy's profile, drift, advisory, and supply-chain paths.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: picoclaw-security-guardian
|
||||
version: 0.0.3
|
||||
version: 0.0.4
|
||||
description: Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -18,6 +18,13 @@ picoclaw:
|
||||
|
||||
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill picoclaw-security-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
@@ -27,7 +34,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="picoclaw-security-guardian"
|
||||
VERSION="0.0.3"
|
||||
VERSION="0.0.4"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picoclaw-security-guardian",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.3] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
|
||||
@@ -4,6 +4,14 @@ Picoclaw-only local posture-review findings package for ClawSec.
|
||||
|
||||
Status: implemented (v0.0.1), Picoclaw-specific.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill picoclaw-self-pen-testing -a openclaw -y
|
||||
```
|
||||
|
||||
## What it does
|
||||
|
||||
Given a generated Picoclaw posture profile, it emits severity-ranked findings and a summary count for local operator review.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: picoclaw-self-pen-testing
|
||||
version: 0.0.2
|
||||
version: 0.0.3
|
||||
description: Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -18,6 +18,13 @@ picoclaw:
|
||||
|
||||
Purpose: keep Picoclaw posture-review checks isolated from the broader guardian package so moderation-sensitive checks can be versioned/published independently.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill picoclaw-self-pen-testing -a openclaw -y
|
||||
```
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
@@ -27,7 +34,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="picoclaw-self-pen-testing"
|
||||
VERSION="0.0.2"
|
||||
VERSION="0.0.3"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picoclaw-self-pen-testing",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"description": "Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta3] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.1-beta2] - 2026-05-13
|
||||
|
||||
### Security
|
||||
|
||||
@@ -4,6 +4,14 @@ Baseline skill for Picoclaw runtime traffic monitoring.
|
||||
|
||||
This package is intentionally a spec scaffold. Builders should add the Picoclaw-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill picoclaw-traffic-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Intended Capability
|
||||
|
||||
- detect outbound secret exfiltration in Picoclaw gateway HTTP/HTTPS traffic
|
||||
@@ -15,4 +23,3 @@ This package is intentionally a spec scaffold. Builders should add the Picoclaw-
|
||||
## Builder Notes
|
||||
|
||||
Keep runtime ownership in this skill. `picoclaw-security-guardian` should only profile and drift-check this skill's state, config, and output fingerprints.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: picoclaw-traffic-guardian
|
||||
version: 0.0.1-beta2
|
||||
version: 0.0.1-beta3
|
||||
description: Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
@@ -15,6 +15,13 @@ picoclaw:
|
||||
|
||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill picoclaw-traffic-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
@@ -24,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="picoclaw-traffic-guardian"
|
||||
VERSION="0.0.1-beta2"
|
||||
VERSION="0.0.1-beta3"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
@@ -145,4 +152,3 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
||||
- default blocking
|
||||
- sending traffic to external services
|
||||
- collecting full request/response bodies
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picoclaw-traffic-guardian",
|
||||
"version": "0.0.1-beta2",
|
||||
"version": "0.0.1-beta3",
|
||||
"description": "Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.7] - 2026-06-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
||||
|
||||
## [0.0.6] - 2026-05-14
|
||||
|
||||
### Security
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
A small, dependency-free integrity guard for OpenClaw agent workspaces.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill soul-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Required runtime: `python3`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: soul-guardian
|
||||
version: 0.0.6
|
||||
version: 0.0.7
|
||||
description: Drift detection + baseline integrity guard for agent workspace files with automatic alerting support
|
||||
homepage: https://clawsec.prompt.security
|
||||
metadata: {"openclaw":{"emoji":"👻","category":"security"}}
|
||||
@@ -14,6 +14,14 @@ clawdis:
|
||||
|
||||
Protects your agent's core files (SOUL.md, AGENTS.md, etc.) from unauthorized changes with automatic detection, restoration, and **user alerting**.
|
||||
|
||||
## Vercel Skills Installation
|
||||
|
||||
Install with the Vercel Skills CLI for this harness:
|
||||
|
||||
```bash
|
||||
npx skills add prompt-security/clawsec --skill soul-guardian -a openclaw -y
|
||||
```
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Required runtime: `python3`
|
||||
@@ -22,7 +30,6 @@ Protects your agent's core files (SOUL.md, AGENTS.md, etc.) from unauthorized ch
|
||||
- Network behavior: none by default
|
||||
- Trust model: any scheduling is opt-in, but restore mode intentionally overwrites drifted files
|
||||
|
||||
|
||||
## Release Artifact Verification
|
||||
|
||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||
@@ -31,7 +38,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_NAME="soul-guardian"
|
||||
VERSION="0.0.6"
|
||||
VERSION="0.0.7"
|
||||
REPO="prompt-security/clawsec"
|
||||
TAG="${SKILL_NAME}-v${VERSION}"
|
||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soul-guardian",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"description": "Drift detection and baseline integrity guard for agent workspace prompt files. Auto-restore critical files with tamper-evident audit logging.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
Reference in New Issue
Block a user