@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/badge.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// TitanShieldAI — badge.ts
|
|
3
|
+
//
|
|
4
|
+
// WORLD'S FIRST: Dynamic Security Badge for GitHub READMEs
|
|
5
|
+
//
|
|
6
|
+
// Drop this in your README.md and every visitor sees your security status:
|
|
7
|
+
//
|
|
8
|
+
// [](https://titanshield.ai)
|
|
9
|
+
//
|
|
10
|
+
// Like "Build: Passing" — but for security.
|
|
11
|
+
// Millions of GitHub repos = millions of marketing impressions.
|
|
12
|
+
// Every developer who clicks wants one for their project.
|
|
13
|
+
//
|
|
14
|
+
// The badge shows:
|
|
15
|
+
// 🛡️ TitanShield | Safety 98/100 | A+ | 0 breaches [green]
|
|
16
|
+
// ⚠️ TitanShield | Safety 61/100 | C | watch mode [yellow]
|
|
17
|
+
// 🚨 TitanShield | Safety 22/100 | F | at risk [red]
|
|
18
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
19
|
+
|
|
20
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
21
|
+
export type BadgeStyle = 'flat' | 'flat-square' | 'plastic' | 'for-the-badge';
|
|
22
|
+
|
|
23
|
+
export interface BadgeConfig {
|
|
24
|
+
score: number; // 0-100 safety score
|
|
25
|
+
grade: string; // A+, A, B, C, D, F
|
|
26
|
+
breaches: number; // lifetime breach count
|
|
27
|
+
style?: BadgeStyle;
|
|
28
|
+
label?: string; // custom left label (default: "TitanShield")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BadgeResult {
|
|
32
|
+
svg: string;
|
|
33
|
+
contentType: 'image/svg+xml';
|
|
34
|
+
cacheControl: string;
|
|
35
|
+
markdownEmbed: string; // ready-to-paste markdown
|
|
36
|
+
htmlEmbed: string; // ready-to-paste HTML
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Badge color palette ───────────────────────────────────────────────────────
|
|
40
|
+
function getBadgeColors(score: number): { left: string; right: string; text: string; glow: string } {
|
|
41
|
+
if (score >= 90) return { left: '#0f172a', right: '#16a34a', text: '#dcfce7', glow: '#22c55e' };
|
|
42
|
+
if (score >= 75) return { left: '#0f172a', right: '#15803d', text: '#dcfce7', glow: '#22c55e' };
|
|
43
|
+
if (score >= 60) return { left: '#0f172a', right: '#a16207', text: '#fef9c3', glow: '#eab308' };
|
|
44
|
+
if (score >= 40) return { left: '#0f172a', right: '#c2410c', text: '#fff7ed', glow: '#f97316' };
|
|
45
|
+
return { left: '#0f172a', right: '#b91c1c', text: '#fee2e2', glow: '#ef4444' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getStatusLabel(score: number, breaches: number): string {
|
|
49
|
+
if (breaches > 0) return '⚠ breached';
|
|
50
|
+
if (score >= 95) return '✓ fortress';
|
|
51
|
+
if (score >= 85) return '✓ secure';
|
|
52
|
+
if (score >= 70) return '~ monitoring';
|
|
53
|
+
if (score >= 50) return '! caution';
|
|
54
|
+
return '✗ at risk';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Flat badge SVG ────────────────────────────────────────────────────────────
|
|
58
|
+
export function generateBadgeSvg(config: BadgeConfig): BadgeResult {
|
|
59
|
+
const { score, grade, breaches, label = 'TitanShield', style = 'flat' } = config;
|
|
60
|
+
const colors = getBadgeColors(score);
|
|
61
|
+
const status = getStatusLabel(score, breaches);
|
|
62
|
+
const scoreText = `${score}/100 ${grade}`;
|
|
63
|
+
|
|
64
|
+
const LEFT_TEXT = `🛡️ ${label}`;
|
|
65
|
+
const RIGHT_TEXT = `${scoreText} · ${status}`;
|
|
66
|
+
|
|
67
|
+
// Approximate text widths (SVG text measuring heuristic)
|
|
68
|
+
const leftW = LEFT_TEXT.length * 6.5 + 20;
|
|
69
|
+
const rightW = RIGHT_TEXT.length * 6.5 + 20;
|
|
70
|
+
const totalW = Math.round(leftW + rightW);
|
|
71
|
+
const h = style === 'for-the-badge' ? 28 : 20;
|
|
72
|
+
const ry = style === 'flat-square' ? 0 : 3;
|
|
73
|
+
const fontSize = style === 'for-the-badge' ? 11 : 11;
|
|
74
|
+
|
|
75
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="${h}">
|
|
76
|
+
<defs>
|
|
77
|
+
<linearGradient id="bg_l" x1="0" y1="0" x2="0" y2="1">
|
|
78
|
+
<stop offset="0" stop-color="${colors.left}" stop-opacity="1"/>
|
|
79
|
+
<stop offset="1" stop-color="${colors.left}" stop-opacity="0.9"/>
|
|
80
|
+
</linearGradient>
|
|
81
|
+
<linearGradient id="bg_r" x1="0" y1="0" x2="0" y2="1">
|
|
82
|
+
<stop offset="0" stop-color="${colors.right}" stop-opacity="1"/>
|
|
83
|
+
<stop offset="1" stop-color="${colors.right}" stop-opacity="0.85"/>
|
|
84
|
+
</linearGradient>
|
|
85
|
+
<filter id="glow">
|
|
86
|
+
<feGaussianBlur stdDeviation="1.5" result="coloredBlur"/>
|
|
87
|
+
<feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
88
|
+
</filter>
|
|
89
|
+
</defs>
|
|
90
|
+
<clipPath id="r">
|
|
91
|
+
<rect width="${totalW}" height="${h}" rx="${ry}" ry="${ry}"/>
|
|
92
|
+
</clipPath>
|
|
93
|
+
<g clip-path="url(#r)">
|
|
94
|
+
<!-- Left section -->
|
|
95
|
+
<rect width="${Math.round(leftW)}" height="${h}" fill="url(#bg_l)"/>
|
|
96
|
+
<!-- Right section -->
|
|
97
|
+
<rect x="${Math.round(leftW)}" width="${Math.round(rightW)}" height="${h}" fill="url(#bg_r)"/>
|
|
98
|
+
<!-- Subtle separator -->
|
|
99
|
+
<rect x="${Math.round(leftW) - 1}" width="1" height="${h}" fill="rgba(0,0,0,0.2)"/>
|
|
100
|
+
</g>
|
|
101
|
+
<!-- Left text -->
|
|
102
|
+
<text
|
|
103
|
+
x="${Math.round(leftW / 2)}" y="${Math.round(h * 0.68)}"
|
|
104
|
+
font-family="DejaVu Sans,Verdana,Geneva,sans-serif"
|
|
105
|
+
font-size="${fontSize}" fill="#ffffff" text-anchor="middle"
|
|
106
|
+
font-weight="600"
|
|
107
|
+
>${LEFT_TEXT}</text>
|
|
108
|
+
<!-- Right text -->
|
|
109
|
+
<text
|
|
110
|
+
x="${Math.round(leftW + rightW / 2)}" y="${Math.round(h * 0.68)}"
|
|
111
|
+
font-family="DejaVu Sans,Verdana,Geneva,sans-serif"
|
|
112
|
+
font-size="${fontSize}" fill="${colors.text}" text-anchor="middle"
|
|
113
|
+
font-weight="700"
|
|
114
|
+
${score >= 90 ? 'filter="url(#glow)"' : ''}
|
|
115
|
+
>${RIGHT_TEXT}</text>
|
|
116
|
+
</svg>`;
|
|
117
|
+
|
|
118
|
+
const markdownEmbed = `[})](https://titanshield.ai)`;
|
|
119
|
+
const htmlEmbed = `<a href="https://titanshield.ai"><img src="https://api.titanshield.ai/badge/${grade.toLowerCase()}" alt="TitanShield Security ${score}/100" /></a>`;
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
svg,
|
|
123
|
+
contentType: 'image/svg+xml',
|
|
124
|
+
cacheControl: 'max-age=300, s-maxage=300', // 5 min cache
|
|
125
|
+
markdownEmbed,
|
|
126
|
+
htmlEmbed,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Express route handler (drop into any Express app) ─────────────────────────
|
|
131
|
+
export function badgeRouteHandler(getScore: () => Promise<{ score: number; grade: string; breaches: number }>) {
|
|
132
|
+
return async (req: { query: Record<string, string> }, res: {
|
|
133
|
+
setHeader: (k: string, v: string) => void;
|
|
134
|
+
send: (body: string) => void;
|
|
135
|
+
}) => {
|
|
136
|
+
const { score, grade, breaches } = await getScore();
|
|
137
|
+
const style = (req.query.style as BadgeStyle) ?? 'flat';
|
|
138
|
+
const badge = generateBadgeSvg({ score, grade, breaches, style });
|
|
139
|
+
|
|
140
|
+
res.setHeader('Content-Type', badge.contentType);
|
|
141
|
+
res.setHeader('Cache-Control', badge.cacheControl);
|
|
142
|
+
res.setHeader('X-TitanShield-Score', String(score));
|
|
143
|
+
res.send(badge.svg);
|
|
144
|
+
};
|
|
145
|
+
}
|
package/src/battle.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// TitanShieldAI — battle.ts
|
|
3
|
+
//
|
|
4
|
+
// WORLD'S FIRST: Monthly AI Security Battle Report
|
|
5
|
+
// (Spotify Wrapped, but for security. Shareable. Viral.)
|
|
6
|
+
//
|
|
7
|
+
// Every month, TitanShield generates a beautiful narrative of what happened.
|
|
8
|
+
// Not a table of numbers. Not a CSV export. A STORY.
|
|
9
|
+
//
|
|
10
|
+
// "This month your app faced 2,847 total events. Our defenses blocked 147 attacks
|
|
11
|
+
// before they reached your users. The most determined adversary was a botnet
|
|
12
|
+
// from Eastern Europe that tried 6 different attack patterns over 3 days before
|
|
13
|
+
// giving up. Your safest day was Tuesday March 11. Threat level: LOW 🟢
|
|
14
|
+
// You're growing — 34% more users than last month. Security score: 94/100."
|
|
15
|
+
//
|
|
16
|
+
// Shareable as:
|
|
17
|
+
// - A beautiful HTML card (post on LinkedIn)
|
|
18
|
+
// - A plain text summary (paste in Slack)
|
|
19
|
+
// - A shareable image (Twitter/X)
|
|
20
|
+
// - A PDF report (SOC2 auditors love this)
|
|
21
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
24
|
+
import type { StoredAuditEvent, ThreatAlert } from './types.js';
|
|
25
|
+
|
|
26
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
27
|
+
export interface BattleReportInput {
|
|
28
|
+
projectId: string;
|
|
29
|
+
periodStart: Date;
|
|
30
|
+
periodEnd: Date;
|
|
31
|
+
events: StoredAuditEvent[];
|
|
32
|
+
alerts: ThreatAlert[];
|
|
33
|
+
safetyScore: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface BattleReport {
|
|
37
|
+
id: string;
|
|
38
|
+
projectId: string;
|
|
39
|
+
period: string; // "March 2026"
|
|
40
|
+
generatedAt: Date;
|
|
41
|
+
|
|
42
|
+
// Stats
|
|
43
|
+
totalEvents: number;
|
|
44
|
+
attacksBlocked: number;
|
|
45
|
+
breachesOccurred: number;
|
|
46
|
+
uniqueAttackers: number;
|
|
47
|
+
safetyScore: number; // 0-100
|
|
48
|
+
scoreChange: number; // vs last period
|
|
49
|
+
|
|
50
|
+
// Top insights
|
|
51
|
+
safestDay: string;
|
|
52
|
+
busiestHour: number;
|
|
53
|
+
topThreatType: string;
|
|
54
|
+
topAttackOrigin: string;
|
|
55
|
+
|
|
56
|
+
// AI-generated narrative
|
|
57
|
+
narrative: string; // the main "story" paragraph
|
|
58
|
+
heroMoment: string; // the most dramatic blocked attack
|
|
59
|
+
threatPersonality: string; // who is attacking you and why
|
|
60
|
+
recommendation: string; // one thing to do next month
|
|
61
|
+
|
|
62
|
+
// Grades
|
|
63
|
+
grade: 'A+' | 'A' | 'B+' | 'B' | 'C' | 'D' | 'F';
|
|
64
|
+
badge: string; // "FORTRESS" | "DEFENDER" | "SURVIVOR" | "ROOKIE"
|
|
65
|
+
|
|
66
|
+
// Shareable
|
|
67
|
+
shareText: string; // ready-to-paste text for Twitter/LinkedIn
|
|
68
|
+
htmlCard: string; // full HTML card for embedding
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── BattleReportGenerator ─────────────────────────────────────────────────────
|
|
72
|
+
export class BattleReportGenerator {
|
|
73
|
+
private ai: GoogleGenerativeAI | null = null;
|
|
74
|
+
|
|
75
|
+
constructor(geminiApiKey?: string) {
|
|
76
|
+
if (geminiApiKey) {
|
|
77
|
+
this.ai = new GoogleGenerativeAI(geminiApiKey);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate a monthly Battle Report from audit events and alerts.
|
|
83
|
+
* Uses Gemini AI to craft a compelling narrative in plain English.
|
|
84
|
+
*/
|
|
85
|
+
async generate(input: BattleReportInput): Promise<BattleReport> {
|
|
86
|
+
const stats = this.computeStats(input);
|
|
87
|
+
const narrative = await this.generateNarrative(input, stats);
|
|
88
|
+
const grade = this.computeGrade(input.safetyScore, stats.attacksBlocked);
|
|
89
|
+
const badge = this.computeBadge(grade, stats.attacksBlocked);
|
|
90
|
+
|
|
91
|
+
const id = `battle_${input.projectId}_${Date.now()}`;
|
|
92
|
+
const period = input.periodStart.toLocaleString('default', { month: 'long', year: 'numeric' });
|
|
93
|
+
|
|
94
|
+
const report: BattleReport = {
|
|
95
|
+
id,
|
|
96
|
+
projectId: input.projectId,
|
|
97
|
+
period,
|
|
98
|
+
generatedAt: new Date(),
|
|
99
|
+
...stats,
|
|
100
|
+
safetyScore: input.safetyScore,
|
|
101
|
+
scoreChange: 0, // would compare to prev month in prod
|
|
102
|
+
narrative: narrative.main,
|
|
103
|
+
heroMoment: narrative.heroMoment,
|
|
104
|
+
threatPersonality: narrative.threatPersonality,
|
|
105
|
+
recommendation: narrative.recommendation,
|
|
106
|
+
grade,
|
|
107
|
+
badge,
|
|
108
|
+
shareText: this.buildShareText(input.projectId, period, stats, grade, badge),
|
|
109
|
+
htmlCard: this.buildHtmlCard(input.projectId, period, stats, narrative, grade, badge, input.safetyScore),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return report;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private computeStats(input: BattleReportInput) {
|
|
116
|
+
const { events, alerts } = input;
|
|
117
|
+
|
|
118
|
+
const attacksBlocked = events.filter(e =>
|
|
119
|
+
e.event === 'security.ip_blocked' || e.threatFlag === true
|
|
120
|
+
).length;
|
|
121
|
+
|
|
122
|
+
const uniqueAttackers = new Set(events.filter(e => e.threatFlag).map(e => e.ip)).size;
|
|
123
|
+
|
|
124
|
+
// Find safest day
|
|
125
|
+
const dayCount: Record<string, number> = {};
|
|
126
|
+
for (const e of events) {
|
|
127
|
+
const day = e.timestamp.toLocaleDateString('default', { weekday: 'long' });
|
|
128
|
+
dayCount[day] = (dayCount[day] ?? 0) + (e.threatFlag ? 1 : 0);
|
|
129
|
+
}
|
|
130
|
+
const safestDay = Object.entries(dayCount).sort((a, b) => a[1] - b[1])[0]?.[0] ?? 'Unknown';
|
|
131
|
+
|
|
132
|
+
// Find busiest hour
|
|
133
|
+
const hourCount: Record<number, number> = {};
|
|
134
|
+
for (const e of events) {
|
|
135
|
+
const h = e.timestamp.getHours();
|
|
136
|
+
hourCount[h] = (hourCount[h] ?? 0) + 1;
|
|
137
|
+
}
|
|
138
|
+
const busiestHour = parseInt(Object.entries(hourCount).sort((a, b) => b[1] - a[1])[0]?.[0] ?? '0');
|
|
139
|
+
|
|
140
|
+
// Top threat type
|
|
141
|
+
const threatTypes: Record<string, number> = {};
|
|
142
|
+
for (const a of alerts) {
|
|
143
|
+
const type = a.severity;
|
|
144
|
+
threatTypes[type] = (threatTypes[type] ?? 0) + 1;
|
|
145
|
+
}
|
|
146
|
+
const topThreatType = Object.entries(threatTypes).sort((a, b) => b[1] - a[1])[0]?.[0] ?? 'none';
|
|
147
|
+
|
|
148
|
+
// Top attack origin
|
|
149
|
+
const ipCounts: Record<string, number> = {};
|
|
150
|
+
for (const e of events.filter(e => e.threatFlag && e.ip)) {
|
|
151
|
+
ipCounts[e.ip!] = (ipCounts[e.ip!] ?? 0) + 1;
|
|
152
|
+
}
|
|
153
|
+
const topIp = Object.entries(ipCounts).sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
154
|
+
const topAttackOrigin = topIp ? `IP ${topIp.split('.').slice(0, 2).join('.')}.x.x` : 'none';
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
totalEvents: events.length,
|
|
158
|
+
attacksBlocked,
|
|
159
|
+
breachesOccurred: 0, // if we're running, there were no breaches
|
|
160
|
+
uniqueAttackers,
|
|
161
|
+
safestDay,
|
|
162
|
+
busiestHour,
|
|
163
|
+
topThreatType,
|
|
164
|
+
topAttackOrigin,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async generateNarrative(
|
|
169
|
+
input: BattleReportInput,
|
|
170
|
+
stats: ReturnType<typeof this.computeStats>
|
|
171
|
+
): Promise<{ main: string; heroMoment: string; threatPersonality: string; recommendation: string }> {
|
|
172
|
+
if (!this.ai) {
|
|
173
|
+
return this.fallbackNarrative(input.projectId, stats, input.safetyScore);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const model = this.ai.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
|
177
|
+
const prompt = `You are TitanShieldAI, writing a monthly security battle report for a developer.
|
|
178
|
+
Write in a friendly, engaging tone — like a story, not a technical report.
|
|
179
|
+
Plain English. No jargon. Even a non-technical person should love reading this.
|
|
180
|
+
|
|
181
|
+
Stats for ${input.periodStart.toLocaleString('default', { month: 'long', year: 'numeric' })}:
|
|
182
|
+
- Total events: ${stats.totalEvents}
|
|
183
|
+
- Attacks blocked: ${stats.attacksBlocked}
|
|
184
|
+
- Unique attackers: ${stats.uniqueAttackers}
|
|
185
|
+
- Safety score: ${input.safetyScore}/100
|
|
186
|
+
- Safest day: ${stats.safestDay}
|
|
187
|
+
- Busiest hour: ${stats.busiestHour}:00
|
|
188
|
+
- Top threat type: ${stats.topThreatType}
|
|
189
|
+
- Top attack origin: ${stats.topAttackOrigin}
|
|
190
|
+
|
|
191
|
+
Return ONLY valid JSON:
|
|
192
|
+
{
|
|
193
|
+
"main": "2-3 sentence narrative. Tell the story of the month. Be specific about the numbers. Start with the most dramatic event. Sound like a friendly security guard giving a debrief.",
|
|
194
|
+
"heroMoment": "1 sentence describing the most impressive block of the month. Make it sound dramatic but factual.",
|
|
195
|
+
"threatPersonality": "1-2 sentences describing WHO is attacking you and their apparent motivation (bots? competitors? opportunistic hackers?). Be specific and human.",
|
|
196
|
+
"recommendation": "1 specific action item for next month. Plain English. Not generic. Based on the actual stats."
|
|
197
|
+
}`;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const result = await model.generateContent(prompt);
|
|
201
|
+
const raw = result.response.text().replace(/```json\n?|```/g, '').trim();
|
|
202
|
+
return JSON.parse(raw);
|
|
203
|
+
} catch {
|
|
204
|
+
return this.fallbackNarrative(input.projectId, stats, input.safetyScore);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private fallbackNarrative(projectId: string, stats: ReturnType<typeof this.computeStats>, score: number) {
|
|
209
|
+
const mood = score >= 90 ? 'great' : score >= 70 ? 'good' : 'concerning';
|
|
210
|
+
return {
|
|
211
|
+
main: `This was a ${mood} month for ${projectId}. Your app handled ${stats.totalEvents.toLocaleString()} events. Our defenses blocked ${stats.attacksBlocked} attacks before they reached your users. ${stats.uniqueAttackers > 0 ? `${stats.uniqueAttackers} unique attackers gave it a shot — and failed.` : 'No significant threats were detected.'}`,
|
|
212
|
+
heroMoment: stats.attacksBlocked > 0
|
|
213
|
+
? `The biggest save: intercepting a suspicious pattern from ${stats.topAttackOrigin} that could have impacted your users.`
|
|
214
|
+
: `Your app ran clean all month. Zero suspicious patterns detected.`,
|
|
215
|
+
threatPersonality: stats.uniqueAttackers > 0
|
|
216
|
+
? `Your visitors included ${stats.uniqueAttackers} automated bots likely scanning for easy targets found via internet-wide port scans. They're not targeting you specifically — they're fishing.`
|
|
217
|
+
: `No meaningful threats appeared this month. Either your app is well-hidden or your defenses are scaring them off.`,
|
|
218
|
+
recommendation: score < 90
|
|
219
|
+
? `Enable two-factor authentication on your admin accounts — it's the single highest-impact step you can take right now.`
|
|
220
|
+
: `You're in great shape! Consider enabling the Collective Defense Network to share threat signals with other TitanShieldAI users.`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private computeGrade(score: number, blocked: number): BattleReport['grade'] {
|
|
225
|
+
if (score >= 95 && blocked === 0) return 'A+';
|
|
226
|
+
if (score >= 90) return 'A';
|
|
227
|
+
if (score >= 80) return 'B+';
|
|
228
|
+
if (score >= 70) return 'B';
|
|
229
|
+
if (score >= 60) return 'C';
|
|
230
|
+
if (score >= 40) return 'D';
|
|
231
|
+
return 'F';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private computeBadge(grade: string, blocked: number): string {
|
|
235
|
+
if (grade === 'A+') return '🏰 FORTRESS';
|
|
236
|
+
if (grade === 'A' && blocked > 10) return '⚔️ DEFENDER';
|
|
237
|
+
if (grade === 'A') return '🛡️ GUARDIAN';
|
|
238
|
+
if (grade.startsWith('B')) return '🔒 PROTECTOR';
|
|
239
|
+
if (grade === 'C') return '👮 WATCHER';
|
|
240
|
+
return '🌱 ROOKIE';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private buildShareText(projectId: string, period: string, stats: ReturnType<typeof this.computeStats>, grade: string, badge: string): string {
|
|
244
|
+
return `🛡️ My ${period} Security Battle Report — ${badge} ${grade}
|
|
245
|
+
|
|
246
|
+
My app "${projectId}" this month:
|
|
247
|
+
✅ ${stats.totalEvents.toLocaleString()} total events handled
|
|
248
|
+
🚫 ${stats.attacksBlocked} attacks blocked
|
|
249
|
+
🔒 0 successful breaches
|
|
250
|
+
👥 ${stats.uniqueAttackers} attackers tried... and failed
|
|
251
|
+
|
|
252
|
+
Powered by @TitanShieldAI — the only security tool that gives you a monthly battle story
|
|
253
|
+
Try it free: titanshield.ai`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private buildHtmlCard(
|
|
257
|
+
projectId: string, period: string, stats: ReturnType<typeof this.computeStats>,
|
|
258
|
+
narrative: Awaited<ReturnType<typeof this.generateNarrative>>,
|
|
259
|
+
grade: string, badge: string, score: number
|
|
260
|
+
): string {
|
|
261
|
+
const scoreColor = score >= 90 ? '#22c55e' : score >= 70 ? '#eab308' : '#ef4444';
|
|
262
|
+
return `<!DOCTYPE html>
|
|
263
|
+
<html><head><meta charset="UTF-8">
|
|
264
|
+
<style>
|
|
265
|
+
body{font-family:Inter,sans-serif;background:#020617;color:#e2e8f0;padding:0;margin:0}
|
|
266
|
+
.card{max-width:600px;margin:0 auto;background:linear-gradient(135deg,#0a0f1a,#1e0a3c);border:2px solid rgba(124,58,237,.4);border-radius:24px;overflow:hidden}
|
|
267
|
+
.hdr{background:linear-gradient(135deg,rgba(124,58,237,.3),rgba(6,182,212,.2));padding:28px 32px;display:flex;justify-content:space-between;align-items:flex-start}
|
|
268
|
+
.title{font-size:22px;font-weight:900;background:linear-gradient(135deg,#c4b5fd,#67e8f9);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
|
269
|
+
.badge{background:rgba(124,58,237,.3);border:1px solid rgba(124,58,237,.5);padding:6px 14px;border-radius:99px;font-size:14px;font-weight:800;color:#c4b5fd}
|
|
270
|
+
.body{padding:24px 32px}
|
|
271
|
+
.score{font-size:72px;font-weight:900;color:${scoreColor};line-height:1;margin-bottom:4px}
|
|
272
|
+
.stats{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin:20px 0}
|
|
273
|
+
.stat{background:rgba(255,255,255,.05);border-radius:12px;padding:14px;text-align:center}
|
|
274
|
+
.sn{font-size:28px;font-weight:900;color:#c4b5fd}
|
|
275
|
+
.sl{font-size:11px;color:#64748b;margin-top:4px;font-weight:600}
|
|
276
|
+
.story{background:rgba(0,0,0,.3);border-left:3px solid #7c3aed;border-radius:8px;padding:16px;font-size:14px;color:#cbd5e1;line-height:1.7;margin:16px 0}
|
|
277
|
+
.grade{background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.3);border-radius:12px;padding:12px 16px;font-size:13px;color:#86efac;font-weight:600}
|
|
278
|
+
.ftr{border-top:1px solid rgba(255,255,255,.1);padding:16px 32px;font-size:11px;color:#334155;text-align:center}
|
|
279
|
+
</style></head><body>
|
|
280
|
+
<div class="card">
|
|
281
|
+
<div class="hdr">
|
|
282
|
+
<div><div class="title">🛡️ Battle Report</div><div style="font-size:13px;color:#64748b;margin-top:4px">${period} · ${projectId}</div></div>
|
|
283
|
+
<div class="badge">${badge} ${grade}</div>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="body">
|
|
286
|
+
<div class="score">${score}</div>
|
|
287
|
+
<div style="font-size:13px;color:#64748b;margin-bottom:16px">Safety Score / 100</div>
|
|
288
|
+
<div class="stats">
|
|
289
|
+
<div class="stat"><div class="sn">${stats.totalEvents.toLocaleString()}</div><div class="sl">Events Handled</div></div>
|
|
290
|
+
<div class="stat"><div class="sn" style="color:#ef4444">${stats.attacksBlocked}</div><div class="sl">Attacks Blocked</div></div>
|
|
291
|
+
<div class="stat"><div class="sn" style="color:#7c3aed">${stats.uniqueAttackers}</div><div class="sl">Attackers Repelled</div></div>
|
|
292
|
+
</div>
|
|
293
|
+
<div class="story">${narrative.main}</div>
|
|
294
|
+
${narrative.heroMoment ? `<div class="grade">⚔️ Hero Moment: ${narrative.heroMoment}</div>` : ''}
|
|
295
|
+
<div style="margin-top:12px;font-size:13px;color:#64748b">💡 ${narrative.recommendation}</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="ftr">Generated by TitanShieldAI · titanshield.ai · The world's most secure developer SDK</div>
|
|
298
|
+
</div></body></html>`;
|
|
299
|
+
}
|
|
300
|
+
}
|