@winstonsayno/mcp-gateway 3.6.0 → 3.8.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 +34 -0
- package/README.md +20 -0
- package/dashboard/portal.html +202 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +47 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/gateway/api.d.ts +3 -0
- package/dist/gateway/api.d.ts.map +1 -1
- package/dist/gateway/api.js +230 -1
- package/dist/gateway/api.js.map +1 -1
- package/dist/gateway/index.d.ts +5 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +31 -2
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/invoker.d.ts +4 -0
- package/dist/gateway/invoker.d.ts.map +1 -1
- package/dist/gateway/invoker.js +35 -1
- package/dist/gateway/invoker.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/catalog.d.ts +1 -1
- package/dist/policy/compliance.d.ts +119 -0
- package/dist/policy/compliance.d.ts.map +1 -0
- package/dist/policy/compliance.js +216 -0
- package/dist/policy/compliance.js.map +1 -0
- package/dist/portal/index.d.ts +90 -0
- package/dist/portal/index.d.ts.map +1 -0
- package/dist/portal/index.js +223 -0
- package/dist/portal/index.js.map +1 -0
- package/dist/utils/types.d.ts +56 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compliance suite (3.7): PII detection and redaction, data residency, SOC 2 / GDPR reports.
|
|
3
|
+
*
|
|
4
|
+
* - **PII:** `compliance.pii` scans tool arguments (before they leave for the upstream) and/or results (before they
|
|
5
|
+
* reach the client) for e-mail addresses, phone numbers, payment cards (Luhn-checked), US SSNs, IBANs (mod-97),
|
|
6
|
+
* IPv4 addresses and PRC resident ID numbers (checksum). `action: redact` (default) masks matches
|
|
7
|
+
* (`[REDACTED:email]`), `block` refuses the call (`-32012`), `tag` only counts them.
|
|
8
|
+
* - **Residency:** servers declare a `region`; `compliance.residency` rules pin tenants (or everybody) to region
|
|
9
|
+
* globs. A call that would send a tenant's data to a server — or a federation peer — outside its regions is
|
|
10
|
+
* refused (`-32011`).
|
|
11
|
+
* - **Reports:** `GET /api/v1/compliance/report?framework=soc2|gdpr` summarises controls and evidence from the
|
|
12
|
+
* live configuration, metrics and request history (JSON or Markdown).
|
|
13
|
+
*
|
|
14
|
+
* @module policy/compliance
|
|
15
|
+
*/
|
|
16
|
+
import type { ComplianceConfig, PiiCategory } from '../utils/types.js';
|
|
17
|
+
export declare const ERR_RESIDENCY = -32011;
|
|
18
|
+
export declare const ERR_PII_BLOCKED = -32012;
|
|
19
|
+
export declare const PII_CATEGORIES: PiiCategory[];
|
|
20
|
+
export interface PiiFinding {
|
|
21
|
+
category: PiiCategory;
|
|
22
|
+
/** JSON path of the string the match was found in. */
|
|
23
|
+
path: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PiiScanResult<T> {
|
|
26
|
+
value: T;
|
|
27
|
+
findings: PiiFinding[];
|
|
28
|
+
}
|
|
29
|
+
/** Scan (and optionally mask) every string inside a JSON value. */
|
|
30
|
+
export declare function scanPii<T>(value: T, opts?: {
|
|
31
|
+
categories?: PiiCategory[];
|
|
32
|
+
redact?: boolean;
|
|
33
|
+
}): PiiScanResult<T>;
|
|
34
|
+
export type PiiDirection = 'arguments' | 'results';
|
|
35
|
+
/** Applies `compliance.pii` and `compliance.residency`; keeps counters for the reports. */
|
|
36
|
+
export declare class ComplianceEngine {
|
|
37
|
+
private readonly config;
|
|
38
|
+
readonly piiCounts: Map<string, number>;
|
|
39
|
+
readonly blocked: {
|
|
40
|
+
pii: number;
|
|
41
|
+
residency: number;
|
|
42
|
+
};
|
|
43
|
+
constructor(config: () => ComplianceConfig | undefined);
|
|
44
|
+
private piiCfg;
|
|
45
|
+
piiApplies(serverId: string, dir: PiiDirection): boolean;
|
|
46
|
+
/** Scan a payload; returns the (possibly redacted) value, or `blocked` with the categories found. */
|
|
47
|
+
applyPii<T>(serverId: string, dir: PiiDirection, value: T): {
|
|
48
|
+
value: T;
|
|
49
|
+
categories: PiiCategory[];
|
|
50
|
+
blocked: boolean;
|
|
51
|
+
};
|
|
52
|
+
/** Allowed region globs for a tenant (undefined = unrestricted). */
|
|
53
|
+
regionsFor(tenant: string | undefined): string[] | undefined;
|
|
54
|
+
/** Whether a tenant may send data to a server / peer in `region`. Unknown region counts as a violation when pinned. */
|
|
55
|
+
residencyAllows(tenant: string | undefined, region: string | undefined): boolean;
|
|
56
|
+
noteResidencyBlock(): void;
|
|
57
|
+
findings(): Record<string, number>;
|
|
58
|
+
}
|
|
59
|
+
export interface ReportInput {
|
|
60
|
+
framework: 'soc2' | 'gdpr';
|
|
61
|
+
generatedAt: string;
|
|
62
|
+
gatewayVersion: string;
|
|
63
|
+
period: {
|
|
64
|
+
since: string;
|
|
65
|
+
until: string;
|
|
66
|
+
};
|
|
67
|
+
config: {
|
|
68
|
+
authStrategy: string;
|
|
69
|
+
tenants: number;
|
|
70
|
+
auditEnabled: boolean;
|
|
71
|
+
auditRetentionDays?: number;
|
|
72
|
+
tlsUpstreams: number;
|
|
73
|
+
plainUpstreams: number;
|
|
74
|
+
policyRules: number;
|
|
75
|
+
approvals: boolean;
|
|
76
|
+
outputFilter: boolean;
|
|
77
|
+
pii?: {
|
|
78
|
+
action: string;
|
|
79
|
+
scope: string;
|
|
80
|
+
categories: string[];
|
|
81
|
+
};
|
|
82
|
+
residencyRules: number;
|
|
83
|
+
secretsProviders: number;
|
|
84
|
+
rotationSeconds?: number;
|
|
85
|
+
rateLimit: boolean;
|
|
86
|
+
authLockout: boolean;
|
|
87
|
+
redactPatterns: number;
|
|
88
|
+
};
|
|
89
|
+
activity: {
|
|
90
|
+
calls: number;
|
|
91
|
+
errors: number;
|
|
92
|
+
denied: number;
|
|
93
|
+
clients: Array<{
|
|
94
|
+
client: string;
|
|
95
|
+
calls: number;
|
|
96
|
+
}>;
|
|
97
|
+
piiFindings: Record<string, number>;
|
|
98
|
+
blocked: {
|
|
99
|
+
pii: number;
|
|
100
|
+
residency: number;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
warnings: Array<{
|
|
104
|
+
id: string;
|
|
105
|
+
severity: string;
|
|
106
|
+
message: string;
|
|
107
|
+
}>;
|
|
108
|
+
}
|
|
109
|
+
export interface ControlResult {
|
|
110
|
+
id: string;
|
|
111
|
+
title: string;
|
|
112
|
+
status: 'pass' | 'warn' | 'fail';
|
|
113
|
+
evidence: string;
|
|
114
|
+
}
|
|
115
|
+
/** Map configuration and activity to framework controls. */
|
|
116
|
+
export declare function evaluateControls(r: ReportInput): ControlResult[];
|
|
117
|
+
export declare function buildReport(r: ReportInput): Record<string, unknown>;
|
|
118
|
+
export declare function reportMarkdown(rep: Record<string, unknown>): string;
|
|
119
|
+
//# sourceMappingURL=compliance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/policy/compliance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compliance suite (3.7): PII detection and redaction, data residency, SOC 2 / GDPR reports.
|
|
3
|
+
*
|
|
4
|
+
* - **PII:** `compliance.pii` scans tool arguments (before they leave for the upstream) and/or results (before they
|
|
5
|
+
* reach the client) for e-mail addresses, phone numbers, payment cards (Luhn-checked), US SSNs, IBANs (mod-97),
|
|
6
|
+
* IPv4 addresses and PRC resident ID numbers (checksum). `action: redact` (default) masks matches
|
|
7
|
+
* (`[REDACTED:email]`), `block` refuses the call (`-32012`), `tag` only counts them.
|
|
8
|
+
* - **Residency:** servers declare a `region`; `compliance.residency` rules pin tenants (or everybody) to region
|
|
9
|
+
* globs. A call that would send a tenant's data to a server — or a federation peer — outside its regions is
|
|
10
|
+
* refused (`-32011`).
|
|
11
|
+
* - **Reports:** `GET /api/v1/compliance/report?framework=soc2|gdpr` summarises controls and evidence from the
|
|
12
|
+
* live configuration, metrics and request history (JSON or Markdown).
|
|
13
|
+
*
|
|
14
|
+
* @module policy/compliance
|
|
15
|
+
*/
|
|
16
|
+
import { globToRegExp } from '../utils/tool-filter.js';
|
|
17
|
+
export const ERR_RESIDENCY = -32011;
|
|
18
|
+
export const ERR_PII_BLOCKED = -32012;
|
|
19
|
+
export const PII_CATEGORIES = ['email', 'phone', 'credit-card', 'ssn', 'iban', 'ipv4', 'cn-id'];
|
|
20
|
+
function luhn(digits) {
|
|
21
|
+
let sum = 0;
|
|
22
|
+
let dbl = false;
|
|
23
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
24
|
+
let d = digits.charCodeAt(i) - 48;
|
|
25
|
+
if (dbl) {
|
|
26
|
+
d *= 2;
|
|
27
|
+
if (d > 9)
|
|
28
|
+
d -= 9;
|
|
29
|
+
}
|
|
30
|
+
sum += d;
|
|
31
|
+
dbl = !dbl;
|
|
32
|
+
}
|
|
33
|
+
return sum % 10 === 0;
|
|
34
|
+
}
|
|
35
|
+
function ibanValid(raw) {
|
|
36
|
+
const s = raw.replace(/\s+/g, '').toUpperCase();
|
|
37
|
+
if (s.length < 15 || s.length > 34)
|
|
38
|
+
return false;
|
|
39
|
+
const re = s.slice(4) + s.slice(0, 4);
|
|
40
|
+
let rem = 0;
|
|
41
|
+
for (const ch of re) {
|
|
42
|
+
const v = ch >= 'A' && ch <= 'Z' ? String(ch.charCodeAt(0) - 55) : ch;
|
|
43
|
+
for (const c of v)
|
|
44
|
+
rem = (rem * 10 + (c.charCodeAt(0) - 48)) % 97;
|
|
45
|
+
}
|
|
46
|
+
return rem === 1;
|
|
47
|
+
}
|
|
48
|
+
function cnIdValid(id) {
|
|
49
|
+
const w = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
|
|
50
|
+
const codes = '10X98765432';
|
|
51
|
+
let sum = 0;
|
|
52
|
+
for (let i = 0; i < 17; i++)
|
|
53
|
+
sum += (id.charCodeAt(i) - 48) * w[i];
|
|
54
|
+
return codes[sum % 11] === id[17].toUpperCase();
|
|
55
|
+
}
|
|
56
|
+
const DETECTORS = [
|
|
57
|
+
{ category: 'email', re: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g },
|
|
58
|
+
{ category: 'iban', re: /\b[A-Z]{2}\d{2}(?: ?[A-Z0-9]{4}){2,7}(?: ?[A-Z0-9]{1,4})?\b/g, valid: ibanValid },
|
|
59
|
+
{ category: 'credit-card', re: /\b(?:\d[ -]?){12,18}\d\b/g, valid: (m) => luhn(m.replace(/[ -]/g, '')) && m.replace(/[ -]/g, '').length >= 13 },
|
|
60
|
+
{ category: 'cn-id', re: /\b\d{6}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b/g, valid: cnIdValid },
|
|
61
|
+
{ category: 'ssn', re: /\b(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b/g },
|
|
62
|
+
{ category: 'phone', re: /(?<![\w+])\+\d{1,3}[ .-]?\(?\d{1,4}\)?(?:[ .-]?\d{2,4}){2,4}(?!\w)|\(\d{3}\)[ .-]?\d{3}[ .-]\d{4}\b|\b\d{3}[.-]\d{3}[.-]\d{4}\b|\b1[3-9]\d{9}\b/g },
|
|
63
|
+
{ category: 'ipv4', re: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g },
|
|
64
|
+
];
|
|
65
|
+
/** Scan (and optionally mask) every string inside a JSON value. */
|
|
66
|
+
export function scanPii(value, opts = {}) {
|
|
67
|
+
const cats = new Set(opts.categories ?? PII_CATEGORIES);
|
|
68
|
+
const dets = DETECTORS.filter((d) => cats.has(d.category));
|
|
69
|
+
const findings = [];
|
|
70
|
+
const scanString = (s, path) => {
|
|
71
|
+
let out = s;
|
|
72
|
+
for (const d of dets) {
|
|
73
|
+
out = out.replace(d.re, (m) => {
|
|
74
|
+
if (m.startsWith('[REDACTED:'))
|
|
75
|
+
return m;
|
|
76
|
+
if (d.valid && !d.valid(m))
|
|
77
|
+
return m;
|
|
78
|
+
findings.push({ category: d.category, path });
|
|
79
|
+
return opts.redact ? `[REDACTED:${d.category}]` : m;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
};
|
|
84
|
+
const walk = (v, path, depth) => {
|
|
85
|
+
if (depth > 64)
|
|
86
|
+
return v;
|
|
87
|
+
if (typeof v === 'string')
|
|
88
|
+
return scanString(v, path);
|
|
89
|
+
if (Array.isArray(v))
|
|
90
|
+
return v.map((x, i) => walk(x, `${path}[${i}]`, depth + 1));
|
|
91
|
+
if (v && typeof v === 'object') {
|
|
92
|
+
const o = {};
|
|
93
|
+
for (const [k, x] of Object.entries(v))
|
|
94
|
+
o[k] = walk(x, path ? `${path}.${k}` : k, depth + 1);
|
|
95
|
+
return o;
|
|
96
|
+
}
|
|
97
|
+
return v;
|
|
98
|
+
};
|
|
99
|
+
const out = walk(value, '', 0);
|
|
100
|
+
return { value: opts.redact ? out : value, findings };
|
|
101
|
+
}
|
|
102
|
+
/** Applies `compliance.pii` and `compliance.residency`; keeps counters for the reports. */
|
|
103
|
+
export class ComplianceEngine {
|
|
104
|
+
config;
|
|
105
|
+
piiCounts = new Map();
|
|
106
|
+
blocked = { pii: 0, residency: 0 };
|
|
107
|
+
constructor(config) {
|
|
108
|
+
this.config = config;
|
|
109
|
+
}
|
|
110
|
+
piiCfg() {
|
|
111
|
+
const p = this.config()?.pii;
|
|
112
|
+
return p && p.enabled !== false ? p : undefined;
|
|
113
|
+
}
|
|
114
|
+
piiApplies(serverId, dir) {
|
|
115
|
+
const p = this.piiCfg();
|
|
116
|
+
if (!p)
|
|
117
|
+
return false;
|
|
118
|
+
const scope = p.scope ?? 'both';
|
|
119
|
+
if (scope !== 'both' && scope !== dir)
|
|
120
|
+
return false;
|
|
121
|
+
return !p.servers?.length || p.servers.some((g) => globToRegExp(g).test(serverId));
|
|
122
|
+
}
|
|
123
|
+
/** Scan a payload; returns the (possibly redacted) value, or `blocked` with the categories found. */
|
|
124
|
+
applyPii(serverId, dir, value) {
|
|
125
|
+
const p = this.piiCfg();
|
|
126
|
+
if (!p || !this.piiApplies(serverId, dir))
|
|
127
|
+
return { value, categories: [], blocked: false };
|
|
128
|
+
const action = p.action ?? 'redact';
|
|
129
|
+
const r = scanPii(value, { categories: p.categories, redact: action === 'redact' });
|
|
130
|
+
const categories = [...new Set(r.findings.map((f) => f.category))];
|
|
131
|
+
for (const f of r.findings)
|
|
132
|
+
this.piiCounts.set(`${dir}:${f.category}`, (this.piiCounts.get(`${dir}:${f.category}`) ?? 0) + 1);
|
|
133
|
+
const blocked = action === 'block' && categories.length > 0;
|
|
134
|
+
if (blocked)
|
|
135
|
+
this.blocked.pii++;
|
|
136
|
+
return { value: r.value, categories, blocked };
|
|
137
|
+
}
|
|
138
|
+
/** Allowed region globs for a tenant (undefined = unrestricted). */
|
|
139
|
+
regionsFor(tenant) {
|
|
140
|
+
const rules = this.config()?.residency?.rules ?? [];
|
|
141
|
+
const rule = rules.find((r) => (r.tenants ?? ['*']).some((g) => globToRegExp(g).test(tenant ?? '-')));
|
|
142
|
+
return rule?.regions;
|
|
143
|
+
}
|
|
144
|
+
/** Whether a tenant may send data to a server / peer in `region`. Unknown region counts as a violation when pinned. */
|
|
145
|
+
residencyAllows(tenant, region) {
|
|
146
|
+
const allowed = this.regionsFor(tenant);
|
|
147
|
+
if (!allowed)
|
|
148
|
+
return true;
|
|
149
|
+
if (!region)
|
|
150
|
+
return this.config()?.residency?.allowUnknown === true;
|
|
151
|
+
return allowed.some((g) => globToRegExp(g).test(region));
|
|
152
|
+
}
|
|
153
|
+
noteResidencyBlock() {
|
|
154
|
+
this.blocked.residency++;
|
|
155
|
+
}
|
|
156
|
+
findings() {
|
|
157
|
+
return Object.fromEntries([...this.piiCounts.entries()].sort());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Map configuration and activity to framework controls. */
|
|
161
|
+
export function evaluateControls(r) {
|
|
162
|
+
const c = r.config;
|
|
163
|
+
const ctl = (id, title, ok, evidence) => ({ id, title, status: ok === 'warn' ? 'warn' : ok ? 'pass' : 'fail', evidence });
|
|
164
|
+
if (r.framework === 'soc2') {
|
|
165
|
+
return [
|
|
166
|
+
ctl('CC6.1', 'Logical access is authenticated', c.authStrategy !== 'none', `auth.strategy = ${c.authStrategy}`),
|
|
167
|
+
ctl('CC6.1-b', 'Access is scoped per tenant / role', c.tenants > 0 ? true : 'warn', `${c.tenants} tenant(s) configured`),
|
|
168
|
+
ctl('CC6.6', 'Brute-force and abuse protection', c.authLockout && c.rateLimit ? true : 'warn', `rate limit: ${c.rateLimit ? 'on' : 'off'}, lockout: ${c.authLockout ? 'on' : 'off'}`),
|
|
169
|
+
ctl('CC6.7', 'Data in transit to upstreams is encrypted', c.plainUpstreams === 0 ? true : 'warn', `${c.tlsUpstreams} TLS / ${c.plainUpstreams} plain-HTTP upstream(s)`),
|
|
170
|
+
ctl('CC6.8', 'Tool calls are governed by policy', c.policyRules > 0 || c.approvals ? true : 'warn', `${c.policyRules} policy rule(s), approvals ${c.approvals ? 'on' : 'off'}`),
|
|
171
|
+
ctl('CC7.2', 'Activity is logged and retained', c.auditEnabled, c.auditEnabled ? `audit log on${c.auditRetentionDays ? `, ${c.auditRetentionDays} day retention` : ''}` : 'audit log off'),
|
|
172
|
+
ctl('CC7.3', 'Security events are detected', c.outputFilter || !!c.pii ? true : 'warn', `output filter ${c.outputFilter ? 'on' : 'off'}, PII scanning ${c.pii ? c.pii.action : 'off'}`),
|
|
173
|
+
ctl('CC8.1', 'Credentials are managed and rotated', c.secretsProviders > 0 ? (c.rotationSeconds ? true : 'warn') : 'warn', `${c.secretsProviders} secret provider(s), rotation ${c.rotationSeconds ? `every ${c.rotationSeconds}s` : 'off'}`),
|
|
174
|
+
ctl('C1.1', 'Confidential data is redacted from logs', c.redactPatterns >= 0, `built-in secret patterns + ${c.redactPatterns} custom`),
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
return [
|
|
178
|
+
ctl('Art.5(1)(c)', 'Data minimisation: personal data is redacted', c.pii ? (c.pii.action === 'tag' ? 'warn' : true) : 'warn', c.pii ? `PII ${c.pii.action} on ${c.pii.scope} (${c.pii.categories.join(', ')})` : 'PII scanning off'),
|
|
179
|
+
ctl('Art.5(1)(e)', 'Storage limitation: logs have a retention period', c.auditEnabled ? (c.auditRetentionDays ? true : 'warn') : true, c.auditEnabled ? `audit retention ${c.auditRetentionDays ?? 'unlimited'} days` : 'no persistent audit log'),
|
|
180
|
+
ctl('Art.25', 'Data protection by design (default deny / scoping)', c.authStrategy !== 'none', `auth.strategy = ${c.authStrategy}, ${c.tenants} tenant(s)`),
|
|
181
|
+
ctl('Art.30', 'Records of processing activities', c.auditEnabled ? true : 'warn', c.auditEnabled ? 'audit log on' : 'audit log off'),
|
|
182
|
+
ctl('Art.32', 'Security of processing (encryption, access control)', c.plainUpstreams === 0 && c.authStrategy !== 'none' ? true : 'warn', `${c.plainUpstreams} plain-HTTP upstream(s)`),
|
|
183
|
+
ctl('Art.44', 'International transfers are restricted (data residency)', c.residencyRules > 0 ? true : 'warn', `${c.residencyRules} residency rule(s); ${r.activity.blocked.residency} transfer(s) blocked`),
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
export function buildReport(r) {
|
|
187
|
+
const controls = evaluateControls(r);
|
|
188
|
+
const summary = { pass: controls.filter((c) => c.status === 'pass').length, warn: controls.filter((c) => c.status === 'warn').length, fail: controls.filter((c) => c.status === 'fail').length };
|
|
189
|
+
return { framework: r.framework, generatedAt: r.generatedAt, gatewayVersion: r.gatewayVersion, period: r.period, summary, controls, activity: r.activity, warnings: r.warnings };
|
|
190
|
+
}
|
|
191
|
+
export function reportMarkdown(rep) {
|
|
192
|
+
const r = rep;
|
|
193
|
+
const icon = { pass: '✅', warn: '⚠️', fail: '❌' };
|
|
194
|
+
const lines = [
|
|
195
|
+
`# ${r.framework === 'soc2' ? 'SOC 2' : 'GDPR'} compliance report`,
|
|
196
|
+
'',
|
|
197
|
+
`Generated ${r.generatedAt} by mcp-gateway ${r.gatewayVersion} · period ${r.period.since} → ${r.period.until}`,
|
|
198
|
+
'',
|
|
199
|
+
`**${r.summary.pass} pass · ${r.summary.warn} warn · ${r.summary.fail} fail**`,
|
|
200
|
+
'',
|
|
201
|
+
'| Control | Status | Evidence |',
|
|
202
|
+
'|---|---|---|',
|
|
203
|
+
...r.controls.map((c) => `| ${c.id} — ${c.title} | ${icon[c.status]} ${c.status} | ${c.evidence.replace(/\|/g, '\\|')} |`),
|
|
204
|
+
'',
|
|
205
|
+
'## Activity',
|
|
206
|
+
'',
|
|
207
|
+
`- Calls: ${r.activity.calls} (errors ${r.activity.errors}, refused ${r.activity.denied})`,
|
|
208
|
+
`- Blocked: ${r.activity.blocked.pii} for PII, ${r.activity.blocked.residency} for data residency`,
|
|
209
|
+
`- PII findings: ${Object.entries(r.activity.piiFindings).map(([k, v]) => `${k} ${v}`).join(', ') || 'none'}`,
|
|
210
|
+
`- Top clients: ${r.activity.clients.slice(0, 10).map((c) => `${c.client} (${c.calls})`).join(', ') || 'none'}`,
|
|
211
|
+
];
|
|
212
|
+
if (r.warnings.length)
|
|
213
|
+
lines.push('', '## Security warnings', '', ...r.warnings.map((w) => `- **${w.severity}** ${w.id}: ${w.message}`));
|
|
214
|
+
return lines.join('\n') + '\n';
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=compliance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/policy/compliance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Developer portal (3.8): self-serve API keys, usage, interactive tool docs.
|
|
3
|
+
*
|
|
4
|
+
* With `portal.enabled` (requires `auth.strategy: api-key`) developers sign up at `/portal` with a name and e-mail
|
|
5
|
+
* and receive an API key — immediately (`signup: open`), after an operator approves it (`approval`), or never
|
|
6
|
+
* (`closed`, operators issue keys). Keys are stored hashed (`sha256:`), optionally persisted to
|
|
7
|
+
* `portal.keysFile`, and join the configured `auth.apiKeys` as `portal-<id>` with the `portal.defaults` scope
|
|
8
|
+
* (servers, tools, rate limit, TTL). A key holder can see its usage, rotate or revoke its key, and browse the tools
|
|
9
|
+
* it may call with generated example arguments and copy-paste snippets.
|
|
10
|
+
*
|
|
11
|
+
* @module portal
|
|
12
|
+
*/
|
|
13
|
+
import type { ApiKeyConfig, PortalConfig } from '../utils/types.js';
|
|
14
|
+
export type PortalKeyStatus = 'active' | 'pending' | 'revoked' | 'denied';
|
|
15
|
+
export interface PortalKey {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
email: string;
|
|
19
|
+
/** SHA-256 hex of the key; the key itself is shown once. */
|
|
20
|
+
hash: string;
|
|
21
|
+
/** First characters of the key, for recognising it. */
|
|
22
|
+
prefix: string;
|
|
23
|
+
status: PortalKeyStatus;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
expiresAt?: string;
|
|
26
|
+
decidedAt?: string;
|
|
27
|
+
servers?: string[];
|
|
28
|
+
tools?: string[];
|
|
29
|
+
rateLimit?: {
|
|
30
|
+
limit: number;
|
|
31
|
+
windowSeconds: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare class PortalError extends Error {
|
|
35
|
+
readonly status: number;
|
|
36
|
+
constructor(status: number, message: string);
|
|
37
|
+
}
|
|
38
|
+
export declare const clientIdOf: (k: PortalKey) => string;
|
|
39
|
+
export declare class PortalStore {
|
|
40
|
+
private readonly config;
|
|
41
|
+
private readonly opts;
|
|
42
|
+
private keys;
|
|
43
|
+
constructor(config: () => PortalConfig | undefined, opts?: {
|
|
44
|
+
baseDir?: () => string | undefined;
|
|
45
|
+
onChange?: () => void;
|
|
46
|
+
now?: () => number;
|
|
47
|
+
});
|
|
48
|
+
get enabled(): boolean;
|
|
49
|
+
private now;
|
|
50
|
+
private file;
|
|
51
|
+
load(): void;
|
|
52
|
+
private save;
|
|
53
|
+
private newSecret;
|
|
54
|
+
private defaults;
|
|
55
|
+
/** Self-serve signup. Returns the key only when it is issued right away. */
|
|
56
|
+
signup(input: {
|
|
57
|
+
name?: unknown;
|
|
58
|
+
email?: unknown;
|
|
59
|
+
}): {
|
|
60
|
+
record: PortalKey;
|
|
61
|
+
key?: string;
|
|
62
|
+
};
|
|
63
|
+
/** Operator decision on a pending key. */
|
|
64
|
+
decide(id: string, approve: boolean): PortalKey;
|
|
65
|
+
revoke(id: string): PortalKey;
|
|
66
|
+
/** Replace a key's secret (the old one stops working immediately). */
|
|
67
|
+
rotate(id: string): {
|
|
68
|
+
record: PortalKey;
|
|
69
|
+
key: string;
|
|
70
|
+
};
|
|
71
|
+
get(id: string): PortalKey | undefined;
|
|
72
|
+
/** Portal key behind a client id (`key:portal-<id>`). */
|
|
73
|
+
byClientId(clientId: string | undefined): PortalKey | undefined;
|
|
74
|
+
list(): PortalKey[];
|
|
75
|
+
/** Active keys in `auth.apiKeys` form. */
|
|
76
|
+
apiKeys(): ApiKeyConfig[];
|
|
77
|
+
}
|
|
78
|
+
/** Public view of a key record (no hash). */
|
|
79
|
+
export declare function publicKey(k: PortalKey): Omit<PortalKey, 'hash'> & {
|
|
80
|
+
clientId: string;
|
|
81
|
+
};
|
|
82
|
+
/** Example arguments from a JSON schema (defaults, enums, examples, then type placeholders). */
|
|
83
|
+
export declare function exampleArgs(schema: unknown, depth?: number): unknown;
|
|
84
|
+
/** Copy-paste snippets for one tool. */
|
|
85
|
+
export declare function toolSnippets(baseUrl: string, server: string, tool: string, args: unknown): {
|
|
86
|
+
curl: string;
|
|
87
|
+
javascript: string;
|
|
88
|
+
python: string;
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/portal/index.ts"],"names":[],"mappings":"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"}
|