@donotdev/security 0.0.1

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.
Files changed (47) hide show
  1. package/README.md +201 -0
  2. package/dist/client/HealthMonitor.d.ts +85 -0
  3. package/dist/client/HealthMonitor.d.ts.map +1 -0
  4. package/dist/client/HealthMonitor.js +1 -0
  5. package/dist/client/index.d.ts +6 -0
  6. package/dist/client/index.d.ts.map +1 -0
  7. package/dist/client/index.js +1 -0
  8. package/dist/common/AuthHardening.d.ts +3 -0
  9. package/dist/common/AuthHardening.d.ts.map +1 -0
  10. package/dist/common/AuthHardening.js +1 -0
  11. package/dist/common/SecurityConfig.d.ts +11 -0
  12. package/dist/common/SecurityConfig.d.ts.map +1 -0
  13. package/dist/common/SecurityConfig.js +0 -0
  14. package/dist/common/index.d.ts +2 -0
  15. package/dist/common/index.d.ts.map +1 -0
  16. package/dist/common/index.js +0 -0
  17. package/dist/index.d.ts +6 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +1 -0
  20. package/dist/server/AnomalyDetector.d.ts +68 -0
  21. package/dist/server/AnomalyDetector.d.ts.map +1 -0
  22. package/dist/server/AnomalyDetector.js +2 -0
  23. package/dist/server/AuditLogger.d.ts +40 -0
  24. package/dist/server/AuditLogger.d.ts.map +1 -0
  25. package/dist/server/AuditLogger.js +2 -0
  26. package/dist/server/AuthHardening.d.ts +3 -0
  27. package/dist/server/AuthHardening.d.ts.map +1 -0
  28. package/dist/server/AuthHardening.js +1 -0
  29. package/dist/server/DndevSecurity.d.ts +147 -0
  30. package/dist/server/DndevSecurity.d.ts.map +1 -0
  31. package/dist/server/DndevSecurity.js +1 -0
  32. package/dist/server/PiiEncryptor.d.ts +70 -0
  33. package/dist/server/PiiEncryptor.d.ts.map +1 -0
  34. package/dist/server/PiiEncryptor.js +1 -0
  35. package/dist/server/PrivacyManager.d.ts +89 -0
  36. package/dist/server/PrivacyManager.d.ts.map +1 -0
  37. package/dist/server/PrivacyManager.js +1 -0
  38. package/dist/server/RateLimiter.d.ts +80 -0
  39. package/dist/server/RateLimiter.d.ts.map +1 -0
  40. package/dist/server/RateLimiter.js +1 -0
  41. package/dist/server/SecretValidator.d.ts +26 -0
  42. package/dist/server/SecretValidator.d.ts.map +1 -0
  43. package/dist/server/SecretValidator.js +1 -0
  44. package/dist/server/index.d.ts +16 -0
  45. package/dist/server/index.d.ts.map +1 -0
  46. package/dist/server/index.js +1 -0
  47. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # @donotdev/security
