@attested-intelligence/aga-mcp-server 2.0.1 → 2.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/README.md +197 -124
- package/SECURITY.md +59 -0
- package/dist/adapters/openclaw.d.ts +43 -0
- package/dist/adapters/openclaw.d.ts.map +1 -0
- package/dist/adapters/openclaw.js +86 -0
- package/dist/adapters/openclaw.js.map +1 -0
- package/dist/core/bundle.d.ts +9 -2
- package/dist/core/bundle.d.ts.map +1 -1
- package/dist/core/bundle.js +16 -2
- package/dist/core/bundle.js.map +1 -1
- package/dist/core/identity.d.ts +19 -10
- package/dist/core/identity.d.ts.map +1 -1
- package/dist/core/identity.js +45 -11
- package/dist/core/identity.js.map +1 -1
- package/dist/core/portal.d.ts +10 -1
- package/dist/core/portal.d.ts.map +1 -1
- package/dist/core/portal.js +16 -12
- package/dist/core/portal.js.map +1 -1
- package/dist/core/types.d.ts +29 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/crypto/index.d.ts +5 -6
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js +5 -6
- package/dist/crypto/index.js.map +1 -1
- package/dist/crypto/sign.d.ts +2 -0
- package/dist/crypto/sign.d.ts.map +1 -1
- package/dist/crypto/sign.js +6 -0
- package/dist/crypto/sign.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/governance.d.ts +7 -1
- package/dist/middleware/governance.d.ts.map +1 -1
- package/dist/middleware/governance.js +18 -11
- package/dist/middleware/governance.js.map +1 -1
- package/dist/proxy/evaluator.d.ts +14 -0
- package/dist/proxy/evaluator.d.ts.map +1 -0
- package/dist/proxy/evaluator.js +141 -0
- package/dist/proxy/evaluator.js.map +1 -0
- package/dist/proxy/index.d.ts +22 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +230 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/profiles.d.ts +16 -0
- package/dist/proxy/profiles.d.ts.map +1 -0
- package/dist/proxy/profiles.js +43 -0
- package/dist/proxy/profiles.js.map +1 -0
- package/dist/proxy/server.d.ts +106 -0
- package/dist/proxy/server.d.ts.map +1 -0
- package/dist/proxy/server.js +389 -0
- package/dist/proxy/server.js.map +1 -0
- package/dist/proxy/stdio-bridge.d.ts +42 -0
- package/dist/proxy/stdio-bridge.d.ts.map +1 -0
- package/dist/proxy/stdio-bridge.js +142 -0
- package/dist/proxy/stdio-bridge.js.map +1 -0
- package/dist/proxy/types.d.ts +36 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/types.js +11 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/proxy/verify.d.ts +29 -0
- package/dist/proxy/verify.d.ts.map +1 -0
- package/dist/proxy/verify.js +183 -0
- package/dist/proxy/verify.js.map +1 -0
- package/dist/server.d.ts +7 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +342 -214
- package/dist/server.js.map +1 -1
- package/dist/storage/sqlite.js +6 -6
- package/independent-verifier/README.md +31 -0
- package/independent-verifier/package.json +18 -0
- package/independent-verifier/verify.ts +211 -0
- package/package.json +97 -71
- package/src/adapters/openclaw.ts +125 -0
- package/src/core/artifact.ts +45 -0
- package/src/core/attestation.ts +33 -0
- package/src/core/behavioral.ts +132 -0
- package/src/core/bundle.ts +45 -0
- package/src/core/chain.ts +72 -0
- package/src/core/checkpoint.ts +22 -0
- package/src/core/delegation.ts +146 -0
- package/src/core/disclosure.ts +32 -0
- package/src/core/identity.ts +62 -0
- package/src/core/index.ts +14 -0
- package/src/core/portal.ts +117 -0
- package/src/core/quarantine.ts +16 -0
- package/src/core/receipt.ts +33 -0
- package/src/core/subject.ts +11 -0
- package/src/core/types.ts +285 -0
- package/src/crypto/hash.ts +33 -0
- package/src/crypto/index.ts +5 -0
- package/src/crypto/merkle.ts +43 -0
- package/src/crypto/salt.ts +18 -0
- package/src/crypto/sign.ts +42 -0
- package/src/crypto/types.ts +19 -0
- package/src/index.ts +12 -0
- package/src/middleware/governance.ts +95 -0
- package/src/middleware/index.ts +1 -0
- package/src/proxy/evaluator.ts +176 -0
- package/src/proxy/index.ts +259 -0
- package/src/proxy/profiles.ts +48 -0
- package/src/proxy/server.ts +499 -0
- package/src/proxy/stdio-bridge.ts +171 -0
- package/src/proxy/types.ts +40 -0
- package/src/proxy/verify.ts +202 -0
- package/src/server.ts +435 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/interface.ts +21 -0
- package/src/storage/memory.ts +27 -0
- package/src/storage/sqlite.ts +45 -0
- package/src/tools/README.md +13 -0
- package/src/utils/canonical.ts +14 -0
- package/src/utils/constants.ts +3 -0
- package/src/utils/timestamp.ts +12 -0
- package/src/utils/uuid.ts +2 -0
- package/dist/context.d.ts +0 -39
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -113
- package/dist/context.js.map +0 -1
- package/dist/core/measurement.d.ts +0 -16
- package/dist/core/measurement.d.ts.map +0 -1
- package/dist/core/measurement.js +0 -18
- package/dist/core/measurement.js.map +0 -1
- package/dist/crypto/canonicalize.d.ts +0 -7
- package/dist/crypto/canonicalize.d.ts.map +0 -1
- package/dist/crypto/canonicalize.js +0 -21
- package/dist/crypto/canonicalize.js.map +0 -1
- package/dist/crypto/keys.d.ts +0 -10
- package/dist/crypto/keys.d.ts.map +0 -1
- package/dist/crypto/keys.js +0 -19
- package/dist/crypto/keys.js.map +0 -1
- package/dist/prompts/drift-analysis.d.ts +0 -13
- package/dist/prompts/drift-analysis.d.ts.map +0 -1
- package/dist/prompts/drift-analysis.js +0 -43
- package/dist/prompts/drift-analysis.js.map +0 -1
- package/dist/prompts/governance-report.d.ts +0 -7
- package/dist/prompts/governance-report.d.ts.map +0 -1
- package/dist/prompts/governance-report.js +0 -26
- package/dist/prompts/governance-report.js.map +0 -1
- package/dist/prompts/nccoe-demo.d.ts +0 -14
- package/dist/prompts/nccoe-demo.d.ts.map +0 -1
- package/dist/prompts/nccoe-demo.js +0 -47
- package/dist/prompts/nccoe-demo.js.map +0 -1
- package/dist/resources/cosai-mapping.d.ts +0 -24
- package/dist/resources/cosai-mapping.d.ts.map +0 -1
- package/dist/resources/cosai-mapping.js +0 -127
- package/dist/resources/cosai-mapping.js.map +0 -1
- package/dist/resources/crypto-primitives.d.ts +0 -3
- package/dist/resources/crypto-primitives.d.ts.map +0 -1
- package/dist/resources/crypto-primitives.js +0 -52
- package/dist/resources/crypto-primitives.js.map +0 -1
- package/dist/resources/sample-bundle.d.ts +0 -6
- package/dist/resources/sample-bundle.d.ts.map +0 -1
- package/dist/resources/sample-bundle.js +0 -58
- package/dist/resources/sample-bundle.js.map +0 -1
- package/dist/resources/specification.d.ts +0 -3
- package/dist/resources/specification.d.ts.map +0 -1
- package/dist/resources/specification.js +0 -161
- package/dist/resources/specification.js.map +0 -1
- package/dist/tools/create-artifact.d.ts +0 -25
- package/dist/tools/create-artifact.d.ts.map +0 -1
- package/dist/tools/create-artifact.js +0 -85
- package/dist/tools/create-artifact.js.map +0 -1
- package/dist/tools/delegate-subagent.d.ts +0 -18
- package/dist/tools/delegate-subagent.d.ts.map +0 -1
- package/dist/tools/delegate-subagent.js +0 -50
- package/dist/tools/delegate-subagent.js.map +0 -1
- package/dist/tools/disclose-claim.d.ts +0 -14
- package/dist/tools/disclose-claim.d.ts.map +0 -1
- package/dist/tools/disclose-claim.js +0 -23
- package/dist/tools/disclose-claim.js.map +0 -1
- package/dist/tools/export-bundle.d.ts +0 -8
- package/dist/tools/export-bundle.d.ts.map +0 -1
- package/dist/tools/export-bundle.js +0 -25
- package/dist/tools/export-bundle.js.map +0 -1
- package/dist/tools/full-lifecycle.d.ts +0 -16
- package/dist/tools/full-lifecycle.d.ts.map +0 -1
- package/dist/tools/full-lifecycle.js +0 -121
- package/dist/tools/full-lifecycle.js.map +0 -1
- package/dist/tools/generate-receipt.d.ts +0 -16
- package/dist/tools/generate-receipt.d.ts.map +0 -1
- package/dist/tools/generate-receipt.js +0 -31
- package/dist/tools/generate-receipt.js.map +0 -1
- package/dist/tools/get-chain.d.ts +0 -14
- package/dist/tools/get-chain.d.ts.map +0 -1
- package/dist/tools/get-chain.js +0 -45
- package/dist/tools/get-chain.js.map +0 -1
- package/dist/tools/get-portal-state.d.ts +0 -8
- package/dist/tools/get-portal-state.d.ts.map +0 -1
- package/dist/tools/get-portal-state.js +0 -15
- package/dist/tools/get-portal-state.js.map +0 -1
- package/dist/tools/init-chain.d.ts +0 -10
- package/dist/tools/init-chain.d.ts.map +0 -1
- package/dist/tools/init-chain.js +0 -13
- package/dist/tools/init-chain.js.map +0 -1
- package/dist/tools/measure-behavior.d.ts +0 -12
- package/dist/tools/measure-behavior.d.ts.map +0 -1
- package/dist/tools/measure-behavior.js +0 -29
- package/dist/tools/measure-behavior.js.map +0 -1
- package/dist/tools/measure-subject.d.ts +0 -15
- package/dist/tools/measure-subject.d.ts.map +0 -1
- package/dist/tools/measure-subject.js +0 -106
- package/dist/tools/measure-subject.js.map +0 -1
- package/dist/tools/quarantine-status.d.ts +0 -8
- package/dist/tools/quarantine-status.d.ts.map +0 -1
- package/dist/tools/quarantine-status.js +0 -16
- package/dist/tools/quarantine-status.js.map +0 -1
- package/dist/tools/revoke-artifact.d.ts +0 -13
- package/dist/tools/revoke-artifact.d.ts.map +0 -1
- package/dist/tools/revoke-artifact.js +0 -24
- package/dist/tools/revoke-artifact.js.map +0 -1
- package/dist/tools/rotate-keys.d.ts +0 -13
- package/dist/tools/rotate-keys.d.ts.map +0 -1
- package/dist/tools/rotate-keys.js +0 -39
- package/dist/tools/rotate-keys.js.map +0 -1
- package/dist/tools/server-info.d.ts +0 -8
- package/dist/tools/server-info.d.ts.map +0 -1
- package/dist/tools/server-info.js +0 -23
- package/dist/tools/server-info.js.map +0 -1
- package/dist/tools/set-verification-tier.d.ts +0 -11
- package/dist/tools/set-verification-tier.d.ts.map +0 -1
- package/dist/tools/set-verification-tier.js +0 -31
- package/dist/tools/set-verification-tier.js.map +0 -1
- package/dist/tools/start-monitoring.d.ts +0 -12
- package/dist/tools/start-monitoring.d.ts.map +0 -1
- package/dist/tools/start-monitoring.js +0 -17
- package/dist/tools/start-monitoring.js.map +0 -1
- package/dist/tools/trigger-measurement.d.ts +0 -15
- package/dist/tools/trigger-measurement.d.ts.map +0 -1
- package/dist/tools/trigger-measurement.js +0 -86
- package/dist/tools/trigger-measurement.js.map +0 -1
- package/dist/tools/verify-artifact.d.ts +0 -13
- package/dist/tools/verify-artifact.d.ts.map +0 -1
- package/dist/tools/verify-artifact.js +0 -6
- package/dist/tools/verify-artifact.js.map +0 -1
- package/dist/tools/verify-bundle.d.ts +0 -13
- package/dist/tools/verify-bundle.d.ts.map +0 -1
- package/dist/tools/verify-bundle.js +0 -6
- package/dist/tools/verify-bundle.js.map +0 -1
- package/dist/types.d.ts +0 -261
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -8
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance Middleware - wraps every MCP tool handler.
|
|
3
|
+
*
|
|
4
|
+
* NCCoE filing Section 4: "The portal operates as a Policy Enforcement Point (PEP)...
|
|
5
|
+
* Every tool invocation, API call, actuator command, and data access passes through
|
|
6
|
+
* the portal, which evaluates it against the sealed artifact's enforcement parameters."
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - TERMINATED state → reject all governed tools
|
|
10
|
+
* - PHANTOM_QUARANTINE → capture tool call as forensic input, reject
|
|
11
|
+
* - ACTIVE_MONITORING → allow, log to chain
|
|
12
|
+
* - Ungoverned tools (get_server_info, get_portal_state, list_claims) → always allow
|
|
13
|
+
*/
|
|
14
|
+
import type { Portal } from '../core/portal.js';
|
|
15
|
+
import type { QuarantineState } from '../core/types.js';
|
|
16
|
+
import { captureInput } from '../core/quarantine.js';
|
|
17
|
+
import type { BehavioralMonitor } from '../core/behavioral.js';
|
|
18
|
+
import { sha256Str } from '../crypto/hash.js';
|
|
19
|
+
import { canonicalize } from '../utils/canonical.js';
|
|
20
|
+
|
|
21
|
+
export type ToolResult = { content: Array<{ type: 'text'; text: string }> };
|
|
22
|
+
export type ToolHandler<T = any> = (args: T) => Promise<ToolResult>;
|
|
23
|
+
|
|
24
|
+
const UNGOVERNED_TOOLS = new Set([
|
|
25
|
+
'get_server_info',
|
|
26
|
+
'get_portal_state',
|
|
27
|
+
'get_receipts',
|
|
28
|
+
'get_chain_events',
|
|
29
|
+
'list_claims',
|
|
30
|
+
'init_chain', // must work before attestation
|
|
31
|
+
'attest_subject', // creates the governance relationship
|
|
32
|
+
'verify_chain', // read-only verification
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export function createGovernanceWrapper(
|
|
36
|
+
portal: Portal,
|
|
37
|
+
quarantine: { current: QuarantineState | null },
|
|
38
|
+
toolName: string,
|
|
39
|
+
behavioralMonitor?: BehavioralMonitor
|
|
40
|
+
) {
|
|
41
|
+
const isGoverned = !UNGOVERNED_TOOLS.has(toolName);
|
|
42
|
+
|
|
43
|
+
return function wrapHandler<T>(handler: ToolHandler<T>): ToolHandler<T> {
|
|
44
|
+
if (!isGoverned) return handler;
|
|
45
|
+
|
|
46
|
+
return async (args: T): Promise<ToolResult> => {
|
|
47
|
+
const j = (x: unknown): ToolResult => ({
|
|
48
|
+
content: [{ type: 'text', text: JSON.stringify(x, null, 2) }]
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// TERMINATED → reject everything
|
|
52
|
+
if (portal.state === 'TERMINATED') {
|
|
53
|
+
return j({
|
|
54
|
+
success: false,
|
|
55
|
+
error: 'GOVERNANCE_BLOCKED: Portal is terminated. Agent governance has been revoked. Re-attestation required.',
|
|
56
|
+
portal_state: portal.state,
|
|
57
|
+
tool: toolName,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// PHANTOM_QUARANTINE → capture as forensic input, reject
|
|
62
|
+
if (portal.state === 'PHANTOM_QUARANTINE' && quarantine.current?.active) {
|
|
63
|
+
captureInput(quarantine.current, `tool_call:${toolName}`, {
|
|
64
|
+
tool: toolName,
|
|
65
|
+
args,
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
});
|
|
68
|
+
return j({
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'GOVERNANCE_QUARANTINED: Agent is in phantom quarantine. All outputs are severed. Inputs are being captured for forensic analysis.',
|
|
71
|
+
portal_state: portal.state,
|
|
72
|
+
tool: toolName,
|
|
73
|
+
forensic_capture: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// INITIALIZATION or ARTIFACT_VERIFICATION → not yet governed
|
|
78
|
+
if (portal.state === 'INITIALIZATION' || portal.state === 'ARTIFACT_VERIFICATION') {
|
|
79
|
+
return j({
|
|
80
|
+
success: false,
|
|
81
|
+
error: 'GOVERNANCE_NOT_READY: No active policy artifact. Call attest_subject first.',
|
|
82
|
+
portal_state: portal.state,
|
|
83
|
+
tool: toolName,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ACTIVE_MONITORING or DRIFT_DETECTED → record + allow through
|
|
88
|
+
if (behavioralMonitor) {
|
|
89
|
+
const argsHash = sha256Str(canonicalize(args));
|
|
90
|
+
behavioralMonitor.recordInvocation(toolName, argsHash);
|
|
91
|
+
}
|
|
92
|
+
return handler(args);
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './governance.js';
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGA Governance Proxy - Tool Policy Evaluator
|
|
3
|
+
* Ported from aga-mcp-gateway/src/governance/policy.ts with rate limiting.
|
|
4
|
+
*
|
|
5
|
+
* Patent: USPTO App. No. 19/433,835
|
|
6
|
+
* Copyright (c) 2026 Attested Intelligence Holdings LLC
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ToolPolicy, ToolCallDecision } from './types.js';
|
|
11
|
+
|
|
12
|
+
// ── Rate Limiter ────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
interface RateWindow {
|
|
15
|
+
timestamps: number[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const rateLimits = new Map<string, RateWindow>();
|
|
19
|
+
|
|
20
|
+
function checkRateLimit(toolName: string, maxPerMinute: number): boolean {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const cutoff = now - 60_000;
|
|
23
|
+
|
|
24
|
+
let window = rateLimits.get(toolName);
|
|
25
|
+
if (!window) {
|
|
26
|
+
window = { timestamps: [] };
|
|
27
|
+
rateLimits.set(toolName, window);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Prune expired entries
|
|
31
|
+
window.timestamps = window.timestamps.filter(t => t > cutoff);
|
|
32
|
+
|
|
33
|
+
if (window.timestamps.length >= maxPerMinute) return false;
|
|
34
|
+
|
|
35
|
+
window.timestamps.push(now);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resetRateLimits(): void {
|
|
40
|
+
rateLimits.clear();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── Path Utilities (from aga-mcp-gateway) ───────────────────
|
|
44
|
+
|
|
45
|
+
export function cleanPath(p: string): string {
|
|
46
|
+
p = p.replace(/\\/g, '/');
|
|
47
|
+
p = p.replace(/\/+/g, '/');
|
|
48
|
+
|
|
49
|
+
const segments = p.split('/');
|
|
50
|
+
const resolved: string[] = [];
|
|
51
|
+
const absolute = segments[0] === '';
|
|
52
|
+
|
|
53
|
+
for (const seg of segments) {
|
|
54
|
+
if (seg === '' || seg === '.') continue;
|
|
55
|
+
if (seg === '..') {
|
|
56
|
+
if (resolved.length > 0 && resolved[resolved.length - 1] !== '..') {
|
|
57
|
+
resolved.pop();
|
|
58
|
+
} else if (!absolute) {
|
|
59
|
+
resolved.push('..');
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
resolved.push(seg);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let result = (absolute ? '/' : '') + resolved.join('/');
|
|
67
|
+
if (result === '') result = '.';
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function matchesPrefix(prefix: string, candidate: string): boolean {
|
|
72
|
+
const cleanPrefix = cleanPath(prefix);
|
|
73
|
+
const cleanCandidate = cleanPath(candidate);
|
|
74
|
+
|
|
75
|
+
if (cleanCandidate === cleanPrefix) return true;
|
|
76
|
+
const prefixWithSlash = cleanPrefix.endsWith('/') ? cleanPrefix : cleanPrefix + '/';
|
|
77
|
+
return cleanCandidate.startsWith(prefixWithSlash);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkPathConstraints(
|
|
81
|
+
constraint: { path_prefix?: string; path_keys?: string[] },
|
|
82
|
+
args?: Record<string, unknown>,
|
|
83
|
+
): string | null {
|
|
84
|
+
if (!constraint.path_prefix) return null;
|
|
85
|
+
const keys = constraint.path_keys?.length ? constraint.path_keys : ['path'];
|
|
86
|
+
if (!args) return null;
|
|
87
|
+
|
|
88
|
+
for (const key of keys) {
|
|
89
|
+
const val = args[key];
|
|
90
|
+
if (typeof val === 'string') {
|
|
91
|
+
if (!matchesPrefix(constraint.path_prefix, val)) {
|
|
92
|
+
return `path "${val}" outside allowed prefix "${constraint.path_prefix}"`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function checkDeniedPatterns(
|
|
100
|
+
constraint: { denied_patterns?: string[] },
|
|
101
|
+
args?: Record<string, unknown>,
|
|
102
|
+
): string | null {
|
|
103
|
+
if (!constraint.denied_patterns?.length) return null;
|
|
104
|
+
if (!args) return null;
|
|
105
|
+
|
|
106
|
+
for (const [, val] of Object.entries(args)) {
|
|
107
|
+
if (typeof val !== 'string') continue;
|
|
108
|
+
for (const pattern of constraint.denied_patterns) {
|
|
109
|
+
if (val.includes(pattern)) {
|
|
110
|
+
return `argument value matches denied pattern "${pattern}"`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Main Evaluator ──────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export function evaluate(
|
|
120
|
+
policy: ToolPolicy,
|
|
121
|
+
toolName: string,
|
|
122
|
+
args?: Record<string, unknown>,
|
|
123
|
+
): ToolCallDecision {
|
|
124
|
+
const base = { tool_name: toolName, policy_mode: policy.mode };
|
|
125
|
+
|
|
126
|
+
// Audit-only mode: always permit
|
|
127
|
+
if (policy.mode === 'audit_only') {
|
|
128
|
+
return { ...base, allowed: true, reason: 'audit_only: all calls permitted' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (policy.mode !== 'allowlist' && policy.mode !== 'denylist') {
|
|
132
|
+
return { ...base, allowed: false, reason: `unknown policy mode: ${policy.mode}` };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const constraint = policy.constraints[toolName];
|
|
136
|
+
|
|
137
|
+
if (policy.mode === 'allowlist') {
|
|
138
|
+
if (!constraint) {
|
|
139
|
+
return { ...base, allowed: false, reason: 'tool not in allowlist' };
|
|
140
|
+
}
|
|
141
|
+
if (!constraint.allowed) {
|
|
142
|
+
return { ...base, allowed: false, reason: 'tool explicitly disallowed' };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Rate limit check
|
|
146
|
+
if (constraint.max_calls_per_minute) {
|
|
147
|
+
if (!checkRateLimit(toolName, constraint.max_calls_per_minute)) {
|
|
148
|
+
return { ...base, allowed: false, reason: `rate limit exceeded: ${constraint.max_calls_per_minute}/min` };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const pathResult = checkPathConstraints(constraint, args);
|
|
153
|
+
if (pathResult !== null) {
|
|
154
|
+
return { ...base, allowed: false, reason: pathResult };
|
|
155
|
+
}
|
|
156
|
+
const patternResult = checkDeniedPatterns(constraint, args);
|
|
157
|
+
if (patternResult !== null) {
|
|
158
|
+
return { ...base, allowed: false, reason: patternResult };
|
|
159
|
+
}
|
|
160
|
+
return { ...base, allowed: true, reason: 'tool permitted by allowlist' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Denylist mode
|
|
164
|
+
if (constraint && !constraint.allowed) {
|
|
165
|
+
return { ...base, allowed: false, reason: 'tool denied by denylist' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Rate limit check for denylist mode (tool not explicitly denied)
|
|
169
|
+
if (constraint?.max_calls_per_minute) {
|
|
170
|
+
if (!checkRateLimit(toolName, constraint.max_calls_per_minute)) {
|
|
171
|
+
return { ...base, allowed: false, reason: `rate limit exceeded: ${constraint.max_calls_per_minute}/min` };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { ...base, allowed: true, reason: 'tool not denied' };
|
|
176
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AGA Governance Proxy - CLI Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* aga-proxy start --upstream "node server.js" # stdio upstream
|
|
7
|
+
* aga-proxy start --upstream-url http://host:port # HTTP upstream
|
|
8
|
+
* aga-proxy start --profile standard # policy profile
|
|
9
|
+
* aga-proxy stop
|
|
10
|
+
* aga-proxy status
|
|
11
|
+
* aga-proxy export --output bundle.json
|
|
12
|
+
* aga-proxy verify bundle.json
|
|
13
|
+
*
|
|
14
|
+
* Patent: USPTO App. No. 19/433,835
|
|
15
|
+
* Copyright (c) 2026 Attested Intelligence Holdings LLC
|
|
16
|
+
* SPDX-License-Identifier: MIT
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Command } from 'commander';
|
|
20
|
+
import * as fs from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import * as os from 'node:os';
|
|
23
|
+
import { GovernanceProxy } from './server.js';
|
|
24
|
+
import { PROFILES } from './profiles.js';
|
|
25
|
+
import type { ToolPolicy } from './types.js';
|
|
26
|
+
|
|
27
|
+
const program = new Command();
|
|
28
|
+
let proxy: GovernanceProxy | null = null;
|
|
29
|
+
|
|
30
|
+
function getDataDir(): string {
|
|
31
|
+
return path.join(os.homedir(), '.aga-proxy');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getPidFile(): string {
|
|
35
|
+
return path.join(getDataDir(), 'proxy.pid');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.name('aga-proxy')
|
|
40
|
+
.description('AGA Governance Proxy - cryptographic runtime governance for MCP tool calls')
|
|
41
|
+
.version('0.1.0');
|
|
42
|
+
|
|
43
|
+
// ── start ────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.command('start')
|
|
47
|
+
.description('Start the governance proxy')
|
|
48
|
+
.option('-p, --port <port>', 'Proxy port', '18800')
|
|
49
|
+
.option('--upstream <command>', 'Downstream MCP server command (stdio)')
|
|
50
|
+
.option('--upstream-url <url>', 'Downstream MCP server URL (HTTP)')
|
|
51
|
+
.option('--profile <name>', 'Policy profile: permissive, standard, restrictive', 'permissive')
|
|
52
|
+
.option('--policy <path>', 'Custom policy JSON file')
|
|
53
|
+
.action(async (opts) => {
|
|
54
|
+
const port = parseInt(opts.port, 10);
|
|
55
|
+
let policy: ToolPolicy;
|
|
56
|
+
|
|
57
|
+
if (opts.policy) {
|
|
58
|
+
policy = JSON.parse(fs.readFileSync(opts.policy, 'utf-8'));
|
|
59
|
+
} else {
|
|
60
|
+
policy = PROFILES[opts.profile] ?? PROFILES.permissive;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const upstream = opts.upstream ? parseUpstreamCommand(opts.upstream) : undefined;
|
|
64
|
+
|
|
65
|
+
proxy = new GovernanceProxy({
|
|
66
|
+
port,
|
|
67
|
+
policy,
|
|
68
|
+
upstream,
|
|
69
|
+
upstreamUrl: opts.upstreamUrl,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
proxy.on('started', ({ port: p }: { port: number }) => {
|
|
73
|
+
console.log(`AGA Governance Proxy started on port ${p}`);
|
|
74
|
+
console.log(`Policy mode: ${policy.mode}`);
|
|
75
|
+
if (opts.upstream) console.log(`Upstream (stdio): ${opts.upstream}`);
|
|
76
|
+
if (opts.upstreamUrl) console.log(`Upstream (HTTP): ${opts.upstreamUrl}`);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
proxy.on('error', (err: Error) => {
|
|
80
|
+
console.error(`Proxy error: ${err.message}`);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Ensure data dir exists
|
|
84
|
+
const dataDir = getDataDir();
|
|
85
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
await proxy.start();
|
|
88
|
+
|
|
89
|
+
// Write PID file
|
|
90
|
+
fs.writeFileSync(getPidFile(), String(process.pid));
|
|
91
|
+
|
|
92
|
+
// Graceful shutdown
|
|
93
|
+
const shutdown = async () => {
|
|
94
|
+
console.log('\nShutting down...');
|
|
95
|
+
if (proxy) {
|
|
96
|
+
await proxy.stop();
|
|
97
|
+
try { fs.unlinkSync(getPidFile()); } catch { /* ok */ }
|
|
98
|
+
}
|
|
99
|
+
process.exit(0);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
process.on('SIGINT', shutdown);
|
|
103
|
+
process.on('SIGTERM', shutdown);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ── run (foreground, alias for start) ────────────────────────
|
|
107
|
+
|
|
108
|
+
program
|
|
109
|
+
.command('run')
|
|
110
|
+
.description('Run proxy in foreground (same as start, Ctrl+C to stop)')
|
|
111
|
+
.option('-p, --port <port>', 'Proxy port', '18800')
|
|
112
|
+
.option('--upstream <command>', 'Downstream MCP server command (stdio)')
|
|
113
|
+
.option('--upstream-url <url>', 'Downstream MCP server URL (HTTP)')
|
|
114
|
+
.option('--profile <name>', 'Policy profile', 'permissive')
|
|
115
|
+
.option('--policy <path>', 'Custom policy JSON file')
|
|
116
|
+
.action(async (opts) => {
|
|
117
|
+
// Delegate to start - identical behavior in Node.js
|
|
118
|
+
await program.commands.find(c => c.name() === 'start')!.parseAsync(
|
|
119
|
+
['node', 'aga-proxy', 'start', ...process.argv.slice(3)],
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// ── stop ─────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
program
|
|
126
|
+
.command('stop')
|
|
127
|
+
.description('Stop the running proxy')
|
|
128
|
+
.action(async () => {
|
|
129
|
+
const pidFile = getPidFile();
|
|
130
|
+
if (!fs.existsSync(pidFile)) {
|
|
131
|
+
console.log('No running proxy found');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
135
|
+
try {
|
|
136
|
+
process.kill(pid, 'SIGTERM');
|
|
137
|
+
console.log(`Sent SIGTERM to proxy (PID ${pid})`);
|
|
138
|
+
fs.unlinkSync(pidFile);
|
|
139
|
+
} catch {
|
|
140
|
+
console.log('Proxy process not found, cleaning up PID file');
|
|
141
|
+
fs.unlinkSync(pidFile);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ── status ───────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
program
|
|
148
|
+
.command('status')
|
|
149
|
+
.description('Show proxy status')
|
|
150
|
+
.action(async () => {
|
|
151
|
+
if (proxy) {
|
|
152
|
+
console.log(JSON.stringify(proxy.getStatus(), null, 2));
|
|
153
|
+
} else {
|
|
154
|
+
const pidFile = getPidFile();
|
|
155
|
+
if (fs.existsSync(pidFile)) {
|
|
156
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, 0); // Check if alive
|
|
159
|
+
console.log(JSON.stringify({ running: true, pid }, null, 2));
|
|
160
|
+
} catch {
|
|
161
|
+
console.log(JSON.stringify({ running: false, stale_pid: pid }, null, 2));
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
console.log(JSON.stringify({ running: false }, null, 2));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ── export ───────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
program
|
|
172
|
+
.command('export')
|
|
173
|
+
.description('Export evidence bundle')
|
|
174
|
+
.option('-o, --output <path>', 'Output file', 'evidence-bundle.json')
|
|
175
|
+
.action(async (opts) => {
|
|
176
|
+
if (!proxy) {
|
|
177
|
+
console.error('Proxy not running in this process. Start the proxy first.');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
const bundle = await proxy.exportBundle();
|
|
181
|
+
fs.writeFileSync(opts.output, JSON.stringify(bundle, null, 2));
|
|
182
|
+
console.log(`Evidence bundle exported to ${opts.output}`);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// ── verify ───────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
program
|
|
188
|
+
.command('verify <bundle>')
|
|
189
|
+
.description('Verify an evidence bundle (Ed25519-SHA256-JCS format)')
|
|
190
|
+
.action(async (bundlePath) => {
|
|
191
|
+
const { verifyGatewayBundle } = await import('./verify.js');
|
|
192
|
+
const bundleJson = fs.readFileSync(bundlePath, 'utf-8');
|
|
193
|
+
const result = await verifyGatewayBundle(bundleJson);
|
|
194
|
+
|
|
195
|
+
console.log(`Algorithm: ${result.algorithm_valid ? 'PASS' : 'FAIL'}`);
|
|
196
|
+
console.log(`Signatures: ${result.receipt_signatures_valid ? 'PASS' : 'FAIL'} (${result.receipts_checked} receipts)`);
|
|
197
|
+
console.log(`Chain integrity: ${result.chain_integrity_valid ? 'PASS' : 'FAIL'}`);
|
|
198
|
+
console.log(`Merkle proofs: ${result.merkle_proofs_valid ? 'PASS' : 'FAIL'}`);
|
|
199
|
+
console.log(`Consistency: ${result.bundle_consistent ? 'PASS' : 'FAIL'}`);
|
|
200
|
+
console.log(`\nOVERALL: ${result.overall_valid ? 'VERIFIED' : 'FAILED'}`);
|
|
201
|
+
if (result.error) console.log(`Error: ${result.error}`);
|
|
202
|
+
|
|
203
|
+
process.exit(result.overall_valid ? 0 : 1);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ── policy ───────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
const policyCmd = program.command('policy').description('Policy management');
|
|
209
|
+
|
|
210
|
+
policyCmd
|
|
211
|
+
.command('show')
|
|
212
|
+
.description('Show current policy')
|
|
213
|
+
.action(() => {
|
|
214
|
+
if (!proxy) {
|
|
215
|
+
console.error('Proxy not running in this process.');
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
console.log(JSON.stringify(proxy.getStatus(), null, 2));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
policyCmd
|
|
222
|
+
.command('switch <profile>')
|
|
223
|
+
.description('Switch policy profile')
|
|
224
|
+
.action(async (profile) => {
|
|
225
|
+
if (!proxy) {
|
|
226
|
+
console.error('Proxy not running in this process.');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const newPolicy = PROFILES[profile];
|
|
230
|
+
if (!newPolicy) {
|
|
231
|
+
console.error(`Unknown profile: ${profile}. Available: ${Object.keys(PROFILES).join(', ')}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
await proxy.switchPolicy(newPolicy);
|
|
235
|
+
console.log(`Switched to ${profile} profile`);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ── helpers ──────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
function parseUpstreamCommand(cmd: string): { command: string; args: string[] } {
|
|
241
|
+
const parts = cmd.split(/\s+/);
|
|
242
|
+
return { command: parts[0], args: parts.slice(1) };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── main ─────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
export { GovernanceProxy } from './server.js';
|
|
248
|
+
export { evaluate, resetRateLimits } from './evaluator.js';
|
|
249
|
+
export { PROFILES, PERMISSIVE, STANDARD, RESTRICTIVE } from './profiles.js';
|
|
250
|
+
export type { ToolPolicy, ToolConstraint, ToolCallDecision, ProxyConfig } from './types.js';
|
|
251
|
+
|
|
252
|
+
// Only parse CLI if run directly
|
|
253
|
+
const isDirectRun = process.argv[1]?.includes('proxy') || process.argv[1]?.includes('aga-proxy');
|
|
254
|
+
if (isDirectRun) {
|
|
255
|
+
program.parseAsync().catch((err) => {
|
|
256
|
+
console.error(err);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGA Governance Proxy - Built-in Policy Profiles
|
|
3
|
+
*
|
|
4
|
+
* Patent: USPTO App. No. 19/433,835
|
|
5
|
+
* Copyright (c) 2026 Attested Intelligence Holdings LLC
|
|
6
|
+
* SPDX-License-Identifier: MIT
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ToolPolicy } from './types.js';
|
|
10
|
+
|
|
11
|
+
/** All tools permitted, no rate limits, logging only. */
|
|
12
|
+
export const PERMISSIVE: ToolPolicy = {
|
|
13
|
+
mode: 'audit_only',
|
|
14
|
+
constraints: {},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** All common tools allowed with rate limits. Dangerous patterns denied. */
|
|
18
|
+
export const STANDARD: ToolPolicy = {
|
|
19
|
+
mode: 'allowlist',
|
|
20
|
+
constraints: {
|
|
21
|
+
filesystem_read: { name: 'filesystem_read', allowed: true, max_calls_per_minute: 30 },
|
|
22
|
+
filesystem_write: { name: 'filesystem_write', allowed: true, max_calls_per_minute: 30, denied_patterns: ['/etc/', '/sys/', '/proc/'] },
|
|
23
|
+
shell_execute: { name: 'shell_execute', allowed: true, max_calls_per_minute: 10, denied_patterns: ['rm -rf', 'mkfs', 'dd if=', ':(){:|:&};:'] },
|
|
24
|
+
web_search: { name: 'web_search', allowed: true, max_calls_per_minute: 20 },
|
|
25
|
+
web_fetch: { name: 'web_fetch', allowed: true, max_calls_per_minute: 20 },
|
|
26
|
+
send_message: { name: 'send_message', allowed: true, max_calls_per_minute: 5 },
|
|
27
|
+
calendar_create: { name: 'calendar_create', allowed: true, max_calls_per_minute: 5 },
|
|
28
|
+
memory_search: { name: 'memory_search', allowed: true, max_calls_per_minute: 30 },
|
|
29
|
+
memory_store: { name: 'memory_store', allowed: true, max_calls_per_minute: 10 },
|
|
30
|
+
code_execute: { name: 'code_execute', allowed: true, max_calls_per_minute: 10 },
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Explicit allowlist only. All unrecognized tools denied. Low rate limits. */
|
|
35
|
+
export const RESTRICTIVE: ToolPolicy = {
|
|
36
|
+
mode: 'allowlist',
|
|
37
|
+
constraints: {
|
|
38
|
+
filesystem_read: { name: 'filesystem_read', allowed: true, max_calls_per_minute: 10, path_prefix: '/home' },
|
|
39
|
+
web_search: { name: 'web_search', allowed: true, max_calls_per_minute: 5 },
|
|
40
|
+
memory_search: { name: 'memory_search', allowed: true, max_calls_per_minute: 10 },
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const PROFILES: Record<string, ToolPolicy> = {
|
|
45
|
+
permissive: PERMISSIVE,
|
|
46
|
+
standard: STANDARD,
|
|
47
|
+
restrictive: RESTRICTIVE,
|
|
48
|
+
};
|