mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fd3059271 | |||
| 1b676fd42c | |||
| 59d54ed778 | |||
| d99f324f72 | |||
| 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -53,9 +53,21 @@ jobs:
|
||||
|
||||
- name: Collect traffic
|
||||
env:
|
||||
GH_TRAFFIC_TOKEN: ${{ secrets.TRAFFIC_ARCHIVE_TOKEN || github.token }}
|
||||
# Traffic endpoints reject the Actions GITHUB_TOKEN ("Resource not
|
||||
# accessible by integration") — a PAT from a user with push access
|
||||
# is required: classic with repo scope, or fine-grained with read
|
||||
# access to Administration on this repository.
|
||||
GH_TRAFFIC_TOKEN: ${{ secrets.TRAFFIC_ARCHIVE_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: node scripts/archive-github-traffic.mjs --archive-dir "${TRAFFIC_ARCHIVE_DIR}"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${GH_TRAFFIC_TOKEN}" ]; then
|
||||
echo "::error::No traffic-capable token configured. Set the TRAFFIC_ARCHIVE_TOKEN secret to a PAT with push access (classic: repo scope; fine-grained: Administration read)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node scripts/archive-github-traffic.mjs --archive-dir "${TRAFFIC_ARCHIVE_DIR}"
|
||||
|
||||
- name: Commit archive
|
||||
run: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -144,14 +152,6 @@ jobs:
|
||||
md_version_changed=true
|
||||
fi
|
||||
|
||||
if [ "${json_version_changed}" != "true" ] && [ "${md_version_changed}" != "true" ]; then
|
||||
echo "::error file=${skill_dir}::Changed skill package has no version bump. Update skill.json and SKILL.md versions and add CHANGELOG.md release notes."
|
||||
failures=$((failures + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Version bump detected for ${skill_dir} (skill.json changed: ${json_version_changed}, SKILL.md changed: ${md_version_changed})"
|
||||
|
||||
if [ ! -f "${json_path}" ]; then
|
||||
echo "::error file=${json_path}::Missing skill.json after version bump."
|
||||
failures=$((failures + 1))
|
||||
@@ -182,6 +182,20 @@ jobs:
|
||||
continue
|
||||
fi
|
||||
|
||||
skill_release_name="$(basename "${skill_dir}")"
|
||||
release_tag="${skill_release_name}-v${head_json_version}"
|
||||
if [ "${json_version_changed}" != "true" ] && [ "${md_version_changed}" != "true" ]; then
|
||||
if git show-ref --verify --quiet "refs/tags/${release_tag}"; then
|
||||
echo "::error file=${skill_dir}::Changed skill package has no version bump and release tag ${release_tag} already exists. Update skill.json and SKILL.md versions and add CHANGELOG.md release notes."
|
||||
failures=$((failures + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "No version bump detected for ${skill_dir}, but release tag ${release_tag} does not exist; treating ${head_json_version} as unreleased."
|
||||
else
|
||||
echo "Version bump detected for ${skill_dir} (skill.json changed: ${json_version_changed}, SKILL.md changed: ${md_version_changed})"
|
||||
fi
|
||||
|
||||
echo "Version parity OK for ${skill_dir}: ${head_json_version}"
|
||||
|
||||
changelog_path="${skill_dir}/CHANGELOG.md"
|
||||
@@ -223,11 +237,17 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ "${failures}" -gt 0 ]; then
|
||||
echo "::error::Found ${failures} skill metadata/release-notes issue(s) across ${checked_skills} bumped skill(s)."
|
||||
echo "::error::Found ${failures} skill metadata/release-notes issue(s) across ${checked_skills} changed skill(s)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Validated ${checked_skills} bumped skill(s): version parity and changelog release notes are present."
|
||||
echo "Validated ${checked_skills} changed 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'
|
||||
@@ -241,6 +261,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 +434,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 +701,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 "${inner_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 +783,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
|
||||
@@ -664,6 +845,7 @@ jobs:
|
||||
publishable: ${{ steps.publishable.outputs.publishable }}
|
||||
openclaw_skill: ${{ steps.publishable.outputs.openclaw_skill }}
|
||||
publish_clawhub: ${{ steps.publishable.outputs.publish_clawhub }}
|
||||
clawhub_slug: ${{ steps.publishable.outputs.clawhub_slug }}
|
||||
steps:
|
||||
- name: Parse tag
|
||||
id: parse
|
||||
@@ -761,16 +943,32 @@ jobs:
|
||||
PUBLISH_CLAWHUB=true
|
||||
fi
|
||||
|
||||
CLAWHUB_SLUG=$(node scripts/ci/resolve_clawhub_slug.mjs "$SKILL_PATH")
|
||||
|
||||
echo "internal=${INTERNAL}" >> $GITHUB_OUTPUT
|
||||
echo "openclaw_skill=${OPENCLAW_SKILL}" >> $GITHUB_OUTPUT
|
||||
echo "publish_clawhub=${PUBLISH_CLAWHUB}" >> $GITHUB_OUTPUT
|
||||
echo "publishable=${PUBLISHABLE}" >> $GITHUB_OUTPUT
|
||||
echo "clawhub_slug=${CLAWHUB_SLUG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
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 +1068,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 +1212,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 "$INNER_DIR" "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
|
||||
@@ -1055,6 +1322,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
|
||||
CLAWHUB_SLUG="${{ steps.publishable.outputs.clawhub_slug }}"
|
||||
VERSION="${{ steps.parse.outputs.version }}"
|
||||
REPO="${{ github.repository }}"
|
||||
TAG="${{ github.ref_name }}"
|
||||
@@ -1062,13 +1330,33 @@ 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
|
||||
|
||||
**Via ClawHub (recommended):**
|
||||
\`\`\`bash
|
||||
npx clawhub@latest install ${SKILL_NAME}
|
||||
npx clawhub@latest install ${CLAWHUB_SLUG}
|
||||
\`\`\`
|
||||
|
||||
**If you already have \`clawsec-suite\` installed:**
|
||||
@@ -1115,38 +1403,63 @@ jobs:
|
||||
echo "INSTALL_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Prepare GitHub release body
|
||||
env:
|
||||
SKILL_NAME: ${{ steps.parse.outputs.skill_name }}
|
||||
VERSION: ${{ steps.parse.outputs.version }}
|
||||
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
||||
QUICK_INSTALL: ${{ steps.install.outputs.quick_install }}
|
||||
REPO: ${{ github.repository }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node -e '
|
||||
const { readFileSync, writeFileSync } = require("node:fs");
|
||||
const bodyPath = `${process.env.RUNNER_TEMP}/skill-release-body.md`;
|
||||
const report = readFileSync("release-assets/skillspector-report.md", "utf8").trimEnd();
|
||||
const body = [
|
||||
`## ${process.env.SKILL_NAME} ${process.env.VERSION}`,
|
||||
"",
|
||||
process.env.CHANGELOG || "",
|
||||
"",
|
||||
process.env.QUICK_INSTALL || "",
|
||||
"",
|
||||
"### SkillSpector Security Report",
|
||||
"",
|
||||
report,
|
||||
"",
|
||||
`Download the generated release-payload scan: [skillspector-report.md](https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/skillspector-report.md)`,
|
||||
"",
|
||||
"### Verification",
|
||||
"",
|
||||
"`checksums.json` is cryptographically signed (`checksums.sig`) using the ClawSec CI signing key.",
|
||||
"Verify the signature first, then trust hashes from `checksums.json`:",
|
||||
"```bash",
|
||||
`curl -sLO https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/checksums.json`,
|
||||
`curl -sLO https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/checksums.sig`,
|
||||
`curl -sLO https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/signing-public.pem`,
|
||||
"openssl base64 -d -A -in checksums.sig -out checksums.sig.bin",
|
||||
"openssl pkeyutl -verify -rawin -pubin -inkey signing-public.pem -sigfile checksums.sig.bin -in checksums.json",
|
||||
"```",
|
||||
"",
|
||||
"### Files",
|
||||
"",
|
||||
"See `checksums.json` for the complete file manifest with SHA256 hashes.",
|
||||
"The zip archive preserves the full directory structure of the skill.",
|
||||
"",
|
||||
"---",
|
||||
"*Released by ClawSec skill distribution pipeline*",
|
||||
].join("\n");
|
||||
writeFileSync(bodyPath, `${body}\n`);
|
||||
'
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
name: "${{ steps.parse.outputs.skill_name }} ${{ steps.parse.outputs.version }}"
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: release-assets/*
|
||||
body: |
|
||||
## ${{ steps.parse.outputs.skill_name }} ${{ steps.parse.outputs.version }}
|
||||
|
||||
${{ steps.changelog.outputs.changelog }}
|
||||
|
||||
${{ steps.install.outputs.quick_install }}
|
||||
|
||||
### Verification
|
||||
|
||||
`checksums.json` is cryptographically signed (`checksums.sig`) using the ClawSec CI signing key.
|
||||
Verify the signature first, then trust hashes from `checksums.json`:
|
||||
```bash
|
||||
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/checksums.json
|
||||
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/checksums.sig
|
||||
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/signing-public.pem
|
||||
openssl base64 -d -A -in checksums.sig -out checksums.sig.bin
|
||||
openssl pkeyutl -verify -rawin -pubin -inkey signing-public.pem -sigfile checksums.sig.bin -in checksums.json
|
||||
```
|
||||
|
||||
### Files
|
||||
|
||||
See `checksums.json` for the complete file manifest with SHA256 hashes.
|
||||
The zip archive preserves the full directory structure of the skill.
|
||||
|
||||
---
|
||||
*Released by ClawSec skill distribution pipeline*
|
||||
body_path: ${{ runner.temp }}/skill-release-body.md
|
||||
draft: false
|
||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
|
||||
env:
|
||||
@@ -1284,23 +1597,24 @@ jobs:
|
||||
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
|
||||
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
||||
SKILL_NAME="${{ needs.release-tag.outputs.skill_name }}"
|
||||
CLAWHUB_SLUG="${{ needs.release-tag.outputs.clawhub_slug }}"
|
||||
VERSION="${{ needs.release-tag.outputs.version }}"
|
||||
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
|
||||
|
||||
set +e
|
||||
CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
||||
clawhub inspect "$SKILL_NAME" --version "$VERSION" --json \
|
||||
clawhub inspect "$CLAWHUB_SLUG" --version "$VERSION" --json \
|
||||
> /tmp/clawhub-existing-version.json 2> /tmp/clawhub-existing-version.err
|
||||
STATUS=$?
|
||||
set -e
|
||||
|
||||
if [ "$STATUS" -eq 0 ]; then
|
||||
echo "::error::ClawHub already contains ${SKILL_NAME}@${VERSION}. Bump the version before tagging."
|
||||
echo "::error::ClawHub already contains ${CLAWHUB_SLUG}@${VERSION}. Bump the version before tagging."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Eqi "Version not found|Skill not found" /tmp/clawhub-existing-version.err; then
|
||||
echo "No existing ${SKILL_NAME}@${VERSION} detected in ClawHub. Proceeding."
|
||||
echo "No existing ${CLAWHUB_SLUG}@${VERSION} detected in ClawHub. Proceeding."
|
||||
else
|
||||
echo "::error::Failed to verify ClawHub version precondition."
|
||||
cat /tmp/clawhub-existing-version.err
|
||||
@@ -1315,6 +1629,7 @@ jobs:
|
||||
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
||||
SKILL_PATH="${{ needs.release-tag.outputs.skill_path }}"
|
||||
SKILL_NAME="${{ needs.release-tag.outputs.skill_name }}"
|
||||
CLAWHUB_SLUG="${{ needs.release-tag.outputs.clawhub_slug }}"
|
||||
VERSION="${{ needs.release-tag.outputs.version }}"
|
||||
NAME=$(jq -r '.name' "$SKILL_PATH/skill.json")
|
||||
CHANGELOG="Release ${VERSION} via CI"
|
||||
@@ -1323,7 +1638,7 @@ jobs:
|
||||
|
||||
if ! CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
||||
clawhub publish "$SKILL_PATH" \
|
||||
--slug "$SKILL_NAME" \
|
||||
--slug "$CLAWHUB_SLUG" \
|
||||
--name "$NAME" \
|
||||
--version "$VERSION" \
|
||||
--changelog "$CHANGELOG" \
|
||||
@@ -1333,7 +1648,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Successfully published $SKILL_NAME@$VERSION to ClawHub"
|
||||
echo "✓ Successfully published $SKILL_NAME@$VERSION to ClawHub as $CLAWHUB_SLUG"
|
||||
|
||||
republish-clawhub:
|
||||
# Manual workflow to republish a specific tag to ClawHub
|
||||
@@ -1360,6 +1675,12 @@ jobs:
|
||||
|
||||
echo "Parsed tag: skill=${SKILL_NAME}, version=${VERSION}"
|
||||
|
||||
- name: Checkout workflow helpers
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare ClawHub slug helper
|
||||
run: cp scripts/ci/resolve_clawhub_slug.mjs "$RUNNER_TEMP/resolve_clawhub_slug.mjs"
|
||||
|
||||
- name: Checkout tag
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -1389,6 +1710,8 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CLAWHUB_SLUG=$(node "$RUNNER_TEMP/resolve_clawhub_slug.mjs" "$SKILL_PATH")
|
||||
echo "clawhub_slug=${CLAWHUB_SLUG}" >> $GITHUB_OUTPUT
|
||||
echo "Skill is publishable to ClawHub"
|
||||
|
||||
- name: Setup Node
|
||||
@@ -1396,6 +1719,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}
|
||||
|
||||
@@ -1464,18 +1790,19 @@ jobs:
|
||||
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
||||
SKILL_PATH="${{ steps.parse.outputs.skill_path }}"
|
||||
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
|
||||
CLAWHUB_SLUG="${{ steps.publishable.outputs.clawhub_slug }}"
|
||||
VERSION="${{ steps.parse.outputs.version }}"
|
||||
NAME=$(jq -r '.name' "$SKILL_PATH/skill.json")
|
||||
CHANGELOG="Manual republish of ${VERSION} via workflow_dispatch"
|
||||
|
||||
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
|
||||
|
||||
echo "Publishing $SKILL_NAME@$VERSION to ClawHub..."
|
||||
echo "Publishing $SKILL_NAME@$VERSION to ClawHub as $CLAWHUB_SLUG..."
|
||||
|
||||
# Publish with idempotent retry handling
|
||||
if ! CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
||||
clawhub publish "$SKILL_PATH" \
|
||||
--slug "$SKILL_NAME" \
|
||||
--slug "$CLAWHUB_SLUG" \
|
||||
--name "$NAME" \
|
||||
--version "$VERSION" \
|
||||
--changelog "$CHANGELOG" \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -321,7 +321,14 @@ const fetchJson = async ({ repo, token, pathname, fetchImpl }) => {
|
||||
if (!response.ok) {
|
||||
const body = await response.text().catch(() => '');
|
||||
const suffix = body ? ` ${body.slice(0, 500)}` : '';
|
||||
throw new Error(`GitHub traffic API request failed for ${repo}: ${url.pathname}${url.search} returned ${response.status}.${suffix}`);
|
||||
const lacksPushAccess = response.status === 403
|
||||
&& /resource not accessible|must have push access/i.test(body);
|
||||
const hint = lacksPushAccess
|
||||
? ' Traffic endpoints require a token with push access to the repository; the Actions GITHUB_TOKEN is always rejected. Use a classic PAT with the repo scope or a fine-grained PAT with read access to Administration.'
|
||||
: response.status === 401
|
||||
? ' The token was rejected as invalid — it may be expired or revoked. Rotate the TRAFFIC_ARCHIVE_TOKEN secret.'
|
||||
: '';
|
||||
throw new Error(`GitHub traffic API request failed for ${repo}: ${url.pathname}${url.search} returned ${response.status}.${suffix}${hint}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
#!/usr/bin/env node
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { installAgentForSkill, PLATFORM_KEYS } from "./skill_platforms.mjs";
|
||||
|
||||
const KNOWN_AGENT_TYPES = new Set(["codex", "hermes-agent", "openclaw", "universal"]);
|
||||
|
||||
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 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, KNOWN_AGENT_TYPES);
|
||||
|
||||
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,79 @@
|
||||
#!/usr/bin/env node
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { collectDeclaredPlatforms, PLATFORM_KEYS } from "./skill_platforms.mjs";
|
||||
|
||||
const EXPLICIT_SLUGS = new Map([
|
||||
["openclaw-traffic-guardian", "clawsec-openclaw-traffic-guardian"],
|
||||
["openclaw-audit-watchdog", "clawsec-openclaw-audit-watchdog"],
|
||||
["soul-guardian", "clawsec-openclaw-soul-guardian"],
|
||||
["hermes-attestation-guardian", "clawsec-hermes-attestation-guardian"],
|
||||
["hermes-traffic-guardian", "clawsec-hermes-traffic-guardian"],
|
||||
["nanoclaw-traffic-guardian", "clawsec-nanoclaw-traffic-guardian"],
|
||||
["picoclaw-security-guardian", "clawsec-picoclaw-security-guardian"],
|
||||
["picoclaw-self-pen-testing", "clawsec-picoclaw-self-pen-testing"],
|
||||
["picoclaw-traffic-guardian", "clawsec-picoclaw-traffic-guardian"],
|
||||
["clawtributor", "clawsec-clawtributor"],
|
||||
]);
|
||||
|
||||
function usage() {
|
||||
return [
|
||||
"Usage: node scripts/ci/resolve_clawhub_slug.mjs <skill-dir-or-name>",
|
||||
"",
|
||||
"Prints the ClawHub slug for a skill without changing the GitHub release tag or skill package name.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function loadSkill(input) {
|
||||
const skillJsonPath = existsSync(path.join(input, "skill.json")) ? path.join(input, "skill.json") : null;
|
||||
if (!skillJsonPath) {
|
||||
return { name: input, platforms: [] };
|
||||
}
|
||||
|
||||
const skill = JSON.parse(readFileSync(skillJsonPath, "utf8"));
|
||||
if (!skill.name || typeof skill.name !== "string") {
|
||||
throw new Error(`${skillJsonPath} missing string field: name`);
|
||||
}
|
||||
|
||||
return { name: skill.name, platforms: collectDeclaredPlatforms(skill) };
|
||||
}
|
||||
|
||||
export function resolveClawHubSlug({ name, platforms = [] }) {
|
||||
if (!/^[a-z0-9-]+$/.test(name)) {
|
||||
throw new Error(`Invalid skill name for ClawHub slug mapping: ${name}`);
|
||||
}
|
||||
|
||||
if (name.startsWith("clawsec-")) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (EXPLICIT_SLUGS.has(name)) {
|
||||
return EXPLICIT_SLUGS.get(name);
|
||||
}
|
||||
|
||||
if (PLATFORM_KEYS.some((platform) => name.startsWith(`${platform}-`))) {
|
||||
return `clawsec-${name}`;
|
||||
}
|
||||
|
||||
const declaredPlatforms = collectDeclaredPlatforms({ platforms });
|
||||
if (declaredPlatforms.length === 1 && PLATFORM_KEYS.includes(declaredPlatforms[0])) {
|
||||
return `clawsec-${declaredPlatforms[0]}-${name}`;
|
||||
}
|
||||
|
||||
return `clawsec-${name}`;
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
const input = process.argv[2];
|
||||
if (!input || input === "--help" || input === "-h") {
|
||||
console.log(usage());
|
||||
process.exit(input ? 0 : 1);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(resolveClawHubSlug(loadSkill(input)));
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
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: innerDir,
|
||||
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,52 @@
|
||||
export const PLATFORM_KEYS = Object.freeze(["openclaw", "nanoclaw", "hermes", "picoclaw"]);
|
||||
|
||||
const PLATFORM_AGENT_ALIASES = new Map([["hermes", "hermes-agent"]]);
|
||||
|
||||
function asStringArray(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim());
|
||||
}
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return [value.trim()];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function collectDeclaredPlatforms(skill) {
|
||||
const platforms = new Set([
|
||||
...asStringArray(skill.platform),
|
||||
...asStringArray(skill.platforms),
|
||||
]);
|
||||
|
||||
for (const key of PLATFORM_KEYS) {
|
||||
if (skill[key] && typeof skill[key] === "object") {
|
||||
platforms.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return [...platforms];
|
||||
}
|
||||
|
||||
export function installAgentForSkill(skill, agentTypes, fallback = "openclaw") {
|
||||
const platforms = collectDeclaredPlatforms(skill);
|
||||
if (platforms.length === 0) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const matchedAgents = new Set();
|
||||
let allPlatformsMatched = true;
|
||||
for (const platform of platforms) {
|
||||
const candidate = PLATFORM_AGENT_ALIASES.get(platform) || platform;
|
||||
if (agentTypes.has(candidate)) {
|
||||
matchedAgents.add(candidate);
|
||||
} else {
|
||||
allPlatformsMatched = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPlatformsMatched && matchedAgents.size === 1) {
|
||||
return [...matchedAgents][0];
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
#!/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";
|
||||
import { installAgentForSkill } from "./skill_platforms.mjs";
|
||||
|
||||
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"];
|
||||
|
||||
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 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 = installAgentForSkill(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);
|
||||
});
|
||||
@@ -76,6 +76,40 @@ test('fetchGitHubTraffic requests the daily GitHub traffic endpoints with auth',
|
||||
assert.deepEqual(snapshot.clones.clones, responses[`/repos/${TEST_REPOSITORY}/traffic/clones?per=day`].clones);
|
||||
});
|
||||
|
||||
test('fetchGitHubTraffic explains traffic token requirements on 403', async () => {
|
||||
const fetchImpl = async () => new globalThis.Response(
|
||||
JSON.stringify({ message: 'Resource not accessible by integration' }),
|
||||
{ status: 403 },
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
fetchGitHubTraffic({
|
||||
repo: TEST_REPOSITORY,
|
||||
token: 'installation-token',
|
||||
capturedAt,
|
||||
fetchImpl,
|
||||
}),
|
||||
/returned 403\..*push access/,
|
||||
);
|
||||
});
|
||||
|
||||
test('fetchGitHubTraffic flags invalid tokens on 401', async () => {
|
||||
const fetchImpl = async () => new globalThis.Response(
|
||||
JSON.stringify({ message: 'Bad credentials' }),
|
||||
{ status: 401 },
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
fetchGitHubTraffic({
|
||||
repo: TEST_REPOSITORY,
|
||||
token: 'expired-token',
|
||||
capturedAt,
|
||||
fetchImpl,
|
||||
}),
|
||||
/returned 401\..*expired or revoked/,
|
||||
);
|
||||
});
|
||||
|
||||
test('mergeTrafficArchive upserts daily views and clones without double-counting overlapping windows', () => {
|
||||
const archive = mergeTrafficArchive(
|
||||
{
|
||||
@@ -232,7 +266,8 @@ test('traffic archive workflow uses a daily schedule and a dedicated archive bra
|
||||
|
||||
assert.match(workflow, /cron:\s+'17 3 \* \* \*'/);
|
||||
assert.match(workflow, /TRAFFIC_ARCHIVE_BRANCH:\s+traffic-archive/);
|
||||
assert.match(workflow, /TRAFFIC_ARCHIVE_TOKEN/);
|
||||
assert.match(workflow, /GH_TRAFFIC_TOKEN:\s*\$\{\{\s*secrets\.TRAFFIC_ARCHIVE_TOKEN\b/);
|
||||
assert.doesNotMatch(workflow, /GH_TRAFFIC_TOKEN:[^\n]*github\.token/);
|
||||
assert.match(workflow, /node scripts\/archive-github-traffic\.mjs/);
|
||||
assert.match(workflow, /git add traffic\/archive\.json traffic\/summary\.json/);
|
||||
assert.match(workflow, /git rm --ignore-unmatch traffic\/README\.md/);
|
||||
|
||||
@@ -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,45 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveClawHubSlug } from "./ci/resolve_clawhub_slug.mjs";
|
||||
import { collectDeclaredPlatforms, installAgentForSkill } from "./ci/skill_platforms.mjs";
|
||||
|
||||
const cases = [
|
||||
["openclaw-traffic-guardian", ["openclaw"], "clawsec-openclaw-traffic-guardian"],
|
||||
["openclaw-audit-watchdog", ["openclaw"], "clawsec-openclaw-audit-watchdog"],
|
||||
["soul-guardian", ["openclaw"], "clawsec-openclaw-soul-guardian"],
|
||||
["hermes-attestation-guardian", ["hermes"], "clawsec-hermes-attestation-guardian"],
|
||||
["hermes-traffic-guardian", ["hermes"], "clawsec-hermes-traffic-guardian"],
|
||||
["nanoclaw-traffic-guardian", ["nanoclaw"], "clawsec-nanoclaw-traffic-guardian"],
|
||||
["picoclaw-security-guardian", ["picoclaw"], "clawsec-picoclaw-security-guardian"],
|
||||
["picoclaw-self-pen-testing", ["picoclaw"], "clawsec-picoclaw-self-pen-testing"],
|
||||
["picoclaw-traffic-guardian", ["picoclaw"], "clawsec-picoclaw-traffic-guardian"],
|
||||
["clawtributor", ["openclaw", "nanoclaw", "hermes", "picoclaw"], "clawsec-clawtributor"],
|
||||
["clawsec-feed", ["openclaw"], "clawsec-feed"],
|
||||
["clawsec-suite", ["openclaw"], "clawsec-suite"],
|
||||
];
|
||||
|
||||
for (const [name, platforms, expected] of cases) {
|
||||
assert.equal(resolveClawHubSlug({ name, platforms }), expected, `${name} should map to ${expected}`);
|
||||
assert.equal(resolveClawHubSlug({ name }), expected, `${name} should map to ${expected} without metadata`);
|
||||
}
|
||||
|
||||
assert.throws(
|
||||
() => resolveClawHubSlug({ name: "../openclaw-traffic-guardian", platforms: ["openclaw"] }),
|
||||
/Invalid skill name/,
|
||||
"unsafe skill names must be rejected",
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
collectDeclaredPlatforms({
|
||||
platform: "openclaw",
|
||||
platforms: ["hermes", "openclaw", ""],
|
||||
picoclaw: { requires: {} },
|
||||
}),
|
||||
["openclaw", "hermes", "picoclaw"],
|
||||
"declared platform parsing should combine legacy fields, arrays, and platform metadata keys",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
installAgentForSkill({ platform: "hermes" }, new Set(["codex", "hermes-agent", "openclaw"])),
|
||||
"hermes-agent",
|
||||
"install agent selection should reuse platform aliases",
|
||||
);
|
||||
@@ -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\/\*\*'/,
|
||||
@@ -19,11 +37,223 @@ assert.match(
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/No version bump detected for \$\{skill_dir\}; skipping\./,
|
||||
'Changed skill directories without a version bump must fail validation instead of being skipped',
|
||||
'Changed skill directories without a version bump must not be skipped without release-tag validation',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/::error file=\$\{skill_dir\}::Changed skill package has no version bump\./,
|
||||
'Skill release validation must emit an explicit missing-version-bump error',
|
||||
/skill_release_name="\$\(basename "\$\{skill_dir\}"\)"/,
|
||||
'Skill release validation must derive the release tag prefix from the skill package directory',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/release_tag="\$\{skill_release_name\}-v\$\{head_json_version\}"/,
|
||||
'Skill release validation must use the skill package directory name for release tag checks',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/release_tag="\$\{head_skill_name\}-v\$\{head_json_version\}"/,
|
||||
'Skill release validation must not use skill.json name for release tag checks because release tags resolve to skill directories',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/git show-ref --verify --quiet "refs\/tags\/\$\{release_tag\}"/,
|
||||
'Skill release validation must check whether the current skill version has already been tagged',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/No version bump detected for \$\{skill_dir\}, but release tag \$\{release_tag\} does not exist; treating \$\{head_json_version\} as unreleased\./,
|
||||
'Skill release validation must allow edits to an unchanged version when that release tag does not exist yet',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/::error file=\$\{skill_dir\}::Changed skill package has no version bump and release tag \$\{release_tag\} already exists\./,
|
||||
'Skill release validation must still fail unchanged versions after their release tag exists',
|
||||
);
|
||||
|
||||
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,
|
||||
/### SkillSpector Security Report[\s\S]*\[skillspector-report\.md\]\(https:\/\/github\.com\/\$\{process\.env\.REPO\}\/releases\/download\/\$\{process\.env\.TAG\}\/skillspector-report\.md\)/,
|
||||
'GitHub release notes must include a direct SkillSpector report link',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/readFileSync\("release-assets\/skillspector-report\.md", "utf8"\)/,
|
||||
'GitHub release notes must load the generated SkillSpector report content into the release body file',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/body_path: \$\{\{ runner\.temp \}\}\/skill-release-body\.md/,
|
||||
'GitHub release creation must use body_path for the generated release body file',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/SKILLSPECTOR_REPORT_EOF|\$\{\{ steps\.skillspector_report\.outputs\.body \}\}|cat release-assets\/skillspector-report\.md[\s\S]*>> "\$GITHUB_OUTPUT"/,
|
||||
'SkillSpector report content must not be sent through GitHub Actions step outputs',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/generate_skillspector_report "\$\{inner_dir\}" "\$\{out_assets\}\/skillspector-report\.md"/,
|
||||
'PR dry-run SkillSpector scan must target the staged release payload, not the source skill directory',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/generate_skillspector_report "\$\{skill_dir\}" "\$\{out_assets\}\/skillspector-report\.md"/,
|
||||
'PR dry-run SkillSpector scan must not include source-only test directories',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/generate_skillspector_report "\$INNER_DIR" "release-assets\/skillspector-report\.md"/,
|
||||
'Tag release SkillSpector scan must target the staged release payload, not the source skill directory',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/generate_skillspector_report "\$SKILL_PATH" "release-assets\/skillspector-report\.md"/,
|
||||
'Tag release SkillSpector scan must not include source-only test directories',
|
||||
);
|
||||
|
||||
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',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/clawhub_slug: \$\{\{ steps\.publishable\.outputs\.clawhub_slug \}\}/,
|
||||
'Skill release workflow must expose the resolved ClawHub slug from release-tag outputs',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/CLAWHUB_SLUG=\$\(node scripts\/ci\/resolve_clawhub_slug\.mjs "\$SKILL_PATH"\)/,
|
||||
'Skill release workflow must resolve the ClawHub slug from the skill package path',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/cp scripts\/ci\/resolve_clawhub_slug\.mjs "\$RUNNER_TEMP\/resolve_clawhub_slug\.mjs"/,
|
||||
'Manual ClawHub republish must preserve the current slug helper before checking out an older release tag',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/CLAWHUB_SLUG=\$\(node "\$RUNNER_TEMP\/resolve_clawhub_slug\.mjs" "\$SKILL_PATH"\)/,
|
||||
'Manual ClawHub republish must resolve slugs with the preserved helper against the checked-out tag metadata',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/npx clawhub@latest install \$\{CLAWHUB_SLUG\}/,
|
||||
'GitHub release quick install instructions must use the resolved ClawHub slug',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/clawhub inspect "\$CLAWHUB_SLUG" --version "\$VERSION" --json/,
|
||||
'Duplicate ClawHub version guard must inspect the resolved ClawHub slug',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/--slug "\$CLAWHUB_SLUG"/,
|
||||
'ClawHub publish must use the resolved ClawHub slug',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/clawhub inspect "\$SKILL_NAME" --version "\$VERSION" --json/,
|
||||
'Duplicate ClawHub version guard must not inspect the raw skill package name',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
workflow,
|
||||
/--slug "\$SKILL_NAME"/,
|
||||
'ClawHub publish must not use the raw skill package name as the ClawHub slug',
|
||||
);
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
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 { readdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const scanIndex = process.argv.indexOf("scan");
|
||||
if (scanIndex === -1 || !process.argv[scanIndex + 1]) {
|
||||
console.error("missing scan target");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
function containsTestDirectory(dir) {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
const lowerName = entry.name.toLowerCase();
|
||||
if (lowerName === "test" || lowerName === "tests") {
|
||||
return true;
|
||||
}
|
||||
if (containsTestDirectory(path.join(dir, entry.name))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const scanTarget = process.argv[scanIndex + 1];
|
||||
if (containsTestDirectory(scanTarget)) {
|
||||
console.error("SkillSpector test fixture must scan the staged release payload, not source test directories.");
|
||||
process.exit(42);
|
||||
}
|
||||
|
||||
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,15 @@
|
||||
# Changelog
|
||||
|
||||
## [0.0.1-beta3] - 2026-06-10
|
||||
|
||||
### Security
|
||||
- Added the `POLICY_REVIEW` scope for approval-sensitive social-account mutation requests, contributed by @kriptoburak.
|
||||
- Defined required JSONL metadata for social-account mutation findings, including source type, mutation category, approval-marker presence, and execution context.
|
||||
|
||||
### Changed
|
||||
- Clarified that persistent social monitor and webhook configuration changes are review findings, while read-only social research should remain covered by no-false-positive tests.
|
||||
- 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,10 +4,19 @@ 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
|
||||
- detect inbound command-injection and tool-abuse payloads
|
||||
- record operator-review findings for approval-sensitive social-account mutations
|
||||
- write redacted local JSONL findings
|
||||
- provide explicit start, stop, status, and log-query commands
|
||||
- integrate with `clawsec-suite` as an optional add-on
|
||||
@@ -15,4 +24,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,7 +1,7 @@
|
||||
---
|
||||
name: openclaw-traffic-guardian
|
||||
version: 0.0.1-beta2
|
||||
description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.
|
||||
version: 0.0.1-beta3
|
||||
description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, inbound injection detection, and social-account policy review.
|
||||
homepage: https://clawsec.prompt.security
|
||||
author: prompt-security
|
||||
license: AGPL-3.0-or-later
|
||||
@@ -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}"
|
||||
@@ -103,6 +110,7 @@ Builders should use this skill as the OpenClaw landing zone for runtime traffic
|
||||
- optional HTTPS inspection with per-process CA trust
|
||||
- outbound exfiltration detection
|
||||
- inbound injection detection
|
||||
- approval-sensitive social-account mutation review
|
||||
- redacted local threat logs
|
||||
- optional OpenClaw hook/status integration
|
||||
|
||||
@@ -136,8 +144,10 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
||||
3. Scope proxy environment variables to the target OpenClaw process.
|
||||
4. Inspect HTTP request/response text up to a bounded byte limit.
|
||||
5. Support optional HTTPS MITM only when the operator supplies per-process trust configuration.
|
||||
6. Emit JSONL findings with redacted snippets.
|
||||
7. Provide a `status` command that reports mode, listener, CA fingerprint if present, and last findings.
|
||||
6. Flag requests matching `SPEC.md`'s Outbound POLICY_REVIEW cases as operator-review findings, including TweetClaw or other X/Twitter automation writes and scheduler/background-runner repeats without a fresh operator-approval marker.
|
||||
7. Detect repeat/background-runner context from bounded request metadata such as paths, headers, user-agent, client context, tool invocation metadata, or scheduler identifiers.
|
||||
8. Emit JSONL findings with redacted snippets plus source type, mutation category, approval-marker presence, and direct-operator versus background-runner context.
|
||||
9. Provide a `status` command that reports mode, listener, CA fingerprint if present, and last findings.
|
||||
|
||||
## Out of Scope for v0.0.1 Implementation
|
||||
|
||||
@@ -146,4 +156,3 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
||||
- default blocking
|
||||
- sending traffic to external services
|
||||
- collecting full request/response bodies
|
||||
|
||||
|
||||
@@ -45,6 +45,24 @@ Findings must be JSON objects with these fields:
|
||||
}
|
||||
```
|
||||
|
||||
`POLICY_REVIEW` findings must keep the same base schema and add these fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"threat_type": "POLICY_REVIEW",
|
||||
"pattern": "social_account_mutation",
|
||||
"source_type": "openclaw_tool_request",
|
||||
"mutation_category": "post",
|
||||
"approval_marker_present": false,
|
||||
"execution_context": "background_runner"
|
||||
}
|
||||
```
|
||||
|
||||
- `source_type`: `http_request`, `openclaw_tool_request`, or `unknown`.
|
||||
- `mutation_category`: `post`, `reply`, `repost`, `like`, `follow`, `unfollow`, `dm`, `media_upload`, `persistent_monitor`, `webhook_config`, `giveaway_draw`, or `other_social_account_mutation`.
|
||||
- `approval_marker_present`: boolean; do not persist marker secrets or full approval tokens.
|
||||
- `execution_context`: `direct_operator`, `scheduler`, `background_runner`, or `unknown`.
|
||||
|
||||
## Minimum Detection Set
|
||||
|
||||
Outbound EXFIL:
|
||||
@@ -64,6 +82,12 @@ Inbound INJECTION:
|
||||
- destructive remove commands
|
||||
- SSH authorized-key injection shapes
|
||||
|
||||
Outbound POLICY_REVIEW:
|
||||
|
||||
- social-account write requests such as post, reply, repost, like, follow, unfollow, DM, media upload, persistent monitor creation/update, webhook configuration changes, or giveaway draw actions
|
||||
- OpenClaw plugin/tool requests that invoke TweetClaw or another X/Twitter automation plugin for account mutation
|
||||
- scheduler or background-runner requests that would repeat social-account mutations without a fresh operator approval
|
||||
|
||||
## Safety Requirements
|
||||
|
||||
- Default mode is detect-and-log.
|
||||
@@ -72,6 +96,7 @@ Inbound INJECTION:
|
||||
- Maximum scan bytes must be configurable and bounded.
|
||||
- CA trust must be per-process by default.
|
||||
- System trust-store instructions must require explicit operator confirmation and must never run automatically.
|
||||
- POLICY_REVIEW findings must create an operator-review record only; they must not auto-block, auto-approve, or rewrite the requested action.
|
||||
|
||||
## Tests Required Before Release
|
||||
|
||||
@@ -79,7 +104,7 @@ Inbound INJECTION:
|
||||
- redaction tests proving secrets are not persisted
|
||||
- proxy fixture tests for HTTP request and response inspection
|
||||
- no-false-positive tests for common benign traffic
|
||||
- policy-review fixture tests for TweetClaw/social-account mutation examples and benign read-only social research requests
|
||||
- lifecycle tests for stale PID/state cleanup
|
||||
- status output tests
|
||||
- OpenClaw hook integration tests if hook files are added
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "openclaw-traffic-guardian",
|
||||
"version": "0.0.1-beta2",
|
||||
"description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.",
|
||||
"version": "0.0.1-beta3",
|
||||
"description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, inbound injection detection, and social-account policy review.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"homepage": "https://clawsec.prompt.security/",
|
||||
@@ -15,7 +15,10 @@
|
||||
"injection",
|
||||
"proxy",
|
||||
"mitm",
|
||||
"runtime"
|
||||
"runtime",
|
||||
"policy-review",
|
||||
"operator-review",
|
||||
"social-account-mutation"
|
||||
],
|
||||
"sbom": {
|
||||
"files": [
|
||||
@@ -84,6 +87,7 @@
|
||||
"https_mitm_inspection": "planned_optional",
|
||||
"egress_exfiltration_detection": "planned",
|
||||
"inbound_injection_detection": "planned",
|
||||
"social_account_policy_review": "planned",
|
||||
"blocking": "future_version"
|
||||
},
|
||||
"execution": {
|
||||
@@ -96,6 +100,7 @@
|
||||
"Default to detect-and-log mode; blocking is out of scope for v0.0.1 implementation.",
|
||||
"Scope HTTP_PROXY/HTTPS_PROXY to the OpenClaw process being monitored.",
|
||||
"Redact secret snippets before writing logs or sending conversation alerts.",
|
||||
"Record POLICY_REVIEW findings for approval-sensitive social-account mutations without auto-blocking, auto-approving, or rewriting requests.",
|
||||
"Integrate with clawsec-suite as an optional add-on, not a default install."
|
||||
],
|
||||
"triggers": [
|
||||
@@ -103,7 +108,9 @@
|
||||
"openclaw traffic monitoring",
|
||||
"monitor openclaw egress",
|
||||
"inspect openclaw http traffic",
|
||||
"detect openclaw exfiltration"
|
||||
"detect openclaw exfiltration",
|
||||
"review social account mutations",
|
||||
"detect tweetclaw write actions"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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