2
+
3
+ Opt-in SOC2-grade security controls for DoNotDev apps — audit logging, rate limiting, PII encryption, anomaly detection, auth hardening, and GDPR privacy management.
4
+
5
+ > **Opt-in by design.** Baseline safety (brute-force lockout, session timeout) lives in `@donotdev/core` and is always active. This package adds the compliance and observability layer for teams that need SOC2, GDPR, or enterprise-grade audit trails.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bun add @donotdev/security
11
+ ```
12
+
13
+ ## Import Paths
14
+
15
+ ```typescript
16
+ // Client-safe (HealthMonitor, AuthHardening, SecurityContext type)
17
+ import { HealthMonitor, AuthHardening } from '@donotdev/security';
18
+
19
+ // Server-only (DndevSecurity, AuditLogger, PiiEncryptor, etc.)
20
+ import { DndevSecurity } from '@donotdev/security/server';
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Zero-config (all baseline controls active)
26
+
27
+ ```typescript
28
+ // functions/src/security.ts
29
+ import { DndevSecurity } from '@donotdev/security/server';
30
+
31
+ export const security = new DndevSecurity();
32
+ ```
33
+
34
+ Pass to `CrudService` and base functions:
35
+
36
+ ```typescript
37
+ import { createBaseFunction } from '@donotdev/functions/firebase';
38
+ import { security } from './security';
39
+
40
+ export const myFunction = createBaseFunction(
41
+ 'my-operation',
42
+ schema,
43
+ handler,
44
+ 'user',
45
+ undefined, // requiredRole config
46
+ security // ← opt-in SOC2 audit trail
47
+ );
48
+ ```
49
+
50
+ ### Full SOC2 config
51
+
52
+ ```typescript
53
+ export const security = new DndevSecurity({
54
+ // PII field encryption (AES-256-GCM)
55
+ piiSecret: process.env.PII_SECRET,
56
+
57
+ // Auth hardening overrides (default: 5 attempts → 15min lockout, 8h session)
58
+ auth: {
59
+ maxAttempts: 3,
60
+ lockoutMs: 30 * 60 * 1000,
61
+ },
62
+
63
+ // Data retention (SOC2 P6)
64
+ retention: [
65
+ { collection: 'audit_logs', days: 365 },
66
+ { collection: 'sessions', days: 90 },
67
+ ],
68
+
69
+ // Anomaly detection with custom handler
70
+ anomaly: {
71
+ authFailures: 5,
72
+ onAnomaly: (type, count, userId) =>
73
+ notifySlack(`[ANOMALY] ${type} x${count} by ${userId}`),
74
+ },
75
+
76
+ // Pluggable rate limit backend (Firestore, Postgres, Redis)
77
+ // Default: in-memory (fine for single-instance, not for serverless)
78
+ rateLimitBackend: {
79
+ check: (key, cfg) => checkRateLimitWithFirestore(key, cfg),
80
+ },
81
+ });
82
+ ```
83
+
84
+ ## Controls
85
+
86
+ | Control | Default | Config |
87
+ |---|---|---|
88
+ | Structured audit log (stdout JSON) | ✅ on | `logger` |
89
+ | Rate limiting (100 writes / 500 reads per min) | ✅ on | `rateLimit` |
90
+ | In-memory rate limit backend | ✅ default | `rateLimitBackend` to swap |
91
+ | Brute-force lockout (5 attempts → 15min) | ✅ on | `auth` |
92
+ | Session timeout tracking (8h idle) | ✅ on | `auth.sessionTimeoutMs` |
93
+ | Anomaly detection (stderr warn) | ✅ on | `anomaly` + `onAnomaly` |
94
+ | Secret scrubbing on all log output | ✅ on | — |
95
+ | PII field encryption (AES-256-GCM) | ❌ opt-in | `piiSecret` |
96
+ | Data retention / right-to-erasure | ❌ opt-in | `retention` |
97
+
98
+ ## Package Structure
99
+
100
+ ```
101
+ security/src/
102
+ ├── client/
103
+ │ └── HealthMonitor.ts # Circuit breaker + uptime tracking (browser-safe)
104
+ ├── common/
105
+ │ ├── AuthHardening.ts # Re-export stub → canonical impl in @donotdev/core
106
+ │ └── SecurityConfig.ts # Re-exports SecurityContext types from @donotdev/core
107
+ └── server/
108
+ ├── DndevSecurity.ts # Main SOC2 orchestrator (implements SecurityContext)
109
+ ├── AuditLogger.ts # Structured JSON audit log (stdout / custom transport)
110
+ ├── RateLimiter.ts # In-memory fixed-window rate limiter
111
+ ├── PiiEncryptor.ts # AES-256-GCM field-level encryption
112
+ ├── AuthHardening.ts # Re-export → common/AuthHardening
113
+ ├── AnomalyDetector.ts # Threshold-based behavioral alerts
114
+ ├── PrivacyManager.ts # Retention policies + right-to-erasure
115
+ └── SecretValidator.ts # Secret scrubbing for log output
116
+ ```
117
+
118
+ > **`AuthHardening` canonical implementation** lives in `@donotdev/core` (always installed).
119
+ > `@donotdev/security` re-exports it for backwards compatibility.
120
+ > Firebase and Supabase providers import directly from `@donotdev/core` — no forced security dep.
121
+
122
+ ## SecurityContext Interface
123
+
124
+ `DndevSecurity` implements `SecurityContext` from `@donotdev/core`:
125
+
126
+ ```typescript
127
+ interface SecurityContext {
128
+ audit(event: Omit<AuditEvent, 'timestamp'>): void | Promise<void>;
129
+ checkRateLimit(key: string, operation: 'read' | 'write'): Promise<void>;
130
+ encryptPii<T>(data: T, piiFields: string[]): T;
131
+ decryptPii<T>(data: T, piiFields: string[]): T;
132
+ authHardening?: AuthHardeningContext;
133
+ }
134
+ ```
135
+
136
+ Any object implementing this interface can be passed as `security` — no hard dep on this package.
137
+
138
+ ## Audit Event Types
139
+
140
+ Covers SOC2 CC6, CC7, C1, P1–P8:
141
+
142
+ ```typescript
143
+ type AuditEventType =
144
+ | 'auth.login.success' | 'auth.login.failure' | 'auth.logout'
145
+ | 'auth.locked' | 'auth.unlocked' | 'auth.session.expired'
146
+ | 'auth.mfa.enrolled' | 'auth.mfa.challenged'
147
+ | 'auth.password.reset' | 'auth.role.changed'
148
+ | 'crud.create' | 'crud.read' | 'crud.update' | 'crud.delete'
149
+ | 'pii.access' | 'pii.export' | 'pii.erase'
150
+ | 'rate_limit.exceeded' | 'anomaly.detected' | 'config.changed';
151
+ ```
152
+
153
+ ## PII Encryption
154
+
155
+ Mark fields for encryption in your entity definition, then pass `security` to `CrudService`:
156
+
157
+ ```typescript
158
+ // Entity definition
159
+ const UserEntity = defineEntity('users', {
160
+ fields: { email: field.email(), ssn: field.string() },
161
+ security: { piiFields: ['email', 'ssn'] },
162
+ });
163
+
164
+ // Service setup
165
+ const security = new DndevSecurity({ piiSecret: process.env.PII_SECRET });
166
+ crudService.setSecurity(security);
167
+ // email + ssn are now transparently encrypted at rest
168
+ ```
169
+
170
+ ## Pluggable Rate Limit Backend
171
+
172
+ For serverless/distributed deployments where in-memory state is lost between invocations:
173
+
174
+ ```typescript
175
+ import { checkRateLimitWithFirestore } from '@donotdev/functions/shared';
176
+
177
+ const security = new DndevSecurity({
178
+ rateLimitBackend: {
179
+ check: (key, cfg) => checkRateLimitWithFirestore(key, cfg),
180
+ },
181
+ });
182
+ ```
183
+
184
+ Implement `RateLimitBackend` from `@donotdev/core` for custom backends (Redis, Postgres, etc.):
185
+
186
+ ```typescript
187
+ import type { RateLimitBackend } from '@donotdev/core';
188
+
189
+ const redisBackend: RateLimitBackend = {
190
+ async check(key, { maxAttempts, windowMs, blockDurationMs }) {
191
+ // your Redis logic
192
+ return { allowed: true, remaining: 99, resetAt: null, blockRemainingSeconds: null };
193
+ },
194
+ };
195
+ ```
196
+
197
+ ## License
198
+
199
+ All rights reserved. The DoNotDev framework and its premium features are the exclusive property of **Ambroise Park Consulting**.
200
+
201
+ © Ambroise Park Consulting – 2025
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @fileoverview HealthMonitor
3
+ * @description Circuit breaker pattern + health check probes for availability.
4
+ * Covers SOC2 Availability Principle (A1): health monitoring, graceful degradation.
5
+ *
6
+ * Zero deps — uses fetch + AbortSignal for liveness probes.
7
+ *
8
+ * @version 0.0.1
9
+ * @since 0.0.1
10
+ * @author AMBROISE PARK Consulting
11
+ */
12
+ export type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';
13
+ export interface CircuitBreakerConfig {
14
+ /**
15
+ * Failure count threshold before circuit opens (default: 5).
16
+ */
17
+ failureThreshold?: number;
18
+ /**
19
+ * Cooldown before attempting half-open (ms, default: 10_000).
20
+ */
21
+ resetTimeoutMs?: number;
22
+ /**
23
+ * Request timeout for health checks (ms, default: 3_000).
24
+ */
25
+ probeTimeoutMs?: number;
26
+ }
27
+ export interface HealthMonitorConfig extends CircuitBreakerConfig {
28
+ /**
29
+ * URL to probe for liveness (e.g., '/api/health').
30
+ * If not set, liveness checks always return true.
31
+ */
32
+ livenessUrl?: string;
33
+ }
34
+ /**
35
+ * Availability monitor: circuit breaker + liveness probe (SOC2 A1).
36
+ *
37
+ * Wrap downstream calls with `protect()` to prevent cascading failures.
38
+ * Use `checkLiveness()` in health check endpoints.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const monitor = new HealthMonitor({ livenessUrl: '/api/health', failureThreshold: 3 });
43
+ *
44
+ * // Protect a downstream call:
45
+ * const safeCall = monitor.protect(async () => fetchUserData(id));
46
+ * try {
47
+ * const data = await safeCall();
48
+ * } catch {
49
+ * // Circuit open — show cached/degraded UI
50
+ * }
51
+ *
52
+ * // Liveness probe in API route:
53
+ * const alive = await monitor.checkLiveness();
54
+ * ```
55
+ *
56
+ * @version 0.0.1
57
+ * @since 0.0.1
58
+ * @author AMBROISE PARK Consulting
59
+ */
60
+ export declare class HealthMonitor {
61
+ private readonly breaker;
62
+ private readonly config;
63
+ private _status;
64
+ /**
65
+ * Guards the half-open state: only one probe executes at a time.
66
+ * Concurrent calls in half-open state are rejected rather than all executing,
67
+ * which would re-open the circuit on a mass failure storm.
68
+ */
69
+ private _probing;
70
+ constructor(config?: HealthMonitorConfig);
71
+ /**
72
+ * Wrap an async function with circuit-breaker protection.
73
+ * The returned function throws when the circuit is open.
74
+ * In half-open state only one probe executes at a time — concurrent calls are rejected.
75
+ */
76
+ protect<T>(fn: () => Promise<T>): () => Promise<T>;
77
+ /** Current circuit health status. */
78
+ get status(): HealthStatus;
79
+ /**
80
+ * HTTP liveness probe — returns true if the endpoint responds 2xx within timeout.
81
+ * Always returns true if no `livenessUrl` configured.
82
+ */
83
+ checkLiveness(): Promise<boolean>;
84
+ }
85
+ //# sourceMappingURL=HealthMonitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HealthMonitor.d.ts","sourceRoot":"","sources":["../../src/client/HealthMonitor.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAwDD,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,OAAO,CAA2B;IAC1C;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,GAAE,mBAAwB;IAK5C;;;;OAIG;IACH,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC;IAuClD,qCAAqC;IACrC,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;CAWxC"}
@@ -0,0 +1 @@
1
+ class i{state="closed";failures=0;lastOpenedAt=0;failureThreshold;resetTimeoutMs;constructor(e={}){this.failureThreshold=e.failureThreshold??5,this.resetTimeoutMs=e.resetTimeoutMs??1e4}_evaluateState(){return this.state==="open"&&Date.now()-this.lastOpenedAt>this.resetTimeoutMs&&(this.state="half-open"),this.state}get currentState(){return this.state}isOpen(){return this._evaluateState()==="open"}recordSuccess(){this.failures=0,this.state="closed"}recordFailure(){this.failures+=1,this.failures>=this.failureThreshold&&(this.state="open",this.lastOpenedAt=Date.now())}}class a{breaker;config;_status="healthy";_probing=!1;constructor(e={}){this.config=e,this.breaker=new i(e)}protect(e){return async()=>{if(this.breaker.isOpen())throw this._status="unhealthy",new Error("[dndev/security] Circuit breaker open \u2014 downstream service unavailable");const s=this.breaker.currentState==="half-open";if(s){if(this._probing)throw this._status="unhealthy",new Error("[dndev/security] Circuit breaker half-open \u2014 probe in progress, retry shortly");this._probing=!0}try{const t=await e();return this.breaker.recordSuccess(),this._status="healthy",t}catch(t){throw this.breaker.recordFailure(),this._status=this.breaker.isOpen()?"unhealthy":"degraded",t}finally{s&&(this._probing=!1)}}}get status(){return this._status}async checkLiveness(){if(!this.config.livenessUrl)return!0;try{return(await fetch(this.config.livenessUrl,{signal:AbortSignal.timeout(this.config.probeTimeoutMs??3e3)})).ok}catch{return!1}}}export{a as HealthMonitor};
@@ -0,0 +1,6 @@
1
+ export { HealthMonitor } from './HealthMonitor';
2
+ export type { HealthMonitorConfig, HealthStatus, CircuitBreakerConfig } from './HealthMonitor';
3
+ export type { SecurityContext, AuditEvent, AuditEventType } from '../common/SecurityConfig';
4
+ export { AuthHardening } from '../common/AuthHardening';
5
+ export type { AuthHardeningConfig, LockoutResult } from '../common/AuthHardening';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAG/F,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG5F,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1 @@
1
+ import{HealthMonitor as t}from"./HealthMonitor";import{AuthHardening as n}from"../common/AuthHardening";export{n as AuthHardening,t as HealthMonitor};
@@ -0,0 +1,3 @@
1
+ export { AuthHardening } from '@donotdev/core';
2
+ export type { AuthHardeningConfig, LockoutResult } from '@donotdev/core';
3
+ //# sourceMappingURL=AuthHardening.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthHardening.d.ts","sourceRoot":"","sources":["../../src/common/AuthHardening.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ import{AuthHardening as n}from"@donotdev/core";export{n as AuthHardening};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @fileoverview Security Config Types
3
+ * @description Re-exports SecurityContext types from @donotdev/core.
4
+ * Keeping the canonical definition in core avoids circular deps.
5
+ *
6
+ * @version 0.0.1
7
+ * @since 0.0.1
8
+ * @author AMBROISE PARK Consulting
9
+ */
10
+ export type { AuditEventType, AuditEvent, SecurityContext, AuthHardeningContext, ServerRateLimitConfig, ServerRateLimitResult, RateLimitBackend } from '@donotdev/core';
11
+ //# sourceMappingURL=SecurityConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityConfig.d.ts","sourceRoot":"","sources":["../../src/common/SecurityConfig.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
File without changes
@@ -0,0 +1,2 @@
1
+ export type { AuditEventType, AuditEvent, SecurityContext } from './SecurityConfig';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
File without changes
@@ -0,0 +1,6 @@
1
+ export { HealthMonitor } from './client/HealthMonitor';
2
+ export type { HealthMonitorConfig, HealthStatus, CircuitBreakerConfig } from './client/HealthMonitor';
3
+ export type { SecurityContext, AuditEvent, AuditEventType } from './common/SecurityConfig';
4
+ export { AuthHardening } from './common/AuthHardening';
5
+ export type { AuthHardeningConfig, LockoutResult } from './common/AuthHardening';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEtG,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAG3F,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{HealthMonitor as t}from"./client/HealthMonitor";import{AuthHardening as n}from"./common/AuthHardening";export{n as AuthHardening,t as HealthMonitor};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @fileoverview AnomalyDetector
3
+ * @description Threshold-based behavioral anomaly detection with configurable alerting.
4
+ * Covers SOC2 CC7.2 (anomaly detection), CC7.3 (incident response triggers).
5
+ *
6
+ * @version 0.0.1
7
+ * @since 0.0.1
8
+ * @author AMBROISE PARK Consulting
9
+ */
10
+ export type AnomalyType = 'auth.failures' | 'bulk.deletes' | 'bulk.reads' | 'bulk.exports' | 'rate_limit.exceeded';
11
+ export interface AnomalyThresholds {
12
+ /**
13
+ * Max auth failures per window before alert.
14
+ * @default 10
15
+ */
16
+ authFailures?: number;
17
+ /**
18
+ * Max deletes per window before alert.
19
+ * @default 50
20
+ */
21
+ bulkDeletes?: number;
22
+ /**
23
+ * Max reads per window before alert.
24
+ * @default 1000
25
+ */
26
+ bulkReads?: number;
27
+ /**
28
+ * Max bulk exports per window before alert.
29
+ * @default 5
30
+ */
31
+ bulkExports?: number;
32
+ /**
33
+ * Max rate-limit-exceeded events per window before alert.
34
+ * Defaults to 10 rather than 1 to prevent flooding alert handlers (Slack, PagerDuty)
35
+ * during sustained attacks. Every individual breach is still recorded in the audit log.
36
+ * @default 10
37
+ */
38
+ rateLimitExceeded?: number;
39
+ /**
40
+ * Window size in milliseconds.
41
+ * @default 60_000 (1 minute)
42
+ */
43
+ windowMs?: number;
44
+ }
45
+ /**
46
+ * Called when an anomaly is detected.
47
+ * @param type - anomaly type
48
+ * @param count - current count in window
49
+ * @param userId - user who triggered it (undefined for system)
50
+ */
51
+ export type AnomalyHandler = (type: AnomalyType, count: number, userId?: string) => void;
52
+ export declare class AnomalyDetector {
53
+ private readonly counters;
54
+ private readonly thresholds;
55
+ private readonly onAnomaly;
56
+ constructor(thresholds?: AnomalyThresholds, onAnomaly?: AnomalyHandler);
57
+ /**
58
+ * Record an event and fire `onAnomaly` if threshold is breached.
59
+ * For `rate_limit.exceeded`, the handler fires once per window threshold
60
+ * (not on every single breach) to prevent flooding alert sinks during attacks.
61
+ */
62
+ record(type: AnomalyType, userId?: string): void;
63
+ private getThreshold;
64
+ private _evictExpired;
65
+ /** Current count for a type + userId in the active window. */
66
+ getCount(type: AnomalyType, userId?: string): number;
67
+ }
68
+ //# sourceMappingURL=AnomalyDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnomalyDetector.d.ts","sourceRoot":"","sources":["../../src/server/AnomalyDetector.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,MAAM,WAAW,GACnB,eAAe,GACf,cAAc,GACd,YAAY,GACZ,cAAc,GACd,qBAAqB,CAAC;AAE1B,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AA8BzF,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;IACzD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;gBAE/B,UAAU,GAAE,iBAAsB,EAAE,SAAS,CAAC,EAAE,cAAc;IA0B1E;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IA4BhD,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,aAAa;IAQrB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;CAOrD"}
@@ -0,0 +1,2 @@
1
+ const c=1e4;class l{counters=new Map;thresholds;onAnomaly;constructor(t={},o){this.thresholds={authFailures:t.authFailures??10,bulkDeletes:t.bulkDeletes??50,bulkReads:t.bulkReads??1e3,bulkExports:t.bulkExports??5,rateLimitExceeded:t.rateLimitExceeded??10,windowMs:t.windowMs??6e4},this.onAnomaly=o??((s,e,n)=>{process.stderr.write(JSON.stringify({level:"warn",service:"dndev-anomaly",type:"anomaly.detected",anomalyType:s,count:e,userId:n,timestamp:new Date().toISOString()})+`
2
+ `)})}record(t,o){const s=`${t}:${o??"__global__"}`,e=Date.now();!this.counters.has(s)&&this.counters.size>=1e4&&this._evictExpired(e);const r=this.counters.get(s)??{count:0,windowStart:e};e-r.windowStart>this.thresholds.windowMs&&(r.count=0,r.windowStart=e),r.count+=1,this.counters.set(s,r);const i=this.getThreshold(t);r.count===i&&this.onAnomaly(t,r.count,o)}getThreshold(t){switch(t){case"auth.failures":return this.thresholds.authFailures;case"bulk.deletes":return this.thresholds.bulkDeletes;case"bulk.reads":return this.thresholds.bulkReads;case"bulk.exports":return this.thresholds.bulkExports;case"rate_limit.exceeded":return this.thresholds.rateLimitExceeded}}_evictExpired(t){for(const[o,s]of this.counters)t-s.windowStart>this.thresholds.windowMs&&this.counters.delete(o)}getCount(t,o){const s=`${t}:${o??"__global__"}`,e=this.counters.get(s);return!e||Date.now()-e.windowStart>this.thresholds.windowMs?0:e.count}}export{l as AnomalyDetector};
@@ -0,0 +1,40 @@
1
+ import type { AuditEvent } from '../common/SecurityConfig';
2
+ export interface AuditLoggerOptions {
3
+ /** Log level (default: 'info') */
4
+ level?: 'debug' | 'info' | 'warn' | 'error';
5
+ /** Service name tag added to every entry (default: 'dndev') */
6
+ service?: string;
7
+ /** Custom writer — defaults to process.stdout (JSON per line) */
8
+ write?: (entry: Record<string, unknown>) => void;
9
+ }
10
+ /**
11
+ * Structured JSON audit logger (SOC2 CC7.1).
12
+ *
13
+ * Produces one JSON object per line on stdout. Pipe to cloud logging
14
+ * (Cloud Logging, Datadog, Splunk, etc.) for SIEM ingestion and immutable storage.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const logger = new AuditLogger({ service: 'my-app' });
19
+ * logger.log({ type: 'auth.login.success', userId: 'u123' });
20
+ * // → {"level":"info","service":"my-app","type":"auth.login.success","userId":"u123","timestamp":"..."}
21
+ * ```
22
+ *
23
+ * @version 0.0.1
24
+ * @since 0.0.1
25
+ * @author AMBROISE PARK Consulting
26
+ */
27
+ export declare class AuditLogger {
28
+ private readonly service;
29
+ private readonly level;
30
+ private readonly write;
31
+ constructor(opts?: AuditLoggerOptions);
32
+ /**
33
+ * Log an audit event with automatic timestamp.
34
+ * Secrets in all event fields are automatically scrubbed before writing.
35
+ */
36
+ log(event: Omit<AuditEvent, 'timestamp'> & {
37
+ timestamp?: string;
38
+ }): void;
39
+ }
40
+ //# sourceMappingURL=AuditLogger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuditLogger.d.ts","sourceRoot":"","sources":["../../src/server/AuditLogger.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAClD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2C;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2C;gBAErD,IAAI,GAAE,kBAAuB;IAqBzC;;;OAGG;IACH,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAUzE"}
@@ -0,0 +1,2 @@
1
+ import{scrubSecrets as s}from"./SecretValidator";class c{service;level;write;constructor(t={}){this.service=t.service??"dndev",this.level=t.level??"info",this.write=t.write??(e=>{let i;try{i=JSON.stringify(e)}catch{i=JSON.stringify({level:e.level,service:e.service,type:e.type,timestamp:e.timestamp,_serializeError:"Audit entry contained non-serializable values"})}process.stdout.write(i+`
2
+ `)})}log(t){const e=s(t),i={level:this.level,service:this.service,...e,timestamp:e.timestamp??new Date().toISOString()};this.write(i)}}export{c as AuditLogger};
@@ -0,0 +1,3 @@
1
+ export { AuthHardening } from '../common/AuthHardening';
2
+ export type { AuthHardeningConfig, LockoutResult } from '../common/AuthHardening';
3
+ //# sourceMappingURL=AuthHardening.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthHardening.d.ts","sourceRoot":"","sources":["../../src/server/AuthHardening.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1 @@
1
+ import{AuthHardening as n}from"../common/AuthHardening";export{n as AuthHardening};
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @fileoverview DndevSecurity
3
+ * @description Main SOC2 security context. Wires all server-side security controls
4
+ * into a single object that CrudService and auth adapters consume.
5
+ *
6
+ * Zero-config path: all baseline controls active with sensible defaults.
7
+ * Advanced controls (PII encryption, MFA, retention) require explicit config.
8
+ *
9
+ * @version 0.0.1
10
+ * @since 0.0.1
11
+ * @author AMBROISE PARK Consulting
12
+ */
13
+ import { AuditLogger } from './AuditLogger';
14
+ import { DndevRateLimiter } from './RateLimiter';
15
+ import { PiiEncryptor } from './PiiEncryptor';
16
+ import { AuthHardening } from './AuthHardening';
17
+ import { AnomalyDetector } from './AnomalyDetector';
18
+ import { PrivacyManager } from './PrivacyManager';
19
+ import type { AuditLoggerOptions } from './AuditLogger';
20
+ import type { RateLimiterOptions } from './RateLimiter';
21
+ import type { AuthHardeningConfig } from './AuthHardening';
22
+ import type { AnomalyThresholds, AnomalyHandler } from './AnomalyDetector';
23
+ import type { RetentionPolicy } from './PrivacyManager';
24
+ import type { SecurityContext, AuditEvent, AuthHardeningContext, RateLimitBackend } from '../common/SecurityConfig';
25
+ export interface DndevSecurityConfig {
26
+ /**
27
+ * Master secret for PII field encryption (AES-256-GCM).
28
+ * Required if any entity defines `security.piiFields`.
29
+ * Store in your secret manager — never in code.
30
+ */
31
+ piiSecret?: string;
32
+ /** PII encryption salt override (default: 'dndev-pii-v1') */
33
+ piiSalt?: string;
34
+ /** Rate limiter options (default: 100 writes/min, 500 reads/min) */
35
+ rateLimit?: RateLimiterOptions;
36
+ /** Auth hardening options (default: 5 attempts → 15min lockout, 8h session) */
37
+ auth?: AuthHardeningConfig;
38
+ /** Anomaly detection thresholds + handler */
39
+ anomaly?: AnomalyThresholds & {
40
+ onAnomaly?: AnomalyHandler;
41
+ };
42
+ /** Data retention policies (required for SOC2 P6) */
43
+ retention?: RetentionPolicy[];
44
+ /** Audit logger options (default: JSON to stdout) */
45
+ logger?: AuditLoggerOptions;
46
+ /**
47
+ * Pluggable rate limit backend (default: in-memory).
48
+ * Provide a Firestore, Postgres, or Redis-backed implementation for
49
+ * distributed/serverless deployments where in-memory state is lost between invocations.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { checkRateLimitWithFirestore } from '@donotdev/functions/shared';
54
+ * const security = new DndevSecurity({
55
+ * rateLimitBackend: { check: (key, cfg) => checkRateLimitWithFirestore(key, cfg) },
56
+ * });
57
+ * ```
58
+ */
59
+ rateLimitBackend?: RateLimitBackend;
60
+ }
61
+ /**
62
+ * Main SOC2 security context for DoNotDev applications.
63
+ *
64
+ * Implements `SecurityContext` — pass an instance to `CrudService` and auth adapters.
65
+ * All baseline controls are ON by default at zero config:
66
+ * - Structured audit logging (stdout JSON)
67
+ * - Rate limiting (100 writes/min, 500 reads/min per key)
68
+ * - Brute-force lockout (5 attempts → 15min lockout)
69
+ * - Session timeout tracking (8h idle)
70
+ * - Anomaly detection (stderr warn at thresholds)
71
+ * - Secret scrubbing on all log output
72
+ *
73
+ * Advanced controls require explicit config:
74
+ * - PII encryption → `piiSecret`
75
+ * - Data retention/erasure → `retention`
76
+ * - Custom anomaly handler → `anomaly.onAnomaly`
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // Zero-config (baseline SOC2 controls):
81
+ * export const security = new DndevSecurity();
82
+ *
83
+ * // Full SOC2 config:
84
+ * export const security = new DndevSecurity({
85
+ * piiSecret: process.env.PII_SECRET,
86
+ * auth: { maxAttempts: 3, lockoutMs: 30 * 60 * 1000 },
87
+ * retention: [
88
+ * { collection: 'audit_logs', days: 365 },
89
+ * { collection: 'sessions', days: 90 },
90
+ * ],
91
+ * anomaly: {
92
+ * authFailures: 5,
93
+ * onAnomaly: (type, count, userId) => notifySlack(`${type} x${count} by ${userId}`),
94
+ * },
95
+ * });
96
+ * ```
97
+ *
98
+ * @version 0.0.1
99
+ * @since 0.0.1
100
+ * @author AMBROISE PARK Consulting
101
+ */
102
+ export declare class DndevSecurity implements SecurityContext {
103
+ /** Structured audit logger (stdout JSON) */
104
+ readonly auditLogger: AuditLogger;
105
+ /** Rate limiter (in-memory fixed-window, used when no rateLimitBackend provided) */
106
+ readonly rateLimiter: DndevRateLimiter;
107
+ /** PII field encryptor (null if piiSecret not provided) */
108
+ readonly piiEncryptor: PiiEncryptor | null;
109
+ /**
110
+ * Auth hardening (brute-force lockout, session timeout).
111
+ * Also satisfies `SecurityContext.authHardening` — auth adapters delegate
112
+ * lockout here to avoid running a second, hardcoded lockout Map in parallel.
113
+ */
114
+ readonly authHardening: AuthHardening & AuthHardeningContext;
115
+ /** Anomaly detector (threshold-based behavioral alerts) */
116
+ readonly anomalyDetector: AnomalyDetector;
117
+ /** Privacy manager (retention policies, right-to-erasure) */
118
+ readonly privacyManager: PrivacyManager;
119
+ /** Optional pluggable backend (Firestore, Postgres, Redis) — replaces in-memory limiter */
120
+ private readonly _rateLimitBackend?;
121
+ /** RateLimitConfig derived from rateLimit options, used when delegating to backend */
122
+ private readonly _backendWriteConfig;
123
+ private readonly _backendReadConfig;
124
+ constructor(config?: DndevSecurityConfig);
125
+ /**
126
+ * Emit a structured audit event. Secrets in metadata are automatically scrubbed.
127
+ */
128
+ audit(event: Omit<AuditEvent, 'timestamp'>): void;
129
+ /**
130
+ * Check rate limit for key + operation.
131
+ * Delegates to injected `rateLimitBackend` when provided (Firestore, Postgres, Redis),
132
+ * otherwise falls back to the in-memory limiter.
133
+ * @throws {Error} 'Rate limit exceeded' when threshold is breached.
134
+ */
135
+ checkRateLimit(key: string, operation: 'read' | 'write'): Promise<void>;
136
+ /** Encrypt PII fields using AES-256-GCM (no-op if piiSecret not configured). */
137
+ encryptPii<T extends Record<string, unknown>>(data: T, piiFields: string[]): T;
138
+ /** Decrypt PII fields (no-op if piiSecret not configured). */
139
+ decryptPii<T extends Record<string, unknown>>(data: T, piiFields: string[]): T;
140
+ /**
141
+ * Record a behavioral anomaly event. Called by CrudService after mutations so
142
+ * the anomaly detector can fire alerts when thresholds are breached.
143
+ */
144
+ private static readonly VALID_ANOMALY_TYPES;
145
+ recordAnomaly(type: string, userId?: string): void;
146
+ }
147
+ //# sourceMappingURL=DndevSecurity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DndevSecurity.d.ts","sourceRoot":"","sources":["../../src/server/DndevSecurity.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAe,MAAM,mBAAmB,CAAC;AACxF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,oBAAoB,EAAE,gBAAgB,EAAyB,MAAM,0BAA0B,CAAC;AAE3I,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,iBAAiB,GAAG;QAAE,SAAS,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IAC7D,qDAAqD;IACrD,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,qDAAqD;IACrD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAAa,aAAc,YAAW,eAAe;IACnD,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,aAAa,GAAG,oBAAoB,CAAC;IAC7D,2DAA2D;IAC3D,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAmB;IACtD,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAwB;IAC5D,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;gBAE/C,MAAM,GAAE,mBAAwB;IAkC5C;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG,IAAI;IAOjD;;;;;OAKG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB7E,gFAAgF;IAChF,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC;IAK9E,8DAA8D;IAC9D,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC;IAK9E;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAMxC;IAEH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;CASnD"}
@@ -0,0 +1 @@
1
+ import{AuditLogger as n}from"./AuditLogger";import{DndevRateLimiter as o}from"./RateLimiter";import{PiiEncryptor as d}from"./PiiEncryptor";import{AuthHardening as s}from"./AuthHardening";import{AnomalyDetector as c}from"./AnomalyDetector";import{PrivacyManager as m}from"./PrivacyManager";import{scrubSecrets as l}from"./SecretValidator";class i{auditLogger;rateLimiter;piiEncryptor;authHardening;anomalyDetector;privacyManager;_rateLimitBackend;_backendWriteConfig;_backendReadConfig;constructor(t={}){if(this.auditLogger=new n(t.logger),this.rateLimiter=new o(t.rateLimit),t.piiSecret&&!t.piiSalt)throw new Error("[dndev/security] DndevSecurity: PII encryption requires both piiSecret and piiSalt configuration. Provide a per-deployment unique salt stored in your secret manager.");this.piiEncryptor=t.piiSecret&&t.piiSalt?new d(t.piiSecret,t.piiSalt):null,this.authHardening=new s(t.auth),this.anomalyDetector=new c(t.anomaly,t.anomaly?.onAnomaly),this.privacyManager=new m(t.retention),this._rateLimitBackend=t.rateLimitBackend;const e=(t.rateLimit?.writes?.durationSeconds??60)*1e3,r=(t.rateLimit?.reads?.durationSeconds??60)*1e3;this._backendWriteConfig={maxAttempts:t.rateLimit?.writes?.points??100,windowMs:e,blockDurationMs:e},this._backendReadConfig={maxAttempts:t.rateLimit?.reads?.points??500,windowMs:r,blockDurationMs:r}}audit(t){const e=t.metadata?l(t.metadata):void 0;this.auditLogger.log({...t,metadata:e})}async checkRateLimit(t,e){if(this._rateLimitBackend){const r=e==="write"?this._backendWriteConfig:this._backendReadConfig,a=await this._rateLimitBackend.check(t,r);if(!a.allowed)throw this.anomalyDetector.record("rate_limit.exceeded",t),new Error(`Rate limit exceeded. Try again in ${a.blockRemainingSeconds} seconds.`);return}try{await this.rateLimiter.check(t,e)}catch(r){throw this.anomalyDetector.record("rate_limit.exceeded",t),r}}encryptPii(t,e){return!this.piiEncryptor||e.length===0?t:this.piiEncryptor.encryptFields(t,e)}decryptPii(t,e){return!this.piiEncryptor||e.length===0?t:this.piiEncryptor.decryptFields(t,e)}static VALID_ANOMALY_TYPES=new Set(["auth.failures","bulk.deletes","bulk.reads","bulk.exports","rate_limit.exceeded"]);recordAnomaly(t,e){if(!i.VALID_ANOMALY_TYPES.has(t))throw new Error(`[dndev/security] DndevSecurity: unknown anomaly type "${t}". Valid types: ${[...i.VALID_ANOMALY_TYPES].join(", ")}`);this.anomalyDetector.record(t,e)}}export{i as DndevSecurity};
@@ -0,0 +1,70 @@
1
+ /**
2
+ * AES-256-GCM PII encryptor with per-field IV (SOC2 C1, CC6.1).
3
+ *
4
+ * **IMPORTANT:** Instantiate ONCE at app startup (e.g. inside `DndevSecurity`).
5
+ * The constructor calls `scryptSync` which is CPU-intensive by design — repeated
6
+ * construction per-request is a DoS vector.
7
+ *
8
+ * Each field gets a fresh random IV — identical plaintext produces different ciphertext.
9
+ * Output format: `dnpii1:<ivHex>:<authTagHex>:<ciphertextHex>`
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // Salt MUST be a per-deployment secret stored alongside piiSecret in your secret manager.
14
+ * // NEVER use a hard-coded or framework-level salt.
15
+ * const enc = new PiiEncryptor(process.env.PII_SECRET!, process.env.PII_SALT!);
16
+ * const stored = enc.encryptFields({ email: 'alice@example.com', name: 'Alice' }, ['email']);
17
+ * // stored.email → 'dnpii1:a1b2...:c3d4...:e5f6...' (encrypted)
18
+ * // stored.name → 'Alice' (unchanged)
19
+ *
20
+ * const plain = enc.decryptFields(stored, ['email']);
21
+ * // plain.email → 'alice@example.com'
22
+ * ```
23
+ *
24
+ * @version 0.0.2
25
+ * @since 0.0.1
26
+ * @author AMBROISE PARK Consulting
27
+ */
28
+ export declare class PiiEncryptor {
29
+ private readonly key;
30
+ /**
31
+ * @param secret - Master secret (min 32 chars). Store in secret manager, never in code.
32
+ * @param salt - Per-deployment salt. Must be unique per deployment and stored as a secret.
33
+ * Do NOT use a shared or hard-coded value — a universal salt eliminates
34
+ * rainbow-table resistance for the derived key.
35
+ */
36
+ constructor(secret: string, salt: string);
37
+ /**
38
+ * Encrypt a single string value.
39
+ * @returns `dnpii1:<iv>:<tag>:<ciphertext>` (all hex, prefixed for unambiguous detection)
40
+ */
41
+ encrypt(plaintext: string): string;
42
+ /**
43
+ * Decrypt a value produced by `encrypt()`.
44
+ * Supports the legacy format (no `dnpii1:` prefix) for backward compatibility.
45
+ * @throws if ciphertext is malformed or authentication tag is invalid.
46
+ */
47
+ decrypt(ciphertext: string): string;
48
+ /**
49
+ * Encrypt specified fields in a data object (returns new object, input unchanged).
50
+ * Non-string field values and missing fields are silently skipped.
51
+ */
52
+ encryptFields<T extends Record<string, unknown>>(data: T, piiFields: string[]): T;
53
+ /**
54
+ * Detect whether a string was produced by `encrypt()`.
55
+ * Checks for the `dnpii1:` version prefix — unambiguous, no false positives.
56
+ * Also accepts the legacy format (24-char IV hex + 32-char tag hex) for backward compat.
57
+ */
58
+ private isEncrypted;
59
+ /** Zero the derived key buffer. Call when the encryptor is no longer needed. */
60
+ dispose(): void;
61
+ /** TC39 explicit resource management support. */
62
+ [Symbol.dispose](): void;
63
+ /**
64
+ * Decrypt specified fields in a data object (returns new object, input unchanged).
65
+ * Fields that do not pass the encrypted-format check are left as-is.
66
+ * Auth tag failures (wrong key / tampered data) are re-thrown — they must not be silenced.
67
+ */
68
+ decryptFields<T extends Record<string, unknown>>(data: T, piiFields: string[]): T;
69
+ }
70
+ //# sourceMappingURL=PiiEncryptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PiiEncryptor.d.ts","sourceRoot":"","sources":["../../src/server/PiiEncryptor.ts"],"names":[],"mappings":"AAmCA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B;;;;;OAKG;gBACS,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAexC;;;OAGG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAWlC;;;;OAIG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IA8BnC;;;OAGG;IACH,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC;IAYjF;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAgBnB,gFAAgF;IAChF,OAAO,IAAI,IAAI;IAIf,iDAAiD;IACjD,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;IAIxB;;;;OAIG;IACH,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC;CAYlF"}
@@ -0,0 +1 @@
1
+ import{createCipheriv as y,createDecipheriv as u,randomBytes as g,scryptSync as E}from"node:crypto";const d="aes-256-gcm",v=32,i=12,a=16,o="dnpii1:";class w{key;constructor(t,r){if(!t||t.length<32)throw new Error("[dndev/security] PiiEncryptor: secret must be at least 32 characters");if(!r||r.length<8)throw new Error("[dndev/security] PiiEncryptor: salt is required and must be at least 8 characters. Use a per-deployment secret stored in your secret manager \u2014 never a hard-coded value.");this.key=E(t,r,v,{N:65536,r:8,p:1})}encrypt(t){const r=g(i),e=y(d,this.key,r),s=Buffer.concat([e.update(t,"utf8"),e.final()]),n=e.getAuthTag();return`${o}${r.toString("hex")}:${n.toString("hex")}:${s.toString("hex")}`}decrypt(t){const e=(t.startsWith(o)?t.slice(o.length):t).split(":");if(e.length!==3)throw new Error("[dndev/security] PiiEncryptor: invalid ciphertext format");const[s,n,l]=e,c=Buffer.from(s,"hex"),h=Buffer.from(n,"hex"),p=Buffer.from(l,"hex");if(c.length!==i)throw new Error(`[dndev/security] PiiEncryptor: invalid IV length ${c.length}, expected ${i}`);if(h.length!==a)throw new Error(`[dndev/security] PiiEncryptor: invalid auth tag length ${h.length}, expected ${a}`);const f=u(d,this.key,c);return f.setAuthTag(h),f.update(p).toString("utf8")+f.final("utf8")}encryptFields(t,r){if(r.length===0)return t;const e={...t};for(const s of r){const n=e[s];typeof n=="string"&&(e[s]=this.encrypt(n))}return e}isEncrypted(t){if(t.startsWith(o))return!0;const r=t.split(":");if(r.length!==3)return!1;const[e,s]=r,n=/^[0-9a-f]+$/i;return e.length===i*2&&s.length===a*2&&n.test(e)&&n.test(s)}dispose(){this.key.fill(0)}[Symbol.dispose](){this.dispose()}decryptFields(t,r){if(r.length===0)return t;const e={...t};for(const s of r){const n=e[s];typeof n=="string"&&this.isEncrypted(n)&&(e[s]=this.decrypt(n))}return e}}export{w as PiiEncryptor};
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @fileoverview PrivacyManager
3
+ * @description Right-to-erasure workflows and data retention policies.
4
+ * Covers SOC2 Privacy Principle (P1-P8) and GDPR Article 17.
5
+ *
6
+ * @version 0.0.1
7
+ * @since 0.0.1
8
+ * @author AMBROISE PARK Consulting
9
+ */
10
+ export interface RetentionPolicy {
11
+ /** Collection/table name */
12
+ collection: string;
13
+ /**
14
+ * Days to retain data. 0 = no automatic purge.
15
+ * After `days` days, `shouldPurge()` returns true.
16
+ */
17
+ days: number;
18
+ /**
19
+ * Field to read for the document's creation timestamp (ISO 8601 string).
20
+ * @default 'createdAt'
21
+ */
22
+ dateField?: string;
23
+ }
24
+ export interface ErasureRequest {
25
+ /** User whose data should be erased */
26
+ userId: string;
27
+ /** Collections to erase data from */
28
+ collections: string[];
29
+ /**
30
+ * Callback that performs the actual deletion for one collection.
31
+ * Called once per collection in `collections`.
32
+ */
33
+ deleteUserData: (collection: string, userId: string) => Promise<void>;
34
+ }
35
+ export interface ErasureResult {
36
+ /** Collections successfully erased */
37
+ erased: string[];
38
+ /** Collections that failed with error messages */
39
+ errors: Array<{
40
+ collection: string;
41
+ message: string;
42
+ }>;
43
+ }
44
+ /**
45
+ * Privacy Manager — right-to-erasure (GDPR Art. 17) and retention policies (SOC2 P6).
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const privacy = new PrivacyManager([
50
+ * { collection: 'audit_logs', days: 365 },
51
+ * { collection: 'sessions', days: 90 },
52
+ * ]);
53
+ *
54
+ * // Erase all user data across collections:
55
+ * await privacy.eraseUser({
56
+ * userId: 'u123',
57
+ * collections: ['users', 'orders', 'sessions'],
58
+ * deleteUserData: async (col, uid) => db.from(col).delete().eq('user_id', uid),
59
+ * });
60
+ *
61
+ * // Check if a document should be purged:
62
+ * privacy.shouldPurge('audit_logs', doc.createdAt); // true if > 365 days old
63
+ * ```
64
+ *
65
+ * @version 0.0.1
66
+ * @since 0.0.1
67
+ * @author AMBROISE PARK Consulting
68
+ */
69
+ export declare class PrivacyManager {
70
+ private readonly policies;
71
+ constructor(policies?: RetentionPolicy[]);
72
+ /**
73
+ * Execute right-to-erasure for a user across multiple collections.
74
+ * Calls `deleteUserData` for each collection; collects partial errors without aborting.
75
+ * @throws {Error} if `collections` is empty — a silent no-op would be a GDPR violation.
76
+ */
77
+ eraseUser(request: ErasureRequest): Promise<ErasureResult>;
78
+ /**
79
+ * Check if a document should be purged based on its age and the configured retention policy.
80
+ * Returns `false` if no policy is configured for the collection.
81
+ *
82
+ * @param collection - Collection name
83
+ * @param dateIso - ISO 8601 creation timestamp of the document
84
+ */
85
+ shouldPurge(collection: string, dateIso: string): boolean;
86
+ /** Return all configured retention policies (used by `dn soc2` checker). */
87
+ getPolicies(): RetentionPolicy[];
88
+ }
89
+ //# sourceMappingURL=PrivacyManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrivacyManager.d.ts","sourceRoot":"","sources":["../../src/server/PrivacyManager.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kDAAkD;IAClD,MAAM,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;gBAEjC,QAAQ,GAAE,eAAe,EAAO;IAI5C;;;;OAIG;IACG,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA2BhE;;;;;;OAMG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAwBzD,4EAA4E;IAC5E,WAAW,IAAI,eAAe,EAAE;CAGjC"}
@@ -0,0 +1 @@
1
+ class a{policies;constructor(e=[]){this.policies=e}async eraseUser(e){if(e.collections.length===0)throw new Error("[dndev/security] eraseUser: collections array is empty. Provide at least one collection to erase user data from. A no-op erasure silently violates GDPR Art. 17.");const r=[],s=[];for(const o of e.collections)try{await e.deleteUserData(o,e.userId),r.push(o)}catch(t){s.push({collection:o,message:t instanceof Error?t.message:String(t)})}return{erased:r,errors:s}}shouldPurge(e,r){const s=this.policies.find(n=>n.collection===e);if(!s||s.days===0)return!1;if(!r)throw new Error(`[dndev/security] shouldPurge: missing dateIso for collection "${e}". Expected ISO 8601 string. Cannot determine if document should be purged.`);const o=new Date(r).getTime();if(isNaN(o))throw new Error(`[dndev/security] shouldPurge: invalid dateIso "${r}" for collection "${e}". Expected ISO 8601 string. Cannot determine if document should be purged.`);const t=Date.now()-o,i=s.days*24*60*60*1e3;return t>i}getPolicies(){return this.policies}}export{a as PrivacyManager};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @fileoverview RateLimiter
3
+ * @description In-memory fixed-window rate limiter. Zero deps.
4
+ * Covers SOC2 CC6.6 / OWASP API4: unrestricted resource consumption.
5
+ *
6
+ * Note: This is a fixed-window (not sliding-window) implementation. A burst of
7
+ * `points` requests at the end of window N and `points` at the start of N+1 can
8
+ * briefly double the effective rate. Use a Redis-backed backend with a true
9
+ * sliding-window for strict rate control in production.
10
+ *
11
+ * For distributed (multi-replica) deployments, implement RateLimiterBackend
12
+ * and provide a Redis-backed implementation.
13
+ *
14
+ * @version 0.0.1
15
+ * @since 0.0.1
16
+ * @author AMBROISE PARK Consulting
17
+ */
18
+ export interface RateLimitWindow {
19
+ /** Max requests in window (default: 100 for writes, 500 for reads) */
20
+ points: number;
21
+ /** Window duration in seconds (default: 60) */
22
+ durationSeconds: number;
23
+ }
24
+ /**
25
+ * Backend interface for rate limiting — implement with Redis in production.
26
+ *
27
+ * @version 0.0.2
28
+ * @since 0.0.1
29
+ * @author AMBROISE PARK Consulting
30
+ */
31
+ export interface RateLimiterBackend {
32
+ increment(key: string, windowMs: number): Promise<number>;
33
+ reset(key: string): Promise<void>;
34
+ }
35
+ /**
36
+ * In-memory rate limiter (single instance / single replica).
37
+ * Performs lazy eviction of expired entries when the store exceeds
38
+ * {@link EVICTION_THRESHOLD} keys to prevent unbounded memory growth.
39
+ *
40
+ * @version 0.0.1
41
+ * @since 0.0.1
42
+ * @author AMBROISE PARK Consulting
43
+ */
44
+ export declare class MemoryRateLimiterBackend implements RateLimiterBackend {
45
+ private readonly store;
46
+ increment(key: string, windowMs: number): Promise<number>;
47
+ reset(key: string): Promise<void>;
48
+ private _evictExpired;
49
+ }
50
+ export interface RateLimiterOptions {
51
+ writes?: Partial<RateLimitWindow>;
52
+ reads?: Partial<RateLimitWindow>;
53
+ /** Custom backend (default: MemoryRateLimiterBackend) */
54
+ backend?: RateLimiterBackend;
55
+ }
56
+ /**
57
+ * Rate limiter with separate write/read limits (SOC2 CC6.6).
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const limiter = new DndevRateLimiter({ writes: { points: 50 } });
62
+ * await limiter.check('user:abc', 'write'); // throws if > 50 writes/min
63
+ * ```
64
+ *
65
+ * @version 0.0.1
66
+ * @since 0.0.1
67
+ * @author AMBROISE PARK Consulting
68
+ */
69
+ export declare class DndevRateLimiter {
70
+ private readonly backend;
71
+ private readonly writes;
72
+ private readonly reads;
73
+ constructor(opts?: RateLimiterOptions);
74
+ /**
75
+ * Consume one point for key + operation.
76
+ * @throws {Error} with message 'Rate limit exceeded' when threshold breached.
77
+ */
78
+ check(key: string, operation: 'read' | 'write'): Promise<void>;
79
+ }
80
+ //# sourceMappingURL=RateLimiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/server/RateLimiter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,eAAe,EAAE,MAAM,CAAC;CACzB;AASD;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAKD;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,YAAW,kBAAkB;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IAElD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBzD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,OAAO,CAAC,aAAa;CAStB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACjC,yDAAyD;IACzD,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;gBAE5B,IAAI,GAAE,kBAAuB;IAYzC;;;OAGG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAWrE"}
@@ -0,0 +1 @@
1
+ const c=1e4;class o{store=new Map;async increment(t,s){const e=Date.now(),n=this.store.get(t);return!n||e-n.windowStart>s?(!n&&this.store.size>=1e4&&this._evictExpired(e),this.store.set(t,{count:1,windowStart:e,windowMs:s}),1):(n.count+=1,n.count)}async reset(t){this.store.delete(t)}_evictExpired(t){for(const[s,e]of this.store)t-e.windowStart>e.windowMs&&this.store.delete(s)}}class d{backend;writes;reads;constructor(t={}){this.backend=t.backend??new o,this.writes={points:t.writes?.points??100,durationSeconds:t.writes?.durationSeconds??60},this.reads={points:t.reads?.points??500,durationSeconds:t.reads?.durationSeconds??60}}async check(t,s){const e=s==="write"?this.writes:this.reads,n=e.durationSeconds*1e3,i=await this.backend.increment(`${s}:${t}`,n);if(i>e.points)throw new Error(`Rate limit exceeded: ${i}/${e.points} ${s} requests in ${e.durationSeconds}s`)}}export{d as DndevRateLimiter,o as MemoryRateLimiterBackend};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Recursively scrub secret values from a log payload.
3
+ * Replaces matching string values with `[REDACTED]`.
4
+ * Safe to use as a Pino serializer or before any `console.log` call.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const payload = { user: 'alice', password: 'hunter2', meta: { apiKey: 'sk_live_abc' } };
9
+ * console.log(JSON.stringify(scrubSecrets(payload)));
10
+ * // → {"user":"alice","password":"[REDACTED]","meta":{"apiKey":"[REDACTED]"}}
11
+ * ```
12
+ */
13
+ export declare function scrubSecrets(value: unknown): unknown;
14
+ /**
15
+ * Assert that an object does NOT contain any detectable secrets.
16
+ * Throws before the value would reach a log sink.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * assertNoSecrets(responsePayload, 'API response');
21
+ * ```
22
+ *
23
+ * @throws {Error} if secrets are detected or if the value is non-serializable
24
+ */
25
+ export declare function assertNoSecrets(obj: unknown, context: string): void;
26
+ //# sourceMappingURL=SecretValidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretValidator.d.ts","sourceRoot":"","sources":["../../src/server/SecretValidator.ts"],"names":[],"mappings":"AAqCA;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAsBpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAmBnE"}
@@ -0,0 +1 @@
1
+ const n=[/password\s*[:=]\s*\S+/gi,/secret\s*[:=]\s*\S+/gi,/api[_-]?key\s*[:=]\s*\S+/gi,/token\s*[:=]\s*\S+/gi,/bearer\s+[A-Za-z0-9\-._~+/]+=*/gi,/-----BEGIN .+?-----/g,/sk_live_[A-Za-z0-9]+/g,/sk_test_[A-Za-z0-9]+/g,/ghp_[A-Za-z0-9]{36,}/g,/gho_[A-Za-z0-9]{36,}/g,/AKIA[A-Z0-9]{16}/g,/xox[bpsa]-[A-Za-z0-9\-]+/g,/glpat-[A-Za-z0-9\-_]{20,}/g],a=/password|passwd|secret|token|apikey|api_key|credential|private_key|access_key|\bauth\b/i;function i(e){if(typeof e=="string"){let t=e;for(const r of n)t=t.replace(r,"[REDACTED]");return t}if(Array.isArray(e))return e.map(i);if(e!==null&&typeof e=="object"){const t={};for(const[r,s]of Object.entries(e))t[r]=a.test(r)?"[REDACTED]":i(s);return t}return e}function o(e,t){let r,s;try{s=JSON.stringify(e),r=JSON.stringify(i(e))}catch{throw new Error(`[dndev/security] assertNoSecrets: cannot serialize value in "${t}". Non-serializable values cannot be verified for secrets. Audit the object manually.`)}if(r!==s)throw new Error(`[dndev/security] Secret detected in ${t}. Aborting to prevent credential leak.`)}export{o as assertNoSecrets,i as scrubSecrets};
@@ -0,0 +1,16 @@
1
+ export { AuditLogger } from './AuditLogger';
2
+ export type { AuditLoggerOptions } from './AuditLogger';
3
+ export { DndevRateLimiter, MemoryRateLimiterBackend } from './RateLimiter';
4
+ export type { RateLimiterBackend, RateLimiterOptions, RateLimitWindow } from './RateLimiter';
5
+ export { PiiEncryptor } from './PiiEncryptor';
6
+ export { AuthHardening } from './AuthHardening';
7
+ export type { AuthHardeningConfig, LockoutResult } from './AuthHardening';
8
+ export { AnomalyDetector } from './AnomalyDetector';
9
+ export type { AnomalyThresholds, AnomalyHandler, AnomalyType } from './AnomalyDetector';
10
+ export { PrivacyManager } from './PrivacyManager';
11
+ export type { RetentionPolicy, ErasureRequest, ErasureResult } from './PrivacyManager';
12
+ export { scrubSecrets, assertNoSecrets } from './SecretValidator';
13
+ export { DndevSecurity } from './DndevSecurity';
14
+ export type { DndevSecurityConfig } from './DndevSecurity';
15
+ export type { SecurityContext, AuditEvent, AuditEventType } from '../common/SecurityConfig';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC3E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE7F,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExF,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEvF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG3D,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1 @@
1
+ import{AuditLogger as o}from"./AuditLogger";import{DndevRateLimiter as m,MemoryRateLimiterBackend as i}from"./RateLimiter";import{PiiEncryptor as p}from"./PiiEncryptor";import{AuthHardening as f}from"./AuthHardening";import{AnomalyDetector as x}from"./AnomalyDetector";import{PrivacyManager as s}from"./PrivacyManager";import{scrubSecrets as g,assertNoSecrets as u}from"./SecretValidator";import{DndevSecurity as A}from"./DndevSecurity";export{x as AnomalyDetector,o as AuditLogger,f as AuthHardening,m as DndevRateLimiter,A as DndevSecurity,i as MemoryRateLimiterBackend,p as PiiEncryptor,s as PrivacyManager,u as assertNoSecrets,g as scrubSecrets};
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@donotdev/security",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "license": "SEE LICENSE IN LICENSE.md",
7
+ "description": "SOC2-grade security controls for DoNotDev — audit logging, rate limiting, PII encryption, auth hardening, anomaly detection, privacy management",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./server": {
17
+ "types": "./dist/server/index.d.ts",
18
+ "import": "./dist/server/index.js",
19
+ "default": "./dist/server/index.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "dev": "tsc --noEmit --watch --listFiles false --listEmittedFiles false",
24
+ "clean": "rimraf dist tsconfig.tsbuildinfo",
25
+ "type-check": "tsc --noEmit",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest"
28
+ },
29
+ "dependencies": {},
30
+ "peerDependencies": {
31
+ "@donotdev/core": "^0.0.24"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "package.json",
36
+ "README.md",
37
+ "LICENSE.md"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/donotdev/dndev.git"
42
+ },
43
+ "keywords": [
44
+ "donotdev",
45
+ "dndev",
46
+ "security",
47
+ "soc2",
48
+ "audit",
49
+ "rate-limit",
50
+ "encryption",
51
+ "privacy",
52
+ "typescript"
53
+ ],
54
+ "publishConfig": {
55
+ "registry": "https://registry.npmjs.org",
56
+ "access": "public"
57
+ }
58
+ }