palaryn 0.1.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/LICENSE +21 -0
- package/README.md +716 -0
- package/dist/sdk/typescript/src/client.d.ts +71 -0
- package/dist/sdk/typescript/src/client.d.ts.map +1 -0
- package/dist/sdk/typescript/src/client.js +176 -0
- package/dist/sdk/typescript/src/client.js.map +1 -0
- package/dist/sdk/typescript/src/errors.d.ts +50 -0
- package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
- package/dist/sdk/typescript/src/errors.js +103 -0
- package/dist/sdk/typescript/src/errors.js.map +1 -0
- package/dist/sdk/typescript/src/index.d.ts +4 -0
- package/dist/sdk/typescript/src/index.d.ts.map +1 -0
- package/dist/sdk/typescript/src/index.js +15 -0
- package/dist/sdk/typescript/src/index.js.map +1 -0
- package/dist/sdk/typescript/src/types.d.ts +101 -0
- package/dist/sdk/typescript/src/types.d.ts.map +1 -0
- package/dist/sdk/typescript/src/types.js +6 -0
- package/dist/sdk/typescript/src/types.js.map +1 -0
- package/dist/src/admin/index.d.ts +2 -0
- package/dist/src/admin/index.d.ts.map +1 -0
- package/dist/src/admin/index.js +6 -0
- package/dist/src/admin/index.js.map +1 -0
- package/dist/src/admin/routes.d.ts +5 -0
- package/dist/src/admin/routes.d.ts.map +1 -0
- package/dist/src/admin/routes.js +471 -0
- package/dist/src/admin/routes.js.map +1 -0
- package/dist/src/admin/templates.d.ts +51 -0
- package/dist/src/admin/templates.d.ts.map +1 -0
- package/dist/src/admin/templates.js +500 -0
- package/dist/src/admin/templates.js.map +1 -0
- package/dist/src/anomaly/detector.d.ts +141 -0
- package/dist/src/anomaly/detector.d.ts.map +1 -0
- package/dist/src/anomaly/detector.js +554 -0
- package/dist/src/anomaly/detector.js.map +1 -0
- package/dist/src/anomaly/index.d.ts +2 -0
- package/dist/src/anomaly/index.d.ts.map +1 -0
- package/dist/src/anomaly/index.js +7 -0
- package/dist/src/anomaly/index.js.map +1 -0
- package/dist/src/approval/manager.d.ts +147 -0
- package/dist/src/approval/manager.d.ts.map +1 -0
- package/dist/src/approval/manager.js +511 -0
- package/dist/src/approval/manager.js.map +1 -0
- package/dist/src/approval/webhook.d.ts +36 -0
- package/dist/src/approval/webhook.d.ts.map +1 -0
- package/dist/src/approval/webhook.js +135 -0
- package/dist/src/approval/webhook.js.map +1 -0
- package/dist/src/audit/logger.d.ts +70 -0
- package/dist/src/audit/logger.d.ts.map +1 -0
- package/dist/src/audit/logger.js +440 -0
- package/dist/src/audit/logger.js.map +1 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +22 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/password.d.ts +3 -0
- package/dist/src/auth/password.d.ts.map +1 -0
- package/dist/src/auth/password.js +25 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/auth/pkce.d.ts +13 -0
- package/dist/src/auth/pkce.d.ts.map +1 -0
- package/dist/src/auth/pkce.js +58 -0
- package/dist/src/auth/pkce.js.map +1 -0
- package/dist/src/auth/providers.d.ts +28 -0
- package/dist/src/auth/providers.d.ts.map +1 -0
- package/dist/src/auth/providers.js +198 -0
- package/dist/src/auth/providers.js.map +1 -0
- package/dist/src/auth/routes.d.ts +14 -0
- package/dist/src/auth/routes.d.ts.map +1 -0
- package/dist/src/auth/routes.js +431 -0
- package/dist/src/auth/routes.js.map +1 -0
- package/dist/src/auth/session.d.ts +24 -0
- package/dist/src/auth/session.d.ts.map +1 -0
- package/dist/src/auth/session.js +105 -0
- package/dist/src/auth/session.js.map +1 -0
- package/dist/src/billing/index.d.ts +7 -0
- package/dist/src/billing/index.d.ts.map +1 -0
- package/dist/src/billing/index.js +14 -0
- package/dist/src/billing/index.js.map +1 -0
- package/dist/src/billing/plan-enforcer.d.ts +44 -0
- package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
- package/dist/src/billing/plan-enforcer.js +110 -0
- package/dist/src/billing/plan-enforcer.js.map +1 -0
- package/dist/src/billing/routes.d.ts +15 -0
- package/dist/src/billing/routes.d.ts.map +1 -0
- package/dist/src/billing/routes.js +193 -0
- package/dist/src/billing/routes.js.map +1 -0
- package/dist/src/billing/stripe-client.d.ts +14 -0
- package/dist/src/billing/stripe-client.d.ts.map +1 -0
- package/dist/src/billing/stripe-client.js +51 -0
- package/dist/src/billing/stripe-client.js.map +1 -0
- package/dist/src/billing/webhook-handler.d.ts +19 -0
- package/dist/src/billing/webhook-handler.d.ts.map +1 -0
- package/dist/src/billing/webhook-handler.js +169 -0
- package/dist/src/billing/webhook-handler.js.map +1 -0
- package/dist/src/billing/webhook-routes.d.ts +5 -0
- package/dist/src/billing/webhook-routes.d.ts.map +1 -0
- package/dist/src/billing/webhook-routes.js +30 -0
- package/dist/src/billing/webhook-routes.js.map +1 -0
- package/dist/src/budget/manager.d.ts +95 -0
- package/dist/src/budget/manager.d.ts.map +1 -0
- package/dist/src/budget/manager.js +547 -0
- package/dist/src/budget/manager.js.map +1 -0
- package/dist/src/budget/usage-extractor.d.ts +38 -0
- package/dist/src/budget/usage-extractor.d.ts.map +1 -0
- package/dist/src/budget/usage-extractor.js +165 -0
- package/dist/src/budget/usage-extractor.js.map +1 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +115 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config/defaults.d.ts +3 -0
- package/dist/src/config/defaults.d.ts.map +1 -0
- package/dist/src/config/defaults.js +243 -0
- package/dist/src/config/defaults.js.map +1 -0
- package/dist/src/config/validate.d.ts +15 -0
- package/dist/src/config/validate.d.ts.map +1 -0
- package/dist/src/config/validate.js +105 -0
- package/dist/src/config/validate.js.map +1 -0
- package/dist/src/dlp/composite-scanner.d.ts +47 -0
- package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
- package/dist/src/dlp/composite-scanner.js +186 -0
- package/dist/src/dlp/composite-scanner.js.map +1 -0
- package/dist/src/dlp/index.d.ts +10 -0
- package/dist/src/dlp/index.d.ts.map +1 -0
- package/dist/src/dlp/index.js +26 -0
- package/dist/src/dlp/index.js.map +1 -0
- package/dist/src/dlp/interfaces.d.ts +33 -0
- package/dist/src/dlp/interfaces.d.ts.map +1 -0
- package/dist/src/dlp/interfaces.js +3 -0
- package/dist/src/dlp/interfaces.js.map +1 -0
- package/dist/src/dlp/patterns.d.ts +9 -0
- package/dist/src/dlp/patterns.d.ts.map +1 -0
- package/dist/src/dlp/patterns.js +25 -0
- package/dist/src/dlp/patterns.js.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.js +148 -0
- package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.js +290 -0
- package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
- package/dist/src/dlp/regex-backend.d.ts +32 -0
- package/dist/src/dlp/regex-backend.d.ts.map +1 -0
- package/dist/src/dlp/regex-backend.js +153 -0
- package/dist/src/dlp/regex-backend.js.map +1 -0
- package/dist/src/dlp/scanner.d.ts +122 -0
- package/dist/src/dlp/scanner.d.ts.map +1 -0
- package/dist/src/dlp/scanner.js +444 -0
- package/dist/src/dlp/scanner.js.map +1 -0
- package/dist/src/dlp/text-normalizer.d.ts +41 -0
- package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
- package/dist/src/dlp/text-normalizer.js +203 -0
- package/dist/src/dlp/text-normalizer.js.map +1 -0
- package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
- package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
- package/dist/src/dlp/trufflehog-backend.js +151 -0
- package/dist/src/dlp/trufflehog-backend.js.map +1 -0
- package/dist/src/executor/http-executor.d.ts +25 -0
- package/dist/src/executor/http-executor.d.ts.map +1 -0
- package/dist/src/executor/http-executor.js +333 -0
- package/dist/src/executor/http-executor.js.map +1 -0
- package/dist/src/executor/index.d.ts +6 -0
- package/dist/src/executor/index.d.ts.map +1 -0
- package/dist/src/executor/index.js +12 -0
- package/dist/src/executor/index.js.map +1 -0
- package/dist/src/executor/interfaces.d.ts +11 -0
- package/dist/src/executor/interfaces.d.ts.map +1 -0
- package/dist/src/executor/interfaces.js +3 -0
- package/dist/src/executor/interfaces.js.map +1 -0
- package/dist/src/executor/noop-executor.d.ts +13 -0
- package/dist/src/executor/noop-executor.d.ts.map +1 -0
- package/dist/src/executor/noop-executor.js +21 -0
- package/dist/src/executor/noop-executor.js.map +1 -0
- package/dist/src/executor/registry.d.ts +30 -0
- package/dist/src/executor/registry.d.ts.map +1 -0
- package/dist/src/executor/registry.js +62 -0
- package/dist/src/executor/registry.js.map +1 -0
- package/dist/src/executor/slack-executor.d.ts +24 -0
- package/dist/src/executor/slack-executor.d.ts.map +1 -0
- package/dist/src/executor/slack-executor.js +147 -0
- package/dist/src/executor/slack-executor.js.map +1 -0
- package/dist/src/index.d.ts +25 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +74 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/auth-verifier.d.ts +23 -0
- package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
- package/dist/src/mcp/auth-verifier.js +162 -0
- package/dist/src/mcp/auth-verifier.js.map +1 -0
- package/dist/src/mcp/bridge.d.ts +132 -0
- package/dist/src/mcp/bridge.d.ts.map +1 -0
- package/dist/src/mcp/bridge.js +734 -0
- package/dist/src/mcp/bridge.js.map +1 -0
- package/dist/src/mcp/http-transport.d.ts +32 -0
- package/dist/src/mcp/http-transport.d.ts.map +1 -0
- package/dist/src/mcp/http-transport.js +538 -0
- package/dist/src/mcp/http-transport.js.map +1 -0
- package/dist/src/mcp/index.d.ts +10 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +17 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/oauth-pages.d.ts +23 -0
- package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
- package/dist/src/mcp/oauth-pages.js +121 -0
- package/dist/src/mcp/oauth-pages.js.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.js +226 -0
- package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
- package/dist/src/mcp/oauth-provider.d.ts +95 -0
- package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
- package/dist/src/mcp/oauth-provider.js +360 -0
- package/dist/src/mcp/oauth-provider.js.map +1 -0
- package/dist/src/mcp/oauth-stores.d.ts +62 -0
- package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-stores.js +154 -0
- package/dist/src/mcp/oauth-stores.js.map +1 -0
- package/dist/src/mcp/server.d.ts +18 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +51 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/metrics/collector.d.ts +106 -0
- package/dist/src/metrics/collector.d.ts.map +1 -0
- package/dist/src/metrics/collector.js +311 -0
- package/dist/src/metrics/collector.js.map +1 -0
- package/dist/src/metrics/index.d.ts +2 -0
- package/dist/src/metrics/index.d.ts.map +1 -0
- package/dist/src/metrics/index.js +6 -0
- package/dist/src/metrics/index.js.map +1 -0
- package/dist/src/middleware/auth.d.ts +77 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +720 -0
- package/dist/src/middleware/auth.js.map +1 -0
- package/dist/src/middleware/session.d.ts +18 -0
- package/dist/src/middleware/session.d.ts.map +1 -0
- package/dist/src/middleware/session.js +67 -0
- package/dist/src/middleware/session.js.map +1 -0
- package/dist/src/middleware/validate.d.ts +3 -0
- package/dist/src/middleware/validate.d.ts.map +1 -0
- package/dist/src/middleware/validate.js +85 -0
- package/dist/src/middleware/validate.js.map +1 -0
- package/dist/src/policy/engine.d.ts +107 -0
- package/dist/src/policy/engine.d.ts.map +1 -0
- package/dist/src/policy/engine.js +646 -0
- package/dist/src/policy/engine.js.map +1 -0
- package/dist/src/policy/index.d.ts +3 -0
- package/dist/src/policy/index.d.ts.map +1 -0
- package/dist/src/policy/index.js +8 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/opa-engine.d.ts +176 -0
- package/dist/src/policy/opa-engine.d.ts.map +1 -0
- package/dist/src/policy/opa-engine.js +790 -0
- package/dist/src/policy/opa-engine.js.map +1 -0
- package/dist/src/proxy/forward-proxy.d.ts +30 -0
- package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
- package/dist/src/proxy/forward-proxy.js +580 -0
- package/dist/src/proxy/forward-proxy.js.map +1 -0
- package/dist/src/proxy/index.d.ts +2 -0
- package/dist/src/proxy/index.d.ts.map +1 -0
- package/dist/src/proxy/index.js +8 -0
- package/dist/src/proxy/index.js.map +1 -0
- package/dist/src/ratelimit/limiter.d.ts +45 -0
- package/dist/src/ratelimit/limiter.d.ts.map +1 -0
- package/dist/src/ratelimit/limiter.js +158 -0
- package/dist/src/ratelimit/limiter.js.map +1 -0
- package/dist/src/replay/engine.d.ts +40 -0
- package/dist/src/replay/engine.d.ts.map +1 -0
- package/dist/src/replay/engine.js +106 -0
- package/dist/src/replay/engine.js.map +1 -0
- package/dist/src/replay/index.d.ts +2 -0
- package/dist/src/replay/index.d.ts.map +1 -0
- package/dist/src/replay/index.js +6 -0
- package/dist/src/replay/index.js.map +1 -0
- package/dist/src/saas/index.d.ts +2 -0
- package/dist/src/saas/index.d.ts.map +1 -0
- package/dist/src/saas/index.js +18 -0
- package/dist/src/saas/index.js.map +1 -0
- package/dist/src/saas/routes.d.ts +18 -0
- package/dist/src/saas/routes.d.ts.map +1 -0
- package/dist/src/saas/routes.js +1566 -0
- package/dist/src/saas/routes.js.map +1 -0
- package/dist/src/server/app.d.ts +44 -0
- package/dist/src/server/app.d.ts.map +1 -0
- package/dist/src/server/app.js +854 -0
- package/dist/src/server/app.js.map +1 -0
- package/dist/src/server/errors.d.ts +32 -0
- package/dist/src/server/errors.d.ts.map +1 -0
- package/dist/src/server/errors.js +39 -0
- package/dist/src/server/errors.js.map +1 -0
- package/dist/src/server/gateway.d.ts +165 -0
- package/dist/src/server/gateway.d.ts.map +1 -0
- package/dist/src/server/gateway.js +964 -0
- package/dist/src/server/gateway.js.map +1 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +295 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/logger.d.ts +33 -0
- package/dist/src/server/logger.d.ts.map +1 -0
- package/dist/src/server/logger.js +230 -0
- package/dist/src/server/logger.js.map +1 -0
- package/dist/src/server/stream-proxy.d.ts +32 -0
- package/dist/src/server/stream-proxy.d.ts.map +1 -0
- package/dist/src/server/stream-proxy.js +184 -0
- package/dist/src/server/stream-proxy.js.map +1 -0
- package/dist/src/storage/file-persistence.d.ts +48 -0
- package/dist/src/storage/file-persistence.d.ts.map +1 -0
- package/dist/src/storage/file-persistence.js +280 -0
- package/dist/src/storage/file-persistence.js.map +1 -0
- package/dist/src/storage/index.d.ts +5 -0
- package/dist/src/storage/index.d.ts.map +1 -0
- package/dist/src/storage/index.js +21 -0
- package/dist/src/storage/index.js.map +1 -0
- package/dist/src/storage/interfaces.d.ts +237 -0
- package/dist/src/storage/interfaces.d.ts.map +1 -0
- package/dist/src/storage/interfaces.js +3 -0
- package/dist/src/storage/interfaces.js.map +1 -0
- package/dist/src/storage/memory.d.ts +162 -0
- package/dist/src/storage/memory.d.ts.map +1 -0
- package/dist/src/storage/memory.js +603 -0
- package/dist/src/storage/memory.js.map +1 -0
- package/dist/src/storage/postgres.d.ts +267 -0
- package/dist/src/storage/postgres.d.ts.map +1 -0
- package/dist/src/storage/postgres.js +1555 -0
- package/dist/src/storage/postgres.js.map +1 -0
- package/dist/src/storage/redis.d.ts +202 -0
- package/dist/src/storage/redis.d.ts.map +1 -0
- package/dist/src/storage/redis.js +629 -0
- package/dist/src/storage/redis.js.map +1 -0
- package/dist/src/tracing/index.d.ts +2 -0
- package/dist/src/tracing/index.d.ts.map +1 -0
- package/dist/src/tracing/index.js +6 -0
- package/dist/src/tracing/index.js.map +1 -0
- package/dist/src/tracing/provider.d.ts +43 -0
- package/dist/src/tracing/provider.d.ts.map +1 -0
- package/dist/src/tracing/provider.js +74 -0
- package/dist/src/tracing/provider.js.map +1 -0
- package/dist/src/trust/calculator.d.ts +54 -0
- package/dist/src/trust/calculator.d.ts.map +1 -0
- package/dist/src/trust/calculator.js +102 -0
- package/dist/src/trust/calculator.js.map +1 -0
- package/dist/src/trust/index.d.ts +2 -0
- package/dist/src/trust/index.d.ts.map +1 -0
- package/dist/src/trust/index.js +7 -0
- package/dist/src/trust/index.js.map +1 -0
- package/dist/src/types/budget.d.ts +30 -0
- package/dist/src/types/budget.d.ts.map +1 -0
- package/dist/src/types/budget.js +3 -0
- package/dist/src/types/budget.js.map +1 -0
- package/dist/src/types/config.d.ts +176 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +3 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/events.d.ts +24 -0
- package/dist/src/types/events.d.ts.map +1 -0
- package/dist/src/types/events.js +3 -0
- package/dist/src/types/events.js.map +1 -0
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +24 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/policy.d.ts +60 -0
- package/dist/src/types/policy.d.ts.map +1 -0
- package/dist/src/types/policy.js +3 -0
- package/dist/src/types/policy.js.map +1 -0
- package/dist/src/types/stripe-config.d.ts +12 -0
- package/dist/src/types/stripe-config.d.ts.map +1 -0
- package/dist/src/types/stripe-config.js +3 -0
- package/dist/src/types/stripe-config.js.map +1 -0
- package/dist/src/types/subscription.d.ts +24 -0
- package/dist/src/types/subscription.d.ts.map +1 -0
- package/dist/src/types/subscription.js +38 -0
- package/dist/src/types/subscription.js.map +1 -0
- package/dist/src/types/tool-call.d.ts +42 -0
- package/dist/src/types/tool-call.d.ts.map +1 -0
- package/dist/src/types/tool-call.js +3 -0
- package/dist/src/types/tool-call.js.map +1 -0
- package/dist/src/types/tool-result.d.ts +58 -0
- package/dist/src/types/tool-result.d.ts.map +1 -0
- package/dist/src/types/tool-result.js +3 -0
- package/dist/src/types/tool-result.js.map +1 -0
- package/dist/src/types/user.d.ts +101 -0
- package/dist/src/types/user.d.ts.map +1 -0
- package/dist/src/types/user.js +6 -0
- package/dist/src/types/user.js.map +1 -0
- package/dist/tests/integration/api.test.d.ts +2 -0
- package/dist/tests/integration/api.test.d.ts.map +1 -0
- package/dist/tests/integration/api.test.js +1199 -0
- package/dist/tests/integration/api.test.js.map +1 -0
- package/dist/tests/integration/proxy.test.d.ts +2 -0
- package/dist/tests/integration/proxy.test.d.ts.map +1 -0
- package/dist/tests/integration/proxy.test.js +251 -0
- package/dist/tests/integration/proxy.test.js.map +1 -0
- package/dist/tests/integration/storage.test.d.ts +16 -0
- package/dist/tests/integration/storage.test.d.ts.map +1 -0
- package/dist/tests/integration/storage.test.js +826 -0
- package/dist/tests/integration/storage.test.js.map +1 -0
- package/dist/tests/unit/admin.test.d.ts +2 -0
- package/dist/tests/unit/admin.test.d.ts.map +1 -0
- package/dist/tests/unit/admin.test.js +698 -0
- package/dist/tests/unit/admin.test.js.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.js +903 -0
- package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
- package/dist/tests/unit/approval-manager.test.d.ts +2 -0
- package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-manager.test.js +528 -0
- package/dist/tests/unit/approval-manager.test.js.map +1 -0
- package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
- package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-webhook.test.js +355 -0
- package/dist/tests/unit/approval-webhook.test.js.map +1 -0
- package/dist/tests/unit/audit-logger.test.d.ts +2 -0
- package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
- package/dist/tests/unit/audit-logger.test.js +635 -0
- package/dist/tests/unit/audit-logger.test.js.map +1 -0
- package/dist/tests/unit/auth-routes.test.d.ts +2 -0
- package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-routes.test.js +281 -0
- package/dist/tests/unit/auth-routes.test.js.map +1 -0
- package/dist/tests/unit/auth.test.d.ts +2 -0
- package/dist/tests/unit/auth.test.d.ts.map +1 -0
- package/dist/tests/unit/auth.test.js +1382 -0
- package/dist/tests/unit/auth.test.js.map +1 -0
- package/dist/tests/unit/billing.test.d.ts +2 -0
- package/dist/tests/unit/billing.test.d.ts.map +1 -0
- package/dist/tests/unit/billing.test.js +579 -0
- package/dist/tests/unit/billing.test.js.map +1 -0
- package/dist/tests/unit/budget-manager.test.d.ts +2 -0
- package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-manager.test.js +778 -0
- package/dist/tests/unit/budget-manager.test.js.map +1 -0
- package/dist/tests/unit/budget-race.test.d.ts +2 -0
- package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-race.test.js +58 -0
- package/dist/tests/unit/budget-race.test.js.map +1 -0
- package/dist/tests/unit/cli.test.d.ts +2 -0
- package/dist/tests/unit/cli.test.d.ts.map +1 -0
- package/dist/tests/unit/cli.test.js +93 -0
- package/dist/tests/unit/cli.test.js.map +1 -0
- package/dist/tests/unit/concurrency.test.d.ts +2 -0
- package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
- package/dist/tests/unit/concurrency.test.js +1270 -0
- package/dist/tests/unit/concurrency.test.js.map +1 -0
- package/dist/tests/unit/config-validate.test.d.ts +2 -0
- package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
- package/dist/tests/unit/config-validate.test.js +230 -0
- package/dist/tests/unit/config-validate.test.js.map +1 -0
- package/dist/tests/unit/defaults.test.d.ts +2 -0
- package/dist/tests/unit/defaults.test.d.ts.map +1 -0
- package/dist/tests/unit/defaults.test.js +364 -0
- package/dist/tests/unit/defaults.test.js.map +1 -0
- package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
- package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-backends.test.js +563 -0
- package/dist/tests/unit/dlp-backends.test.js.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.js +739 -0
- package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
- package/dist/tests/unit/error-responses.test.d.ts +2 -0
- package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
- package/dist/tests/unit/error-responses.test.js +101 -0
- package/dist/tests/unit/error-responses.test.js.map +1 -0
- package/dist/tests/unit/executor-registry.test.d.ts +2 -0
- package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
- package/dist/tests/unit/executor-registry.test.js +390 -0
- package/dist/tests/unit/executor-registry.test.js.map +1 -0
- package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
- package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
- package/dist/tests/unit/forward-proxy.test.js +621 -0
- package/dist/tests/unit/forward-proxy.test.js.map +1 -0
- package/dist/tests/unit/gateway-features.test.d.ts +2 -0
- package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
- package/dist/tests/unit/gateway-features.test.js +753 -0
- package/dist/tests/unit/gateway-features.test.js.map +1 -0
- package/dist/tests/unit/http-executor.test.d.ts +2 -0
- package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/http-executor.test.js +310 -0
- package/dist/tests/unit/http-executor.test.js.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.js +1136 -0
- package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.js +899 -0
- package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.js +759 -0
- package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
- package/dist/tests/unit/mcp-server.test.d.ts +15 -0
- package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-server.test.js +158 -0
- package/dist/tests/unit/mcp-server.test.js.map +1 -0
- package/dist/tests/unit/metrics.test.d.ts +2 -0
- package/dist/tests/unit/metrics.test.d.ts.map +1 -0
- package/dist/tests/unit/metrics.test.js +208 -0
- package/dist/tests/unit/metrics.test.js.map +1 -0
- package/dist/tests/unit/oauth.test.d.ts +2 -0
- package/dist/tests/unit/oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/oauth.test.js +281 -0
- package/dist/tests/unit/oauth.test.js.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
- package/dist/tests/unit/opa-engine.test.d.ts +2 -0
- package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-engine.test.js +1813 -0
- package/dist/tests/unit/opa-engine.test.js.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.js +528 -0
- package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.d.ts +2 -0
- package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +1345 -0
- package/dist/tests/unit/policy-engine.test.js.map +1 -0
- package/dist/tests/unit/policy-store.test.d.ts +2 -0
- package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-store.test.js +60 -0
- package/dist/tests/unit/policy-store.test.js.map +1 -0
- package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
- package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/postgres-storage.test.js +614 -0
- package/dist/tests/unit/postgres-storage.test.js.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
- package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.js +166 -0
- package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
- package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
- package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
- package/dist/tests/unit/rate-limiter.test.js +443 -0
- package/dist/tests/unit/rate-limiter.test.js.map +1 -0
- package/dist/tests/unit/redis-storage.test.d.ts +2 -0
- package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/redis-storage.test.js +766 -0
- package/dist/tests/unit/redis-storage.test.js.map +1 -0
- package/dist/tests/unit/replay-engine.test.d.ts +2 -0
- package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/replay-engine.test.js +371 -0
- package/dist/tests/unit/replay-engine.test.js.map +1 -0
- package/dist/tests/unit/saas-routes.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes.test.js +1399 -0
- package/dist/tests/unit/saas-routes.test.js.map +1 -0
- package/dist/tests/unit/session.test.d.ts +2 -0
- package/dist/tests/unit/session.test.d.ts.map +1 -0
- package/dist/tests/unit/session.test.js +532 -0
- package/dist/tests/unit/session.test.js.map +1 -0
- package/dist/tests/unit/slack-executor.test.d.ts +2 -0
- package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/slack-executor.test.js +209 -0
- package/dist/tests/unit/slack-executor.test.js.map +1 -0
- package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
- package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/storage-hardening.test.js +165 -0
- package/dist/tests/unit/storage-hardening.test.js.map +1 -0
- package/dist/tests/unit/storage.test.d.ts +2 -0
- package/dist/tests/unit/storage.test.d.ts.map +1 -0
- package/dist/tests/unit/storage.test.js +698 -0
- package/dist/tests/unit/storage.test.js.map +1 -0
- package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
- package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
- package/dist/tests/unit/text-normalizer.test.js +229 -0
- package/dist/tests/unit/text-normalizer.test.js.map +1 -0
- package/dist/tests/unit/tracing.test.d.ts +2 -0
- package/dist/tests/unit/tracing.test.d.ts.map +1 -0
- package/dist/tests/unit/tracing.test.js +611 -0
- package/dist/tests/unit/tracing.test.js.map +1 -0
- package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
- package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
- package/dist/tests/unit/trust-calculator.test.js +497 -0
- package/dist/tests/unit/trust-calculator.test.js.map +1 -0
- package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
- package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
- package/dist/tests/unit/ts-sdk.test.js +421 -0
- package/dist/tests/unit/ts-sdk.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
- package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor.test.js +271 -0
- package/dist/tests/unit/usage-extractor.test.js.map +1 -0
- package/dist/tests/unit/user-stores.test.d.ts +2 -0
- package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
- package/dist/tests/unit/user-stores.test.js +687 -0
- package/dist/tests/unit/user-stores.test.js.map +1 -0
- package/dist/tests/unit/validate.test.d.ts +2 -0
- package/dist/tests/unit/validate.test.d.ts.map +1 -0
- package/dist/tests/unit/validate.test.js +545 -0
- package/dist/tests/unit/validate.test.js.map +1 -0
- package/package.json +86 -0
- package/policy-packs/README.md +42 -0
- package/policy-packs/default.yaml +46 -0
- package/policy-packs/dev_fast.yaml +54 -0
- package/policy-packs/prod_strict.yaml +83 -0
|
@@ -0,0 +1,1345 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("fs"));
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const engine_1 = require("../../src/policy/engine");
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Helpers
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/** Create a temporary directory that is cleaned up automatically. */
|
|
44
|
+
let tmpDir;
|
|
45
|
+
beforeAll(() => {
|
|
46
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'policy-engine-test-'));
|
|
47
|
+
});
|
|
48
|
+
afterAll(() => {
|
|
49
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
/** Write a YAML string to a temporary file and return its path. */
|
|
52
|
+
function writeTmpYaml(filename, content) {
|
|
53
|
+
const filePath = path.join(tmpDir, filename);
|
|
54
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
55
|
+
return filePath;
|
|
56
|
+
}
|
|
57
|
+
/** Build a minimal valid ToolCall for testing. */
|
|
58
|
+
function buildToolCall(overrides = {}) {
|
|
59
|
+
return {
|
|
60
|
+
tool_call_id: 'tc-001',
|
|
61
|
+
task_id: 'task-001',
|
|
62
|
+
workspace_id: 'ws-default',
|
|
63
|
+
actor: {
|
|
64
|
+
type: 'agent',
|
|
65
|
+
id: 'agent-1',
|
|
66
|
+
...(overrides.actor || {}),
|
|
67
|
+
},
|
|
68
|
+
source: {
|
|
69
|
+
platform: 'langgraph',
|
|
70
|
+
...(overrides.source || {}),
|
|
71
|
+
},
|
|
72
|
+
tool: {
|
|
73
|
+
name: 'http.request',
|
|
74
|
+
capability: 'read',
|
|
75
|
+
...(overrides.tool || {}),
|
|
76
|
+
},
|
|
77
|
+
args: {
|
|
78
|
+
method: 'GET',
|
|
79
|
+
url: 'https://api.github.com/repos',
|
|
80
|
+
...(overrides.args || {}),
|
|
81
|
+
},
|
|
82
|
+
context: overrides.context,
|
|
83
|
+
constraints: overrides.constraints,
|
|
84
|
+
timestamp: overrides.timestamp,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// A valid minimal policy pack YAML
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
const VALID_PACK_YAML = `
|
|
91
|
+
name: test_pack
|
|
92
|
+
version: "1.0.0"
|
|
93
|
+
description: "Test policy pack"
|
|
94
|
+
|
|
95
|
+
rules:
|
|
96
|
+
- name: "allow-reads"
|
|
97
|
+
description: "Allow read operations"
|
|
98
|
+
effect: ALLOW
|
|
99
|
+
priority: 10
|
|
100
|
+
conditions:
|
|
101
|
+
capabilities:
|
|
102
|
+
- "read"
|
|
103
|
+
|
|
104
|
+
- name: "deny-all"
|
|
105
|
+
description: "Default deny everything else"
|
|
106
|
+
effect: DENY
|
|
107
|
+
priority: 100
|
|
108
|
+
conditions: {}
|
|
109
|
+
`;
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Tests
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
describe('PolicyEngine', () => {
|
|
114
|
+
// -----------------------------------------------------------------------
|
|
115
|
+
// Loading & Parsing
|
|
116
|
+
// -----------------------------------------------------------------------
|
|
117
|
+
describe('loadPack (constructor)', () => {
|
|
118
|
+
it('should load a valid YAML policy pack from disk', () => {
|
|
119
|
+
const packPath = writeTmpYaml('valid.yaml', VALID_PACK_YAML);
|
|
120
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
121
|
+
const pack = engine.getPack();
|
|
122
|
+
expect(pack.name).toBe('test_pack');
|
|
123
|
+
expect(pack.version).toBe('1.0.0');
|
|
124
|
+
expect(pack.rules).toHaveLength(2);
|
|
125
|
+
});
|
|
126
|
+
it('should throw when the policy pack file does not exist', () => {
|
|
127
|
+
const missingPath = path.join(tmpDir, 'nonexistent.yaml');
|
|
128
|
+
expect(() => new engine_1.PolicyEngine(missingPath)).toThrow(/Failed to read policy pack file/);
|
|
129
|
+
});
|
|
130
|
+
it('should throw on invalid YAML syntax', () => {
|
|
131
|
+
const badYaml = writeTmpYaml('bad-syntax.yaml', '{ invalid: yaml: [');
|
|
132
|
+
expect(() => new engine_1.PolicyEngine(badYaml)).toThrow(/Failed to parse YAML/);
|
|
133
|
+
});
|
|
134
|
+
it('should throw when the YAML file contains a non-object (e.g. a scalar)', () => {
|
|
135
|
+
const scalarYaml = writeTmpYaml('scalar.yaml', 'just a string');
|
|
136
|
+
expect(() => new engine_1.PolicyEngine(scalarYaml)).toThrow(/does not contain a valid YAML object/);
|
|
137
|
+
});
|
|
138
|
+
it('should throw when the YAML file is empty (null content)', () => {
|
|
139
|
+
const emptyYaml = writeTmpYaml('empty.yaml', '');
|
|
140
|
+
expect(() => new engine_1.PolicyEngine(emptyYaml)).toThrow(/does not contain a valid YAML object/);
|
|
141
|
+
});
|
|
142
|
+
it('should sort rules by priority (ascending) on load', () => {
|
|
143
|
+
const yaml = `
|
|
144
|
+
name: priority_test
|
|
145
|
+
version: "1.0.0"
|
|
146
|
+
rules:
|
|
147
|
+
- name: "low-priority"
|
|
148
|
+
effect: DENY
|
|
149
|
+
priority: 50
|
|
150
|
+
conditions: {}
|
|
151
|
+
- name: "high-priority"
|
|
152
|
+
effect: ALLOW
|
|
153
|
+
priority: 5
|
|
154
|
+
conditions: {}
|
|
155
|
+
- name: "medium-priority"
|
|
156
|
+
effect: DENY
|
|
157
|
+
priority: 25
|
|
158
|
+
conditions: {}
|
|
159
|
+
`;
|
|
160
|
+
const packPath = writeTmpYaml('priority.yaml', yaml);
|
|
161
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
162
|
+
const pack = engine.getPack();
|
|
163
|
+
expect(pack.rules[0].name).toBe('high-priority');
|
|
164
|
+
expect(pack.rules[1].name).toBe('medium-priority');
|
|
165
|
+
expect(pack.rules[2].name).toBe('low-priority');
|
|
166
|
+
});
|
|
167
|
+
it('should place rules without explicit priority at the end', () => {
|
|
168
|
+
const yaml = `
|
|
169
|
+
name: no_priority_test
|
|
170
|
+
version: "1.0.0"
|
|
171
|
+
rules:
|
|
172
|
+
- name: "no-priority"
|
|
173
|
+
effect: DENY
|
|
174
|
+
conditions: {}
|
|
175
|
+
- name: "has-priority"
|
|
176
|
+
effect: ALLOW
|
|
177
|
+
priority: 10
|
|
178
|
+
conditions: {}
|
|
179
|
+
`;
|
|
180
|
+
const packPath = writeTmpYaml('no-priority.yaml', yaml);
|
|
181
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
182
|
+
const pack = engine.getPack();
|
|
183
|
+
expect(pack.rules[0].name).toBe('has-priority');
|
|
184
|
+
expect(pack.rules[1].name).toBe('no-priority');
|
|
185
|
+
});
|
|
186
|
+
it('should load the real default.yaml policy pack successfully', () => {
|
|
187
|
+
const defaultPath = path.resolve(__dirname, '../../policy-packs/default.yaml');
|
|
188
|
+
const engine = new engine_1.PolicyEngine(defaultPath);
|
|
189
|
+
const pack = engine.getPack();
|
|
190
|
+
expect(pack.name).toBe('default');
|
|
191
|
+
expect(pack.rules.length).toBeGreaterThanOrEqual(4);
|
|
192
|
+
expect(pack.rules[0].name).toBe('Block metadata endpoints');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
// -----------------------------------------------------------------------
|
|
196
|
+
// Validation
|
|
197
|
+
// -----------------------------------------------------------------------
|
|
198
|
+
describe('validate', () => {
|
|
199
|
+
it('should return valid for a correctly structured policy pack', () => {
|
|
200
|
+
const pack = {
|
|
201
|
+
name: 'valid_pack',
|
|
202
|
+
version: '1.0.0',
|
|
203
|
+
rules: [
|
|
204
|
+
{
|
|
205
|
+
name: 'rule1',
|
|
206
|
+
effect: 'ALLOW',
|
|
207
|
+
conditions: {},
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
212
|
+
expect(result.valid).toBe(true);
|
|
213
|
+
expect(result.errors).toHaveLength(0);
|
|
214
|
+
});
|
|
215
|
+
it('should return errors when name is missing', () => {
|
|
216
|
+
const pack = {
|
|
217
|
+
name: '',
|
|
218
|
+
version: '1.0.0',
|
|
219
|
+
rules: [{ name: 'r', effect: 'ALLOW', conditions: {} }],
|
|
220
|
+
};
|
|
221
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
222
|
+
expect(result.valid).toBe(false);
|
|
223
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/non-empty "name" string/)]));
|
|
224
|
+
});
|
|
225
|
+
it('should return errors when version is missing', () => {
|
|
226
|
+
const pack = {
|
|
227
|
+
name: 'test',
|
|
228
|
+
version: '',
|
|
229
|
+
rules: [{ name: 'r', effect: 'ALLOW', conditions: {} }],
|
|
230
|
+
};
|
|
231
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
232
|
+
expect(result.valid).toBe(false);
|
|
233
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/non-empty "version" string/)]));
|
|
234
|
+
});
|
|
235
|
+
it('should return errors when rules is not an array', () => {
|
|
236
|
+
const pack = {
|
|
237
|
+
name: 'test',
|
|
238
|
+
version: '1.0.0',
|
|
239
|
+
rules: 'not-an-array',
|
|
240
|
+
};
|
|
241
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
242
|
+
expect(result.valid).toBe(false);
|
|
243
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/"rules" array/)]));
|
|
244
|
+
});
|
|
245
|
+
it('should return errors for a rule with no name', () => {
|
|
246
|
+
const pack = {
|
|
247
|
+
name: 'test',
|
|
248
|
+
version: '1.0.0',
|
|
249
|
+
rules: [{ name: '', effect: 'ALLOW', conditions: {} }],
|
|
250
|
+
};
|
|
251
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
252
|
+
expect(result.valid).toBe(false);
|
|
253
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/Rule\[0\].*non-empty "name"/)]));
|
|
254
|
+
});
|
|
255
|
+
it('should return errors for a rule with missing effect', () => {
|
|
256
|
+
const pack = {
|
|
257
|
+
name: 'test',
|
|
258
|
+
version: '1.0.0',
|
|
259
|
+
rules: [{ name: 'r', effect: '', conditions: {} }],
|
|
260
|
+
};
|
|
261
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
262
|
+
expect(result.valid).toBe(false);
|
|
263
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/must have an "effect" string/)]));
|
|
264
|
+
});
|
|
265
|
+
it('should return errors for a rule with invalid effect value', () => {
|
|
266
|
+
const pack = {
|
|
267
|
+
name: 'test',
|
|
268
|
+
version: '1.0.0',
|
|
269
|
+
rules: [{ name: 'r', effect: 'EXECUTE', conditions: {} }],
|
|
270
|
+
};
|
|
271
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
272
|
+
expect(result.valid).toBe(false);
|
|
273
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/invalid effect "EXECUTE"/)]));
|
|
274
|
+
});
|
|
275
|
+
it('should return errors for a rule with missing conditions', () => {
|
|
276
|
+
const pack = {
|
|
277
|
+
name: 'test',
|
|
278
|
+
version: '1.0.0',
|
|
279
|
+
rules: [{ name: 'r', effect: 'ALLOW' }],
|
|
280
|
+
};
|
|
281
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
282
|
+
expect(result.valid).toBe(false);
|
|
283
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/must have a "conditions" object/)]));
|
|
284
|
+
});
|
|
285
|
+
it('should return errors when priority is not a finite number', () => {
|
|
286
|
+
const pack = {
|
|
287
|
+
name: 'test',
|
|
288
|
+
version: '1.0.0',
|
|
289
|
+
rules: [
|
|
290
|
+
{
|
|
291
|
+
name: 'r',
|
|
292
|
+
effect: 'ALLOW',
|
|
293
|
+
priority: Infinity,
|
|
294
|
+
conditions: {},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
299
|
+
expect(result.valid).toBe(false);
|
|
300
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/"priority" must be a finite number/)]));
|
|
301
|
+
});
|
|
302
|
+
it('should return errors for an invalid tool_match regex', () => {
|
|
303
|
+
const pack = {
|
|
304
|
+
name: 'test',
|
|
305
|
+
version: '1.0.0',
|
|
306
|
+
rules: [
|
|
307
|
+
{
|
|
308
|
+
name: 'r',
|
|
309
|
+
effect: 'ALLOW',
|
|
310
|
+
conditions: { tool_match: '[invalid(' },
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
315
|
+
expect(result.valid).toBe(false);
|
|
316
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/conditions.tool_match.*not a valid regex/)]));
|
|
317
|
+
});
|
|
318
|
+
it('should return errors for an invalid label_match regex', () => {
|
|
319
|
+
const pack = {
|
|
320
|
+
name: 'test',
|
|
321
|
+
version: '1.0.0',
|
|
322
|
+
rules: [
|
|
323
|
+
{
|
|
324
|
+
name: 'r',
|
|
325
|
+
effect: 'ALLOW',
|
|
326
|
+
conditions: { label_match: '[bad regex(' },
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
};
|
|
330
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
331
|
+
expect(result.valid).toBe(false);
|
|
332
|
+
expect(result.errors).toEqual(expect.arrayContaining([expect.stringMatching(/conditions.label_match.*not a valid regex/)]));
|
|
333
|
+
});
|
|
334
|
+
it('should accept valid tool_match and label_match regex patterns', () => {
|
|
335
|
+
const pack = {
|
|
336
|
+
name: 'test',
|
|
337
|
+
version: '1.0.0',
|
|
338
|
+
rules: [
|
|
339
|
+
{
|
|
340
|
+
name: 'r',
|
|
341
|
+
effect: 'ALLOW',
|
|
342
|
+
conditions: {
|
|
343
|
+
tool_match: '^http\\.',
|
|
344
|
+
label_match: 'prod|staging',
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
const result = engine_1.PolicyEngine.validate(pack);
|
|
350
|
+
expect(result.valid).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
// -----------------------------------------------------------------------
|
|
354
|
+
// evaluate() -- Decision Types
|
|
355
|
+
// -----------------------------------------------------------------------
|
|
356
|
+
describe('evaluate - decisions', () => {
|
|
357
|
+
it('should return ALLOW decision when a matching ALLOW rule is found', () => {
|
|
358
|
+
const yaml = `
|
|
359
|
+
name: allow_test
|
|
360
|
+
version: "1.0.0"
|
|
361
|
+
rules:
|
|
362
|
+
- name: "allow-reads"
|
|
363
|
+
effect: ALLOW
|
|
364
|
+
priority: 1
|
|
365
|
+
conditions:
|
|
366
|
+
capabilities:
|
|
367
|
+
- "read"
|
|
368
|
+
`;
|
|
369
|
+
const packPath = writeTmpYaml('allow.yaml', yaml);
|
|
370
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
371
|
+
const result = engine.evaluate(buildToolCall());
|
|
372
|
+
expect(result.decision).toBe('allow');
|
|
373
|
+
expect(result.rule_name).toBe('allow-reads');
|
|
374
|
+
});
|
|
375
|
+
it('should return DENY decision when a matching DENY rule is found', () => {
|
|
376
|
+
const yaml = `
|
|
377
|
+
name: deny_test
|
|
378
|
+
version: "1.0.0"
|
|
379
|
+
rules:
|
|
380
|
+
- name: "deny-writes"
|
|
381
|
+
effect: DENY
|
|
382
|
+
priority: 1
|
|
383
|
+
conditions:
|
|
384
|
+
capabilities:
|
|
385
|
+
- "write"
|
|
386
|
+
`;
|
|
387
|
+
const packPath = writeTmpYaml('deny.yaml', yaml);
|
|
388
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
389
|
+
const tc = buildToolCall({ tool: { name: 'db.write', capability: 'write' } });
|
|
390
|
+
const result = engine.evaluate(tc);
|
|
391
|
+
expect(result.decision).toBe('deny');
|
|
392
|
+
expect(result.rule_name).toBe('deny-writes');
|
|
393
|
+
});
|
|
394
|
+
it('should return TRANSFORM decision', () => {
|
|
395
|
+
const yaml = `
|
|
396
|
+
name: transform_test
|
|
397
|
+
version: "1.0.0"
|
|
398
|
+
rules:
|
|
399
|
+
- name: "strip-auth"
|
|
400
|
+
description: "Strip authorization header"
|
|
401
|
+
effect: TRANSFORM
|
|
402
|
+
priority: 1
|
|
403
|
+
conditions:
|
|
404
|
+
tools:
|
|
405
|
+
- "http.request"
|
|
406
|
+
transformations:
|
|
407
|
+
- type: strip_header
|
|
408
|
+
target: "Authorization"
|
|
409
|
+
`;
|
|
410
|
+
const packPath = writeTmpYaml('transform.yaml', yaml);
|
|
411
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
412
|
+
const result = engine.evaluate(buildToolCall());
|
|
413
|
+
expect(result.decision).toBe('transform');
|
|
414
|
+
expect(result.rule_name).toBe('strip-auth');
|
|
415
|
+
expect(result.transformations).toEqual([
|
|
416
|
+
{ type: 'strip_header', target: 'Authorization' },
|
|
417
|
+
]);
|
|
418
|
+
});
|
|
419
|
+
it('should return REQUIRE_APPROVAL decision with approval metadata', () => {
|
|
420
|
+
const yaml = `
|
|
421
|
+
name: approval_test
|
|
422
|
+
version: "1.0.0"
|
|
423
|
+
rules:
|
|
424
|
+
- name: "require-approval-writes"
|
|
425
|
+
description: "Writes need approval"
|
|
426
|
+
effect: REQUIRE_APPROVAL
|
|
427
|
+
priority: 1
|
|
428
|
+
conditions:
|
|
429
|
+
capabilities:
|
|
430
|
+
- "write"
|
|
431
|
+
approval:
|
|
432
|
+
scope: "team_lead"
|
|
433
|
+
ttl_seconds: 1800
|
|
434
|
+
reason: "Write operations require approval"
|
|
435
|
+
`;
|
|
436
|
+
const packPath = writeTmpYaml('approval.yaml', yaml);
|
|
437
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
438
|
+
const tc = buildToolCall({ tool: { name: 'db.update', capability: 'write' } });
|
|
439
|
+
const result = engine.evaluate(tc);
|
|
440
|
+
expect(result.decision).toBe('require_approval');
|
|
441
|
+
expect(result.approval).toBeDefined();
|
|
442
|
+
expect(result.approval.scope).toBe('team_lead');
|
|
443
|
+
expect(result.approval.ttl_seconds).toBe(1800);
|
|
444
|
+
expect(result.approval.reason).toBe('Write operations require approval');
|
|
445
|
+
});
|
|
446
|
+
it('should apply default DENY when no rules match and no pack default_effect', () => {
|
|
447
|
+
const yaml = `
|
|
448
|
+
name: no_match_test
|
|
449
|
+
version: "1.0.0"
|
|
450
|
+
rules:
|
|
451
|
+
- name: "only-specific-tool"
|
|
452
|
+
effect: ALLOW
|
|
453
|
+
priority: 1
|
|
454
|
+
conditions:
|
|
455
|
+
tools:
|
|
456
|
+
- "very.specific.tool"
|
|
457
|
+
`;
|
|
458
|
+
const packPath = writeTmpYaml('no-match.yaml', yaml);
|
|
459
|
+
const engine = new engine_1.PolicyEngine(packPath, 'DENY');
|
|
460
|
+
const result = engine.evaluate(buildToolCall());
|
|
461
|
+
expect(result.decision).toBe('deny');
|
|
462
|
+
expect(result.rule_name).toBe('__default');
|
|
463
|
+
expect(result.reasons).toEqual(expect.arrayContaining([expect.stringMatching(/No matching rule found/)]));
|
|
464
|
+
});
|
|
465
|
+
it('should use pack-level default_effect when no rules match', () => {
|
|
466
|
+
const yaml = `
|
|
467
|
+
name: pack_default_test
|
|
468
|
+
version: "1.0.0"
|
|
469
|
+
default_effect: ALLOW
|
|
470
|
+
rules:
|
|
471
|
+
- name: "only-specific-tool"
|
|
472
|
+
effect: DENY
|
|
473
|
+
priority: 1
|
|
474
|
+
conditions:
|
|
475
|
+
tools:
|
|
476
|
+
- "very.specific.tool"
|
|
477
|
+
`;
|
|
478
|
+
const packPath = writeTmpYaml('pack-default.yaml', yaml);
|
|
479
|
+
const engine = new engine_1.PolicyEngine(packPath, 'DENY');
|
|
480
|
+
const result = engine.evaluate(buildToolCall());
|
|
481
|
+
// Pack-level default_effect takes precedence over constructor default
|
|
482
|
+
expect(result.decision).toBe('allow');
|
|
483
|
+
expect(result.rule_name).toBe('__default');
|
|
484
|
+
});
|
|
485
|
+
it('should include description in reasons when rule has a description', () => {
|
|
486
|
+
const yaml = `
|
|
487
|
+
name: desc_test
|
|
488
|
+
version: "1.0.0"
|
|
489
|
+
rules:
|
|
490
|
+
- name: "described-rule"
|
|
491
|
+
description: "This rule has a detailed description"
|
|
492
|
+
effect: ALLOW
|
|
493
|
+
priority: 1
|
|
494
|
+
conditions: {}
|
|
495
|
+
`;
|
|
496
|
+
const packPath = writeTmpYaml('desc.yaml', yaml);
|
|
497
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
498
|
+
const result = engine.evaluate(buildToolCall());
|
|
499
|
+
expect(result.reasons).toContain('This rule has a detailed description');
|
|
500
|
+
expect(result.reasons).toContain('Matched rule "described-rule"');
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
// -----------------------------------------------------------------------
|
|
504
|
+
// evaluate() -- Condition Matching
|
|
505
|
+
// -----------------------------------------------------------------------
|
|
506
|
+
describe('evaluate - condition matching', () => {
|
|
507
|
+
it('should match on tool name (tools condition)', () => {
|
|
508
|
+
const yaml = `
|
|
509
|
+
name: tool_name_test
|
|
510
|
+
version: "1.0.0"
|
|
511
|
+
rules:
|
|
512
|
+
- name: "match-http"
|
|
513
|
+
effect: ALLOW
|
|
514
|
+
priority: 1
|
|
515
|
+
conditions:
|
|
516
|
+
tools:
|
|
517
|
+
- "http.request"
|
|
518
|
+
- name: "deny-all"
|
|
519
|
+
effect: DENY
|
|
520
|
+
priority: 100
|
|
521
|
+
conditions: {}
|
|
522
|
+
`;
|
|
523
|
+
const packPath = writeTmpYaml('tool-name.yaml', yaml);
|
|
524
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
525
|
+
const httpCall = buildToolCall({ tool: { name: 'http.request', capability: 'read' } });
|
|
526
|
+
expect(engine.evaluate(httpCall).decision).toBe('allow');
|
|
527
|
+
const dbCall = buildToolCall({ tool: { name: 'db.query', capability: 'read' } });
|
|
528
|
+
expect(engine.evaluate(dbCall).decision).toBe('deny');
|
|
529
|
+
});
|
|
530
|
+
it('should match on tool_match regex', () => {
|
|
531
|
+
const yaml = `
|
|
532
|
+
name: tool_regex_test
|
|
533
|
+
version: "1.0.0"
|
|
534
|
+
rules:
|
|
535
|
+
- name: "match-http-wildcard"
|
|
536
|
+
effect: ALLOW
|
|
537
|
+
priority: 1
|
|
538
|
+
conditions:
|
|
539
|
+
tool_match: "^http\\\\."
|
|
540
|
+
- name: "deny-all"
|
|
541
|
+
effect: DENY
|
|
542
|
+
priority: 100
|
|
543
|
+
conditions: {}
|
|
544
|
+
`;
|
|
545
|
+
const packPath = writeTmpYaml('tool-regex.yaml', yaml);
|
|
546
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
547
|
+
const httpCall = buildToolCall({ tool: { name: 'http.request', capability: 'read' } });
|
|
548
|
+
expect(engine.evaluate(httpCall).decision).toBe('allow');
|
|
549
|
+
const httpPostCall = buildToolCall({ tool: { name: 'http.post', capability: 'write' } });
|
|
550
|
+
expect(engine.evaluate(httpPostCall).decision).toBe('allow');
|
|
551
|
+
const dbCall = buildToolCall({ tool: { name: 'db.query', capability: 'read' } });
|
|
552
|
+
expect(engine.evaluate(dbCall).decision).toBe('deny');
|
|
553
|
+
});
|
|
554
|
+
it('should match on capability', () => {
|
|
555
|
+
const yaml = `
|
|
556
|
+
name: cap_test
|
|
557
|
+
version: "1.0.0"
|
|
558
|
+
rules:
|
|
559
|
+
- name: "allow-reads"
|
|
560
|
+
effect: ALLOW
|
|
561
|
+
priority: 1
|
|
562
|
+
conditions:
|
|
563
|
+
capabilities:
|
|
564
|
+
- "read"
|
|
565
|
+
- name: "deny-all"
|
|
566
|
+
effect: DENY
|
|
567
|
+
priority: 100
|
|
568
|
+
conditions: {}
|
|
569
|
+
`;
|
|
570
|
+
const packPath = writeTmpYaml('capability.yaml', yaml);
|
|
571
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
572
|
+
const readCall = buildToolCall({ tool: { name: 'http.request', capability: 'read' } });
|
|
573
|
+
expect(engine.evaluate(readCall).decision).toBe('allow');
|
|
574
|
+
const writeCall = buildToolCall({ tool: { name: 'http.request', capability: 'write' } });
|
|
575
|
+
expect(engine.evaluate(writeCall).decision).toBe('deny');
|
|
576
|
+
});
|
|
577
|
+
it('should match on domain extracted from URL in args', () => {
|
|
578
|
+
const yaml = `
|
|
579
|
+
name: domain_test
|
|
580
|
+
version: "1.0.0"
|
|
581
|
+
rules:
|
|
582
|
+
- name: "allow-github"
|
|
583
|
+
effect: ALLOW
|
|
584
|
+
priority: 1
|
|
585
|
+
conditions:
|
|
586
|
+
domains:
|
|
587
|
+
- "api.github.com"
|
|
588
|
+
- name: "deny-all"
|
|
589
|
+
effect: DENY
|
|
590
|
+
priority: 100
|
|
591
|
+
conditions: {}
|
|
592
|
+
`;
|
|
593
|
+
const packPath = writeTmpYaml('domain-match.yaml', yaml);
|
|
594
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
595
|
+
const githubCall = buildToolCall({ args: { url: 'https://api.github.com/repos', method: 'GET' } });
|
|
596
|
+
expect(engine.evaluate(githubCall).decision).toBe('allow');
|
|
597
|
+
const otherCall = buildToolCall({ args: { url: 'https://evil.com/data', method: 'GET' } });
|
|
598
|
+
expect(engine.evaluate(otherCall).decision).toBe('deny');
|
|
599
|
+
});
|
|
600
|
+
it('should match wildcard subdomain patterns (*.example.com)', () => {
|
|
601
|
+
const yaml = `
|
|
602
|
+
name: wildcard_domain_test
|
|
603
|
+
version: "1.0.0"
|
|
604
|
+
rules:
|
|
605
|
+
- name: "allow-aws"
|
|
606
|
+
effect: ALLOW
|
|
607
|
+
priority: 1
|
|
608
|
+
conditions:
|
|
609
|
+
domains:
|
|
610
|
+
- "*.amazonaws.com"
|
|
611
|
+
- name: "deny-all"
|
|
612
|
+
effect: DENY
|
|
613
|
+
priority: 100
|
|
614
|
+
conditions: {}
|
|
615
|
+
`;
|
|
616
|
+
const packPath = writeTmpYaml('wildcard-domain.yaml', yaml);
|
|
617
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
618
|
+
const s3Call = buildToolCall({ args: { url: 'https://s3.amazonaws.com/bucket/key', method: 'GET' } });
|
|
619
|
+
expect(engine.evaluate(s3Call).decision).toBe('allow');
|
|
620
|
+
const deepSubCall = buildToolCall({ args: { url: 'https://us-east-1.s3.amazonaws.com/b', method: 'GET' } });
|
|
621
|
+
expect(engine.evaluate(deepSubCall).decision).toBe('allow');
|
|
622
|
+
const otherCall = buildToolCall({ args: { url: 'https://evil.com/data', method: 'GET' } });
|
|
623
|
+
expect(engine.evaluate(otherCall).decision).toBe('deny');
|
|
624
|
+
});
|
|
625
|
+
it('should fail domain match when tool call has no URL', () => {
|
|
626
|
+
const yaml = `
|
|
627
|
+
name: no_url_domain_test
|
|
628
|
+
version: "1.0.0"
|
|
629
|
+
rules:
|
|
630
|
+
- name: "require-domain"
|
|
631
|
+
effect: ALLOW
|
|
632
|
+
priority: 1
|
|
633
|
+
conditions:
|
|
634
|
+
domains:
|
|
635
|
+
- "api.github.com"
|
|
636
|
+
- name: "deny-all"
|
|
637
|
+
effect: DENY
|
|
638
|
+
priority: 100
|
|
639
|
+
conditions: {}
|
|
640
|
+
`;
|
|
641
|
+
const packPath = writeTmpYaml('no-url.yaml', yaml);
|
|
642
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
643
|
+
// Must explicitly set url to undefined to override the default in buildToolCall
|
|
644
|
+
const noUrlCall = buildToolCall({ args: { method: 'GET', url: undefined } });
|
|
645
|
+
expect(engine.evaluate(noUrlCall).decision).toBe('deny');
|
|
646
|
+
});
|
|
647
|
+
it('should match on HTTP method (case-insensitive)', () => {
|
|
648
|
+
const yaml = `
|
|
649
|
+
name: method_test
|
|
650
|
+
version: "1.0.0"
|
|
651
|
+
rules:
|
|
652
|
+
- name: "allow-gets"
|
|
653
|
+
effect: ALLOW
|
|
654
|
+
priority: 1
|
|
655
|
+
conditions:
|
|
656
|
+
methods:
|
|
657
|
+
- "GET"
|
|
658
|
+
- "HEAD"
|
|
659
|
+
- name: "deny-all"
|
|
660
|
+
effect: DENY
|
|
661
|
+
priority: 100
|
|
662
|
+
conditions: {}
|
|
663
|
+
`;
|
|
664
|
+
const packPath = writeTmpYaml('method.yaml', yaml);
|
|
665
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
666
|
+
const getCall = buildToolCall({ args: { method: 'get', url: 'https://api.github.com/repos' } });
|
|
667
|
+
expect(engine.evaluate(getCall).decision).toBe('allow');
|
|
668
|
+
const postCall = buildToolCall({ args: { method: 'POST', url: 'https://api.github.com/repos' } });
|
|
669
|
+
expect(engine.evaluate(postCall).decision).toBe('deny');
|
|
670
|
+
});
|
|
671
|
+
it('should fail method match when tool call has no method', () => {
|
|
672
|
+
const yaml = `
|
|
673
|
+
name: no_method_test
|
|
674
|
+
version: "1.0.0"
|
|
675
|
+
rules:
|
|
676
|
+
- name: "require-method"
|
|
677
|
+
effect: ALLOW
|
|
678
|
+
priority: 1
|
|
679
|
+
conditions:
|
|
680
|
+
methods:
|
|
681
|
+
- "GET"
|
|
682
|
+
- name: "deny-all"
|
|
683
|
+
effect: DENY
|
|
684
|
+
priority: 100
|
|
685
|
+
conditions: {}
|
|
686
|
+
`;
|
|
687
|
+
const packPath = writeTmpYaml('no-method.yaml', yaml);
|
|
688
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
689
|
+
// Must explicitly set method to undefined to override the default in buildToolCall
|
|
690
|
+
const noMethodCall = buildToolCall({ args: { url: 'https://api.github.com/repos', method: undefined } });
|
|
691
|
+
expect(engine.evaluate(noMethodCall).decision).toBe('deny');
|
|
692
|
+
});
|
|
693
|
+
it('should match on actor type (actor_types condition)', () => {
|
|
694
|
+
const yaml = `
|
|
695
|
+
name: actor_type_test
|
|
696
|
+
version: "1.0.0"
|
|
697
|
+
rules:
|
|
698
|
+
- name: "allow-agents"
|
|
699
|
+
effect: ALLOW
|
|
700
|
+
priority: 1
|
|
701
|
+
conditions:
|
|
702
|
+
actor_types:
|
|
703
|
+
- "agent"
|
|
704
|
+
- name: "deny-all"
|
|
705
|
+
effect: DENY
|
|
706
|
+
priority: 100
|
|
707
|
+
conditions: {}
|
|
708
|
+
`;
|
|
709
|
+
const packPath = writeTmpYaml('actor-type.yaml', yaml);
|
|
710
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
711
|
+
const agentCall = buildToolCall({ actor: { type: 'agent', id: 'a1' } });
|
|
712
|
+
expect(engine.evaluate(agentCall).decision).toBe('allow');
|
|
713
|
+
const userCall = buildToolCall({ actor: { type: 'user', id: 'u1' } });
|
|
714
|
+
expect(engine.evaluate(userCall).decision).toBe('deny');
|
|
715
|
+
});
|
|
716
|
+
it('should match on actor id (actors condition)', () => {
|
|
717
|
+
const yaml = `
|
|
718
|
+
name: actor_id_test
|
|
719
|
+
version: "1.0.0"
|
|
720
|
+
rules:
|
|
721
|
+
- name: "allow-specific-actor"
|
|
722
|
+
effect: ALLOW
|
|
723
|
+
priority: 1
|
|
724
|
+
conditions:
|
|
725
|
+
actors:
|
|
726
|
+
- "trusted-agent-1"
|
|
727
|
+
- name: "deny-all"
|
|
728
|
+
effect: DENY
|
|
729
|
+
priority: 100
|
|
730
|
+
conditions: {}
|
|
731
|
+
`;
|
|
732
|
+
const packPath = writeTmpYaml('actor-id.yaml', yaml);
|
|
733
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
734
|
+
const trustedCall = buildToolCall({ actor: { type: 'agent', id: 'trusted-agent-1' } });
|
|
735
|
+
expect(engine.evaluate(trustedCall).decision).toBe('allow');
|
|
736
|
+
const untrustedCall = buildToolCall({ actor: { type: 'agent', id: 'unknown-agent' } });
|
|
737
|
+
expect(engine.evaluate(untrustedCall).decision).toBe('deny');
|
|
738
|
+
});
|
|
739
|
+
it('should match on labels (at least one label matches)', () => {
|
|
740
|
+
const yaml = `
|
|
741
|
+
name: label_test
|
|
742
|
+
version: "1.0.0"
|
|
743
|
+
rules:
|
|
744
|
+
- name: "allow-production"
|
|
745
|
+
effect: ALLOW
|
|
746
|
+
priority: 1
|
|
747
|
+
conditions:
|
|
748
|
+
labels:
|
|
749
|
+
- "production"
|
|
750
|
+
- "staging"
|
|
751
|
+
- name: "deny-all"
|
|
752
|
+
effect: DENY
|
|
753
|
+
priority: 100
|
|
754
|
+
conditions: {}
|
|
755
|
+
`;
|
|
756
|
+
const packPath = writeTmpYaml('labels.yaml', yaml);
|
|
757
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
758
|
+
const prodCall = buildToolCall({ context: { labels: ['production', 'important'] } });
|
|
759
|
+
expect(engine.evaluate(prodCall).decision).toBe('allow');
|
|
760
|
+
const stagingCall = buildToolCall({ context: { labels: ['staging'] } });
|
|
761
|
+
expect(engine.evaluate(stagingCall).decision).toBe('allow');
|
|
762
|
+
const devCall = buildToolCall({ context: { labels: ['development'] } });
|
|
763
|
+
expect(engine.evaluate(devCall).decision).toBe('deny');
|
|
764
|
+
const noLabelsCall = buildToolCall();
|
|
765
|
+
expect(engine.evaluate(noLabelsCall).decision).toBe('deny');
|
|
766
|
+
});
|
|
767
|
+
it('should match on label_match regex', () => {
|
|
768
|
+
const yaml = `
|
|
769
|
+
name: label_regex_test
|
|
770
|
+
version: "1.0.0"
|
|
771
|
+
rules:
|
|
772
|
+
- name: "allow-env-labels"
|
|
773
|
+
effect: ALLOW
|
|
774
|
+
priority: 1
|
|
775
|
+
conditions:
|
|
776
|
+
label_match: "^(prod|staging)"
|
|
777
|
+
- name: "deny-all"
|
|
778
|
+
effect: DENY
|
|
779
|
+
priority: 100
|
|
780
|
+
conditions: {}
|
|
781
|
+
`;
|
|
782
|
+
const packPath = writeTmpYaml('label-regex.yaml', yaml);
|
|
783
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
784
|
+
const prodCall = buildToolCall({ context: { labels: ['production'] } });
|
|
785
|
+
expect(engine.evaluate(prodCall).decision).toBe('allow');
|
|
786
|
+
const stagingCall = buildToolCall({ context: { labels: ['staging-test'] } });
|
|
787
|
+
expect(engine.evaluate(stagingCall).decision).toBe('allow');
|
|
788
|
+
const devCall = buildToolCall({ context: { labels: ['development'] } });
|
|
789
|
+
expect(engine.evaluate(devCall).decision).toBe('deny');
|
|
790
|
+
});
|
|
791
|
+
it('should match on platforms', () => {
|
|
792
|
+
const yaml = `
|
|
793
|
+
name: platform_test
|
|
794
|
+
version: "1.0.0"
|
|
795
|
+
rules:
|
|
796
|
+
- name: "allow-langgraph"
|
|
797
|
+
effect: ALLOW
|
|
798
|
+
priority: 1
|
|
799
|
+
conditions:
|
|
800
|
+
platforms:
|
|
801
|
+
- "langgraph"
|
|
802
|
+
- name: "deny-all"
|
|
803
|
+
effect: DENY
|
|
804
|
+
priority: 100
|
|
805
|
+
conditions: {}
|
|
806
|
+
`;
|
|
807
|
+
const packPath = writeTmpYaml('platforms.yaml', yaml);
|
|
808
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
809
|
+
const lgCall = buildToolCall({ source: { platform: 'langgraph' } });
|
|
810
|
+
expect(engine.evaluate(lgCall).decision).toBe('allow');
|
|
811
|
+
const n8nCall = buildToolCall({ source: { platform: 'n8n' } });
|
|
812
|
+
expect(engine.evaluate(n8nCall).decision).toBe('deny');
|
|
813
|
+
});
|
|
814
|
+
it('should match on workspace_ids', () => {
|
|
815
|
+
const yaml = `
|
|
816
|
+
name: workspace_test
|
|
817
|
+
version: "1.0.0"
|
|
818
|
+
rules:
|
|
819
|
+
- name: "allow-specific-ws"
|
|
820
|
+
effect: ALLOW
|
|
821
|
+
priority: 1
|
|
822
|
+
conditions:
|
|
823
|
+
workspace_ids:
|
|
824
|
+
- "ws-alpha"
|
|
825
|
+
- "ws-beta"
|
|
826
|
+
- name: "deny-all"
|
|
827
|
+
effect: DENY
|
|
828
|
+
priority: 100
|
|
829
|
+
conditions: {}
|
|
830
|
+
`;
|
|
831
|
+
const packPath = writeTmpYaml('workspace.yaml', yaml);
|
|
832
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
833
|
+
const alphaCall = buildToolCall();
|
|
834
|
+
alphaCall.workspace_id = 'ws-alpha';
|
|
835
|
+
expect(engine.evaluate(alphaCall).decision).toBe('allow');
|
|
836
|
+
const gammaCall = buildToolCall();
|
|
837
|
+
gammaCall.workspace_id = 'ws-gamma';
|
|
838
|
+
expect(engine.evaluate(gammaCall).decision).toBe('deny');
|
|
839
|
+
});
|
|
840
|
+
it('should apply AND logic for multiple conditions in a single rule', () => {
|
|
841
|
+
const yaml = `
|
|
842
|
+
name: and_logic_test
|
|
843
|
+
version: "1.0.0"
|
|
844
|
+
rules:
|
|
845
|
+
- name: "specific-combo"
|
|
846
|
+
effect: ALLOW
|
|
847
|
+
priority: 1
|
|
848
|
+
conditions:
|
|
849
|
+
tools:
|
|
850
|
+
- "http.request"
|
|
851
|
+
capabilities:
|
|
852
|
+
- "read"
|
|
853
|
+
methods:
|
|
854
|
+
- "GET"
|
|
855
|
+
- name: "deny-all"
|
|
856
|
+
effect: DENY
|
|
857
|
+
priority: 100
|
|
858
|
+
conditions: {}
|
|
859
|
+
`;
|
|
860
|
+
const packPath = writeTmpYaml('and-logic.yaml', yaml);
|
|
861
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
862
|
+
// All three conditions match
|
|
863
|
+
const fullMatch = buildToolCall({
|
|
864
|
+
tool: { name: 'http.request', capability: 'read' },
|
|
865
|
+
args: { method: 'GET', url: 'https://example.com' },
|
|
866
|
+
});
|
|
867
|
+
expect(engine.evaluate(fullMatch).decision).toBe('allow');
|
|
868
|
+
// Tool matches, capability matches, but method doesn't
|
|
869
|
+
const partialMatch = buildToolCall({
|
|
870
|
+
tool: { name: 'http.request', capability: 'read' },
|
|
871
|
+
args: { method: 'POST', url: 'https://example.com' },
|
|
872
|
+
});
|
|
873
|
+
expect(engine.evaluate(partialMatch).decision).toBe('deny');
|
|
874
|
+
});
|
|
875
|
+
it('should match everything when conditions object is empty', () => {
|
|
876
|
+
const yaml = `
|
|
877
|
+
name: empty_conditions_test
|
|
878
|
+
version: "1.0.0"
|
|
879
|
+
rules:
|
|
880
|
+
- name: "catch-all"
|
|
881
|
+
effect: ALLOW
|
|
882
|
+
priority: 1
|
|
883
|
+
conditions: {}
|
|
884
|
+
`;
|
|
885
|
+
const packPath = writeTmpYaml('empty-conditions.yaml', yaml);
|
|
886
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
887
|
+
const anyCall = buildToolCall({
|
|
888
|
+
tool: { name: 'anything', capability: 'admin' },
|
|
889
|
+
args: { method: 'DELETE', url: 'https://evil.com/destroy' },
|
|
890
|
+
});
|
|
891
|
+
expect(engine.evaluate(anyCall).decision).toBe('allow');
|
|
892
|
+
expect(engine.evaluate(anyCall).rule_name).toBe('catch-all');
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
// -----------------------------------------------------------------------
|
|
896
|
+
// evaluate() -- Priority Ordering
|
|
897
|
+
// -----------------------------------------------------------------------
|
|
898
|
+
describe('evaluate - priority ordering', () => {
|
|
899
|
+
it('should return the first matching rule by priority (lower = higher precedence)', () => {
|
|
900
|
+
const yaml = `
|
|
901
|
+
name: priority_order_test
|
|
902
|
+
version: "1.0.0"
|
|
903
|
+
rules:
|
|
904
|
+
- name: "low-priority-allow"
|
|
905
|
+
effect: ALLOW
|
|
906
|
+
priority: 50
|
|
907
|
+
conditions:
|
|
908
|
+
capabilities:
|
|
909
|
+
- "read"
|
|
910
|
+
- name: "high-priority-deny"
|
|
911
|
+
effect: DENY
|
|
912
|
+
priority: 5
|
|
913
|
+
conditions:
|
|
914
|
+
capabilities:
|
|
915
|
+
- "read"
|
|
916
|
+
`;
|
|
917
|
+
const packPath = writeTmpYaml('priority-order.yaml', yaml);
|
|
918
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
919
|
+
const readCall = buildToolCall({ tool: { name: 'http.request', capability: 'read' } });
|
|
920
|
+
const result = engine.evaluate(readCall);
|
|
921
|
+
// The higher-precedence (lower number) rule should win
|
|
922
|
+
expect(result.decision).toBe('deny');
|
|
923
|
+
expect(result.rule_name).toBe('high-priority-deny');
|
|
924
|
+
});
|
|
925
|
+
it('should match more specific rules before catch-all when priorities enforce it', () => {
|
|
926
|
+
const yaml = `
|
|
927
|
+
name: specificity_test
|
|
928
|
+
version: "1.0.0"
|
|
929
|
+
rules:
|
|
930
|
+
- name: "specific-allow"
|
|
931
|
+
effect: ALLOW
|
|
932
|
+
priority: 10
|
|
933
|
+
conditions:
|
|
934
|
+
tools:
|
|
935
|
+
- "http.request"
|
|
936
|
+
methods:
|
|
937
|
+
- "GET"
|
|
938
|
+
- name: "broad-deny"
|
|
939
|
+
effect: DENY
|
|
940
|
+
priority: 20
|
|
941
|
+
conditions: {}
|
|
942
|
+
`;
|
|
943
|
+
const packPath = writeTmpYaml('specificity.yaml', yaml);
|
|
944
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
945
|
+
const getCall = buildToolCall({
|
|
946
|
+
tool: { name: 'http.request', capability: 'read' },
|
|
947
|
+
args: { method: 'GET', url: 'https://example.com' },
|
|
948
|
+
});
|
|
949
|
+
expect(engine.evaluate(getCall).decision).toBe('allow');
|
|
950
|
+
const postCall = buildToolCall({
|
|
951
|
+
tool: { name: 'http.request', capability: 'write' },
|
|
952
|
+
args: { method: 'POST', url: 'https://example.com' },
|
|
953
|
+
});
|
|
954
|
+
expect(engine.evaluate(postCall).decision).toBe('deny');
|
|
955
|
+
});
|
|
956
|
+
});
|
|
957
|
+
// -----------------------------------------------------------------------
|
|
958
|
+
// evaluate() -- Domain Blocklist / Allowlist (Pack-level)
|
|
959
|
+
// -----------------------------------------------------------------------
|
|
960
|
+
describe('evaluate - pack-level domain lists', () => {
|
|
961
|
+
it('should DENY when domain is in pack-level domain_blocklist', () => {
|
|
962
|
+
const yaml = `
|
|
963
|
+
name: blocklist_test
|
|
964
|
+
version: "1.0.0"
|
|
965
|
+
domain_blocklist:
|
|
966
|
+
- "evil.com"
|
|
967
|
+
- "metadata.google.internal"
|
|
968
|
+
rules:
|
|
969
|
+
- name: "allow-all"
|
|
970
|
+
effect: ALLOW
|
|
971
|
+
priority: 1
|
|
972
|
+
conditions: {}
|
|
973
|
+
`;
|
|
974
|
+
const packPath = writeTmpYaml('blocklist.yaml', yaml);
|
|
975
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
976
|
+
const evilCall = buildToolCall({ args: { url: 'https://evil.com/steal', method: 'GET' } });
|
|
977
|
+
const result = engine.evaluate(evilCall);
|
|
978
|
+
expect(result.decision).toBe('deny');
|
|
979
|
+
expect(result.rule_name).toBe('__pack_domain_blocklist');
|
|
980
|
+
expect(result.reasons[0]).toContain('evil.com');
|
|
981
|
+
});
|
|
982
|
+
it('should DENY when domain is not in pack-level domain_allowlist', () => {
|
|
983
|
+
const yaml = `
|
|
984
|
+
name: allowlist_test
|
|
985
|
+
version: "1.0.0"
|
|
986
|
+
domain_allowlist:
|
|
987
|
+
- "api.github.com"
|
|
988
|
+
- "api.slack.com"
|
|
989
|
+
rules:
|
|
990
|
+
- name: "allow-all"
|
|
991
|
+
effect: ALLOW
|
|
992
|
+
priority: 1
|
|
993
|
+
conditions: {}
|
|
994
|
+
`;
|
|
995
|
+
const packPath = writeTmpYaml('allowlist.yaml', yaml);
|
|
996
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
997
|
+
// Allowed domain
|
|
998
|
+
const githubCall = buildToolCall({ args: { url: 'https://api.github.com/repos', method: 'GET' } });
|
|
999
|
+
expect(engine.evaluate(githubCall).decision).toBe('allow');
|
|
1000
|
+
// Not in allowlist
|
|
1001
|
+
const unknownCall = buildToolCall({ args: { url: 'https://unknown.io/api', method: 'GET' } });
|
|
1002
|
+
const result = engine.evaluate(unknownCall);
|
|
1003
|
+
expect(result.decision).toBe('deny');
|
|
1004
|
+
expect(result.rule_name).toBe('__pack_domain_allowlist');
|
|
1005
|
+
});
|
|
1006
|
+
it('should ALLOW tool calls without URL even when domain_allowlist is set', () => {
|
|
1007
|
+
const yaml = `
|
|
1008
|
+
name: no_url_allowlist_test
|
|
1009
|
+
version: "1.0.0"
|
|
1010
|
+
domain_allowlist:
|
|
1011
|
+
- "api.github.com"
|
|
1012
|
+
rules:
|
|
1013
|
+
- name: "allow-all"
|
|
1014
|
+
effect: ALLOW
|
|
1015
|
+
priority: 1
|
|
1016
|
+
conditions: {}
|
|
1017
|
+
`;
|
|
1018
|
+
const packPath = writeTmpYaml('no-url-allowlist.yaml', yaml);
|
|
1019
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
1020
|
+
// No URL in args -- domain checks should be skipped
|
|
1021
|
+
const noUrlCall = buildToolCall({ args: { method: 'GET' } });
|
|
1022
|
+
expect(engine.evaluate(noUrlCall).decision).toBe('allow');
|
|
1023
|
+
});
|
|
1024
|
+
it('should support wildcard domains in pack-level blocklist', () => {
|
|
1025
|
+
const yaml = `
|
|
1026
|
+
name: wildcard_blocklist_test
|
|
1027
|
+
version: "1.0.0"
|
|
1028
|
+
domain_blocklist:
|
|
1029
|
+
- "*.evil.com"
|
|
1030
|
+
rules:
|
|
1031
|
+
- name: "allow-all"
|
|
1032
|
+
effect: ALLOW
|
|
1033
|
+
priority: 1
|
|
1034
|
+
conditions: {}
|
|
1035
|
+
`;
|
|
1036
|
+
const packPath = writeTmpYaml('wildcard-blocklist.yaml', yaml);
|
|
1037
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
1038
|
+
const subdomainCall = buildToolCall({ args: { url: 'https://api.evil.com/steal', method: 'GET' } });
|
|
1039
|
+
expect(engine.evaluate(subdomainCall).decision).toBe('deny');
|
|
1040
|
+
// The root domain itself should also match (*.evil.com -> evil.com)
|
|
1041
|
+
const rootCall = buildToolCall({ args: { url: 'https://evil.com/steal', method: 'GET' } });
|
|
1042
|
+
expect(engine.evaluate(rootCall).decision).toBe('deny');
|
|
1043
|
+
});
|
|
1044
|
+
it('should check blocklist before allowlist', () => {
|
|
1045
|
+
const yaml = `
|
|
1046
|
+
name: blocklist_before_allowlist_test
|
|
1047
|
+
version: "1.0.0"
|
|
1048
|
+
domain_allowlist:
|
|
1049
|
+
- "*.example.com"
|
|
1050
|
+
domain_blocklist:
|
|
1051
|
+
- "bad.example.com"
|
|
1052
|
+
rules:
|
|
1053
|
+
- name: "allow-all"
|
|
1054
|
+
effect: ALLOW
|
|
1055
|
+
priority: 1
|
|
1056
|
+
conditions: {}
|
|
1057
|
+
`;
|
|
1058
|
+
const packPath = writeTmpYaml('blocklist-first.yaml', yaml);
|
|
1059
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
1060
|
+
// Domain is in both allowlist (via wildcard) and blocklist -- blocklist wins
|
|
1061
|
+
const badCall = buildToolCall({ args: { url: 'https://bad.example.com/data', method: 'GET' } });
|
|
1062
|
+
expect(engine.evaluate(badCall).decision).toBe('deny');
|
|
1063
|
+
expect(engine.evaluate(badCall).rule_name).toBe('__pack_domain_blocklist');
|
|
1064
|
+
// Good subdomain is fine
|
|
1065
|
+
const goodCall = buildToolCall({ args: { url: 'https://good.example.com/data', method: 'GET' } });
|
|
1066
|
+
expect(engine.evaluate(goodCall).decision).toBe('allow');
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
// -----------------------------------------------------------------------
|
|
1070
|
+
// evaluate() -- Rule-level domain_blocklist
|
|
1071
|
+
// -----------------------------------------------------------------------
|
|
1072
|
+
describe('evaluate - rule-level domain_blocklist', () => {
|
|
1073
|
+
it('should not match rule when call domain is in rule-level domain_blocklist', () => {
|
|
1074
|
+
const yaml = `
|
|
1075
|
+
name: rule_blocklist_test
|
|
1076
|
+
version: "1.0.0"
|
|
1077
|
+
rules:
|
|
1078
|
+
- name: "allow-gets-except-bad"
|
|
1079
|
+
effect: ALLOW
|
|
1080
|
+
priority: 1
|
|
1081
|
+
conditions:
|
|
1082
|
+
methods:
|
|
1083
|
+
- "GET"
|
|
1084
|
+
domain_blocklist:
|
|
1085
|
+
- "bad.example.com"
|
|
1086
|
+
- name: "deny-all"
|
|
1087
|
+
effect: DENY
|
|
1088
|
+
priority: 100
|
|
1089
|
+
conditions: {}
|
|
1090
|
+
`;
|
|
1091
|
+
const packPath = writeTmpYaml('rule-blocklist.yaml', yaml);
|
|
1092
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
1093
|
+
const badCall = buildToolCall({ args: { url: 'https://bad.example.com/data', method: 'GET' } });
|
|
1094
|
+
expect(engine.evaluate(badCall).decision).toBe('deny');
|
|
1095
|
+
expect(engine.evaluate(badCall).rule_name).toBe('deny-all');
|
|
1096
|
+
const goodCall = buildToolCall({ args: { url: 'https://good.example.com/data', method: 'GET' } });
|
|
1097
|
+
expect(engine.evaluate(goodCall).decision).toBe('allow');
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
// -----------------------------------------------------------------------
|
|
1101
|
+
// reload()
|
|
1102
|
+
// -----------------------------------------------------------------------
|
|
1103
|
+
describe('reload', () => {
|
|
1104
|
+
it('should reload policy pack from disk reflecting file changes', () => {
|
|
1105
|
+
const yaml1 = `
|
|
1106
|
+
name: reload_v1
|
|
1107
|
+
version: "1.0.0"
|
|
1108
|
+
rules:
|
|
1109
|
+
- name: "deny-all"
|
|
1110
|
+
effect: DENY
|
|
1111
|
+
priority: 1
|
|
1112
|
+
conditions: {}
|
|
1113
|
+
`;
|
|
1114
|
+
const packPath = writeTmpYaml('reload.yaml', yaml1);
|
|
1115
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
1116
|
+
expect(engine.getPack().name).toBe('reload_v1');
|
|
1117
|
+
expect(engine.evaluate(buildToolCall()).decision).toBe('deny');
|
|
1118
|
+
// Overwrite the file with a new pack
|
|
1119
|
+
const yaml2 = `
|
|
1120
|
+
name: reload_v2
|
|
1121
|
+
version: "2.0.0"
|
|
1122
|
+
rules:
|
|
1123
|
+
- name: "allow-all"
|
|
1124
|
+
effect: ALLOW
|
|
1125
|
+
priority: 1
|
|
1126
|
+
conditions: {}
|
|
1127
|
+
`;
|
|
1128
|
+
fs.writeFileSync(packPath, yaml2, 'utf-8');
|
|
1129
|
+
engine.reload();
|
|
1130
|
+
expect(engine.getPack().name).toBe('reload_v2');
|
|
1131
|
+
expect(engine.evaluate(buildToolCall()).decision).toBe('allow');
|
|
1132
|
+
});
|
|
1133
|
+
it('should throw if the reloaded file is invalid', () => {
|
|
1134
|
+
const validYaml = `
|
|
1135
|
+
name: reload_valid
|
|
1136
|
+
version: "1.0.0"
|
|
1137
|
+
rules:
|
|
1138
|
+
- name: "allow-all"
|
|
1139
|
+
effect: ALLOW
|
|
1140
|
+
priority: 1
|
|
1141
|
+
conditions: {}
|
|
1142
|
+
`;
|
|
1143
|
+
const packPath = writeTmpYaml('reload-invalid.yaml', validYaml);
|
|
1144
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
1145
|
+
expect(engine.getPack().name).toBe('reload_valid');
|
|
1146
|
+
// Overwrite with invalid YAML
|
|
1147
|
+
fs.writeFileSync(packPath, 'just a scalar string', 'utf-8');
|
|
1148
|
+
expect(() => engine.reload()).toThrow(/does not contain a valid YAML object/);
|
|
1149
|
+
// The original pack should still be loaded (reload failed, old pack retained)
|
|
1150
|
+
// Actually, per the implementation, reload() throws, so the internal state
|
|
1151
|
+
// stays as-is because the assignment only happens after loadPack succeeds.
|
|
1152
|
+
expect(engine.getPack().name).toBe('reload_valid');
|
|
1153
|
+
});
|
|
1154
|
+
});
|
|
1155
|
+
// -----------------------------------------------------------------------
|
|
1156
|
+
// fromPack() -- Static Factory
|
|
1157
|
+
// -----------------------------------------------------------------------
|
|
1158
|
+
describe('fromPack', () => {
|
|
1159
|
+
it('should create a working engine from an in-memory PolicyPack', () => {
|
|
1160
|
+
const pack = {
|
|
1161
|
+
name: 'in_memory_pack',
|
|
1162
|
+
version: '1.0.0',
|
|
1163
|
+
rules: [
|
|
1164
|
+
{
|
|
1165
|
+
name: 'allow-reads',
|
|
1166
|
+
effect: 'ALLOW',
|
|
1167
|
+
priority: 10,
|
|
1168
|
+
conditions: { capabilities: ['read'] },
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
name: 'deny-all',
|
|
1172
|
+
effect: 'DENY',
|
|
1173
|
+
priority: 100,
|
|
1174
|
+
conditions: {},
|
|
1175
|
+
},
|
|
1176
|
+
],
|
|
1177
|
+
};
|
|
1178
|
+
const engine = engine_1.PolicyEngine.fromPack(pack);
|
|
1179
|
+
const loaded = engine.getPack();
|
|
1180
|
+
expect(loaded.name).toBe('in_memory_pack');
|
|
1181
|
+
expect(loaded.version).toBe('1.0.0');
|
|
1182
|
+
expect(loaded.rules).toHaveLength(2);
|
|
1183
|
+
});
|
|
1184
|
+
it('should throw on an invalid pack', () => {
|
|
1185
|
+
const badPack = {
|
|
1186
|
+
name: '',
|
|
1187
|
+
version: '1.0.0',
|
|
1188
|
+
rules: [{ name: 'r', effect: 'ALLOW', conditions: {} }],
|
|
1189
|
+
};
|
|
1190
|
+
expect(() => engine_1.PolicyEngine.fromPack(badPack)).toThrow(/Invalid policy pack/);
|
|
1191
|
+
});
|
|
1192
|
+
it('should evaluate rules correctly', () => {
|
|
1193
|
+
const pack = {
|
|
1194
|
+
name: 'eval_test',
|
|
1195
|
+
version: '1.0.0',
|
|
1196
|
+
rules: [
|
|
1197
|
+
{
|
|
1198
|
+
name: 'allow-reads',
|
|
1199
|
+
effect: 'ALLOW',
|
|
1200
|
+
priority: 10,
|
|
1201
|
+
conditions: { capabilities: ['read'] },
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
name: 'deny-all',
|
|
1205
|
+
effect: 'DENY',
|
|
1206
|
+
priority: 100,
|
|
1207
|
+
conditions: {},
|
|
1208
|
+
},
|
|
1209
|
+
],
|
|
1210
|
+
};
|
|
1211
|
+
const engine = engine_1.PolicyEngine.fromPack(pack);
|
|
1212
|
+
const readCall = buildToolCall({ tool: { name: 'http.request', capability: 'read' } });
|
|
1213
|
+
expect(engine.evaluate(readCall).decision).toBe('allow');
|
|
1214
|
+
const writeCall = buildToolCall({ tool: { name: 'http.request', capability: 'write' } });
|
|
1215
|
+
expect(engine.evaluate(writeCall).decision).toBe('deny');
|
|
1216
|
+
});
|
|
1217
|
+
it('should sort rules by priority', () => {
|
|
1218
|
+
const pack = {
|
|
1219
|
+
name: 'sort_test',
|
|
1220
|
+
version: '1.0.0',
|
|
1221
|
+
rules: [
|
|
1222
|
+
{ name: 'low-priority', effect: 'DENY', priority: 50, conditions: {} },
|
|
1223
|
+
{ name: 'high-priority', effect: 'ALLOW', priority: 5, conditions: {} },
|
|
1224
|
+
{ name: 'medium-priority', effect: 'DENY', priority: 25, conditions: {} },
|
|
1225
|
+
],
|
|
1226
|
+
};
|
|
1227
|
+
const engine = engine_1.PolicyEngine.fromPack(pack);
|
|
1228
|
+
const loaded = engine.getPack();
|
|
1229
|
+
expect(loaded.rules[0].name).toBe('high-priority');
|
|
1230
|
+
expect(loaded.rules[1].name).toBe('medium-priority');
|
|
1231
|
+
expect(loaded.rules[2].name).toBe('low-priority');
|
|
1232
|
+
});
|
|
1233
|
+
it('should not mutate the original pack', () => {
|
|
1234
|
+
const pack = {
|
|
1235
|
+
name: 'no_mutate_test',
|
|
1236
|
+
version: '1.0.0',
|
|
1237
|
+
rules: [
|
|
1238
|
+
{ name: 'z-rule', effect: 'DENY', priority: 50, conditions: {} },
|
|
1239
|
+
{ name: 'a-rule', effect: 'ALLOW', priority: 5, conditions: {} },
|
|
1240
|
+
],
|
|
1241
|
+
};
|
|
1242
|
+
const originalFirstRule = pack.rules[0].name;
|
|
1243
|
+
engine_1.PolicyEngine.fromPack(pack);
|
|
1244
|
+
// Original pack should be unchanged
|
|
1245
|
+
expect(pack.rules[0].name).toBe(originalFirstRule);
|
|
1246
|
+
});
|
|
1247
|
+
it('should respect the defaultEffect parameter', () => {
|
|
1248
|
+
const pack = {
|
|
1249
|
+
name: 'default_effect_test',
|
|
1250
|
+
version: '1.0.0',
|
|
1251
|
+
rules: [
|
|
1252
|
+
{
|
|
1253
|
+
name: 'only-specific',
|
|
1254
|
+
effect: 'DENY',
|
|
1255
|
+
priority: 1,
|
|
1256
|
+
conditions: { tools: ['very.specific.tool'] },
|
|
1257
|
+
},
|
|
1258
|
+
],
|
|
1259
|
+
};
|
|
1260
|
+
const engine = engine_1.PolicyEngine.fromPack(pack, 'ALLOW');
|
|
1261
|
+
const result = engine.evaluate(buildToolCall());
|
|
1262
|
+
expect(result.decision).toBe('allow');
|
|
1263
|
+
expect(result.rule_name).toBe('__default');
|
|
1264
|
+
});
|
|
1265
|
+
it('should support domain_allowlist and domain_blocklist', () => {
|
|
1266
|
+
const pack = {
|
|
1267
|
+
name: 'domain_list_test',
|
|
1268
|
+
version: '1.0.0',
|
|
1269
|
+
domain_blocklist: ['evil.com'],
|
|
1270
|
+
domain_allowlist: ['api.github.com'],
|
|
1271
|
+
rules: [
|
|
1272
|
+
{ name: 'allow-all', effect: 'ALLOW', priority: 1, conditions: {} },
|
|
1273
|
+
],
|
|
1274
|
+
};
|
|
1275
|
+
const engine = engine_1.PolicyEngine.fromPack(pack);
|
|
1276
|
+
const evilCall = buildToolCall({ args: { url: 'https://evil.com/steal', method: 'GET' } });
|
|
1277
|
+
expect(engine.evaluate(evilCall).decision).toBe('deny');
|
|
1278
|
+
const githubCall = buildToolCall({ args: { url: 'https://api.github.com/repos', method: 'GET' } });
|
|
1279
|
+
expect(engine.evaluate(githubCall).decision).toBe('allow');
|
|
1280
|
+
const unknownCall = buildToolCall({ args: { url: 'https://unknown.io/api', method: 'GET' } });
|
|
1281
|
+
expect(engine.evaluate(unknownCall).decision).toBe('deny');
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
// -----------------------------------------------------------------------
|
|
1285
|
+
// getPack()
|
|
1286
|
+
// -----------------------------------------------------------------------
|
|
1287
|
+
describe('getPack', () => {
|
|
1288
|
+
it('should return the currently loaded policy pack', () => {
|
|
1289
|
+
const packPath = writeTmpYaml('getpack.yaml', VALID_PACK_YAML);
|
|
1290
|
+
const engine = new engine_1.PolicyEngine(packPath);
|
|
1291
|
+
const pack = engine.getPack();
|
|
1292
|
+
expect(pack).toBeDefined();
|
|
1293
|
+
expect(pack.name).toBe('test_pack');
|
|
1294
|
+
expect(pack.version).toBe('1.0.0');
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
// -----------------------------------------------------------------------
|
|
1298
|
+
// SSRF Bypass Tests (IPv6 variants)
|
|
1299
|
+
// -----------------------------------------------------------------------
|
|
1300
|
+
describe('SSRF protection - IPv6 bypass vectors', () => {
|
|
1301
|
+
// Use a policy with a domain_allowlist to activate SSRF protection
|
|
1302
|
+
const ssrfPack = {
|
|
1303
|
+
name: 'ssrf_test',
|
|
1304
|
+
version: '1.0.0',
|
|
1305
|
+
domain_allowlist: ['api.github.com', 'api.slack.com'],
|
|
1306
|
+
rules: [
|
|
1307
|
+
{
|
|
1308
|
+
name: 'allow-all',
|
|
1309
|
+
effect: 'ALLOW',
|
|
1310
|
+
priority: 1,
|
|
1311
|
+
conditions: {},
|
|
1312
|
+
},
|
|
1313
|
+
],
|
|
1314
|
+
};
|
|
1315
|
+
it('should deny IPv6 unspecified address (::)', () => {
|
|
1316
|
+
const engine = engine_1.PolicyEngine.fromPack(ssrfPack);
|
|
1317
|
+
const tc = buildToolCall({ args: { url: 'http://[::]/secret', method: 'GET' } });
|
|
1318
|
+
const result = engine.evaluate(tc);
|
|
1319
|
+
expect(result.decision).toBe('deny');
|
|
1320
|
+
expect(result.rule_name).toBe('__ssrf_protection');
|
|
1321
|
+
});
|
|
1322
|
+
it('should deny IPv6 documentation range (2001:db8::1)', () => {
|
|
1323
|
+
const engine = engine_1.PolicyEngine.fromPack(ssrfPack);
|
|
1324
|
+
const tc = buildToolCall({ args: { url: 'http://[2001:db8::1]/data', method: 'GET' } });
|
|
1325
|
+
const result = engine.evaluate(tc);
|
|
1326
|
+
expect(result.decision).toBe('deny');
|
|
1327
|
+
expect(result.rule_name).toBe('__ssrf_protection');
|
|
1328
|
+
});
|
|
1329
|
+
it('should deny IPv4-mapped IPv6 loopback (::ffff:127.0.0.1)', () => {
|
|
1330
|
+
const engine = engine_1.PolicyEngine.fromPack(ssrfPack);
|
|
1331
|
+
const tc = buildToolCall({ args: { url: 'http://[::ffff:127.0.0.1]/internal', method: 'GET' } });
|
|
1332
|
+
const result = engine.evaluate(tc);
|
|
1333
|
+
expect(result.decision).toBe('deny');
|
|
1334
|
+
expect(result.rule_name).toBe('__ssrf_protection');
|
|
1335
|
+
});
|
|
1336
|
+
it('should deny IPv4-mapped IPv6 private address (::ffff:10.0.0.1)', () => {
|
|
1337
|
+
const engine = engine_1.PolicyEngine.fromPack(ssrfPack);
|
|
1338
|
+
const tc = buildToolCall({ args: { url: 'http://[::ffff:10.0.0.1]/internal', method: 'GET' } });
|
|
1339
|
+
const result = engine.evaluate(tc);
|
|
1340
|
+
expect(result.decision).toBe('deny');
|
|
1341
|
+
expect(result.rule_name).toBe('__ssrf_protection');
|
|
1342
|
+
});
|
|
1343
|
+
});
|
|
1344
|
+
});
|
|
1345
|
+
//# sourceMappingURL=policy-engine.test.js.map
|