From 345d51852409ef3857caed8ce70bd9d9321239d0 Mon Sep 17 00:00:00 2001 From: Saturday-boyi <2174084306@qq.com> Date: Tue, 8 Sep 2026 09:15:42 +0800 Subject: [PATCH] fix: reject unknown CLI options before scanning --- CHANGELOG.md | 6 ++++++ README.md | 2 ++ package.json | 2 +- src/cli.js | 2 ++ test/skill-audit.test.js | 25 +++++++++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c5b32..e1272b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project are documented here, following [Keep a Changelog](https://keepachangelog.com/) and semantic versioning. +## [0.1.3] - 2026-09-08 + +### Fixed + +- Reject unknown CLI options with an actionable error and exit code 2 instead of silently ignoring them. + ## [0.1.2] - 2026-08-23 ### Fixed diff --git a/README.md b/README.md index 82dd6cc..cd41541 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ npx @royalpinto007/skill-audit ./my-skill --format sarif > skill-audit.sarif **Exit codes:** `0` clean (below threshold) · `1` findings at/above `--fail-on` · `2` bad usage. +Unknown options are rejected with exit code `2` before scanning, so a misspelled flag cannot silently change the scan. + ## In CI (GitHub Action) One line — drop it into any workflow. It gates the job and can upload findings to the Security tab: diff --git a/package.json b/package.json index f507413..31d2dd3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@royalpinto007/skill-audit", - "version": "0.1.2", + "version": "0.1.3", "description": "Security scanner for agent skills. Scan a Claude/agent Skill for prompt-injection, dangerous shell, secret access, and exfiltration before you trust it. Zero dependencies, SARIF output, npx skill-audit .", "type": "module", "bin": { diff --git a/src/cli.js b/src/cli.js index 94856dd..70e6d85 100644 --- a/src/cli.js +++ b/src/cli.js @@ -42,12 +42,14 @@ function parseArgs(argv) { else if (a.startsWith("--format=")) opts.format = a.split("=")[1]; else if (a.startsWith("--fail-on=")) opts.failOn = a.split("=")[1]; else if (!a.startsWith("-")) opts.path = a; + else return { error: `skill-audit: unknown option "${a}"\n` }; } return opts; } export function run(argv, { version }) { const o = parseArgs(argv); + if (o.error) { process.stderr.write(o.error); return 2; } if (o.help) { process.stdout.write(HELP); return 0; } if (o.version) { process.stdout.write(version + "\n"); return 0; } if (o.rules) { diff --git a/test/skill-audit.test.js b/test/skill-audit.test.js index 31f4c63..37166ba 100644 --- a/test/skill-audit.test.js +++ b/test/skill-audit.test.js @@ -1,5 +1,6 @@ import { test } from "node:test"; import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { basename, dirname, join } from "node:path"; @@ -10,6 +11,30 @@ import { RULES } from "../src/rules.js"; const here = dirname(fileURLToPath(import.meta.url)); const fixture = (n) => join(here, "fixtures", n); +test("CLI rejects unknown options before scanning", () => { + const cli = join(here, "..", "bin", "skill-audit.js"); + for (const args of [["--output", "report.json"], ["--output=report.json"], ["-x"]]) { + const result = spawnSync(process.execPath, [cli, fixture("clean-skill"), ...args], { + encoding: "utf8", + }); + assert.equal(result.status, 2, `${args.join(" ")}: ${result.stderr}`); + assert.match(result.stderr, /unknown option/i); + assert.ok(result.stderr.includes(args[0])); + assert.equal(result.stdout, ""); + } +}); + +test("CLI still accepts supported value option forms", () => { + const cli = join(here, "..", "bin", "skill-audit.js"); + for (const args of [["--format", "json", "--fail-on", "info"], ["--format=json", "--fail-on=info"]]) { + const result = spawnSync(process.execPath, [cli, fixture("clean-skill"), ...args], { + encoding: "utf8", + }); + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(JSON.parse(result.stdout).findings, []); + } +}); + test("malicious skill triggers the expected high-signal rules", () => { const { findings } = scanSkill(fixture("malicious-skill")); const ids = new Set(findings.map((f) => f.rule));