@denial-web/clawguard 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/docs/ARCHITECTURE.md +15 -3
- package/docs/ARCHITECTURE_ROADMAP.md +10 -0
- package/docs/LAUNCH_CHECKLIST.md +2 -3
- package/docs/NPM_PUBLISHING.md +9 -1
- package/package.json +1 -1
- package/src/cli.js +273 -4
- package/src/config.js +4 -1
- package/examples/safe-openclaw-plugin/dist/index.js +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ClawGuard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Security gate and governance scanner for OpenClaw-style skills, ClawHub installs, MCP configs, and agent tools.
|
|
4
4
|
|
|
5
5
|
ClawGuard helps developers answer one simple question before enabling a skill:
|
|
6
6
|
|
|
@@ -8,6 +8,8 @@ ClawGuard helps developers answer one simple question before enabling a skill:
|
|
|
8
8
|
|
|
9
9
|
This project is compatible with OpenClaw-style skill directories, but it is not affiliated with OpenClaw.
|
|
10
10
|
|
|
11
|
+
ClawGuard is user-triggered today: run it before install, in CI, or as a local review step. It does not yet automatically intercept OpenClaw or ClawHub installs.
|
|
12
|
+
|
|
11
13
|
## Demo Preview
|
|
12
14
|
|
|
13
15
|
[Watch the repeatable demo video](docs/assets/clawguard-demo.mp4), or regenerate it locally with `npm run demo:capture`.
|
|
@@ -41,7 +43,23 @@ Run ClawGuard directly from npm:
|
|
|
41
43
|
npx @denial-web/clawguard scan ./path/to/skill
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
Use gate mode before installing or trusting a skill:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx @denial-web/clawguard gate ./path/to/skill --policy governed
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Gate mode exits with `0` for allow, `1` for warn/review/sandbox decisions, and `2` for block.
|
|
53
|
+
|
|
54
|
+
Use install mode to copy a skill only after the policy gate allows it:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx @denial-web/clawguard install ./path/to/skill --to ./.agents/skills --policy governed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Install mode never executes scanned files or installs dependencies. It refuses warn/review/sandbox/block decisions before copying files.
|
|
61
|
+
|
|
62
|
+
When testing the published package, run `npx` from outside this repository. From inside the ClawGuard source checkout, use the local commands instead:
|
|
45
63
|
|
|
46
64
|
```bash
|
|
47
65
|
npm test
|
|
@@ -172,6 +190,8 @@ Findings:
|
|
|
172
190
|
## Roadmap
|
|
173
191
|
|
|
174
192
|
- `clawguard scan <path>` CLI
|
|
193
|
+
- `clawguard gate <path>` policy gate
|
|
194
|
+
- `clawguard install <path> --to <dir>` guarded copy installer
|
|
175
195
|
- OpenClaw `SKILL.md` metadata mismatch checks
|
|
176
196
|
- `.clawguard.json` policy/config support
|
|
177
197
|
- MCP/plugin config scanning
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ClawGuard Architecture
|
|
2
2
|
|
|
3
|
-
ClawGuard is an independent static governance
|
|
3
|
+
ClawGuard is an independent static governance gate for OpenClaw-style skills, ClawHub packages, and MCP/tool configs. It should stay compatible with OpenClaw without pretending to be OpenClaw.
|
|
4
4
|
|
|
5
5
|
## Mission
|
|
6
6
|
|
|
@@ -18,11 +18,13 @@ The product should be small, explainable, and useful in three moments:
|
|
|
18
18
|
|
|
19
19
|
1. CLI
|
|
20
20
|
|
|
21
|
-
Current
|
|
21
|
+
Current surfaces: `clawguard scan <path>`, `clawguard gate <path>`, and `clawguard install <path> --to <dir>`.
|
|
22
22
|
|
|
23
23
|
Target surface:
|
|
24
24
|
|
|
25
25
|
- `clawguard scan <path>`
|
|
26
|
+
- `clawguard gate <path>`
|
|
27
|
+
- `clawguard install <path> --to <dir>`
|
|
26
28
|
- `clawguard scan-skill <skill-dir>`
|
|
27
29
|
- `clawguard scan-workspace <workspace-dir>`
|
|
28
30
|
- `clawguard scan-mcp <config-path>`
|
|
@@ -47,7 +49,17 @@ The product should be small, explainable, and useful in three moments:
|
|
|
47
49
|
|
|
48
50
|
6. Install gate
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
Current surfaces: `clawguard gate <path>` and `clawguard install <path> --to <dir>`.
|
|
53
|
+
|
|
54
|
+
Gate mode maps scan results into allow, warn, sandbox, or block decisions and exits with install-wrapper friendly codes:
|
|
55
|
+
|
|
56
|
+
- `0`: allow
|
|
57
|
+
- `1`: warn, manual review, sandbox required, or dual approval
|
|
58
|
+
- `2`: block
|
|
59
|
+
|
|
60
|
+
Install mode is a conservative wrapper: it scans first, copies only on `allow`, refuses non-allow decisions before copying, rejects symlink sources, and never executes scanned files or dependency install scripts.
|
|
61
|
+
|
|
62
|
+
Future surface: direct integration pattern around OpenClaw/ClawHub install/update flows. It should scan a downloaded bundle before the user enables it.
|
|
51
63
|
|
|
52
64
|
## Trust Boundaries
|
|
53
65
|
|
|
@@ -99,14 +99,24 @@ Build:
|
|
|
99
99
|
- Decisions: allow, warn, manual review, sandbox required, dual approval, block.
|
|
100
100
|
- `.clawguard.json` config.
|
|
101
101
|
- Suppressions with reason and optional expiry.
|
|
102
|
+
- Install gate command with policy exit codes.
|
|
103
|
+
- Guarded install command that copies only after an `allow` decision.
|
|
102
104
|
- Policy check command for saved reports.
|
|
103
105
|
|
|
104
106
|
Success demo:
|
|
105
107
|
|
|
106
108
|
```bash
|
|
109
|
+
clawguard gate ./skills/my-skill --policy governed
|
|
110
|
+
clawguard install ./skills/my-skill --to ./.agents/skills --policy governed
|
|
107
111
|
clawguard scan ./skills --policy governed --fail-on-policy
|
|
108
112
|
```
|
|
109
113
|
|
|
114
|
+
Gate exit codes:
|
|
115
|
+
|
|
116
|
+
- `0`: allow
|
|
117
|
+
- `1`: warn, manual review, sandbox required, or dual approval
|
|
118
|
+
- `2`: block
|
|
119
|
+
|
|
110
120
|
## Phase 4: Reports and CI
|
|
111
121
|
|
|
112
122
|
Goal: make ClawGuard easy to adopt by maintainers.
|
package/docs/LAUNCH_CHECKLIST.md
CHANGED
|
@@ -25,8 +25,8 @@ Use this before sharing ClawGuard publicly.
|
|
|
25
25
|
|
|
26
26
|
## GitHub Repository
|
|
27
27
|
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
28
|
+
- [x] Repo description: `Governance and security scanner for OpenClaw skills, ClawHub installs, MCP configs, and skill dependencies.`
|
|
29
|
+
- [x] Topics: `openclaw`, `clawhub`, `mcp`, `security`, `ai-agents`, `scanner`, `governance`, `supply-chain`.
|
|
30
30
|
- [x] License is visible.
|
|
31
31
|
- [x] Security policy is visible.
|
|
32
32
|
- [x] GitHub Action example is documented.
|
|
@@ -60,5 +60,4 @@ Include:
|
|
|
60
60
|
|
|
61
61
|
- Record a short GIF or video using [docs/DEMO_SCRIPT.md](DEMO_SCRIPT.md).
|
|
62
62
|
- Regenerate demo assets with `npm run demo:capture` after visual UI changes.
|
|
63
|
-
- Apply the repository description and topics in GitHub after the repo is created.
|
|
64
63
|
- Validate against real installed skill folders once a public skill archive or local ClawHub install is available.
|
package/docs/NPM_PUBLISHING.md
CHANGED
|
@@ -23,7 +23,7 @@ Provider: GitHub Actions
|
|
|
23
23
|
Organization or user: denial-web
|
|
24
24
|
Repository: clawguard
|
|
25
25
|
Workflow filename: publish.yml
|
|
26
|
-
Environment name:
|
|
26
|
+
Environment name: blank
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
After the trusted publisher is connected, publish from GitHub Actions:
|
|
@@ -62,5 +62,13 @@ The release event will trigger `.github/workflows/publish.yml`.
|
|
|
62
62
|
After publishing, test the package from npm:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
+
cd /private/tmp
|
|
65
66
|
npx @denial-web/clawguard scan examples/risky-skill
|
|
66
67
|
```
|
|
68
|
+
|
|
69
|
+
When testing from outside the repository, point the scan command at a real skill path. For example:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd /private/tmp
|
|
73
|
+
npx @denial-web/clawguard scan /Users/hy/CascadeProjects/ClawGuard/examples/risky-skill
|
|
74
|
+
```
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
|
27
27
|
|
|
28
28
|
const command = args[0];
|
|
29
29
|
|
|
30
|
-
if (!["scan", "scan-workspace"].includes(command)) {
|
|
30
|
+
if (!["scan", "scan-workspace", "gate", "install"].includes(command)) {
|
|
31
31
|
console.error(`Unknown command: ${command}`);
|
|
32
32
|
printHelp();
|
|
33
33
|
process.exit(1);
|
|
@@ -54,15 +54,28 @@ try {
|
|
|
54
54
|
await writeReportFile(options.htmlPath, createHtmlReport(result));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
if (
|
|
57
|
+
if (command === "install") {
|
|
58
|
+
const install = await handleInstall(result, options);
|
|
59
|
+
if (options.json) {
|
|
60
|
+
console.log(JSON.stringify(createInstallResult(result, install), null, 2));
|
|
61
|
+
} else {
|
|
62
|
+
printInstallResult(result, install);
|
|
63
|
+
}
|
|
64
|
+
} else if (command === "gate") {
|
|
65
|
+
if (options.json) {
|
|
66
|
+
console.log(JSON.stringify(createGateResult(result), null, 2));
|
|
67
|
+
} else {
|
|
68
|
+
printGateResult(result, options);
|
|
69
|
+
}
|
|
70
|
+
} else if (options.json) {
|
|
58
71
|
console.log(JSON.stringify(result, null, 2));
|
|
59
72
|
} else {
|
|
60
73
|
printHumanResult(result, options);
|
|
61
74
|
}
|
|
62
75
|
|
|
63
|
-
process.exit(shouldFail(result, options) ? 2 : 0);
|
|
76
|
+
process.exit(["gate", "install"].includes(command) ? gateExitCode(result.policy.decision) : shouldFail(result, options) ? 2 : 0);
|
|
64
77
|
} catch (error) {
|
|
65
|
-
console.error(
|
|
78
|
+
console.error(`${commandLabel(command)} failed: ${error.message}`);
|
|
66
79
|
process.exit(1);
|
|
67
80
|
}
|
|
68
81
|
|
|
@@ -71,6 +84,8 @@ function printHelp() {
|
|
|
71
84
|
|
|
72
85
|
Usage:
|
|
73
86
|
clawguard scan <path> [--json] [--policy <preset>] [--fail-on <level>]
|
|
87
|
+
clawguard gate <path> [--json] [--policy <preset>]
|
|
88
|
+
clawguard install <path> --to <dir> [--policy <preset>] [--dry-run]
|
|
74
89
|
clawguard scan-workspace <path> [--json] [--policy <preset>]
|
|
75
90
|
npm run scan -- <path>
|
|
76
91
|
|
|
@@ -89,8 +104,19 @@ Options:
|
|
|
89
104
|
Default: manual_review.
|
|
90
105
|
--max-file-size <size> Skip individual files larger than this size. Examples: 512kb, 1mb.
|
|
91
106
|
Default: 1mb.
|
|
107
|
+
--to <dir> Install destination parent directory for install mode.
|
|
108
|
+
--name <name> Install folder/file name. Defaults to the source basename.
|
|
109
|
+
--dry-run Run install gate and show the destination without copying files.
|
|
110
|
+
|
|
111
|
+
Gate exit codes:
|
|
112
|
+
0 = allow
|
|
113
|
+
1 = warn, manual review, sandbox required, or dual approval
|
|
114
|
+
2 = block
|
|
92
115
|
|
|
93
116
|
Examples:
|
|
117
|
+
npx @denial-web/clawguard gate ./skills/my-skill
|
|
118
|
+
npx @denial-web/clawguard gate ./skills/my-skill --policy governed
|
|
119
|
+
npx @denial-web/clawguard install ./skills/my-skill --to ./.agents/skills --policy governed
|
|
94
120
|
npm run scan -- examples/risky-skill
|
|
95
121
|
npm run scan -- examples/metadata-mismatch-skill --policy governed --fail-on-policy
|
|
96
122
|
npm run scan -- examples/metadata-mismatch-skill --html clawguard.html
|
|
@@ -171,6 +197,229 @@ function printHumanResult(result, options) {
|
|
|
171
197
|
}
|
|
172
198
|
}
|
|
173
199
|
|
|
200
|
+
function printGateResult(result, options) {
|
|
201
|
+
const decision = result.policy.decision;
|
|
202
|
+
console.log(`ClawGuard gate: ${result.target}`);
|
|
203
|
+
console.log(`Decision: ${formatDecision(decision)}`);
|
|
204
|
+
console.log(`Risk: ${result.level.toUpperCase()} (${result.score}/100)`);
|
|
205
|
+
console.log(`Policy: ${result.policy.preset}`);
|
|
206
|
+
console.log(`Exit code: ${gateExitCode(decision)}`);
|
|
207
|
+
console.log(`Reason: ${result.policy.reason}`);
|
|
208
|
+
|
|
209
|
+
if (result.configPath) {
|
|
210
|
+
console.log(`Config: ${result.configPath}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (result.policy.requiredActions.length > 0) {
|
|
214
|
+
console.log(`Required actions: ${result.policy.requiredActions.join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (result.findings.length > 0) {
|
|
218
|
+
console.log(`Findings: ${result.findings.length}`);
|
|
219
|
+
const topFindings = result.findings.slice(0, 5);
|
|
220
|
+
for (const finding of topFindings) {
|
|
221
|
+
console.log(`- [${finding.severity.toUpperCase()}] ${finding.title}`);
|
|
222
|
+
console.log(` ${finding.file}:${finding.line}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (result.findings.length > topFindings.length) {
|
|
226
|
+
console.log(`- ${result.findings.length - topFindings.length} more finding(s). Run scan for full details.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (decision === "allow") {
|
|
231
|
+
console.log("\nGate result: safe to continue under the selected policy.");
|
|
232
|
+
} else if (decision === "block") {
|
|
233
|
+
console.log("\nGate result: block install or trust until reviewed.");
|
|
234
|
+
} else {
|
|
235
|
+
console.log("\nGate result: pause before install or trust.");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function handleInstall(result, options) {
|
|
240
|
+
const decision = result.policy.decision;
|
|
241
|
+
const sourcePath = path.resolve(options.target);
|
|
242
|
+
const destination = resolveInstallDestination(sourcePath, options);
|
|
243
|
+
const install = {
|
|
244
|
+
destination,
|
|
245
|
+
dryRun: options.dryRun,
|
|
246
|
+
installed: false,
|
|
247
|
+
skipped: decision !== "allow"
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
if (decision !== "allow") {
|
|
251
|
+
return install;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!options.installDir) {
|
|
255
|
+
throw new Error("install requires --to <dir>. ClawGuard will not guess an install location.");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (options.dryRun) {
|
|
259
|
+
return install;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await assertInstallableSource(sourcePath);
|
|
263
|
+
await assertDestinationAvailable(destination);
|
|
264
|
+
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
265
|
+
await fs.cp(sourcePath, destination, {
|
|
266
|
+
recursive: true,
|
|
267
|
+
errorOnExist: true,
|
|
268
|
+
force: false,
|
|
269
|
+
verbatimSymlinks: true
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
install.installed = true;
|
|
273
|
+
install.skipped = false;
|
|
274
|
+
return install;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function printInstallResult(result, install) {
|
|
278
|
+
const decision = result.policy.decision;
|
|
279
|
+
console.log(`ClawGuard install: ${result.target}`);
|
|
280
|
+
console.log(`Decision: ${formatDecision(decision)}`);
|
|
281
|
+
console.log(`Risk: ${result.level.toUpperCase()} (${result.score}/100)`);
|
|
282
|
+
console.log(`Policy: ${result.policy.preset}`);
|
|
283
|
+
console.log(`Exit code: ${gateExitCode(decision)}`);
|
|
284
|
+
console.log(`Destination: ${install.destination ?? "not selected"}`);
|
|
285
|
+
console.log(`Installed: ${install.installed ? "yes" : "no"}`);
|
|
286
|
+
|
|
287
|
+
if (install.dryRun) {
|
|
288
|
+
console.log("Dry run: yes");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (result.policy.requiredActions.length > 0) {
|
|
292
|
+
console.log(`Required actions: ${result.policy.requiredActions.join(", ")}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (decision === "allow" && install.installed) {
|
|
296
|
+
console.log("\nInstall result: copied after passing the selected policy.");
|
|
297
|
+
} else if (decision === "allow" && install.dryRun) {
|
|
298
|
+
console.log("\nInstall result: dry run passed; no files were copied.");
|
|
299
|
+
} else if (decision === "allow") {
|
|
300
|
+
console.log("\nInstall result: ready to copy after passing the selected policy.");
|
|
301
|
+
} else if (decision === "block") {
|
|
302
|
+
console.log("\nInstall result: blocked before copying files.");
|
|
303
|
+
} else {
|
|
304
|
+
console.log("\nInstall result: paused before copying files.");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function createInstallResult(result, install) {
|
|
309
|
+
return {
|
|
310
|
+
...createGateResult(result),
|
|
311
|
+
destination: install.destination,
|
|
312
|
+
installed: install.installed,
|
|
313
|
+
dryRun: install.dryRun,
|
|
314
|
+
skipped: install.skipped
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function createGateResult(result) {
|
|
319
|
+
return {
|
|
320
|
+
target: result.target,
|
|
321
|
+
decision: result.policy.decision,
|
|
322
|
+
exitCode: gateExitCode(result.policy.decision),
|
|
323
|
+
risk: {
|
|
324
|
+
level: result.level,
|
|
325
|
+
score: result.score
|
|
326
|
+
},
|
|
327
|
+
policy: {
|
|
328
|
+
preset: result.policy.preset,
|
|
329
|
+
reason: result.policy.reason,
|
|
330
|
+
requiredActions: result.policy.requiredActions
|
|
331
|
+
},
|
|
332
|
+
summary: result.summary,
|
|
333
|
+
findings: result.findings.map((finding) => ({
|
|
334
|
+
ruleId: finding.ruleId,
|
|
335
|
+
severity: finding.severity,
|
|
336
|
+
title: finding.title,
|
|
337
|
+
file: finding.file,
|
|
338
|
+
line: finding.line,
|
|
339
|
+
recommendation: finding.recommendation
|
|
340
|
+
}))
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function resolveInstallDestination(sourcePath, options) {
|
|
345
|
+
if (!options.installDir) {
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const installName = options.installName ?? path.basename(sourcePath);
|
|
350
|
+
return path.resolve(options.installDir, installName);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function assertInstallableSource(sourcePath) {
|
|
354
|
+
const stats = await fs.lstat(sourcePath);
|
|
355
|
+
|
|
356
|
+
if (stats.isSymbolicLink()) {
|
|
357
|
+
throw new Error("install source cannot be a symlink");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (stats.isDirectory()) {
|
|
361
|
+
await assertDirectoryHasNoSymlinks(sourcePath);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function assertDirectoryHasNoSymlinks(directory) {
|
|
366
|
+
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
367
|
+
|
|
368
|
+
for (const entry of entries) {
|
|
369
|
+
const entryPath = path.join(directory, entry.name);
|
|
370
|
+
|
|
371
|
+
if (entry.isSymbolicLink()) {
|
|
372
|
+
throw new Error(`install source contains a symlink: ${entryPath}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (entry.isDirectory()) {
|
|
376
|
+
await assertDirectoryHasNoSymlinks(entryPath);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function assertDestinationAvailable(destination) {
|
|
382
|
+
try {
|
|
383
|
+
await fs.lstat(destination);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (error.code === "ENOENT") {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
throw new Error(`install destination already exists: ${destination}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function commandLabel(commandName) {
|
|
396
|
+
if (commandName === "gate") {
|
|
397
|
+
return "Gate";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (commandName === "install") {
|
|
401
|
+
return "Install";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return "Scan";
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function formatDecision(decision) {
|
|
408
|
+
return decision.replaceAll("_", " ").toUpperCase();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function gateExitCode(decision) {
|
|
412
|
+
if (decision === "allow") {
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (decision === "block") {
|
|
417
|
+
return 2;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return 1;
|
|
421
|
+
}
|
|
422
|
+
|
|
174
423
|
function parseOptions(values) {
|
|
175
424
|
const options = {
|
|
176
425
|
json: false,
|
|
@@ -182,6 +431,9 @@ function parseOptions(values) {
|
|
|
182
431
|
policy: undefined,
|
|
183
432
|
policyFailOn: undefined,
|
|
184
433
|
maxFileSizeBytes: undefined,
|
|
434
|
+
installDir: undefined,
|
|
435
|
+
installName: undefined,
|
|
436
|
+
dryRun: false,
|
|
185
437
|
target: "."
|
|
186
438
|
};
|
|
187
439
|
const paths = [];
|
|
@@ -258,6 +510,23 @@ function parseOptions(values) {
|
|
|
258
510
|
continue;
|
|
259
511
|
}
|
|
260
512
|
|
|
513
|
+
if (value === "--to") {
|
|
514
|
+
options.installDir = requireNextValue(values, index, "--to");
|
|
515
|
+
index += 1;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (value === "--name") {
|
|
520
|
+
options.installName = requireNextValue(values, index, "--name");
|
|
521
|
+
index += 1;
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (value === "--dry-run") {
|
|
526
|
+
options.dryRun = true;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
|
|
261
530
|
if (value.startsWith("--")) {
|
|
262
531
|
throw new Error(`Unknown option: ${value}`);
|
|
263
532
|
}
|
package/src/config.js
CHANGED
|
@@ -61,7 +61,10 @@ export function mergeConfig(config, cliOptions = {}) {
|
|
|
61
61
|
json: Boolean(cliOptions.json),
|
|
62
62
|
configPath: cliOptions.configPath,
|
|
63
63
|
htmlPath: cliOptions.htmlPath,
|
|
64
|
-
sarifPath: cliOptions.sarifPath
|
|
64
|
+
sarifPath: cliOptions.sarifPath,
|
|
65
|
+
installDir: cliOptions.installDir,
|
|
66
|
+
installName: cliOptions.installName,
|
|
67
|
+
dryRun: Boolean(cliOptions.dryRun)
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const safe = true;
|