10 ms·
Glassworm is back: A new wave of invisible Unicode attacks hits repositories
- DropDead 7mo agoWhy didn't some make av rule to find stuff like this, they are just plain text files
- abound 7mo agoYeah it would have been nice to end with "and here's a five-line shell script to check if your project is likely affected". But to their credit, they do have an open-source tool [1], I'm just not willing to install a big blob of JavaScript to look for vulns in my other big blobs of JavaScript [1] https://github.com/AikidoSec/safe-chain https://github.com/AikidoSec/safe-chain
- nine_k 7mo agoSomething like this should work, assuming your encoding is Unicode (normally UTF-8), which grep would interpret: grep -P '[\x{200B}\x{200C}\x{200D}\x{FEFF}]' code.ts See https://stackoverflow.com/q/78129129/223424 https://stackoverflow.com/q/78129129/223424
- pwoyke 7mo agoThe grep approach catches zero-width joiners and BOM characters but misses what GlassWorm uses - variation selectors (U+FE00-FE0F and U+E0100-E01EF). Those don't show up in most regex patterns people reach for, and they're valid Unicode so editors don't flag them either. ESLint won't catch it because variation selectors are legal characters - they're meant for glyph selection in CJK text and emoji. The issue is that GlassWorm uses thousands of them per line where legitimate use is 1-2. It's a density problem, not a character-class problem. We ran into this while analyzing the waves at work and ended up building a scanner around it - counts variation selector clusters per line, matches the decoder pattern (codePointAt + the specific arithmetic GlassWorm uses) in a narrow window to cut false positives from minified code. Open-sourced it last week: https://github.com/afine-com/glassworm-hunter https://github.com/afine-com/glassworm-hunter
- nine_k 7mo agoThe rule must be very simple: any occurrence of `eval()` should be a BIG RED FLAG. It should be handled like a live bomb, which it is. Then, any appearance of unprintable characters should also be flagged. There are rather few legitimate uses of some zero-width characters, like ZWJ in emoji composition. Ideally all such characters should be inserted as \xNNNN escape sequences, and not literal characters. Simple lint rules would suffice for that, with zero AI involvement.
- trollbridge 7mo agoIn our repos, we have some basic stuff like ruff that runs, and that includes a hard error on any Unicode characters. We mostly did this after some un-fun times when byte order marks somehow ended up in a file and it made something fail. I have considered allowing a short list that does not include emojis, joining characters, and so on - basically just currency symbols, accent marks, and everything else you'd find in CP-1521 but never got around to it.
- WalterBright 7mo ago> There are rather few legitimate uses of some zero-width characters, like ZWJ in emoji composition. Emojis are another abomination that should be removed from Unicode. If you want pictures, use a gif.
- sghitbyabazooka 7mo ago( ꏿ ﹏ ꏿ ; )
- _flux 7mo agoArguably them being in Unicode is an accessibility issue, unless we thought to standardize GIF names, and then that already sounds a lot like Unicode.
- WalterBright 7mo agoHow is it an accessibility issue? HTML allows things like little gif files. I've done this myself when I wrote text that contained Egyptian hieroglyphs. It works just fine!
- charcircuit 7mo agoIsn't that what this article is about? Advertising an av rule in their product that catches this.
- gnabgib 7mo agoSmall discussion yesterday (9+9 points, 9+4 comments) https://news.ycombinator.com/item?id=47374479 https://news.ycombinator.com/item?id=47374479 https://news.ycombinator.com/item?id=47385244 https://news.ycombinator.com/item?id=47385244
- robutsume 7mo ago[dead]
- minus7 7mo agoThe `eval` alone should be enough of a red flag
- kordlessagain 7mo agoNo it’s not.
- SahAssar 7mo agoIt really is. There are very few proper use-cases for eval.
- nswango 7mo agoFor a long time the standard way of loading JSON was using eval.
- _flux 7mo agoAnd why do we not anymore make use of it, but instead implemented separate JSON loading functionality in JavaScript? Can you think of any reasons beyond performance?
- bulbar 7mo agoWhy did you opt in for such a comment while a straight forward response without belittling tone would have achieved the same?
- _flux 7mo agoI actually gave it some thought. I had written the actual reason first, but I realized that the person I was responding to must know this, yet keeps arguing in that eval is just fine. I would say they are arguing that in bad faith, so I wanted to enter a dialogue where they are either forced to agree, or more likely, not respond at all.
- 7mo ago
- btown 7mo agoIMO while the bar is high to say "it's the responsibility of the repository operator itself to guard against a certain class of attack" - I think this qualifies. The same way GitHub provides Secret Scanning [0], it should alert upon spans of zero-width characters that are not used in a linguistically standard way (don't need an LLM for this, just n-tuples). Sure, third-party services like the OP can provide bots that can scan. But if you create an ecosystem in which PRs can be submitted by threat actors, part of your commitment to the community should be to provide visibility into attacks that cannot be seen by the naked eye, and make that protection the norm rather than the exception. [0] https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security https://docs.github.com/en/get-started/learning-about-github...
- andrewflnr 7mo agoRegardless of the thorny question of whether it's Github's responsibility, it sure would be a good thing for them to do ASAP.
- jacquesm 7mo agoIt absolutely is. They are simply spreading malware. You can't claim to be a 'dumb pipe' when your whole reason for existence is to make something people deemed 'too complex' simple enough for others to use, then you have an immediate responsibility to not only reduce complexity but to also ensure safety. Dumbing stuff down comes with a duty of care.
- godelski 7mo agoHere's the big reason GitHub should do it: It makes the product better I know people love to talk money and costs and "value", but HN is a space for developers, not the business people. Our primary concern, as developers, is to make the product better. The business people need us to make the product better, keep the company growing, and beat out the competition. We need them to keep us from fixating on things that are useful but low priority and ensuring we keep having money. The contention between us is good, it keeps balance. It even ensures things keep getting better even if an effective monopoly forms as they still need us, the developers, to make the company continue growing (look at monopolies people aren't angry at and how they're different). And they need us more than we need them. So I'd argue it's the responsibility of the developers, hired by GitHub, to create this feature because it makes the product better. Because that's the thing you've been hired for: to make the product better. Your concern isn't about the money, your concern is about the product. That's what you're hired for.
- faangguyindia 7mo agoBack in time I was on hacking forums where lot of script kiddies used to make malicious code. I am wondering how that they've LLM, are people using them for making new kind of malicious codes more sophisticated than before?
- Yokohiii 7mo agoIn this case LLMs were obviously used to dress the code up as more legitimate, adding more human or project relevant noise. It's social engineering, but you leave the tedious bits to an LLM. The sophisticated part is the obscurity in the whole process, not the code.
- ocornut 7mo agoIt baffles me that any maintainer would merge code like the one highlighted in the issue, without knowing what it does. That’s regardless of being or not being able to see the “invisible” characters. There’s a transforming function here and an eval() call. The mere fact that a software maintainer would merge code without knowing what it does says more about the terrible state of software.
- pdonis 7mo agoWish I could upvote this more.
- mmlb 7mo agoIn this instance the PR that was merged was from 6 years ago and was clear https://github.com/pedronauck/reworm/pull/28 https://github.com/pedronauck/reworm/pull/28. Looks to me like a force push overwrote the commit that now exists in history since it was done 6y later.
- globular-toast 7mo agoSo who force pushed and why?
- retropragma 7mo agolikely a compromised github API token
- dspillett 7mo ago> It baffles me that any maintainer would merge code like the one highlighted in the issue, without knowing what it does. I don't know if it is relevant in any specific case that is being discussed here, but if the exploit route is via gaining access to the accounts of previously trusted submitters (or otherwise being able to impersonate them) it could be a case of teams with a pile of PRs to review (many of which are the sloppy unverified LLM output that is causing a problem for some popular projects) lets through an update from a trusted source that has been compromised. It could correctly be argued that this is a problem caused by laziness and corner cutting, but it is still understandable because projects that are essentially run by a volunteer workforce have limited time resources available.
- vitus 7mo agoLooks like the repo owner force-pushed a bad commit to replace an existing one. But then, why not forge it to maintain the existing timestamp + author, e.g. via `git commit --amend -C df8c18`? Innocuous PR (but do note the line about "pedronauck pushed a commit that referenced this pull request last week"): https://github.com/pedronauck/reworm/pull/28 https://github.com/pedronauck/reworm/pull/28 Original commit: https://github.com/pedronauck/reworm/commit/df8c18 https://github.com/pedronauck/reworm/commit/df8c18 Amended commit: https://github.com/pedronauck/reworm/commit/d50cd8 https://github.com/pedronauck/reworm/commit/d50cd8 Either way, pretty clear sign that the owner's creds (and possibly an entire machine) are compromised.
- chrismorgan 7mo agoThe value of the technique, I suppose, is that it hides a large payload a bit better. The part you can see stinks (a bunch of magic numbers and eval), but I suppose it’s still easier to overlook than a 9000-character line of hexadecimal (if still encoded or even decoded but still encrypted) or stuff mentioning Solana and Russian timezones (I just decoded and decrypted the payload out of curiosity). But really, it still has to be injected after the fact. Even the most superficial code review should catch it.
- vitus 7mo agoAgreed on all those fronts. I'm just dismayed by all the comments suggesting that maintainers just merged PRs with this trojan, when the attack vector implies a more mundane form of credential compromise (and not, as the article implies, AI being used to sneak malicious changes past code review at scale).
- jeltz 7mo agoYeah, the attack vector seems to be stolen credentials. I would be much more interested in an attack which actually uses Invisible characters as the main vector.
- aneyadeng 7mo ago[dead]
- WalterBright 7mo agoUnicode should be for visible characters. Invisible characters are an abomination. So are ways to hide text by using Unicode so-called "characters" to cause the cursor to go backwards. Things that vanish on a printout should not be in Unicode. Remove them from Unicode.
- abujazar 7mo agoInvisible characters are there for visible characters to be printed correctly...
- WalterBright 7mo agoI'll grant that a space and a newline are necessary. The rest, nope.
- abujazar 7mo agoYou're talking about a subset of ASCII then. Unicode is supposed to support different languages and advanced typography, for which those characters are necessary. You can't write e.g. Arabic or Hebrew without those "unnecessary" invisible characters.
- WalterBright 7mo agoPlease explain why an invisible zero width "character" is necessary.
- slim 7mo agoif you write كلب which is an arabic word written right to left in the middle of an english sentence, you want to preserve the order of the characters in the stream for computer processing purposes. meaning the chararacter ك must come before the ل and after the e and the space with respect to the memory layout. whereas when displayed, it must be inverted to be legible. the solution is to have an invisible character that indicates a switch in text direction. if you were wondering, the situation where you want to write text in a foreign language within your text is very common outside english speaking countries.
- tolciho 7mo agoAttacks employing invisible characters are not a new thing. Prior efforts here include terminal escape sequences, possibly hidden with CSS that if blindly copied and pasted would execute who knows what if the particular terminal allowed escape sequences to do too much (a common feature of featuritis) or the terminal had errors in its invisible character parsing code. For data or code hiding the Acme::Bleach Perl module is an old example though by no means the oldest example of such. This is largely irrelevant given how relevant not learning from history is for most. Invisible characters may also cause hard to debug issues, such as lpr(1) not working for a user, who turned out to have a control character hiding in their .cshrc. Such things as hex viewers and OCD levels of attention to detail are suggested.
- aplomb1026 7mo ago[dead]
- codechicago277 7mo agoI wonder if this could be used for prompt injection, if you copy and paste the seemingly empty string into an LLM does it understand? Maybe the affect Unicode characters aren’t tokenized.
- max_ 7mo agoI don't have to worry about any of this. My clawbot & other AI agents already have this figured out. /s
- zzo38computer 7mo agoI use non-Unicode mode in the terminal emulator (and text editors, etc), I use a non-Unicode locale, and will always use ASCII for most kind of source code files (mainly C) (in some cases, other character sets will be used such as PC character set, but usually it will be ASCII). Doing this will mitigate many of this when maintaining your own software. I am apparently not the only one; I have seen others suggest similar things. (If you need non-ASCII text (e.g. for documentation) you might store them in separate files instead. If you only need a small number of them in a few string literals, then you might use the \x escapes; add comments if necessary to explain it.) The article is about in JavaScript, although it can apply to other programming languages as well. However, even in JavaScript, you can use \u escapes in place of the non-ASCII characters. (One of my ideas in a programming language design intended to be better instead of C, is that it forces visible ASCII (and a few control characters, with some restrictions on their use), unless you specify by a directive or switch that you want to allow non-ASCII bytes.)
- TacticalCoder 7mo ago> ... and will always use ASCII for most kind of source code files Same. And I enforce it. I've got scripts and hooks that enforces source files to only ever be a subset of ASCII (not even all ASCII codes have their place in source code). Unicode chars strings are perfectly fine in resource files. You can build perfectly i18n/l10n apps and webapps without ever using a single Unicode character in a source file. And if you really do need one, there's indeed ASCII escaping available in many languages. Some shall complan that their name as "Author: ..." in comments cannot be written properly in ASCII. If I wanted to be facetious I'd say that soon we'll see: # Author: Claude Opus 27.2 and so the point shall be moot anyway.
- userbinator 7mo agoCP437 forever! The biggest use of Unicode in source repos now might be LLM slop, so I certainly don't miss its absence at all.
- amake 7mo agoThat’s great for you. Isn’t feasible for software development by teams that are native in a language with a non-Latin script.
- chairmansteve 7mo agoeval() used to be evil.... Are people using eval() in production code?
- NoMoreNicksLeft 7mo agoWhy can't code editors have a default-on feature where they show any invisible character (other than newlines)? I seem to remember Sublime doing this at least in some cases... the characters were rendered as a lozenge shape with the hex value of the character. Is there ever a circumstance where the invisible characters are both legitimate and you as a software developer wouldn't want to see them in the source code?
- ted_dunning 7mo agoCheck out emacs for options like this. And, yes, there is a circumstance if you want to include Arabic or Hebrew in comments or strings. You need the zero width left-right markers to make that work.
- mhitza 7mo agoTheir button animations almost "crash" Firefox mobile. As soon as I reach them the entire page scrolls at single digit FPS.
- bawolff 7mo agoI feel like the threat of this type of thing is really overstated. Sure the payload is invisible (although tbh im surprised it is. PUA characters usually show up as boxes with hexcodes for me), but the part where you put an "empty" string through eval isn't. If you are not reviewing your code enough to notice something as non sensical as eval() an empty string, would you really notice the non obfuscated payload either?
- loumf 7mo agoThe threat is that you depend on this library or use the VS Code Extension.
- Arrowmaster 7mo agoHonestly I was expecting more. There are many languages that support Unicode in variable or function names and I expected it to be used there. It sounds like Python only allows approved Unicode characters to start a variable name but if it allowed any you could do something like `nonprintable = lambda x: insert exploit code here`. If that was hidden in what looked like a blank line between other additions would you catch it? I'm sure there's some other language out there that has similar syntax and lax Unicode rules this could be used in. The solution is that this and many other Unicode formatting characters should be ignored and converted to a visible indicator in all code views when you expect plain text.
- bawolff 7mo ago> The solution is that this and many other Unicode formatting characters This isn't about formating characters, this is about private use characters.
- like_any_other 7mo agoInvisible characters, lookalike characters, reversing text order attacks [1].. the only way to use unicode safely seems to be by whitelisting a small subset of it. And please, everyone arguing the code snippet should never have passed review - do you honestly believe this is the only kind of attack that can exploit invisible characters? [1] https://attack.mitre.org/techniques/T1036/002/ https://attack.mitre.org/techniques/T1036/002/
- hananova 7mo agoMy hot take is that all programming languages should go back to only accepting source code saved in 7-bit ASCII. With perhaps an exception for comments.
- krior 7mo agoYeah, fuck those non-english-speaking peasants /s.
- hananova 7mo agoI'm a non-english-speaking peasant. I code in English, because it's the lingua franca of coding, and because they form the only characters that you can reliably use everywhere. Besides, that's why the ban only extends to syntax and string literals (use escapes instead), and not comments. From my experience, the only two nationalities that insist on mixing their native languages with the mostly English syntax of programming languages are the French and the Japanese. And they can just suck it up for the other 8 billion of us.
- robutsume 7mo ago[dead]
- rvnx 7mo agoThis shows the failure of human reviews alone, an LLM-based reviewer would have caught it. Both approaches are complementary
- rhysfonixone 7mo agoExactly this. I think a hybrid approach is going to be mandatory before long, if it's not already. A well-prompted frontier-lab LLM would catch things like this easily.
- anesxvito 7mo ago[dead]
- invalidusernam3 7mo agoeval is the major red flag here
- Marsymars 7mo agoNew repo, but seems configurable to do what you're asking for: https://github.com/mit-d/check-unicode https://github.com/mit-d/check-unicode Though also seems relatively straightforward to code up in your scripting language of choice to call from pre-commit.
- mmsc 7mo agoGitHub advertises itself as warning about those Unicode characters: https://github.blog/changelog/2025-05-01-github-now-provides-a-warning-about-hidden-unicode-text/ https://github.blog/changelog/2025-05-01-github-now-provides... Of course, it doesn't work though. I reported this to their bug bounty, they paid me a bounty, and told me "we won't be fixing it": https://joshua.hu/2025-bug-bounty-stories-fail#githubs-utf-filter-warning https://joshua.hu/2025-bug-bounty-stories-fail#githubs-utf-f... The exact quote is "Thanks for the submission! We have reviewed your report and validated your findings. After internally assessing your report based on factors including the complexity of successfully exploiting the vulnerability, the potential data and information exposure, as well as the systems and users that would be impacted, we have determined that they do not present a significant security risk to be eligible under our rewards structure." The funny thing is, they actually gave me $500 and a lifetime GitHub Pro for the submission.
- iam_circuit 7mo ago[dead]
- stainlu 7mo ago[flagged]
- herpdyderp 7mo agoI keep seeing this and wondering if the ESLint default rules against weird characters would catch this? But I can’t figure out how to check.
- CGamesPlay 7mo agoAppears not to. https://claude.ai/share/ac070cf5-0034-4f3c-9a8c-1c43a58eea36 https://claude.ai/share/ac070cf5-0034-4f3c-9a8c-1c43a58eea36 Claude’s analysis seems solid here based on reading the snippets it tested. A purpose-built linter could be cross-language, it’s pretty reasonable to blanket ban these characters entirely, or at least allowlist them.
- nstart 7mo agoI don't quite understand how this is working tbh. I looked at one of the affected repos, ironically named "reworm". The malicious code was introduced in this commit - https://github.com/pedronauck/reworm/commit/d50cd8c8966893c6269153a3c093c801fd62ba16 https://github.com/pedronauck/reworm/commit/d50cd8c8966893c6... It says coauthored by dependabot and refers to a PR opened in 2020 (https://github.com/pedronauck/reworm/pull/28 https://github.com/pedronauck/reworm/pull/28). That PR itself was merged in 2020 here - https://github.com/pedronauck/reworm/commit/df8c1803c519f599c3b61abd6613c0f98ab44fa4 https://github.com/pedronauck/reworm/commit/df8c1803c519f599... But the commit with the worm (d50cd8c), re-introduces the same change from df8c180 to the file `yarn.lock`. And when you look at the history of yarn.lock inside of github, all references to the original version bump (df8c180) are gone...? In fact if you look at the overall commit history, the clean df8c180 commit does not exist. I'm struggling to understand what kind of shenanigans happened here exactly.
- RVuRnvbM2e 7mo agoSomeone has maintainer/admin access to the repository and has force-pushed to master overwriting the git history. Notice that the original commit is verified: https://github.com/pedronauck/reworm/commit/df8c1803c519f599c3b61abd6613c0f98ab44fa4 https://github.com/pedronauck/reworm/commit/df8c1803c519f599... While the malicious one is not: https://github.com/pedronauck/reworm/commit/d50cd8c8966893c6269153a3c093c801fd62ba16 https://github.com/pedronauck/reworm/commit/d50cd8c8966893c6...
- globular-toast 7mo agoThis reveals a deeper flaw in the whole git/npm pipeline (would apply to other systems like PyPI etc, not npm exclusively). These systems should operate on a "pull" model, not a push. The system should have rejected a build that wasn't derived from the latest in its repository. It would be quite easy in concept to set up one's own system to pull every source on npm and alert when the upstream has deviated.
- redman25 7mo ago
- diven_rastdus 7mo ago[dead]
- efilife 7mo agoAI comment
- nulltrace 7mo agoGrepping your own source for variation selectors is the easy part. The problem is nobody's doing that on what they install. A compromised upstream package lands those characters in your node_modules and your CI never looks twice. `npm audit signatures` catches some supply chain stuff but not this. Honestly surprised no package manager has a "scan installed files for suspicious Unicode" step yet.
- P-MATRIX 7mo agoThis gets a lot worse when a coding agent is in the loop. A human at least has a review step—an autonomous agent that reads a Glassworm-infected file just acts on it. The fix probably needs to happen at the tool result layer, before the payload ever enters the agent's context, not just on what the agent writes out.
- NooneAtAll3 7mo ago[dead]
- retropragma 7mo agoIf anyone's curious what the malware does: https://pastebin.com/raw/KiuwueMU https://pastebin.com/raw/KiuwueMU Looks like it's pilfering Solana wallets that don't belong to Russians.