Smuggling arbitrary data through an emoji
104 comments
·February 12, 2025ComputerGuru
paulgb
I just tested and private use characters render as boxes for me (), the point here was to encode them in a way that they are hidden and treated as "part of" another character when copy/pasting.
diggan
> the point here was to encode them in a way that they are hidden and treated as "part of" another character when copy/pasting
AKA "Steganography" for the curious ones: https://en.wikipedia.org/wiki/Steganography
rexxars
For a real-world use case: Sanity used this trick[0] to encode Content Source Maps[1] into the actual text served on a webpage when it is in "preview mode". This allows an editor to easily trace some piece of content back to a potentially deep content structure just by clicking on the text/content in question.
It has it's drawbacks/limitations - eg you want to prevent adding it for things that needs to be parsed/used verbatim, like date/timestamps, urls, "ids" etc - but it's still a pretty fun trick.
riskable
Oh this is just the tip of the iceberg when it comes to abusing Unicode! You can use a similar technique to this to overflow the buffer on loads of systems that accept Unicode strings. Normally it just produces an error and/or a crash but sometimes you get lucky and it'll do all sorts of fun things! :)
I remember doing penetration testing waaaaaay back in the day (before Python 3 existed) and using mere diacritics to turn a single character into many bytes that would then overflow the buffer of a back-end web server. This only ever caused it to crash (and usually auto-restart) but I could definitely see how this could be used to exploit certain systems/software with enough fiddling.
vzaliva
The title lis little misleading: "Note that the base character does not need to be an emoji – the treatment of variation selectors is the same with regular characters. It’s just more fun with emoji."
Using this approach with non-emoji characters makes it more stealth and even more disturbing.
vessenes
I love the idea of using this for LLM output watermarking. It hits the sweet spot - will catch 99% of slop generators with no fuss, since they only copy and paste anyway, almost no impact on other core use cases.
I wonder how much you’d embed with each letter or token that’s output - userid, prompt ref, date, token number?
I also wonder how this is interpreted in a terminal. Really cool!
zos_kia
With the amount of pre processing that is done before integrating stuff in a dataset I'd be surprised if those kinds of shenanigans even worked
OutOfHere
Just you wait until AI starts calling human output to be slop.
kevinsync
StegCloak [0] is in the same ballpark and takes this idea a step further by encrypting the hidden payload via AES-256-CTR -- pretty neat little trick
giancarlostoro
There's a Better Discord plugin that I think uses this or something similar, so you could send completely encrypted messages, that look like nothing to everyone else. You'd need to share a password secret for them to decode it though.
HanClinto
Even more than just simply watermarking LLM output, it seems like this could be a neat way to package logprobs data.
Basically, include probability information about every token generated to give a bit of transparency to the generation process. It's part of the OpenAI api spec, and many other engines (such as llama.cpp) support providing this information. Normally it's attached as a separate field, but there are neat ways to visualize it (such as mikupad [0]).
Probably a bad idea, but this still tickles my brain.
frontporch
you dont need 256 codepoints so you can neatly represent an octet (whatever that is), you just need 2 bits. you can just stack as many diacritical marks you want on any glyph. either the renderer allows practically unlimited or it allows 1/none. in either case that's a vuln. what would be really earth shattering is what i was hoping this article was: a way to just embed "; rm -rf ~/" into text without it being rendered. you also definitely dont need rust for this unless you want to exclude 90% of the programmer population.
paulgb
I think the Rust is more readable for bytemucking stuff than dynamic languages because the reader doesn't have to infer the byte widths, but for what it's worth the demo contains a TypeScript implementation: https://github.com/paulgb/emoji-encoder/blob/main/app/encodi...
iNic
The tokenizer catches it: https://platform.openai.com/tokenizer.
null
jerpint
The ability to add watermarks to text is really interesting. Obviously it could be worked around , but could be a good way to subtly watermark e.g. LLM outputs
tyho
There are way better ways to watermark LLM output. It's easy to make it undetectable, which this is'nt.
antognini
The issue with the standard watermark techniques is that they require an output of at least a few hundred tokens to reliably imprint the watermark. This technique would apply to much shorter outputs.
shawnz
I recently worked on a steganographics project which could be useful for this problem. See: https://github.com/shawnz/textcoder
pava0
For example?
tyho
A crude way: To watermark: First establish a keyed DRBG. For every nth token prediction: read a bit from the DRBG for every possible token to label them red/black. before selecting the next token, set the logit for black tokens to -Inf, this ensures a red token will be selected.
To detect: Establish the same DRBG. Tokenize, for each nth token, determine the red set of tokens in that position. If you only see red tokens in lots of positions, then you can be confident the content is watermarked with your key.
This would probably take a bit of fiddling to work well, but would be pretty much undetectable. Conceptually it's forcing the LLM to use a "flagged" synonym at key positions. A more sophisticated version of a shiboleth.
In practice you might chose to instead watermark all tokens, less heavy handedly (nudge logits, rather than override), and use highly robust error correcting codes.
nzach
so.... in theory you should be able to create several visually identical links that give access to different resources?
I've always assumed links without any tracking information (unique hash, query params, etc) were safe to click(with regards to my privacy). but if this works for links I may need to revise my strategy regarding how to approach links sent to me.
password4321
This tool and idea sketchy AF: https://github.com/zws-im/zws
("Shorten URLs using invisible spaces")
cscheid
My understanding is that "weird" unicode code points become https://en.wikipedia.org/wiki/Punycode. I used the 󠅘󠅕󠅜󠅜󠅟 (copy-pasted from the post, presumably with the payload in it) to type a fake domain into Chrome, and the Punycode I got appeared to not have any of the encoding bits.
However, I then pasted the emoji into the _query_ part of a URL. I pointed it to my own website, and sure enough, I can definitely see the payload in the nginx logs. Yikes.
Edit: I pasted the very same Emoji that 'paulgb used in their post before the parenthetical in the first paragraph, but it seems HN scrubs those from comments.
bmicraft
domains get "punycode" encoded, urls get "url encoded"[1], which should make unicode characters stand out. That being said, browsers do accept some non-ascii characters in urls and convert them automatially, so theoretically you could put "invalid" characters into a link and have the browser convert it only after clicking. That might be a viable strategy.
riquito
> I've always assumed links without any tracking information (unique hash, query params, etc) were safe to click(with regards to my privacy). but if this works for links I may need to revise my strategy regarding how to approach links sent to me.
Well, it was never safe, what you see and where the link are pointing at are different things, that's why the actual link is displayed at the bottom left of your browser when you move your mouse over it (or focus it via keyboard)
layer8
URIs with non-ASCII characters are technically invalid. Browsers and the like should (but likely don’t all do) percent-encode any invalid characters for display if they accept such invalid URIs.
dmbche
You need to decode the text after copy pasting it, I believe clicking on text will not interact with the obfuscated data since your computer will just find the unicode and ignore the obfuscated data.
This is just so that you can hide data and send it to someone to be decoded (or watermarking as mentionned)
nzach
yes, I understand this is not a security risk.
but my fear is precisely that I my be sending data to a remote host while I'm completely unaware of this fact.
I tried to create a POC with some popular url shortner services, but doesn't seems to work.
what I wanted to create was a link like <host.tld>/innoc󠅥󠅣󠅕󠅢󠄝󠅙󠅔󠄪󠅑󠅒󠅓ent that redirects to google.com. in this case the "c" contains some hidden data that will be sent to the server while the user is not aware. this seems possible with the correct piece of software.
cess11
HTML entity encoding will show the hidden content, try with https://mothereff.in/html-entities.
This is cute but unnecessary - Unicode includes a massive range called PUA: the private use area. The codes in this range aren’t mapped to anything (and won’t be mapped to anything) and are for internal/custom use, not to be passed to external systems (for example, we use them in fish-shell to safely parse tokens into a string, turning an unescaped special character into just another Unicode code point in the string, but in the PUA area, then intercept that later in the pipeline).
You’re not supposed to expose them outside your api boundary but when you encounter them you are prescribed to pass them through as-is, and that’s what most systems and libraries do. It’s a clear potential exfiltration avenue, but given that most sane developers don’t know much more about Unicode other than “always use Unicode to avoid internationalization issues”, it’s often left wide open.