Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.6] - 2026-09-11

### Fixed

- Reject multiple positional paths with exit code 2 instead of silently scanning only the last target.

## [0.1.5] - 2026-09-11

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,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.

Pass at most one target path per invocation; extra paths are rejected with exit code `2` before scanning. With no path, the current directory is scanned.

Unknown options are rejected with exit code `2` before scanning, so a misspelled flag cannot silently change the scan.

## In CI (GitHub Action)
Expand Down
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@royalpinto007/skill-audit",
"version": "0.1.5",
"version": "0.1.6",
"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 <path>.",
"type": "module",
"bin": {
Expand Down
5 changes: 4 additions & 1 deletion src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ function parseArgs(argv) {
else if (a === "--fail-on") opts.failOn = argv[++i];
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 if (!a.startsWith("-")) {
if (opts.path !== null) return { error: "skill-audit: only one path is supported; scan each target separately\n" };
opts.path = a;
}
else return { error: `skill-audit: unknown option "${a}"\n` };
}
return opts;
Expand Down
40 changes: 40 additions & 0 deletions test/skill-audit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,46 @@ test("CLI still accepts supported value option forms", () => {
}
});

test("CLI rejects multiple positional paths before scanning", () => {
const cli = join(here, "..", "bin", "skill-audit.js");
const clean = fixture("clean-skill");
const malicious = fixture("malicious-skill");
for (const args of [
[malicious, clean],
[clean, malicious],
[fixture("missing-skill"), clean],
[clean, clean],
["", clean],
[malicious, "--format", "json", clean],
["--format=sarif", malicious, "--fail-on", "high", clean],
]) {
const result = spawnSync(process.execPath, [cli, ...args], { encoding: "utf8" });
assert.equal(result.status, 2, `${args.join(" ")}: ${result.stderr}`);
assert.match(result.stderr, /only one.*path/i);
assert.equal(result.stdout, "");
}
});

test("CLI preserves default and single paths with value options", () => {
const cli = join(here, "..", "bin", "skill-audit.js");
const clean = fixture("clean-skill");
for (const args of [
["--format", "json"],
[clean, "--format", "json", "--fail-on", "high"],
["--format", "json", clean, "--fail-on", "high"],
["--format=json", "--fail-on=high", clean],
]) {
const result = spawnSync(process.execPath, [cli, ...args], {
cwd: args.includes(clean) ? fixture("malicious-skill") : clean, encoding: "utf8",
});
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stderr, "");
const report = JSON.parse(result.stdout);
assert.deepEqual(report.findings, []);
assert.equal(report.filesScanned, scanSkill(clean).files);
}
});

test("malicious skill triggers the expected high-signal rules", () => {
const { findings } = scanSkill(fixture("malicious-skill"));
const ids = new Set(findings.map((f) => f.rule));
Expand Down
Loading