Compare commits

...

2 Commits

Author SHA1 Message Date
Claude 26227fcb81 docs(clawsec-scanner): align DAST docs with static hook inspection model
HOOK.md and the dast_runner.mjs header still described the pre-0.0.4
behavior of executing target hook handlers in an isolated harness. Since
0.0.4 the scanner only reads hook source and pattern-matches risk signals
(see dast_hook_executor.mjs and evaluateHook's DAST-STATIC-* findings).

- Rewrite HOOK.md DAST capability bullet and CLAWSEC_SKIP_DAST entry to
  describe static hook source inspection without target code execution
- Rewrite dast_runner.mjs header docblock to match the implementation
- Retitle SKILL.md engine heading to 'DAST (Static Hook Inspection)' and
  fix stale roadmap heading (v0.0.4 -> current version)
- Bump skill to 0.0.6 with changelog entry: HOOK.md ships in the release
  payload and clawsec-scanner-v0.0.5 is already tagged, so CI requires a
  version bump

https://claude.ai/code/session_01XwoALBxaEYGDnn9v68JFiJ
2026-06-11 06:47:30 +00:00
davida-ps 1b676fd42c fix(skills): scan staged payload with SkillSpector (#264)
* fix(skills): scan staged payload with skillspector

* fix(skills): embed skillspector report in releases

* fix(skills): use body path for release notes
2026-06-10 17:18:54 +03:00
9 changed files with 148 additions and 40 deletions
+53 -28
View File
@@ -710,7 +710,7 @@ jobs:
--source-ref "${HEAD_SHA}"
# --- Generate SkillSpector report ---
if ! generate_skillspector_report "${skill_dir}" "${out_assets}/skillspector-report.md"; then
if ! generate_skillspector_report "${inner_dir}" "${out_assets}/skillspector-report.md"; then
failures=$((failures + 1))
rm -rf "${staging_dir}"
echo "::endgroup::"
@@ -1221,7 +1221,7 @@ jobs:
--source-ref "$TAG"
# --- Generate SkillSpector report ---
generate_skillspector_report "$SKILL_PATH" "release-assets/skillspector-report.md"
generate_skillspector_report "$INNER_DIR" "release-assets/skillspector-report.md"
test -s release-assets/skill-card.md
test -s release-assets/permissions.json
@@ -1403,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:
+1 -1
View File
@@ -469,7 +469,7 @@ async function main() {
await runSkillSpector({
skillspectorBin: args.skillspectorBin,
skillDir: tempSkillDir,
skillDir: innerDir,
reportPath: path.join(releaseAssetsDir, "skillspector-report.md"),
});
+48
View File
@@ -88,6 +88,54 @@ assert.match(
'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/,
+30 -1
View File
@@ -94,7 +94,36 @@ try {
await writeFile(
fakeSkillspector,
`#!/usr/bin/env node
import { writeFileSync } from "node:fs";
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]) {
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## [0.0.6] - 2026-06-11
### Changed
- Updated shipped documentation (hook HOOK.md, dast_runner.mjs header, SKILL.md roadmap heading) to describe the static hook source inspection model introduced in 0.0.4, removing stale references to executing target hook handlers.
## [0.0.5] - 2026-06-10
### Changed
+3 -3
View File
@@ -1,6 +1,6 @@
---
name: clawsec-scanner
version: 0.0.5
version: 0.0.6
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:
@@ -50,7 +50,7 @@ The scanner orchestrates four complementary scan types to provide comprehensive
- **Bandit** for Python: Leverages existing `pyproject.toml` configuration
- Identifies: hardcoded secrets (API keys, tokens), command injection (`eval`, `exec`), path traversal, unsafe deserialization
4. **Dynamic Analysis (DAST)**
4. **DAST (Static Hook Inspection)**
- Static hook inspection for OpenClaw hook handlers discovered from `HOOK.md` metadata
- Verifies coverage and source-level risk signals without importing, transpiling, or invoking target handlers
- Note: Traditional web DAST tools (ZAP, Burp) do not apply to agent platforms - this provides agent-specific testing
@@ -464,7 +464,7 @@ npx clawhub@latest install clawsec-suite
## Roadmap
### v0.0.4 (Current)
### v0.0.6 (Current)
- [x] Dependency scanning (npm audit, pip-audit)
- [x] CVE database integration (OSV, NVD, GitHub Advisory)
- [x] SAST analysis (Semgrep, Bandit)
@@ -20,7 +20,7 @@ The hook orchestrates four independent scanning engines:
1. **Dependency Scanning**: Executes `npm audit` and `pip-audit` to detect known vulnerabilities in JavaScript and Python dependencies
2. **SAST (Static Analysis)**: Runs Semgrep (JS/TS) and Bandit (Python) to detect security issues like hardcoded secrets, command injection, and path traversal
3. **CVE Database Lookup**: Queries OSV API (primary), NVD 2.0 (optional), and GitHub Advisory Database (optional) for vulnerability enrichment
4. **DAST (Dynamic Analysis)**: Executes real OpenClaw hook handlers in an isolated harness and tests malicious-input resilience, timeout behavior, output bounds, and event mutation safety
4. **DAST (Static Hook Inspection)**: Reads OpenClaw hook handler source in an isolated helper process and pattern-matches source-level risk signals (subprocess execution, dynamic imports, `eval`, environment access) without importing, transpiling, or executing target handler code
## Safety Contract
@@ -48,7 +48,7 @@ The hook orchestrates four independent scanning engines:
- `CLAWSEC_SKIP_DEPENDENCY_SCAN`: Set to `1` to disable dependency scanning (npm audit, pip-audit).
- `CLAWSEC_SKIP_SAST`: Set to `1` to disable static analysis (Semgrep, Bandit).
- `CLAWSEC_SKIP_DAST`: Set to `1` to disable dynamic analysis (hook security tests).
- `CLAWSEC_SKIP_DAST`: Set to `1` to disable static hook inspection (DAST hook source checks).
- `CLAWSEC_SKIP_CVE_LOOKUP`: Set to `1` to disable CVE database enrichment.
### Advanced Options
@@ -1,13 +1,13 @@
#!/usr/bin/env node
/**
* DAST (Dynamic Application Security Testing) Runner for ClawSec Scanner.
* DAST Runner for ClawSec Scanner (static OpenClaw hook inspection).
*
* Scope:
* - Discover OpenClaw hooks from target directories
* - Execute real hook handlers in an isolated harness process
* - Validate malicious-input resilience, timeout behavior, output bounds,
* and event mutation safety
* - Inspect hook handler source in an isolated helper process without
* importing, transpiling, or invoking target handler code
* - Report coverage and source-level risk signals as DAST-STATIC-* findings
*/
import fs from "node:fs/promises";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawsec-scanner",
"version": "0.0.5",
"version": "0.0.6",
"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",