@permission-protocol/mcp-guard 0.1.0 → 0.2.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 (88) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +101 -59
  4. package/SECURITY.md +40 -0
  5. package/dist/src/approval-server.d.ts +69 -3
  6. package/dist/src/approval-server.js +345 -86
  7. package/dist/src/approval-server.js.map +1 -1
  8. package/dist/src/approval-ui.d.ts +5 -0
  9. package/dist/src/approval-ui.js +120 -81
  10. package/dist/src/approval-ui.js.map +1 -1
  11. package/dist/src/classify.d.ts +29 -0
  12. package/dist/src/classify.js +107 -0
  13. package/dist/src/classify.js.map +1 -0
  14. package/dist/src/cli.js +49 -12
  15. package/dist/src/cli.js.map +1 -1
  16. package/dist/src/config.d.ts +21 -1
  17. package/dist/src/config.js +92 -5
  18. package/dist/src/config.js.map +1 -1
  19. package/dist/src/enforcement/index.d.ts +12 -0
  20. package/dist/src/enforcement/index.js +53 -0
  21. package/dist/src/enforcement/index.js.map +1 -0
  22. package/dist/src/enforcement/policy.d.ts +29 -0
  23. package/dist/src/enforcement/policy.js +60 -0
  24. package/dist/src/enforcement/policy.js.map +1 -0
  25. package/dist/src/enforcement/types.d.ts +19 -0
  26. package/dist/src/enforcement/types.js +3 -0
  27. package/dist/src/enforcement/types.js.map +1 -0
  28. package/dist/src/enforcement/verifier.d.ts +4 -0
  29. package/dist/src/enforcement/verifier.js +110 -0
  30. package/dist/src/enforcement/verifier.js.map +1 -0
  31. package/dist/src/engine.d.ts +11 -2
  32. package/dist/src/engine.js +20 -14
  33. package/dist/src/engine.js.map +1 -1
  34. package/dist/src/frame.d.ts +67 -0
  35. package/dist/src/frame.js +113 -0
  36. package/dist/src/frame.js.map +1 -0
  37. package/dist/src/pending.d.ts +108 -12
  38. package/dist/src/pending.js +174 -24
  39. package/dist/src/pending.js.map +1 -1
  40. package/dist/src/proxy.d.ts +29 -2
  41. package/dist/src/proxy.js +274 -105
  42. package/dist/src/proxy.js.map +1 -1
  43. package/dist/src/receipt-verify.d.ts +36 -0
  44. package/dist/src/receipt-verify.js +88 -0
  45. package/dist/src/receipt-verify.js.map +1 -0
  46. package/dist/src/receipt.d.ts +81 -7
  47. package/dist/src/receipt.js +100 -17
  48. package/dist/src/receipt.js.map +1 -1
  49. package/dist/src/signer.d.ts +38 -0
  50. package/dist/src/signer.js +112 -0
  51. package/dist/src/signer.js.map +1 -0
  52. package/dist/src/verify-cli.d.ts +1 -0
  53. package/dist/src/verify-cli.js +133 -0
  54. package/dist/src/verify-cli.js.map +1 -0
  55. package/dist/src/verify-core.d.ts +22 -0
  56. package/dist/src/verify-core.js +50 -0
  57. package/dist/src/verify-core.js.map +1 -0
  58. package/dist/src/webhook.d.ts +35 -0
  59. package/dist/src/webhook.js +57 -0
  60. package/dist/src/webhook.js.map +1 -0
  61. package/example/test.sh +5 -7
  62. package/package.json +33 -7
  63. package/templates/github-actions/receipt-gate.yml +64 -0
  64. package/assets/demo.gif +0 -0
  65. package/demo/_demo-scenario.sh +0 -43
  66. package/dist/src/tests/config.test.d.ts +0 -1
  67. package/dist/src/tests/config.test.js +0 -63
  68. package/dist/src/tests/config.test.js.map +0 -1
  69. package/dist/src/tests/engine.test.d.ts +0 -1
  70. package/dist/src/tests/engine.test.js +0 -46
  71. package/dist/src/tests/engine.test.js.map +0 -1
  72. package/dist/src/tests/receipt.test.d.ts +0 -1
  73. package/dist/src/tests/receipt.test.js +0 -44
  74. package/dist/src/tests/receipt.test.js.map +0 -1
  75. package/docs/index.html +0 -771
  76. package/permission-protocol-mcp-guard-0.1.0.tgz +0 -0
  77. package/src/approval-server.ts +0 -136
  78. package/src/approval-ui.ts +0 -154
  79. package/src/cli.ts +0 -84
  80. package/src/config.ts +0 -68
  81. package/src/engine.ts +0 -31
  82. package/src/pending.ts +0 -65
  83. package/src/proxy.ts +0 -173
  84. package/src/receipt.ts +0 -52
  85. package/src/tests/config.test.ts +0 -61
  86. package/src/tests/engine.test.ts +0 -47
  87. package/src/tests/receipt.test.ts +0 -44
  88. package/tsconfig.json +0 -16
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Permission Deck Slice 2.5 — GitHub webhook ingestion (the no-agent adoption path).
3
+ *
4
+ * A customer points their repo's webhook at the deck. When a PR is opened/labeled
5
+ * with the trigger label, the deck enqueues it as a Decide card. Approving it mints
6
+ * the scoped receipt the deploy-gate verifies — so a human authorizes the merge in
7
+ * the deck without any agent ever calling a tool.
8
+ *
9
+ * Pure functions. No I/O. Fully unit-testable.
10
+ */
11
+ export interface ParsedPrEvent {
12
+ action: string;
13
+ pr_number: number;
14
+ repo: string;
15
+ head_sha: string;
16
+ title: string;
17
+ labels: string[];
18
+ html_url: string | null;
19
+ }
20
+ export declare const DEFAULT_TRIGGER_LABEL = "needs-authority";
21
+ /**
22
+ * Verify a GitHub webhook HMAC-SHA256 signature (`X-Hub-Signature-256: sha256=<hex>`).
23
+ * Constant-time compare. Fails closed when the secret or header is missing.
24
+ */
25
+ export declare function verifyGithubSignature(secret: string | undefined, rawBody: string | Buffer, signatureHeader: string | undefined): boolean;
26
+ /** Parse a GitHub `pull_request` webhook payload into the fields we gate on. */
27
+ export declare function parsePrEvent(payload: any): ParsedPrEvent | null;
28
+ /** Should this event open an authority gate? A relevant action carrying the trigger label. */
29
+ export declare function shouldGate(ev: ParsedPrEvent, triggerLabel?: string): boolean;
30
+ /** The scope binding a merge receipt must carry to satisfy the deploy-gate for this PR. */
31
+ export declare function scopeForPr(ev: ParsedPrEvent): {
32
+ scope: string;
33
+ scope_ref: string;
34
+ scope_sha: string;
35
+ };
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_TRIGGER_LABEL = void 0;
7
+ exports.verifyGithubSignature = verifyGithubSignature;
8
+ exports.parsePrEvent = parsePrEvent;
9
+ exports.shouldGate = shouldGate;
10
+ exports.scopeForPr = scopeForPr;
11
+ const node_crypto_1 = __importDefault(require("node:crypto"));
12
+ exports.DEFAULT_TRIGGER_LABEL = 'needs-authority';
13
+ /**
14
+ * Verify a GitHub webhook HMAC-SHA256 signature (`X-Hub-Signature-256: sha256=<hex>`).
15
+ * Constant-time compare. Fails closed when the secret or header is missing.
16
+ */
17
+ function verifyGithubSignature(secret, rawBody, signatureHeader) {
18
+ if (!secret || !signatureHeader)
19
+ return false;
20
+ const expected = 'sha256=' + node_crypto_1.default.createHmac('sha256', secret).update(rawBody).digest('hex');
21
+ const a = Buffer.from(signatureHeader);
22
+ const b = Buffer.from(expected);
23
+ if (a.length !== b.length)
24
+ return false;
25
+ return node_crypto_1.default.timingSafeEqual(a, b);
26
+ }
27
+ /** Parse a GitHub `pull_request` webhook payload into the fields we gate on. */
28
+ function parsePrEvent(payload) {
29
+ if (!payload || typeof payload !== 'object' || !payload.pull_request)
30
+ return null;
31
+ const pr = payload.pull_request;
32
+ if (pr.number == null)
33
+ return null;
34
+ return {
35
+ action: String(payload.action ?? ''),
36
+ pr_number: Number(pr.number),
37
+ repo: String(payload.repository?.full_name ?? ''),
38
+ head_sha: String(pr.head?.sha ?? ''),
39
+ title: String(pr.title ?? ''),
40
+ labels: Array.isArray(pr.labels) ? pr.labels.map((l) => String(l?.name ?? '')).filter(Boolean) : [],
41
+ html_url: pr.html_url ? String(pr.html_url) : null,
42
+ };
43
+ }
44
+ /** Should this event open an authority gate? A relevant action carrying the trigger label. */
45
+ function shouldGate(ev, triggerLabel = exports.DEFAULT_TRIGGER_LABEL) {
46
+ const relevant = ['opened', 'reopened', 'labeled', 'synchronize'].includes(ev.action);
47
+ return relevant && ev.labels.includes(triggerLabel);
48
+ }
49
+ /** The scope binding a merge receipt must carry to satisfy the deploy-gate for this PR. */
50
+ function scopeForPr(ev) {
51
+ return {
52
+ scope: 'github:merge',
53
+ scope_ref: `refs/pull/${ev.pr_number}/merge`,
54
+ scope_sha: ev.head_sha,
55
+ };
56
+ }
57
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":";;;;;;AA6BA,sDAWC;AAGD,oCAaC;AAGD,gCAGC;AAGD,gCAMC;AAvED,8DAAiC;AAuBpB,QAAA,qBAAqB,GAAG,iBAAiB,CAAC;AAEvD;;;GAGG;AACH,SAAgB,qBAAqB,CACnC,MAA0B,EAC1B,OAAwB,EACxB,eAAmC;IAEnC,IAAI,CAAC,MAAM,IAAI,CAAC,eAAe;QAAE,OAAO,KAAK,CAAC;IAC9C,MAAM,QAAQ,GAAG,SAAS,GAAG,qBAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/F,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,qBAAM,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,gFAAgF;AAChF,SAAgB,YAAY,CAAC,OAAY;IACvC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAClF,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAChC,IAAI,EAAE,CAAC,MAAM,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;QACpC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC;QAC5B,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC;QACjD,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;QACpC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;QACxG,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;KACnD,CAAC;AACJ,CAAC;AAED,8FAA8F;AAC9F,SAAgB,UAAU,CAAC,EAAiB,EAAE,eAAuB,6BAAqB;IACxF,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IACtF,OAAO,QAAQ,IAAI,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AACtD,CAAC;AAED,2FAA2F;AAC3F,SAAgB,UAAU,CAAC,EAAiB;IAC1C,OAAO;QACL,KAAK,EAAE,cAAc;QACrB,SAAS,EAAE,aAAa,EAAE,CAAC,SAAS,QAAQ;QAC5C,SAAS,EAAE,EAAE,CAAC,QAAQ;KACvB,CAAC;AACJ,CAAC"}
package/example/test.sh CHANGED
@@ -8,7 +8,7 @@ echo "=== MCP Guard Demo ==="
8
8
  echo ""
