bosia 0.8.12 → 0.8.13
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/package.json +1 -1
- package/src/core/csrf.ts +21 -0
- package/src/core/env.ts +1 -0
- package/src/core/server.ts +7 -0
- package/templates/default/.env.example +6 -0
- package/templates/demo/.env.example +5 -0
package/package.json
CHANGED
package/src/core/csrf.ts
CHANGED
|
@@ -10,6 +10,14 @@ export interface CsrfConfig {
|
|
|
10
10
|
checkOrigin: boolean;
|
|
11
11
|
/** Additional origins to allow (e.g. CDN or mobile app origin). */
|
|
12
12
|
allowedOrigins?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Request paths exempt from the origin check — for server-to-server webhooks
|
|
15
|
+
* that carry no Origin/Referer. Matched exact or on a path boundary
|
|
16
|
+
* ("/webhook" also covers "/webhook/…", but not "/webhooky"). Exempt routes
|
|
17
|
+
* bypass CSRF entirely, so they MUST authenticate the caller themselves
|
|
18
|
+
* (verify a webhook token/signature).
|
|
19
|
+
*/
|
|
20
|
+
exemptPaths?: string[];
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
const DEFAULT_CSRF_CONFIG: CsrfConfig = {
|
|
@@ -18,6 +26,18 @@ const DEFAULT_CSRF_CONFIG: CsrfConfig = {
|
|
|
18
26
|
|
|
19
27
|
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
20
28
|
|
|
29
|
+
// Exact match, or a prefix match on a path boundary so "/webhook" covers
|
|
30
|
+
// "/webhook/xendit" but never "/webhooky". Prefix-boundary, no globs — swap in a
|
|
31
|
+
// matcher if wildcard path segments are ever needed.
|
|
32
|
+
function isPathExempt(pathname: string, patterns: string[]): boolean {
|
|
33
|
+
for (const p of patterns) {
|
|
34
|
+
if (pathname === p) return true;
|
|
35
|
+
const prefix = p.endsWith("/") ? p : p + "/";
|
|
36
|
+
if (pathname.startsWith(prefix)) return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
/**
|
|
22
42
|
* Check whether a request passes CSRF validation.
|
|
23
43
|
* Returns `null` on success, or an error message string to reject with 403.
|
|
@@ -29,6 +49,7 @@ export function checkCsrf(
|
|
|
29
49
|
): string | null {
|
|
30
50
|
if (!config.checkOrigin) return null;
|
|
31
51
|
if (SAFE_METHODS.has(request.method.toUpperCase())) return null;
|
|
52
|
+
if (config.exemptPaths && isPathExempt(url.pathname, config.exemptPaths)) return null;
|
|
32
53
|
|
|
33
54
|
// Derive the expected origin.
|
|
34
55
|
// `X-Forwarded-*` headers are only trusted when `TRUST_PROXY=true`, since a
|
package/src/core/env.ts
CHANGED
package/src/core/server.ts
CHANGED
|
@@ -113,10 +113,12 @@ function splitCsvEnv(key: string): string[] | undefined {
|
|
|
113
113
|
// ─── CSRF Config ─────────────────────────────────────────
|
|
114
114
|
|
|
115
115
|
const _csrfAllowedOrigins = splitCsvEnv("CSRF_ALLOWED_ORIGINS");
|
|
116
|
+
const _csrfExemptPaths = splitCsvEnv("CSRF_EXEMPT_PATHS");
|
|
116
117
|
|
|
117
118
|
const CSRF_CONFIG: CsrfConfig = {
|
|
118
119
|
checkOrigin: true,
|
|
119
120
|
allowedOrigins: _csrfAllowedOrigins,
|
|
121
|
+
exemptPaths: _csrfExemptPaths,
|
|
120
122
|
};
|
|
121
123
|
|
|
122
124
|
if (_csrfAllowedOrigins?.length) {
|
|
@@ -125,6 +127,11 @@ if (_csrfAllowedOrigins?.length) {
|
|
|
125
127
|
console.log("🛡️ CSRF: same-origin only");
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
if (_csrfExemptPaths?.length) {
|
|
131
|
+
// These paths skip the origin check — they must authenticate callers themselves.
|
|
132
|
+
console.warn(`⚠️ CSRF exempt paths (must self-authenticate): ${_csrfExemptPaths.join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
128
135
|
// ─── CORS Config ──────────────────────────────────────────
|
|
129
136
|
|
|
130
137
|
const _corsAllowedOrigins = splitCsvEnv("CORS_ALLOWED_ORIGINS");
|
|
@@ -65,6 +65,12 @@ PUBLIC_STATIC_APP_NAME=My Bosia App
|
|
|
65
65
|
# Leave unset to allow same-origin requests only.
|
|
66
66
|
# CSRF_ALLOWED_ORIGINS=
|
|
67
67
|
|
|
68
|
+
# Comma-separated request paths exempt from the CSRF origin check — for
|
|
69
|
+
# server-to-server webhooks that send no Origin/Referer. Matched exact or on a
|
|
70
|
+
# path boundary ("/webhook" covers "/webhook/..." but not "/webhooky"). Exempt
|
|
71
|
+
# routes bypass CSRF, so they MUST verify the caller (webhook token/signature).
|
|
72
|
+
# CSRF_EXEMPT_PATHS=/webhook/xendit
|
|
73
|
+
|
|
68
74
|
# Comma-separated list of origins allowed to make cross-origin requests.
|
|
69
75
|
# Leave unset to disable CORS.
|
|
70
76
|
# CORS_ALLOWED_ORIGINS=
|
|
@@ -21,6 +21,11 @@ BODY_SIZE_LIMIT=512K
|
|
|
21
21
|
# Example: https://app.example.com, https://admin.example.com
|
|
22
22
|
CSRF_ALLOWED_ORIGINS=
|
|
23
23
|
|
|
24
|
+
# Comma-separated request paths exempt from the CSRF origin check — for
|
|
25
|
+
# server-to-server webhooks that send no Origin/Referer. Exempt routes MUST
|
|
26
|
+
# verify the caller themselves (webhook token/signature). Example: /webhook/xendit
|
|
27
|
+
CSRF_EXEMPT_PATHS=
|
|
28
|
+
|
|
24
29
|
# Comma-separated list of origins allowed to make cross-origin requests.
|
|
25
30
|
# Leave unset to disable CORS (browsers block cross-origin requests by default).
|
|
26
31
|
# Example: https://app.example.com, http://localhost:5173
|