@titanshield/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/TitanShield.d.ts +107 -0
- package/dist/TitanShield.d.ts.map +1 -0
- package/dist/TitanShield.js +248 -0
- package/dist/TitanShield.js.map +1 -0
- package/dist/audit.d.ts +8 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +76 -0
- package/dist/audit.js.map +1 -0
- package/dist/auto.d.ts +12 -0
- package/dist/auto.d.ts.map +1 -0
- package/dist/auto.js +129 -0
- package/dist/auto.js.map +1 -0
- package/dist/badge.d.ts +27 -0
- package/dist/badge.d.ts.map +1 -0
- package/dist/badge.js +127 -0
- package/dist/badge.js.map +1 -0
- package/dist/battle.d.ts +50 -0
- package/dist/battle.d.ts.map +1 -0
- package/dist/battle.js +239 -0
- package/dist/battle.js.map +1 -0
- package/dist/biometrics.d.ts +63 -0
- package/dist/biometrics.d.ts.map +1 -0
- package/dist/biometrics.js +248 -0
- package/dist/biometrics.js.map +1 -0
- package/dist/collective.d.ts +63 -0
- package/dist/collective.d.ts.map +1 -0
- package/dist/collective.js +203 -0
- package/dist/collective.js.map +1 -0
- package/dist/compliance.d.ts +3 -0
- package/dist/compliance.d.ts.map +1 -0
- package/dist/compliance.js +71 -0
- package/dist/compliance.js.map +1 -0
- package/dist/dna.d.ts +82 -0
- package/dist/dna.d.ts.map +1 -0
- package/dist/dna.js +219 -0
- package/dist/dna.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/nlrules.d.ts +68 -0
- package/dist/nlrules.d.ts.map +1 -0
- package/dist/nlrules.js +232 -0
- package/dist/nlrules.js.map +1 -0
- package/dist/prevent.d.ts +119 -0
- package/dist/prevent.d.ts.map +1 -0
- package/dist/prevent.js +380 -0
- package/dist/prevent.js.map +1 -0
- package/dist/quantum.d.ts +105 -0
- package/dist/quantum.d.ts.map +1 -0
- package/dist/quantum.js +269 -0
- package/dist/quantum.js.map +1 -0
- package/dist/scanner.d.ts +61 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +364 -0
- package/dist/scanner.js.map +1 -0
- package/dist/threats.d.ts +10 -0
- package/dist/threats.d.ts.map +1 -0
- package/dist/threats.js +96 -0
- package/dist/threats.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +51 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +59 -0
- package/dist/validate.js.map +1 -0
- package/package.json +33 -0
- package/src/TitanShield.ts +303 -0
- package/src/audit.ts +75 -0
- package/src/auto.ts +137 -0
- package/src/badge.ts +145 -0
- package/src/battle.ts +300 -0
- package/src/biometrics.ts +307 -0
- package/src/collective.ts +269 -0
- package/src/compliance.ts +74 -0
- package/src/dna.ts +304 -0
- package/src/index.ts +59 -0
- package/src/nlrules.ts +297 -0
- package/src/prevent.ts +474 -0
- package/src/quantum.ts +341 -0
- package/src/scanner.ts +431 -0
- package/src/threats.ts +105 -0
- package/src/types.ts +108 -0
- package/src/validate.ts +72 -0
- package/tsconfig.json +26 -0
package/dist/nlrules.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
// TitanShieldAI — nlrules.ts
|
|
4
|
+
//
|
|
5
|
+
// WORLD'S FIRST: Natural Language Security Rules
|
|
6
|
+
//
|
|
7
|
+
// Write security rules in plain English. AI converts them to running code.
|
|
8
|
+
// No other security tool on earth does this.
|
|
9
|
+
//
|
|
10
|
+
// Examples:
|
|
11
|
+
// "Block all logins from Russia after 10pm"
|
|
12
|
+
// "Lock any account after 3 failed attempts"
|
|
13
|
+
// "Alert me when someone exports more than 1000 records"
|
|
14
|
+
// "Require multi-factor auth for all admin users"
|
|
15
|
+
// "Block requests with more than 5 different user agents per hour"
|
|
16
|
+
//
|
|
17
|
+
// The AI (Gemini) parses the English, extracts structured intent,
|
|
18
|
+
// compiles it to a TitanShield Rule, and starts enforcing it immediately.
|
|
19
|
+
//
|
|
20
|
+
// No engineers needed. No docs to read. No configuration files.
|
|
21
|
+
// Just say what you want. TitanShield does it.
|
|
22
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.NaturalLanguageRuleParser = void 0;
|
|
25
|
+
const generative_ai_1 = require("@google/generative-ai");
|
|
26
|
+
// ── NaturalLanguageRuleParser ─────────────────────────────────────────────────
|
|
27
|
+
class NaturalLanguageRuleParser {
|
|
28
|
+
constructor(geminiApiKey) {
|
|
29
|
+
this.ai = null;
|
|
30
|
+
this.rules = [];
|
|
31
|
+
if (geminiApiKey) {
|
|
32
|
+
this.ai = new generative_ai_1.GoogleGenerativeAI(geminiApiKey);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse a natural language security rule into a structured TitanShield rule.
|
|
37
|
+
* Uses Gemini to understand intent, then compiles to an active rule.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const rule = await parser.parse("Block all logins from Russia after 10pm");
|
|
41
|
+
* // → { conditions: [{type:'geo_country', value:'RU'}, {type:'time_window', value:'22:00'}], action:'block' }
|
|
42
|
+
*/
|
|
43
|
+
async parse(text) {
|
|
44
|
+
const id = `rule_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
45
|
+
let parsed;
|
|
46
|
+
if (this.ai) {
|
|
47
|
+
parsed = await this.parseWithAI(text, id);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
parsed = this.parseWithHeuristics(text, id);
|
|
51
|
+
}
|
|
52
|
+
const rule = {
|
|
53
|
+
id,
|
|
54
|
+
originalText: text,
|
|
55
|
+
conditions: parsed.conditions ?? [{ type: 'always' }],
|
|
56
|
+
action: parsed.action ?? 'alert',
|
|
57
|
+
severity: parsed.severity ?? 'warning',
|
|
58
|
+
message: parsed.message ?? `Rule: ${text}`,
|
|
59
|
+
confidence: parsed.confidence ?? 'medium',
|
|
60
|
+
createdAt: new Date(),
|
|
61
|
+
active: true,
|
|
62
|
+
triggerCount: 0,
|
|
63
|
+
};
|
|
64
|
+
this.rules.push(rule);
|
|
65
|
+
console.log(`[TitanShield NL] ✅ Rule compiled: "${text}" → ${rule.action} (${rule.confidence} confidence)`);
|
|
66
|
+
return rule;
|
|
67
|
+
}
|
|
68
|
+
async parseWithAI(text, id) {
|
|
69
|
+
const model = this.ai.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
|
70
|
+
const prompt = `You are a security rule compiler for TitanShieldAI.
|
|
71
|
+
Parse this natural language security rule into structured JSON.
|
|
72
|
+
|
|
73
|
+
Rule: "${text}"
|
|
74
|
+
|
|
75
|
+
Return ONLY a valid JSON object with this exact structure:
|
|
76
|
+
{
|
|
77
|
+
"conditions": [
|
|
78
|
+
{
|
|
79
|
+
"type": "geo_country|time_window|event_type|failure_count|data_volume|user_role|ip_reputation|new_device|rate_threshold|user_agent_diversity|always",
|
|
80
|
+
"value": "the value to compare (string, number, or array of strings)",
|
|
81
|
+
"operator": "gt|lt|eq|in|not_in|after|before|between",
|
|
82
|
+
"window": (optional milliseconds for time windows)
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"action": "block|alert|log|lockout|require_mfa|rate_limit|flag",
|
|
86
|
+
"severity": "info|warning|critical",
|
|
87
|
+
"message": "Plain English: what this rule does and why. Start with an emoji.",
|
|
88
|
+
"confidence": "high|medium|low"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Country codes: US, RU, CN, KP, IR, etc.
|
|
92
|
+
Event types: user.login, user.login_failed, user.logout, data.export, data.read, admin.settings_changed
|
|
93
|
+
Time format for time_window values: "HH:MM" in 24h format
|
|
94
|
+
|
|
95
|
+
Be creative and thorough. Extract ALL conditions from the rule text.`;
|
|
96
|
+
try {
|
|
97
|
+
const result = await model.generateContent(prompt);
|
|
98
|
+
const raw = result.response.text().replace(/```json\n?|```/g, '').trim();
|
|
99
|
+
return JSON.parse(raw);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return this.parseWithHeuristics(text, id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
parseWithHeuristics(text, _id) {
|
|
106
|
+
const lower = text.toLowerCase();
|
|
107
|
+
const conditions = [];
|
|
108
|
+
// Geo detection
|
|
109
|
+
const countries = {
|
|
110
|
+
'russia': 'RU', 'china': 'CN', 'north korea': 'KP', 'iran': 'IR',
|
|
111
|
+
'nigeria': 'NG', 'usa': 'US', 'india': 'IN', 'brazil': 'BR',
|
|
112
|
+
};
|
|
113
|
+
for (const [name, code] of Object.entries(countries)) {
|
|
114
|
+
if (lower.includes(name)) {
|
|
115
|
+
conditions.push({ type: 'geo_country', value: code, operator: 'eq' });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Time detection
|
|
119
|
+
const timeMatch = lower.match(/after\s+(\d{1,2})(pm|am)/);
|
|
120
|
+
if (timeMatch) {
|
|
121
|
+
let hour = parseInt(timeMatch[1]);
|
|
122
|
+
if (timeMatch[2] === 'pm' && hour !== 12)
|
|
123
|
+
hour += 12;
|
|
124
|
+
conditions.push({ type: 'time_window', value: `${String(hour).padStart(2, '0')}:00`, operator: 'after' });
|
|
125
|
+
}
|
|
126
|
+
// Event detection
|
|
127
|
+
if (lower.includes('login'))
|
|
128
|
+
conditions.push({ type: 'event_type', value: 'user.login' });
|
|
129
|
+
if (lower.includes('export'))
|
|
130
|
+
conditions.push({ type: 'event_type', value: 'data.export' });
|
|
131
|
+
if (lower.includes('admin'))
|
|
132
|
+
conditions.push({ type: 'user_role', value: 'admin', operator: 'eq' });
|
|
133
|
+
// Failure count
|
|
134
|
+
const failMatch = lower.match(/(\d+)\s+failed/);
|
|
135
|
+
if (failMatch) {
|
|
136
|
+
conditions.push({ type: 'failure_count', value: parseInt(failMatch[1]), operator: 'gt', window: 300000 });
|
|
137
|
+
}
|
|
138
|
+
// Action detection
|
|
139
|
+
let action = 'alert';
|
|
140
|
+
if (lower.includes('block') || lower.includes('deny'))
|
|
141
|
+
action = 'block';
|
|
142
|
+
else if (lower.includes('lock'))
|
|
143
|
+
action = 'lockout';
|
|
144
|
+
else if (lower.includes('mfa') || lower.includes('multi-factor'))
|
|
145
|
+
action = 'require_mfa';
|
|
146
|
+
else if (lower.includes('alert') || lower.includes('notify'))
|
|
147
|
+
action = 'alert';
|
|
148
|
+
return {
|
|
149
|
+
conditions: conditions.length > 0 ? conditions : [{ type: 'always' }],
|
|
150
|
+
action,
|
|
151
|
+
severity: action === 'block' || action === 'lockout' ? 'critical' : 'warning',
|
|
152
|
+
message: `🛡️ NL Rule: ${text}`,
|
|
153
|
+
confidence: 'medium',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Evaluate all active rules against an incoming request context.
|
|
158
|
+
* Returns list of matching rules with their required actions.
|
|
159
|
+
*/
|
|
160
|
+
evaluate(ctx) {
|
|
161
|
+
const matches = [];
|
|
162
|
+
const now = new Date();
|
|
163
|
+
const currentHour = now.getHours();
|
|
164
|
+
const currentMinute = now.getMinutes();
|
|
165
|
+
for (const rule of this.rules.filter(r => r.active)) {
|
|
166
|
+
const matched = rule.conditions.every(cond => this.evaluateCondition(cond, ctx, currentHour, currentMinute));
|
|
167
|
+
if (matched) {
|
|
168
|
+
rule.triggerCount++;
|
|
169
|
+
matches.push({
|
|
170
|
+
rule,
|
|
171
|
+
action: rule.action,
|
|
172
|
+
message: `🛡️ Rule triggered: "${rule.originalText}" → ${rule.action.toUpperCase()}`,
|
|
173
|
+
blocked: rule.action === 'block' || rule.action === 'lockout',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return matches;
|
|
178
|
+
}
|
|
179
|
+
evaluateCondition(cond, ctx, hour, _minute) {
|
|
180
|
+
switch (cond.type) {
|
|
181
|
+
case 'always': return true;
|
|
182
|
+
case 'geo_country':
|
|
183
|
+
return ctx.country === cond.value;
|
|
184
|
+
case 'time_window':
|
|
185
|
+
if (typeof cond.value === 'string') {
|
|
186
|
+
const [h] = cond.value.split(':').map(Number);
|
|
187
|
+
if (cond.operator === 'after')
|
|
188
|
+
return hour >= h;
|
|
189
|
+
if (cond.operator === 'before')
|
|
190
|
+
return hour < h;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
case 'event_type':
|
|
194
|
+
return ctx.event === cond.value || (Array.isArray(cond.value) && cond.value.includes(ctx.event));
|
|
195
|
+
case 'failure_count':
|
|
196
|
+
return typeof cond.value === 'number' && (ctx.failedLoginCount ?? 0) >= cond.value;
|
|
197
|
+
case 'data_volume':
|
|
198
|
+
return typeof cond.value === 'number' && (ctx.dataVolumeAccessed ?? 0) >= cond.value;
|
|
199
|
+
case 'user_role':
|
|
200
|
+
return ctx.userRole === cond.value;
|
|
201
|
+
case 'rate_threshold':
|
|
202
|
+
return typeof cond.value === 'number' && (ctx.recentEventCount ?? 0) >= cond.value;
|
|
203
|
+
case 'new_device':
|
|
204
|
+
return ctx.metadata?.newDevice === true;
|
|
205
|
+
case 'user_agent_diversity':
|
|
206
|
+
return typeof cond.value === 'number' && (ctx.uniqueUserAgents ?? 0) >= cond.value;
|
|
207
|
+
default: return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Get all active rules with their trigger counts */
|
|
211
|
+
getRules() { return [...this.rules]; }
|
|
212
|
+
/** Enable/disable a rule by ID */
|
|
213
|
+
setActive(ruleId, active) {
|
|
214
|
+
const rule = this.rules.find(r => r.id === ruleId);
|
|
215
|
+
if (rule) {
|
|
216
|
+
rule.active = active;
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
/** Delete a rule by ID */
|
|
222
|
+
deleteRule(ruleId) {
|
|
223
|
+
const idx = this.rules.findIndex(r => r.id === ruleId);
|
|
224
|
+
if (idx >= 0) {
|
|
225
|
+
this.rules.splice(idx, 1);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.NaturalLanguageRuleParser = NaturalLanguageRuleParser;
|
|
232
|
+
//# sourceMappingURL=nlrules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nlrules.js","sourceRoot":"","sources":["../src/nlrules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
+
export interface LockoutRecord {
|
|
3
|
+
attempts: number;
|
|
4
|
+
firstAttemptAt: number;
|
|
5
|
+
lockedUntil?: number;
|
|
6
|
+
lastAttemptIp?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SessionFingerprint {
|
|
9
|
+
ip: string;
|
|
10
|
+
userAgent: string;
|
|
11
|
+
acceptLang: string;
|
|
12
|
+
hash: string;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
export interface PreventionConfig {
|
|
16
|
+
maxFailedLogins?: number;
|
|
17
|
+
lockoutDurationMs?: number;
|
|
18
|
+
lockoutWindowMs?: number;
|
|
19
|
+
enableSecurityHeaders?: boolean;
|
|
20
|
+
cspDirectives?: Record<string, string | string[]>;
|
|
21
|
+
frameOptions?: 'DENY' | 'SAMEORIGIN';
|
|
22
|
+
enableCsrf?: boolean;
|
|
23
|
+
csrfExemptPaths?: string[];
|
|
24
|
+
enableBotDetection?: boolean;
|
|
25
|
+
blockHeadlessBrowsers?: boolean;
|
|
26
|
+
blockedCountries?: string[];
|
|
27
|
+
blockedIpRanges?: string[];
|
|
28
|
+
enablePathTraversalProtection?: boolean;
|
|
29
|
+
enableCommandInjectionProtection?: boolean;
|
|
30
|
+
}
|
|
31
|
+
declare const lockouts: Map<string, LockoutRecord>;
|
|
32
|
+
declare const sessions: Map<string, SessionFingerprint>;
|
|
33
|
+
declare const csrfTokens: Map<string, {
|
|
34
|
+
expires: number;
|
|
35
|
+
}>;
|
|
36
|
+
declare const ipReputation: Map<string, {
|
|
37
|
+
score: number;
|
|
38
|
+
reason: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare class AccountLockout {
|
|
41
|
+
private config;
|
|
42
|
+
constructor(config?: PreventionConfig);
|
|
43
|
+
/**
|
|
44
|
+
* Record a failed login attempt for this identifier (userId or IP).
|
|
45
|
+
* Returns lock status with human-readable message.
|
|
46
|
+
*/
|
|
47
|
+
recordFailure(identifier: string, ip?: string): {
|
|
48
|
+
locked: boolean;
|
|
49
|
+
attemptsLeft: number;
|
|
50
|
+
message: string;
|
|
51
|
+
lockedFor?: number;
|
|
52
|
+
};
|
|
53
|
+
/** Call this on SUCCESSFUL login to reset the counter */
|
|
54
|
+
recordSuccess(identifier: string): void;
|
|
55
|
+
/** Check if an account is currently locked (before attempting auth) */
|
|
56
|
+
isLocked(identifier: string): {
|
|
57
|
+
locked: boolean;
|
|
58
|
+
unlockAt?: number;
|
|
59
|
+
message?: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export declare function securityHeaders(config?: PreventionConfig): (_req: Request, res: Response, next: NextFunction) => void;
|
|
63
|
+
export declare class CsrfProtection {
|
|
64
|
+
private tokenTtlMs;
|
|
65
|
+
private exemptPaths;
|
|
66
|
+
constructor(config?: PreventionConfig);
|
|
67
|
+
/** Generate a CSRF token for a session */
|
|
68
|
+
generateToken(sessionId: string): string;
|
|
69
|
+
/** Validate a CSRF token */
|
|
70
|
+
validateToken(sessionId: string, token: string): boolean;
|
|
71
|
+
/** Express middleware — validates CSRF on state-changing requests */
|
|
72
|
+
middleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
73
|
+
}
|
|
74
|
+
export declare class SessionFingerprinter {
|
|
75
|
+
/**
|
|
76
|
+
* Create a fingerprint from request characteristics.
|
|
77
|
+
* Used to detect if someone stole a session cookie and is using it
|
|
78
|
+
* from a different device/browser/location.
|
|
79
|
+
*/
|
|
80
|
+
createFingerprint(req: Request): SessionFingerprint;
|
|
81
|
+
/** Store a fingerprint for a session */
|
|
82
|
+
store(sessionId: string, fp: SessionFingerprint): void;
|
|
83
|
+
/**
|
|
84
|
+
* Verify current request matches the original session fingerprint.
|
|
85
|
+
* Returns { valid, reason } — soft verification (logs warning, doesn't hard block by default).
|
|
86
|
+
*/
|
|
87
|
+
verify(sessionId: string, req: Request): {
|
|
88
|
+
valid: boolean;
|
|
89
|
+
reason?: string;
|
|
90
|
+
riskLevel: 'none' | 'low' | 'high';
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export declare function detectBot(req: Request): {
|
|
94
|
+
isBot: boolean;
|
|
95
|
+
confidence: 'high' | 'medium' | 'low';
|
|
96
|
+
reason: string;
|
|
97
|
+
};
|
|
98
|
+
export declare function detectAdvancedInjection(req: Request): {
|
|
99
|
+
threat: boolean;
|
|
100
|
+
type: string;
|
|
101
|
+
detail: string;
|
|
102
|
+
} | null;
|
|
103
|
+
export declare class IpReputationEngine {
|
|
104
|
+
private blockedRanges;
|
|
105
|
+
constructor(config?: PreventionConfig);
|
|
106
|
+
/** Flag an IP as malicious (called by AI threat engine when threat detected) */
|
|
107
|
+
flagIp(ip: string, reason: string, score?: number): void;
|
|
108
|
+
/** Check if an IP should be blocked */
|
|
109
|
+
checkIp(ip: string): {
|
|
110
|
+
blocked: boolean;
|
|
111
|
+
score: number;
|
|
112
|
+
reason: string;
|
|
113
|
+
};
|
|
114
|
+
/** Call this after AI detects a threat to auto-add IP to blocklist */
|
|
115
|
+
autoBlockFromThreat(ip: string, alertNarrative: string): void;
|
|
116
|
+
}
|
|
117
|
+
export declare function titanPreventionMiddleware(lockout: AccountLockout, ipEngine: IpReputationEngine, config?: PreventionConfig, onThreat?: (event: string, detail: string, ip: string) => void): (req: Request, res: Response, next: NextFunction) => void;
|
|
118
|
+
export { lockouts, sessions, csrfTokens, ipReputation };
|
|
119
|
+
//# sourceMappingURL=prevent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prevent.d.ts","sourceRoot":"","sources":["../src/prevent.ts"],"names":[],"mappings":"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"}
|