9
9
  echo "Building..."
10
10
  cd "$ROOT_DIR"
11
- npm run build --silent 2>/dev/null
11
+ [ -f dist/src/cli.js ] || npm run build --silent 2>/dev/null
12
12
 
13
13
  echo "Sending test requests through mcp-guard proxy..."
14
14
  echo ""
@@ -42,14 +42,12 @@ cat "$RESPONSES" | while IFS= read -r line; do
42
42
  done
43
43
 
44
44
  echo ""
45
- echo "--- Receipts (stderr) ---"
46
- grep "receipt:" "$RECEIPTS" | sed 's/\[mcp-guard\] receipt: //' | while IFS= read -r line; do
47
- echo "$line" | python3 -m json.tool 2>/dev/null || echo "$line"
48
- done
45
+ echo "--- Receipts (signed; full JSON in pp-receipts.jsonl) ---"
46
+ grep "\[mcp-guard\] receipt " "$RECEIPTS" || true
49
47
 
50
48
  echo ""
51
49
  echo "--- Guard Log ---"
52
- grep -v "receipt:" "$RECEIPTS" || true
50
+ grep -v "\[mcp-guard\] receipt " "$RECEIPTS" || true
53
51
 
54
52
  rm -f "$RESPONSES" "$RECEIPTS"
