palaryn 0.1.0 → 0.3.2
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 +243 -588
- package/dist/sdk/typescript/src/client.js +2 -2
- package/dist/sdk/typescript/src/client.js.map +1 -1
- package/dist/src/anomaly/detector.d.ts +7 -4
- package/dist/src/anomaly/detector.d.ts.map +1 -1
- package/dist/src/anomaly/detector.js +22 -12
- package/dist/src/anomaly/detector.js.map +1 -1
- package/dist/src/audit/logger.d.ts +10 -0
- package/dist/src/audit/logger.d.ts.map +1 -1
- package/dist/src/audit/logger.js +52 -38
- package/dist/src/audit/logger.js.map +1 -1
- package/dist/src/auth/routes.d.ts.map +1 -1
- package/dist/src/auth/routes.js +35 -0
- package/dist/src/auth/routes.js.map +1 -1
- package/dist/src/budget/manager.d.ts +5 -0
- package/dist/src/budget/manager.d.ts.map +1 -1
- package/dist/src/budget/manager.js +32 -0
- package/dist/src/budget/manager.js.map +1 -1
- package/dist/src/budget/model-pricing.d.ts +20 -0
- package/dist/src/budget/model-pricing.d.ts.map +1 -0
- package/dist/src/budget/model-pricing.js +107 -0
- package/dist/src/budget/model-pricing.js.map +1 -0
- package/dist/src/budget/usage-extractor.d.ts +3 -1
- package/dist/src/budget/usage-extractor.d.ts.map +1 -1
- package/dist/src/budget/usage-extractor.js +47 -3
- package/dist/src/budget/usage-extractor.js.map +1 -1
- package/dist/src/config/defaults.d.ts.map +1 -1
- package/dist/src/config/defaults.js +65 -13
- package/dist/src/config/defaults.js.map +1 -1
- package/dist/src/dlp/tool-patterns.d.ts +7 -0
- package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
- package/dist/src/dlp/tool-patterns.js +34 -0
- package/dist/src/dlp/tool-patterns.js.map +1 -0
- package/dist/src/executor/filesystem-executor.d.ts +28 -0
- package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
- package/dist/src/executor/filesystem-executor.js +192 -0
- package/dist/src/executor/filesystem-executor.js.map +1 -0
- package/dist/src/executor/http-executor.d.ts.map +1 -1
- package/dist/src/executor/http-executor.js +22 -2
- package/dist/src/executor/http-executor.js.map +1 -1
- package/dist/src/executor/index.d.ts +4 -0
- package/dist/src/executor/index.d.ts.map +1 -1
- package/dist/src/executor/index.js +9 -1
- package/dist/src/executor/index.js.map +1 -1
- package/dist/src/executor/shell-executor.d.ts +22 -0
- package/dist/src/executor/shell-executor.d.ts.map +1 -0
- package/dist/src/executor/shell-executor.js +119 -0
- package/dist/src/executor/shell-executor.js.map +1 -0
- package/dist/src/executor/sql-executor.d.ts +29 -0
- package/dist/src/executor/sql-executor.d.ts.map +1 -0
- package/dist/src/executor/sql-executor.js +114 -0
- package/dist/src/executor/sql-executor.js.map +1 -0
- package/dist/src/executor/websocket-executor.d.ts +26 -0
- package/dist/src/executor/websocket-executor.d.ts.map +1 -0
- package/dist/src/executor/websocket-executor.js +205 -0
- package/dist/src/executor/websocket-executor.js.map +1 -0
- package/dist/src/interceptor/index.d.ts +2 -0
- package/dist/src/interceptor/index.d.ts.map +1 -0
- package/dist/src/interceptor/index.js +6 -0
- package/dist/src/interceptor/index.js.map +1 -0
- package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
- package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
- package/dist/src/interceptor/provider-interceptor.js +302 -0
- package/dist/src/interceptor/provider-interceptor.js.map +1 -0
- package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
- package/dist/src/mcp/auth-verifier.js +3 -2
- package/dist/src/mcp/auth-verifier.js.map +1 -1
- package/dist/src/mcp/bridge.d.ts +14 -10
- package/dist/src/mcp/bridge.d.ts.map +1 -1
- package/dist/src/mcp/bridge.js +51 -227
- package/dist/src/mcp/bridge.js.map +1 -1
- package/dist/src/mcp/http-transport.d.ts +2 -0
- package/dist/src/mcp/http-transport.d.ts.map +1 -1
- package/dist/src/mcp/http-transport.js +117 -66
- package/dist/src/mcp/http-transport.js.map +1 -1
- package/dist/src/mcp/internal-auth.d.ts +13 -0
- package/dist/src/mcp/internal-auth.d.ts.map +1 -0
- package/dist/src/mcp/internal-auth.js +12 -0
- package/dist/src/mcp/internal-auth.js.map +1 -0
- package/dist/src/mcp/tool-definitions.d.ts +41 -0
- package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
- package/dist/src/mcp/tool-definitions.js +491 -0
- package/dist/src/mcp/tool-definitions.js.map +1 -0
- package/dist/src/middleware/auth.js.map +1 -1
- package/dist/src/middleware/session.js.map +1 -1
- package/dist/src/middleware/validate.d.ts +8 -0
- package/dist/src/middleware/validate.d.ts.map +1 -1
- package/dist/src/middleware/validate.js +45 -0
- package/dist/src/middleware/validate.js.map +1 -1
- package/dist/src/policy/engine.d.ts +4 -0
- package/dist/src/policy/engine.d.ts.map +1 -1
- package/dist/src/policy/engine.js +117 -0
- package/dist/src/policy/engine.js.map +1 -1
- package/dist/src/saas/routes.d.ts.map +1 -1
- package/dist/src/saas/routes.js +355 -22
- package/dist/src/saas/routes.js.map +1 -1
- package/dist/src/server/app.d.ts.map +1 -1
- package/dist/src/server/app.js +24 -3
- package/dist/src/server/app.js.map +1 -1
- package/dist/src/server/gateway.d.ts.map +1 -1
- package/dist/src/server/gateway.js +17 -0
- package/dist/src/server/gateway.js.map +1 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +18 -0
- package/dist/src/server/index.js.map +1 -1
- package/dist/src/storage/interfaces.d.ts +14 -3
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/memory.d.ts +2 -0
- package/dist/src/storage/memory.d.ts.map +1 -1
- package/dist/src/storage/memory.js +6 -0
- package/dist/src/storage/memory.js.map +1 -1
- package/dist/src/storage/postgres.d.ts +5 -0
- package/dist/src/storage/postgres.d.ts.map +1 -1
- package/dist/src/storage/postgres.js +16 -0
- package/dist/src/storage/postgres.js.map +1 -1
- package/dist/src/storage/redis.d.ts +10 -0
- package/dist/src/storage/redis.d.ts.map +1 -1
- package/dist/src/storage/redis.js +65 -0
- package/dist/src/storage/redis.js.map +1 -1
- package/dist/src/types/budget.d.ts +4 -0
- package/dist/src/types/budget.d.ts.map +1 -1
- package/dist/src/types/config.d.ts +58 -0
- package/dist/src/types/config.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +1 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/policy.d.ts +11 -1
- package/dist/src/types/policy.d.ts.map +1 -1
- package/dist/src/types/tool-result.d.ts +11 -0
- package/dist/src/types/tool-result.d.ts.map +1 -1
- package/dist/tests/unit/app-routes.test.d.ts +2 -0
- package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/app-routes.test.js +715 -0
- package/dist/tests/unit/app-routes.test.js.map +1 -0
- package/dist/tests/unit/audit-logger.test.js +105 -0
- package/dist/tests/unit/audit-logger.test.js.map +1 -1
- package/dist/tests/unit/auth-providers.test.d.ts +2 -0
- package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-providers.test.js +279 -0
- package/dist/tests/unit/auth-providers.test.js.map +1 -0
- package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
- package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-routes-extended.test.js +993 -0
- package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
- package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
- package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-verifier.test.js +505 -0
- package/dist/tests/unit/auth-verifier.test.js.map +1 -0
- package/dist/tests/unit/billing-routes.test.d.ts +2 -0
- package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/billing-routes.test.js +432 -0
- package/dist/tests/unit/billing-routes.test.js.map +1 -0
- package/dist/tests/unit/config-defaults.test.d.ts +2 -0
- package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
- package/dist/tests/unit/config-defaults.test.js +119 -0
- package/dist/tests/unit/config-defaults.test.js.map +1 -0
- package/dist/tests/unit/defaults.test.js +0 -10
- package/dist/tests/unit/defaults.test.js.map +1 -1
- package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
- package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/filesystem-executor.test.js +280 -0
- package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
- package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
- package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/gateway-branches.test.js +1039 -0
- package/dist/tests/unit/gateway-branches.test.js.map +1 -0
- package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
- package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/http-executor-branches.test.js +495 -0
- package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
- package/dist/tests/unit/logger.test.d.ts +2 -0
- package/dist/tests/unit/logger.test.d.ts.map +1 -0
- package/dist/tests/unit/logger.test.js +97 -0
- package/dist/tests/unit/logger.test.js.map +1 -0
- package/dist/tests/unit/mcp-internal-auth.test.d.ts +2 -0
- package/dist/tests/unit/mcp-internal-auth.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-internal-auth.test.js +445 -0
- package/dist/tests/unit/mcp-internal-auth.test.js.map +1 -0
- package/dist/tests/unit/metrics.test.js +102 -0
- package/dist/tests/unit/metrics.test.js.map +1 -1
- package/dist/tests/unit/model-pricing.test.d.ts +2 -0
- package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
- package/dist/tests/unit/model-pricing.test.js +87 -0
- package/dist/tests/unit/model-pricing.test.js.map +1 -0
- package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
- package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
- package/dist/tests/unit/oauth-stores.test.js +260 -0
- package/dist/tests/unit/oauth-stores.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +466 -0
- package/dist/tests/unit/policy-engine.test.js.map +1 -1
- package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
- package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
- package/dist/tests/unit/provider-interceptor.test.js +472 -0
- package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-branches.test.js +2165 -0
- package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-crud.test.js +332 -0
- package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-data.test.js +405 -0
- package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
- package/dist/tests/unit/saas-routes.test.js +3 -3
- package/dist/tests/unit/saas-routes.test.js.map +1 -1
- package/dist/tests/unit/shell-executor.test.d.ts +2 -0
- package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/shell-executor.test.js +145 -0
- package/dist/tests/unit/shell-executor.test.js.map +1 -0
- package/dist/tests/unit/sql-executor.test.d.ts +2 -0
- package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/sql-executor.test.js +177 -0
- package/dist/tests/unit/sql-executor.test.js.map +1 -0
- package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
- package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
- package/dist/tests/unit/stream-proxy.test.js +147 -0
- package/dist/tests/unit/stream-proxy.test.js.map +1 -0
- package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
- package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
- package/dist/tests/unit/tool-definitions.test.js +184 -0
- package/dist/tests/unit/tool-definitions.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor.test.js +140 -0
- package/dist/tests/unit/usage-extractor.test.js.map +1 -1
- package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
- package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
- package/dist/tests/unit/webhook-handler.test.js +453 -0
- package/dist/tests/unit/webhook-handler.test.js.map +1 -0
- package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
- package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/webhook-routes.test.js +69 -0
- package/dist/tests/unit/webhook-routes.test.js.map +1 -0
- package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
- package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/websocket-executor.test.js +121 -0
- package/dist/tests/unit/websocket-executor.test.js.map +1 -0
- package/package.json +8 -2
- package/policy-packs/demo_fail.yaml +41 -0
- package/policy-packs/full_tools.yaml +136 -0
- package/src/admin/index.ts +1 -0
- package/src/admin/routes.ts +509 -0
- package/src/admin/templates.ts +572 -0
- package/src/anomaly/detector.ts +730 -0
- package/src/anomaly/index.ts +1 -0
- package/src/approval/manager.ts +569 -0
- package/src/approval/webhook.ts +133 -0
- package/src/audit/logger.ts +490 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/password.ts +21 -0
- package/src/auth/pkce.ts +22 -0
- package/src/auth/providers.ts +208 -0
- package/src/auth/routes.ts +561 -0
- package/src/auth/session.ts +84 -0
- package/src/billing/index.ts +6 -0
- package/src/billing/plan-enforcer.ts +135 -0
- package/src/billing/routes.ts +229 -0
- package/src/billing/stripe-client.ts +58 -0
- package/src/billing/webhook-handler.ts +182 -0
- package/src/billing/webhook-routes.ts +28 -0
- package/src/budget/manager.ts +679 -0
- package/src/budget/model-pricing.ts +119 -0
- package/src/budget/usage-extractor.ts +214 -0
- package/src/cli.ts +91 -0
- package/src/config/defaults.ts +261 -0
- package/src/config/validate.ts +88 -0
- package/src/dlp/composite-scanner.ts +213 -0
- package/src/dlp/index.ts +9 -0
- package/src/dlp/interfaces.ts +34 -0
- package/src/dlp/patterns.ts +30 -0
- package/src/dlp/prompt-injection-backend.ts +181 -0
- package/src/dlp/prompt-injection-patterns.ts +302 -0
- package/src/dlp/regex-backend.ts +181 -0
- package/src/dlp/scanner.ts +502 -0
- package/src/dlp/text-normalizer.ts +225 -0
- package/src/dlp/tool-patterns.ts +35 -0
- package/src/dlp/trufflehog-backend.ts +190 -0
- package/src/executor/filesystem-executor.ts +196 -0
- package/src/executor/http-executor.ts +349 -0
- package/src/executor/index.ts +9 -0
- package/src/executor/interfaces.ts +11 -0
- package/src/executor/noop-executor.ts +23 -0
- package/src/executor/registry.ts +64 -0
- package/src/executor/shell-executor.ts +148 -0
- package/src/executor/slack-executor.ts +176 -0
- package/src/executor/sql-executor.ts +146 -0
- package/src/executor/websocket-executor.ts +211 -0
- package/src/index.ts +24 -0
- package/src/interceptor/index.ts +1 -0
- package/src/interceptor/provider-interceptor.ts +315 -0
- package/src/mcp/auth-verifier.ts +152 -0
- package/src/mcp/bridge.ts +703 -0
- package/src/mcp/http-transport.ts +698 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/internal-auth.ts +14 -0
- package/src/mcp/oauth-pages.ts +139 -0
- package/src/mcp/oauth-postgres-stores.ts +278 -0
- package/src/mcp/oauth-provider.ts +536 -0
- package/src/mcp/oauth-stores.ts +202 -0
- package/src/mcp/server.ts +55 -0
- package/src/mcp/tool-definitions.ts +562 -0
- package/src/metrics/collector.ts +357 -0
- package/src/metrics/index.ts +1 -0
- package/src/middleware/auth.ts +814 -0
- package/src/middleware/session.ts +85 -0
- package/src/middleware/validate.ts +130 -0
- package/src/policy/engine.ts +815 -0
- package/src/policy/index.ts +2 -0
- package/src/policy/opa-engine.ts +829 -0
- package/src/proxy/forward-proxy.ts +649 -0
- package/src/proxy/index.ts +1 -0
- package/src/ratelimit/limiter.ts +196 -0
- package/src/replay/engine.ts +142 -0
- package/src/replay/index.ts +1 -0
- package/src/saas/index.ts +1 -0
- package/src/saas/routes.ts +2178 -0
- package/src/server/app.ts +985 -0
- package/src/server/errors.ts +49 -0
- package/src/server/gateway.ts +1130 -0
- package/src/server/index.ts +307 -0
- package/src/server/logger.ts +255 -0
- package/src/server/stream-proxy.ts +202 -0
- package/src/storage/file-persistence.ts +315 -0
- package/src/storage/index.ts +4 -0
- package/src/storage/interfaces.ts +287 -0
- package/src/storage/memory.ts +686 -0
- package/src/storage/postgres.ts +1831 -0
- package/src/storage/redis.ts +835 -0
- package/src/tracing/index.ts +1 -0
- package/src/tracing/provider.ts +100 -0
- package/src/trust/calculator.ts +141 -0
- package/src/trust/index.ts +7 -0
- package/src/types/budget.ts +36 -0
- package/src/types/config.ts +278 -0
- package/src/types/events.ts +41 -0
- package/src/types/express.d.ts +14 -0
- package/src/types/index.ts +7 -0
- package/src/types/policy.ts +83 -0
- package/src/types/stripe-config.ts +11 -0
- package/src/types/subscription.ts +59 -0
- package/src/types/tool-call.ts +47 -0
- package/src/types/tool-result.ts +82 -0
- package/src/types/user.ts +125 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { ProviderInterceptConfig } from '../types/config';
|
|
2
|
+
|
|
3
|
+
export interface ProviderToolBlock {
|
|
4
|
+
provider: 'claude' | 'openai' | 'gemini' | 'unknown';
|
|
5
|
+
tool_name: string;
|
|
6
|
+
tool_type: string;
|
|
7
|
+
inputs: Record<string, unknown>;
|
|
8
|
+
outputs?: unknown;
|
|
9
|
+
block_index: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Provider = ProviderToolBlock['provider'];
|
|
13
|
+
|
|
14
|
+
const DEFAULT_URL_PATTERNS = [
|
|
15
|
+
'api\\.anthropic\\.com',
|
|
16
|
+
'api\\.openai\\.com',
|
|
17
|
+
'generativelanguage\\.googleapis\\.com',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const CLAUDE_TOOL_TYPES: Record<string, string> = {
|
|
21
|
+
computer_20241022: 'computer_use',
|
|
22
|
+
str_replace_editor: 'text_editor',
|
|
23
|
+
bash_20241022: 'bash',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const OPENAI_TOOL_TYPES = new Set([
|
|
27
|
+
'code_interpreter',
|
|
28
|
+
'file_search',
|
|
29
|
+
'web_search',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const GEMINI_TOOL_TYPES = new Set([
|
|
33
|
+
'code_execution',
|
|
34
|
+
'google_search',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
export class ProviderInterceptor {
|
|
38
|
+
private urlPatterns: RegExp[];
|
|
39
|
+
private scanInputs: boolean;
|
|
40
|
+
private scanOutputs: boolean;
|
|
41
|
+
|
|
42
|
+
constructor(config: ProviderInterceptConfig) {
|
|
43
|
+
const patterns = config.provider_url_patterns?.length
|
|
44
|
+
? config.provider_url_patterns
|
|
45
|
+
: DEFAULT_URL_PATTERNS;
|
|
46
|
+
this.urlPatterns = patterns.map((p) => new RegExp(p));
|
|
47
|
+
this.scanInputs = config.scan_inputs;
|
|
48
|
+
this.scanOutputs = config.scan_outputs;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Check if a URL matches any provider pattern */
|
|
52
|
+
matchesProvider(url: string): boolean {
|
|
53
|
+
return this.urlPatterns.some((re) => re.test(url));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Detect which provider a URL belongs to */
|
|
57
|
+
detectProvider(url: string): Provider {
|
|
58
|
+
if (/api\.anthropic\.com/.test(url)) return 'claude';
|
|
59
|
+
if (/api\.openai\.com/.test(url)) return 'openai';
|
|
60
|
+
if (/generativelanguage\.googleapis\.com/.test(url)) return 'gemini';
|
|
61
|
+
return 'unknown';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Extract tool blocks from a request body (pre-execution) */
|
|
65
|
+
extractFromRequest(body: unknown, provider: string): ProviderToolBlock[] {
|
|
66
|
+
if (!this.scanInputs || !body || typeof body !== 'object') return [];
|
|
67
|
+
try {
|
|
68
|
+
switch (provider) {
|
|
69
|
+
case 'claude': return this.extractClaudeRequest(body);
|
|
70
|
+
case 'openai': return this.extractOpenAIRequest(body);
|
|
71
|
+
case 'gemini': return this.extractGeminiRequest(body);
|
|
72
|
+
default: return [];
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Extract tool blocks from a response body (post-execution) */
|
|
80
|
+
extractFromResponse(body: unknown, provider: string): ProviderToolBlock[] {
|
|
81
|
+
if (!this.scanOutputs || !body || typeof body !== 'object') return [];
|
|
82
|
+
try {
|
|
83
|
+
switch (provider) {
|
|
84
|
+
case 'claude': return this.extractClaudeResponse(body);
|
|
85
|
+
case 'openai': return this.extractOpenAIResponse(body);
|
|
86
|
+
case 'gemini': return this.extractGeminiResponse(body);
|
|
87
|
+
default: return [];
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Get all inputs as a flat object for DLP scanning */
|
|
95
|
+
flattenInputsForDLP(blocks: ProviderToolBlock[]): Record<string, unknown> {
|
|
96
|
+
const result: Record<string, unknown> = {};
|
|
97
|
+
for (const block of blocks) {
|
|
98
|
+
for (const [key, value] of Object.entries(block.inputs)) {
|
|
99
|
+
result[`${block.tool_name}.${key}`] = value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Get all outputs as a flat object for DLP scanning */
|
|
106
|
+
flattenOutputsForDLP(blocks: ProviderToolBlock[]): Record<string, unknown> {
|
|
107
|
+
const result: Record<string, unknown> = {};
|
|
108
|
+
for (const block of blocks) {
|
|
109
|
+
if (block.outputs !== undefined) {
|
|
110
|
+
result[`${block.tool_name}.output`] = block.outputs;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Claude extraction ---
|
|
117
|
+
|
|
118
|
+
private extractClaudeRequest(body: unknown): ProviderToolBlock[] {
|
|
119
|
+
const blocks: ProviderToolBlock[] = [];
|
|
120
|
+
const messages = getArray(body, 'messages');
|
|
121
|
+
let index = 0;
|
|
122
|
+
for (const msg of messages) {
|
|
123
|
+
const content = getArray(msg, 'content');
|
|
124
|
+
for (const part of content) {
|
|
125
|
+
if (!isObject(part)) continue;
|
|
126
|
+
const type = getString(part, 'type');
|
|
127
|
+
if (type === 'tool_use') {
|
|
128
|
+
const name = getString(part, 'name') || 'unknown';
|
|
129
|
+
blocks.push({
|
|
130
|
+
provider: 'claude',
|
|
131
|
+
tool_name: name,
|
|
132
|
+
tool_type: resolveClaudeToolType(name),
|
|
133
|
+
inputs: isObject(part.input) ? part.input as Record<string, unknown> : {},
|
|
134
|
+
block_index: index++,
|
|
135
|
+
});
|
|
136
|
+
} else if (type === 'tool_result') {
|
|
137
|
+
const toolUseId = getString(part, 'tool_use_id') || 'unknown';
|
|
138
|
+
blocks.push({
|
|
139
|
+
provider: 'claude',
|
|
140
|
+
tool_name: `tool_result:${toolUseId}`,
|
|
141
|
+
tool_type: 'tool_result',
|
|
142
|
+
inputs: {},
|
|
143
|
+
outputs: part.content,
|
|
144
|
+
block_index: index++,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return blocks;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private extractClaudeResponse(body: unknown): ProviderToolBlock[] {
|
|
153
|
+
const blocks: ProviderToolBlock[] = [];
|
|
154
|
+
const content = getArray(body, 'content');
|
|
155
|
+
let index = 0;
|
|
156
|
+
for (const part of content) {
|
|
157
|
+
if (!isObject(part)) continue;
|
|
158
|
+
if (getString(part, 'type') === 'tool_use') {
|
|
159
|
+
const name = getString(part, 'name') || 'unknown';
|
|
160
|
+
blocks.push({
|
|
161
|
+
provider: 'claude',
|
|
162
|
+
tool_name: name,
|
|
163
|
+
tool_type: resolveClaudeToolType(name),
|
|
164
|
+
inputs: isObject(part.input) ? part.input as Record<string, unknown> : {},
|
|
165
|
+
block_index: index++,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return blocks;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- OpenAI extraction ---
|
|
173
|
+
|
|
174
|
+
private extractOpenAIRequest(body: unknown): ProviderToolBlock[] {
|
|
175
|
+
const blocks: ProviderToolBlock[] = [];
|
|
176
|
+
const messages = getArray(body, 'messages');
|
|
177
|
+
let index = 0;
|
|
178
|
+
for (const msg of messages) {
|
|
179
|
+
const toolCalls = getArray(msg, 'tool_calls');
|
|
180
|
+
for (const tc of toolCalls) {
|
|
181
|
+
if (!isObject(tc)) continue;
|
|
182
|
+
const fn = isObject(tc.function) ? tc.function as Record<string, unknown> : null;
|
|
183
|
+
if (!fn) continue;
|
|
184
|
+
const name = getString(fn, 'name') || 'unknown';
|
|
185
|
+
const args = parseJsonString(getString(fn, 'arguments'));
|
|
186
|
+
blocks.push({
|
|
187
|
+
provider: 'openai',
|
|
188
|
+
tool_name: name,
|
|
189
|
+
tool_type: resolveOpenAIToolType(name),
|
|
190
|
+
inputs: args,
|
|
191
|
+
block_index: index++,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return blocks;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private extractOpenAIResponse(body: unknown): ProviderToolBlock[] {
|
|
199
|
+
const blocks: ProviderToolBlock[] = [];
|
|
200
|
+
const choices = getArray(body, 'choices');
|
|
201
|
+
let index = 0;
|
|
202
|
+
for (const choice of choices) {
|
|
203
|
+
if (!isObject(choice)) continue;
|
|
204
|
+
const message = isObject(choice.message) ? choice.message as Record<string, unknown> : null;
|
|
205
|
+
if (!message) continue;
|
|
206
|
+
const toolCalls = getArray(message, 'tool_calls');
|
|
207
|
+
for (const tc of toolCalls) {
|
|
208
|
+
if (!isObject(tc)) continue;
|
|
209
|
+
const fn = isObject(tc.function) ? tc.function as Record<string, unknown> : null;
|
|
210
|
+
if (!fn) continue;
|
|
211
|
+
const name = getString(fn, 'name') || 'unknown';
|
|
212
|
+
const args = parseJsonString(getString(fn, 'arguments'));
|
|
213
|
+
blocks.push({
|
|
214
|
+
provider: 'openai',
|
|
215
|
+
tool_name: name,
|
|
216
|
+
tool_type: resolveOpenAIToolType(name),
|
|
217
|
+
inputs: args,
|
|
218
|
+
block_index: index++,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return blocks;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --- Gemini extraction ---
|
|
226
|
+
|
|
227
|
+
private extractGeminiRequest(body: unknown): ProviderToolBlock[] {
|
|
228
|
+
const blocks: ProviderToolBlock[] = [];
|
|
229
|
+
const contents = getArray(body, 'contents');
|
|
230
|
+
let index = 0;
|
|
231
|
+
for (const content of contents) {
|
|
232
|
+
if (!isObject(content)) continue;
|
|
233
|
+
const parts = getArray(content, 'parts');
|
|
234
|
+
for (const part of parts) {
|
|
235
|
+
if (!isObject(part)) continue;
|
|
236
|
+
const fc = isObject(part.functionCall) ? part.functionCall as Record<string, unknown> : null;
|
|
237
|
+
if (!fc) continue;
|
|
238
|
+
const name = getString(fc, 'name') || 'unknown';
|
|
239
|
+
blocks.push({
|
|
240
|
+
provider: 'gemini',
|
|
241
|
+
tool_name: name,
|
|
242
|
+
tool_type: resolveGeminiToolType(name),
|
|
243
|
+
inputs: isObject(fc.args) ? fc.args as Record<string, unknown> : {},
|
|
244
|
+
block_index: index++,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return blocks;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private extractGeminiResponse(body: unknown): ProviderToolBlock[] {
|
|
252
|
+
const blocks: ProviderToolBlock[] = [];
|
|
253
|
+
const candidates = getArray(body, 'candidates');
|
|
254
|
+
let index = 0;
|
|
255
|
+
for (const candidate of candidates) {
|
|
256
|
+
if (!isObject(candidate)) continue;
|
|
257
|
+
const content = isObject(candidate.content) ? candidate.content as Record<string, unknown> : null;
|
|
258
|
+
if (!content) continue;
|
|
259
|
+
const parts = getArray(content, 'parts');
|
|
260
|
+
for (const part of parts) {
|
|
261
|
+
if (!isObject(part)) continue;
|
|
262
|
+
const fc = isObject(part.functionCall) ? part.functionCall as Record<string, unknown> : null;
|
|
263
|
+
if (!fc) continue;
|
|
264
|
+
const name = getString(fc, 'name') || 'unknown';
|
|
265
|
+
blocks.push({
|
|
266
|
+
provider: 'gemini',
|
|
267
|
+
tool_name: name,
|
|
268
|
+
tool_type: resolveGeminiToolType(name),
|
|
269
|
+
inputs: isObject(fc.args) ? fc.args as Record<string, unknown> : {},
|
|
270
|
+
block_index: index++,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return blocks;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// --- Helpers ---
|
|
279
|
+
|
|
280
|
+
function isObject(val: unknown): val is Record<string, unknown> {
|
|
281
|
+
return val !== null && typeof val === 'object' && !Array.isArray(val);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getArray(obj: unknown, key: string): unknown[] {
|
|
285
|
+
if (!isObject(obj)) return [];
|
|
286
|
+
const val = obj[key];
|
|
287
|
+
return Array.isArray(val) ? val : [];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getString(obj: Record<string, unknown>, key: string): string {
|
|
291
|
+
const val = obj[key];
|
|
292
|
+
return typeof val === 'string' ? val : '';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function parseJsonString(str: string): Record<string, unknown> {
|
|
296
|
+
if (!str) return {};
|
|
297
|
+
try {
|
|
298
|
+
const parsed = JSON.parse(str);
|
|
299
|
+
return isObject(parsed) ? parsed : {};
|
|
300
|
+
} catch {
|
|
301
|
+
return {};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function resolveClaudeToolType(name: string): string {
|
|
306
|
+
return CLAUDE_TOOL_TYPES[name] || 'function';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function resolveOpenAIToolType(name: string): string {
|
|
310
|
+
return OPENAI_TOOL_TYPES.has(name) ? name : 'function';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function resolveGeminiToolType(name: string): string {
|
|
314
|
+
return GEMINI_TOOL_TYPES.has(name) ? name : 'function';
|
|
315
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid OAuth Token Verifier for MCP bearer auth.
|
|
3
|
+
*
|
|
4
|
+
* Implements OAuthTokenVerifier from the MCP SDK. Tries OAuth token
|
|
5
|
+
* verification first, then falls back to API key lookup (config keys +
|
|
6
|
+
* SaaS user keys). This preserves backward compatibility so that existing
|
|
7
|
+
* `--header "Authorization: Bearer <api-key>"` usage still works.
|
|
8
|
+
*/
|
|
9
|
+
import * as crypto from 'crypto';
|
|
10
|
+
import { OAuthTokenVerifier } from '@modelcontextprotocol/sdk/server/auth/provider.js';
|
|
11
|
+
import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
|
|
12
|
+
import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|
|
13
|
+
import { AuthConfig } from '../types/config';
|
|
14
|
+
import { UserApiKeyStore } from '../storage/interfaces';
|
|
15
|
+
import { resolvePermissions } from '../middleware/auth';
|
|
16
|
+
import { PalarynOAuthProvider } from './oauth-provider';
|
|
17
|
+
|
|
18
|
+
export interface HybridVerifierDeps {
|
|
19
|
+
oauthProvider?: PalarynOAuthProvider;
|
|
20
|
+
authConfig: AuthConfig;
|
|
21
|
+
userApiKeyStore?: UserApiKeyStore;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class HybridTokenVerifier implements OAuthTokenVerifier {
|
|
25
|
+
private oauthProvider?: PalarynOAuthProvider;
|
|
26
|
+
private authConfig: AuthConfig;
|
|
27
|
+
private userApiKeyStore?: UserApiKeyStore;
|
|
28
|
+
|
|
29
|
+
constructor(deps: HybridVerifierDeps) {
|
|
30
|
+
this.oauthProvider = deps.oauthProvider;
|
|
31
|
+
this.authConfig = deps.authConfig;
|
|
32
|
+
this.userApiKeyStore = deps.userApiKeyStore;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Constant-time API key lookup. Iterates all configured keys and uses
|
|
37
|
+
* crypto.timingSafeEqual to prevent timing-based side-channel attacks.
|
|
38
|
+
*/
|
|
39
|
+
private constantTimeLookup(token: string): string | undefined {
|
|
40
|
+
const tokenHash = crypto.createHash('sha256').update(token).digest();
|
|
41
|
+
let matched: string | undefined;
|
|
42
|
+
|
|
43
|
+
for (const key of Object.keys(this.authConfig.api_keys)) {
|
|
44
|
+
const keyHash = crypto.createHash('sha256').update(key).digest();
|
|
45
|
+
if (crypto.timingSafeEqual(tokenHash, keyHash)) {
|
|
46
|
+
matched = key;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return matched;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async verifyAccessToken(token: string): Promise<AuthInfo> {
|
|
54
|
+
// 1. Try OAuth token first (if provider is configured)
|
|
55
|
+
if (this.oauthProvider) {
|
|
56
|
+
try {
|
|
57
|
+
return await this.oauthProvider.verifyAccessToken(token);
|
|
58
|
+
} catch {
|
|
59
|
+
// Not an OAuth token — fall through to API key
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. Try config API keys (constant-time comparison to prevent timing attacks)
|
|
64
|
+
if (this.authConfig.enabled) {
|
|
65
|
+
const matchedKey = this.constantTimeLookup(token);
|
|
66
|
+
const keyConfig = matchedKey ? this.authConfig.api_keys[matchedKey] : undefined;
|
|
67
|
+
if (keyConfig && !keyConfig.revoked) {
|
|
68
|
+
if (keyConfig.expires_at) {
|
|
69
|
+
const expiresAt = new Date(keyConfig.expires_at);
|
|
70
|
+
if (expiresAt.getTime() <= Date.now()) {
|
|
71
|
+
throw new InvalidTokenError('API key has expired');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fire-and-forget: update last_used_at
|
|
76
|
+
keyConfig.last_used_at = new Date().toISOString();
|
|
77
|
+
|
|
78
|
+
const roles = keyConfig.roles || [];
|
|
79
|
+
const permissions = resolvePermissions(roles, this.authConfig.rbac);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
token,
|
|
83
|
+
clientId: 'api_key',
|
|
84
|
+
scopes: ['mcp:tools'],
|
|
85
|
+
expiresAt: keyConfig.expires_at
|
|
86
|
+
? Math.floor(new Date(keyConfig.expires_at).getTime() / 1000)
|
|
87
|
+
: Math.floor(Date.now() / 1000) + 86400, // default: 24h from now
|
|
88
|
+
extra: {
|
|
89
|
+
workspace_id: keyConfig.workspace_id,
|
|
90
|
+
actor_id: `apikey:${crypto.createHash('sha256').update(token).digest('hex').slice(0, 12)}`,
|
|
91
|
+
roles,
|
|
92
|
+
permissions,
|
|
93
|
+
auth_method: 'api_key',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 3. Try SaaS-generated API keys (UserApiKeyStore, supports salted + unsalted)
|
|
100
|
+
if (this.userApiKeyStore) {
|
|
101
|
+
let saasKey: ReturnType<typeof this.userApiKeyStore.getByKeyHash>;
|
|
102
|
+
if (this.userApiKeyStore.verifyToken) {
|
|
103
|
+
saasKey = this.userApiKeyStore.verifyToken(token);
|
|
104
|
+
} else {
|
|
105
|
+
const keyHash = crypto.createHash('sha256').update(token).digest('hex');
|
|
106
|
+
saasKey = this.userApiKeyStore.getByKeyHash(keyHash);
|
|
107
|
+
}
|
|
108
|
+
if (saasKey && !saasKey.revoked) {
|
|
109
|
+
// Fire-and-forget: update last_used_at
|
|
110
|
+
this.userApiKeyStore.update(saasKey.id, { last_used_at: new Date().toISOString() });
|
|
111
|
+
|
|
112
|
+
const roles = saasKey.roles || [];
|
|
113
|
+
const permissions = resolvePermissions(roles, this.authConfig.rbac);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
token,
|
|
117
|
+
clientId: 'api_key',
|
|
118
|
+
scopes: ['mcp:tools'],
|
|
119
|
+
expiresAt: Math.floor(Date.now() / 1000) + 86400, // 24h
|
|
120
|
+
extra: {
|
|
121
|
+
workspace_id: saasKey.workspace_id,
|
|
122
|
+
actor_id: `apikey:${crypto.createHash('sha256').update(token).digest('hex').slice(0, 12)}`,
|
|
123
|
+
user_id: saasKey.user_id,
|
|
124
|
+
roles,
|
|
125
|
+
permissions,
|
|
126
|
+
auth_method: 'api_key',
|
|
127
|
+
api_key_tags: saasKey.tags && saasKey.tags.length > 0 ? saasKey.tags : undefined,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 4. Auth disabled — anonymous access
|
|
134
|
+
if (!this.authConfig.enabled) {
|
|
135
|
+
return {
|
|
136
|
+
token,
|
|
137
|
+
clientId: 'anonymous',
|
|
138
|
+
scopes: ['mcp:tools'],
|
|
139
|
+
expiresAt: Math.floor(Date.now() / 1000) + 86400,
|
|
140
|
+
extra: {
|
|
141
|
+
workspace_id: 'ws_default',
|
|
142
|
+
actor_id: 'anonymous',
|
|
143
|
+
roles: [],
|
|
144
|
+
permissions: [],
|
|
145
|
+
auth_method: 'none',
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new InvalidTokenError('Invalid access token');
|
|
151
|
+
}
|
|
152
|
+
}
|