-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathsentry.ts
More file actions
99 lines (91 loc) · 2.94 KB
/
Copy pathsentry.ts
File metadata and controls
99 lines (91 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import * as Sentry from '@sentry/cloudflare';
import type { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
import type { AppEnv } from '../types/appenv';
export function sentryOptions(env: Env) : Sentry.CloudflareOptions {
const transportOptions: Sentry.CloudflareOptions['transportOptions'] = {};
if (env.CF_ACCESS_ID && env.CF_ACCESS_SECRET) {
transportOptions.headers = {
'CF-Access-Client-Id': env.CF_ACCESS_ID,
'CF-Access-Client-Secret': env.CF_ACCESS_SECRET,
};
}
return {
dsn: env.SENTRY_DSN,
release: env.CF_VERSION_METADATA.id,
environment: env.ENVIRONMENT,
enableLogs: true,
sendDefaultPii: true,
tracesSampleRate: 1.0,
transportOptions,
allowUrls: [
// Only capture errors from our API endpoints
new RegExp(`^https://${env.CUSTOM_DOMAIN.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/api/.*$`)
]
};
}
export function initHonoSentry(app: Hono<AppEnv>): void {
// Report unhandled exceptions from routes/middleware
app.onError((err, c) => {
Sentry.captureException(err);
if (err instanceof HTTPException) {
return err.getResponse();
}
return c.json({ error: 'Internal server error' }, 500);
});
// Light context binding for better traces
app.use('*', async (c, next) => {
try {
const url = new URL(c.req.url);
Sentry.setTag('http.method', c.req.method);
Sentry.setTag('http.path', url.pathname);
const cfRay = c.req.header('cf-ray');
if (cfRay) Sentry.setTag('cf_ray', cfRay);
} catch {
console.error('Failed to set Sentry context');
}
return next();
});
}
export type SecurityEventType =
| 'csrf_violation'
| 'rate_limit_exceeded'
| 'auth_violation'
| 'oauth_state_mismatch'
| 'jwt_invalid'
| string;
export type SecuritySeverity = 'debug' | 'info' | 'warning' | 'error' | 'fatal';
export interface SecurityEventOptions {
level?: SecuritySeverity;
error?: unknown;
}
export function captureSecurityEvent(
type: SecurityEventType,
data: Record<string, unknown> = {},
options: SecurityEventOptions = {},
): void {
try {
const level: SecuritySeverity = options.level ?? 'warning';
Sentry.withScope((scope) => {
scope.setTag('security_event', type);
scope.setContext('security', data);
scope.setLevel(level);
Sentry.addBreadcrumb({
category: 'security',
level,
data: { type, ...data },
});
if (options.error !== undefined) {
Sentry.captureException(options.error, { level, extra: data });
} else {
Sentry.captureMessage(`[security] ${type}`, level);
}
});
} catch {
// no-op: telemetry must not break the app
console.error('Failed to capture security event');
}
}
export function captureException(error: Error): void {
Sentry.captureException(error);
}