55
53
  echo ""
@@ -58,7 +56,7 @@ echo ""
58
56
  echo "To try the approval UI, run:"
59
57
  echo " node dist/src/cli.js --config example/pp.config.yaml --approval-port 3100 --agent-id demo-agent -- node dist/example/server.js"
60
58
  echo ""
61
- echo "Then open http://localhost:3100 and send a require_approval tool call."
59
+ echo "Then open the Approval UI URL it prints (it ends with #t=<token>) and send a require_approval tool call."
62
60
  echo "The proxy will hold the response until you approve or deny in the UI."
63
61
  echo ""
64
62
  echo "Done!"
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@permission-protocol/mcp-guard",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP Guard blocks dangerous AI agent actions using a simple YAML config file.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/permission-protocol/mcp-guard.git"
8
+ },
5
9
  "publishConfig": {
6
10
  "access": "public"
7
11
  },
@@ -9,21 +13,43 @@
9
13
  "bin": {
10
14
  "mcp-guard": "bin/mcp-guard"
11
15
  },
16
+ "files": [
17
+ "bin",
18
+ "dist/src",
19
+ "!dist/src/tests",
20
+ "dist/example",
21
+ "example",
22
+ "templates",
23
+ "README.md",
24
+ "CHANGELOG.md",
25
+ "SECURITY.md",
26
+ "LICENSE"
27
+ ],
12
28
  "scripts": {
13
29
  "build": "tsc",
14
30
  "test": "node --test dist/src/tests/*.test.js",
15
31
  "demo": "npm run build && bash example/test.sh",
32
+ "deck": "npm run build && node scripts/deck.mjs",
33
+ "slice1": "npm run build && node scripts/demo-slice1.mjs",
34
+ "slice2": "npm run build && node scripts/demo-slice2.mjs",
35
+ "slice25": "npm run build && node scripts/demo-slice25.mjs",
16
36
  "prepublishOnly": "npm run build"
17
37
  },
