@wopr-network/platform-core 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/biome.json +61 -0
- package/dist/admin/admin-audit-log-repository.d.ts +33 -0
- package/dist/admin/admin-audit-log-repository.js +102 -0
- package/dist/admin/audit-log.d.ts +49 -0
- package/dist/admin/audit-log.js +63 -0
- package/dist/admin/index.d.ts +6 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/role-store.d.ts +37 -0
- package/dist/admin/role-store.js +106 -0
- package/dist/auth/api-key-repository.d.ts +11 -0
- package/dist/auth/api-key-repository.js +33 -0
- package/dist/auth/api-key-repository.test.d.ts +1 -0
- package/dist/auth/api-key-repository.test.js +46 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +140 -0
- package/dist/auth/better-auth.d.ts +42 -0
- package/dist/auth/better-auth.js +196 -0
- package/dist/auth/index.d.ts +186 -0
- package/dist/auth/index.js +422 -0
- package/dist/auth/login-history-repository.d.ts +14 -0
- package/dist/auth/login-history-repository.js +15 -0
- package/dist/auth/login-history-repository.test.d.ts +1 -0
- package/dist/auth/login-history-repository.test.js +47 -0
- package/dist/auth/middleware.d.ts +55 -0
- package/dist/auth/middleware.js +101 -0
- package/dist/auth/middleware.test.d.ts +1 -0
- package/dist/auth/middleware.test.js +213 -0
- package/dist/auth/scoped-tokens.test.d.ts +1 -0
- package/dist/auth/scoped-tokens.test.js +306 -0
- package/dist/auth/tenant-access.test.d.ts +1 -0
- package/dist/auth/tenant-access.test.js +62 -0
- package/dist/auth/user-creator.d.ts +9 -0
- package/dist/auth/user-creator.js +47 -0
- package/dist/auth/user-creator.test.d.ts +1 -0
- package/dist/auth/user-creator.test.js +78 -0
- package/dist/auth/user-role-repository.d.ts +31 -0
- package/dist/auth/user-role-repository.js +53 -0
- package/dist/auth/user-role-repository.test.d.ts +1 -0
- package/dist/auth/user-role-repository.test.js +122 -0
- package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
- package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
- package/dist/billing/index.d.ts +7 -0
- package/dist/billing/index.js +7 -0
- package/dist/billing/payment-processor.d.ts +127 -0
- package/dist/billing/payment-processor.js +8 -0
- package/dist/billing/payment-processor.test.d.ts +1 -0
- package/dist/billing/payment-processor.test.js +71 -0
- package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
- package/dist/billing/payram/charge-store.d.ts +41 -0
- package/dist/billing/payram/charge-store.js +72 -0
- package/dist/billing/payram/charge-store.test.d.ts +1 -0
- package/dist/billing/payram/charge-store.test.js +64 -0
- package/dist/billing/payram/checkout.d.ts +15 -0
- package/dist/billing/payram/checkout.js +24 -0
- package/dist/billing/payram/checkout.test.d.ts +1 -0
- package/dist/billing/payram/checkout.test.js +74 -0
- package/dist/billing/payram/client.d.ts +7 -0
- package/dist/billing/payram/client.js +15 -0
- package/dist/billing/payram/client.test.d.ts +1 -0
- package/dist/billing/payram/client.test.js +52 -0
- package/dist/billing/payram/index.d.ts +8 -0
- package/dist/billing/payram/index.js +4 -0
- package/dist/billing/payram/types.d.ts +40 -0
- package/dist/billing/payram/types.js +1 -0
- package/dist/billing/payram/webhook.d.ts +19 -0
- package/dist/billing/payram/webhook.js +67 -0
- package/dist/billing/payram/webhook.test.d.ts +7 -0
- package/dist/billing/payram/webhook.test.js +248 -0
- package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
- package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
- package/dist/billing/stripe/checkout.d.ts +20 -0
- package/dist/billing/stripe/checkout.js +63 -0
- package/dist/billing/stripe/checkout.test.d.ts +1 -0
- package/dist/billing/stripe/checkout.test.js +148 -0
- package/dist/billing/stripe/client.d.ts +14 -0
- package/dist/billing/stripe/client.js +33 -0
- package/dist/billing/stripe/client.test.d.ts +1 -0
- package/dist/billing/stripe/client.test.js +58 -0
- package/dist/billing/stripe/credit-prices.d.ts +63 -0
- package/dist/billing/stripe/credit-prices.js +81 -0
- package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
- package/dist/billing/stripe/credit-prices.test.js +87 -0
- package/dist/billing/stripe/index.d.ts +14 -0
- package/dist/billing/stripe/index.js +8 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
- package/dist/billing/stripe/payment-methods.d.ts +25 -0
- package/dist/billing/stripe/payment-methods.js +53 -0
- package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
- package/dist/billing/stripe/payment-methods.test.js +122 -0
- package/dist/billing/stripe/portal.d.ts +10 -0
- package/dist/billing/stripe/portal.js +16 -0
- package/dist/billing/stripe/portal.test.d.ts +1 -0
- package/dist/billing/stripe/portal.test.js +48 -0
- package/dist/billing/stripe/setup-intent.d.ts +16 -0
- package/dist/billing/stripe/setup-intent.js +22 -0
- package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
- package/dist/billing/stripe/setup-intent.test.js +58 -0
- package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
- package/dist/billing/stripe/stripe-payment-processor.js +166 -0
- package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
- package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
- package/dist/billing/stripe/tenant-store.d.ts +56 -0
- package/dist/billing/stripe/tenant-store.js +119 -0
- package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
- package/dist/billing/stripe/tenant-store.test.js +97 -0
- package/dist/billing/stripe/types.d.ts +49 -0
- package/dist/billing/stripe/types.js +1 -0
- package/dist/billing/webhook-seen-repository.d.ts +14 -0
- package/dist/billing/webhook-seen-repository.js +13 -0
- package/dist/config/billing-env.test.d.ts +1 -0
- package/dist/config/billing-env.test.js +48 -0
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.js +38 -0
- package/dist/config/logger.d.ts +2 -0
- package/dist/config/logger.js +11 -0
- package/dist/config/provider-endpoints.d.ts +6 -0
- package/dist/config/provider-endpoints.js +12 -0
- package/dist/credits/auto-topup-charge.d.ts +27 -0
- package/dist/credits/auto-topup-charge.js +139 -0
- package/dist/credits/auto-topup-charge.test.d.ts +1 -0
- package/dist/credits/auto-topup-charge.test.js +242 -0
- package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
- package/dist/credits/auto-topup-event-log-repository.js +18 -0
- package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
- package/dist/credits/auto-topup-schedule.d.ts +27 -0
- package/dist/credits/auto-topup-schedule.js +66 -0
- package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
- package/dist/credits/auto-topup-schedule.test.js +145 -0
- package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
- package/dist/credits/auto-topup-settings-repository.js +184 -0
- package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
- package/dist/credits/auto-topup-settings-repository.test.js +104 -0
- package/dist/credits/auto-topup-usage.d.ts +22 -0
- package/dist/credits/auto-topup-usage.js +56 -0
- package/dist/credits/auto-topup-usage.test.d.ts +1 -0
- package/dist/credits/auto-topup-usage.test.js +181 -0
- package/dist/credits/credit-expiry-cron.d.ts +19 -0
- package/dist/credits/credit-expiry-cron.js +50 -0
- package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
- package/dist/credits/credit-expiry-cron.test.js +67 -0
- package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
- package/dist/credits/credit-ledger-extra.test.js +40 -0
- package/dist/credits/credit-ledger.bench.d.ts +1 -0
- package/dist/credits/credit-ledger.bench.js +33 -0
- package/dist/credits/credit-ledger.d.ts +130 -0
- package/dist/credits/credit-ledger.js +293 -0
- package/dist/credits/credit-ledger.test.d.ts +4 -0
- package/dist/credits/credit-ledger.test.js +203 -0
- package/dist/credits/credit-transaction-repository.d.ts +17 -0
- package/dist/credits/credit-transaction-repository.js +35 -0
- package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
- package/dist/credits/credit-transaction-repository.test.js +232 -0
- package/dist/credits/credit.d.ts +75 -0
- package/dist/credits/credit.js +139 -0
- package/dist/credits/credit.test.d.ts +1 -0
- package/dist/credits/credit.test.js +196 -0
- package/dist/credits/dividend-cron.d.ts +29 -0
- package/dist/credits/dividend-cron.js +88 -0
- package/dist/credits/dividend-cron.test.d.ts +1 -0
- package/dist/credits/dividend-cron.test.js +128 -0
- package/dist/credits/dividend-repository.d.ts +29 -0
- package/dist/credits/dividend-repository.js +126 -0
- package/dist/credits/dividend-repository.test.d.ts +1 -0
- package/dist/credits/dividend-repository.test.js +176 -0
- package/dist/credits/index.d.ts +9 -0
- package/dist/credits/index.js +5 -0
- package/dist/credits/repository-types.d.ts +29 -0
- package/dist/credits/repository-types.js +1 -0
- package/dist/credits/signup-grant.d.ts +12 -0
- package/dist/credits/signup-grant.js +35 -0
- package/dist/credits/signup-grant.test.d.ts +1 -0
- package/dist/credits/signup-grant.test.js +51 -0
- package/dist/credits/tenant-customer-repository.d.ts +30 -0
- package/dist/credits/tenant-customer-repository.js +5 -0
- package/dist/db/auth-user-repository.d.ts +46 -0
- package/dist/db/auth-user-repository.js +90 -0
- package/dist/db/credit-column.d.ts +27 -0
- package/dist/db/credit-column.js +13 -0
- package/dist/db/index.d.ts +14 -0
- package/dist/db/index.js +8 -0
- package/dist/db/schema/account-deletion-requests.d.ts +203 -0
- package/dist/db/schema/account-deletion-requests.js +36 -0
- package/dist/db/schema/account-export-requests.d.ts +148 -0
- package/dist/db/schema/account-export-requests.js +19 -0
- package/dist/db/schema/admin-audit.d.ts +194 -0
- package/dist/db/schema/admin-audit.js +21 -0
- package/dist/db/schema/admin-users.d.ts +177 -0
- package/dist/db/schema/admin-users.js +23 -0
- package/dist/db/schema/affiliate-fraud.d.ts +160 -0
- package/dist/db/schema/affiliate-fraud.js +18 -0
- package/dist/db/schema/affiliate.d.ts +277 -0
- package/dist/db/schema/affiliate.js +32 -0
- package/dist/db/schema/coupon-codes.d.ts +143 -0
- package/dist/db/schema/coupon-codes.js +17 -0
- package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
- package/dist/db/schema/credit-auto-topup-settings.js +27 -0
- package/dist/db/schema/credit-auto-topup.d.ts +130 -0
- package/dist/db/schema/credit-auto-topup.js +21 -0
- package/dist/db/schema/credits.d.ts +283 -0
- package/dist/db/schema/credits.js +38 -0
- package/dist/db/schema/dividend-distributions.d.ts +130 -0
- package/dist/db/schema/dividend-distributions.js +19 -0
- package/dist/db/schema/email-notifications.d.ts +99 -0
- package/dist/db/schema/email-notifications.js +21 -0
- package/dist/db/schema/index.d.ts +33 -0
- package/dist/db/schema/index.js +33 -0
- package/dist/db/schema/meter-events.d.ts +599 -0
- package/dist/db/schema/meter-events.js +55 -0
- package/dist/db/schema/notification-preferences.d.ts +165 -0
- package/dist/db/schema/notification-preferences.js +18 -0
- package/dist/db/schema/notification-queue.d.ts +236 -0
- package/dist/db/schema/notification-queue.js +40 -0
- package/dist/db/schema/org-memberships.d.ts +63 -0
- package/dist/db/schema/org-memberships.js +15 -0
- package/dist/db/schema/organization-members.d.ts +235 -0
- package/dist/db/schema/organization-members.js +27 -0
- package/dist/db/schema/payram.d.ts +164 -0
- package/dist/db/schema/payram.js +21 -0
- package/dist/db/schema/platform-api-keys.d.ts +143 -0
- package/dist/db/schema/platform-api-keys.js +20 -0
- package/dist/db/schema/promotion-redemptions.d.ts +143 -0
- package/dist/db/schema/promotion-redemptions.js +18 -0
- package/dist/db/schema/promotions.d.ts +445 -0
- package/dist/db/schema/promotions.js +48 -0
- package/dist/db/schema/provider-credentials.d.ts +201 -0
- package/dist/db/schema/provider-credentials.js +36 -0
- package/dist/db/schema/rate-limit-entries.d.ts +75 -0
- package/dist/db/schema/rate-limit-entries.js +7 -0
- package/dist/db/schema/secret-audit-log.d.ts +109 -0
- package/dist/db/schema/secret-audit-log.js +15 -0
- package/dist/db/schema/session-usage.d.ts +194 -0
- package/dist/db/schema/session-usage.js +19 -0
- package/dist/db/schema/spending-limits.d.ts +92 -0
- package/dist/db/schema/spending-limits.js +8 -0
- package/dist/db/schema/tenant-addons.d.ts +58 -0
- package/dist/db/schema/tenant-addons.js +9 -0
- package/dist/db/schema/tenant-api-keys.d.ts +131 -0
- package/dist/db/schema/tenant-api-keys.js +21 -0
- package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
- package/dist/db/schema/tenant-capability-settings.js +12 -0
- package/dist/db/schema/tenant-customers.d.ts +303 -0
- package/dist/db/schema/tenant-customers.js +25 -0
- package/dist/db/schema/tenants.d.ts +126 -0
- package/dist/db/schema/tenants.js +18 -0
- package/dist/db/schema/user-roles.d.ts +98 -0
- package/dist/db/schema/user-roles.js +18 -0
- package/dist/db/schema/webhook-seen-events.d.ts +58 -0
- package/dist/db/schema/webhook-seen-events.js +9 -0
- package/dist/email/billing-emails.d.ts +51 -0
- package/dist/email/billing-emails.js +163 -0
- package/dist/email/billing-emails.test.d.ts +1 -0
- package/dist/email/billing-emails.test.js +162 -0
- package/dist/email/client.d.ts +51 -0
- package/dist/email/client.js +102 -0
- package/dist/email/client.test.d.ts +1 -0
- package/dist/email/client.test.js +120 -0
- package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
- package/dist/email/drizzle-billing-email-repository.js +36 -0
- package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
- package/dist/email/drizzle-billing-email-repository.test.js +42 -0
- package/dist/email/index.d.ts +33 -0
- package/dist/email/index.js +22 -0
- package/dist/email/notification-preferences-store.d.ts +12 -0
- package/dist/email/notification-preferences-store.js +82 -0
- package/dist/email/notification-preferences-store.test.d.ts +1 -0
- package/dist/email/notification-preferences-store.test.js +86 -0
- package/dist/email/notification-queue-store.d.ts +25 -0
- package/dist/email/notification-queue-store.js +97 -0
- package/dist/email/notification-queue-store.test.d.ts +1 -0
- package/dist/email/notification-queue-store.test.js +177 -0
- package/dist/email/notification-repository-types.d.ts +70 -0
- package/dist/email/notification-repository-types.js +6 -0
- package/dist/email/notification-service.d.ts +41 -0
- package/dist/email/notification-service.js +196 -0
- package/dist/email/notification-service.test.d.ts +1 -0
- package/dist/email/notification-service.test.js +160 -0
- package/dist/email/notification-templates.d.ts +18 -0
- package/dist/email/notification-templates.js +574 -0
- package/dist/email/notification-templates.test.d.ts +1 -0
- package/dist/email/notification-templates.test.js +238 -0
- package/dist/email/notification-worker.d.ts +24 -0
- package/dist/email/notification-worker.js +109 -0
- package/dist/email/notification-worker.test.d.ts +1 -0
- package/dist/email/notification-worker.test.js +153 -0
- package/dist/email/require-verified.d.ts +25 -0
- package/dist/email/require-verified.js +52 -0
- package/dist/email/require-verified.test.d.ts +1 -0
- package/dist/email/require-verified.test.js +62 -0
- package/dist/email/resend-adapter.d.ts +47 -0
- package/dist/email/resend-adapter.js +137 -0
- package/dist/email/resend-adapter.test.d.ts +1 -0
- package/dist/email/resend-adapter.test.js +190 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/templates.js +359 -0
- package/dist/email/templates.test.d.ts +1 -0
- package/dist/email/templates.test.js +170 -0
- package/dist/email/verification.d.ts +42 -0
- package/dist/email/verification.js +83 -0
- package/dist/email/verification.test.d.ts +1 -0
- package/dist/email/verification.test.js +141 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +23 -0
- package/dist/metering/aggregator.d.ts +54 -0
- package/dist/metering/aggregator.js +123 -0
- package/dist/metering/aggregator.test.d.ts +1 -0
- package/dist/metering/aggregator.test.js +179 -0
- package/dist/metering/dlq.d.ts +31 -0
- package/dist/metering/dlq.js +82 -0
- package/dist/metering/dlq.test.d.ts +1 -0
- package/dist/metering/dlq.test.js +117 -0
- package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
- package/dist/metering/drizzle-usage-summary-repository.js +98 -0
- package/dist/metering/emitter.d.ts +66 -0
- package/dist/metering/emitter.js +185 -0
- package/dist/metering/emitter.test.d.ts +1 -0
- package/dist/metering/emitter.test.js +171 -0
- package/dist/metering/index.d.ts +11 -0
- package/dist/metering/index.js +5 -0
- package/dist/metering/load-test.bench.d.ts +1 -0
- package/dist/metering/load-test.bench.js +103 -0
- package/dist/metering/meter-event-repository.d.ts +33 -0
- package/dist/metering/meter-event-repository.js +58 -0
- package/dist/metering/meter-repositories.test.d.ts +1 -0
- package/dist/metering/meter-repositories.test.js +419 -0
- package/dist/metering/metering.test.d.ts +1 -0
- package/dist/metering/metering.test.js +1046 -0
- package/dist/metering/reconciliation-cron.d.ts +37 -0
- package/dist/metering/reconciliation-cron.js +85 -0
- package/dist/metering/reconciliation-cron.test.d.ts +1 -0
- package/dist/metering/reconciliation-cron.test.js +162 -0
- package/dist/metering/reconciliation-repository.d.ts +27 -0
- package/dist/metering/reconciliation-repository.js +43 -0
- package/dist/metering/reconciliation-repository.test.d.ts +1 -0
- package/dist/metering/reconciliation-repository.test.js +160 -0
- package/dist/metering/types.d.ts +88 -0
- package/dist/metering/types.js +1 -0
- package/dist/metering/wal.d.ts +49 -0
- package/dist/metering/wal.js +124 -0
- package/dist/metering/wal.test.d.ts +1 -0
- package/dist/metering/wal.test.js +175 -0
- package/dist/middleware/csrf.d.ts +24 -0
- package/dist/middleware/csrf.js +80 -0
- package/dist/middleware/csrf.test.d.ts +1 -0
- package/dist/middleware/csrf.test.js +152 -0
- package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
- package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
- package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
- package/dist/middleware/get-client-ip.d.ts +22 -0
- package/dist/middleware/get-client-ip.js +51 -0
- package/dist/middleware/get-client-ip.test.d.ts +1 -0
- package/dist/middleware/get-client-ip.test.js +40 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/rate-limit-repository.d.ts +19 -0
- package/dist/middleware/rate-limit-repository.js +1 -0
- package/dist/middleware/rate-limit.d.ts +57 -0
- package/dist/middleware/rate-limit.js +109 -0
- package/dist/middleware/rate-limit.test.d.ts +1 -0
- package/dist/middleware/rate-limit.test.js +247 -0
- package/dist/security/credential-vault/audit-repository.d.ts +27 -0
- package/dist/security/credential-vault/audit-repository.js +42 -0
- package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/audit-repository.test.js +78 -0
- package/dist/security/credential-vault/credential-repository.d.ts +94 -0
- package/dist/security/credential-vault/credential-repository.js +145 -0
- package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
- package/dist/security/credential-vault/credential-repository.test.js +206 -0
- package/dist/security/credential-vault/index.d.ts +12 -0
- package/dist/security/credential-vault/index.js +6 -0
- package/dist/security/credential-vault/key-rotation.d.ts +18 -0
- package/dist/security/credential-vault/key-rotation.js +52 -0
- package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
- package/dist/security/credential-vault/key-rotation.test.js +95 -0
- package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
- package/dist/security/credential-vault/migrate-plaintext.js +80 -0
- package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
- package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
- package/dist/security/credential-vault/migration-check.d.ts +15 -0
- package/dist/security/credential-vault/migration-check.js +71 -0
- package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
- package/dist/security/credential-vault/migration-check.test.js +457 -0
- package/dist/security/credential-vault/store.d.ts +106 -0
- package/dist/security/credential-vault/store.js +181 -0
- package/dist/security/credential-vault/store.test.d.ts +1 -0
- package/dist/security/credential-vault/store.test.js +482 -0
- package/dist/security/encryption.d.ts +22 -0
- package/dist/security/encryption.js +53 -0
- package/dist/security/encryption.test.d.ts +1 -0
- package/dist/security/encryption.test.js +95 -0
- package/dist/security/host-validation.d.ts +11 -0
- package/dist/security/host-validation.js +108 -0
- package/dist/security/host-validation.test.d.ts +1 -0
- package/dist/security/host-validation.test.js +106 -0
- package/dist/security/index.d.ts +11 -0
- package/dist/security/index.js +11 -0
- package/dist/security/key-audit.d.ts +16 -0
- package/dist/security/key-audit.js +35 -0
- package/dist/security/key-audit.test.d.ts +1 -0
- package/dist/security/key-audit.test.js +50 -0
- package/dist/security/key-injection.d.ts +28 -0
- package/dist/security/key-injection.js +57 -0
- package/dist/security/key-injection.test.d.ts +1 -0
- package/dist/security/key-injection.test.js +97 -0
- package/dist/security/key-validation.d.ts +16 -0
- package/dist/security/key-validation.js +78 -0
- package/dist/security/key-validation.test.d.ts +1 -0
- package/dist/security/key-validation.test.js +87 -0
- package/dist/security/redirect-allowlist.d.ts +6 -0
- package/dist/security/redirect-allowlist.js +36 -0
- package/dist/security/redirect-allowlist.test.d.ts +1 -0
- package/dist/security/redirect-allowlist.test.js +55 -0
- package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
- package/dist/security/tenant-keys/capability-settings-store.js +33 -0
- package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
- package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
- package/dist/security/tenant-keys/index.d.ts +10 -0
- package/dist/security/tenant-keys/index.js +5 -0
- package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
- package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
- package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
- package/dist/security/tenant-keys/key-resolution.js +59 -0
- package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/key-resolution.test.js +97 -0
- package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
- package/dist/security/tenant-keys/org-key-resolution.js +50 -0
- package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
- package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
- package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
- package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
- package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
- package/dist/security/types.d.ts +35 -0
- package/dist/security/types.js +15 -0
- package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
- package/dist/tenancy/drizzle-org-repository.js +126 -0
- package/dist/tenancy/index.d.ts +6 -0
- package/dist/tenancy/index.js +3 -0
- package/dist/tenancy/org-member-repository.d.ts +57 -0
- package/dist/tenancy/org-member-repository.js +99 -0
- package/dist/tenancy/org-repository.test.d.ts +1 -0
- package/dist/tenancy/org-repository.test.js +143 -0
- package/dist/tenancy/org-service.d.ts +70 -0
- package/dist/tenancy/org-service.js +223 -0
- package/dist/tenancy/org-service.test.d.ts +1 -0
- package/dist/tenancy/org-service.test.js +550 -0
- package/dist/test/db.d.ts +33 -0
- package/dist/test/db.js +65 -0
- package/dist/trpc/index.d.ts +1 -0
- package/dist/trpc/index.js +1 -0
- package/dist/trpc/init.d.ts +49 -0
- package/dist/trpc/init.js +108 -0
- package/dist/trpc/init.test.d.ts +1 -0
- package/dist/trpc/init.test.js +154 -0
- package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
- package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
- package/drizzle/migrations/meta/_journal.json +13 -0
- package/drizzle.config.ts +41 -0
- package/package.json +64 -0
- package/src/admin/admin-audit-log-repository.ts +135 -0
- package/src/admin/audit-log.ts +111 -0
- package/src/admin/index.ts +6 -0
- package/src/admin/role-store.ts +134 -0
- package/src/auth/api-key-repository.test.ts +63 -0
- package/src/auth/api-key-repository.ts +46 -0
- package/src/auth/auth.test.ts +166 -0
- package/src/auth/better-auth.ts +216 -0
- package/src/auth/index.ts +520 -0
- package/src/auth/login-history-repository.test.ts +54 -0
- package/src/auth/login-history-repository.ts +28 -0
- package/src/auth/middleware.test.ts +264 -0
- package/src/auth/middleware.ts +117 -0
- package/src/auth/scoped-tokens.test.ts +362 -0
- package/src/auth/tenant-access.test.ts +69 -0
- package/src/auth/user-creator.test.ts +98 -0
- package/src/auth/user-creator.ts +54 -0
- package/src/auth/user-role-repository.test.ts +149 -0
- package/src/auth/user-role-repository.ts +67 -0
- package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
- package/src/billing/index.ts +22 -0
- package/src/billing/payment-processor.test.ts +93 -0
- package/src/billing/payment-processor.ts +150 -0
- package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
- package/src/billing/payram/charge-store.test.ts +84 -0
- package/src/billing/payram/charge-store.ts +109 -0
- package/src/billing/payram/checkout.test.ts +99 -0
- package/src/billing/payram/checkout.ts +40 -0
- package/src/billing/payram/client.test.ts +62 -0
- package/src/billing/payram/client.ts +21 -0
- package/src/billing/payram/index.ts +14 -0
- package/src/billing/payram/types.ts +44 -0
- package/src/billing/payram/webhook.test.ts +318 -0
- package/src/billing/payram/webhook.ts +97 -0
- package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
- package/src/billing/stripe/checkout.test.ts +186 -0
- package/src/billing/stripe/checkout.ts +82 -0
- package/src/billing/stripe/client.test.ts +64 -0
- package/src/billing/stripe/client.ts +39 -0
- package/src/billing/stripe/credit-prices.test.ts +114 -0
- package/src/billing/stripe/credit-prices.ts +113 -0
- package/src/billing/stripe/index.ts +14 -0
- package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
- package/src/billing/stripe/payment-methods.test.ts +157 -0
- package/src/billing/stripe/payment-methods.ts +76 -0
- package/src/billing/stripe/portal.test.ts +63 -0
- package/src/billing/stripe/portal.ts +25 -0
- package/src/billing/stripe/setup-intent.test.ts +78 -0
- package/src/billing/stripe/setup-intent.ts +34 -0
- package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
- package/src/billing/stripe/stripe-payment-processor.ts +255 -0
- package/src/billing/stripe/tenant-store.test.ts +124 -0
- package/src/billing/stripe/tenant-store.ts +151 -0
- package/src/billing/stripe/types.ts +53 -0
- package/src/billing/webhook-seen-repository.ts +24 -0
- package/src/config/billing-env.test.ts +54 -0
- package/src/config/index.ts +44 -0
- package/src/config/logger.ts +12 -0
- package/src/config/provider-endpoints.ts +14 -0
- package/src/credits/auto-topup-charge.test.ts +292 -0
- package/src/credits/auto-topup-charge.ts +171 -0
- package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
- package/src/credits/auto-topup-event-log-repository.ts +30 -0
- package/src/credits/auto-topup-schedule.test.ts +179 -0
- package/src/credits/auto-topup-schedule.ts +93 -0
- package/src/credits/auto-topup-settings-repository.test.ts +123 -0
- package/src/credits/auto-topup-settings-repository.ts +245 -0
- package/src/credits/auto-topup-usage.test.ts +220 -0
- package/src/credits/auto-topup-usage.ts +68 -0
- package/src/credits/credit-expiry-cron.test.ts +125 -0
- package/src/credits/credit-expiry-cron.ts +76 -0
- package/src/credits/credit-ledger-extra.test.ts +57 -0
- package/src/credits/credit-ledger.bench.ts +56 -0
- package/src/credits/credit-ledger.test.ts +276 -0
- package/src/credits/credit-ledger.ts +450 -0
- package/src/credits/credit-transaction-repository.test.ts +274 -0
- package/src/credits/credit-transaction-repository.ts +62 -0
- package/src/credits/credit.test.ts +234 -0
- package/src/credits/credit.ts +160 -0
- package/src/credits/dividend-cron.test.ts +158 -0
- package/src/credits/dividend-cron.ts +127 -0
- package/src/credits/dividend-repository.test.ts +223 -0
- package/src/credits/dividend-repository.ts +182 -0
- package/src/credits/index.ts +25 -0
- package/src/credits/repository-types.ts +33 -0
- package/src/credits/signup-grant.test.ts +63 -0
- package/src/credits/signup-grant.ts +44 -0
- package/src/credits/tenant-customer-repository.ts +28 -0
- package/src/db/auth-user-repository.ts +124 -0
- package/src/db/credit-column.ts +17 -0
- package/src/db/index.ts +21 -0
- package/src/db/schema/account-deletion-requests.ts +41 -0
- package/src/db/schema/account-export-requests.ts +24 -0
- package/src/db/schema/admin-audit.ts +26 -0
- package/src/db/schema/admin-users.ts +31 -0
- package/src/db/schema/affiliate-fraud.ts +23 -0
- package/src/db/schema/affiliate.ts +38 -0
- package/src/db/schema/coupon-codes.ts +22 -0
- package/src/db/schema/credit-auto-topup-settings.ts +32 -0
- package/src/db/schema/credit-auto-topup.ts +26 -0
- package/src/db/schema/credits.ts +44 -0
- package/src/db/schema/dividend-distributions.ts +24 -0
- package/src/db/schema/email-notifications.ts +26 -0
- package/src/db/schema/index.ts +33 -0
- package/src/db/schema/meter-events.ts +70 -0
- package/src/db/schema/notification-preferences.ts +19 -0
- package/src/db/schema/notification-queue.ts +45 -0
- package/src/db/schema/org-memberships.ts +20 -0
- package/src/db/schema/organization-members.ts +37 -0
- package/src/db/schema/payram.ts +26 -0
- package/src/db/schema/platform-api-keys.ts +25 -0
- package/src/db/schema/promotion-redemptions.ts +23 -0
- package/src/db/schema/promotions.ts +57 -0
- package/src/db/schema/provider-credentials.ts +41 -0
- package/src/db/schema/rate-limit-entries.ts +12 -0
- package/src/db/schema/secret-audit-log.ts +20 -0
- package/src/db/schema/session-usage.ts +24 -0
- package/src/db/schema/spending-limits.ts +9 -0
- package/src/db/schema/tenant-addons.ts +14 -0
- package/src/db/schema/tenant-api-keys.ts +26 -0
- package/src/db/schema/tenant-capability-settings.ts +17 -0
- package/src/db/schema/tenant-customers.ts +35 -0
- package/src/db/schema/tenants.ts +23 -0
- package/src/db/schema/user-roles.ts +23 -0
- package/src/db/schema/webhook-seen-events.ts +14 -0
- package/src/email/billing-emails.test.ts +198 -0
- package/src/email/billing-emails.ts +211 -0
- package/src/email/client.test.ts +149 -0
- package/src/email/client.ts +137 -0
- package/src/email/drizzle-billing-email-repository.test.ts +52 -0
- package/src/email/drizzle-billing-email-repository.ts +59 -0
- package/src/email/index.ts +57 -0
- package/src/email/notification-preferences-store.test.ts +102 -0
- package/src/email/notification-preferences-store.ts +90 -0
- package/src/email/notification-queue-store.test.ts +215 -0
- package/src/email/notification-queue-store.ts +127 -0
- package/src/email/notification-repository-types.ts +101 -0
- package/src/email/notification-service.test.ts +178 -0
- package/src/email/notification-service.ts +265 -0
- package/src/email/notification-templates.test.ts +261 -0
- package/src/email/notification-templates.ts +727 -0
- package/src/email/notification-worker.test.ts +189 -0
- package/src/email/notification-worker.ts +133 -0
- package/src/email/require-verified.ts +65 -0
- package/src/email/resend-adapter.test.ts +253 -0
- package/src/email/resend-adapter.ts +157 -0
- package/src/email/templates.test.ts +217 -0
- package/src/email/templates.ts +469 -0
- package/src/email/verification.test.ts +185 -0
- package/src/email/verification.ts +110 -0
- package/src/index.ts +51 -0
- package/src/metering/aggregator.test.ts +239 -0
- package/src/metering/aggregator.ts +160 -0
- package/src/metering/dlq.test.ts +134 -0
- package/src/metering/dlq.ts +102 -0
- package/src/metering/drizzle-usage-summary-repository.ts +167 -0
- package/src/metering/emitter.test.ts +202 -0
- package/src/metering/emitter.ts +227 -0
- package/src/metering/index.ts +21 -0
- package/src/metering/load-test.bench.ts +130 -0
- package/src/metering/meter-event-repository.ts +87 -0
- package/src/metering/meter-repositories.test.ts +491 -0
- package/src/metering/metering.test.ts +1317 -0
- package/src/metering/reconciliation-cron.test.ts +202 -0
- package/src/metering/reconciliation-cron.ts +134 -0
- package/src/metering/reconciliation-repository.test.ts +196 -0
- package/src/metering/reconciliation-repository.ts +83 -0
- package/src/metering/types.ts +93 -0
- package/src/metering/wal.test.ts +222 -0
- package/src/metering/wal.ts +139 -0
- package/src/middleware/csrf.test.ts +178 -0
- package/src/middleware/csrf.ts +101 -0
- package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
- package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
- package/src/middleware/get-client-ip.test.ts +49 -0
- package/src/middleware/get-client-ip.ts +62 -0
- package/src/middleware/index.ts +12 -0
- package/src/middleware/rate-limit-repository.ts +22 -0
- package/src/middleware/rate-limit.test.ts +338 -0
- package/src/middleware/rate-limit.ts +169 -0
- package/src/security/credential-vault/audit-repository.test.ts +91 -0
- package/src/security/credential-vault/audit-repository.ts +64 -0
- package/src/security/credential-vault/credential-repository.test.ts +264 -0
- package/src/security/credential-vault/credential-repository.ts +233 -0
- package/src/security/credential-vault/index.ts +26 -0
- package/src/security/credential-vault/key-rotation.test.ts +139 -0
- package/src/security/credential-vault/key-rotation.ts +70 -0
- package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
- package/src/security/credential-vault/migrate-plaintext.ts +101 -0
- package/src/security/credential-vault/migration-check.test.ts +533 -0
- package/src/security/credential-vault/migration-check.ts +88 -0
- package/src/security/credential-vault/store.test.ts +569 -0
- package/src/security/credential-vault/store.ts +284 -0
- package/src/security/encryption.test.ts +114 -0
- package/src/security/encryption.ts +65 -0
- package/src/security/host-validation.test.ts +136 -0
- package/src/security/host-validation.ts +116 -0
- package/src/security/index.ts +59 -0
- package/src/security/key-audit.test.ts +57 -0
- package/src/security/key-audit.ts +45 -0
- package/src/security/key-injection.test.ts +131 -0
- package/src/security/key-injection.ts +71 -0
- package/src/security/key-validation.test.ts +111 -0
- package/src/security/key-validation.ts +84 -0
- package/src/security/redirect-allowlist.test.ts +70 -0
- package/src/security/redirect-allowlist.ts +35 -0
- package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
- package/src/security/tenant-keys/capability-settings-store.ts +53 -0
- package/src/security/tenant-keys/index.ts +10 -0
- package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
- package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
- package/src/security/tenant-keys/key-resolution.test.ts +173 -0
- package/src/security/tenant-keys/key-resolution.ts +87 -0
- package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
- package/src/security/tenant-keys/org-key-resolution.ts +76 -0
- package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
- package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
- package/src/security/types.ts +43 -0
- package/src/tenancy/drizzle-org-repository.ts +169 -0
- package/src/tenancy/index.ts +6 -0
- package/src/tenancy/org-member-repository.ts +159 -0
- package/src/tenancy/org-repository.test.ts +172 -0
- package/src/tenancy/org-service.test.ts +634 -0
- package/src/tenancy/org-service.ts +290 -0
- package/src/test/db.ts +97 -0
- package/src/trpc/index.ts +11 -0
- package/src/trpc/init.test.ts +196 -0
- package/src/trpc/init.ts +138 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
const ALGORITHM = "aes-256-gcm";
|
|
3
|
+
const IV_BYTES = 16;
|
|
4
|
+
const KEY_BYTES = 32;
|
|
5
|
+
/**
|
|
6
|
+
* Derive a per-instance encryption key from the instance ID and a platform secret.
|
|
7
|
+
* Uses HMAC-SHA256 so the platform never stores the raw key — it's deterministic
|
|
8
|
+
* from (instanceId + platformSecret) but the secret lives only in Docker secrets.
|
|
9
|
+
*/
|
|
10
|
+
export function deriveInstanceKey(instanceId, platformSecret) {
|
|
11
|
+
return createHmac("sha256", platformSecret).update(instanceId).digest();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate a random 32-byte encryption key.
|
|
15
|
+
* Used when creating a new instance to produce a Docker secret.
|
|
16
|
+
*/
|
|
17
|
+
export function generateInstanceKey() {
|
|
18
|
+
return randomBytes(KEY_BYTES);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Encrypt a plaintext string with AES-256-GCM.
|
|
22
|
+
* Returns a structured payload with iv, authTag, and ciphertext (all hex-encoded).
|
|
23
|
+
*/
|
|
24
|
+
export function encrypt(plaintext, key) {
|
|
25
|
+
if (key.length !== KEY_BYTES) {
|
|
26
|
+
throw new Error(`Encryption key must be ${KEY_BYTES} bytes, got ${key.length}`);
|
|
27
|
+
}
|
|
28
|
+
const iv = randomBytes(IV_BYTES);
|
|
29
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
30
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
31
|
+
const authTag = cipher.getAuthTag();
|
|
32
|
+
return {
|
|
33
|
+
iv: iv.toString("hex"),
|
|
34
|
+
authTag: authTag.toString("hex"),
|
|
35
|
+
ciphertext: encrypted.toString("hex"),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Decrypt an AES-256-GCM encrypted payload back to plaintext.
|
|
40
|
+
* Throws on tampered data or wrong key.
|
|
41
|
+
*/
|
|
42
|
+
export function decrypt(payload, key) {
|
|
43
|
+
if (key.length !== KEY_BYTES) {
|
|
44
|
+
throw new Error(`Encryption key must be ${KEY_BYTES} bytes, got ${key.length}`);
|
|
45
|
+
}
|
|
46
|
+
const iv = Buffer.from(payload.iv, "hex");
|
|
47
|
+
const authTag = Buffer.from(payload.authTag, "hex");
|
|
48
|
+
const ciphertext = Buffer.from(payload.ciphertext, "hex");
|
|
49
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
50
|
+
decipher.setAuthTag(authTag);
|
|
51
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
52
|
+
return decrypted.toString("utf-8");
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { decrypt, deriveInstanceKey, encrypt, generateInstanceKey } from "./encryption.js";
|
|
4
|
+
describe("encryption", () => {
|
|
5
|
+
describe("generateInstanceKey", () => {
|
|
6
|
+
it("returns a 32-byte buffer", () => {
|
|
7
|
+
const key = generateInstanceKey();
|
|
8
|
+
expect(key).toBeInstanceOf(Buffer);
|
|
9
|
+
expect(key.length).toBe(32);
|
|
10
|
+
});
|
|
11
|
+
it("generates unique keys", () => {
|
|
12
|
+
const a = generateInstanceKey();
|
|
13
|
+
const b = generateInstanceKey();
|
|
14
|
+
expect(a.equals(b)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe("deriveInstanceKey", () => {
|
|
18
|
+
it("returns a 32-byte buffer", () => {
|
|
19
|
+
const key = deriveInstanceKey("instance-123", "platform-secret");
|
|
20
|
+
expect(key).toBeInstanceOf(Buffer);
|
|
21
|
+
expect(key.length).toBe(32);
|
|
22
|
+
});
|
|
23
|
+
it("is deterministic for the same inputs", () => {
|
|
24
|
+
const a = deriveInstanceKey("instance-123", "secret");
|
|
25
|
+
const b = deriveInstanceKey("instance-123", "secret");
|
|
26
|
+
expect(a.equals(b)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it("differs for different instance IDs", () => {
|
|
29
|
+
const a = deriveInstanceKey("instance-1", "secret");
|
|
30
|
+
const b = deriveInstanceKey("instance-2", "secret");
|
|
31
|
+
expect(a.equals(b)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it("differs for different secrets", () => {
|
|
34
|
+
const a = deriveInstanceKey("instance-1", "secret-a");
|
|
35
|
+
const b = deriveInstanceKey("instance-1", "secret-b");
|
|
36
|
+
expect(a.equals(b)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("encrypt / decrypt round-trip", () => {
|
|
40
|
+
const key = generateInstanceKey();
|
|
41
|
+
it("encrypts and decrypts a simple string", () => {
|
|
42
|
+
const plaintext = "hello world";
|
|
43
|
+
const encrypted = encrypt(plaintext, key);
|
|
44
|
+
const decrypted = decrypt(encrypted, key);
|
|
45
|
+
expect(decrypted).toBe(plaintext);
|
|
46
|
+
});
|
|
47
|
+
it("encrypts and decrypts JSON secrets", () => {
|
|
48
|
+
const secrets = JSON.stringify({ ANTHROPIC_API_KEY: "sk-ant-test123", DISCORD_TOKEN: "token123" });
|
|
49
|
+
const encrypted = encrypt(secrets, key);
|
|
50
|
+
const decrypted = decrypt(encrypted, key);
|
|
51
|
+
expect(decrypted).toBe(secrets);
|
|
52
|
+
});
|
|
53
|
+
it("encrypts and decrypts empty string", () => {
|
|
54
|
+
const encrypted = encrypt("", key);
|
|
55
|
+
const decrypted = decrypt(encrypted, key);
|
|
56
|
+
expect(decrypted).toBe("");
|
|
57
|
+
});
|
|
58
|
+
it("produces different ciphertext for same plaintext (random IV)", () => {
|
|
59
|
+
const plaintext = "same data";
|
|
60
|
+
const a = encrypt(plaintext, key);
|
|
61
|
+
const b = encrypt(plaintext, key);
|
|
62
|
+
expect(a.ciphertext).not.toBe(b.ciphertext);
|
|
63
|
+
expect(a.iv).not.toBe(b.iv);
|
|
64
|
+
});
|
|
65
|
+
it("returns hex-encoded fields", () => {
|
|
66
|
+
const encrypted = encrypt("test", key);
|
|
67
|
+
expect(encrypted.iv).toMatch(/^[0-9a-f]+$/);
|
|
68
|
+
expect(encrypted.authTag).toMatch(/^[0-9a-f]+$/);
|
|
69
|
+
expect(encrypted.ciphertext).toMatch(/^[0-9a-f]+$/);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("decrypt error cases", () => {
|
|
73
|
+
const key = generateInstanceKey();
|
|
74
|
+
it("throws with wrong key", () => {
|
|
75
|
+
const encrypted = encrypt("secret data", key);
|
|
76
|
+
const wrongKey = generateInstanceKey();
|
|
77
|
+
expect(() => decrypt(encrypted, wrongKey)).toThrow();
|
|
78
|
+
});
|
|
79
|
+
it("throws with tampered ciphertext", () => {
|
|
80
|
+
const encrypted = encrypt("secret data", key);
|
|
81
|
+
const tampered = { ...encrypted, ciphertext: "deadbeef".repeat(8) };
|
|
82
|
+
expect(() => decrypt(tampered, tampered.iv.length > 0 ? key : key)).toThrow();
|
|
83
|
+
});
|
|
84
|
+
it("throws with tampered auth tag", () => {
|
|
85
|
+
const encrypted = encrypt("secret data", key);
|
|
86
|
+
const tampered = { ...encrypted, authTag: "00".repeat(16) };
|
|
87
|
+
expect(() => decrypt(tampered, key)).toThrow();
|
|
88
|
+
});
|
|
89
|
+
it("throws with wrong key length", () => {
|
|
90
|
+
const shortKey = randomBytes(16);
|
|
91
|
+
expect(() => encrypt("test", shortKey)).toThrow("Encryption key must be 32 bytes");
|
|
92
|
+
expect(() => decrypt({ iv: "", authTag: "", ciphertext: "" }, shortKey)).toThrow("Encryption key must be 32 bytes");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a node registration host to prevent SSRF.
|
|
3
|
+
* Blocks loopback, link-local, multicast, broadcast, and unspecified addresses always.
|
|
4
|
+
* Blocks private ranges (10.x, 172.16-31.x, 192.168.x, fc00::/7) unless
|
|
5
|
+
* ALLOW_PRIVATE_NODE_HOSTS=true (for VPC deployments).
|
|
6
|
+
*
|
|
7
|
+
* NOTE: This validates the host string only. It does NOT perform DNS resolution,
|
|
8
|
+
* so hostnames that resolve to private IPs (e.g., via DNS rebinding or nip.io tricks)
|
|
9
|
+
* are not caught here. Out-of-scope for this issue.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateNodeHost(host: string): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
/**
|
|
3
|
+
* Validate a node registration host to prevent SSRF.
|
|
4
|
+
* Blocks loopback, link-local, multicast, broadcast, and unspecified addresses always.
|
|
5
|
+
* Blocks private ranges (10.x, 172.16-31.x, 192.168.x, fc00::/7) unless
|
|
6
|
+
* ALLOW_PRIVATE_NODE_HOSTS=true (for VPC deployments).
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This validates the host string only. It does NOT perform DNS resolution,
|
|
9
|
+
* so hostnames that resolve to private IPs (e.g., via DNS rebinding or nip.io tricks)
|
|
10
|
+
* are not caught here. Out-of-scope for this issue.
|
|
11
|
+
*/
|
|
12
|
+
export function validateNodeHost(host) {
|
|
13
|
+
const trimmed = host.trim();
|
|
14
|
+
if (trimmed.length === 0) {
|
|
15
|
+
throw new Error("Invalid node host: empty");
|
|
16
|
+
}
|
|
17
|
+
// Check for "localhost" hostname
|
|
18
|
+
if (trimmed.toLowerCase() === "localhost") {
|
|
19
|
+
throw new Error("Invalid node host: loopback hostname");
|
|
20
|
+
}
|
|
21
|
+
const ipVersion = isIP(trimmed);
|
|
22
|
+
if (ipVersion === 4) {
|
|
23
|
+
validateIPv4(trimmed);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (ipVersion === 6) {
|
|
27
|
+
validateIPv6(trimmed);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Reject malformed IPv4-looking strings (e.g. 999.999.999.999) that isIP()
|
|
31
|
+
// returns 0 for but validateHostname() would accept as digit-only labels.
|
|
32
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(trimmed)) {
|
|
33
|
+
throw new Error("Invalid node host: malformed IPv4 address");
|
|
34
|
+
}
|
|
35
|
+
// Not an IP — treat as hostname
|
|
36
|
+
validateHostname(trimmed);
|
|
37
|
+
}
|
|
38
|
+
function validateIPv4(ip) {
|
|
39
|
+
const parts = ip.split(".").map(Number);
|
|
40
|
+
const [a, b] = parts;
|
|
41
|
+
// Unspecified
|
|
42
|
+
if (a === 0)
|
|
43
|
+
throw new Error("Invalid node host: unspecified address");
|
|
44
|
+
// Loopback — ALWAYS blocked
|
|
45
|
+
if (a === 127)
|
|
46
|
+
throw new Error("Invalid node host: loopback address");
|
|
47
|
+
// Link-local — ALWAYS blocked
|
|
48
|
+
if (a === 169 && b === 254)
|
|
49
|
+
throw new Error("Invalid node host: link-local address");
|
|
50
|
+
// Multicast
|
|
51
|
+
if (a >= 224 && a <= 239)
|
|
52
|
+
throw new Error("Invalid node host: multicast address");
|
|
53
|
+
// Broadcast
|
|
54
|
+
if (a === 255 && b === 255)
|
|
55
|
+
throw new Error("Invalid node host: broadcast address");
|
|
56
|
+
// Private ranges — blocked unless ALLOW_PRIVATE_NODE_HOSTS
|
|
57
|
+
if (!allowPrivateHosts()) {
|
|
58
|
+
if (a === 10)
|
|
59
|
+
throw new Error("Invalid node host: private address");
|
|
60
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
61
|
+
throw new Error("Invalid node host: private address");
|
|
62
|
+
if (a === 192 && b === 168)
|
|
63
|
+
throw new Error("Invalid node host: private address");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function validateIPv6(ip) {
|
|
67
|
+
const normalized = ip.toLowerCase();
|
|
68
|
+
// Loopback ::1 — ALWAYS blocked
|
|
69
|
+
if (normalized === "::1")
|
|
70
|
+
throw new Error("Invalid node host: loopback address");
|
|
71
|
+
// Unspecified :: — ALWAYS blocked
|
|
72
|
+
if (normalized === "::")
|
|
73
|
+
throw new Error("Invalid node host: unspecified address");
|
|
74
|
+
// IPv6-mapped IPv4 — check the embedded IPv4
|
|
75
|
+
const v4MappedMatch = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
76
|
+
if (v4MappedMatch) {
|
|
77
|
+
validateIPv4(v4MappedMatch[1]);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Link-local fe80::/10 — ALWAYS blocked
|
|
81
|
+
if (normalized.startsWith("fe80:") || normalized.startsWith("fe80")) {
|
|
82
|
+
throw new Error("Invalid node host: link-local address");
|
|
83
|
+
}
|
|
84
|
+
// Unique local fc00::/7 (fc00:: and fd00::) — private
|
|
85
|
+
if (!allowPrivateHosts()) {
|
|
86
|
+
if (normalized.startsWith("fc") || normalized.startsWith("fd")) {
|
|
87
|
+
throw new Error("Invalid node host: private address");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function validateHostname(host) {
|
|
92
|
+
if (host.length > 253)
|
|
93
|
+
throw new Error("Invalid node host: hostname too long");
|
|
94
|
+
if (host.startsWith(".") || host.endsWith("."))
|
|
95
|
+
throw new Error("Invalid node host: invalid hostname");
|
|
96
|
+
// RFC 1123: labels separated by dots, alphanumeric + hyphens
|
|
97
|
+
const labelPattern = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
98
|
+
const labels = host.split(".");
|
|
99
|
+
for (const label of labels) {
|
|
100
|
+
if (label.length === 0 || label.length > 63)
|
|
101
|
+
throw new Error("Invalid node host: invalid hostname");
|
|
102
|
+
if (!labelPattern.test(label))
|
|
103
|
+
throw new Error("Invalid node host: invalid hostname");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function allowPrivateHosts() {
|
|
107
|
+
return process.env.ALLOW_PRIVATE_NODE_HOSTS === "true";
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { validateNodeHost } from "./host-validation.js";
|
|
3
|
+
describe("validateNodeHost", () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.unstubAllEnvs();
|
|
6
|
+
});
|
|
7
|
+
// --- VALID hosts ---
|
|
8
|
+
it("accepts a public IPv4 address", () => {
|
|
9
|
+
expect(() => validateNodeHost("203.0.113.10")).not.toThrow();
|
|
10
|
+
});
|
|
11
|
+
it("accepts a valid hostname", () => {
|
|
12
|
+
expect(() => validateNodeHost("node-1.fleet.wopr.network")).not.toThrow();
|
|
13
|
+
});
|
|
14
|
+
it("accepts a public IPv6 address", () => {
|
|
15
|
+
expect(() => validateNodeHost("2001:db8::1")).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
// --- ALWAYS blocked ---
|
|
18
|
+
it("rejects empty string", () => {
|
|
19
|
+
expect(() => validateNodeHost("")).toThrow("Invalid node host");
|
|
20
|
+
});
|
|
21
|
+
it("rejects whitespace-only string", () => {
|
|
22
|
+
expect(() => validateNodeHost(" ")).toThrow("Invalid node host");
|
|
23
|
+
});
|
|
24
|
+
it("rejects loopback 127.0.0.1", () => {
|
|
25
|
+
expect(() => validateNodeHost("127.0.0.1")).toThrow("Invalid node host");
|
|
26
|
+
});
|
|
27
|
+
it("rejects loopback 127.0.0.2", () => {
|
|
28
|
+
expect(() => validateNodeHost("127.0.0.2")).toThrow("Invalid node host");
|
|
29
|
+
});
|
|
30
|
+
it("rejects link-local 169.254.169.254", () => {
|
|
31
|
+
expect(() => validateNodeHost("169.254.169.254")).toThrow("Invalid node host");
|
|
32
|
+
});
|
|
33
|
+
it("rejects IPv6 loopback ::1", () => {
|
|
34
|
+
expect(() => validateNodeHost("::1")).toThrow("Invalid node host");
|
|
35
|
+
});
|
|
36
|
+
it("rejects IPv6-mapped loopback ::ffff:127.0.0.1", () => {
|
|
37
|
+
expect(() => validateNodeHost("::ffff:127.0.0.1")).toThrow("Invalid node host");
|
|
38
|
+
});
|
|
39
|
+
it("rejects multicast 224.0.0.1", () => {
|
|
40
|
+
expect(() => validateNodeHost("224.0.0.1")).toThrow("Invalid node host");
|
|
41
|
+
});
|
|
42
|
+
it("rejects broadcast 255.255.255.255", () => {
|
|
43
|
+
expect(() => validateNodeHost("255.255.255.255")).toThrow("Invalid node host");
|
|
44
|
+
});
|
|
45
|
+
it("rejects 0.0.0.0", () => {
|
|
46
|
+
expect(() => validateNodeHost("0.0.0.0")).toThrow("Invalid node host");
|
|
47
|
+
});
|
|
48
|
+
it("rejects localhost hostname", () => {
|
|
49
|
+
expect(() => validateNodeHost("localhost")).toThrow("Invalid node host");
|
|
50
|
+
});
|
|
51
|
+
// --- Private ranges (blocked by default) ---
|
|
52
|
+
it("rejects 10.x.x.x by default", () => {
|
|
53
|
+
expect(() => validateNodeHost("10.0.0.1")).toThrow("Invalid node host");
|
|
54
|
+
});
|
|
55
|
+
it("rejects 172.16.x.x by default", () => {
|
|
56
|
+
expect(() => validateNodeHost("172.16.0.1")).toThrow("Invalid node host");
|
|
57
|
+
});
|
|
58
|
+
it("rejects 192.168.x.x by default", () => {
|
|
59
|
+
expect(() => validateNodeHost("192.168.1.1")).toThrow("Invalid node host");
|
|
60
|
+
});
|
|
61
|
+
it("rejects IPv6 unique local fc00::", () => {
|
|
62
|
+
expect(() => validateNodeHost("fc00::1")).toThrow("Invalid node host");
|
|
63
|
+
});
|
|
64
|
+
// --- Private ranges (allowed with env var) ---
|
|
65
|
+
it("allows 10.x.x.x when ALLOW_PRIVATE_NODE_HOSTS=true", () => {
|
|
66
|
+
vi.stubEnv("ALLOW_PRIVATE_NODE_HOSTS", "true");
|
|
67
|
+
expect(() => validateNodeHost("10.0.0.1")).not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
it("allows 172.16.x.x when ALLOW_PRIVATE_NODE_HOSTS=true", () => {
|
|
70
|
+
vi.stubEnv("ALLOW_PRIVATE_NODE_HOSTS", "true");
|
|
71
|
+
expect(() => validateNodeHost("172.16.0.1")).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
it("allows 192.168.x.x when ALLOW_PRIVATE_NODE_HOSTS=true", () => {
|
|
74
|
+
vi.stubEnv("ALLOW_PRIVATE_NODE_HOSTS", "true");
|
|
75
|
+
expect(() => validateNodeHost("192.168.1.1")).not.toThrow();
|
|
76
|
+
});
|
|
77
|
+
it("still blocks loopback even with ALLOW_PRIVATE_NODE_HOSTS=true", () => {
|
|
78
|
+
vi.stubEnv("ALLOW_PRIVATE_NODE_HOSTS", "true");
|
|
79
|
+
expect(() => validateNodeHost("127.0.0.1")).toThrow("Invalid node host");
|
|
80
|
+
});
|
|
81
|
+
it("still blocks link-local even with ALLOW_PRIVATE_NODE_HOSTS=true", () => {
|
|
82
|
+
vi.stubEnv("ALLOW_PRIVATE_NODE_HOSTS", "true");
|
|
83
|
+
expect(() => validateNodeHost("169.254.169.254")).toThrow("Invalid node host");
|
|
84
|
+
});
|
|
85
|
+
// --- Malformed IPv4-looking strings ---
|
|
86
|
+
it("rejects malformed dotted-quad 999.999.999.999", () => {
|
|
87
|
+
expect(() => validateNodeHost("999.999.999.999")).toThrow("Invalid node host: malformed IPv4 address");
|
|
88
|
+
});
|
|
89
|
+
it("rejects malformed dotted-quad 256.0.0.1", () => {
|
|
90
|
+
expect(() => validateNodeHost("256.0.0.1")).toThrow("Invalid node host: malformed IPv4 address");
|
|
91
|
+
});
|
|
92
|
+
it("rejects malformed dotted-quad 1.2.3.999", () => {
|
|
93
|
+
expect(() => validateNodeHost("1.2.3.999")).toThrow("Invalid node host: malformed IPv4 address");
|
|
94
|
+
});
|
|
95
|
+
// --- Hostname validation ---
|
|
96
|
+
it("rejects hostnames with spaces", () => {
|
|
97
|
+
expect(() => validateNodeHost("host name")).toThrow("Invalid node host");
|
|
98
|
+
});
|
|
99
|
+
it("rejects hostnames starting with a dot", () => {
|
|
100
|
+
expect(() => validateNodeHost(".example.com")).toThrow("Invalid node host");
|
|
101
|
+
});
|
|
102
|
+
it("rejects hostnames longer than 253 characters", () => {
|
|
103
|
+
const long = "a".repeat(254);
|
|
104
|
+
expect(() => validateNodeHost(long)).toThrow("Invalid node host");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { EncryptedPayload, Provider, ValidateKeyRequest, ValidateKeyResponse, WriteSecretsRequest, ProviderEndpoint } from "./types.js";
|
|
2
|
+
export { providerSchema, validateKeyRequestSchema, writeSecretsRequestSchema } from "./types.js";
|
|
3
|
+
export { deriveInstanceKey, generateInstanceKey, encrypt, decrypt } from "./encryption.js";
|
|
4
|
+
export { validateNodeHost } from "./host-validation.js";
|
|
5
|
+
export { assertSafeRedirectUrl } from "./redirect-allowlist.js";
|
|
6
|
+
export type { KeyLeakMatch } from "./key-audit.js";
|
|
7
|
+
export { scanForKeyLeaks } from "./key-audit.js";
|
|
8
|
+
export { writeEncryptedSeed, forwardSecretsToInstance } from "./key-injection.js";
|
|
9
|
+
export { PROVIDER_ENDPOINTS, validateProviderKey } from "./key-validation.js";
|
|
10
|
+
export { type SecretAuditEvent, type ISecretAuditRepository, DrizzleSecretAuditRepository, type CredentialRow, type CredentialSummaryRow, type InsertCredentialRow, type ICredentialMigrationAccess, type ICredentialRepository, type IMigrationTenantKeyAccess, DrizzleCredentialRepository, DrizzleMigrationTenantKeyAccess, type RotationResult, reEncryptAllCredentials, type MigrationResult, migratePlaintextCredentials, type PlaintextFinding, auditCredentialEncryption, type AuthType, type CreateCredentialInput, type CredentialSummary, type DecryptedCredential, type ICredentialVaultStore, type RotateCredentialInput, CredentialVaultStore, getVaultEncryptionKey, } from "./credential-vault/index.js";
|
|
11
|
+
export { type IKeyResolutionRepository, DrizzleKeyResolutionRepository, type ResolvedKey, resolveApiKey, buildPooledKeysMap, type TenantApiKey, type ITenantKeyRepository, TenantKeyRepository, type CapabilityName, type TenantCapabilitySetting, type ICapabilitySettingsRepository, ALL_CAPABILITIES, CapabilitySettingsStore, type IOrgMembershipRepository, type OrgResolvedKey, DrizzleOrgMembershipRepository, resolveApiKeyWithOrgFallback, } from "./tenant-keys/index.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { providerSchema, validateKeyRequestSchema, writeSecretsRequestSchema } from "./types.js";
|
|
2
|
+
export { deriveInstanceKey, generateInstanceKey, encrypt, decrypt } from "./encryption.js";
|
|
3
|
+
export { validateNodeHost } from "./host-validation.js";
|
|
4
|
+
export { assertSafeRedirectUrl } from "./redirect-allowlist.js";
|
|
5
|
+
export { scanForKeyLeaks } from "./key-audit.js";
|
|
6
|
+
export { writeEncryptedSeed, forwardSecretsToInstance } from "./key-injection.js";
|
|
7
|
+
export { PROVIDER_ENDPOINTS, validateProviderKey } from "./key-validation.js";
|
|
8
|
+
// Credential vault
|
|
9
|
+
export { DrizzleSecretAuditRepository, DrizzleCredentialRepository, DrizzleMigrationTenantKeyAccess, reEncryptAllCredentials, migratePlaintextCredentials, auditCredentialEncryption, CredentialVaultStore, getVaultEncryptionKey, } from "./credential-vault/index.js";
|
|
10
|
+
// Tenant keys
|
|
11
|
+
export { DrizzleKeyResolutionRepository, resolveApiKey, buildPooledKeysMap, TenantKeyRepository, ALL_CAPABILITIES, CapabilitySettingsStore, DrizzleOrgMembershipRepository, resolveApiKeyWithOrgFallback, } from "./tenant-keys/index.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key material audit — grep-based detection of leaked key patterns in log output.
|
|
3
|
+
*
|
|
4
|
+
* This utility scans arbitrary text (log lines, config dumps, etc.) for patterns
|
|
5
|
+
* that look like provider API keys. Used in tests to verify zero key leakage.
|
|
6
|
+
*/
|
|
7
|
+
export interface KeyLeakMatch {
|
|
8
|
+
provider: string;
|
|
9
|
+
line: number;
|
|
10
|
+
match: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Scan text content for key-like patterns.
|
|
14
|
+
* Returns an array of matches found — empty array means no leaks detected.
|
|
15
|
+
*/
|
|
16
|
+
export declare function scanForKeyLeaks(content: string): KeyLeakMatch[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key material audit — grep-based detection of leaked key patterns in log output.
|
|
3
|
+
*
|
|
4
|
+
* This utility scans arbitrary text (log lines, config dumps, etc.) for patterns
|
|
5
|
+
* that look like provider API keys. Used in tests to verify zero key leakage.
|
|
6
|
+
*/
|
|
7
|
+
/** Patterns that match common provider key formats. */
|
|
8
|
+
const KEY_PATTERNS = [
|
|
9
|
+
{ provider: "anthropic", pattern: /sk-ant-[A-Za-z0-9_-]{20,}/ },
|
|
10
|
+
{ provider: "openai", pattern: /sk-[A-Za-z0-9]{20,}/ },
|
|
11
|
+
{ provider: "google", pattern: /AIza[A-Za-z0-9_-]{30,}/ },
|
|
12
|
+
{ provider: "discord", pattern: /[A-Za-z0-9_-]{24}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}/ },
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Scan text content for key-like patterns.
|
|
16
|
+
* Returns an array of matches found — empty array means no leaks detected.
|
|
17
|
+
*/
|
|
18
|
+
export function scanForKeyLeaks(content) {
|
|
19
|
+
const leaks = [];
|
|
20
|
+
const lines = content.split("\n");
|
|
21
|
+
for (let i = 0; i < lines.length; i++) {
|
|
22
|
+
const line = lines[i];
|
|
23
|
+
for (const { provider, pattern } of KEY_PATTERNS) {
|
|
24
|
+
const match = line.match(pattern);
|
|
25
|
+
if (match) {
|
|
26
|
+
leaks.push({
|
|
27
|
+
provider,
|
|
28
|
+
line: i + 1,
|
|
29
|
+
match: `${match[0].slice(0, 8)}...${match[0].slice(-4)}`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return leaks;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { scanForKeyLeaks } from "./key-audit.js";
|
|
3
|
+
describe("key-audit", () => {
|
|
4
|
+
describe("scanForKeyLeaks", () => {
|
|
5
|
+
it("detects Anthropic key patterns", () => {
|
|
6
|
+
const content = "info: processing request with key sk-ant-abcdefghijklmnopqrstuvwxyz123456";
|
|
7
|
+
const leaks = scanForKeyLeaks(content);
|
|
8
|
+
expect(leaks.length).toBe(1);
|
|
9
|
+
expect(leaks[0].provider).toBe("anthropic");
|
|
10
|
+
expect(leaks[0].line).toBe(1);
|
|
11
|
+
});
|
|
12
|
+
it("detects OpenAI key patterns", () => {
|
|
13
|
+
const content = "debug: using API key sk-abcdefghijklmnopqrstuvwxyz";
|
|
14
|
+
const leaks = scanForKeyLeaks(content);
|
|
15
|
+
expect(leaks.some((l) => l.provider === "openai")).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it("detects Google key patterns", () => {
|
|
18
|
+
const content = "config: api_key=AIzaSyA1234567890abcdefghijklmnopqrstuvwx";
|
|
19
|
+
const leaks = scanForKeyLeaks(content);
|
|
20
|
+
expect(leaks.some((l) => l.provider === "google")).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it("returns empty array for clean logs", () => {
|
|
23
|
+
const content = [
|
|
24
|
+
"info: server started on port 3000",
|
|
25
|
+
"info: health check passed",
|
|
26
|
+
"debug: processing request id=abc-123",
|
|
27
|
+
].join("\n");
|
|
28
|
+
const leaks = scanForKeyLeaks(content);
|
|
29
|
+
expect(leaks).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
it("detects multiple leaks across lines", () => {
|
|
32
|
+
const content = [
|
|
33
|
+
"line 1: safe content",
|
|
34
|
+
"line 2: key=sk-ant-abcdefghijklmnopqrstuvwxyz",
|
|
35
|
+
"line 3: also sk-abcdefghijklmnopqrstuvwxyz",
|
|
36
|
+
].join("\n");
|
|
37
|
+
const leaks = scanForKeyLeaks(content);
|
|
38
|
+
expect(leaks.length).toBeGreaterThanOrEqual(2);
|
|
39
|
+
});
|
|
40
|
+
it("truncates matched key in output for safety", () => {
|
|
41
|
+
const content = "key: sk-ant-abcdefghijklmnopqrstuvwxyz123456";
|
|
42
|
+
const leaks = scanForKeyLeaks(content);
|
|
43
|
+
expect(leaks[0].match).toContain("...");
|
|
44
|
+
expect(leaks[0].match.length).toBeLessThan(40);
|
|
45
|
+
});
|
|
46
|
+
it("handles empty input", () => {
|
|
47
|
+
expect(scanForKeyLeaks("")).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EncryptedPayload } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Write an encrypted seed file to an instance's WOPR_HOME volume.
|
|
4
|
+
* Used during initial provisioning when the instance container isn't running yet.
|
|
5
|
+
*
|
|
6
|
+
* The platform writes ciphertext only — it never sees or persists the plaintext keys.
|
|
7
|
+
* The instance decrypts on first boot using its own derived key (injected as a Docker secret).
|
|
8
|
+
*
|
|
9
|
+
* @param woprHome - Absolute path to the instance's WOPR_HOME volume mount.
|
|
10
|
+
* @param secrets - Key-value map of secrets to encrypt.
|
|
11
|
+
* @param instanceKey - The 32-byte instance-derived encryption key.
|
|
12
|
+
* @returns The encrypted payload that was written.
|
|
13
|
+
*/
|
|
14
|
+
export declare function writeEncryptedSeed(woprHome: string, secrets: Record<string, string>, instanceKey: Buffer): Promise<EncryptedPayload>;
|
|
15
|
+
/**
|
|
16
|
+
* Forward secrets opaquely to a running instance container.
|
|
17
|
+
* The platform acts as a pass-through — it never parses or logs the request body.
|
|
18
|
+
*
|
|
19
|
+
* @param instanceUrl - The internal URL of the running instance (e.g., http://container:3000).
|
|
20
|
+
* @param sessionToken - The user's session token for authentication.
|
|
21
|
+
* @param opaqueBody - The raw request body string to forward without parsing.
|
|
22
|
+
* @returns Whether the write succeeded.
|
|
23
|
+
*/
|
|
24
|
+
export declare function forwardSecretsToInstance(instanceUrl: string, sessionToken: string, opaqueBody: string): Promise<{
|
|
25
|
+
ok: boolean;
|
|
26
|
+
status: number;
|
|
27
|
+
error?: string;
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { logger } from "../config/logger.js";
|
|
4
|
+
import { encrypt } from "./encryption.js";
|
|
5
|
+
/**
|
|
6
|
+
* Write an encrypted seed file to an instance's WOPR_HOME volume.
|
|
7
|
+
* Used during initial provisioning when the instance container isn't running yet.
|
|
8
|
+
*
|
|
9
|
+
* The platform writes ciphertext only — it never sees or persists the plaintext keys.
|
|
10
|
+
* The instance decrypts on first boot using its own derived key (injected as a Docker secret).
|
|
11
|
+
*
|
|
12
|
+
* @param woprHome - Absolute path to the instance's WOPR_HOME volume mount.
|
|
13
|
+
* @param secrets - Key-value map of secrets to encrypt.
|
|
14
|
+
* @param instanceKey - The 32-byte instance-derived encryption key.
|
|
15
|
+
* @returns The encrypted payload that was written.
|
|
16
|
+
*/
|
|
17
|
+
export async function writeEncryptedSeed(woprHome, secrets, instanceKey) {
|
|
18
|
+
const serialized = JSON.stringify(secrets);
|
|
19
|
+
const encrypted = encrypt(serialized, instanceKey);
|
|
20
|
+
await mkdir(woprHome, { recursive: true });
|
|
21
|
+
const seedPath = path.join(woprHome, "secrets.enc");
|
|
22
|
+
await writeFile(seedPath, JSON.stringify(encrypted), { mode: 0o600 });
|
|
23
|
+
logger.info("Wrote encrypted seed file", { path: seedPath });
|
|
24
|
+
return encrypted;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Forward secrets opaquely to a running instance container.
|
|
28
|
+
* The platform acts as a pass-through — it never parses or logs the request body.
|
|
29
|
+
*
|
|
30
|
+
* @param instanceUrl - The internal URL of the running instance (e.g., http://container:3000).
|
|
31
|
+
* @param sessionToken - The user's session token for authentication.
|
|
32
|
+
* @param opaqueBody - The raw request body string to forward without parsing.
|
|
33
|
+
* @returns Whether the write succeeded.
|
|
34
|
+
*/
|
|
35
|
+
export async function forwardSecretsToInstance(instanceUrl, sessionToken, opaqueBody) {
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(`${instanceUrl}/config/secrets`, {
|
|
38
|
+
method: "PUT",
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
Authorization: `Bearer ${sessionToken}`,
|
|
42
|
+
},
|
|
43
|
+
body: opaqueBody,
|
|
44
|
+
signal: AbortSignal.timeout(10_000),
|
|
45
|
+
});
|
|
46
|
+
if (response.ok) {
|
|
47
|
+
return { ok: true, status: response.status };
|
|
48
|
+
}
|
|
49
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
50
|
+
return { ok: false, status: response.status, error: errorText };
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const message = err instanceof Error ? err.message : "Failed to forward secrets";
|
|
54
|
+
logger.error("Failed to forward secrets to instance", { error: message });
|
|
55
|
+
return { ok: false, status: 502, error: message };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|