Skip to content

Commit 972170d

Browse files
committed
add GitHub Actions workflow, README badges and build script
1 parent 3171158 commit 972170d

5 files changed

Lines changed: 100 additions & 24 deletions

File tree

.github/workflows/ci.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
node-version: [18.x, 20.x, 22.x]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Use Node.js ${{ matrix.node-version }}
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: ${{ matrix.node-version }}
23+
cache: 'npm'
24+
25+
- run: npm ci
26+
- run: npm run build
27+
- run: npm test
28+
- run: npm run smoke
29+
30+
publish:
31+
needs: test
32+
runs-on: ubuntu-latest
33+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
34+
35+
steps:
36+
- uses: actions/checkout@v4
37+
- uses: actions/setup-node@v4
38+
with:
39+
node-version: '20.x'
40+
registry-url: 'https://registry.npmjs.org'
41+
42+
- run: npm ci
43+
- run: npm run build
44+
45+
- name: Publish to npm
46+
run: npm publish --access public
47+
env:
48+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
49+
continue-on-error: true

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# AgentGuard
22

3+
[![CI](https://github.com/krishkumar/agentguard/workflows/CI/badge.svg)](https://github.com/krishkumar/agentguard/actions)
4+
[![npm version](https://badge.fury.io/js/ai-agentguard.svg)](https://www.npmjs.com/package/ai-agentguard)
5+
[![npm downloads](https://img.shields.io/npm/dm/ai-agentguard.svg)](https://www.npmjs.com/package/ai-agentguard)
6+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7+
[![Node.js Version](https://img.shields.io/node/v/ai-agentguard.svg)](https://nodejs.org/)
8+
39
**Work safely with agents like Claude Code.**
410

511
AI coding agents are powerful, but with great power comes `rm -rf /`.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"agentguard-shell": "./dist/bin/agentguard-shell.js"
1313
},
1414
"scripts": {
15-
"build": "tsc && chmod +x dist/bin/*.js && mkdir -p dist/bin/wrappers && cp src/bin/wrappers/* dist/bin/wrappers/ && chmod +x dist/bin/wrappers/*",
15+
"build": "./scripts/build.sh",
1616
"dev": "tsc --watch",
1717
"test": "vitest --run",
1818
"test:watch": "vitest",

scripts/build.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "🔨 Building AgentGuard..."
5+
6+
# Compile TypeScript
7+
npx tsc
8+
9+
# Make executables executable if they exist
10+
if [ -d "dist/bin" ]; then
11+
find dist/bin -name "*.js" -exec chmod +x {} \; 2>/dev/null || true
12+
fi
13+
14+
# Copy wrapper scripts if they exist
15+
mkdir -p dist/bin/wrappers
16+
if [ -d "src/bin/wrappers" ] && [ "$(ls -A src/bin/wrappers 2>/dev/null)" ]; then
17+
cp src/bin/wrappers/* dist/bin/wrappers/ 2>/dev/null || true
18+
chmod +x dist/bin/wrappers/* 2>/dev/null || true
19+
fi
20+
21+
echo "✅ Build complete"

tests/unit/rule-engine.test.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ describe('RuleEngine', () => {
242242

243243
expect(result.action).toBe(ValidationAction.BLOCK);
244244
// May be blocked by catastrophic path detection OR chained command validation
245-
expect(result.reason).toMatch(/Catastrophic path|Chained command blocked/);
245+
expect(result.reason).toMatch(/critical system\/user|Chained command blocked/);
246246
});
247247

248248
it('blocks chain if first segment is blocked', () => {
@@ -339,7 +339,7 @@ describe('RuleEngine', () => {
339339

340340
expect(result.action).toBe(ValidationAction.BLOCK);
341341
// May be blocked by catastrophic path detection OR chained command validation
342-
expect(result.reason).toMatch(/Catastrophic path|Chained command blocked/);
342+
expect(result.reason).toMatch(/critical system\/user|Chained command blocked/);
343343
});
344344

345345
it('allows chain with default policy when no rules match', () => {
@@ -356,7 +356,7 @@ describe('RuleEngine', () => {
356356
});
357357
});
358358

359-
describe('Catastrophic path detection', () => {
359+
describe('critical system\/user detection', () => {
360360
/**
361361
* Tests for the catastrophic path detection feature.
362362
* This catches attacks like "rm -rf node_modules dist ~/" where dangerous paths
@@ -386,8 +386,8 @@ describe('RuleEngine', () => {
386386
const result = engine.validate(command, rules);
387387

388388
expect(result.action).toBe(ValidationAction.BLOCK);
389-
expect(result.reason).toContain('Catastrophic path');
390-
expect(result.reason).toContain('critical system/user files');
389+
expect(result.reason).toContain('critical system\/user');
390+
expect(result.reason).toContain('critical system\/user files');
391391
});
392392

393393
it('should block rm -rf with ~ (tilde) hidden among arguments', () => {
@@ -399,7 +399,7 @@ describe('RuleEngine', () => {
399399
const result = engine.validate(command, rules);
400400

401401
expect(result.action).toBe(ValidationAction.BLOCK);
402-
expect(result.reason).toContain('Catastrophic path');
402+
expect(result.reason).toContain('critical system\/user');
403403
});
404404

405405
it('should block rm -rf /', () => {
@@ -409,7 +409,7 @@ describe('RuleEngine', () => {
409409
const result = engine.validate(command, rules);
410410

411411
expect(result.action).toBe(ValidationAction.BLOCK);
412-
expect(result.reason).toContain('Catastrophic path');
412+
expect(result.reason).toContain('critical system\/user');
413413
});
414414

415415
it('should block rm -rf /home', () => {
@@ -419,7 +419,7 @@ describe('RuleEngine', () => {
419419
const result = engine.validate(command, rules);
420420

421421
expect(result.action).toBe(ValidationAction.BLOCK);
422-
expect(result.reason).toContain('Catastrophic path');
422+
expect(result.reason).toContain('critical system\/user');
423423
});
424424

425425
it('should block rm -rf /etc', () => {
@@ -429,7 +429,7 @@ describe('RuleEngine', () => {
429429
const result = engine.validate(command, rules);
430430

431431
expect(result.action).toBe(ValidationAction.BLOCK);
432-
expect(result.reason).toContain('Catastrophic path');
432+
expect(result.reason).toContain('critical system\/user');
433433
});
434434

435435
it('should block rm -r (without -f) with catastrophic paths', () => {
@@ -440,7 +440,7 @@ describe('RuleEngine', () => {
440440
const result = engine.validate(command, rules);
441441

442442
expect(result.action).toBe(ValidationAction.BLOCK);
443-
expect(result.reason).toContain('Catastrophic path');
443+
expect(result.reason).toContain('critical system\/user');
444444
});
445445

446446
it('should block rm with combined flags like -fR', () => {
@@ -451,7 +451,7 @@ describe('RuleEngine', () => {
451451
const result = engine.validate(command, rules);
452452

453453
expect(result.action).toBe(ValidationAction.BLOCK);
454-
expect(result.reason).toContain('Catastrophic path');
454+
expect(result.reason).toContain('critical system\/user');
455455
});
456456

457457
it('should NOT block rm -rf with safe paths only', () => {
@@ -492,7 +492,7 @@ describe('RuleEngine', () => {
492492
const result = engine.validate(command, rules);
493493

494494
expect(result.action).toBe(ValidationAction.BLOCK);
495-
expect(result.reason).toContain('Catastrophic path');
495+
expect(result.reason).toContain('critical system\/user');
496496
});
497497

498498
it('should block rm -rf with /usr', () => {
@@ -502,7 +502,7 @@ describe('RuleEngine', () => {
502502
const result = engine.validate(command, rules);
503503

504504
expect(result.action).toBe(ValidationAction.BLOCK);
505-
expect(result.reason).toContain('Catastrophic path');
505+
expect(result.reason).toContain('critical system\/user');
506506
});
507507

508508
it('catastrophic path check runs before pattern rules', () => {
@@ -515,7 +515,7 @@ describe('RuleEngine', () => {
515515

516516
// Should still be blocked despite the ALLOW rule
517517
expect(result.action).toBe(ValidationAction.BLOCK);
518-
expect(result.reason).toContain('Catastrophic path');
518+
expect(result.reason).toContain('critical system\/user');
519519
});
520520
});
521521

@@ -549,7 +549,7 @@ describe('RuleEngine', () => {
549549
const result = engine.validate(command, rules);
550550

551551
expect(result.action).toBe(ValidationAction.BLOCK);
552-
expect(result.reason).toContain('Catastrophic path');
552+
expect(result.reason).toContain('critical system\/user');
553553
expect(result.reason).toContain('via sudo');
554554
});
555555

@@ -560,7 +560,7 @@ describe('RuleEngine', () => {
560560
const result = engine.validate(command, rules);
561561

562562
expect(result.action).toBe(ValidationAction.BLOCK);
563-
expect(result.reason).toContain('Catastrophic path');
563+
expect(result.reason).toContain('critical system\/user');
564564
});
565565

566566
it('should block sudo -u root rm -rf /', () => {
@@ -570,7 +570,7 @@ describe('RuleEngine', () => {
570570
const result = engine.validate(command, rules);
571571

572572
expect(result.action).toBe(ValidationAction.BLOCK);
573-
expect(result.reason).toContain('Catastrophic path');
573+
expect(result.reason).toContain('critical system\/user');
574574
});
575575

576576
it('should block bash -c "rm -rf /"', () => {
@@ -580,7 +580,7 @@ describe('RuleEngine', () => {
580580
const result = engine.validate(command, rules);
581581

582582
expect(result.action).toBe(ValidationAction.BLOCK);
583-
expect(result.reason).toContain('Catastrophic path');
583+
expect(result.reason).toContain('critical system\/user');
584584
expect(result.reason).toContain('via bash -c');
585585
});
586586

@@ -592,7 +592,7 @@ describe('RuleEngine', () => {
592592
const result = engine.validate(command, rules);
593593

594594
expect(result.action).toBe(ValidationAction.BLOCK);
595-
expect(result.reason).toContain('Catastrophic path');
595+
expect(result.reason).toContain('critical system\/user');
596596
});
597597

598598
it('should block sudo bash -c "rm -rf /"', () => {
@@ -602,7 +602,7 @@ describe('RuleEngine', () => {
602602
const result = engine.validate(command, rules);
603603

604604
expect(result.action).toBe(ValidationAction.BLOCK);
605-
expect(result.reason).toContain('Catastrophic path');
605+
expect(result.reason).toContain('critical system\/user');
606606
expect(result.reason).toContain('via sudo');
607607
expect(result.reason).toContain('bash -c');
608608
});
@@ -614,7 +614,7 @@ describe('RuleEngine', () => {
614614
const result = engine.validate(command, rules);
615615

616616
expect(result.action).toBe(ValidationAction.BLOCK);
617-
expect(result.reason).toContain('Catastrophic path');
617+
expect(result.reason).toContain('critical system\/user');
618618
});
619619

620620
it('should block xargs rm -rf with recursive flag', () => {
@@ -658,7 +658,7 @@ describe('RuleEngine', () => {
658658
const result = engine.validate(command, rules);
659659

660660
expect(result.action).toBe(ValidationAction.BLOCK);
661-
expect(result.reason).toContain('Catastrophic path');
661+
expect(result.reason).toContain('critical system\/user');
662662
});
663663

664664
it('should block timeout 30 rm -rf /', () => {
@@ -668,7 +668,7 @@ describe('RuleEngine', () => {
668668
const result = engine.validate(command, rules);
669669

670670
expect(result.action).toBe(ValidationAction.BLOCK);
671-
expect(result.reason).toContain('Catastrophic path');
671+
expect(result.reason).toContain('critical system\/user');
672672
});
673673

674674
it('should NOT block sudo ls -la', () => {

0 commit comments

Comments
 (0)