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,790 @@
|
|
|
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
|
+
exports.OPAEngine = void 0;
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
const https = __importStar(require("https"));
|
|
39
|
+
const url_1 = require("url");
|
|
40
|
+
class OPAEngine {
|
|
41
|
+
constructor(config) {
|
|
42
|
+
// Circuit breaker state for remote OPA calls
|
|
43
|
+
this.circuitState = 'closed';
|
|
44
|
+
this.consecutiveFailures = 0;
|
|
45
|
+
this.lastFailureTime = 0;
|
|
46
|
+
this.failureThreshold = 3;
|
|
47
|
+
this.recoveryTimeMs = 30000; // 30s cooldown
|
|
48
|
+
this.config = {
|
|
49
|
+
policy_path: 'v1/data/palaryn/policy',
|
|
50
|
+
timeout_ms: 5000,
|
|
51
|
+
fallback_decision: 'deny',
|
|
52
|
+
package_name: 'palaryn.policy',
|
|
53
|
+
...config,
|
|
54
|
+
};
|
|
55
|
+
// Validate and normalize server_url if provided
|
|
56
|
+
if (this.config.server_url) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = new url_1.URL(this.config.server_url);
|
|
59
|
+
// In production, require HTTPS to prevent credential exposure
|
|
60
|
+
if (process.env.NODE_ENV === 'production' && parsed.protocol !== 'https:') {
|
|
61
|
+
throw new Error(`OPA server URL must use HTTPS in production (got "${parsed.protocol}")`);
|
|
62
|
+
}
|
|
63
|
+
// Block private/reserved IP addresses to prevent SSRF via config
|
|
64
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
65
|
+
if (this.isPrivateOPAHost(hostname)) {
|
|
66
|
+
// In production, block private IPs entirely
|
|
67
|
+
if (process.env.NODE_ENV === 'production') {
|
|
68
|
+
throw new Error(`OPA server URL must not use private/reserved IP address in production: "${hostname}"`);
|
|
69
|
+
}
|
|
70
|
+
// In development, allow but warn
|
|
71
|
+
console.warn(`[OPAEngine] WARNING: OPA server URL points to private address "${hostname}". This would be blocked in production.`);
|
|
72
|
+
}
|
|
73
|
+
// Normalize: remove trailing slashes for consistency
|
|
74
|
+
this.config.server_url = parsed.origin + parsed.pathname.replace(/\/+$/, '');
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err instanceof Error && (err.message.includes('HTTPS in production') || err.message.includes('private/reserved IP'))) {
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`Invalid OPA server URL: "${this.config.server_url}". Must be a valid URL (e.g., "http://localhost:8181").`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Evaluate a ToolCall against OPA policies.
|
|
86
|
+
* If server_url is configured, makes HTTP POST to OPA server.
|
|
87
|
+
* If rego_policy is configured (inline), evaluates locally.
|
|
88
|
+
*/
|
|
89
|
+
async evaluate(toolCall) {
|
|
90
|
+
if (this.config.server_url) {
|
|
91
|
+
// Circuit breaker check for remote OPA
|
|
92
|
+
if (this.circuitState === 'open') {
|
|
93
|
+
if (Date.now() - this.lastFailureTime > this.recoveryTimeMs) {
|
|
94
|
+
// Try half-open: allow one request through
|
|
95
|
+
this.circuitState = 'half-open';
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Circuit open: immediate fallback, no HTTP call
|
|
99
|
+
return this.fallbackResult('OPA circuit breaker open: too many consecutive failures');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return this.evaluateRemote(toolCall);
|
|
103
|
+
}
|
|
104
|
+
if (this.config.rego_policy) {
|
|
105
|
+
return this.evaluateLocal(toolCall);
|
|
106
|
+
}
|
|
107
|
+
// No OPA configured, return fallback
|
|
108
|
+
return this.fallbackResult('OPA not configured: no server_url or rego_policy provided');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Evaluate against remote OPA server via REST API.
|
|
112
|
+
* POST ${server_url}/${policy_path} with { input: toolCall }
|
|
113
|
+
*/
|
|
114
|
+
async evaluateRemote(toolCall) {
|
|
115
|
+
const url = `${this.config.server_url}/${this.config.policy_path}`;
|
|
116
|
+
const input = this.buildInput(toolCall);
|
|
117
|
+
try {
|
|
118
|
+
const response = await this.httpPost(url, { input }, this.config.timeout_ms);
|
|
119
|
+
const result = this.parseOPAResponse(response, toolCall);
|
|
120
|
+
// Success: reset circuit breaker
|
|
121
|
+
this.onSuccess();
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// Failure: update circuit breaker
|
|
126
|
+
this.onFailure();
|
|
127
|
+
// OPA unreachable -- use fallback
|
|
128
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
129
|
+
return this.fallbackResult(`OPA server error: ${message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Evaluate inline Rego policy locally using a simple rule evaluator.
|
|
134
|
+
* This is a lightweight Rego subset evaluator -- NOT a full Rego runtime.
|
|
135
|
+
* Supports basic patterns that cover 80% of policy use cases:
|
|
136
|
+
* - package declaration
|
|
137
|
+
* - default decision rules (default decision = "deny")
|
|
138
|
+
* - Simple condition-based rules with equality checks
|
|
139
|
+
* - Array membership (input.tool.capability == "admin")
|
|
140
|
+
* - String matching patterns
|
|
141
|
+
*/
|
|
142
|
+
async evaluateLocal(toolCall) {
|
|
143
|
+
const input = this.buildInput(toolCall);
|
|
144
|
+
try {
|
|
145
|
+
const result = this.evaluateRegoSubset(this.config.rego_policy, input);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
150
|
+
return this.fallbackResult(`Rego evaluation error: ${message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Lightweight Rego subset evaluator.
|
|
155
|
+
* Parses basic Rego rules and evaluates them against input.
|
|
156
|
+
*
|
|
157
|
+
* Supported Rego patterns:
|
|
158
|
+
* ```rego
|
|
159
|
+
* package palaryn.policy
|
|
160
|
+
*
|
|
161
|
+
* default decision = "deny"
|
|
162
|
+
* default rule_id = "opa_local"
|
|
163
|
+
* default rule_name = "OPA local evaluation"
|
|
164
|
+
*
|
|
165
|
+
* decision = "allow" {
|
|
166
|
+
* input.tool.capability == "read"
|
|
167
|
+
* }
|
|
168
|
+
*
|
|
169
|
+
* decision = "deny" {
|
|
170
|
+
* input.tool.name == "dangerous"
|
|
171
|
+
* }
|
|
172
|
+
*
|
|
173
|
+
* reasons[reason] {
|
|
174
|
+
* input.tool.capability == "admin"
|
|
175
|
+
* reason := "Admin capability requires approval"
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
evaluateRegoSubset(policy, input) {
|
|
180
|
+
const lines = policy.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#') && !l.startsWith('import'));
|
|
181
|
+
// Remove package declaration
|
|
182
|
+
const contentLines = lines.filter(l => !l.startsWith('package '));
|
|
183
|
+
// Parse default values
|
|
184
|
+
const defaults = {};
|
|
185
|
+
// Parse rules (blocks of the form: head = "value" { conditions })
|
|
186
|
+
const rules = [];
|
|
187
|
+
// Parse reasons rules (reasons[reason] { ... reason := "..." })
|
|
188
|
+
const reasonRules = [];
|
|
189
|
+
// Join into one string to handle multi-line blocks
|
|
190
|
+
const joined = contentLines.join('\n');
|
|
191
|
+
// Parse defaults: default <field> = "<value>"
|
|
192
|
+
const defaultRegex = /default\s+(\w+)\s*=\s*"([^"]*)"/g;
|
|
193
|
+
let defaultMatch;
|
|
194
|
+
while ((defaultMatch = defaultRegex.exec(joined)) !== null) {
|
|
195
|
+
defaults[defaultMatch[1]] = defaultMatch[2];
|
|
196
|
+
}
|
|
197
|
+
// Parse rule blocks using brace-depth-aware extraction
|
|
198
|
+
const ruleBlocks = this.extractBraceBlocks(joined);
|
|
199
|
+
for (const block of ruleBlocks) {
|
|
200
|
+
// Check if this is a reasons collection rule
|
|
201
|
+
if (block.head.startsWith('reasons[reason]') || block.head.startsWith('reasons[')) {
|
|
202
|
+
const bodyLines = block.body.split('\n').map(l => l.trim()).filter(l => l);
|
|
203
|
+
const conditions = [];
|
|
204
|
+
let reason = '';
|
|
205
|
+
for (const line of bodyLines) {
|
|
206
|
+
const assignMatch = line.match(/^reason\s*:=\s*"([^"]*)"$/);
|
|
207
|
+
if (assignMatch) {
|
|
208
|
+
reason = assignMatch[1];
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
conditions.push(line);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (reason) {
|
|
215
|
+
reasonRules.push({ conditions, reason });
|
|
216
|
+
}
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
// Parse field = "value" from the head
|
|
220
|
+
const headMatch = block.head.match(/^(\w+)\s*=\s*"([^"]*)"$/);
|
|
221
|
+
if (!headMatch)
|
|
222
|
+
continue;
|
|
223
|
+
const field = headMatch[1];
|
|
224
|
+
const value = headMatch[2];
|
|
225
|
+
const conditions = block.body
|
|
226
|
+
.split('\n')
|
|
227
|
+
.map(l => l.trim())
|
|
228
|
+
.filter(l => l && !l.startsWith('#') && !l.startsWith('reason'));
|
|
229
|
+
rules.push({ field, value, conditions });
|
|
230
|
+
}
|
|
231
|
+
// Wrap input for lookups (OPA uses input.X paths)
|
|
232
|
+
const evalContext = { input };
|
|
233
|
+
// Evaluate rules against input. First matching rule wins for each field.
|
|
234
|
+
const resolved = { ...defaults };
|
|
235
|
+
for (const rule of rules) {
|
|
236
|
+
// Only override if not already resolved by a higher-priority rule
|
|
237
|
+
// (rules are evaluated in order; first match wins for each field)
|
|
238
|
+
if (resolved[rule.field] !== undefined && resolved[rule.field] !== defaults[rule.field]) {
|
|
239
|
+
// Already resolved by a prior matching rule -- skip unless this is a different field
|
|
240
|
+
// Actually, in Rego, later matching rules can override. But for our subset,
|
|
241
|
+
// we want first-match-wins to match common policy patterns.
|
|
242
|
+
// However, we still need to check all rules since a deny can override an allow.
|
|
243
|
+
// Let's use last-match-wins to be more Rego-like.
|
|
244
|
+
}
|
|
245
|
+
if (this.evaluateConditions(rule.conditions, evalContext)) {
|
|
246
|
+
resolved[rule.field] = rule.value;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Collect reasons
|
|
250
|
+
const reasons = [];
|
|
251
|
+
for (const rr of reasonRules) {
|
|
252
|
+
if (this.evaluateConditions(rr.conditions, evalContext)) {
|
|
253
|
+
reasons.push(rr.reason);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const decision = this.normalizeDecision(resolved['decision']);
|
|
257
|
+
const ruleId = resolved['rule_id'] || 'opa_local';
|
|
258
|
+
const ruleName = resolved['rule_name'] || 'OPA local evaluation';
|
|
259
|
+
if (reasons.length === 0) {
|
|
260
|
+
reasons.push(`OPA local decision: ${decision}`);
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
decision,
|
|
264
|
+
rule_id: ruleId,
|
|
265
|
+
rule_name: ruleName,
|
|
266
|
+
reasons,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Evaluate a list of Rego conditions against the evaluation context.
|
|
271
|
+
* All conditions must be true (AND logic) for the rule to match.
|
|
272
|
+
*
|
|
273
|
+
* Supported condition patterns:
|
|
274
|
+
* - input.field.path == "value" (equality)
|
|
275
|
+
* - input.field.path != "value" (inequality)
|
|
276
|
+
* - input.field.path >= / <= / > / < value (numeric comparisons)
|
|
277
|
+
* - input.field.path == true/false (boolean)
|
|
278
|
+
* - input.field.path == 123 (number)
|
|
279
|
+
* - startswith(input.field, "prefix")
|
|
280
|
+
* - endswith(input.field, "suffix")
|
|
281
|
+
* - contains(input.field, "substring")
|
|
282
|
+
* - count(input.field) op value (array/object/string length)
|
|
283
|
+
* - regex.match("pattern", input.field) (regex matching)
|
|
284
|
+
* - is_string/is_number/is_boolean/is_array(input.field) (type checks)
|
|
285
|
+
* - "value" in input.field (array membership)
|
|
286
|
+
* - some x in input.field; x == "value" (iteration with existential check)
|
|
287
|
+
* - input.field.path (truthy check)
|
|
288
|
+
* - not input.field.path (falsy check)
|
|
289
|
+
*/
|
|
290
|
+
evaluateConditions(conditions, context) {
|
|
291
|
+
if (conditions.length === 0)
|
|
292
|
+
return true;
|
|
293
|
+
// Expand semicolon-separated conditions into individual conditions
|
|
294
|
+
const expanded = [];
|
|
295
|
+
for (const cond of conditions) {
|
|
296
|
+
if (cond.includes(';')) {
|
|
297
|
+
expanded.push(...cond.split(';').map(c => c.trim()).filter(c => c));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
expanded.push(cond);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Separate `some` iteration blocks from normal conditions.
|
|
304
|
+
// `some x in collection` introduces an existential quantifier:
|
|
305
|
+
// subsequent conditions referencing `x` must hold for at least one element.
|
|
306
|
+
const normalConditions = [];
|
|
307
|
+
const someBlocks = [];
|
|
308
|
+
let currentSome = null;
|
|
309
|
+
for (const cond of expanded) {
|
|
310
|
+
const someMatch = cond.match(/^some\s+(\w+)\s+in\s+([\w.[\]]+)$/);
|
|
311
|
+
if (someMatch) {
|
|
312
|
+
// Finalize any previous some block
|
|
313
|
+
if (currentSome) {
|
|
314
|
+
someBlocks.push(currentSome);
|
|
315
|
+
}
|
|
316
|
+
currentSome = { varName: someMatch[1], collectionPath: someMatch[2], subConditions: [] };
|
|
317
|
+
}
|
|
318
|
+
else if (currentSome && this.conditionReferencesVar(cond, currentSome.varName)) {
|
|
319
|
+
// This condition references the some variable -- attach to current block
|
|
320
|
+
currentSome.subConditions.push(cond);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// Finalize any pending some block before adding a normal condition
|
|
324
|
+
if (currentSome) {
|
|
325
|
+
someBlocks.push(currentSome);
|
|
326
|
+
currentSome = null;
|
|
327
|
+
}
|
|
328
|
+
normalConditions.push(cond);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (currentSome) {
|
|
332
|
+
someBlocks.push(currentSome);
|
|
333
|
+
}
|
|
334
|
+
// Evaluate normal conditions (all must pass -- AND logic)
|
|
335
|
+
for (const cond of normalConditions) {
|
|
336
|
+
if (!this.evaluateSingleCondition(cond, context)) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Evaluate some blocks (existential: at least one element must satisfy all sub-conditions)
|
|
341
|
+
for (const block of someBlocks) {
|
|
342
|
+
const collection = this.resolveValue(block.collectionPath, context);
|
|
343
|
+
if (!Array.isArray(collection))
|
|
344
|
+
return false;
|
|
345
|
+
let anyMatch = false;
|
|
346
|
+
for (const element of collection) {
|
|
347
|
+
// Bind the iteration variable in a local context
|
|
348
|
+
const localContext = { ...context, [block.varName]: element };
|
|
349
|
+
const allSubCondsMet = block.subConditions.every(c => this.evaluateSingleCondition(c, localContext));
|
|
350
|
+
if (allSubCondsMet) {
|
|
351
|
+
anyMatch = true;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (!anyMatch)
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Check whether a condition string references a given variable name.
|
|
362
|
+
* Uses word-boundary detection to avoid false positives (e.g., "xray" matching "x").
|
|
363
|
+
*/
|
|
364
|
+
conditionReferencesVar(condition, varName) {
|
|
365
|
+
const regex = new RegExp(`(?:^|[^\\w.])${varName}(?:[^\\w]|$)`);
|
|
366
|
+
return regex.test(condition);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Evaluate a single Rego condition.
|
|
370
|
+
*/
|
|
371
|
+
evaluateSingleCondition(condition, context) {
|
|
372
|
+
const trimmed = condition.trim();
|
|
373
|
+
if (!trimmed)
|
|
374
|
+
return true;
|
|
375
|
+
// Equality: input.path == "value"
|
|
376
|
+
const eqMatch = trimmed.match(/^([\w.[\]]+)\s*==\s*(.+)$/);
|
|
377
|
+
if (eqMatch) {
|
|
378
|
+
const left = this.resolveValue(eqMatch[1], context);
|
|
379
|
+
const right = this.parseRightValue(eqMatch[2].trim());
|
|
380
|
+
return left === right;
|
|
381
|
+
}
|
|
382
|
+
// Inequality: input.path != "value"
|
|
383
|
+
const neqMatch = trimmed.match(/^([\w.[\]]+)\s*!=\s*(.+)$/);
|
|
384
|
+
if (neqMatch) {
|
|
385
|
+
const left = this.resolveValue(neqMatch[1], context);
|
|
386
|
+
const right = this.parseRightValue(neqMatch[2].trim());
|
|
387
|
+
return left !== right;
|
|
388
|
+
}
|
|
389
|
+
// Numeric comparisons: >=, <=, >, <
|
|
390
|
+
const compMatch = trimmed.match(/^([\w.[\]]+)\s*(>=|<=|>|<)\s*(.+)$/);
|
|
391
|
+
if (compMatch) {
|
|
392
|
+
const left = this.resolveValue(compMatch[1], context);
|
|
393
|
+
const right = this.parseRightValue(compMatch[3].trim());
|
|
394
|
+
if (typeof left !== 'number' || typeof right !== 'number')
|
|
395
|
+
return false;
|
|
396
|
+
switch (compMatch[2]) {
|
|
397
|
+
case '>=': return left >= right;
|
|
398
|
+
case '<=': return left <= right;
|
|
399
|
+
case '>': return left > right;
|
|
400
|
+
case '<': return left < right;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// startswith(input.path, "value")
|
|
404
|
+
const startsWithMatch = trimmed.match(/^startswith\(\s*([\w.[\]]+)\s*,\s*"([^"]*)"\s*\)$/);
|
|
405
|
+
if (startsWithMatch) {
|
|
406
|
+
const val = this.resolveValue(startsWithMatch[1], context);
|
|
407
|
+
if (typeof val !== 'string')
|
|
408
|
+
return false;
|
|
409
|
+
return val.startsWith(startsWithMatch[2]);
|
|
410
|
+
}
|
|
411
|
+
// endswith(input.path, "value")
|
|
412
|
+
const endsWithMatch = trimmed.match(/^endswith\(\s*([\w.[\]]+)\s*,\s*"([^"]*)"\s*\)$/);
|
|
413
|
+
if (endsWithMatch) {
|
|
414
|
+
const val = this.resolveValue(endsWithMatch[1], context);
|
|
415
|
+
if (typeof val !== 'string')
|
|
416
|
+
return false;
|
|
417
|
+
return val.endsWith(endsWithMatch[2]);
|
|
418
|
+
}
|
|
419
|
+
// contains(input.path, "value")
|
|
420
|
+
const containsMatch = trimmed.match(/^contains\(\s*([\w.[\]]+)\s*,\s*"([^"]*)"\s*\)$/);
|
|
421
|
+
if (containsMatch) {
|
|
422
|
+
const val = this.resolveValue(containsMatch[1], context);
|
|
423
|
+
if (typeof val !== 'string')
|
|
424
|
+
return false;
|
|
425
|
+
return val.includes(containsMatch[2]);
|
|
426
|
+
}
|
|
427
|
+
// count(input.path) op value -- returns length of array, object keys, or string
|
|
428
|
+
const countMatch = trimmed.match(/^count\(\s*([\w.[\]]+)\s*\)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
|
|
429
|
+
if (countMatch) {
|
|
430
|
+
const val = this.resolveValue(countMatch[1], context);
|
|
431
|
+
const len = Array.isArray(val) ? val.length :
|
|
432
|
+
(typeof val === 'object' && val !== null) ? Object.keys(val).length :
|
|
433
|
+
(typeof val === 'string') ? val.length : 0;
|
|
434
|
+
const right = this.parseRightValue(countMatch[3].trim());
|
|
435
|
+
if (typeof right !== 'number')
|
|
436
|
+
return false;
|
|
437
|
+
switch (countMatch[2]) {
|
|
438
|
+
case '==': return len === right;
|
|
439
|
+
case '!=': return len !== right;
|
|
440
|
+
case '>': return len > right;
|
|
441
|
+
case '<': return len < right;
|
|
442
|
+
case '>=': return len >= right;
|
|
443
|
+
case '<=': return len <= right;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// regex.match("pattern", input.path) -- regex pattern matching
|
|
447
|
+
const regexMatchPattern = trimmed.match(/^regex\.match\(\s*"([^"]*)"\s*,\s*([\w.[\]]+)\s*\)$/);
|
|
448
|
+
if (regexMatchPattern) {
|
|
449
|
+
const pattern = regexMatchPattern[1];
|
|
450
|
+
const val = this.resolveValue(regexMatchPattern[2], context);
|
|
451
|
+
if (typeof val !== 'string')
|
|
452
|
+
return false;
|
|
453
|
+
try {
|
|
454
|
+
const re = new RegExp(pattern);
|
|
455
|
+
return re.test(val);
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Type checks: is_string, is_number, is_boolean, is_array
|
|
462
|
+
const typeCheckMatch = trimmed.match(/^(is_string|is_number|is_boolean|is_array)\(\s*([\w.[\]]+)\s*\)$/);
|
|
463
|
+
if (typeCheckMatch) {
|
|
464
|
+
const val = this.resolveValue(typeCheckMatch[2], context);
|
|
465
|
+
switch (typeCheckMatch[1]) {
|
|
466
|
+
case 'is_string': return typeof val === 'string';
|
|
467
|
+
case 'is_number': return typeof val === 'number';
|
|
468
|
+
case 'is_boolean': return typeof val === 'boolean';
|
|
469
|
+
case 'is_array': return Array.isArray(val);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Array membership: "value" in input.path or variable in input.path
|
|
473
|
+
const inMatch = trimmed.match(/^(.+)\s+in\s+([\w.[\]]+)$/);
|
|
474
|
+
if (inMatch) {
|
|
475
|
+
const leftRaw = inMatch[1].trim();
|
|
476
|
+
let left;
|
|
477
|
+
if (leftRaw.startsWith('"') && leftRaw.endsWith('"')) {
|
|
478
|
+
left = leftRaw.slice(1, -1);
|
|
479
|
+
}
|
|
480
|
+
else if (leftRaw === 'true') {
|
|
481
|
+
left = true;
|
|
482
|
+
}
|
|
483
|
+
else if (leftRaw === 'false') {
|
|
484
|
+
left = false;
|
|
485
|
+
}
|
|
486
|
+
else if (leftRaw === 'null') {
|
|
487
|
+
left = null;
|
|
488
|
+
}
|
|
489
|
+
else if (!isNaN(Number(leftRaw)) && leftRaw !== '') {
|
|
490
|
+
left = Number(leftRaw);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
// Try to resolve as a path or variable reference
|
|
494
|
+
left = this.resolveValue(leftRaw, context);
|
|
495
|
+
}
|
|
496
|
+
const arr = this.resolveValue(inMatch[2], context);
|
|
497
|
+
if (Array.isArray(arr)) {
|
|
498
|
+
return arr.includes(left);
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
// Negation: not input.path
|
|
503
|
+
const notMatch = trimmed.match(/^not\s+([\w.[\]]+)$/);
|
|
504
|
+
if (notMatch) {
|
|
505
|
+
const val = this.resolveValue(notMatch[1], context);
|
|
506
|
+
return !val;
|
|
507
|
+
}
|
|
508
|
+
// Truthy check: input.path
|
|
509
|
+
if (/^[\w.[\]]+$/.test(trimmed)) {
|
|
510
|
+
const val = this.resolveValue(trimmed, context);
|
|
511
|
+
return !!val;
|
|
512
|
+
}
|
|
513
|
+
// Unknown condition format -- treat as non-match
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Resolve a dot-notation path against the evaluation context.
|
|
518
|
+
* E.g., "input.tool.name" resolves to context.input.tool.name
|
|
519
|
+
*/
|
|
520
|
+
resolveValue(path, context) {
|
|
521
|
+
return this.resolvePath(context, path);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Parse the right-hand side of a comparison.
|
|
525
|
+
* Handles quoted strings, booleans, numbers.
|
|
526
|
+
*/
|
|
527
|
+
parseRightValue(raw) {
|
|
528
|
+
// Quoted string
|
|
529
|
+
const strMatch = raw.match(/^"([^"]*)"$/);
|
|
530
|
+
if (strMatch)
|
|
531
|
+
return strMatch[1];
|
|
532
|
+
// Boolean
|
|
533
|
+
if (raw === 'true')
|
|
534
|
+
return true;
|
|
535
|
+
if (raw === 'false')
|
|
536
|
+
return false;
|
|
537
|
+
// Null
|
|
538
|
+
if (raw === 'null')
|
|
539
|
+
return null;
|
|
540
|
+
// Number
|
|
541
|
+
const num = Number(raw);
|
|
542
|
+
if (!isNaN(num) && raw !== '')
|
|
543
|
+
return num;
|
|
544
|
+
// If it looks like a path, resolve it
|
|
545
|
+
if (/^[\w.[\]]+$/.test(raw)) {
|
|
546
|
+
return raw; // Return as-is (literal identifier)
|
|
547
|
+
}
|
|
548
|
+
return raw;
|
|
549
|
+
}
|
|
550
|
+
/** Convert ToolCall to OPA input format */
|
|
551
|
+
buildInput(toolCall) {
|
|
552
|
+
return {
|
|
553
|
+
tool_call_id: toolCall.tool_call_id,
|
|
554
|
+
task_id: toolCall.task_id,
|
|
555
|
+
workspace_id: toolCall.workspace_id,
|
|
556
|
+
actor: toolCall.actor,
|
|
557
|
+
source: toolCall.source,
|
|
558
|
+
tool: toolCall.tool,
|
|
559
|
+
args: toolCall.args,
|
|
560
|
+
constraints: toolCall.constraints,
|
|
561
|
+
context: toolCall.context,
|
|
562
|
+
timestamp: toolCall.timestamp,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/** Parse OPA REST API response into PolicyEvalResult */
|
|
566
|
+
parseOPAResponse(response, toolCall) {
|
|
567
|
+
const result = response?.result;
|
|
568
|
+
if (!result) {
|
|
569
|
+
return this.fallbackResult('OPA returned empty result');
|
|
570
|
+
}
|
|
571
|
+
const decision = this.normalizeDecision(result.decision);
|
|
572
|
+
return {
|
|
573
|
+
decision,
|
|
574
|
+
rule_id: result.rule_id || 'opa_policy',
|
|
575
|
+
rule_name: result.rule_name || 'OPA policy evaluation',
|
|
576
|
+
reasons: Array.isArray(result.reasons) ? result.reasons :
|
|
577
|
+
result.reasons ? [String(result.reasons)] :
|
|
578
|
+
[`OPA decision: ${decision}`],
|
|
579
|
+
transformations: Array.isArray(result.transformations) ? result.transformations : undefined,
|
|
580
|
+
approval: result.approval,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/** Normalize OPA decision string to PolicyDecision */
|
|
584
|
+
normalizeDecision(decision) {
|
|
585
|
+
if (!decision)
|
|
586
|
+
return this.config.fallback_decision || 'deny';
|
|
587
|
+
const lower = String(decision).toLowerCase();
|
|
588
|
+
if (['allow', 'deny', 'transform', 'require_approval'].includes(lower)) {
|
|
589
|
+
return lower;
|
|
590
|
+
}
|
|
591
|
+
// Common OPA patterns
|
|
592
|
+
if (lower === 'true' || lower === 'allowed')
|
|
593
|
+
return 'allow';
|
|
594
|
+
if (lower === 'false' || lower === 'denied')
|
|
595
|
+
return 'deny';
|
|
596
|
+
return this.config.fallback_decision || 'deny';
|
|
597
|
+
}
|
|
598
|
+
/** HTTP POST helper using built-in Node http/https */
|
|
599
|
+
httpPost(url, body, timeoutMs) {
|
|
600
|
+
return new Promise((resolve, reject) => {
|
|
601
|
+
const parsed = new url_1.URL(url);
|
|
602
|
+
const transport = parsed.protocol === 'https:' ? https : http;
|
|
603
|
+
const data = JSON.stringify(body);
|
|
604
|
+
const req = transport.request({
|
|
605
|
+
hostname: parsed.hostname,
|
|
606
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
607
|
+
path: parsed.pathname + parsed.search,
|
|
608
|
+
method: 'POST',
|
|
609
|
+
headers: {
|
|
610
|
+
'Content-Type': 'application/json',
|
|
611
|
+
'Content-Length': Buffer.byteLength(data),
|
|
612
|
+
},
|
|
613
|
+
timeout: timeoutMs,
|
|
614
|
+
}, (res) => {
|
|
615
|
+
let responseData = '';
|
|
616
|
+
res.on('data', (chunk) => responseData += chunk);
|
|
617
|
+
res.on('end', () => {
|
|
618
|
+
try {
|
|
619
|
+
resolve(JSON.parse(responseData));
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
reject(new Error(`Invalid JSON from OPA: ${responseData.substring(0, 200)}`));
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
req.on('error', reject);
|
|
627
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('OPA request timed out')); });
|
|
628
|
+
req.write(data);
|
|
629
|
+
req.end();
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
/** Build fallback result when OPA is unreachable or not configured */
|
|
633
|
+
fallbackResult(reason) {
|
|
634
|
+
return {
|
|
635
|
+
decision: this.config.fallback_decision || 'deny',
|
|
636
|
+
rule_id: 'opa_fallback',
|
|
637
|
+
rule_name: 'OPA fallback',
|
|
638
|
+
reasons: [reason],
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Extract rule blocks using brace-depth tracking.
|
|
643
|
+
* Finds patterns like `head { body }` where body may contain nested braces.
|
|
644
|
+
* Returns array of { head, body } where head is the text before the opening brace
|
|
645
|
+
* and body is the content between the matched braces.
|
|
646
|
+
*/
|
|
647
|
+
extractBraceBlocks(text) {
|
|
648
|
+
const blocks = [];
|
|
649
|
+
let i = 0;
|
|
650
|
+
while (i < text.length) {
|
|
651
|
+
// Find the next opening brace that is part of a rule (not inside a string)
|
|
652
|
+
const braceIdx = text.indexOf('{', i);
|
|
653
|
+
if (braceIdx === -1)
|
|
654
|
+
break;
|
|
655
|
+
// Extract the head: text from the last statement boundary to the brace
|
|
656
|
+
// Walk backwards from braceIdx to find the start of the head
|
|
657
|
+
let headStart = braceIdx - 1;
|
|
658
|
+
// Skip whitespace before brace
|
|
659
|
+
while (headStart >= 0 && text[headStart] === ' ')
|
|
660
|
+
headStart--;
|
|
661
|
+
// Find the beginning of this statement (after a newline or previous closing brace)
|
|
662
|
+
const searchStart = Math.max(0, i);
|
|
663
|
+
let lineStart = text.lastIndexOf('\n', headStart);
|
|
664
|
+
if (lineStart < searchStart)
|
|
665
|
+
lineStart = searchStart;
|
|
666
|
+
else
|
|
667
|
+
lineStart++; // skip the newline char
|
|
668
|
+
const head = text.substring(lineStart, braceIdx).trim();
|
|
669
|
+
// Skip if this looks like a default line or not a rule head
|
|
670
|
+
if (!head || head.startsWith('default ')) {
|
|
671
|
+
i = braceIdx + 1;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
// Track brace depth to find the matching closing brace
|
|
675
|
+
let depth = 1;
|
|
676
|
+
let j = braceIdx + 1;
|
|
677
|
+
let inString = false;
|
|
678
|
+
while (j < text.length && depth > 0) {
|
|
679
|
+
const ch = text[j];
|
|
680
|
+
if (ch === '"' && (j === 0 || text[j - 1] !== '\\')) {
|
|
681
|
+
inString = !inString;
|
|
682
|
+
}
|
|
683
|
+
else if (!inString) {
|
|
684
|
+
if (ch === '{')
|
|
685
|
+
depth++;
|
|
686
|
+
else if (ch === '}')
|
|
687
|
+
depth--;
|
|
688
|
+
}
|
|
689
|
+
if (depth > 0)
|
|
690
|
+
j++;
|
|
691
|
+
}
|
|
692
|
+
if (depth === 0) {
|
|
693
|
+
const body = text.substring(braceIdx + 1, j).trim();
|
|
694
|
+
blocks.push({ head, body });
|
|
695
|
+
i = j + 1;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
// Unmatched brace, skip
|
|
699
|
+
i = braceIdx + 1;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return blocks;
|
|
703
|
+
}
|
|
704
|
+
/** Resolve a dot-notation path against an object. E.g., "tool.name" -> obj.tool.name */
|
|
705
|
+
resolvePath(obj, path) {
|
|
706
|
+
const parts = path.split('.');
|
|
707
|
+
let current = obj;
|
|
708
|
+
for (const part of parts) {
|
|
709
|
+
if (current === undefined || current === null)
|
|
710
|
+
return undefined;
|
|
711
|
+
current = current[part];
|
|
712
|
+
}
|
|
713
|
+
return current;
|
|
714
|
+
}
|
|
715
|
+
/** Check if OPA is configured and reachable */
|
|
716
|
+
async isAvailable() {
|
|
717
|
+
if (!this.config.server_url)
|
|
718
|
+
return !!this.config.rego_policy;
|
|
719
|
+
try {
|
|
720
|
+
const response = await this.httpPost(`${this.config.server_url}/v1/data`, { input: {} }, 2000);
|
|
721
|
+
return !!response;
|
|
722
|
+
}
|
|
723
|
+
catch {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/** Record a successful remote OPA call -- reset circuit breaker */
|
|
728
|
+
onSuccess() {
|
|
729
|
+
this.consecutiveFailures = 0;
|
|
730
|
+
this.circuitState = 'closed';
|
|
731
|
+
}
|
|
732
|
+
/** Record a failed remote OPA call -- trip circuit if threshold reached */
|
|
733
|
+
onFailure() {
|
|
734
|
+
this.consecutiveFailures++;
|
|
735
|
+
this.lastFailureTime = Date.now();
|
|
736
|
+
if (this.consecutiveFailures >= this.failureThreshold) {
|
|
737
|
+
this.circuitState = 'open';
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
/** Get the current circuit breaker state for monitoring */
|
|
741
|
+
getCircuitState() {
|
|
742
|
+
return {
|
|
743
|
+
state: this.circuitState,
|
|
744
|
+
failures: this.consecutiveFailures,
|
|
745
|
+
lastFailure: this.lastFailureTime,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
/** Reset the circuit breaker (for testing/admin) */
|
|
749
|
+
resetCircuit() {
|
|
750
|
+
this.circuitState = 'closed';
|
|
751
|
+
this.consecutiveFailures = 0;
|
|
752
|
+
this.lastFailureTime = 0;
|
|
753
|
+
}
|
|
754
|
+
/** Check if a hostname is a private/reserved IP address (SSRF protection for OPA config) */
|
|
755
|
+
isPrivateOPAHost(hostname) {
|
|
756
|
+
const bare = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
|
|
757
|
+
// IPv4 private ranges
|
|
758
|
+
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(bare)) {
|
|
759
|
+
const parts = bare.split('.').map(Number);
|
|
760
|
+
if (parts[0] === 127)
|
|
761
|
+
return true; // loopback
|
|
762
|
+
if (parts[0] === 10)
|
|
763
|
+
return true; // 10.0.0.0/8
|
|
764
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|
|
765
|
+
return true; // 172.16.0.0/12
|
|
766
|
+
if (parts[0] === 192 && parts[1] === 168)
|
|
767
|
+
return true; // 192.168.0.0/16
|
|
768
|
+
if (parts[0] === 169 && parts[1] === 254)
|
|
769
|
+
return true; // link-local
|
|
770
|
+
if (parts[0] === 0)
|
|
771
|
+
return true; // 0.0.0.0/8
|
|
772
|
+
}
|
|
773
|
+
// IPv6 private ranges
|
|
774
|
+
if (bare === '::1')
|
|
775
|
+
return true; // loopback
|
|
776
|
+
if (/^fe80:/i.test(bare))
|
|
777
|
+
return true; // link-local
|
|
778
|
+
if (/^f[cd]/i.test(bare))
|
|
779
|
+
return true; // unique local (fc00::/7)
|
|
780
|
+
if (/^ff/i.test(bare))
|
|
781
|
+
return true; // multicast
|
|
782
|
+
// Localhost aliases
|
|
783
|
+
if (bare === 'localhost' || bare === '0.0.0.0')
|
|
784
|
+
return true;
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
getConfig() { return this.config; }
|
|
788
|
+
}
|
|
789
|
+
exports.OPAEngine = OPAEngine;
|
|
790
|
+
//# sourceMappingURL=opa-engine.js.map
|