palaryn 0.1.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -588
- package/dist/sdk/typescript/src/client.js +2 -2
- package/dist/sdk/typescript/src/client.js.map +1 -1
- package/dist/src/anomaly/detector.d.ts +7 -4
- package/dist/src/anomaly/detector.d.ts.map +1 -1
- package/dist/src/anomaly/detector.js +22 -12
- package/dist/src/anomaly/detector.js.map +1 -1
- package/dist/src/audit/logger.d.ts +10 -0
- package/dist/src/audit/logger.d.ts.map +1 -1
- package/dist/src/audit/logger.js +52 -38
- package/dist/src/audit/logger.js.map +1 -1
- package/dist/src/auth/routes.d.ts.map +1 -1
- package/dist/src/auth/routes.js +35 -0
- package/dist/src/auth/routes.js.map +1 -1
- package/dist/src/budget/manager.d.ts +5 -0
- package/dist/src/budget/manager.d.ts.map +1 -1
- package/dist/src/budget/manager.js +32 -0
- package/dist/src/budget/manager.js.map +1 -1
- package/dist/src/budget/model-pricing.d.ts +20 -0
- package/dist/src/budget/model-pricing.d.ts.map +1 -0
- package/dist/src/budget/model-pricing.js +107 -0
- package/dist/src/budget/model-pricing.js.map +1 -0
- package/dist/src/budget/usage-extractor.d.ts +3 -1
- package/dist/src/budget/usage-extractor.d.ts.map +1 -1
- package/dist/src/budget/usage-extractor.js +47 -3
- package/dist/src/budget/usage-extractor.js.map +1 -1
- package/dist/src/config/defaults.d.ts.map +1 -1
- package/dist/src/config/defaults.js +65 -13
- package/dist/src/config/defaults.js.map +1 -1
- package/dist/src/dlp/tool-patterns.d.ts +7 -0
- package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
- package/dist/src/dlp/tool-patterns.js +34 -0
- package/dist/src/dlp/tool-patterns.js.map +1 -0
- package/dist/src/executor/filesystem-executor.d.ts +28 -0
- package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
- package/dist/src/executor/filesystem-executor.js +192 -0
- package/dist/src/executor/filesystem-executor.js.map +1 -0
- package/dist/src/executor/http-executor.d.ts.map +1 -1
- package/dist/src/executor/http-executor.js +22 -2
- package/dist/src/executor/http-executor.js.map +1 -1
- package/dist/src/executor/index.d.ts +4 -0
- package/dist/src/executor/index.d.ts.map +1 -1
- package/dist/src/executor/index.js +9 -1
- package/dist/src/executor/index.js.map +1 -1
- package/dist/src/executor/shell-executor.d.ts +22 -0
- package/dist/src/executor/shell-executor.d.ts.map +1 -0
- package/dist/src/executor/shell-executor.js +119 -0
- package/dist/src/executor/shell-executor.js.map +1 -0
- package/dist/src/executor/sql-executor.d.ts +29 -0
- package/dist/src/executor/sql-executor.d.ts.map +1 -0
- package/dist/src/executor/sql-executor.js +114 -0
- package/dist/src/executor/sql-executor.js.map +1 -0
- package/dist/src/executor/websocket-executor.d.ts +26 -0
- package/dist/src/executor/websocket-executor.d.ts.map +1 -0
- package/dist/src/executor/websocket-executor.js +205 -0
- package/dist/src/executor/websocket-executor.js.map +1 -0
- package/dist/src/interceptor/index.d.ts +2 -0
- package/dist/src/interceptor/index.d.ts.map +1 -0
- package/dist/src/interceptor/index.js +6 -0
- package/dist/src/interceptor/index.js.map +1 -0
- package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
- package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
- package/dist/src/interceptor/provider-interceptor.js +302 -0
- package/dist/src/interceptor/provider-interceptor.js.map +1 -0
- package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
- package/dist/src/mcp/auth-verifier.js +3 -2
- package/dist/src/mcp/auth-verifier.js.map +1 -1
- package/dist/src/mcp/bridge.d.ts +14 -10
- package/dist/src/mcp/bridge.d.ts.map +1 -1
- package/dist/src/mcp/bridge.js +51 -227
- package/dist/src/mcp/bridge.js.map +1 -1
- package/dist/src/mcp/http-transport.d.ts +2 -0
- package/dist/src/mcp/http-transport.d.ts.map +1 -1
- package/dist/src/mcp/http-transport.js +117 -66
- package/dist/src/mcp/http-transport.js.map +1 -1
- package/dist/src/mcp/internal-auth.d.ts +13 -0
- package/dist/src/mcp/internal-auth.d.ts.map +1 -0
- package/dist/src/mcp/internal-auth.js +12 -0
- package/dist/src/mcp/internal-auth.js.map +1 -0
- package/dist/src/mcp/tool-definitions.d.ts +41 -0
- package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
- package/dist/src/mcp/tool-definitions.js +491 -0
- package/dist/src/mcp/tool-definitions.js.map +1 -0
- package/dist/src/middleware/auth.js.map +1 -1
- package/dist/src/middleware/session.js.map +1 -1
- package/dist/src/middleware/validate.d.ts +8 -0
- package/dist/src/middleware/validate.d.ts.map +1 -1
- package/dist/src/middleware/validate.js +45 -0
- package/dist/src/middleware/validate.js.map +1 -1
- package/dist/src/policy/engine.d.ts +4 -0
- package/dist/src/policy/engine.d.ts.map +1 -1
- package/dist/src/policy/engine.js +117 -0
- package/dist/src/policy/engine.js.map +1 -1
- package/dist/src/saas/routes.d.ts.map +1 -1
- package/dist/src/saas/routes.js +355 -22
- package/dist/src/saas/routes.js.map +1 -1
- package/dist/src/server/app.d.ts.map +1 -1
- package/dist/src/server/app.js +24 -3
- package/dist/src/server/app.js.map +1 -1
- package/dist/src/server/gateway.d.ts.map +1 -1
- package/dist/src/server/gateway.js +17 -0
- package/dist/src/server/gateway.js.map +1 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +18 -0
- package/dist/src/server/index.js.map +1 -1
- package/dist/src/storage/interfaces.d.ts +14 -3
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/memory.d.ts +2 -0
- package/dist/src/storage/memory.d.ts.map +1 -1
- package/dist/src/storage/memory.js +6 -0
- package/dist/src/storage/memory.js.map +1 -1
- package/dist/src/storage/postgres.d.ts +5 -0
- package/dist/src/storage/postgres.d.ts.map +1 -1
- package/dist/src/storage/postgres.js +16 -0
- package/dist/src/storage/postgres.js.map +1 -1
- package/dist/src/storage/redis.d.ts +10 -0
- package/dist/src/storage/redis.d.ts.map +1 -1
- package/dist/src/storage/redis.js +65 -0
- package/dist/src/storage/redis.js.map +1 -1
- package/dist/src/types/budget.d.ts +4 -0
- package/dist/src/types/budget.d.ts.map +1 -1
- package/dist/src/types/config.d.ts +58 -0
- package/dist/src/types/config.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +1 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/policy.d.ts +11 -1
- package/dist/src/types/policy.d.ts.map +1 -1
- package/dist/src/types/tool-result.d.ts +11 -0
- package/dist/src/types/tool-result.d.ts.map +1 -1
- package/dist/tests/unit/app-routes.test.d.ts +2 -0
- package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/app-routes.test.js +715 -0
- package/dist/tests/unit/app-routes.test.js.map +1 -0
- package/dist/tests/unit/audit-logger.test.js +105 -0
- package/dist/tests/unit/audit-logger.test.js.map +1 -1
- package/dist/tests/unit/auth-providers.test.d.ts +2 -0
- package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-providers.test.js +279 -0
- package/dist/tests/unit/auth-providers.test.js.map +1 -0
- package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
- package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-routes-extended.test.js +993 -0
- package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
- package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
- package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-verifier.test.js +505 -0
- package/dist/tests/unit/auth-verifier.test.js.map +1 -0
- package/dist/tests/unit/billing-routes.test.d.ts +2 -0
- package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/billing-routes.test.js +432 -0
- package/dist/tests/unit/billing-routes.test.js.map +1 -0
- package/dist/tests/unit/config-defaults.test.d.ts +2 -0
- package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
- package/dist/tests/unit/config-defaults.test.js +119 -0
- package/dist/tests/unit/config-defaults.test.js.map +1 -0
- package/dist/tests/unit/defaults.test.js +0 -10
- package/dist/tests/unit/defaults.test.js.map +1 -1
- package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
- package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/filesystem-executor.test.js +280 -0
- package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
- package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
- package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/gateway-branches.test.js +1039 -0
- package/dist/tests/unit/gateway-branches.test.js.map +1 -0
- package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
- package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/http-executor-branches.test.js +495 -0
- package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
- package/dist/tests/unit/logger.test.d.ts +2 -0
- package/dist/tests/unit/logger.test.d.ts.map +1 -0
- package/dist/tests/unit/logger.test.js +97 -0
- package/dist/tests/unit/logger.test.js.map +1 -0
- package/dist/tests/unit/mcp-internal-auth.test.d.ts +2 -0
- package/dist/tests/unit/mcp-internal-auth.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-internal-auth.test.js +445 -0
- package/dist/tests/unit/mcp-internal-auth.test.js.map +1 -0
- package/dist/tests/unit/metrics.test.js +102 -0
- package/dist/tests/unit/metrics.test.js.map +1 -1
- package/dist/tests/unit/model-pricing.test.d.ts +2 -0
- package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
- package/dist/tests/unit/model-pricing.test.js +87 -0
- package/dist/tests/unit/model-pricing.test.js.map +1 -0
- package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
- package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
- package/dist/tests/unit/oauth-stores.test.js +260 -0
- package/dist/tests/unit/oauth-stores.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +466 -0
- package/dist/tests/unit/policy-engine.test.js.map +1 -1
- package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
- package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
- package/dist/tests/unit/provider-interceptor.test.js +472 -0
- package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-branches.test.js +2165 -0
- package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-crud.test.js +332 -0
- package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-data.test.js +405 -0
- package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
- package/dist/tests/unit/saas-routes.test.js +3 -3
- package/dist/tests/unit/saas-routes.test.js.map +1 -1
- package/dist/tests/unit/shell-executor.test.d.ts +2 -0
- package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/shell-executor.test.js +145 -0
- package/dist/tests/unit/shell-executor.test.js.map +1 -0
- package/dist/tests/unit/sql-executor.test.d.ts +2 -0
- package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/sql-executor.test.js +177 -0
- package/dist/tests/unit/sql-executor.test.js.map +1 -0
- package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
- package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
- package/dist/tests/unit/stream-proxy.test.js +147 -0
- package/dist/tests/unit/stream-proxy.test.js.map +1 -0
- package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
- package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
- package/dist/tests/unit/tool-definitions.test.js +184 -0
- package/dist/tests/unit/tool-definitions.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor.test.js +140 -0
- package/dist/tests/unit/usage-extractor.test.js.map +1 -1
- package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
- package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
- package/dist/tests/unit/webhook-handler.test.js +453 -0
- package/dist/tests/unit/webhook-handler.test.js.map +1 -0
- package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
- package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/webhook-routes.test.js +69 -0
- package/dist/tests/unit/webhook-routes.test.js.map +1 -0
- package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
- package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/websocket-executor.test.js +121 -0
- package/dist/tests/unit/websocket-executor.test.js.map +1 -0
- package/package.json +8 -2
- package/policy-packs/demo_fail.yaml +41 -0
- package/policy-packs/full_tools.yaml +136 -0
- package/src/admin/index.ts +1 -0
- package/src/admin/routes.ts +509 -0
- package/src/admin/templates.ts +572 -0
- package/src/anomaly/detector.ts +730 -0
- package/src/anomaly/index.ts +1 -0
- package/src/approval/manager.ts +569 -0
- package/src/approval/webhook.ts +133 -0
- package/src/audit/logger.ts +490 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/password.ts +21 -0
- package/src/auth/pkce.ts +22 -0
- package/src/auth/providers.ts +208 -0
- package/src/auth/routes.ts +561 -0
- package/src/auth/session.ts +84 -0
- package/src/billing/index.ts +6 -0
- package/src/billing/plan-enforcer.ts +135 -0
- package/src/billing/routes.ts +229 -0
- package/src/billing/stripe-client.ts +58 -0
- package/src/billing/webhook-handler.ts +182 -0
- package/src/billing/webhook-routes.ts +28 -0
- package/src/budget/manager.ts +679 -0
- package/src/budget/model-pricing.ts +119 -0
- package/src/budget/usage-extractor.ts +214 -0
- package/src/cli.ts +91 -0
- package/src/config/defaults.ts +261 -0
- package/src/config/validate.ts +88 -0
- package/src/dlp/composite-scanner.ts +213 -0
- package/src/dlp/index.ts +9 -0
- package/src/dlp/interfaces.ts +34 -0
- package/src/dlp/patterns.ts +30 -0
- package/src/dlp/prompt-injection-backend.ts +181 -0
- package/src/dlp/prompt-injection-patterns.ts +302 -0
- package/src/dlp/regex-backend.ts +181 -0
- package/src/dlp/scanner.ts +502 -0
- package/src/dlp/text-normalizer.ts +225 -0
- package/src/dlp/tool-patterns.ts +35 -0
- package/src/dlp/trufflehog-backend.ts +190 -0
- package/src/executor/filesystem-executor.ts +196 -0
- package/src/executor/http-executor.ts +349 -0
- package/src/executor/index.ts +9 -0
- package/src/executor/interfaces.ts +11 -0
- package/src/executor/noop-executor.ts +23 -0
- package/src/executor/registry.ts +64 -0
- package/src/executor/shell-executor.ts +148 -0
- package/src/executor/slack-executor.ts +176 -0
- package/src/executor/sql-executor.ts +146 -0
- package/src/executor/websocket-executor.ts +211 -0
- package/src/index.ts +24 -0
- package/src/interceptor/index.ts +1 -0
- package/src/interceptor/provider-interceptor.ts +315 -0
- package/src/mcp/auth-verifier.ts +152 -0
- package/src/mcp/bridge.ts +703 -0
- package/src/mcp/http-transport.ts +698 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/internal-auth.ts +14 -0
- package/src/mcp/oauth-pages.ts +139 -0
- package/src/mcp/oauth-postgres-stores.ts +278 -0
- package/src/mcp/oauth-provider.ts +536 -0
- package/src/mcp/oauth-stores.ts +202 -0
- package/src/mcp/server.ts +55 -0
- package/src/mcp/tool-definitions.ts +562 -0
- package/src/metrics/collector.ts +357 -0
- package/src/metrics/index.ts +1 -0
- package/src/middleware/auth.ts +814 -0
- package/src/middleware/session.ts +85 -0
- package/src/middleware/validate.ts +130 -0
- package/src/policy/engine.ts +815 -0
- package/src/policy/index.ts +2 -0
- package/src/policy/opa-engine.ts +829 -0
- package/src/proxy/forward-proxy.ts +649 -0
- package/src/proxy/index.ts +1 -0
- package/src/ratelimit/limiter.ts +196 -0
- package/src/replay/engine.ts +142 -0
- package/src/replay/index.ts +1 -0
- package/src/saas/index.ts +1 -0
- package/src/saas/routes.ts +2178 -0
- package/src/server/app.ts +985 -0
- package/src/server/errors.ts +49 -0
- package/src/server/gateway.ts +1130 -0
- package/src/server/index.ts +307 -0
- package/src/server/logger.ts +255 -0
- package/src/server/stream-proxy.ts +202 -0
- package/src/storage/file-persistence.ts +315 -0
- package/src/storage/index.ts +4 -0
- package/src/storage/interfaces.ts +287 -0
- package/src/storage/memory.ts +686 -0
- package/src/storage/postgres.ts +1831 -0
- package/src/storage/redis.ts +835 -0
- package/src/tracing/index.ts +1 -0
- package/src/tracing/provider.ts +100 -0
- package/src/trust/calculator.ts +141 -0
- package/src/trust/index.ts +7 -0
- package/src/types/budget.ts +36 -0
- package/src/types/config.ts +278 -0
- package/src/types/events.ts +41 -0
- package/src/types/express.d.ts +14 -0
- package/src/types/index.ts +7 -0
- package/src/types/policy.ts +83 -0
- package/src/types/stripe-config.ts +11 -0
- package/src/types/subscription.ts +59 -0
- package/src/types/tool-call.ts +47 -0
- package/src/types/tool-result.ts +82 -0
- package/src/types/user.ts +125 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { createApp, SaaSStores } from './app';
|
|
2
|
+
import { DEFAULT_CONFIG } from '../config/defaults';
|
|
3
|
+
import { GatewayConfig } from '../types/config';
|
|
4
|
+
import { validateConfig } from '../config/validate';
|
|
5
|
+
import { log as devLog } from './logger';
|
|
6
|
+
|
|
7
|
+
export async function startServer(): Promise<void> {
|
|
8
|
+
const config: GatewayConfig = { ...DEFAULT_CONFIG };
|
|
9
|
+
|
|
10
|
+
// Wire up OpenTelemetry tracing from env vars
|
|
11
|
+
if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
12
|
+
config.tracing = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
service_name: process.env.OTEL_SERVICE_NAME || 'palaryn',
|
|
15
|
+
otlp_endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
16
|
+
environment: process.env.NODE_ENV || 'development',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Pre-initialize Postgres SaaS stores when DATABASE_URL is set (persists user
|
|
21
|
+
// data, sessions, workspaces, and API keys across restarts)
|
|
22
|
+
let externalSaaSStores: Partial<SaaSStores> | undefined;
|
|
23
|
+
if (process.env.DATABASE_URL) {
|
|
24
|
+
try {
|
|
25
|
+
const { Pool } = await import('pg');
|
|
26
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
27
|
+
const { initPostgresStorage, createPostgresSaaSStores } = await import('../storage/postgres');
|
|
28
|
+
const { PostgresOAuthClientsStore, PostgresOAuthTokenStore } = await import('../mcp/oauth-postgres-stores');
|
|
29
|
+
await initPostgresStorage(pool);
|
|
30
|
+
|
|
31
|
+
// Create MCP OAuth Postgres tables
|
|
32
|
+
await PostgresOAuthClientsStore.createTables(pool);
|
|
33
|
+
await PostgresOAuthTokenStore.createTables(pool);
|
|
34
|
+
|
|
35
|
+
const pgSaaSStores = createPostgresSaaSStores(pool);
|
|
36
|
+
const mcpOAuthClientsStore = new PostgresOAuthClientsStore(pool);
|
|
37
|
+
const mcpOAuthTokenStore = new PostgresOAuthTokenStore(pool);
|
|
38
|
+
|
|
39
|
+
// Hydrate SaaS stores
|
|
40
|
+
await Promise.all([
|
|
41
|
+
pgSaaSStores.userStore.hydrate(),
|
|
42
|
+
pgSaaSStores.oauthAccountStore.hydrate(),
|
|
43
|
+
pgSaaSStores.workspaceStore.hydrate(),
|
|
44
|
+
pgSaaSStores.workspaceMemberStore.hydrate(),
|
|
45
|
+
pgSaaSStores.sessionStore.hydrate(),
|
|
46
|
+
pgSaaSStores.userApiKeyStore.hydrate(),
|
|
47
|
+
pgSaaSStores.subscriptionStore.hydrate(),
|
|
48
|
+
pgSaaSStores.rateLimitConfigStore.hydrate(),
|
|
49
|
+
pgSaaSStores.budgetConfigStore.hydrate(),
|
|
50
|
+
pgSaaSStores.policyStore.hydrate(),
|
|
51
|
+
mcpOAuthClientsStore.hydrate(),
|
|
52
|
+
mcpOAuthTokenStore.hydrate(),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
externalSaaSStores = {
|
|
56
|
+
...pgSaaSStores,
|
|
57
|
+
mcpOAuthClientsStore,
|
|
58
|
+
mcpOAuthTokenStore,
|
|
59
|
+
};
|
|
60
|
+
console.log('PostgreSQL SaaS stores initialized (incl. MCP OAuth)');
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.warn('PostgreSQL SaaS store init failed, using in-memory:', (err as Error).message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Validate configuration before startup
|
|
67
|
+
const configValidation = validateConfig(config);
|
|
68
|
+
for (const issue of configValidation.issues) {
|
|
69
|
+
if (issue.level === 'fatal') {
|
|
70
|
+
console.error(`[fatal] ${issue.message}`);
|
|
71
|
+
} else {
|
|
72
|
+
console.warn(`[warn] ${issue.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!configValidation.valid) {
|
|
76
|
+
console.error('Configuration validation failed. Fix the fatal errors above and restart.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { app, gateway, tracer, metrics, healthChecks, saasStores, proxyServer } = createApp(config, externalSaaSStores);
|
|
81
|
+
|
|
82
|
+
// Startup warnings
|
|
83
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
84
|
+
if (isProduction && !process.env.REDIS_URL && !process.env.DATABASE_URL) {
|
|
85
|
+
console.warn('[warn] Running in production without REDIS_URL or DATABASE_URL. Data is stored in-memory and will be lost on restart.');
|
|
86
|
+
}
|
|
87
|
+
if (isProduction && config.approval.token_secret === 'palaryn-dev-approval-secret') {
|
|
88
|
+
console.warn('[warn] Using default approval secret in production. Set APPROVAL_SECRET env var.');
|
|
89
|
+
}
|
|
90
|
+
const policyPack = gateway.getCurrentPolicy();
|
|
91
|
+
if (policyPack.rules.length === 0) {
|
|
92
|
+
console.warn('[warn] Policy pack has no rules. All requests will be handled by default_effect (' + config.policy.default_effect + ').');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Wire up Redis if REDIS_URL is set
|
|
96
|
+
if (process.env.REDIS_URL) {
|
|
97
|
+
try {
|
|
98
|
+
const Redis = (await import('ioredis')).default;
|
|
99
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
100
|
+
const { createRedisStores } = await import('../storage/redis');
|
|
101
|
+
const stores = createRedisStores(redis, 'palaryn:');
|
|
102
|
+
|
|
103
|
+
// Inject Redis stores into Gateway (replaces default in-memory stores)
|
|
104
|
+
gateway.setStores({
|
|
105
|
+
idempotencyStore: stores.idempotencyStore,
|
|
106
|
+
rateLimitStore: stores.rateLimitStore,
|
|
107
|
+
auditStore: stores.auditStore,
|
|
108
|
+
budgetStore: stores.budgetStore,
|
|
109
|
+
approvalStore: stores.approvalStore,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Hydrate stores from Redis (restore state after restart)
|
|
113
|
+
await Promise.all([
|
|
114
|
+
stores.budgetStore.hydrate(),
|
|
115
|
+
stores.auditStore.hydrate(),
|
|
116
|
+
stores.approvalStore.hydrate(),
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
// Populate AuditLogger in-memory buffer from hydrated Redis store
|
|
120
|
+
gateway.getAuditLogger().hydrateFromStore();
|
|
121
|
+
|
|
122
|
+
// Populate BudgetManager in-memory maps from hydrated Redis store
|
|
123
|
+
gateway.getBudgetManager().hydrateFromStore();
|
|
124
|
+
|
|
125
|
+
// Hydrate RateLimitStore local windows from Redis
|
|
126
|
+
if (stores.rateLimitStore.hydrateAll) {
|
|
127
|
+
await stores.rateLimitStore.hydrateAll();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Register Redis health check
|
|
131
|
+
healthChecks.push({
|
|
132
|
+
name: 'redis',
|
|
133
|
+
check: async () => {
|
|
134
|
+
try {
|
|
135
|
+
const result = await redis.ping();
|
|
136
|
+
return { status: result === 'PONG' ? 'ok' : 'unhealthy', message: result };
|
|
137
|
+
} catch (err) {
|
|
138
|
+
return { status: 'unhealthy', message: (err as Error).message };
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
console.log('Redis connected:', process.env.REDIS_URL);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.warn('Redis connection failed, using in-memory stores:', (err as Error).message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Wire up PostgreSQL if DATABASE_URL is set
|
|
150
|
+
if (process.env.DATABASE_URL) {
|
|
151
|
+
try {
|
|
152
|
+
const { Pool } = await import('pg');
|
|
153
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
154
|
+
const { initPostgresStorage, createPostgresStores } = await import('../storage/postgres');
|
|
155
|
+
await initPostgresStorage(pool);
|
|
156
|
+
|
|
157
|
+
// Inject Postgres stores if Redis was not already connected (Redis takes priority)
|
|
158
|
+
if (!process.env.REDIS_URL) {
|
|
159
|
+
const pgStores = createPostgresStores(pool);
|
|
160
|
+
gateway.setStores({
|
|
161
|
+
idempotencyStore: pgStores.idempotencyStore,
|
|
162
|
+
rateLimitStore: pgStores.rateLimitStore,
|
|
163
|
+
auditStore: pgStores.auditStore,
|
|
164
|
+
budgetStore: pgStores.budgetStore,
|
|
165
|
+
approvalStore: pgStores.approvalStore,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Hydrate Postgres stores (restore state after restart)
|
|
169
|
+
await Promise.all([
|
|
170
|
+
pgStores.budgetStore.hydrate(),
|
|
171
|
+
pgStores.auditStore.hydrate(),
|
|
172
|
+
pgStores.approvalStore.hydrate(),
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
// Populate AuditLogger in-memory buffer from hydrated Postgres store
|
|
176
|
+
gateway.getAuditLogger().hydrateFromStore();
|
|
177
|
+
|
|
178
|
+
// Populate BudgetManager in-memory maps from hydrated Postgres store
|
|
179
|
+
gateway.getBudgetManager().hydrateFromStore();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Register Postgres health check
|
|
183
|
+
healthChecks.push({
|
|
184
|
+
name: 'postgres',
|
|
185
|
+
check: async () => {
|
|
186
|
+
try {
|
|
187
|
+
await pool.query('SELECT 1');
|
|
188
|
+
return { status: 'ok' };
|
|
189
|
+
} catch (err) {
|
|
190
|
+
return { status: 'unhealthy', message: (err as Error).message };
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
console.log('PostgreSQL connected:', process.env.DATABASE_URL.replace(/:[^:@]*@/, ':***@'));
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.warn('PostgreSQL connection failed:', (err as Error).message);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const server = app.listen(config.port, config.host, () => {
|
|
202
|
+
devLog.serverStart(config.host, config.port, {
|
|
203
|
+
'Policy': config.policy.pack_path,
|
|
204
|
+
'Auth': config.auth.enabled,
|
|
205
|
+
'DLP': config.dlp.enabled,
|
|
206
|
+
'Audit': config.audit.enabled,
|
|
207
|
+
'Tracing': config.tracing?.enabled || false,
|
|
208
|
+
'Redis': !!process.env.REDIS_URL,
|
|
209
|
+
'PostgreSQL': !!process.env.DATABASE_URL,
|
|
210
|
+
'OAuth': config.mcp_oauth?.enabled || config.oauth?.enabled || false,
|
|
211
|
+
'Frontend': config.frontend?.enabled || false,
|
|
212
|
+
'Proxy': config.proxy?.enabled || false,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// S7: Track active requests for graceful shutdown
|
|
217
|
+
let activeRequests = 0;
|
|
218
|
+
server.on('request', (_req: any, res: any) => {
|
|
219
|
+
activeRequests++;
|
|
220
|
+
const decrement = () => { activeRequests = Math.max(0, activeRequests - 1); };
|
|
221
|
+
res.on('finish', decrement);
|
|
222
|
+
res.on('close', decrement);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Start forward proxy server alongside Express
|
|
226
|
+
if (proxyServer && config.proxy?.enabled) {
|
|
227
|
+
const proxyPort = config.proxy.port || 3128;
|
|
228
|
+
proxyServer.listen(proxyPort, () => {
|
|
229
|
+
console.log(`Forward proxy listening on :${proxyPort}`);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Graceful shutdown
|
|
234
|
+
async function gracefulShutdown(signal: string): Promise<void> {
|
|
235
|
+
devLog.serverShutdown(signal);
|
|
236
|
+
|
|
237
|
+
// Force exit after timeout (configurable via SHUTDOWN_TIMEOUT_MS env var)
|
|
238
|
+
const shutdownTimeoutMs = parseInt(process.env.SHUTDOWN_TIMEOUT_MS || '', 10) || 10000;
|
|
239
|
+
const forceTimer = setTimeout(() => {
|
|
240
|
+
console.error('Forced shutdown after timeout');
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}, shutdownTimeoutMs);
|
|
243
|
+
forceTimer.unref();
|
|
244
|
+
|
|
245
|
+
// 1. Stop accepting new connections
|
|
246
|
+
if (proxyServer) {
|
|
247
|
+
await new Promise<void>((resolve) => {
|
|
248
|
+
proxyServer!.close(() => {
|
|
249
|
+
console.log('Proxy server closed');
|
|
250
|
+
resolve();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
server.close(() => {
|
|
255
|
+
console.log('HTTP server closed (no new connections)');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// S7: Wait for active requests to drain (up to 30 seconds)
|
|
259
|
+
const drainTimeout = 30_000;
|
|
260
|
+
const drainStart = Date.now();
|
|
261
|
+
while (activeRequests > 0 && (Date.now() - drainStart) < drainTimeout) {
|
|
262
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 100));
|
|
263
|
+
}
|
|
264
|
+
if (activeRequests > 0) {
|
|
265
|
+
console.warn(`Shutdown: ${activeRequests} active requests still pending after ${drainTimeout}ms drain timeout, force-closing`);
|
|
266
|
+
} else {
|
|
267
|
+
console.log('All active requests drained');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 2. Shut down gateway (audit logger, idempotency store, rate limiter)
|
|
271
|
+
await gateway.shutdown();
|
|
272
|
+
console.log('Gateway shut down');
|
|
273
|
+
|
|
274
|
+
// 3. Flush OTel traces
|
|
275
|
+
if (tracer) {
|
|
276
|
+
await tracer.shutdown();
|
|
277
|
+
console.log('Tracer flushed and shut down');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 4. Flush metrics
|
|
281
|
+
metrics.reset();
|
|
282
|
+
console.log('Metrics reset');
|
|
283
|
+
|
|
284
|
+
console.log('Shutdown complete');
|
|
285
|
+
process.exit(0);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let shuttingDown = false;
|
|
289
|
+
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
290
|
+
process.on(signal, () => {
|
|
291
|
+
if (shuttingDown) return;
|
|
292
|
+
shuttingDown = true;
|
|
293
|
+
gracefulShutdown(signal).catch((err) => {
|
|
294
|
+
console.error('Shutdown error:', err);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Direct execution (node dist/src/server/index.js)
|
|
302
|
+
if (require.main === module) {
|
|
303
|
+
startServer().catch((err) => {
|
|
304
|
+
console.error('Failed to start gateway:', err);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pretty console logger for Palaryn dev mode.
|
|
3
|
+
* Color-coded pipeline steps with box-drawing characters.
|
|
4
|
+
*
|
|
5
|
+
* Also exports a Winston `logger` for structured logging (JSON in production,
|
|
6
|
+
* colorized console in development).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import winston from 'winston';
|
|
10
|
+
|
|
11
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
12
|
+
|
|
13
|
+
export const logger = winston.createLogger({
|
|
14
|
+
level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
|
|
15
|
+
format: isProduction
|
|
16
|
+
? winston.format.combine(
|
|
17
|
+
winston.format.timestamp(),
|
|
18
|
+
winston.format.json(),
|
|
19
|
+
)
|
|
20
|
+
: winston.format.combine(
|
|
21
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
|
22
|
+
winston.format.colorize(),
|
|
23
|
+
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
24
|
+
const metaStr = Object.keys(meta).length > 0 ? ' ' + JSON.stringify(meta) : '';
|
|
25
|
+
return `${timestamp} ${level}: ${message}${metaStr}`;
|
|
26
|
+
}),
|
|
27
|
+
),
|
|
28
|
+
transports: [
|
|
29
|
+
new winston.transports.Console(),
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const RESET = '\x1b[0m';
|
|
34
|
+
const BOLD = '\x1b[1m';
|
|
35
|
+
const DIM = '\x1b[2m';
|
|
36
|
+
|
|
37
|
+
const FG = {
|
|
38
|
+
black: '\x1b[30m',
|
|
39
|
+
red: '\x1b[31m',
|
|
40
|
+
green: '\x1b[32m',
|
|
41
|
+
yellow: '\x1b[33m',
|
|
42
|
+
blue: '\x1b[34m',
|
|
43
|
+
magenta: '\x1b[35m',
|
|
44
|
+
cyan: '\x1b[36m',
|
|
45
|
+
white: '\x1b[37m',
|
|
46
|
+
gray: '\x1b[90m',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const BG = {
|
|
50
|
+
red: '\x1b[41m',
|
|
51
|
+
green: '\x1b[42m',
|
|
52
|
+
yellow: '\x1b[43m',
|
|
53
|
+
blue: '\x1b[44m',
|
|
54
|
+
magenta: '\x1b[45m',
|
|
55
|
+
cyan: '\x1b[46m',
|
|
56
|
+
white: '\x1b[47m',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function timestamp(): string {
|
|
60
|
+
return new Date().toISOString().replace('T', ' ').replace('Z', '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function tag(bg: string, fg: string, label: string): string {
|
|
64
|
+
return `${bg}${fg}${BOLD} ${label} ${RESET}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function indent(text: string, prefix: string = ' '): string {
|
|
68
|
+
return text.split('\n').map(l => prefix + l).join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function truncate(s: string, max: number = 200): string {
|
|
72
|
+
if (s.length <= max) return s;
|
|
73
|
+
return s.slice(0, max) + `${DIM}... (${s.length} chars)${RESET}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function jsonPretty(obj: unknown, maxDepth: number = 3): string {
|
|
77
|
+
try {
|
|
78
|
+
const s = JSON.stringify(obj, null, 2);
|
|
79
|
+
return truncate(s, 500);
|
|
80
|
+
} catch {
|
|
81
|
+
return String(obj);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const log = {
|
|
86
|
+
// ── Request lifecycle ─────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
request(method: string, path: string, ip: string, body?: unknown) {
|
|
89
|
+
const t = timestamp();
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(`${FG.gray}${t}${RESET}`);
|
|
92
|
+
console.log(`${tag(BG.blue, FG.white, 'REQUEST')} ${BOLD}${method}${RESET} ${FG.cyan}${path}${RESET} ${DIM}from ${ip}${RESET}`);
|
|
93
|
+
if (body && Object.keys(body as Record<string, unknown>).length > 0) {
|
|
94
|
+
const b = body as Record<string, unknown>;
|
|
95
|
+
if (b.tool) {
|
|
96
|
+
const tool = b.tool as Record<string, unknown>;
|
|
97
|
+
console.log(` ${FG.yellow}tool:${RESET} ${BOLD}${tool.name}${RESET} v${tool.version} ${DIM}[${tool.capability}]${RESET}`);
|
|
98
|
+
}
|
|
99
|
+
if (b.actor) {
|
|
100
|
+
const actor = b.actor as Record<string, unknown>;
|
|
101
|
+
console.log(` ${FG.yellow}actor:${RESET} ${actor.id} ${DIM}(${actor.type})${RESET}`);
|
|
102
|
+
}
|
|
103
|
+
if (b.args) {
|
|
104
|
+
const args = b.args as Record<string, unknown>;
|
|
105
|
+
if (args.method && args.url) {
|
|
106
|
+
console.log(` ${FG.yellow}target:${RESET} ${args.method} ${FG.cyan}${args.url}${RESET}`);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(` ${FG.yellow}args:${RESET} ${DIM}${truncate(JSON.stringify(args), 120)}${RESET}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (b.tool_call_id) {
|
|
112
|
+
console.log(` ${FG.yellow}call_id:${RESET} ${DIM}${b.tool_call_id}${RESET}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
response(statusCode: number, durationMs: number, body?: unknown) {
|
|
118
|
+
const color = statusCode < 300 ? FG.green : statusCode < 400 ? FG.yellow : FG.red;
|
|
119
|
+
const bgColor = statusCode < 300 ? BG.green : statusCode < 400 ? BG.yellow : BG.red;
|
|
120
|
+
console.log(`${tag(bgColor, FG.white, 'RESPONSE')} ${color}${BOLD}${statusCode}${RESET} ${DIM}in ${durationMs}ms${RESET}`);
|
|
121
|
+
if (body) {
|
|
122
|
+
const b = body as Record<string, unknown>;
|
|
123
|
+
if (b.status) {
|
|
124
|
+
console.log(` ${FG.yellow}status:${RESET} ${b.status === 'ok' ? FG.green : FG.red}${b.status}${RESET}`);
|
|
125
|
+
}
|
|
126
|
+
if (b.error) {
|
|
127
|
+
console.log(` ${FG.red}error:${RESET} ${b.error}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.log('');
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// ── Pipeline steps ────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
pipelineStart(toolCallId: string, toolName: string) {
|
|
136
|
+
console.log(`${FG.gray} ┌─────────────────────────────────────────${RESET}`);
|
|
137
|
+
console.log(`${FG.gray} │${RESET} ${tag(BG.magenta, FG.white, 'PIPELINE')} ${BOLD}${toolName}${RESET} ${DIM}(${toolCallId})${RESET}`);
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
pipelineStep(icon: string, label: string, detail: string, color: string = FG.white) {
|
|
141
|
+
console.log(`${FG.gray} │${RESET} ${icon} ${BOLD}${color}${label}${RESET} ${detail}`);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
pipelineEnd(status: string, durationMs: number) {
|
|
145
|
+
const color = status === 'ok' ? FG.green : status === 'blocked' ? FG.red : FG.yellow;
|
|
146
|
+
console.log(`${FG.gray} │${RESET} ${tag(status === 'ok' ? BG.green : status === 'blocked' ? BG.red : BG.yellow, FG.white, 'DONE')} ${color}${BOLD}${status}${RESET} ${DIM}(${durationMs}ms)${RESET}`);
|
|
147
|
+
console.log(`${FG.gray} └─────────────────────────────────────────${RESET}`);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// ── Specific pipeline stages ──────────────────────────────
|
|
151
|
+
|
|
152
|
+
idempotencyHit(toolCallId: string) {
|
|
153
|
+
this.pipelineStep('$', 'CACHE HIT', `${DIM}returning cached result for ${toolCallId}${RESET}`, FG.cyan);
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
idempotencyMiss() {
|
|
157
|
+
this.pipelineStep('$', 'CACHE', `${FG.green}miss${RESET} ${DIM}(new call)${RESET}`, FG.cyan);
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
rateLimit(allowed: boolean, current?: number, limit?: number, blockedBy?: string) {
|
|
161
|
+
if (allowed) {
|
|
162
|
+
this.pipelineStep('\u{1F6A6}', 'RATE LIMIT', `${FG.green}pass${RESET} ${DIM}(${current}/${limit})${RESET}`, FG.blue);
|
|
163
|
+
} else {
|
|
164
|
+
this.pipelineStep('\u{1F6A6}', 'RATE LIMIT', `${FG.red}BLOCKED${RESET} ${blockedBy}: ${current}/${limit}`, FG.red);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
anomaly(alertCount: number, blocked: boolean) {
|
|
169
|
+
if (alertCount === 0) {
|
|
170
|
+
this.pipelineStep('\u{1F50D}', 'ANOMALY', `${FG.green}clean${RESET}`, FG.blue);
|
|
171
|
+
} else if (blocked) {
|
|
172
|
+
this.pipelineStep('\u{1F50D}', 'ANOMALY', `${FG.red}BLOCKED${RESET} (${alertCount} alerts)`, FG.red);
|
|
173
|
+
} else {
|
|
174
|
+
this.pipelineStep('\u{1F50D}', 'ANOMALY', `${FG.yellow}${alertCount} alert(s) flagged${RESET}`, FG.yellow);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
policy(decision: string, ruleId?: string, reasons?: string[]) {
|
|
179
|
+
const color = decision === 'allow' ? FG.green : decision === 'deny' ? FG.red : decision === 'transform' ? FG.yellow : FG.magenta;
|
|
180
|
+
const icon = decision === 'allow' ? '\u{2705}' : decision === 'deny' ? '\u{274C}' : decision === 'transform' ? '\u{1F504}' : '\u{23F3}';
|
|
181
|
+
this.pipelineStep(icon, 'POLICY', `${color}${BOLD}${decision.toUpperCase()}${RESET} ${DIM}rule: ${ruleId || 'none'}${RESET}`, color);
|
|
182
|
+
if (reasons && reasons.length > 0) {
|
|
183
|
+
for (const r of reasons) {
|
|
184
|
+
console.log(`${FG.gray} │${RESET} ${DIM} -> ${r}${RESET}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
dlp(phase: 'args' | 'output', detected: string[], severity: string, redactionCount: number) {
|
|
190
|
+
const label = phase === 'args' ? 'DLP ARGS' : 'DLP OUTPUT';
|
|
191
|
+
if (detected.length === 0) {
|
|
192
|
+
this.pipelineStep('\u{1F6E1}\u{FE0F}', label, `${FG.green}clean${RESET}`, FG.blue);
|
|
193
|
+
} else {
|
|
194
|
+
const sevColor = severity === 'high' ? FG.red : severity === 'medium' ? FG.yellow : FG.cyan;
|
|
195
|
+
this.pipelineStep('\u{1F6E1}\u{FE0F}', label, `${sevColor}${severity}${RESET} found: ${FG.yellow}${detected.join(', ')}${RESET} ${DIM}(${redactionCount} redactions)${RESET}`, sevColor);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
budget(allowed: boolean, estimated: number, spent: number, remaining: number, reason?: string) {
|
|
200
|
+
if (allowed) {
|
|
201
|
+
this.pipelineStep('\u{1F4B0}', 'BUDGET', `${FG.green}pass${RESET} est:$${estimated.toFixed(4)} spent:$${spent.toFixed(4)} left:$${remaining.toFixed(4)}`, FG.green);
|
|
202
|
+
} else {
|
|
203
|
+
this.pipelineStep('\u{1F4B0}', 'BUDGET', `${FG.red}BLOCKED${RESET} ${reason}`, FG.red);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
transform(transformations: Array<{ type: string; target: string }>) {
|
|
208
|
+
for (const t of transformations) {
|
|
209
|
+
this.pipelineStep('\u{1F504}', 'TRANSFORM', `${FG.yellow}${t.type}${RESET} -> ${t.target}`, FG.yellow);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
executing(toolName: string, target?: string) {
|
|
214
|
+
const detail = target ? `${FG.cyan}${target}${RESET}` : '';
|
|
215
|
+
this.pipelineStep('\u{26A1}', 'EXECUTE', `${BOLD}${toolName}${RESET} ${detail}`, FG.magenta);
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
executed(httpStatus: number, durationMs: number) {
|
|
219
|
+
const color = httpStatus < 300 ? FG.green : httpStatus < 400 ? FG.yellow : FG.red;
|
|
220
|
+
this.pipelineStep('\u{26A1}', 'RESULT', `${color}HTTP ${httpStatus}${RESET} ${DIM}(${durationMs}ms)${RESET}`, color);
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
executionError(error: string) {
|
|
224
|
+
this.pipelineStep('\u{1F4A5}', 'ERROR', `${FG.red}${error}${RESET}`, FG.red);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// ── Server lifecycle ──────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
serverStart(host: string, port: number, config: Record<string, unknown>) {
|
|
230
|
+
console.log('');
|
|
231
|
+
console.log(`${BOLD}${FG.cyan} ╔════════════════════════════════════════════╗${RESET}`);
|
|
232
|
+
console.log(`${BOLD}${FG.cyan} ║${RESET} ${BOLD}Palaryn Gateway${RESET} ${DIM}v0.1.0${RESET} ${BOLD}${FG.cyan}║${RESET}`);
|
|
233
|
+
console.log(`${BOLD}${FG.cyan} ╚════════════════════════════════════════════╝${RESET}`);
|
|
234
|
+
console.log('');
|
|
235
|
+
console.log(` ${FG.green}${BOLD}Server:${RESET} http://${host}:${port}`);
|
|
236
|
+
console.log(` ${FG.green}${BOLD}Admin:${RESET} http://${host}:${port}/admin`);
|
|
237
|
+
console.log(` ${FG.green}${BOLD}Health:${RESET} http://${host}:${port}/health`);
|
|
238
|
+
console.log(` ${FG.green}${BOLD}Metrics:${RESET} http://${host}:${port}/metrics`);
|
|
239
|
+
console.log('');
|
|
240
|
+
for (const [key, val] of Object.entries(config)) {
|
|
241
|
+
const display = typeof val === 'boolean'
|
|
242
|
+
? (val ? `${FG.green}enabled${RESET}` : `${FG.gray}disabled${RESET}`)
|
|
243
|
+
: `${FG.white}${val}${RESET}`;
|
|
244
|
+
console.log(` ${DIM}${key}:${RESET} ${display}`);
|
|
245
|
+
}
|
|
246
|
+
console.log('');
|
|
247
|
+
console.log(`${DIM} Waiting for requests...${RESET}`);
|
|
248
|
+
console.log('');
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
serverShutdown(signal: string) {
|
|
252
|
+
console.log('');
|
|
253
|
+
console.log(`${tag(BG.red, FG.white, 'SHUTDOWN')} ${signal} received`);
|
|
254
|
+
},
|
|
255
|
+
};
|