18
- "keywords": ["mcp", "guard", "proxy", "ai", "safety", "permission-protocol"],
38
+ "keywords": [
39
+ "mcp",
40
+ "guard",
41
+ "proxy",
42
+ "ai",
43
+ "safety",
44
+ "permission-protocol"
45
+ ],
19
46
  "license": "MIT",
20
47
  "dependencies": {
21
- "yaml": "^2.7.0",
22
- "uuid": "^11.1.0"
48
+ "fast-json-stable-stringify": "^2.1.0",
49
+ "yaml": "^2.7.0"
23
50
  },
24
51
  "devDependencies": {
25
- "@types/node": "^22.0.0",
26
- "@types/uuid": "^10.0.0",
27
- "typescript": "^5.7.0"
52
+ "@types/node": "^22.19.15",
53
+ "typescript": "^5.9.3"
28
54
  }
29
55
  }
@@ -0,0 +1,64 @@
1
+ # mcp-guard receipt gate — reference workflow for a deploy pipeline.
2
+ #
3
+ # A deploy runs only with a signed, unexpired receipt that a HUMAN approved for this
4
+ # exact scope and commit. "No receipt = No deploy. This is intentional."
5
+ #
6
+ # Trust boundaries (each one closes a bypass):
7
+ # - Verifier code: a pinned npm version. Nothing from a pull request runs before the gate.
8
+ # - Public key: read from the default branch, never from a pull request. Commit it once
9
+ # with `npx @permission-protocol/mcp-guard export-key .pp/mcp-guard.pub.pem`.
10
+ # - Receipt: delivered out of band (the workflow_dispatch input below), never read
11
+ # from the repository tree, so committing a file cannot satisfy the gate.
12
+ # - Scope: the receipt must name this environment and the exact commit being deployed.
13
+ # Receipts released by a countdown or standing rule are refused (human approval only).
14
+ #
15
+ # Wire-up: trigger this workflow with the receipt JSON the approver received, e.g.
16
+ # gh workflow run deploy.yml -f receipt="$(cat receipt.json)"
17
+
18
+ name: Deploy (receipt-gated)
19
+
20
+ on:
21
+ workflow_dispatch:
22
+ inputs:
23
+ receipt:
24
+ description: Signed mcp-guard receipt JSON for deploy:production at this commit
25
+ required: true
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ jobs:
31
+ receipt-gate:
32
+ name: mcp-guard receipt gate
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: Read the pinned public key from the default branch only
36
+ uses: actions/checkout@v4
37
+ with:
38
+ ref: ${{ github.event.repository.default_branch }}
39
+ sparse-checkout: .pp/mcp-guard.pub.pem
40
+ sparse-checkout-cone-mode: false
41
+ path: trust
42
+
43
+ - uses: actions/setup-node@v4
44
+ with:
45
+ node-version: '22'
46
+
47
+ - name: Verify the receipt (offline, fail closed)
48
+ env:
49
+ RECEIPT: ${{ inputs.receipt }}
50
+ run: |
51
+ printf '%s' "$RECEIPT" > "$RUNNER_TEMP/receipt.json"
52
+ npx --yes @permission-protocol/mcp-guard@0.2.0 verify \
53
+ --receipt-file "$RUNNER_TEMP/receipt.json" \
54
+ --pubkey trust/.pp/mcp-guard.pub.pem \
55
+ --scope deploy:production \
56
+ --scope-sha "$GITHUB_SHA"
57
+
58
+ deploy:
59
+ needs: receipt-gate
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+ - name: Deploy
64
+ run: echo "Deploy $GITHUB_SHA here."
package/assets/demo.gif DELETED
Binary file
@@ -1,43 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
-
4
- GREEN='\033[0;32m'
5
- RED='\033[1;31m'
6
- BOLD='\033[1m'
7
- DIM='\033[2m'
8
- NC='\033[0m'
9
- BG_RED='\033[41m'
10
- BG_GREEN='\033[42m'
11
- BLACK='\033[30m'
12
-
13
- clear
14
- echo ""
15
-
16
- # ── BEFORE (red = danger) ──
17
- echo -e " ${BG_RED}${BLACK}${BOLD} ✖ WITHOUT MCP GUARD ${NC}"
18
- echo ""
19
- sleep 0.6
20
- echo -e " ${DIM}\$${NC} call ${BOLD}delete_user_data${NC}"
21
- sleep 0.4
22
- echo -e " ${GREEN}✔ success${NC} — user data deleted"
23
- echo ""
24
- sleep 1.5
25
-
26
- # ── DIVIDER ──
27
- echo -e " ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
28
- echo ""
29
- sleep 0.4
30
-
31
- # ── AFTER (green = safe) ──
32
- echo -e " ${BG_GREEN}${BLACK}${BOLD} ✔ WITH MCP GUARD ${NC}"
33
- echo ""
34
- sleep 0.6
35
- echo -e " ${DIM}\$${NC} call ${BOLD}delete_user_data${NC}"
36
- sleep 0.4
37
- echo -e " ${RED}${BOLD}✖ BLOCKED${NC}"
38
- echo -e " ${DIM}rule: block-delete-user-data${NC}"
39
- echo ""
40
- sleep 0.5
41
- echo -e " ${DIM}{ \"decision\": \"blocked\" }${NC}"
42
- echo ""
43
- sleep 2.5
@@ -1 +0,0 @@
1
- export {};
@@ -1,63 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const node_test_1 = require("node:test");
7
- const strict_1 = __importDefault(require("node:assert/strict"));
8
- const node_fs_1 = require("node:fs");
9
- const node_path_1 = require("node:path");
10
- const node_os_1 = require("node:os");
11
- const config_js_1 = require("../config.js");
12
- (0, node_test_1.describe)('Config Parser', () => {
13
- (0, node_test_1.it)('parses a valid config', () => {
14
- const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'mcp-guard-test-'));
15
- const path = (0, node_path_1.join)(dir, 'config.yaml');
16
- (0, node_fs_1.writeFileSync)(path, `
17
- default_action: allow
18
- rules:
19
- - id: block-delete
20
- tool: delete_user_data
21
- action: block
22
- - id: hold-deploy
23
- tool: deploy_production
24
- action: require_approval
25
- `);
26
- const config = (0, config_js_1.loadConfig)(path);
27
- strict_1.default.equal(config.default_action, 'allow');
28
- strict_1.default.equal(config.rules.length, 2);
29
- strict_1.default.equal(config.rules[0].id, 'block-delete');
30
- strict_1.default.equal(config.rules[0].tool, 'delete_user_data');
31
- strict_1.default.equal(config.rules[0].action, 'block');
32
- strict_1.default.equal(config.rules[1].action, 'require_approval');
33
- strict_1.default.equal(config.mode, 'enforce'); // default
34
- (0, node_fs_1.unlinkSync)(path);
35
- });
36
- (0, node_test_1.it)('rejects invalid config (bad action)', () => {
37
- const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'mcp-guard-test-'));
38
- const path = (0, node_path_1.join)(dir, 'config.yaml');
39
- (0, node_fs_1.writeFileSync)(path, `
40
- default_action: allow
41
- rules:
42
- - id: bad
43
- tool: foo
44
- action: nuke_it
45
- `);
46
- strict_1.default.throws(() => (0, config_js_1.loadConfig)(path), /invalid action/i);
47
- (0, node_fs_1.unlinkSync)(path);
48
- });
49
- (0, node_test_1.it)('throws on missing file', () => {
50
- strict_1.default.throws(() => (0, config_js_1.loadConfig)('/tmp/nonexistent-mcp-guard-config.yaml'), /Failed to read config/);
51
- });
52
- (0, node_test_1.it)('rejects config with invalid default_action', () => {
53
- const dir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'mcp-guard-test-'));
54
- const path = (0, node_path_1.join)(dir, 'config.yaml');
55
- (0, node_fs_1.writeFileSync)(path, `
56
- default_action: yolo
57
- rules: []
58
- `);
59
- strict_1.default.throws(() => (0, config_js_1.loadConfig)(path), /Invalid default_action/);
60
- (0, node_fs_1.unlinkSync)(path);
61
- });
62
- });
63
- //# sourceMappingURL=config.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../../src/tests/config.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,46 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const node_test_1 = require("node:test");
7
- const strict_1 = __importDefault(require("node:assert/strict"));
8
- const engine_js_1 = require("../engine.js");
9
- (0, node_test_1.describe)('Decision Engine', () => {
10
- const config = {
11
- default_action: 'allow',
12
- mode: 'enforce',
13
- rules: [
14
- { id: 'block-delete', tool: 'delete_user_data', action: 'block' },
15
- { id: 'hold-deploy', tool: 'deploy_production', action: 'require_approval' },
16
- { id: 'allow-email', tool: 'send_email', action: 'allow' },
17
- ],
18
- };
19
- (0, node_test_1.it)('matches a block rule', () => {
20
- const d = (0, engine_js_1.evaluate)('delete_user_data', undefined, config);
21
- strict_1.default.equal(d.decision, 'blocked');
22
- strict_1.default.equal(d.rule_id, 'block-delete');
23
- });
24
- (0, node_test_1.it)('matches a require_approval rule', () => {
25
- const d = (0, engine_js_1.evaluate)('deploy_production', undefined, config);
26
- strict_1.default.equal(d.decision, 'held_for_approval');
27
- strict_1.default.equal(d.rule_id, 'hold-deploy');
28
- });
29
- (0, node_test_1.it)('matches an allow rule', () => {
30
- const d = (0, engine_js_1.evaluate)('send_email', undefined, config);
31
- strict_1.default.equal(d.decision, 'allowed');
32
- strict_1.default.equal(d.rule_id, 'allow-email');
33
- });
34
- (0, node_test_1.it)('falls back to default_action: allow', () => {
35
- const d = (0, engine_js_1.evaluate)('unknown_tool', undefined, config);
36
- strict_1.default.equal(d.decision, 'allowed');
37
- strict_1.default.equal(d.rule_id, null);
38
- });
39
- (0, node_test_1.it)('falls back to default_action: block', () => {
40
- const blockConfig = { default_action: 'block', mode: 'enforce', rules: [] };
41
- const d = (0, engine_js_1.evaluate)('any_tool', undefined, blockConfig);
42
- strict_1.default.equal(d.decision, 'blocked');
43
- strict_1.default.equal(d.rule_id, null);
44
- });
45
- });
46
- //# sourceMappingURL=engine.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"engine.test.js","sourceRoot":"","sources":["../../../src/tests/engine.test.ts"],"names":[],"mappings":";;;;;AAAA,yCAAyC;AACzC,gEAAwC;AACxC,4CAAwC;AAGxC,IAAA,oBAAQ,EAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,MAAM,MAAM,GAAW;QACrB,cAAc,EAAE,OAAO;QACvB,IAAI,EAAE,SAAS;QACf,KAAK,EAAE;YACL,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,OAAO,EAAE;YACjE,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,kBAAkB,EAAE;YAC5E,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;SAC3D;KACF,CAAC;IAEF,IAAA,cAAE,EAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,GAAG,IAAA,oBAAQ,EAAC,kBAAkB,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC1D,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACpC,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,IAAA,cAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,GAAG,IAAA,oBAAQ,EAAC,mBAAmB,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC3D,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QAC9C,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,IAAA,cAAE,EAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,GAAG,IAAA,oBAAQ,EAAC,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QACpD,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACpC,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,IAAA,cAAE,EAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,GAAG,IAAA,oBAAQ,EAAC,cAAc,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QACtD,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACpC,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,IAAA,cAAE,EAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,WAAW,GAAW,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QACpF,MAAM,CAAC,GAAG,IAAA,oBAAQ,EAAC,UAAU,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QACvD,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACpC,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,44 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const node_test_1 = require("node:test");
7
- const strict_1 = __importDefault(require("node:assert/strict"));
8
- const receipt_js_1 = require("../receipt.js");
9
- (0, node_test_1.describe)('Receipt Emitter', () => {
10
- (0, node_test_1.it)('generates a valid receipt with all fields', () => {
11
- const decision = {
12
- decision: 'blocked',
13
- rule_id: 'block-delete',
14
- reason: 'Matched rule "block-delete"',
15
- };
16
- const receipt = (0, receipt_js_1.createReceipt)('agent-1', 'delete_user_data', decision, { name: 'delete_user_data', arguments: {} });
17
- strict_1.default.ok(receipt.receipt_id, 'has receipt_id');
18
- strict_1.default.match(receipt.receipt_id, /^[0-9a-f-]{36}$/, 'receipt_id is UUID');
19
- strict_1.default.ok(receipt.timestamp, 'has timestamp');
20
- strict_1.default.doesNotThrow(() => new Date(receipt.timestamp), 'timestamp is valid ISO');
21
- strict_1.default.equal(receipt.agent_id, 'agent-1');
22
- strict_1.default.equal(receipt.tool_name, 'delete_user_data');
23
- strict_1.default.equal(receipt.decision, 'blocked');
24
- strict_1.default.equal(receipt.reason, 'Matched rule "block-delete"');
25
- strict_1.default.equal(receipt.rule_id, 'block-delete');
26
- strict_1.default.equal(typeof receipt.request_payload_hash, 'string');
27
- strict_1.default.equal(receipt.request_payload_hash.length, 64, 'SHA-256 hex is 64 chars');
28
- strict_1.default.equal(receipt.target_server, 'unknown');
29
- strict_1.default.equal(receipt.mode, 'enforce');
30
- });
31
- (0, node_test_1.it)('hashes different payloads differently', () => {
32
- const decision = { decision: 'allowed', rule_id: null, reason: 'test' };
33
- const r1 = (0, receipt_js_1.createReceipt)('a', 'tool', decision, { foo: 1 });
34
- const r2 = (0, receipt_js_1.createReceipt)('a', 'tool', decision, { foo: 2 });
35
- strict_1.default.notEqual(r1.request_payload_hash, r2.request_payload_hash);
36
- });
37
- (0, node_test_1.it)('generates unique receipt IDs', () => {
38
- const decision = { decision: 'allowed', rule_id: null, reason: 'test' };
39
- const r1 = (0, receipt_js_1.createReceipt)('a', 'tool', decision, {});
40
- const r2 = (0, receipt_js_1.createReceipt)('a', 'tool', decision, {});
41
- strict_1.default.notEqual(r1.receipt_id, r2.receipt_id);
42
- });
43
- });
44
- //# sourceMappingURL=receipt.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"receipt.test.js","sourceRoot":"","sources":["../../../src/tests/receipt.test.ts"],"names":[],"mappings":"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"}