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,196 @@
|
|
|
1
|
+
import { RateLimitConfig } from '../types/config';
|
|
2
|
+
import { RateLimitStore } from '../storage/interfaces';
|
|
3
|
+
import { ToolCall } from '../types/tool-call';
|
|
4
|
+
|
|
5
|
+
interface WindowEntry {
|
|
6
|
+
timestamps: number[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RateLimitResult {
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
current: number;
|
|
12
|
+
limit: number;
|
|
13
|
+
remaining: number;
|
|
14
|
+
reset_at: string;
|
|
15
|
+
blocked_by?: 'actor' | 'workspace';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sliding-window rate limiter.
|
|
20
|
+
* Tracks request counts per actor and per workspace within a configurable window.
|
|
21
|
+
* Optionally delegates to an external RateLimitStore (e.g. Redis) for cross-process state.
|
|
22
|
+
*/
|
|
23
|
+
export class RateLimiter {
|
|
24
|
+
private config: RateLimitConfig;
|
|
25
|
+
private windows: Map<string, WindowEntry> = new Map();
|
|
26
|
+
private store?: RateLimitStore;
|
|
27
|
+
private cleanupTimer?: ReturnType<typeof setInterval>;
|
|
28
|
+
|
|
29
|
+
constructor(config: RateLimitConfig, store?: RateLimitStore) {
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.store = store;
|
|
32
|
+
|
|
33
|
+
// Periodic cleanup of stale entries (every 60 seconds)
|
|
34
|
+
if (config.enabled) {
|
|
35
|
+
this.cleanupTimer = setInterval(() => {
|
|
36
|
+
this.cleanupStaleEntries();
|
|
37
|
+
}, 60_000);
|
|
38
|
+
if (this.cleanupTimer && typeof this.cleanupTimer === 'object' && 'unref' in this.cleanupTimer) {
|
|
39
|
+
this.cleanupTimer.unref();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Remove entries with no timestamps in the current window */
|
|
45
|
+
private cleanupStaleEntries(): void {
|
|
46
|
+
const cutoff = Date.now() - this.config.window_ms;
|
|
47
|
+
for (const [key, entry] of this.windows) {
|
|
48
|
+
const valid = entry.timestamps.filter(t => t > cutoff);
|
|
49
|
+
if (valid.length === 0) {
|
|
50
|
+
this.windows.delete(key);
|
|
51
|
+
} else {
|
|
52
|
+
entry.timestamps = valid;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Stop the periodic cleanup timer (for tests and shutdown) */
|
|
58
|
+
destroy(): void {
|
|
59
|
+
if (this.cleanupTimer) {
|
|
60
|
+
clearInterval(this.cleanupTimer);
|
|
61
|
+
this.cleanupTimer = undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if a tool call is within rate limits.
|
|
67
|
+
* Records the hit and returns the result.
|
|
68
|
+
* Optional overrides allow per-workspace rate limit configuration.
|
|
69
|
+
*/
|
|
70
|
+
check(toolCall: ToolCall, overrides?: { actor_max_per_window?: number; workspace_max_per_window?: number; window_ms?: number }): RateLimitResult {
|
|
71
|
+
if (!this.config.enabled) {
|
|
72
|
+
return { allowed: true, current: 0, limit: 0, remaining: 0, reset_at: '' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const windowMs = overrides?.window_ms ?? this.config.window_ms;
|
|
77
|
+
const actorMax = overrides?.actor_max_per_window ?? this.config.actor_max_per_window;
|
|
78
|
+
const wsMax = overrides?.workspace_max_per_window ?? this.config.workspace_max_per_window;
|
|
79
|
+
|
|
80
|
+
// Check actor rate limit
|
|
81
|
+
const actorKey = `actor:${toolCall.actor.id}`;
|
|
82
|
+
const actorResult = this.hit(actorKey, now, windowMs, actorMax);
|
|
83
|
+
if (!actorResult.allowed) {
|
|
84
|
+
return {
|
|
85
|
+
allowed: false,
|
|
86
|
+
current: actorResult.current,
|
|
87
|
+
limit: actorResult.limit,
|
|
88
|
+
remaining: 0,
|
|
89
|
+
reset_at: new Date(actorResult.resetAt).toISOString(),
|
|
90
|
+
blocked_by: 'actor',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check workspace rate limit
|
|
95
|
+
const wsKey = `workspace:${toolCall.workspace_id}`;
|
|
96
|
+
const wsResult = this.hit(wsKey, now, windowMs, wsMax);
|
|
97
|
+
if (!wsResult.allowed) {
|
|
98
|
+
// Roll back the actor hit since we're rejecting
|
|
99
|
+
this.rollback(actorKey, now);
|
|
100
|
+
return {
|
|
101
|
+
allowed: false,
|
|
102
|
+
current: wsResult.current,
|
|
103
|
+
limit: wsResult.limit,
|
|
104
|
+
remaining: 0,
|
|
105
|
+
reset_at: new Date(wsResult.resetAt).toISOString(),
|
|
106
|
+
blocked_by: 'workspace',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const remaining = Math.min(
|
|
111
|
+
actorMax - actorResult.current,
|
|
112
|
+
wsMax - wsResult.current
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
allowed: true,
|
|
117
|
+
current: actorResult.current,
|
|
118
|
+
limit: actorMax,
|
|
119
|
+
remaining,
|
|
120
|
+
reset_at: new Date(actorResult.resetAt).toISOString(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private hit(key: string, now: number, windowMs: number, maxRequests: number) {
|
|
125
|
+
// Delegate to external store if available
|
|
126
|
+
if (this.store) {
|
|
127
|
+
return this.store.hit(key, windowMs, maxRequests);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const cutoff = now - windowMs;
|
|
131
|
+
let entry = this.windows.get(key);
|
|
132
|
+
if (!entry) {
|
|
133
|
+
entry = { timestamps: [] };
|
|
134
|
+
this.windows.set(key, entry);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Prune expired timestamps
|
|
138
|
+
entry.timestamps = entry.timestamps.filter(t => t > cutoff);
|
|
139
|
+
|
|
140
|
+
// If all timestamps expired, remove the entry entirely to free memory
|
|
141
|
+
if (entry.timestamps.length === 0) {
|
|
142
|
+
// But we still need to record this hit, so keep the entry
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Record this hit
|
|
146
|
+
entry.timestamps.push(now);
|
|
147
|
+
|
|
148
|
+
const current = entry.timestamps.length;
|
|
149
|
+
const allowed = current <= maxRequests;
|
|
150
|
+
const resetAt = entry.timestamps.length > 0 ? entry.timestamps[0] + windowMs : now + windowMs;
|
|
151
|
+
|
|
152
|
+
// Periodic cleanup of stale windows (every 1000 entries)
|
|
153
|
+
if (this.windows.size > 1000) {
|
|
154
|
+
for (const [k, w] of this.windows) {
|
|
155
|
+
const validTimestamps = w.timestamps.filter(t => t > cutoff);
|
|
156
|
+
if (validTimestamps.length === 0) {
|
|
157
|
+
this.windows.delete(k);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { allowed, current, limit: maxRequests, resetAt };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private rollback(key: string, timestamp: number) {
|
|
166
|
+
if (this.store) {
|
|
167
|
+
// Delegate rollback to the store if available
|
|
168
|
+
if ('rollback' in this.store && typeof (this.store as any).rollback === 'function') {
|
|
169
|
+
(this.store as any).rollback(key, this.config.window_ms);
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const entry = this.windows.get(key);
|
|
174
|
+
if (entry) {
|
|
175
|
+
const idx = entry.timestamps.lastIndexOf(timestamp);
|
|
176
|
+
if (idx !== -1) entry.timestamps.splice(idx, 1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Wait for all pending store writes to complete. */
|
|
181
|
+
async flush(): Promise<void> {
|
|
182
|
+
if (this.store && 'flush' in this.store && typeof (this.store as any).flush === 'function') {
|
|
183
|
+
await (this.store as any).flush();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
reset(): void {
|
|
188
|
+
this.windows.clear();
|
|
189
|
+
this.store?.reset();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Get the number of tracked window entries (for diagnostics) */
|
|
193
|
+
getWindowCount(): number {
|
|
194
|
+
return this.windows.size;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { AuditLogger } from '../audit/logger';
|
|
2
|
+
import { PolicyEngine } from '../policy/engine';
|
|
3
|
+
import { ToolCall } from '../types/tool-call';
|
|
4
|
+
import { AuditEvent } from '../types/events';
|
|
5
|
+
|
|
6
|
+
export interface ReplayComparison {
|
|
7
|
+
tool_call_id: string;
|
|
8
|
+
tool_name: string;
|
|
9
|
+
original_decision: string;
|
|
10
|
+
original_rule_id: string;
|
|
11
|
+
replay_decision: string;
|
|
12
|
+
replay_rule_id: string;
|
|
13
|
+
changed: boolean;
|
|
14
|
+
replay_reasons: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ReplayResult {
|
|
18
|
+
task_id: string;
|
|
19
|
+
policy_pack_path: string;
|
|
20
|
+
total_calls: number;
|
|
21
|
+
changed_count: number;
|
|
22
|
+
comparisons: ReplayComparison[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Maximum number of tool calls to replay per session */
|
|
26
|
+
const MAX_REPLAY_CALLS = 100;
|
|
27
|
+
|
|
28
|
+
export class SessionReplayEngine {
|
|
29
|
+
private auditLogger: AuditLogger;
|
|
30
|
+
|
|
31
|
+
constructor(auditLogger: AuditLogger) {
|
|
32
|
+
this.auditLogger = auditLogger;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Replay a task's tool calls against an alternative policy pack.
|
|
37
|
+
*
|
|
38
|
+
* 1. Gets all events for the task_id from the audit logger.
|
|
39
|
+
* 2. Finds TOOL_CALL_RECEIVED events (which contain args_snapshot in metadata).
|
|
40
|
+
* 3. Reconstructs ToolCall objects from event data + args_snapshot.
|
|
41
|
+
* 4. Finds the original POLICY_DECIDED events for each tool_call_id.
|
|
42
|
+
* 5. Creates a new PolicyEngine with the alternative policy pack path.
|
|
43
|
+
* 6. Re-evaluates each ToolCall against the new policy.
|
|
44
|
+
* 7. Returns a comparison report.
|
|
45
|
+
*/
|
|
46
|
+
replay(taskId: string, altPolicyPackPath: string): ReplayResult {
|
|
47
|
+
const events = this.auditLogger.getTaskTrace(taskId);
|
|
48
|
+
|
|
49
|
+
if (events.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
task_id: taskId,
|
|
52
|
+
policy_pack_path: altPolicyPackPath,
|
|
53
|
+
total_calls: 0,
|
|
54
|
+
changed_count: 0,
|
|
55
|
+
comparisons: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Collect TOOL_CALL_RECEIVED and POLICY_DECIDED events
|
|
60
|
+
const receivedEvents = events.filter(e => e.event_type === 'TOOL_CALL_RECEIVED');
|
|
61
|
+
const policyEvents = events.filter(e => e.event_type === 'POLICY_DECIDED');
|
|
62
|
+
|
|
63
|
+
// Build a map of tool_call_id -> POLICY_DECIDED event for quick lookup
|
|
64
|
+
const policyMap = new Map<string, AuditEvent>();
|
|
65
|
+
for (const pe of policyEvents) {
|
|
66
|
+
policyMap.set(pe.tool_call_id, pe);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Cap at MAX_REPLAY_CALLS
|
|
70
|
+
const capped = receivedEvents.slice(0, MAX_REPLAY_CALLS);
|
|
71
|
+
|
|
72
|
+
// Create the alternative policy engine
|
|
73
|
+
const altEngine = new PolicyEngine(altPolicyPackPath);
|
|
74
|
+
|
|
75
|
+
const comparisons: ReplayComparison[] = [];
|
|
76
|
+
|
|
77
|
+
for (const event of capped) {
|
|
78
|
+
// Reconstruct ToolCall from event metadata + args_snapshot
|
|
79
|
+
const toolCall = this.reconstructToolCall(event);
|
|
80
|
+
|
|
81
|
+
// Get original policy decision
|
|
82
|
+
const originalPolicy = policyMap.get(event.tool_call_id);
|
|
83
|
+
const originalDecision = originalPolicy?.metadata?.decision as string || 'unknown';
|
|
84
|
+
const originalRuleId = originalPolicy?.metadata?.rule_id as string || 'unknown';
|
|
85
|
+
|
|
86
|
+
// Re-evaluate against alternative policy
|
|
87
|
+
const replayResult = altEngine.evaluate(toolCall);
|
|
88
|
+
|
|
89
|
+
const changed = originalDecision !== replayResult.decision ||
|
|
90
|
+
originalRuleId !== replayResult.rule_id;
|
|
91
|
+
|
|
92
|
+
comparisons.push({
|
|
93
|
+
tool_call_id: event.tool_call_id,
|
|
94
|
+
tool_name: event.tool_name,
|
|
95
|
+
original_decision: originalDecision,
|
|
96
|
+
original_rule_id: originalRuleId,
|
|
97
|
+
replay_decision: replayResult.decision,
|
|
98
|
+
replay_rule_id: replayResult.rule_id,
|
|
99
|
+
changed,
|
|
100
|
+
replay_reasons: replayResult.reasons,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
task_id: taskId,
|
|
106
|
+
policy_pack_path: altPolicyPackPath,
|
|
107
|
+
total_calls: comparisons.length,
|
|
108
|
+
changed_count: comparisons.filter(c => c.changed).length,
|
|
109
|
+
comparisons,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reconstruct a ToolCall from an audit event's stored data.
|
|
115
|
+
* Uses the args_snapshot from metadata for the args field.
|
|
116
|
+
*/
|
|
117
|
+
private reconstructToolCall(event: AuditEvent): ToolCall {
|
|
118
|
+
const metadata = event.metadata;
|
|
119
|
+
const argsSnapshot = (metadata.args_snapshot as Record<string, unknown>) || {};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
tool_call_id: event.tool_call_id,
|
|
123
|
+
task_id: event.task_id,
|
|
124
|
+
workspace_id: event.workspace_id,
|
|
125
|
+
actor: {
|
|
126
|
+
type: 'agent',
|
|
127
|
+
id: event.actor_id,
|
|
128
|
+
},
|
|
129
|
+
source: {
|
|
130
|
+
platform: (metadata.platform as string) || 'unknown',
|
|
131
|
+
},
|
|
132
|
+
tool: {
|
|
133
|
+
name: event.tool_name,
|
|
134
|
+
capability: (metadata.capability as 'read' | 'write' | 'delete' | 'admin') || 'read',
|
|
135
|
+
},
|
|
136
|
+
args: argsSnapshot,
|
|
137
|
+
context: {
|
|
138
|
+
labels: (metadata.labels as string[]) || [],
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SessionReplayEngine, ReplayComparison, ReplayResult } from './engine';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './routes';
|