mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
fix(clawsec-suite): reduce moderation false positives in publish payload (#195)
This commit is contained in:
@@ -10,3 +10,6 @@ build/
|
|||||||
.env
|
.env
|
||||||
.venv/
|
.venv/
|
||||||
.cache/
|
.cache/
|
||||||
|
|
||||||
|
# Exclude local test harness files from published payloads.
|
||||||
|
test/
|
||||||
|
|||||||
@@ -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,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"],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user