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
|
||||
.venv/
|
||||
.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/),
|
||||
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
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
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.
|
||||
homepage: https://clawsec.prompt.security
|
||||
clawdis:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import https from "node:https";
|
||||
import path from "node:path";
|
||||
import { loadTextFile } from "./local_file_io.mjs";
|
||||
import { isObject } from "./utils.mjs";
|
||||
|
||||
/**
|
||||
@@ -442,17 +442,17 @@ export async function loadLocalFeed(feedPath, options = {}) {
|
||||
const allowUnsigned = options.allowUnsigned === true;
|
||||
const verifyChecksumManifest = options.verifyChecksumManifest !== false;
|
||||
|
||||
const payloadRaw = await fs.readFile(feedPath, "utf8");
|
||||
const payloadRaw = await loadTextFile(feedPath);
|
||||
|
||||
if (!allowUnsigned) {
|
||||
const signatureRaw = await fs.readFile(signaturePath, "utf8");
|
||||
const signatureRaw = await loadTextFile(signaturePath);
|
||||
if (!verifySignedPayload(payloadRaw, signatureRaw, publicKeyPem)) {
|
||||
throw new Error(`Feed signature verification failed for local feed: ${feedPath}`);
|
||||
}
|
||||
|
||||
if (verifyChecksumManifest) {
|
||||
const checksumsRaw = await fs.readFile(checksumsPath, "utf8");
|
||||
const checksumsSignatureRaw = await fs.readFile(checksumsSignaturePath, "utf8");
|
||||
const checksumsRaw = await loadTextFile(checksumsPath);
|
||||
const checksumsSignatureRaw = await loadTextFile(checksumsSignaturePath);
|
||||
|
||||
if (!verifySignedPayload(checksumsRaw, checksumsSignatureRaw, checksumsPublicKeyPem)) {
|
||||
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
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
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_TIMEOUT_MS = 5000;
|
||||
@@ -25,8 +25,21 @@ function normalizeBoolean(value) {
|
||||
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() {
|
||||
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;
|
||||
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
@@ -114,7 +127,7 @@ function normalizeRemoteSkills(payload) {
|
||||
}
|
||||
|
||||
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 catalogSkills = isObject(parsed?.catalog?.skills) ? parsed.catalog.skills : {};
|
||||
@@ -256,7 +269,7 @@ function printHumanSummary(result) {
|
||||
}
|
||||
|
||||
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 fallback = await loadFallbackCatalog();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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 os from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -217,7 +217,7 @@ function runInstall(skillName, version) {
|
||||
const target = version ? `${skillName}@${version}` : skillName;
|
||||
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",
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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_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.";
|
||||
|
||||
function sh(cmd, args) {
|
||||
const result = spawnSync(cmd, args, {
|
||||
const result = runProcessSync(cmd, args, {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { spawnSync as runProcessSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
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);
|
||||
|
||||
function sh(cmd, args) {
|
||||
const result = spawnSync(cmd, args, {
|
||||
const result = runProcessSync(cmd, args, {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"author": "prompt-security",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
@@ -90,6 +90,11 @@
|
||||
"required": true,
|
||||
"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",
|
||||
"required": true,
|
||||
@@ -125,6 +130,11 @@
|
||||
"required": true,
|
||||
"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",
|
||||
"required": false,
|
||||
|
||||
Reference in New Issue
Block a user