@brett.buskirk/agent-gate 0.1.0

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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/action.yml +30 -0
  4. package/bin/agent-gate.js +3 -0
  5. package/lib/action.d.ts +2 -0
  6. package/lib/action.d.ts.map +1 -0
  7. package/lib/action.js +73 -0
  8. package/lib/cli.d.ts +3 -0
  9. package/lib/cli.d.ts.map +1 -0
  10. package/lib/cli.js +36 -0
  11. package/lib/config/load.d.ts +3 -0
  12. package/lib/config/load.d.ts.map +1 -0
  13. package/lib/config/load.js +14 -0
  14. package/lib/config/schema.d.ts +236 -0
  15. package/lib/config/schema.d.ts.map +1 -0
  16. package/lib/config/schema.js +66 -0
  17. package/lib/diff/git.d.ts +9 -0
  18. package/lib/diff/git.d.ts.map +1 -0
  19. package/lib/diff/git.js +20 -0
  20. package/lib/diff/github.d.ts +11 -0
  21. package/lib/diff/github.d.ts.map +1 -0
  22. package/lib/diff/github.js +33 -0
  23. package/lib/diff/parse.d.ts +24 -0
  24. package/lib/diff/parse.d.ts.map +1 -0
  25. package/lib/diff/parse.js +65 -0
  26. package/lib/diff/provider.d.ts +5 -0
  27. package/lib/diff/provider.d.ts.map +1 -0
  28. package/lib/diff/provider.js +2 -0
  29. package/lib/engine.d.ts +15 -0
  30. package/lib/engine.d.ts.map +1 -0
  31. package/lib/engine.js +22 -0
  32. package/lib/report/checkRun.d.ts +3 -0
  33. package/lib/report/checkRun.d.ts.map +1 -0
  34. package/lib/report/checkRun.js +51 -0
  35. package/lib/report/comment.d.ts +4 -0
  36. package/lib/report/comment.d.ts.map +1 -0
  37. package/lib/report/comment.js +71 -0
  38. package/lib/report/json.d.ts +4 -0
  39. package/lib/report/json.d.ts.map +1 -0
  40. package/lib/report/json.js +50 -0
  41. package/lib/report/summary.d.ts +4 -0
  42. package/lib/report/summary.d.ts.map +1 -0
  43. package/lib/report/summary.js +56 -0
  44. package/lib/rules/dangerousPatterns.d.ts +3 -0
  45. package/lib/rules/dangerousPatterns.d.ts.map +1 -0
  46. package/lib/rules/dangerousPatterns.js +42 -0
  47. package/lib/rules/dependencies.d.ts +3 -0
  48. package/lib/rules/dependencies.d.ts.map +1 -0
  49. package/lib/rules/dependencies.js +26 -0
  50. package/lib/rules/diffSize.d.ts +3 -0
  51. package/lib/rules/diffSize.d.ts.map +1 -0
  52. package/lib/rules/diffSize.js +31 -0
  53. package/lib/rules/index.d.ts +4 -0
  54. package/lib/rules/index.d.ts.map +1 -0
  55. package/lib/rules/index.js +17 -0
  56. package/lib/rules/scope.d.ts +3 -0
  57. package/lib/rules/scope.d.ts.map +1 -0
  58. package/lib/rules/scope.js +37 -0
  59. package/lib/rules/secrets.d.ts +3 -0
  60. package/lib/rules/secrets.d.ts.map +1 -0
  61. package/lib/rules/secrets.js +60 -0
  62. package/lib/rules/testsRequired.d.ts +3 -0
  63. package/lib/rules/testsRequired.d.ts.map +1 -0
  64. package/lib/rules/testsRequired.js +26 -0
  65. package/lib/rules/types.d.ts +18 -0
  66. package/lib/rules/types.d.ts.map +1 -0
  67. package/lib/rules/types.js +2 -0
  68. package/lib/utils/glob.d.ts +3 -0
  69. package/lib/utils/glob.d.ts.map +1 -0
  70. package/lib/utils/glob.js +42 -0
  71. package/package.json +66 -0
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dependenciesRule = void 0;
4
+ exports.dependenciesRule = {
5
+ id: 'dependencies',
6
+ description: 'Flag added or changed entries in dependency manifests',
7
+ run(diff, config) {
8
+ const { enabled, severity, manifests } = config.rules.dependencies;
9
+ if (!enabled)
10
+ return [];
11
+ const findings = [];
12
+ for (const file of diff.files) {
13
+ const filename = file.path.split('/').pop() ?? file.path;
14
+ if (manifests.includes(filename) && (file.added > 0 || file.deleted > 0)) {
15
+ findings.push({
16
+ ruleId: 'dependencies',
17
+ severity,
18
+ file: file.path,
19
+ message: `Dependency manifest modified: ${file.path}`,
20
+ suggestion: 'Review added or removed dependencies carefully — agents silently adding packages is a supply-chain risk.',
21
+ });
22
+ }
23
+ }
24
+ return findings;
25
+ },
26
+ };
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './types';
2
+ export declare const diffSizeRule: Rule;
3
+ //# sourceMappingURL=diffSize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diffSize.d.ts","sourceRoot":"","sources":["../../src/rules/diffSize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,SAAS,CAAC;AAI7C,eAAO,MAAM,YAAY,EAAE,IA+B1B,CAAC"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diffSizeRule = void 0;
4
+ exports.diffSizeRule = {
5
+ id: 'diff_size',
6
+ description: 'Flag PRs that exceed file or line count thresholds',
7
+ run(diff, config) {
8
+ const { enabled, severity, max_files, max_lines } = config.rules.diff_size;
9
+ if (!enabled)
10
+ return [];
11
+ const findings = [];
12
+ const totalLines = diff.totalAdded + diff.totalDeleted;
13
+ if (diff.files.length > max_files) {
14
+ findings.push({
15
+ ruleId: 'diff_size',
16
+ severity,
17
+ message: `PR modifies ${diff.files.length} files (max: ${max_files})`,
18
+ suggestion: 'Consider splitting this PR into smaller, more focused changes.',
19
+ });
20
+ }
21
+ if (totalLines > max_lines) {
22
+ findings.push({
23
+ ruleId: 'diff_size',
24
+ severity,
25
+ message: `PR changes ${totalLines} lines (max: ${max_lines})`,
26
+ suggestion: 'Consider splitting this PR into smaller, more focused changes.',
27
+ });
28
+ }
29
+ return findings;
30
+ },
31
+ };
@@ -0,0 +1,4 @@
1
+ import type { Rule } from './types';
2
+ export declare const rules: Rule[];
3
+ export type { Rule, Finding, DiffModel, DiffFile, DiffChunk, DiffLine, Severity } from './types';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAQpC,eAAO,MAAM,KAAK,EAAE,IAAI,EAOvB,CAAC;AAEF,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rules = void 0;
4
+ const secrets_1 = require("./secrets");
5
+ const scope_1 = require("./scope");
6
+ const diffSize_1 = require("./diffSize");
7
+ const testsRequired_1 = require("./testsRequired");
8
+ const dependencies_1 = require("./dependencies");
9
+ const dangerousPatterns_1 = require("./dangerousPatterns");
10
+ exports.rules = [
11
+ secrets_1.secretsRule,
12
+ scope_1.scopeRule,
13
+ diffSize_1.diffSizeRule,
14
+ testsRequired_1.testsRequiredRule,
15
+ dependencies_1.dependenciesRule,
16
+ dangerousPatterns_1.dangerousPatternsRule,
17
+ ];
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './types';
2
+ export declare const scopeRule: Rule;
3
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/rules/scope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,SAAS,CAAC;AAI7C,eAAO,MAAM,SAAS,EAAE,IAuCvB,CAAC"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scopeRule = void 0;
4
+ const glob_1 = require("../utils/glob");
5
+ exports.scopeRule = {
6
+ id: 'scope',
7
+ description: 'Flag changes to files outside the allowed scope or inside a denied path set',
8
+ run(diff, config) {
9
+ const { enabled, severity, allow, deny } = config.rules.scope;
10
+ if (!enabled)
11
+ return [];
12
+ const findings = [];
13
+ for (const file of diff.files) {
14
+ const { path } = file;
15
+ if (deny.length > 0 && (0, glob_1.matchesAny)(path, deny)) {
16
+ findings.push({
17
+ ruleId: 'scope',
18
+ severity,
19
+ file: path,
20
+ message: `File is in a denied path: ${path}`,
21
+ suggestion: 'Remove changes to this file or update the deny list in .agentgate.yml if this is intentional.',
22
+ });
23
+ continue;
24
+ }
25
+ if (allow && allow.length > 0 && !(0, glob_1.matchesAny)(path, allow)) {
26
+ findings.push({
27
+ ruleId: 'scope',
28
+ severity,
29
+ file: path,
30
+ message: `File is outside the allowed scope: ${path}`,
31
+ suggestion: 'Remove changes to this file or add it to the allow list in .agentgate.yml.',
32
+ });
33
+ }
34
+ }
35
+ return findings;
36
+ },
37
+ };
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './types';
2
+ export declare const secretsRule: Rule;
3
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/rules/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,SAAS,CAAC;AA8B7C,eAAO,MAAM,WAAW,EAAE,IAmCzB,CAAC"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secretsRule = void 0;
4
+ const SECRET_PATTERNS = [
5
+ { name: 'AWS Access Key ID', pattern: /AKIA[0-9A-Z]{16}/ },
6
+ {
7
+ name: 'AWS Secret Access Key',
8
+ pattern: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\s*[=:]\s*['"]?[A-Za-z0-9/+=]{40}['"]?/i,
9
+ },
10
+ { name: 'GitHub Personal Access Token', pattern: /ghp_[A-Za-z0-9]{36}/ },
11
+ { name: 'GitHub OAuth Token', pattern: /gho_[A-Za-z0-9]{36}/ },
12
+ { name: 'GitHub App Token', pattern: /ghs_[A-Za-z0-9]{36}/ },
13
+ { name: 'GitHub Refresh Token', pattern: /ghr_[A-Za-z0-9]{76}/ },
14
+ { name: 'Private Key Block', pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/ },
15
+ {
16
+ name: 'Generic API Key',
17
+ pattern: /(?:api[_-]?key|apikey)\s*[=:]\s*['"]?[A-Za-z0-9_\-]{20,}['"]?/i,
18
+ },
19
+ {
20
+ name: 'High-entropy secret assignment',
21
+ pattern: /(?:PASSWORD|SECRET|TOKEN|PASSWD)\s*=\s*['"]?[A-Za-z0-9+/!@#$%^&*]{16,}['"]?/,
22
+ },
23
+ { name: 'Slack Token', pattern: /xox[baprs]-[0-9A-Za-z\-]{10,}/ },
24
+ {
25
+ name: 'Stripe Secret Key',
26
+ pattern: /sk_(?:live|test)_[0-9a-zA-Z]{24,}/,
27
+ },
28
+ ];
29
+ exports.secretsRule = {
30
+ id: 'secrets',
31
+ description: 'Scan added lines for leaked credentials and secrets',
32
+ run(diff, config) {
33
+ const { enabled, severity } = config.rules.secrets;
34
+ if (!enabled)
35
+ return [];
36
+ const findings = [];
37
+ for (const file of diff.files) {
38
+ for (const chunk of file.chunks) {
39
+ for (const line of chunk.lines) {
40
+ if (line.type !== 'add')
41
+ continue;
42
+ for (const { name, pattern } of SECRET_PATTERNS) {
43
+ if (pattern.test(line.content)) {
44
+ findings.push({
45
+ ruleId: 'secrets',
46
+ severity,
47
+ file: file.path,
48
+ line: line.lineNumber,
49
+ message: `Possible ${name} detected`,
50
+ suggestion: 'Remove this secret immediately and rotate it. Use environment variables or a secrets manager instead.',
51
+ });
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ return findings;
59
+ },
60
+ };
@@ -0,0 +1,3 @@
1
+ import type { Rule } from './types';
2
+ export declare const testsRequiredRule: Rule;
3
+ //# sourceMappingURL=testsRequired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testsRequired.d.ts","sourceRoot":"","sources":["../../src/rules/testsRequired.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,SAAS,CAAC;AAI7C,eAAO,MAAM,iBAAiB,EAAE,IAwB/B,CAAC"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.testsRequiredRule = void 0;
4
+ const glob_1 = require("../utils/glob");
5
+ exports.testsRequiredRule = {
6
+ id: 'tests_required',
7
+ description: 'Warn if source files changed but no test files were added or modified',
8
+ run(diff, config) {
9
+ const { enabled, severity, src_globs, test_globs } = config.rules.tests_required;
10
+ if (!enabled)
11
+ return [];
12
+ const hasSrcChanges = diff.files.some((f) => (0, glob_1.matchesAny)(f.path, src_globs));
13
+ const hasTestChanges = diff.files.some((f) => (0, glob_1.matchesAny)(f.path, test_globs));
14
+ if (hasSrcChanges && !hasTestChanges) {
15
+ return [
16
+ {
17
+ ruleId: 'tests_required',
18
+ severity,
19
+ message: 'Source files were changed but no test files were added or modified',
20
+ suggestion: 'Add or update tests to cover the changed source code.',
21
+ },
22
+ ];
23
+ }
24
+ return [];
25
+ },
26
+ };
@@ -0,0 +1,18 @@
1
+ import type { DiffModel } from '../diff/parse';
2
+ import type { Config } from '../config/schema';
3
+ export type { DiffModel, DiffFile, DiffChunk, DiffLine } from '../diff/parse';
4
+ export type Severity = 'error' | 'warning' | 'info';
5
+ export interface Finding {
6
+ ruleId: string;
7
+ severity: Severity;
8
+ file?: string;
9
+ line?: number;
10
+ message: string;
11
+ suggestion?: string;
12
+ }
13
+ export interface Rule {
14
+ id: string;
15
+ description: string;
16
+ run(diff: DiffModel, config: Config): Finding[];
17
+ }
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9E,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;CACjD"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export declare function matchGlob(filePath: string, glob: string): boolean;
2
+ export declare function matchesAny(filePath: string, globs: string[]): boolean;
3
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/utils/glob.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CA8BjE;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAErE"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.matchGlob = matchGlob;
4
+ exports.matchesAny = matchesAny;
5
+ const SPECIAL = new Set(['.', '+', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']);
6
+ function matchGlob(filePath, glob) {
7
+ let pattern = '';
8
+ let i = 0;
9
+ while (i < glob.length) {
10
+ if (glob[i] === '*' && glob[i + 1] === '*' && glob[i + 2] === '/') {
11
+ // **/ — matches any path prefix including none
12
+ pattern += '(?:.+/)?';
13
+ i += 3;
14
+ }
15
+ else if (glob[i] === '*' && glob[i + 1] === '*') {
16
+ // ** at end — matches anything (including path separators)
17
+ pattern += '.*';
18
+ i += 2;
19
+ }
20
+ else if (glob[i] === '*') {
21
+ // * — matches anything within a single path segment
22
+ pattern += '[^/]*';
23
+ i += 1;
24
+ }
25
+ else if (glob[i] === '?') {
26
+ pattern += '[^/]';
27
+ i += 1;
28
+ }
29
+ else if (SPECIAL.has(glob[i])) {
30
+ pattern += '\\' + glob[i];
31
+ i += 1;
32
+ }
33
+ else {
34
+ pattern += glob[i];
35
+ i += 1;
36
+ }
37
+ }
38
+ return new RegExp(`^${pattern}$`).test(filePath);
39
+ }
40
+ function matchesAny(filePath, globs) {
41
+ return globs.some((glob) => matchGlob(filePath, glob));
42
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@brett.buskirk/agent-gate",
3
+ "version": "0.1.0",
4
+ "description": "Guardrail checks for AI-agent-generated pull requests",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "agent-gate": "./bin/agent-gate.js",
8
+ "agentgate": "./bin/agent-gate.js"
9
+ },
10
+ "main": "./lib/engine.js",
11
+ "files": [
12
+ "bin",
13
+ "lib",
14
+ "action.yml"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.build.json",
18
+ "build:action": "ncc build src/action.ts -o dist --source-map --license licenses.txt",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run",
21
+ "test:coverage": "vitest run --coverage",
22
+ "lint": "eslint src test",
23
+ "lint:fix": "eslint src test --fix",
24
+ "format": "prettier --write .",
25
+ "format:check": "prettier --check ."
26
+ },
27
+ "dependencies": {
28
+ "@actions/core": "^1.11.1",
29
+ "@actions/github": "^6.0.0",
30
+ "commander": "^13.1.0",
31
+ "js-yaml": "^4.1.0",
32
+ "zod": "^3.24.2"
33
+ },
34
+ "devDependencies": {
35
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
36
+ "@typescript-eslint/parser": "^8.0.0",
37
+ "@types/js-yaml": "^4.0.9",
38
+ "@types/node": "^20.0.0",
39
+ "@vercel/ncc": "^0.38.3",
40
+ "eslint": "^9.0.0",
41
+ "prettier": "^3.3.0",
42
+ "typescript": "^5.4.0",
43
+ "vitest": "^2.0.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=20.0.0"
47
+ },
48
+ "keywords": [
49
+ "ai-agents",
50
+ "github-action",
51
+ "code-review",
52
+ "security",
53
+ "ci",
54
+ "guardrails"
55
+ ],
56
+ "author": "Brett Buskirk",
57
+ "license": "MIT",
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/brett-buskirk/agent-gate.git"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/brett-buskirk/agent-gate/issues"
64
+ },
65
+ "homepage": "https://github.com/brett-buskirk/agent-gate#readme"
66
+ }