@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/src/dna.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// TitanShieldAI — dna.ts
|
|
3
|
+
//
|
|
4
|
+
// WORLD'S FIRST: Security DNA Profiling
|
|
5
|
+
//
|
|
6
|
+
// Every app has a unique behavioral fingerprint — its "Security DNA."
|
|
7
|
+
// Login times, request patterns, user locations, API usage rhythms.
|
|
8
|
+
// TitanShield learns YOUR specific app's normal, then flags deviations.
|
|
9
|
+
//
|
|
10
|
+
// This is fundamentally different from generic thresholds:
|
|
11
|
+
// Generic: "3+ failed logins = suspicious" (misses slow attacks on your app)
|
|
12
|
+
// DNA: "Your app normally has <2 failed logins/hr. This is 47. That's 23x."
|
|
13
|
+
//
|
|
14
|
+
// The Security DNA builds a live statistical model:
|
|
15
|
+
// - Hourly request distribution (your app's natural rhythm)
|
|
16
|
+
// - Geographic heat map (where your users normally come from)
|
|
17
|
+
// - User behavior graph (how long sessions normally run)
|
|
18
|
+
// - API access patterns (which endpoints are normally hit together)
|
|
19
|
+
// - Login success rate distribution
|
|
20
|
+
//
|
|
21
|
+
// Any deviation from YOUR baseline triggers an anomaly score — not a generic
|
|
22
|
+
// threshold, but a deviation specific to YOUR app's unique pattern.
|
|
23
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
import { createHash } from 'crypto';
|
|
26
|
+
|
|
27
|
+
// ── DNA Profile Types ─────────────────────────────────────────────────────────
|
|
28
|
+
export interface HourlyDistribution {
|
|
29
|
+
[hour: number]: number; // 0-23 → normalized count
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface GeoProfile {
|
|
33
|
+
[countryCode: string]: number; // normalized frequency
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SecurityDNA {
|
|
37
|
+
projectId: string;
|
|
38
|
+
sampledEvents: number;
|
|
39
|
+
lastUpdatedAt: Date;
|
|
40
|
+
maturity: 'learning' | 'establishing' | 'mature';
|
|
41
|
+
|
|
42
|
+
// Behavioral baselines
|
|
43
|
+
hourlyPattern: HourlyDistribution; // when requests normally happen
|
|
44
|
+
geoProfile: GeoProfile; // where users normally come from
|
|
45
|
+
avgSessionDurationMs: number; // how long normal sessions are
|
|
46
|
+
avgRequestsPerSession: number; // typical session depth
|
|
47
|
+
loginSuccessRate: number; // 0-1, your normal success rate
|
|
48
|
+
avgFailedLoginsPerHour: number;
|
|
49
|
+
avgApiCallsPerMinute: number;
|
|
50
|
+
typicalUserAgents: string[]; // known good browsers
|
|
51
|
+
|
|
52
|
+
// Computed anomaly thresholds (auto-calibrated to YOUR app)
|
|
53
|
+
thresholds: {
|
|
54
|
+
maxFailedLoginsPerHour: number; // YOUR max, not generic
|
|
55
|
+
maxRequestsPerMinute: number;
|
|
56
|
+
maxNewCountriesPerDay: number;
|
|
57
|
+
minLoginSuccessRate: number;
|
|
58
|
+
maxSessionDurationMs: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DNAAnomaly {
|
|
63
|
+
type: 'geo_anomaly' | 'time_anomaly' | 'frequency_anomaly' | 'session_anomaly' | 'login_rate_anomaly' | 'new_country';
|
|
64
|
+
deviationFactor: number; // how many standard deviations from YOUR baseline
|
|
65
|
+
description: string; // plain English, specific to YOUR app
|
|
66
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
67
|
+
suggestedAction: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface DNAScanResult {
|
|
71
|
+
anomalies: DNAAnomaly[];
|
|
72
|
+
overallDeviationScore: number; // 0-100
|
|
73
|
+
isNormal: boolean;
|
|
74
|
+
dnaMessage: string; // plain English "your app story" verdict
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── SecurityDNAProfiler ───────────────────────────────────────────────────────
|
|
78
|
+
export class SecurityDNAProfiler {
|
|
79
|
+
private dna: SecurityDNA;
|
|
80
|
+
private readonly LEARNING_THRESHOLD = 100; // events before DNA starts trusting its model
|
|
81
|
+
private readonly MATURE_THRESHOLD = 1000; // events for a mature profile
|
|
82
|
+
|
|
83
|
+
constructor(projectId: string) {
|
|
84
|
+
this.dna = this.initDNA(projectId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private initDNA(projectId: string): SecurityDNA {
|
|
88
|
+
return {
|
|
89
|
+
projectId,
|
|
90
|
+
sampledEvents: 0,
|
|
91
|
+
lastUpdatedAt: new Date(),
|
|
92
|
+
maturity: 'learning',
|
|
93
|
+
hourlyPattern: Object.fromEntries(Array.from({ length: 24 }, (_, i) => [i, 1 / 24])),
|
|
94
|
+
geoProfile: {},
|
|
95
|
+
avgSessionDurationMs: 30 * 60 * 1000, // default 30 min
|
|
96
|
+
avgRequestsPerSession: 10,
|
|
97
|
+
loginSuccessRate: 0.95,
|
|
98
|
+
avgFailedLoginsPerHour: 1,
|
|
99
|
+
avgApiCallsPerMinute: 5,
|
|
100
|
+
typicalUserAgents: [],
|
|
101
|
+
thresholds: {
|
|
102
|
+
maxFailedLoginsPerHour: 5,
|
|
103
|
+
maxRequestsPerMinute: 60,
|
|
104
|
+
maxNewCountriesPerDay: 3,
|
|
105
|
+
minLoginSuccessRate: 0.7,
|
|
106
|
+
maxSessionDurationMs: 8 * 60 * 60 * 1000,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Feed an event into the DNA learner.
|
|
113
|
+
* After enough samples, the DNA self-calibrates to YOUR app's unique patterns.
|
|
114
|
+
*/
|
|
115
|
+
learn(event: {
|
|
116
|
+
eventType: string;
|
|
117
|
+
ip?: string;
|
|
118
|
+
country?: string;
|
|
119
|
+
userAgent?: string;
|
|
120
|
+
sessionDurationMs?: number;
|
|
121
|
+
success?: boolean;
|
|
122
|
+
timestamp?: Date;
|
|
123
|
+
}): void {
|
|
124
|
+
this.dna.sampledEvents++;
|
|
125
|
+
const n = this.dna.sampledEvents;
|
|
126
|
+
|
|
127
|
+
// Update maturity
|
|
128
|
+
if (n >= this.MATURE_THRESHOLD) this.dna.maturity = 'mature';
|
|
129
|
+
else if (n >= this.LEARNING_THRESHOLD) this.dna.maturity = 'establishing';
|
|
130
|
+
else this.dna.maturity = 'learning';
|
|
131
|
+
|
|
132
|
+
// Learn hourly pattern (exponential moving average)
|
|
133
|
+
const hour = (event.timestamp ?? new Date()).getHours();
|
|
134
|
+
this.dna.hourlyPattern[hour] = this.ema(this.dna.hourlyPattern[hour], 1, 0.05);
|
|
135
|
+
for (let h = 0; h < 24; h++) {
|
|
136
|
+
if (h !== hour) {
|
|
137
|
+
this.dna.hourlyPattern[h] = this.ema(this.dna.hourlyPattern[h], 0, 0.001);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
this.normalizeDistribution(this.dna.hourlyPattern as Record<string | number, number>);
|
|
141
|
+
|
|
142
|
+
// Learn geo profile
|
|
143
|
+
if (event.country) {
|
|
144
|
+
this.dna.geoProfile[event.country] = (this.dna.geoProfile[event.country] ?? 0) + 1;
|
|
145
|
+
this.normalizeDistribution(this.dna.geoProfile);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Learn session duration
|
|
149
|
+
if (event.sessionDurationMs) {
|
|
150
|
+
this.dna.avgSessionDurationMs = this.ema(this.dna.avgSessionDurationMs, event.sessionDurationMs, 0.05);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Learn login success rate
|
|
154
|
+
if (event.eventType === 'user.login' || event.eventType === 'user.login_failed') {
|
|
155
|
+
const success = event.success !== false && event.eventType === 'user.login' ? 1 : 0;
|
|
156
|
+
this.dna.loginSuccessRate = this.ema(this.dna.loginSuccessRate, success, 0.02);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Learn user agents
|
|
160
|
+
if (event.userAgent && !this.dna.typicalUserAgents.includes(event.userAgent)) {
|
|
161
|
+
if (this.dna.typicalUserAgents.length < 20) {
|
|
162
|
+
this.dna.typicalUserAgents.push(event.userAgent);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Auto-calibrate thresholds to YOUR app
|
|
167
|
+
this.calibrateThresholds();
|
|
168
|
+
this.dna.lastUpdatedAt = new Date();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Scan a current context against YOUR Security DNA.
|
|
173
|
+
* Returns specific, app-calibrated anomaly descriptions.
|
|
174
|
+
*/
|
|
175
|
+
scan(ctx: {
|
|
176
|
+
currentHour?: number;
|
|
177
|
+
country?: string;
|
|
178
|
+
failedLoginsThisHour?: number;
|
|
179
|
+
requestsPerMinute?: number;
|
|
180
|
+
loginSuccessRate?: number;
|
|
181
|
+
sessionDurationMs?: number;
|
|
182
|
+
userAgent?: string;
|
|
183
|
+
isNewCountry?: boolean;
|
|
184
|
+
}): DNAScanResult {
|
|
185
|
+
const anomalies: DNAAnomaly[] = [];
|
|
186
|
+
|
|
187
|
+
// Only scan meaningfully after some learning (avoid false positives during learning)
|
|
188
|
+
if (this.dna.maturity === 'learning') {
|
|
189
|
+
return {
|
|
190
|
+
anomalies: [],
|
|
191
|
+
overallDeviationScore: 0,
|
|
192
|
+
isNormal: true,
|
|
193
|
+
dnaMessage: `🔬 Learning your app's DNA... (${this.dna.sampledEvents}/${this.LEARNING_THRESHOLD} events seen)`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── 1. Time anomaly (wrong time of day for YOUR app)
|
|
198
|
+
if (ctx.currentHour !== undefined) {
|
|
199
|
+
const expectedFreq = this.dna.hourlyPattern[ctx.currentHour] ?? (1 / 24);
|
|
200
|
+
if (expectedFreq < 0.01) { // this hour is very unusual for your app
|
|
201
|
+
anomalies.push({
|
|
202
|
+
type: 'time_anomaly',
|
|
203
|
+
deviationFactor: 1 / (expectedFreq * 24), // how many times rarer than average
|
|
204
|
+
description: `📅 Request at ${ctx.currentHour}:00 — your app almost never gets traffic at this hour. ${Math.round(expectedFreq * 100)}% of your normal traffic.`,
|
|
205
|
+
severity: expectedFreq < 0.001 ? 'high' : 'medium',
|
|
206
|
+
suggestedAction: 'Verify if this is a legitimate request or automation',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── 2. Geo anomaly
|
|
212
|
+
if (ctx.isNewCountry && ctx.country) {
|
|
213
|
+
const knownFreq = this.dna.geoProfile[ctx.country] ?? 0;
|
|
214
|
+
if (knownFreq < 0.01) {
|
|
215
|
+
anomalies.push({
|
|
216
|
+
type: 'new_country',
|
|
217
|
+
deviationFactor: knownFreq > 0 ? 1 / knownFreq : 100,
|
|
218
|
+
description: `🌍 First request from ${ctx.country} — your app has never (or rarely) seen users from here.`,
|
|
219
|
+
severity: 'medium',
|
|
220
|
+
suggestedAction: 'Consider sending a verification email to the user',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── 3. Login failure rate anomaly
|
|
226
|
+
if (ctx.failedLoginsThisHour !== undefined) {
|
|
227
|
+
const threshold = this.dna.thresholds.maxFailedLoginsPerHour;
|
|
228
|
+
if (ctx.failedLoginsThisHour > threshold) {
|
|
229
|
+
const factor = ctx.failedLoginsThisHour / threshold;
|
|
230
|
+
anomalies.push({
|
|
231
|
+
type: 'login_rate_anomaly',
|
|
232
|
+
deviationFactor: factor,
|
|
233
|
+
description: `🔑 ${ctx.failedLoginsThisHour} failed logins this hour — ${factor.toFixed(1)}x YOUR normal max (${threshold}/hr for your app).`,
|
|
234
|
+
severity: factor > 5 ? 'critical' : factor > 2 ? 'high' : 'medium',
|
|
235
|
+
suggestedAction: factor > 5 ? 'Enable emergency lockout' : 'Monitor and consider adding CAPTCHA',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── 4. Request frequency anomaly
|
|
241
|
+
if (ctx.requestsPerMinute !== undefined) {
|
|
242
|
+
const threshold = this.dna.thresholds.maxRequestsPerMinute;
|
|
243
|
+
if (ctx.requestsPerMinute > threshold) {
|
|
244
|
+
const factor = ctx.requestsPerMinute / threshold;
|
|
245
|
+
anomalies.push({
|
|
246
|
+
type: 'frequency_anomaly',
|
|
247
|
+
deviationFactor: factor,
|
|
248
|
+
description: `⚡ ${ctx.requestsPerMinute} requests/min — ${factor.toFixed(1)}x YOUR normal max. Possible DDoS or scraper.`,
|
|
249
|
+
severity: factor > 10 ? 'critical' : factor > 3 ? 'high' : 'medium',
|
|
250
|
+
suggestedAction: factor > 10 ? 'Enable emergency rate limiting' : 'Apply gentle rate limiting',
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Compute overall deviation score
|
|
256
|
+
const overallScore = Math.min(100,
|
|
257
|
+
anomalies.reduce((sum, a) => sum + Math.min(a.deviationFactor * 10, 40), 0)
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const isNormal = overallScore < 20;
|
|
261
|
+
const dnaMessage = this.generateDNAVerdict(anomalies, overallScore);
|
|
262
|
+
|
|
263
|
+
return { anomalies, overallDeviationScore: overallScore, isNormal, dnaMessage };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private generateDNAVerdict(anomalies: DNAAnomaly[], score: number): string {
|
|
267
|
+
if (anomalies.length === 0) {
|
|
268
|
+
return `✅ DNA Match — everything looks exactly like YOUR app normally behaves`;
|
|
269
|
+
}
|
|
270
|
+
if (score < 30) return `⚠️ Minor DNA mismatch — ${anomalies[0].description}`;
|
|
271
|
+
if (score < 60) return `🟡 Significant DNA deviation — ${anomalies.length} unusual pattern${anomalies.length !== 1 ? 's' : ''} detected`;
|
|
272
|
+
return `🚨 Major DNA deviation — this doesn't look like your normal app at all! ${anomalies.length} critical deviations.`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private ema(current: number, newValue: number, alpha: number): number {
|
|
276
|
+
return current * (1 - alpha) + newValue * alpha;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private normalizeDistribution(dist: Record<string | number, number>): void {
|
|
280
|
+
const total = Object.values(dist).reduce((a, b) => a + b, 0);
|
|
281
|
+
if (total === 0) return;
|
|
282
|
+
for (const key in dist) dist[key] /= total;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private calibrateThresholds(): void {
|
|
286
|
+
// Auto-calibrate to be 3x YOUR normal as the alert threshold
|
|
287
|
+
this.dna.thresholds.maxFailedLoginsPerHour = Math.max(5, this.dna.avgFailedLoginsPerHour * 3);
|
|
288
|
+
this.dna.thresholds.maxRequestsPerMinute = Math.max(60, this.dna.avgApiCallsPerMinute * 3);
|
|
289
|
+
this.dna.thresholds.minLoginSuccessRate = Math.max(0.3, this.dna.loginSuccessRate * 0.7);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
getDNA(): SecurityDNA { return { ...this.dna }; }
|
|
293
|
+
|
|
294
|
+
/** Generate a shareable DNA fingerprint (safe to share publicly) */
|
|
295
|
+
getDNAFingerprint(): string {
|
|
296
|
+
const profile = JSON.stringify({
|
|
297
|
+
maturity: this.dna.maturity,
|
|
298
|
+
sampledEvents: this.dna.sampledEvents,
|
|
299
|
+
topCountries: Object.entries(this.dna.geoProfile).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([k]) => k),
|
|
300
|
+
peakHour: Object.entries(this.dna.hourlyPattern).sort((a, b) => b[1] - a[1])[0]?.[0],
|
|
301
|
+
});
|
|
302
|
+
return createHash('sha256').update(profile).digest('hex').slice(0, 16);
|
|
303
|
+
}
|
|
304
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// @titanshield/core — Public API v0.3
|
|
3
|
+
// World's First & Best Security System — Never Built Before
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
// ── Main class ────────────────────────────────────────────────────────────────
|
|
7
|
+
export { TitanShield } from './TitanShield.js';
|
|
8
|
+
|
|
9
|
+
// ── Input Validation ──────────────────────────────────────────────────────────
|
|
10
|
+
export { Schemas, validate, z } from './validate.js';
|
|
11
|
+
|
|
12
|
+
// ── v0.2 Prevention Engine ────────────────────────────────────────────────────
|
|
13
|
+
export {
|
|
14
|
+
AccountLockout, IpReputationEngine, SessionFingerprinter,
|
|
15
|
+
CsrfProtection, securityHeaders, detectBot,
|
|
16
|
+
detectAdvancedInjection, titanPreventionMiddleware,
|
|
17
|
+
} from './prevent.js';
|
|
18
|
+
export type { PreventionConfig, LockoutRecord, SessionFingerprint } from './prevent.js';
|
|
19
|
+
|
|
20
|
+
// ── v0.3 QUANTUM SHIELD ───────────────────────────────────────────────────────
|
|
21
|
+
export { QuantumSigner, QuantumRandom, QuantumAuditChain, globalQuantumSigner, globalQuantumRandom } from './quantum.js';
|
|
22
|
+
export type { QuantumKeyPair, QuantumSignature, QRNGResult, QuantumBlock } from './quantum.js';
|
|
23
|
+
|
|
24
|
+
// ── v0.3 NATURAL LANGUAGE RULES ───────────────────────────────────────────────
|
|
25
|
+
export { NaturalLanguageRuleParser } from './nlrules.js';
|
|
26
|
+
export type { NLRule, RuleCondition, RuleEvaluationContext, RuleMatch, RuleAction } from './nlrules.js';
|
|
27
|
+
|
|
28
|
+
// ── v0.3 SECURITY DNA ─────────────────────────────────────────────────────────
|
|
29
|
+
export { SecurityDNAProfiler } from './dna.js';
|
|
30
|
+
export type { SecurityDNA, DNAAnomaly, DNAScanResult } from './dna.js';
|
|
31
|
+
|
|
32
|
+
// ── v0.3 COLLECTIVE DEFENSE NETWORK ──────────────────────────────────────────
|
|
33
|
+
export { CollectiveDefenseNetwork } from './collective.js';
|
|
34
|
+
export type { CollectiveThreatSignal, ThreatSignalType, CollectiveStats } from './collective.js';
|
|
35
|
+
|
|
36
|
+
// ── v0.3 AI CODE SCANNER ─────────────────────────────────────────────────────
|
|
37
|
+
export { AISecurityScanner } from './scanner.js';
|
|
38
|
+
export type { SecurityVulnerability, ScanResult, VulnSeverity, VulnCategory } from './scanner.js';
|
|
39
|
+
|
|
40
|
+
// ── v0.3 BATTLE REPORT ───────────────────────────────────────────────────────
|
|
41
|
+
export { BattleReportGenerator } from './battle.js';
|
|
42
|
+
export type { BattleReport, BattleReportInput } from './battle.js';
|
|
43
|
+
|
|
44
|
+
// ── v0.3 GITHUB BADGE ────────────────────────────────────────────────────────
|
|
45
|
+
export { generateBadgeSvg, badgeRouteHandler } from './badge.js';
|
|
46
|
+
export type { BadgeConfig, BadgeResult, BadgeStyle } from './badge.js';
|
|
47
|
+
|
|
48
|
+
// ── v0.3 BEHAVIORAL BIOMETRICS ───────────────────────────────────────────────
|
|
49
|
+
export { BiometricAnalyzer, BIOMETRICS_CLIENT_SCRIPT } from './biometrics.js';
|
|
50
|
+
export type { BiometricPayload, BiometricAnalysis, KeystrokeSample, MouseSample } from './biometrics.js';
|
|
51
|
+
|
|
52
|
+
// ── Core Types ────────────────────────────────────────────────────────────────
|
|
53
|
+
export type {
|
|
54
|
+
TitanConfig, AuditEvent, AuditEventType, StoredAuditEvent,
|
|
55
|
+
ThreatAlert, AnomalyScore, ValidationResult, ValidationError,
|
|
56
|
+
ComplianceScore, ComplianceStandard,
|
|
57
|
+
} from './types.js';
|
|
58
|
+
|
|
59
|
+
|
package/src/nlrules.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// TitanShieldAI — nlrules.ts
|
|
3
|
+
//
|
|
4
|
+
// WORLD'S FIRST: Natural Language Security Rules
|
|
5
|
+
//
|
|
6
|
+
// Write security rules in plain English. AI converts them to running code.
|
|
7
|
+
// No other security tool on earth does this.
|
|
8
|
+
//
|
|
9
|
+
// Examples:
|
|
10
|
+
// "Block all logins from Russia after 10pm"
|
|
11
|
+
// "Lock any account after 3 failed attempts"
|
|
12
|
+
// "Alert me when someone exports more than 1000 records"
|
|
13
|
+
// "Require multi-factor auth for all admin users"
|
|
14
|
+
// "Block requests with more than 5 different user agents per hour"
|
|
15
|
+
//
|
|
16
|
+
// The AI (Gemini) parses the English, extracts structured intent,
|
|
17
|
+
// compiles it to a TitanShield Rule, and starts enforcing it immediately.
|
|
18
|
+
//
|
|
19
|
+
// No engineers needed. No docs to read. No configuration files.
|
|
20
|
+
// Just say what you want. TitanShield does it.
|
|
21
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
24
|
+
|
|
25
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
26
|
+
export type RuleAction = 'block' | 'alert' | 'log' | 'lockout' | 'require_mfa' | 'rate_limit' | 'flag';
|
|
27
|
+
|
|
28
|
+
export type RuleConditionType =
|
|
29
|
+
| 'geo_country' // request from specific country
|
|
30
|
+
| 'time_window' // specific time of day / day of week
|
|
31
|
+
| 'event_type' // specific event (login, data.export, etc.)
|
|
32
|
+
| 'failure_count' // N failures in window
|
|
33
|
+
| 'data_volume' // records accessed / exported
|
|
34
|
+
| 'user_role' // user has specific role
|
|
35
|
+
| 'ip_reputation' // IP score threshold
|
|
36
|
+
| 'new_device' // first time seeing this device
|
|
37
|
+
| 'rate_threshold' // requests per minute
|
|
38
|
+
| 'user_agent_diversity' // N different UAs in window
|
|
39
|
+
| 'always'; // unconditional
|
|
40
|
+
|
|
41
|
+
export interface RuleCondition {
|
|
42
|
+
type: RuleConditionType;
|
|
43
|
+
value?: string | number | string[];
|
|
44
|
+
operator?: 'gt' | 'lt' | 'eq' | 'in' | 'not_in' | 'after' | 'before' | 'between';
|
|
45
|
+
window?: number; // ms
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface NLRule {
|
|
49
|
+
id: string;
|
|
50
|
+
originalText: string; // the English the user typed
|
|
51
|
+
conditions: RuleCondition[];
|
|
52
|
+
action: RuleAction;
|
|
53
|
+
severity: 'info' | 'warning' | 'critical';
|
|
54
|
+
message: string; // human-readable description of what this rule does
|
|
55
|
+
confidence: 'high' | 'medium' | 'low'; // AI's confidence in the interpretation
|
|
56
|
+
createdAt: Date;
|
|
57
|
+
active: boolean;
|
|
58
|
+
triggerCount: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface RuleEvaluationContext {
|
|
62
|
+
event: string;
|
|
63
|
+
ip?: string;
|
|
64
|
+
userId?: string;
|
|
65
|
+
userRole?: string;
|
|
66
|
+
country?: string;
|
|
67
|
+
timestamp?: number;
|
|
68
|
+
metadata?: Record<string, unknown>;
|
|
69
|
+
recentEventCount?: number;
|
|
70
|
+
dataVolumeAccessed?: number;
|
|
71
|
+
failedLoginCount?: number;
|
|
72
|
+
uniqueUserAgents?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface RuleMatch {
|
|
76
|
+
rule: NLRule;
|
|
77
|
+
action: RuleAction;
|
|
78
|
+
message: string;
|
|
79
|
+
blocked: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── NaturalLanguageRuleParser ─────────────────────────────────────────────────
|
|
83
|
+
export class NaturalLanguageRuleParser {
|
|
84
|
+
private ai: GoogleGenerativeAI | null = null;
|
|
85
|
+
private rules: NLRule[] = [];
|
|
86
|
+
|
|
87
|
+
constructor(geminiApiKey?: string) {
|
|
88
|
+
if (geminiApiKey) {
|
|
89
|
+
this.ai = new GoogleGenerativeAI(geminiApiKey);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Parse a natural language security rule into a structured TitanShield rule.
|
|
95
|
+
* Uses Gemini to understand intent, then compiles to an active rule.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* const rule = await parser.parse("Block all logins from Russia after 10pm");
|
|
99
|
+
* // → { conditions: [{type:'geo_country', value:'RU'}, {type:'time_window', value:'22:00'}], action:'block' }
|
|
100
|
+
*/
|
|
101
|
+
async parse(text: string): Promise<NLRule> {
|
|
102
|
+
const id = `rule_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
103
|
+
|
|
104
|
+
let parsed: Partial<NLRule>;
|
|
105
|
+
|
|
106
|
+
if (this.ai) {
|
|
107
|
+
parsed = await this.parseWithAI(text, id);
|
|
108
|
+
} else {
|
|
109
|
+
parsed = this.parseWithHeuristics(text, id);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const rule: NLRule = {
|
|
113
|
+
id,
|
|
114
|
+
originalText: text,
|
|
115
|
+
conditions: parsed.conditions ?? [{ type: 'always' }],
|
|
116
|
+
action: parsed.action ?? 'alert',
|
|
117
|
+
severity: parsed.severity ?? 'warning',
|
|
118
|
+
message: parsed.message ?? `Rule: ${text}`,
|
|
119
|
+
confidence: parsed.confidence ?? 'medium',
|
|
120
|
+
createdAt: new Date(),
|
|
121
|
+
active: true,
|
|
122
|
+
triggerCount: 0,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
this.rules.push(rule);
|
|
126
|
+
console.log(`[TitanShield NL] ✅ Rule compiled: "${text}" → ${rule.action} (${rule.confidence} confidence)`);
|
|
127
|
+
return rule;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private async parseWithAI(text: string, id: string): Promise<Partial<NLRule>> {
|
|
131
|
+
const model = this.ai!.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
|
132
|
+
|
|
133
|
+
const prompt = `You are a security rule compiler for TitanShieldAI.
|
|
134
|
+
Parse this natural language security rule into structured JSON.
|
|
135
|
+
|
|
136
|
+
Rule: "${text}"
|
|
137
|
+
|
|
138
|
+
Return ONLY a valid JSON object with this exact structure:
|
|
139
|
+
{
|
|
140
|
+
"conditions": [
|
|
141
|
+
{
|
|
142
|
+
"type": "geo_country|time_window|event_type|failure_count|data_volume|user_role|ip_reputation|new_device|rate_threshold|user_agent_diversity|always",
|
|
143
|
+
"value": "the value to compare (string, number, or array of strings)",
|
|
144
|
+
"operator": "gt|lt|eq|in|not_in|after|before|between",
|
|
145
|
+
"window": (optional milliseconds for time windows)
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"action": "block|alert|log|lockout|require_mfa|rate_limit|flag",
|
|
149
|
+
"severity": "info|warning|critical",
|
|
150
|
+
"message": "Plain English: what this rule does and why. Start with an emoji.",
|
|
151
|
+
"confidence": "high|medium|low"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Country codes: US, RU, CN, KP, IR, etc.
|
|
155
|
+
Event types: user.login, user.login_failed, user.logout, data.export, data.read, admin.settings_changed
|
|
156
|
+
Time format for time_window values: "HH:MM" in 24h format
|
|
157
|
+
|
|
158
|
+
Be creative and thorough. Extract ALL conditions from the rule text.`;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const result = await model.generateContent(prompt);
|
|
162
|
+
const raw = result.response.text().replace(/```json\n?|```/g, '').trim();
|
|
163
|
+
return JSON.parse(raw);
|
|
164
|
+
} catch {
|
|
165
|
+
return this.parseWithHeuristics(text, id);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private parseWithHeuristics(text: string, _id: string): Partial<NLRule> {
|
|
170
|
+
const lower = text.toLowerCase();
|
|
171
|
+
const conditions: RuleCondition[] = [];
|
|
172
|
+
|
|
173
|
+
// Geo detection
|
|
174
|
+
const countries: Record<string, string> = {
|
|
175
|
+
'russia': 'RU', 'china': 'CN', 'north korea': 'KP', 'iran': 'IR',
|
|
176
|
+
'nigeria': 'NG', 'usa': 'US', 'india': 'IN', 'brazil': 'BR',
|
|
177
|
+
};
|
|
178
|
+
for (const [name, code] of Object.entries(countries)) {
|
|
179
|
+
if (lower.includes(name)) {
|
|
180
|
+
conditions.push({ type: 'geo_country', value: code, operator: 'eq' });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Time detection
|
|
185
|
+
const timeMatch = lower.match(/after\s+(\d{1,2})(pm|am)/);
|
|
186
|
+
if (timeMatch) {
|
|
187
|
+
let hour = parseInt(timeMatch[1]);
|
|
188
|
+
if (timeMatch[2] === 'pm' && hour !== 12) hour += 12;
|
|
189
|
+
conditions.push({ type: 'time_window', value: `${String(hour).padStart(2, '0')}:00`, operator: 'after' });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Event detection
|
|
193
|
+
if (lower.includes('login')) conditions.push({ type: 'event_type', value: 'user.login' });
|
|
194
|
+
if (lower.includes('export')) conditions.push({ type: 'event_type', value: 'data.export' });
|
|
195
|
+
if (lower.includes('admin')) conditions.push({ type: 'user_role', value: 'admin', operator: 'eq' });
|
|
196
|
+
|
|
197
|
+
// Failure count
|
|
198
|
+
const failMatch = lower.match(/(\d+)\s+failed/);
|
|
199
|
+
if (failMatch) {
|
|
200
|
+
conditions.push({ type: 'failure_count', value: parseInt(failMatch[1]), operator: 'gt', window: 300_000 });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Action detection
|
|
204
|
+
let action: RuleAction = 'alert';
|
|
205
|
+
if (lower.includes('block') || lower.includes('deny')) action = 'block';
|
|
206
|
+
else if (lower.includes('lock')) action = 'lockout';
|
|
207
|
+
else if (lower.includes('mfa') || lower.includes('multi-factor')) action = 'require_mfa';
|
|
208
|
+
else if (lower.includes('alert') || lower.includes('notify')) action = 'alert';
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
conditions: conditions.length > 0 ? conditions : [{ type: 'always' }],
|
|
212
|
+
action,
|
|
213
|
+
severity: action === 'block' || action === 'lockout' ? 'critical' : 'warning',
|
|
214
|
+
message: `🛡️ NL Rule: ${text}`,
|
|
215
|
+
confidence: 'medium',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Evaluate all active rules against an incoming request context.
|
|
221
|
+
* Returns list of matching rules with their required actions.
|
|
222
|
+
*/
|
|
223
|
+
evaluate(ctx: RuleEvaluationContext): RuleMatch[] {
|
|
224
|
+
const matches: RuleMatch[] = [];
|
|
225
|
+
const now = new Date();
|
|
226
|
+
const currentHour = now.getHours();
|
|
227
|
+
const currentMinute = now.getMinutes();
|
|
228
|
+
|
|
229
|
+
for (const rule of this.rules.filter(r => r.active)) {
|
|
230
|
+
const matched = rule.conditions.every(cond => this.evaluateCondition(cond, ctx, currentHour, currentMinute));
|
|
231
|
+
|
|
232
|
+
if (matched) {
|
|
233
|
+
rule.triggerCount++;
|
|
234
|
+
matches.push({
|
|
235
|
+
rule,
|
|
236
|
+
action: rule.action,
|
|
237
|
+
message: `🛡️ Rule triggered: "${rule.originalText}" → ${rule.action.toUpperCase()}`,
|
|
238
|
+
blocked: rule.action === 'block' || rule.action === 'lockout',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return matches;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private evaluateCondition(
|
|
247
|
+
cond: RuleCondition,
|
|
248
|
+
ctx: RuleEvaluationContext,
|
|
249
|
+
hour: number,
|
|
250
|
+
_minute: number
|
|
251
|
+
): boolean {
|
|
252
|
+
switch (cond.type) {
|
|
253
|
+
case 'always': return true;
|
|
254
|
+
case 'geo_country':
|
|
255
|
+
return ctx.country === cond.value;
|
|
256
|
+
case 'time_window':
|
|
257
|
+
if (typeof cond.value === 'string') {
|
|
258
|
+
const [h] = cond.value.split(':').map(Number);
|
|
259
|
+
if (cond.operator === 'after') return hour >= h;
|
|
260
|
+
if (cond.operator === 'before') return hour < h;
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
case 'event_type':
|
|
264
|
+
return ctx.event === cond.value || (Array.isArray(cond.value) && cond.value.includes(ctx.event));
|
|
265
|
+
case 'failure_count':
|
|
266
|
+
return typeof cond.value === 'number' && (ctx.failedLoginCount ?? 0) >= cond.value;
|
|
267
|
+
case 'data_volume':
|
|
268
|
+
return typeof cond.value === 'number' && (ctx.dataVolumeAccessed ?? 0) >= cond.value;
|
|
269
|
+
case 'user_role':
|
|
270
|
+
return ctx.userRole === cond.value;
|
|
271
|
+
case 'rate_threshold':
|
|
272
|
+
return typeof cond.value === 'number' && (ctx.recentEventCount ?? 0) >= cond.value;
|
|
273
|
+
case 'new_device':
|
|
274
|
+
return ctx.metadata?.newDevice === true;
|
|
275
|
+
case 'user_agent_diversity':
|
|
276
|
+
return typeof cond.value === 'number' && (ctx.uniqueUserAgents ?? 0) >= cond.value;
|
|
277
|
+
default: return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Get all active rules with their trigger counts */
|
|
282
|
+
getRules(): NLRule[] { return [...this.rules]; }
|
|
283
|
+
|
|
284
|
+
/** Enable/disable a rule by ID */
|
|
285
|
+
setActive(ruleId: string, active: boolean): boolean {
|
|
286
|
+
const rule = this.rules.find(r => r.id === ruleId);
|
|
287
|
+
if (rule) { rule.active = active; return true; }
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Delete a rule by ID */
|
|
292
|
+
deleteRule(ruleId: string): boolean {
|
|
293
|
+
const idx = this.rules.findIndex(r => r.id === ruleId);
|
|
294
|
+
if (idx >= 0) { this.rules.splice(idx, 1); return true; }
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|