fix(clawsec-scanner): release 0.0.2 with real OpenClaw DAST harness (#128)

* fix(clawsec-scanner): ship real openclaw dast harness in 0.0.2

* fix(clawsec-scanner): classify ts harness limits as info coverage

* docs(wiki): add clawsec-scanner module documentation

* docs(release): add clawsec-suite install guidance to quick install text

* docs(readme): clarify standalone installs and suite optionality

* docs(readme): remove standalone quick-install block

* docs(readme): rename skill section and clarify suite start point
This commit is contained in:
davida-ps
2026-03-10 19:27:22 +02:00
committed by GitHub
parent 687822b6cb
commit f0f0f1db97
14 changed files with 1413 additions and 451 deletions
+3
View File
@@ -898,6 +898,9 @@ jobs:
npx clawhub@latest install ${{ steps.parse.outputs.skill_name }}
```
**If you already have `clawsec-suite` installed:**
Ask your agent to pull `${{ steps.parse.outputs.skill_name }}` from the ClawSec catalog and it will handle setup and verification automatically.
**Manual download with verification:**
```bash
# 1. Download the release archive, checksums, and signing material
+5 -2
View File
@@ -159,7 +159,9 @@ See [`skills/clawsec-nanoclaw/INSTALL.md`](skills/clawsec-nanoclaw/INSTALL.md) f
The **clawsec-suite** is a skill-of-skills manager that installs, verifies, and maintains security skills from the ClawSec catalog.
### Skills in the Suite
`clawsec-suite` is optional orchestration; skills can still be installed directly as standalone packages.
### ClawSec Skills
| Skill | Description | Installation | Compatibility |
|-------|-------------|--------------|---------------|
@@ -433,8 +435,9 @@ npm run build
│ ├── populate-local-wiki.sh # Local wiki llms export populator
│ └── release-skill.sh # Manual skill release helper
├── skills/
│ ├── clawsec-suite/ # 📦 Suite installer (skill-of-skills)
│ ├── clawsec-suite/ # 📦 Suite installer (skill-of-skills - start here and have your agent do the rest)
│ ├── clawsec-feed/ # 📡 Advisory feed skill
│ ├── clawsec-scanner/ # 🔍 Vulnerability scanner (deps + SAST + OpenClaw DAST)
│ ├── clawsec-nanoclaw/ # 📱 NanoClaw platform security suite
│ ├── clawsec-clawhub-checker/ # 🧪 ClawHub reputation checks
│ ├── clawtributor/ # 🤝 Community reporting skill
+14
View File
@@ -5,6 +5,20 @@ All notable changes to the ClawSec Scanner will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.2] - 2026-03-10
### Changed
- Replaced simulated DAST checks with real OpenClaw hook execution harness testing
- Updated DAST semantics so high-severity findings are emitted for actual hook execution failures/timeouts, not static payload pattern matches
- Reclassified DAST harness capability limitations (for example missing TypeScript compiler for `.ts` hooks) to `info` coverage findings instead of high severity
- Added DAST harness mode guard to prevent recursive scanner execution when hook handlers are tested in isolation
### Added
- New DAST helper executor script for isolated per-hook execution and timeout enforcement
- DAST harness regression tests covering no-false-positive baseline and malicious-input crash detection
## [0.0.1] - 2026-02-27
### Added
+16 -8
View File
@@ -1,7 +1,7 @@
---
name: clawsec-scanner
version: 0.0.1
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 basic DAST security testing for skill hooks.
version: 0.0.2
description: Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific DAST hook execution testing for OpenClaw hooks.
homepage: https://clawsec.prompt.security
clawdis:
emoji: "🔍"
@@ -16,7 +16,7 @@ Comprehensive security scanner for agent platforms that automates vulnerability
- **Dependency Scanning**: Analyzes npm and Python dependencies using `npm audit` and `pip-audit` with structured JSON output parsing
- **CVE Database Integration**: Queries OSV (primary), NVD 2.0, and GitHub Advisory Database for vulnerability enrichment
- **SAST Analysis**: Static code analysis using Semgrep (JavaScript/TypeScript) and Bandit (Python) to detect hardcoded secrets, command injection, path traversal, and unsafe deserialization
- **DAST Framework**: Basic dynamic analysis for skill hook security testing (input validation, timeout enforcement)
- **DAST Framework**: Agent-specific dynamic analysis with real OpenClaw hook execution harness (malicious input, timeout, output bounds, event mutation safety)
- **Unified Reporting**: Consolidated vulnerability reports with severity classification and remediation guidance
- **Continuous Monitoring**: OpenClaw hook integration for automated periodic scanning
@@ -43,8 +43,8 @@ The scanner orchestrates four complementary scan types to provide comprehensive
- Identifies: hardcoded secrets (API keys, tokens), command injection (`eval`, `exec`), path traversal, unsafe deserialization
4. **Dynamic Analysis (DAST)**
- Test framework for skill hook security validation
- Verifies: malicious input handling, timeout enforcement, resource limits
- Real hook execution harness for OpenClaw hook handlers discovered from `HOOK.md` metadata
- Verifies: malicious input resilience, timeout behavior, output amplification bounds, and core event mutation safety
- Note: Traditional web DAST tools (ZAP, Burp) do not apply to agent platforms - this provides agent-specific testing
### Unified Reporting
@@ -248,7 +248,8 @@ scripts/runner.sh # Orchestration layer
├── scan_dependencies.mjs # npm audit + pip-audit
├── query_cve_databases.mjs # OSV/NVD/GitHub API queries
├── sast_analyzer.mjs # Semgrep + Bandit static analysis
── dast_runner.mjs # Dynamic security testing
── dast_runner.mjs # Dynamic security testing orchestration
└── dast_hook_executor.mjs # Isolated real hook execution harness
lib/
├── report.mjs # Result aggregation and formatting
@@ -325,6 +326,11 @@ proc.on('close', code => {
- Requires Python 3.8+ runtime
- Alternative: use Docker image `returntocorp/semgrep`
**"TypeScript hook not executable in DAST harness"**
- The DAST harness executes real hook handlers and transpiles `handler.ts` files when a TypeScript compiler is available
- Install TypeScript in the scanner environment: `npm install -D typescript` (or provide `handler.js`/`handler.mjs`)
- Without a compiler, scanner reports an `info`-level coverage finding instead of a high-severity vulnerability
**"Concurrent scan detected"**
- Lockfile exists: `/tmp/clawsec-scanner.lock`
- Wait for running scan to complete or manually remove lockfile
@@ -342,6 +348,7 @@ Check scanner is working correctly:
node test/dependency_scanner.test.mjs
node test/cve_integration.test.mjs
node test/sast_engine.test.mjs
node test/dast_harness.test.mjs
# Validate skill structure
python ../../utils/validate_skill.py .
@@ -364,6 +371,7 @@ done
node test/dependency_scanner.test.mjs # Dependency scanning
node test/cve_integration.test.mjs # CVE database APIs
node test/sast_engine.test.mjs # Static analysis
node test/dast_harness.test.mjs # DAST harness execution
```
### Linting
@@ -448,11 +456,11 @@ npx clawhub@latest install clawsec-suite
## Roadmap
### v0.1.0 (Current)
### v0.0.2 (Current)
- [x] Dependency scanning (npm audit, pip-audit)
- [x] CVE database integration (OSV, NVD, GitHub Advisory)
- [x] SAST analysis (Semgrep, Bandit)
- [x] Basic DAST framework for skill hooks
- [x] Real OpenClaw hook execution harness for DAST
- [x] Unified JSON reporting
- [x] OpenClaw hook integration
@@ -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)**: Tests skill hook security including input validation, timeout enforcement, and resource limits
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
## Safety Contract
@@ -196,6 +196,11 @@ function buildAlertMessage(report: ScanReport, format: string): string {
}
const handler = async (event: HookEvent, _context: HookContext): Promise<void> => {
// DAST harness mode executes hook handlers directly; skip recursive scanner runs.
if (process.env.CLAWSEC_DAST_HARNESS === "1" || _context?.dastMode === true) {
return;
}
if (!shouldHandleEvent(event)) return;
const installRoot = configuredPath(
@@ -0,0 +1,273 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
function parseArgs(argv) {
const parsed = {
handler: "",
exportName: "default",
eventB64: "",
contextB64: "",
};
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (token === "--handler") {
parsed.handler = String(argv[i + 1] ?? "").trim();
i += 1;
continue;
}
if (token === "--export") {
parsed.exportName = String(argv[i + 1] ?? "default").trim() || "default";
i += 1;
continue;
}
if (token === "--event") {
parsed.eventB64 = String(argv[i + 1] ?? "").trim();
i += 1;
continue;
}
if (token === "--context") {
parsed.contextB64 = String(argv[i + 1] ?? "").trim();
i += 1;
continue;
}
throw new Error(`Unknown argument: ${token}`);
}
if (!parsed.handler) {
throw new Error("Missing required --handler");
}
if (!parsed.eventB64) {
throw new Error("Missing required --event");
}
if (!parsed.contextB64) {
throw new Error("Missing required --context");
}
return parsed;
}
function decodeBase64Json(value, label) {
try {
const decoded = Buffer.from(value, "base64").toString("utf8");
return JSON.parse(decoded);
} catch (error) {
throw new Error(`Failed to decode ${label}: ${error instanceof Error ? error.message : String(error)}`);
}
}
async function fileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function loadTypeScriptCompiler() {
if (process.env.CLAWSEC_DAST_DISABLE_TYPESCRIPT === "1") {
return null;
}
try {
const imported = await import("typescript");
return imported.default || imported;
} catch {
// Ignore and try require path next.
}
try {
const req = createRequire(import.meta.url);
return req("typescript");
} catch {
return null;
}
}
async function importTypeScriptModule(tsPath) {
const tsCompiler = await loadTypeScriptCompiler();
if (!tsCompiler || typeof tsCompiler.transpileModule !== "function") {
throw new Error(
`Cannot execute TypeScript hook (${tsPath}): typescript compiler not available. ` +
"Install 'typescript' or provide a JavaScript handler file.",
);
}
const source = await fs.readFile(tsPath, "utf8");
const transpiled = tsCompiler.transpileModule(source, {
compilerOptions: {
module: tsCompiler.ModuleKind.ESNext,
target: tsCompiler.ScriptTarget.ES2022,
moduleResolution: tsCompiler.ModuleResolutionKind.NodeNext,
esModuleInterop: true,
sourceMap: false,
inlineSourceMap: false,
declaration: false,
},
fileName: tsPath,
reportDiagnostics: false,
});
const tempFile = path.join(
path.dirname(tsPath),
`.clawsec-dast-${path.basename(tsPath, ".ts")}-${process.pid}-${Date.now()}.mjs`,
);
await fs.writeFile(tempFile, transpiled.outputText, "utf8");
try {
return await import(`${pathToFileURL(tempFile).href}?ts=${Date.now()}`);
} finally {
try {
await fs.unlink(tempFile);
} catch {
// best-effort cleanup
}
}
}
async function loadHookModule(handlerPath) {
const fullPath = path.resolve(handlerPath);
const exists = await fileExists(fullPath);
if (!exists) {
throw new Error(`Hook handler does not exist: ${fullPath}`);
}
const ext = path.extname(fullPath).toLowerCase();
if (ext === ".ts") {
return importTypeScriptModule(fullPath);
}
return import(`${pathToFileURL(fullPath).href}?v=${Date.now()}`);
}
function resolveHandlerExport(mod, exportName) {
if (exportName && exportName !== "default") {
if (typeof mod?.[exportName] === "function") {
return mod[exportName];
}
throw new Error(`Hook export '${exportName}' is not a function`);
}
if (typeof mod?.default === "function") {
return mod.default;
}
if (typeof mod?.handler === "function") {
return mod.handler;
}
throw new Error("Hook module does not export a handler function");
}
function normalizeTimestamp(event) {
const timestamp = event?.timestamp;
if (typeof timestamp === "string" || typeof timestamp === "number") {
const parsed = new Date(timestamp);
if (!Number.isNaN(parsed.getTime())) {
event.timestamp = parsed;
}
}
}
function summarizeMessages(messages) {
if (!Array.isArray(messages)) {
return {
count: 0,
charCount: 0,
};
}
let charCount = 0;
for (const message of messages) {
if (typeof message === "string") {
charCount += message.length;
continue;
}
try {
charCount += JSON.stringify(message).length;
} catch {
charCount += 0;
}
}
return {
count: messages.length,
charCount,
};
}
function coreEventShape(event) {
return {
type: event?.type ?? null,
action: event?.action ?? null,
sessionKey: event?.sessionKey ?? null,
};
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const event = decodeBase64Json(args.eventB64, "event payload");
const context = decodeBase64Json(args.contextB64, "context payload");
normalizeTimestamp(event);
const startedAt = Date.now();
const before = coreEventShape(event);
try {
const mod = await loadHookModule(args.handler);
const handler = resolveHandlerExport(mod, args.exportName);
await handler(event, context);
const after = coreEventShape(event);
const messageSummary = summarizeMessages(event?.messages);
const payload = {
ok: true,
duration_ms: Date.now() - startedAt,
core_before: before,
core_after: after,
messages_count: messageSummary.count,
messages_char_count: messageSummary.charCount,
};
process.stdout.write(JSON.stringify(payload));
} catch (error) {
const after = coreEventShape(event);
const messageSummary = summarizeMessages(event?.messages);
const payload = {
ok: false,
duration_ms: Date.now() - startedAt,
core_before: before,
core_after: after,
messages_count: messageSummary.count,
messages_char_count: messageSummary.charCount,
error: error instanceof Error ? error.message : String(error),
};
process.stdout.write(JSON.stringify(payload));
}
}
main().catch((error) => {
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
process.exit(1);
});
File diff suppressed because it is too large Load Diff
@@ -73,6 +73,7 @@ function assertSourceHookExists() {
"scripts/scan_dependencies.mjs",
"scripts/sast_analyzer.mjs",
"scripts/dast_runner.mjs",
"scripts/dast_hook_executor.mjs",
"scripts/query_cve_databases.mjs",
];
for (const file of requiredScripts) {
+13 -3
View File
@@ -1,7 +1,7 @@
{
"name": "clawsec-scanner",
"version": "0.0.1",
"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 basic DAST security testing for skill hooks.",
"version": "0.0.2",
"description": "Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific DAST hook execution testing for OpenClaw hooks.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
"homepage": "https://clawsec.prompt.security/",
@@ -57,7 +57,12 @@
{
"path": "scripts/dast_runner.mjs",
"required": true,
"description": "Dynamic analysis framework for skill hook security testing"
"description": "Dynamic analysis harness executing OpenClaw hook handlers with malicious-input and timeout checks"
},
{
"path": "scripts/dast_hook_executor.mjs",
"required": true,
"description": "Isolated hook execution helper used by DAST for real OpenClaw harness testing"
},
{
"path": "scripts/setup_scanner_hook.mjs",
@@ -103,6 +108,11 @@
"path": "test/sast_engine.test.mjs",
"required": false,
"description": "Unit tests for SAST analysis (Semgrep, Bandit)"
},
{
"path": "test/dast_harness.test.mjs",
"required": false,
"description": "DAST harness tests for real hook execution and malicious-input failure detection"
}
]
},
@@ -0,0 +1,250 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import {
pass,
fail,
report,
exitWithResults,
createTempDir,
} from "./lib/test_harness.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SKILL_ROOT = path.resolve(__dirname, "..");
const DAST_SCRIPT = path.join(SKILL_ROOT, "scripts", "dast_runner.mjs");
/**
* @param {string} targetPath
* @param {number} timeoutMs
* @param {Record<string, string>} envOverrides
* @returns {Promise<{code: number, stdout: string, stderr: string, report: any}>}
*/
async function runDast(targetPath, timeoutMs = 3000, envOverrides = {}) {
return new Promise((resolve, reject) => {
const proc = spawn(
"node",
[DAST_SCRIPT, "--target", targetPath, "--format", "json", "--timeout", String(timeoutMs)],
{
cwd: SKILL_ROOT,
stdio: ["ignore", "pipe", "pipe"],
env: {
...process.env,
...envOverrides,
},
},
);
let stdout = "";
let stderr = "";
proc.stdout.on("data", (chunk) => {
stdout += String(chunk);
});
proc.stderr.on("data", (chunk) => {
stderr += String(chunk);
});
proc.on("error", reject);
proc.on("close", (code) => {
try {
const parsed = JSON.parse(stdout.trim());
resolve({
code: code ?? 1,
stdout,
stderr,
report: parsed,
});
} catch (error) {
reject(new Error(`Failed to parse DAST JSON output: ${String(error)}\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`));
}
});
});
}
/**
* @param {string} hookDir
* @param {string} eventsLiteral
* @param {string} handlerSource
* @param {string} [handlerFile]
* @returns {Promise<void>}
*/
async function writeHookFixture(hookDir, eventsLiteral, handlerSource, handlerFile = "handler.js") {
await fs.mkdir(hookDir, { recursive: true });
const hookMd = `---
name: ${path.basename(hookDir)}
description: fixture hook
metadata: { "openclaw": { "events": [${eventsLiteral}] } }
---
# Fixture Hook
`;
await fs.writeFile(path.join(hookDir, "HOOK.md"), hookMd, "utf8");
await fs.writeFile(path.join(hookDir, handlerFile), handlerSource, "utf8");
}
async function testSafeHookExecutesAndDoesNotReportMisleadingHigh() {
const testName = "DAST harness: executes real hook and reports no misleading high findings";
const tmp = await createTempDir();
try {
const targetPath = path.join(tmp.path, "skill");
const hookDir = path.join(targetPath, "hooks", "safe-hook");
const markerFile = path.join(hookDir, "executed.marker");
await writeHookFixture(
hookDir,
'"command:new"',
`import fs from "node:fs/promises";
import path from "node:path";
const handler = async (event, context) => {
const marker = path.join(path.dirname(new URL(import.meta.url).pathname), "executed.marker");
await fs.writeFile(marker, String(context?.event || "unknown"), "utf8");
if (!Array.isArray(event.messages)) {
event.messages = [];
}
event.messages.push("hook executed");
};
export default handler;
`,
);
const result = await runDast(targetPath, 2500);
const markerExists = await fs
.access(markerFile)
.then(() => true)
.catch(() => false);
const cleanSummary =
result.report?.summary?.critical === 0
&& result.report?.summary?.high === 0
&& result.report?.summary?.medium === 0
&& result.report?.summary?.low === 0
&& result.report?.summary?.info === 0;
if (result.code === 0 && markerExists && cleanSummary) {
pass(testName);
} else {
fail(
testName,
`Expected exit=0, markerExists=true, clean summary. Got exit=${result.code}, markerExists=${markerExists}, summary=${JSON.stringify(result.report?.summary)} stderr=${result.stderr}`,
);
}
} catch (error) {
fail(testName, error);
} finally {
await tmp.cleanup();
}
}
async function testMaliciousCrashProducesHighFinding() {
const testName = "DAST harness: malicious input crash is reported as high";
const tmp = await createTempDir();
try {
const targetPath = path.join(tmp.path, "skill");
const hookDir = path.join(targetPath, "hooks", "crashy-hook");
await writeHookFixture(
hookDir,
'"message:preprocessed"',
`const handler = async (event) => {
const payload = String(event?.context?.content || "");
if (payload.includes("<script>")) {
throw new Error("Unhandled payload path");
}
};
export default handler;
`,
);
const result = await runDast(targetPath, 2500);
const hasHigh = Number(result.report?.summary?.high || 0) > 0;
const hasCrashFinding = Array.isArray(result.report?.vulnerabilities)
&& result.report.vulnerabilities.some((v) => String(v.id || "").includes("DAST-MALICIOUS-CRASH"));
if (result.code === 1 && hasHigh && hasCrashFinding) {
pass(testName);
} else {
fail(
testName,
`Expected exit=1 and malicious crash high finding. Got exit=${result.code}, summary=${JSON.stringify(result.report?.summary)}, findings=${JSON.stringify(result.report?.vulnerabilities || [])}`,
);
}
} catch (error) {
fail(testName, error);
} finally {
await tmp.cleanup();
}
}
async function testMissingTypeScriptCompilerIsCoverageInfo() {
const testName = "DAST harness: missing TypeScript compiler reports coverage info, not high";
const tmp = await createTempDir();
try {
const targetPath = path.join(tmp.path, "skill");
const hookDir = path.join(targetPath, "hooks", "ts-hook");
await writeHookFixture(
hookDir,
'"command:new"',
`type Ctx = { dastMode?: boolean };
const handler = async (_event: unknown, _context: Ctx): Promise<void> => {
return;
};
export default handler;
`,
"handler.ts",
);
const result = await runDast(
targetPath,
2500,
{ CLAWSEC_DAST_DISABLE_TYPESCRIPT: "1" },
);
const noHigh = Number(result.report?.summary?.high || 0) === 0
&& Number(result.report?.summary?.critical || 0) === 0;
const hasCoverageInfo = Array.isArray(result.report?.vulnerabilities)
&& result.report.vulnerabilities.some((v) => String(v.id || "").includes("DAST-COVERAGE"));
const hasInfoCount = Number(result.report?.summary?.info || 0) > 0;
if (result.code === 0 && noHigh && hasCoverageInfo && hasInfoCount) {
pass(testName);
} else {
fail(
testName,
`Expected coverage info only (no high/critical). Got exit=${result.code}, summary=${JSON.stringify(result.report?.summary)}, findings=${JSON.stringify(result.report?.vulnerabilities || [])}`,
);
}
} catch (error) {
fail(testName, error);
} finally {
await tmp.cleanup();
}
}
async function main() {
await testSafeHookExecutesAndDoesNotReportMisleadingHigh();
await testMaliciousCrashProducesHighFinding();
await testMissingTypeScriptCompilerIsCoverageInfo();
report();
exitWithResults();
}
await main();
+5 -3
View File
@@ -1,8 +1,8 @@
# Wiki Generation Metadata
- Commit hash: `d5aadfbee15b48ebb4872dfb838e4df88c611d56`
- Branch name: `codex/wiki-tab-ui`
- Generation timestamp (local): `2026-02-26T09:16:02+0200`
- Commit hash: `c3983a100581a9f27eb8cc3b5baa4f585e6c45e4`
- Branch name: `codex/clawsec-scanner-0.0.2-dast-harness`
- Generation timestamp (local): `2026-03-10T19:06:29+0200`
- Generation mode: `update`
- Output language: `English`
- Assets copied into `wiki/assets/`:
@@ -13,6 +13,7 @@
## Notes
- Migrated root documentation pages from `docs/` into dedicated `wiki/` operation pages.
- Updated index and cross-links to use `wiki/` as the documentation source of truth.
- Added a dedicated module page for `clawsec-scanner` and linked it from `wiki/INDEX.md`.
- Future updates should preserve existing headings and append `Update Notes` sections when making deltas.
## Source References
@@ -21,6 +22,7 @@
- AGENTS.md
- wiki/overview.md
- wiki/architecture.md
- wiki/modules/clawsec-scanner.md
- wiki/dependencies.md
- wiki/data-flow.md
- wiki/glossary.md
+4
View File
@@ -29,6 +29,7 @@
## Modules
- [Frontend Web App](modules/frontend-web.md)
- [ClawSec Suite Core](modules/clawsec-suite.md)
- [ClawSec Scanner](modules/clawsec-scanner.md)
- [NanoClaw Integration](modules/nanoclaw-integration.md)
- [Automation and Release Pipelines](modules/automation-release.md)
- [Local Validation and Packaging Tools](modules/local-tooling.md)
@@ -40,6 +41,7 @@
- [Generation Metadata](GENERATION.md)
## Update Notes
- 2026-03-10: Added ClawSec Scanner module documentation and linked it under Modules.
- 2026-02-26: Added Operations pages and updated navigation guidance after migrating root docs into wiki pages.
## Source References
@@ -50,4 +52,6 @@
- scripts/populate-local-feed.sh
- scripts/populate-local-skills.sh
- skills/clawsec-suite/skill.json
- skills/clawsec-scanner/skill.json
- wiki/modules/clawsec-scanner.md
- .github/workflows/ci.yml
+102
View File
@@ -0,0 +1,102 @@
# Module: ClawSec Scanner
## Responsibilities
- Provide multi-layer vulnerability scanning for OpenClaw-oriented skill repositories.
- Orchestrate dependency, SAST, and DAST engines into a single report contract.
- Execute real OpenClaw hook handlers in an isolated DAST harness to validate runtime security behavior.
- Support periodic scan execution through an OpenClaw hook integration.
- Normalize findings into severity buckets for downstream triage and automation.
## Key Files
- `skills/clawsec-scanner/skill.json`: skill metadata, SBOM paths, trigger phrases.
- `skills/clawsec-scanner/scripts/runner.sh`: main orchestrator for dependency/SAST/DAST scans.
- `skills/clawsec-scanner/scripts/scan_dependencies.mjs`: `npm audit` + `pip-audit` parsing.
- `skills/clawsec-scanner/scripts/sast_analyzer.mjs`: Semgrep and Bandit execution/parsing.
- `skills/clawsec-scanner/scripts/dast_runner.mjs`: hook discovery + real harness DAST evaluation.
- `skills/clawsec-scanner/scripts/dast_hook_executor.mjs`: isolated per-hook runtime executor.
- `skills/clawsec-scanner/hooks/clawsec-scanner-hook/handler.ts`: periodic OpenClaw event hook.
- `skills/clawsec-scanner/lib/report.mjs`: unified report generation and text/JSON formatting.
## Public Interfaces
| Interface | Consumer | Behavior |
| --- | --- | --- |
| `runner.sh` CLI | Operators/automation | Runs all enabled scan engines and emits merged report output. |
| `dast_runner.mjs` CLI | Operators/CI/hooks | Discovers hooks and runs isolated runtime DAST checks. |
| OpenClaw scanner hook default export | OpenClaw runtime | Handles `agent:bootstrap` and `command:new` scanner trigger events. |
| `ScanReport` JSON output | Humans and automation | Provides normalized severity summary + finding list. |
## Inputs and Outputs
Inputs/outputs are summarized in the table below.
| Type | Name | Location | Description |
| --- | --- | --- | --- |
| Input | Scan target path | `--target` CLI arg | Root directory where skills/hooks are scanned. |
| Input | Dependency manifests | `package-lock.json`, `requirements.txt`, `pyproject.toml` | Drives dependency vulnerability checks. |
| Input | Hook metadata and handlers | `**/HOOK.md`, `handler.{js,mjs,cjs,ts}` | DAST harness discovers and executes these handlers. |
| Input | Env configuration | `CLAWSEC_*`, `GITHUB_TOKEN` | Controls engine behavior, severity filtering, and output paths. |
| Output | Unified scan report | stdout or `--output` file | JSON/text report with severity summary and finding details. |
| Output | Runtime hook alerts | OpenClaw `event.messages` | New vulnerability alerts pushed into conversations. |
| Output | Scanner state file | `~/.openclaw/clawsec-scanner-state.json` by default | De-duplication memory for reported finding IDs. |
## Configuration
| Variable | Default | Module Effect |
| --- | --- | --- |
| `CLAWSEC_SCANNER_INTERVAL` | `86400` | Minimum interval between periodic hook-triggered scans. |
| `CLAWSEC_SCANNER_MIN_SEVERITY` | `medium` | Threshold for findings pushed to conversation alerts. |
| `CLAWSEC_SCANNER_FORMAT` | `text` | Hook alert serialization format (`text` or `json`). |
| `CLAWSEC_SKIP_DEPENDENCY_SCAN` | `0` | Disables dependency scanner when set to `1`. |
| `CLAWSEC_SKIP_SAST` | `0` | Disables Semgrep/Bandit scanner when set to `1`. |
| `CLAWSEC_SKIP_DAST` | `0` | Disables runtime hook DAST checks when set to `1`. |
| `CLAWSEC_SKIP_CVE_LOOKUP` | `0` | Disables CVE enrichment stage when set to `1`. |
| `CLAWSEC_DAST_HARNESS` | unset | Internal guard to avoid recursive scans during harness execution. |
| `CLAWSEC_DAST_DISABLE_TYPESCRIPT` | unset | Test/debug switch forcing TypeScript harness coverage fallback mode. |
## DAST Harness Behavior
- Hook discovery walks the target tree for `HOOK.md` and resolves adjacent handler files.
- Each declared event key is executed in a separate Node subprocess via `dast_hook_executor.mjs`.
- Findings are generated from real runtime behavior:
- Baseline execution crash or timeout.
- Malicious-input crash or timeout.
- Output amplification beyond message/character thresholds.
- Core event identity mutation (`type`, `action`, `sessionKey`).
- Harness capability gaps (for example missing TypeScript compiler for `.ts` handlers) are reported as `info` coverage findings, not high-severity vulnerabilities.
## Example Snippets
```bash
# run scanner end-to-end
bash skills/clawsec-scanner/scripts/runner.sh --target ./skills --format json
```
```bash
# run DAST harness directly
node skills/clawsec-scanner/scripts/dast_runner.mjs --target ./skills --format text --timeout 30000
```
## Tests
| Test File | Focus |
| --- | --- |
| `skills/clawsec-scanner/test/dast_harness.test.mjs` | Real hook execution path, malicious crash detection, TypeScript coverage fallback semantics. |
| `skills/clawsec-scanner/test/reviewer_regressions.test.mjs` | Runner behavior around non-zero DAST exit and merged reporting. |
| `skills/clawsec-scanner/test/dependency_scanner.test.mjs` | Dependency scanner utility/report contracts. |
| `skills/clawsec-scanner/test/sast_engine.test.mjs` | SAST parser/normalization behavior. |
| `skills/clawsec-scanner/test/cve_integration.test.mjs` | OSV/NVD/GitHub enrichment integration checks. |
## Update Notes
- 2026-03-10: Added module page for `clawsec-scanner` and documented the `0.0.2` real OpenClaw DAST harness execution model.
## Source References
- skills/clawsec-scanner/skill.json
- skills/clawsec-scanner/SKILL.md
- skills/clawsec-scanner/CHANGELOG.md
- skills/clawsec-scanner/scripts/runner.sh
- skills/clawsec-scanner/scripts/scan_dependencies.mjs
- skills/clawsec-scanner/scripts/sast_analyzer.mjs
- skills/clawsec-scanner/scripts/dast_runner.mjs
- skills/clawsec-scanner/scripts/dast_hook_executor.mjs
- skills/clawsec-scanner/scripts/setup_scanner_hook.mjs
- skills/clawsec-scanner/hooks/clawsec-scanner-hook/HOOK.md
- skills/clawsec-scanner/hooks/clawsec-scanner-hook/handler.ts
- skills/clawsec-scanner/lib/report.mjs
- skills/clawsec-scanner/lib/utils.mjs
- skills/clawsec-scanner/test/dast_harness.test.mjs
- skills/clawsec-scanner/test/reviewer_regressions.test.mjs