@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.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +101 -59
- package/SECURITY.md +40 -0
- package/dist/src/approval-server.d.ts +69 -3
- package/dist/src/approval-server.js +345 -86
- package/dist/src/approval-server.js.map +1 -1
- package/dist/src/approval-ui.d.ts +5 -0
- package/dist/src/approval-ui.js +120 -81
- package/dist/src/approval-ui.js.map +1 -1
- package/dist/src/classify.d.ts +29 -0
- package/dist/src/classify.js +107 -0
- package/dist/src/classify.js.map +1 -0
- package/dist/src/cli.js +49 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.d.ts +21 -1
- package/dist/src/config.js +92 -5
- package/dist/src/config.js.map +1 -1
- package/dist/src/enforcement/index.d.ts +12 -0
- package/dist/src/enforcement/index.js +53 -0
- package/dist/src/enforcement/index.js.map +1 -0
- package/dist/src/enforcement/policy.d.ts +29 -0
- package/dist/src/enforcement/policy.js +60 -0
- package/dist/src/enforcement/policy.js.map +1 -0
- package/dist/src/enforcement/types.d.ts +19 -0
- package/dist/src/enforcement/types.js +3 -0
- package/dist/src/enforcement/types.js.map +1 -0
- package/dist/src/enforcement/verifier.d.ts +4 -0
- package/dist/src/enforcement/verifier.js +110 -0
- package/dist/src/enforcement/verifier.js.map +1 -0
- package/dist/src/engine.d.ts +11 -2
- package/dist/src/engine.js +20 -14
- package/dist/src/engine.js.map +1 -1
- package/dist/src/frame.d.ts +67 -0
- package/dist/src/frame.js +113 -0
- package/dist/src/frame.js.map +1 -0
- package/dist/src/pending.d.ts +108 -12
- package/dist/src/pending.js +174 -24
- package/dist/src/pending.js.map +1 -1
- package/dist/src/proxy.d.ts +29 -2
- package/dist/src/proxy.js +274 -105
- package/dist/src/proxy.js.map +1 -1
- package/dist/src/receipt-verify.d.ts +36 -0
- package/dist/src/receipt-verify.js +88 -0
- package/dist/src/receipt-verify.js.map +1 -0
- package/dist/src/receipt.d.ts +81 -7
- package/dist/src/receipt.js +100 -17
- package/dist/src/receipt.js.map +1 -1
- package/dist/src/signer.d.ts +38 -0
- package/dist/src/signer.js +112 -0
- package/dist/src/signer.js.map +1 -0
- package/dist/src/verify-cli.d.ts +1 -0
- package/dist/src/verify-cli.js +133 -0
- package/dist/src/verify-cli.js.map +1 -0
- package/dist/src/verify-core.d.ts +22 -0
- package/dist/src/verify-core.js +50 -0
- package/dist/src/verify-core.js.map +1 -0
- package/dist/src/webhook.d.ts +35 -0
- package/dist/src/webhook.js +57 -0
- package/dist/src/webhook.js.map +1 -0
- package/example/test.sh +5 -7
- package/package.json +33 -7
- package/templates/github-actions/receipt-gate.yml +64 -0
- package/assets/demo.gif +0 -0
- package/demo/_demo-scenario.sh +0 -43
- package/dist/src/tests/config.test.d.ts +0 -1
- package/dist/src/tests/config.test.js +0 -63
- package/dist/src/tests/config.test.js.map +0 -1
- package/dist/src/tests/engine.test.d.ts +0 -1
- package/dist/src/tests/engine.test.js +0 -46
- package/dist/src/tests/engine.test.js.map +0 -1
- package/dist/src/tests/receipt.test.d.ts +0 -1
- package/dist/src/tests/receipt.test.js +0 -44
- package/dist/src/tests/receipt.test.js.map +0 -1
- package/docs/index.html +0 -771
- package/permission-protocol-mcp-guard-0.1.0.tgz +0 -0
- package/src/approval-server.ts +0 -136
- package/src/approval-ui.ts +0 -154
- package/src/cli.ts +0 -84
- package/src/config.ts +0 -68
- package/src/engine.ts +0 -31
- package/src/pending.ts +0 -65
- package/src/proxy.ts +0 -173
- package/src/receipt.ts +0 -52
- package/src/tests/config.test.ts +0 -61
- package/src/tests/engine.test.ts +0 -47
- package/src/tests/receipt.test.ts +0 -44
- 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 (
|
|
46
|
-
grep "
|
|
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
|
|
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
|
|
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.
|
|
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": [
|
|
38
|
+
"keywords": [
|
|
39
|
+
"mcp",
|
|
40
|
+
"guard",
|
|
41
|
+
"proxy",
|
|
42
|
+
"ai",
|
|
43
|
+
"safety",
|
|
44
|
+
"permission-protocol"
|
|
45
|
+
],
|
|
19
46
|
"license": "MIT",
|
|
20
47
|
"dependencies": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
48
|
+
"fast-json-stable-stringify": "^2.1.0",
|
|
49
|
+
"yaml": "^2.7.0"
|
|
23
50
|
},
|
|
24
51
|
"devDependencies": {
|
|
25
|
-
"@types/node": "^22.
|
|
26
|
-
"
|
|
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
|
package/demo/_demo-scenario.sh
DELETED
|
@@ -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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"}
|