@troykelly/openclaw-projects 0.0.11 → 0.0.12

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 (88) hide show
  1. package/dist/config.d.ts +8 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +6 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/hooks.d.ts.map +1 -1
  6. package/dist/hooks.js +17 -3
  7. package/dist/hooks.js.map +1 -1
  8. package/dist/register-openclaw.d.ts +3 -1
  9. package/dist/register-openclaw.d.ts.map +1 -1
  10. package/dist/register-openclaw.js +611 -55
  11. package/dist/register-openclaw.js.map +1 -1
  12. package/dist/tools/context-search.d.ts +79 -0
  13. package/dist/tools/context-search.d.ts.map +1 -0
  14. package/dist/tools/context-search.js +265 -0
  15. package/dist/tools/context-search.js.map +1 -0
  16. package/dist/tools/email-send.d.ts.map +1 -1
  17. package/dist/tools/email-send.js +1 -14
  18. package/dist/tools/email-send.js.map +1 -1
  19. package/dist/tools/entity-links.d.ts +117 -0
  20. package/dist/tools/entity-links.d.ts.map +1 -0
  21. package/dist/tools/entity-links.js +446 -0
  22. package/dist/tools/entity-links.js.map +1 -0
  23. package/dist/tools/index.d.ts +4 -0
  24. package/dist/tools/index.d.ts.map +1 -1
  25. package/dist/tools/index.js +8 -0
  26. package/dist/tools/index.js.map +1 -1
  27. package/dist/tools/memory-recall.d.ts +28 -0
  28. package/dist/tools/memory-recall.d.ts.map +1 -1
  29. package/dist/tools/memory-recall.js +42 -3
  30. package/dist/tools/memory-recall.js.map +1 -1
  31. package/dist/tools/memory-store.d.ts +57 -0
  32. package/dist/tools/memory-store.d.ts.map +1 -1
  33. package/dist/tools/memory-store.js +29 -2
  34. package/dist/tools/memory-store.js.map +1 -1
  35. package/dist/tools/message-search.d.ts +1 -1
  36. package/dist/tools/message-search.d.ts.map +1 -1
  37. package/dist/tools/message-search.js +20 -2
  38. package/dist/tools/message-search.js.map +1 -1
  39. package/dist/tools/notes.d.ts +2 -2
  40. package/dist/tools/project-search.d.ts +92 -0
  41. package/dist/tools/project-search.d.ts.map +1 -0
  42. package/dist/tools/project-search.js +160 -0
  43. package/dist/tools/project-search.js.map +1 -0
  44. package/dist/tools/skill-store.d.ts +12 -12
  45. package/dist/tools/threads.d.ts +2 -2
  46. package/dist/tools/threads.d.ts.map +1 -1
  47. package/dist/tools/threads.js +30 -6
  48. package/dist/tools/threads.js.map +1 -1
  49. package/dist/tools/todo-search.d.ts +95 -0
  50. package/dist/tools/todo-search.d.ts.map +1 -0
  51. package/dist/tools/todo-search.js +164 -0
  52. package/dist/tools/todo-search.js.map +1 -0
  53. package/dist/types/openclaw-api.d.ts +15 -0
  54. package/dist/types/openclaw-api.d.ts.map +1 -1
  55. package/dist/utils/auto-linker.d.ts +66 -0
  56. package/dist/utils/auto-linker.d.ts.map +1 -0
  57. package/dist/utils/auto-linker.js +354 -0
  58. package/dist/utils/auto-linker.js.map +1 -0
  59. package/dist/utils/geo.d.ts +24 -0
  60. package/dist/utils/geo.d.ts.map +1 -0
  61. package/dist/utils/geo.js +38 -0
  62. package/dist/utils/geo.js.map +1 -0
  63. package/dist/utils/inbound-gate.d.ts +85 -0
  64. package/dist/utils/inbound-gate.d.ts.map +1 -0
  65. package/dist/utils/inbound-gate.js +133 -0
  66. package/dist/utils/inbound-gate.js.map +1 -0
  67. package/dist/utils/injection-protection.d.ts +81 -0
  68. package/dist/utils/injection-protection.d.ts.map +1 -0
  69. package/dist/utils/injection-protection.js +179 -0
  70. package/dist/utils/injection-protection.js.map +1 -0
  71. package/dist/utils/nominatim.d.ts +18 -0
  72. package/dist/utils/nominatim.d.ts.map +1 -0
  73. package/dist/utils/nominatim.js +56 -0
  74. package/dist/utils/nominatim.js.map +1 -0
  75. package/dist/utils/rate-limiter.d.ts +81 -0
  76. package/dist/utils/rate-limiter.d.ts.map +1 -0
  77. package/dist/utils/rate-limiter.js +188 -0
  78. package/dist/utils/rate-limiter.js.map +1 -0
  79. package/dist/utils/spam-filter.d.ts +79 -0
  80. package/dist/utils/spam-filter.d.ts.map +1 -0
  81. package/dist/utils/spam-filter.js +237 -0
  82. package/dist/utils/spam-filter.js.map +1 -0
  83. package/dist/utils/token-budget.d.ts +68 -0
  84. package/dist/utils/token-budget.d.ts.map +1 -0
  85. package/dist/utils/token-budget.js +142 -0
  86. package/dist/utils/token-budget.js.map +1 -0
  87. package/openclaw.plugin.json +3 -3
  88. package/package.json +1 -1
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Inbound message gate — unified pre-processing gate for inbound messages.
3
+ *
4
+ * Orchestrates spam filter, rate limiter, and token budget as a single
5
+ * evaluation point before any message processing (embedding, linking, etc.).
6
+ *
7
+ * NOTE: All state (rate limiter windows, token budget counters) is held
8
+ * in-memory and does not persist across process restarts or span multiple
9
+ * instances. This is acceptable for single-instance deployments. For
10
+ * multi-instance deployments, the rate limiter and token budget would need
11
+ * to be backed by skill_store or an external store (e.g. Redis) to share
12
+ * state across processes.
13
+ *
14
+ * Part of Issue #1225 — rate limiting and spam protection.
15
+ */
16
+ import { type SpamFilterConfig, type InboundMessage } from './spam-filter.js';
17
+ import { type RateLimiterConfig, type SenderTrust } from './rate-limiter.js';
18
+ import { type TokenBudgetConfig } from './token-budget.js';
19
+ import type { Logger } from '../logger.js';
20
+ /** Re-export InboundMessage for convenience */
21
+ export type { InboundMessage } from './spam-filter.js';
22
+ /** Actions the gate can take on a message */
23
+ export type GateAction = 'allow' | 'reject' | 'rate_limited' | 'budget_exceeded' | 'defer';
24
+ /** Result of the inbound gate evaluation */
25
+ export interface InboundGateDecision {
26
+ /** Action to take on the message */
27
+ action: GateAction;
28
+ /** Human-readable reason for the decision, null if allowed */
29
+ reason: string | null;
30
+ /** Whether to skip embedding for this message */
31
+ skipEmbedding: boolean;
32
+ }
33
+ /** Configuration for the inbound gate */
34
+ export interface InboundGateConfig {
35
+ /** Spam filter configuration */
36
+ spamFilter: SpamFilterConfig;
37
+ /** Rate limiter configuration */
38
+ rateLimiter: RateLimiterConfig;
39
+ /** Token budget configuration */
40
+ tokenBudget: TokenBudgetConfig;
41
+ /** Whether to defer processing for unknown senders */
42
+ deferUnknownSenders?: boolean;
43
+ }
44
+ /** Aggregate gate statistics */
45
+ export interface InboundGateStats {
46
+ /** Total messages evaluated */
47
+ totalEvaluated: number;
48
+ /** Messages allowed through */
49
+ allowed: number;
50
+ /** Messages rejected as spam */
51
+ rejected: number;
52
+ /** Messages rate-limited */
53
+ rateLimited: number;
54
+ /** Messages denied due to budget */
55
+ budgetExceeded: number;
56
+ /** Messages deferred */
57
+ deferred: number;
58
+ }
59
+ /** Inbound gate instance */
60
+ export interface InboundGate {
61
+ /**
62
+ * Evaluate a message and return a gate decision.
63
+ * When estimatedTokens > 0 and the message is allowed, the token budget
64
+ * is atomically consumed during evaluation — callers must NOT separately
65
+ * record token usage or the budget will be double-counted.
66
+ */
67
+ evaluate(message: InboundMessage, trust: SenderTrust, estimatedTokens?: number): InboundGateDecision;
68
+ /** Get aggregate statistics */
69
+ getStats(): InboundGateStats;
70
+ }
71
+ /**
72
+ * Create an inbound message gate.
73
+ *
74
+ * Checks are applied in this order:
75
+ * 1. Spam filter (reject bulk/marketing/known spam)
76
+ * 2. Rate limiter (per-sender and per-recipient caps)
77
+ * 3. Token budget (cost protection)
78
+ * 4. Deferred processing (unknown senders, if configured)
79
+ *
80
+ * @param config - Gate configuration
81
+ * @param logger - Logger for recording decisions
82
+ * @returns InboundGate instance
83
+ */
84
+ export declare function createInboundGate(config: InboundGateConfig, logger: Logger): InboundGate;
85
+ //# sourceMappingURL=inbound-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-gate.d.ts","sourceRoot":"","sources":["../../src/utils/inbound-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAU,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAqB,KAAK,iBAAiB,EAAoB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAClH,OAAO,EAAqB,KAAK,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAChG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,+CAA+C;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,6CAA6C;AAC7C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,cAAc,GAAG,iBAAiB,GAAG,OAAO,CAAC;AAE3F,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iDAAiD;IACjD,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,yCAAyC;AACzC,MAAM,WAAW,iBAAiB;IAChC,gCAAgC;IAChC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,iCAAiC;IACjC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,iCAAiC;IACjC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4BAA4B;AAC5B,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACrG,+BAA+B;IAC/B,QAAQ,IAAI,gBAAgB,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CA4GxF"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Inbound message gate — unified pre-processing gate for inbound messages.
3
+ *
4
+ * Orchestrates spam filter, rate limiter, and token budget as a single
5
+ * evaluation point before any message processing (embedding, linking, etc.).
6
+ *
7
+ * NOTE: All state (rate limiter windows, token budget counters) is held
8
+ * in-memory and does not persist across process restarts or span multiple
9
+ * instances. This is acceptable for single-instance deployments. For
10
+ * multi-instance deployments, the rate limiter and token budget would need
11
+ * to be backed by skill_store or an external store (e.g. Redis) to share
12
+ * state across processes.
13
+ *
14
+ * Part of Issue #1225 — rate limiting and spam protection.
15
+ */
16
+ import { isSpam } from './spam-filter.js';
17
+ import { createRateLimiter } from './rate-limiter.js';
18
+ import { createTokenBudget } from './token-budget.js';
19
+ /**
20
+ * Create an inbound message gate.
21
+ *
22
+ * Checks are applied in this order:
23
+ * 1. Spam filter (reject bulk/marketing/known spam)
24
+ * 2. Rate limiter (per-sender and per-recipient caps)
25
+ * 3. Token budget (cost protection)
26
+ * 4. Deferred processing (unknown senders, if configured)
27
+ *
28
+ * @param config - Gate configuration
29
+ * @param logger - Logger for recording decisions
30
+ * @returns InboundGate instance
31
+ */
32
+ export function createInboundGate(config, logger) {
33
+ const rateLimiter = createRateLimiter(config.rateLimiter);
34
+ const tokenBudget = createTokenBudget(config.tokenBudget);
35
+ let totalEvaluated = 0;
36
+ let allowed = 0;
37
+ let rejected = 0;
38
+ let rateLimited = 0;
39
+ let budgetExceeded = 0;
40
+ let deferred = 0;
41
+ return {
42
+ evaluate(message, trust, estimatedTokens = 0) {
43
+ totalEvaluated++;
44
+ // Step 1: Spam filter
45
+ const spamResult = isSpam(message, config.spamFilter);
46
+ if (spamResult.isSpam) {
47
+ rejected++;
48
+ logger.info('inbound gate: spam rejected', {
49
+ sender: message.sender,
50
+ channel: message.channel,
51
+ reason: spamResult.reason,
52
+ });
53
+ return {
54
+ action: 'reject',
55
+ reason: `spam: ${spamResult.reason}`,
56
+ skipEmbedding: true,
57
+ };
58
+ }
59
+ // Step 2: Rate limiter (pass channel for sender normalization)
60
+ const rateResult = rateLimiter.check(message.sender, message.recipient, trust, message.channel);
61
+ if (!rateResult.allowed) {
62
+ rateLimited++;
63
+ logger.warn('inbound gate: rate limited', {
64
+ sender: message.sender,
65
+ channel: message.channel,
66
+ trust,
67
+ reason: rateResult.reason,
68
+ remaining: rateResult.remaining,
69
+ retryAfterMs: rateResult.retryAfterMs,
70
+ });
71
+ return {
72
+ action: 'rate_limited',
73
+ reason: `rate limited: ${rateResult.reason}`,
74
+ skipEmbedding: true,
75
+ };
76
+ }
77
+ // Step 3: Token budget (uses tryConsume for atomic check-and-record)
78
+ if (estimatedTokens > 0) {
79
+ const budgetResult = tokenBudget.tryConsume(estimatedTokens);
80
+ if (!budgetResult.allowed) {
81
+ budgetExceeded++;
82
+ logger.warn('inbound gate: token budget exceeded', {
83
+ sender: message.sender,
84
+ channel: message.channel,
85
+ estimatedTokens,
86
+ reason: budgetResult.reason,
87
+ });
88
+ return {
89
+ action: 'budget_exceeded',
90
+ reason: `budget exceeded: ${budgetResult.reason}`,
91
+ skipEmbedding: true,
92
+ };
93
+ }
94
+ }
95
+ // Step 4: Deferred processing for unknown senders
96
+ if (config.deferUnknownSenders && trust === 'unknown') {
97
+ deferred++;
98
+ logger.debug('inbound gate: deferring unknown sender', {
99
+ sender: message.sender,
100
+ channel: message.channel,
101
+ });
102
+ return {
103
+ action: 'defer',
104
+ reason: 'unknown sender deferred for batch processing',
105
+ skipEmbedding: true,
106
+ };
107
+ }
108
+ // Message is allowed
109
+ allowed++;
110
+ logger.debug('inbound gate: allowed', {
111
+ sender: message.sender,
112
+ channel: message.channel,
113
+ trust,
114
+ });
115
+ return {
116
+ action: 'allow',
117
+ reason: null,
118
+ skipEmbedding: false,
119
+ };
120
+ },
121
+ getStats() {
122
+ return {
123
+ totalEvaluated,
124
+ allowed,
125
+ rejected,
126
+ rateLimited,
127
+ budgetExceeded,
128
+ deferred,
129
+ };
130
+ },
131
+ };
132
+ }
133
+ //# sourceMappingURL=inbound-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-gate.js","sourceRoot":"","sources":["../../src/utils/inbound-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Prompt injection protection utilities for inbound message processing.
3
+ *
4
+ * Provides layered defence for external message content before LLM exposure:
5
+ * 1. Unicode/control character sanitisation
6
+ * 2. Data boundary marking (spotlighting)
7
+ * 3. Suspicious pattern detection and logging
8
+ *
9
+ * Issue #1224
10
+ */
11
+ /** Result of injection pattern detection */
12
+ export interface InjectionDetectionResult {
13
+ /** Whether any injection patterns were detected */
14
+ detected: boolean;
15
+ /** Names of the patterns that matched */
16
+ patterns: string[];
17
+ }
18
+ /** Options for wrapping external messages */
19
+ export interface WrapOptions {
20
+ /** Communication channel (sms, email) */
21
+ channel?: string;
22
+ /** Sender name or identifier */
23
+ sender?: string;
24
+ }
25
+ /** Options for context sanitisation */
26
+ export interface ContextSanitizeOptions {
27
+ /** Message direction */
28
+ direction?: 'inbound' | 'outbound';
29
+ /** Communication channel */
30
+ channel?: string;
31
+ /** Sender name */
32
+ sender?: string;
33
+ }
34
+ /**
35
+ * Sanitize external message content by removing control characters
36
+ * and invisible Unicode characters that could be used for obfuscation.
37
+ *
38
+ * Preserves legitimate Unicode (emoji, CJK, Arabic, etc.) and whitespace
39
+ * characters (tab, newline, carriage return).
40
+ */
41
+ export declare function sanitizeExternalMessage(text: string): string;
42
+ /**
43
+ * Sanitize a metadata field (sender, channel) for safe insertion into
44
+ * the boundary wrapper header. Strips control chars, invisible Unicode,
45
+ * newlines (which could break out of the header line), and boundary markers.
46
+ */
47
+ export declare function sanitizeMetadataField(field: string): string;
48
+ /**
49
+ * Wrap external message content with data boundary markers.
50
+ *
51
+ * Uses the "spotlighting" / data marking pattern to clearly delineate
52
+ * untrusted external content from system instructions. This tells the LLM
53
+ * "this is external data, not instructions to follow."
54
+ *
55
+ * Content, sender, and channel are all sanitized and have boundary markers
56
+ * escaped before wrapping to prevent breakout attacks.
57
+ */
58
+ export declare function wrapExternalMessage(content: string, options?: WrapOptions): string;
59
+ /**
60
+ * Detect suspicious prompt injection patterns in message content.
61
+ *
62
+ * Returns detection results for logging/monitoring purposes.
63
+ * This function does NOT block or modify the message — it is purely
64
+ * for detection and alerting. Blocking legitimate messages based on
65
+ * pattern matching has too high a false positive rate.
66
+ *
67
+ * The content is sanitized (invisible chars removed) before scanning
68
+ * to prevent Unicode obfuscation from bypassing detection.
69
+ */
70
+ export declare function detectInjectionPatterns(text: string): InjectionDetectionResult;
71
+ /**
72
+ * Sanitize message content for safe inclusion in LLM context.
73
+ *
74
+ * For inbound (external) messages: sanitizes and wraps with boundary markers.
75
+ * For outbound messages: sanitizes only (no boundary wrapping needed).
76
+ *
77
+ * This is the primary function to call when preparing message content
78
+ * for any LLM-facing output (auto-recall context, tool results, etc.).
79
+ */
80
+ export declare function sanitizeMessageForContext(content: string, options?: ContextSanitizeOptions): string;
81
+ //# sourceMappingURL=injection-protection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injection-protection.d.ts","sourceRoot":"","sources":["../../src/utils/injection-protection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAsFH,4CAA4C;AAC5C,MAAM,WAAW,wBAAwB;IACvC,mDAAmD;IACnD,QAAQ,EAAE,OAAO,CAAC;IAClB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACrC,wBAAwB;IACxB,SAAS,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACnC,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5D;AAcD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAmBtF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,CAc9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAQR"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Prompt injection protection utilities for inbound message processing.
3
+ *
4
+ * Provides layered defence for external message content before LLM exposure:
5
+ * 1. Unicode/control character sanitisation
6
+ * 2. Data boundary marking (spotlighting)
7
+ * 3. Suspicious pattern detection and logging
8
+ *
9
+ * Issue #1224
10
+ */
11
+ /** Boundary markers for external message content */
12
+ const EXTERNAL_MSG_START = '[EXTERNAL_MSG_START]';
13
+ const EXTERNAL_MSG_END = '[EXTERNAL_MSG_END]';
14
+ /**
15
+ * Regex matching control characters to strip (ASCII 0x00-0x08, 0x0B, 0x0C, 0x0E-0x1F, 0x7F).
16
+ * Preserves tab (0x09), newline (0x0A), and carriage return (0x0D).
17
+ */
18
+ const CONTROL_CHARS_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
19
+ /**
20
+ * Regex matching Unicode characters commonly used for text direction manipulation
21
+ * and zero-width obfuscation:
22
+ * - U+200B Zero-width space
23
+ * - U+200C Zero-width non-joiner
24
+ * - U+200D Zero-width joiner
25
+ * - U+200E Left-to-right mark
26
+ * - U+200F Right-to-left mark
27
+ * - U+202A Left-to-right embedding
28
+ * - U+202B Right-to-left embedding
29
+ * - U+202C Pop directional formatting
30
+ * - U+202D Left-to-right override
31
+ * - U+202E Right-to-left override
32
+ * - U+2066 Left-to-right isolate
33
+ * - U+2067 Right-to-left isolate
34
+ * - U+2068 First strong isolate
35
+ * - U+2069 Pop directional isolate
36
+ * - U+FEFF Byte order mark / zero-width no-break space
37
+ */
38
+ const UNICODE_INVISIBLE_REGEX = /[\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g;
39
+ const INJECTION_PATTERNS = [
40
+ {
41
+ name: 'instruction_override',
42
+ regex: /\b(?:ignore|disregard|forget|override|bypass)\b.{0,30}\b(?:previous|prior|above|earlier|all|your|system|safety)\b.{0,30}\b(?:instructions?|rules?|guidelines?|prompts?|constraints?|directives?|and\b|do\b|instead\b)/i,
43
+ },
44
+ {
45
+ name: 'role_reassignment',
46
+ regex: /\b(?:you are now|act as|pretend (?:you are|to be)|roleplay as|behave as)\b.{0,50}\b(?:ai|assistant|bot|agent|unrestricted|dan|jailbreak|without.*?(?:restrictions?|limits?|safety|guidelines?))\b/i,
47
+ },
48
+ {
49
+ name: 'new_instructions',
50
+ regex: /\b(?:new instructions?|updated? instructions?|revised? instructions?|important (?:system )?update)\b\s*[:]/i,
51
+ },
52
+ {
53
+ name: 'system_prompt_override',
54
+ regex: /^(?:\s*[-=]{3,}\s*\n)?(?:SYSTEM|ADMIN|DEVELOPER|ROOT)\s*[:>]/im,
55
+ },
56
+ {
57
+ name: 'forget_everything',
58
+ regex: /\bforget (?:everything|all)\b.{0,30}\b(?:you (?:know|learned|were told)|and (?:start|begin))\b/i,
59
+ },
60
+ {
61
+ name: 'prompt_delimiter_exploit',
62
+ regex: /```\s*(?:system|admin|instructions?|prompt)\b/i,
63
+ },
64
+ {
65
+ name: 'tool_call_injection',
66
+ regex: /\b(?:call|use|invoke|execute|run)\b.{0,20}\b(?:the\s+)?(?:sms_send|email_send|memory_store|memory_forget|todo_create|contact_create|memory_recall)\b.{0,20}\btool\b/i,
67
+ },
68
+ {
69
+ name: 'data_exfiltration',
70
+ regex: /\b(?:send|forward|share|export|transmit|email|text)\b.{0,30}\b(?:all|every|the)\b.{0,20}\b(?:memories?|contacts?|data|information|messages?|threads?|projects?|todos?)\b.{0,30}\b(?:to|@)\b/i,
71
+ },
72
+ {
73
+ name: 'system_note_injection',
74
+ regex: /\[\s*(?:SYSTEM|ADMIN|INTERNAL)\s+(?:NOTE|MESSAGE|OVERRIDE|UPDATE)\s*[:]/i,
75
+ },
76
+ {
77
+ name: 'remember_for_later',
78
+ regex: /\bremember (?:this|that)\b.{0,30}\b(?:for later|when (?:someone|anyone|a user|the user))\b.{0,30}\b(?:send|forward|share|export)\b/i,
79
+ },
80
+ ];
81
+ /**
82
+ * Sanitize external message content by removing control characters
83
+ * and invisible Unicode characters that could be used for obfuscation.
84
+ *
85
+ * Preserves legitimate Unicode (emoji, CJK, Arabic, etc.) and whitespace
86
+ * characters (tab, newline, carriage return).
87
+ */
88
+ export function sanitizeExternalMessage(text) {
89
+ return text
90
+ .replace(CONTROL_CHARS_REGEX, '')
91
+ .replace(UNICODE_INVISIBLE_REGEX, '')
92
+ .trim();
93
+ }
94
+ /**
95
+ * Escape boundary marker keywords in a string to prevent breakout attacks.
96
+ * Replaces the keyword portion (EXTERNAL_MSG_START / EXTERNAL_MSG_END)
97
+ * regardless of surrounding brackets, since formatting code may supply
98
+ * brackets that complete a partial marker (e.g., channel `[...START` + `]`).
99
+ */
100
+ function escapeBoundaryMarkers(text) {
101
+ return text
102
+ .replace(/EXTERNAL_MSG_START/g, 'EXTERNAL_MSG_START_ESCAPED')
103
+ .replace(/EXTERNAL_MSG_END/g, 'EXTERNAL_MSG_END_ESCAPED');
104
+ }
105
+ /**
106
+ * Sanitize a metadata field (sender, channel) for safe insertion into
107
+ * the boundary wrapper header. Strips control chars, invisible Unicode,
108
+ * newlines (which could break out of the header line), and boundary markers.
109
+ */
110
+ export function sanitizeMetadataField(field) {
111
+ return escapeBoundaryMarkers(sanitizeExternalMessage(field).replace(/[\r\n]/g, ' '));
112
+ }
113
+ /**
114
+ * Wrap external message content with data boundary markers.
115
+ *
116
+ * Uses the "spotlighting" / data marking pattern to clearly delineate
117
+ * untrusted external content from system instructions. This tells the LLM
118
+ * "this is external data, not instructions to follow."
119
+ *
120
+ * Content, sender, and channel are all sanitized and have boundary markers
121
+ * escaped before wrapping to prevent breakout attacks.
122
+ */
123
+ export function wrapExternalMessage(content, options = {}) {
124
+ const sanitized = sanitizeExternalMessage(content);
125
+ // Escape any existing boundary markers in the content to prevent breakout
126
+ const escaped = escapeBoundaryMarkers(sanitized);
127
+ const attribution = [];
128
+ if (options.channel) {
129
+ attribution.push(`[${sanitizeMetadataField(options.channel)}]`);
130
+ }
131
+ if (options.sender) {
132
+ attribution.push(`from: ${sanitizeMetadataField(options.sender)}`);
133
+ }
134
+ const header = attribution.length > 0
135
+ ? `${EXTERNAL_MSG_START} ${attribution.join(' ')}`
136
+ : EXTERNAL_MSG_START;
137
+ return `${header}\n${escaped}\n${EXTERNAL_MSG_END}`;
138
+ }
139
+ /**
140
+ * Detect suspicious prompt injection patterns in message content.
141
+ *
142
+ * Returns detection results for logging/monitoring purposes.
143
+ * This function does NOT block or modify the message — it is purely
144
+ * for detection and alerting. Blocking legitimate messages based on
145
+ * pattern matching has too high a false positive rate.
146
+ *
147
+ * The content is sanitized (invisible chars removed) before scanning
148
+ * to prevent Unicode obfuscation from bypassing detection.
149
+ */
150
+ export function detectInjectionPatterns(text) {
151
+ const sanitized = sanitizeExternalMessage(text);
152
+ const matched = [];
153
+ for (const pattern of INJECTION_PATTERNS) {
154
+ if (pattern.regex.test(sanitized)) {
155
+ matched.push(pattern.name);
156
+ }
157
+ }
158
+ return {
159
+ detected: matched.length > 0,
160
+ patterns: matched,
161
+ };
162
+ }
163
+ /**
164
+ * Sanitize message content for safe inclusion in LLM context.
165
+ *
166
+ * For inbound (external) messages: sanitizes and wraps with boundary markers.
167
+ * For outbound messages: sanitizes only (no boundary wrapping needed).
168
+ *
169
+ * This is the primary function to call when preparing message content
170
+ * for any LLM-facing output (auto-recall context, tool results, etc.).
171
+ */
172
+ export function sanitizeMessageForContext(content, options = {}) {
173
+ const { direction = 'inbound', channel, sender } = options;
174
+ if (direction === 'outbound') {
175
+ return sanitizeExternalMessage(content);
176
+ }
177
+ return wrapExternalMessage(content, { channel, sender });
178
+ }
179
+ //# sourceMappingURL=injection-protection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injection-protection.js","sourceRoot":"","sources":["../../src/utils/injection-protection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,oDAAoD;AACpD,MAAM,kBAAkB,GAAG,sBAAsB,CAAC;AAClD,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAE9C;;;GAGG;AACH,MAAM,mBAAmB,GAAG,mCAAmC,CAAC;AAEhE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,uBAAuB,GAAG,kDAAkD,CAAC;AAYnF,MAAM,kBAAkB,GAAuB;IAC7C;QACE,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,wNAAwN;KAChO;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,oMAAoM;KAC5M;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,6GAA6G;KACrH;IACD;QACE,IAAI,EAAE,wBAAwB;QAC9B,KAAK,EAAE,gEAAgE;KACxE;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,iGAAiG;KACzG;IACD;QACE,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE,gDAAgD;KACxD;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,KAAK,EAAE,sKAAsK;KAC9K;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,8LAA8L;KACtM;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,0EAA0E;KAClF;IACD;QACE,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,qIAAqI;KAC7I;CACF,CAAC;AA4BF;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO,IAAI;SACR,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;SACpC,IAAI,EAAE,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAAC,IAAY;IACzC,OAAO,IAAI;SACR,OAAO,CAAC,qBAAqB,EAAE,4BAA4B,CAAC;SAC5D,OAAO,CAAC,mBAAmB,EAAE,0BAA0B,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,qBAAqB,CAC1B,uBAAuB,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CACvD,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,UAAuB,EAAE;IAC5E,MAAM,SAAS,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAEnD,0EAA0E;IAC1E,MAAM,OAAO,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAEjD,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,WAAW,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,WAAW,CAAC,IAAI,CAAC,SAAS,qBAAqB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC;QACnC,CAAC,CAAC,GAAG,kBAAkB,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QAClD,CAAC,CAAC,kBAAkB,CAAC;IAEvB,OAAO,GAAG,MAAM,KAAK,OAAO,KAAK,gBAAgB,EAAE,CAAC;AACtD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,MAAM,SAAS,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC;QAC5B,QAAQ,EAAE,OAAO;KAClB,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAAe,EACf,UAAkC,EAAE;IAEpC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAE3D,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC7B,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,mBAAmB,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Nominatim reverse geocoding client with LRU cache.
3
+ * Resolves lat/lng to human-readable address and place label.
4
+ */
5
+ export interface GeocodedLocation {
6
+ address: string;
7
+ placeLabel: string;
8
+ }
9
+ /**
10
+ * Reverse geocode a lat/lng pair via Nominatim.
11
+ * Returns null on any failure (timeout, network error, bad response).
12
+ */
13
+ export declare function reverseGeocode(lat: number, lng: number, nominatimUrl: string): Promise<GeocodedLocation | null>;
14
+ /**
15
+ * Clears the geocode cache (for testing).
16
+ */
17
+ export declare function clearGeocodeCache(): void;
18
+ //# sourceMappingURL=nominatim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nominatim.d.ts","sourceRoot":"","sources":["../../src/utils/nominatim.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAYD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA2ClC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Nominatim reverse geocoding client with LRU cache.
3
+ * Resolves lat/lng to human-readable address and place label.
4
+ */
5
+ const geocodeCache = new Map();
6
+ const MAX_CACHE_SIZE = 500;
7
+ /**
8
+ * Rounds coordinates to ~100m precision for cache key deduplication.
9
+ */
10
+ function cacheKey(lat, lng) {
11
+ return `${Math.round(lat * 1000) / 1000},${Math.round(lng * 1000) / 1000}`;
12
+ }
13
+ /**
14
+ * Reverse geocode a lat/lng pair via Nominatim.
15
+ * Returns null on any failure (timeout, network error, bad response).
16
+ */
17
+ export async function reverseGeocode(lat, lng, nominatimUrl) {
18
+ const key = cacheKey(lat, lng);
19
+ if (geocodeCache.has(key))
20
+ return geocodeCache.get(key);
21
+ try {
22
+ const url = `${nominatimUrl}/reverse?format=jsonv2&lat=${lat}&lon=${lng}`;
23
+ const response = await fetch(url, {
24
+ headers: { 'User-Agent': 'openclaw-projects/1.0' },
25
+ signal: AbortSignal.timeout(5000),
26
+ });
27
+ if (!response.ok) {
28
+ console.warn(`[Nominatim] Reverse geocode failed: HTTP ${response.status} for (${lat}, ${lng})`);
29
+ return null;
30
+ }
31
+ const data = (await response.json());
32
+ const result = {
33
+ address: data.display_name ?? '',
34
+ placeLabel: data.name || data.address?.suburb || data.address?.city || '',
35
+ };
36
+ if (geocodeCache.size >= MAX_CACHE_SIZE) {
37
+ const oldest = geocodeCache.keys().next().value;
38
+ if (oldest !== undefined)
39
+ geocodeCache.delete(oldest);
40
+ }
41
+ geocodeCache.set(key, result);
42
+ return result;
43
+ }
44
+ catch (error) {
45
+ const msg = error instanceof Error ? error.message : String(error);
46
+ console.warn(`[Nominatim] Reverse geocode error for (${lat}, ${lng}): ${msg}`);
47
+ return null;
48
+ }
49
+ }
50
+ /**
51
+ * Clears the geocode cache (for testing).
52
+ */
53
+ export function clearGeocodeCache() {
54
+ geocodeCache.clear();
55
+ }
56
+ //# sourceMappingURL=nominatim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nominatim.js","sourceRoot":"","sources":["../../src/utils/nominatim.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,YAAY,GAAG,IAAI,GAAG,EAA4B,CAAC;AACzD,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B;;GAEG;AACH,SAAS,QAAQ,CAAC,GAAW,EAAE,GAAW;IACxC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC;AAC7E,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,GAAW,EACX,YAAoB;IAEpB,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IAEzD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,GAAG,YAAY,8BAA8B,GAAG,QAAQ,GAAG,EAAE,CAAC;QAC1E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,OAAO,EAAE,EAAE,YAAY,EAAE,uBAAuB,EAAE;YAClD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,4CAA4C,QAAQ,CAAC,MAAM,SAAS,GAAG,KAAK,GAAG,GAAG,CAAC,CAAC;YACjG,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAUlC,CAAC;QAEF,MAAM,MAAM,GAAqB;YAC/B,OAAO,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;YAChC,UAAU,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE;SAC1E,CAAC;QAEF,IAAI,YAAY,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAChD,IAAI,MAAM,KAAK,SAAS;gBAAE,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxD,CAAC;QACD,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,0CAA0C,GAAG,KAAK,GAAG,MAAM,GAAG,EAAE,CAAC,CAAC;QAC/E,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,YAAY,CAAC,KAAK,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Sliding window rate limiter for inbound message processing.
3
+ *
4
+ * Provides per-sender and per-recipient rate limiting with
5
+ * contact-trust-level awareness. Uses in-memory sliding window
6
+ * with automatic cleanup of expired entries.
7
+ *
8
+ * NOTE: State is in-memory and does not persist across process restarts
9
+ * or span multiple instances. For multi-instance deployments, a
10
+ * skill_store-backed implementation would be needed to share rate
11
+ * limit state across processes.
12
+ *
13
+ * Part of Issue #1225 — rate limiting and spam protection.
14
+ */
15
+ import { type MessageChannel } from './spam-filter.js';
16
+ /** Trust levels for sender classification */
17
+ export type SenderTrust = 'trusted' | 'known' | 'unknown' | 'blocked';
18
+ /** Configuration for the rate limiter */
19
+ export interface RateLimiterConfig {
20
+ /** Max messages per window for trusted senders (active thread, replied) */
21
+ trustedSenderLimit: number;
22
+ /** Max messages per window for known contacts */
23
+ knownSenderLimit: number;
24
+ /** Max messages per window for unknown senders */
25
+ unknownSenderLimit: number;
26
+ /** Global max messages per window for a single recipient */
27
+ recipientGlobalLimit: number;
28
+ /** Sliding window duration in milliseconds */
29
+ windowMs: number;
30
+ /**
31
+ * Maximum number of tracked sender entries before forced eviction.
32
+ * Prevents unbounded memory growth if traffic burst creates many keys
33
+ * and then stops (entries would persist until the next cleanup cycle).
34
+ * When exceeded, the oldest entries are evicted during the next check.
35
+ */
36
+ maxSenderEntries?: number;
37
+ }
38
+ /** Result of a rate limit check */
39
+ export interface RateLimitResult {
40
+ /** Whether the message is allowed */
41
+ allowed: boolean;
42
+ /** Human-readable reason if denied, null if allowed */
43
+ reason: string | null;
44
+ /** Messages remaining in current window */
45
+ remaining: number;
46
+ /** The limit that applies to this sender */
47
+ limit: number;
48
+ /** Milliseconds until the earliest entry expires (for retry-after) */
49
+ retryAfterMs: number | null;
50
+ }
51
+ /** Statistics about the rate limiter state */
52
+ export interface RateLimiterStats {
53
+ /** Number of currently tracked senders */
54
+ activeSenders: number;
55
+ /** Number of currently tracked recipients */
56
+ activeRecipients: number;
57
+ }
58
+ /** Rate limiter instance */
59
+ export interface RateLimiter {
60
+ /**
61
+ * Check if a message is allowed and record it if so.
62
+ * Sender and recipient are normalized for consistent matching
63
+ * (phone number formatting, email alias stripping).
64
+ */
65
+ check(sender: string, recipient: string, trust: SenderTrust, channel?: MessageChannel): RateLimitResult;
66
+ /** Get current stats about the limiter */
67
+ getStats(): RateLimiterStats;
68
+ }
69
+ /** Default rate limiter configuration */
70
+ export declare const DEFAULT_RATE_LIMITER_CONFIG: RateLimiterConfig;
71
+ /**
72
+ * Create a rate limiter instance.
73
+ *
74
+ * Uses a sliding window approach: each message records a timestamp,
75
+ * and only timestamps within the current window are counted.
76
+ *
77
+ * @param config - Rate limiter configuration (uses defaults if omitted)
78
+ * @returns RateLimiter instance
79
+ */
80
+ export declare function createRateLimiter(config?: RateLimiterConfig): RateLimiter;
81
+ //# sourceMappingURL=rate-limiter.d.ts.map