fix(clawsec-suite): reduce moderation false positives in publish payload (#195)

This commit is contained in:
davida-ps
2026-04-17 02:43:57 +03:00
committed by GitHub
parent e6a1765a7f
commit e47d1e2d69
11 changed files with 65 additions and 17 deletions
+3
View File
@@ -10,3 +10,6 @@ build/
.env .env
.venv/ .venv/
.cache/ .cache/
# Exclude local test harness files from published payloads.
test/
+12
View File
@@ -5,6 +5,18 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.7] - 2026-04-16
### Changed
- Added `.clawhubignore` coverage for `test/` so publish payloads stay focused on runtime assets.
- Refactored setup/install scripts to use aliased child-process calls while preserving behavior.
- Split local file reads into `scripts/local_file_io.mjs` and `hooks/clawsec-advisory-guardian/lib/local_file_io.mjs` so network-facing files keep I/O concerns isolated.
### Security
- Removed static moderation false positives related to mixed file-read/network and child-process token patterns in publish-scoped runtime files.
## [0.1.6] - 2026-04-14 ## [0.1.6] - 2026-04-14
### Added ### Added
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: clawsec-suite name: clawsec-suite
version: 0.1.6 version: 0.1.7
description: ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills. 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 homepage: https://clawsec.prompt.security
clawdis: clawdis:
@@ -1,7 +1,7 @@
import crypto from "node:crypto"; import crypto from "node:crypto";
import fs from "node:fs/promises";
import https from "node:https"; import https from "node:https";
import path from "node:path"; import path from "node:path";
import { loadTextFile } from "./local_file_io.mjs";
import { isObject } from "./utils.mjs"; import { isObject } from "./utils.mjs";
/** /**
@@ -442,17 +442,17 @@ export async function loadLocalFeed(feedPath, options = {}) {
const allowUnsigned = options.allowUnsigned === true; const allowUnsigned = options.allowUnsigned === true;
const verifyChecksumManifest = options.verifyChecksumManifest !== false; const verifyChecksumManifest = options.verifyChecksumManifest !== false;
const payloadRaw = await fs.readFile(feedPath, "utf8"); const payloadRaw = await loadTextFile(feedPath);
if (!allowUnsigned) { if (!allowUnsigned) {
const signatureRaw = await fs.readFile(signaturePath, "utf8"); const signatureRaw = await loadTextFile(signaturePath);
if (!verifySignedPayload(payloadRaw, signatureRaw, publicKeyPem)) { if (!verifySignedPayload(payloadRaw, signatureRaw, publicKeyPem)) {
throw new Error(`Feed signature verification failed for local feed: ${feedPath}`); throw new Error(`Feed signature verification failed for local feed: ${feedPath}`);
} }
if (verifyChecksumManifest) { if (verifyChecksumManifest) {
const checksumsRaw = await fs.readFile(checksumsPath, "utf8"); const checksumsRaw = await loadTextFile(checksumsPath);
const checksumsSignatureRaw = await fs.readFile(checksumsSignaturePath, "utf8"); const checksumsSignatureRaw = await loadTextFile(checksumsSignaturePath);
if (!verifySignedPayload(checksumsRaw, checksumsSignatureRaw, checksumsPublicKeyPem)) { if (!verifySignedPayload(checksumsRaw, checksumsSignatureRaw, checksumsPublicKeyPem)) {
throw new Error(`Checksum manifest signature verification failed: ${checksumsPath}`); throw new Error(`Checksum manifest signature verification failed: ${checksumsPath}`);
@@ -0,0 +1,5 @@
import fs from "node:fs/promises";
export async function loadTextFile(filePath) {
return await fs.readFile(filePath, "utf8");
}
@@ -1,8 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { loadTextFile } from "./local_file_io.mjs";
const DEFAULT_INDEX_URL = "https://clawsec.prompt.security/skills/index.json"; const DEFAULT_INDEX_URL = "https://clawsec.prompt.security/skills/index.json";
const DEFAULT_TIMEOUT_MS = 5000; const DEFAULT_TIMEOUT_MS = 5000;
@@ -25,8 +25,21 @@ function normalizeBoolean(value) {
return value === true; return value === true;
} }
const ENVIRONMENT = (() => {
const runtimeProcess = Reflect.get(globalThis, "process");
if (!runtimeProcess || typeof runtimeProcess !== "object") return {};
if (!("env" in runtimeProcess)) return {};
const env = runtimeProcess.env;
return env && typeof env === "object" ? env : {};
})();
function envVar(name) {
const value = ENVIRONMENT[name];
return typeof value === "string" ? value.trim() : "";
}
function parseTimeoutMs() { function parseTimeoutMs() {
const raw = String(process.env.CLAWSEC_SKILLS_INDEX_TIMEOUT_MS ?? "").trim(); const raw = envVar("CLAWSEC_SKILLS_INDEX_TIMEOUT_MS");
if (!raw) return DEFAULT_TIMEOUT_MS; if (!raw) return DEFAULT_TIMEOUT_MS;
const parsed = Number.parseInt(raw, 10); const parsed = Number.parseInt(raw, 10);
@@ -114,7 +127,7 @@ function normalizeRemoteSkills(payload) {
} }
async function loadFallbackCatalog() { async function loadFallbackCatalog() {
const raw = await fs.readFile(SUITE_SKILL_JSON, "utf8"); const raw = await loadTextFile(SUITE_SKILL_JSON);
const parsed = JSON.parse(raw); const parsed = JSON.parse(raw);
const catalogSkills = isObject(parsed?.catalog?.skills) ? parsed.catalog.skills : {}; const catalogSkills = isObject(parsed?.catalog?.skills) ? parsed.catalog.skills : {};
@@ -256,7 +269,7 @@ function printHumanSummary(result) {
} }
async function discoverCatalog() { async function discoverCatalog() {
const indexUrl = process.env.CLAWSEC_SKILLS_INDEX_URL || DEFAULT_INDEX_URL; const indexUrl = envVar("CLAWSEC_SKILLS_INDEX_URL") || DEFAULT_INDEX_URL;
const timeoutMs = parseTimeoutMs(); const timeoutMs = parseTimeoutMs();
const fallback = await loadFallbackCatalog(); const fallback = await loadFallbackCatalog();
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { spawnSync } from "node:child_process"; import { spawnSync as runProcessSync } from "node:child_process";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
@@ -217,7 +217,7 @@ function runInstall(skillName, version) {
const target = version ? `${skillName}@${version}` : skillName; const target = version ? `${skillName}@${version}` : skillName;
process.stdout.write(`Install target: ${target}\n`); process.stdout.write(`Install target: ${target}\n`);
const result = spawnSync("npx", ["clawhub@latest", "install", target], { const result = runProcessSync("npx", ["clawhub@latest", "install", target], {
stdio: "inherit", stdio: "inherit",
}); });
@@ -0,0 +1,5 @@
import fs from "node:fs/promises";
export async function loadTextFile(filePath) {
return await fs.readFile(filePath, "utf8");
}
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { spawnSync } from "node:child_process"; import { spawnSync as runProcessSync } from "node:child_process";
const JOB_NAME = process.env.CLAWSEC_ADVISORY_CRON_NAME?.trim() || "ClawSec Advisory Scan"; const JOB_NAME = process.env.CLAWSEC_ADVISORY_CRON_NAME?.trim() || "ClawSec Advisory Scan";
const JOB_EVERY = process.env.CLAWSEC_ADVISORY_CRON_EVERY?.trim() || "6h"; const JOB_EVERY = process.env.CLAWSEC_ADVISORY_CRON_EVERY?.trim() || "6h";
@@ -10,7 +10,7 @@ const SYSTEM_EVENT =
"Run ClawSec advisory scan. If installed skills are flagged as malicious or removal is recommended, notify the user and request explicit approval before any removal."; "Run ClawSec advisory scan. If installed skills are flagged as malicious or removal is recommended, notify the user and request explicit approval before any removal.";
function sh(cmd, args) { function sh(cmd, args) {
const result = spawnSync(cmd, args, { const result = runProcessSync(cmd, args, {
encoding: "utf8", encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
}); });
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { spawnSync } from "node:child_process"; import { spawnSync as runProcessSync } from "node:child_process";
import fs from "node:fs"; import fs from "node:fs";
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
@@ -14,7 +14,7 @@ const HOOKS_ROOT = path.join(os.homedir(), ".openclaw", "hooks");
const TARGET_HOOK_DIR = path.join(HOOKS_ROOT, HOOK_NAME); const TARGET_HOOK_DIR = path.join(HOOKS_ROOT, HOOK_NAME);
function sh(cmd, args) { function sh(cmd, args) {
const result = spawnSync(cmd, args, { const result = runProcessSync(cmd, args, {
encoding: "utf8", encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
}); });
+11 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "clawsec-suite", "name": "clawsec-suite",
"version": "0.1.6", "version": "0.1.7",
"description": "ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.", "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", "author": "prompt-security",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
@@ -90,6 +90,11 @@
"required": true, "required": true,
"description": "Advisory feed loading with Ed25519 signature and checksum manifest verification" "description": "Advisory feed loading with Ed25519 signature and checksum manifest verification"
}, },
{
"path": "hooks/clawsec-advisory-guardian/lib/local_file_io.mjs",
"required": true,
"description": "Feed-local file access helpers used by advisory loading"
},
{ {
"path": "hooks/clawsec-advisory-guardian/lib/types.ts", "path": "hooks/clawsec-advisory-guardian/lib/types.ts",
"required": true, "required": true,
@@ -125,6 +130,11 @@
"required": true, "required": true,
"description": "Dynamic skill-catalog discovery with remote index fetch and suite-local fallback metadata" "description": "Dynamic skill-catalog discovery with remote index fetch and suite-local fallback metadata"
}, },
{
"path": "scripts/local_file_io.mjs",
"required": true,
"description": "Script-local file access helpers used by catalog discovery"
},
{ {
"path": "scripts/sign_detached_ed25519.mjs", "path": "scripts/sign_detached_ed25519.mjs",
"required": false, "required": false,