<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Insomniac&#39;s Log</title><link>https://duti.dev/</link><description></description><generator>Gozer</generator><lastBuildDate>Mon, 11 May 2026 20:08:54 +0100</lastBuildDate><item><title>[Incomplete] On the Privacy of Apple Location Services &amp; Analytics</title><link>https://duti.dev/blog/2026/location-services/</link><description>&lt;blockquote&gt;&#xA;&lt;p&gt;Note: This was written late 2025 with the expectation that I will complete&#xA;this research at some point. I now realize I&#39;ll probably never have the time&#xA;to finish it, and am therefore releasing it now.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;#user-relevant-info&#34;&gt;Skip technical stuff&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Btw this is a sequel to some of my prior research on how the location services&#xA;work. See&#xA;&lt;a href=&#34;https://github.com/acheong08/apple-corelocation-experiments/&#34;&gt;https://github.com/acheong08/apple-corelocation-experiments/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;Contrary to popular belief, GPS is no longer the primary method mobile devices&#xA;use to determine location.&lt;/p&gt;&#xA;&lt;p&gt;Instead, companies like Google and Apple maintain massive databases of Wi-Fi&#xA;hotspots and cell towers. Phones collect signals from these beacons, including&#xA;strength and identifiers, and use them to triangulate their position, with the&#xA;help of data provided by these vendors.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://r2.duti.dev/blog/images/trilateration.png&#34; alt=&#34;Trilateration algorithm for n points&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;To build these databases, iOS and Android devices act as passive wardrivers:&#xA;they continuously report nearby access points to Apple and Google. This data is&#xA;then aggregated across countless devices to determine the locations of access&#xA;points with high accuracy.&lt;/p&gt;&#xA;&lt;p&gt;A recent paper,&#xA;&lt;a href=&#34;https://www.cs.umd.edu/~dml/papers/wifi-surveillance-sp24.pdf&#34;&gt;&amp;quot;Surveilling the Masses with Wi-Fi-Based Positioning Systems&amp;quot;&lt;/a&gt;&#xA;(May 2024), explores how Apple’s location services can be weaponized to track&#xA;movements worldwide, particularly in sensitive contexts like war zones and&#xA;natural disasters.&lt;/p&gt;&#xA;&lt;p&gt;I found the paper fascinating. On the same day it was published, I began reverse&#xA;engineering the &lt;code&gt;clls/wloc&lt;/code&gt; endpoint using definitions decompiled from&#xA;&lt;code&gt;CoreLocationProtobuf.framework&lt;/code&gt;. Over the following weeks, I uncovered&#xA;additional endpoints, such as:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;wifi_request_tile&lt;/code&gt;: retrieves BSSIDs in a given area via an obfuscated tile&#xA;key&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;hcy/pbcwloc&lt;/code&gt;: uploads data about newly discovered endpoints&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;By combining the &lt;code&gt;tile&lt;/code&gt; endpoint with an expanding search algorithm layered on&#xA;top of &lt;code&gt;wloc&lt;/code&gt;, I was able to reproduce the dataset described in the paper within&#xA;a single day.&lt;/p&gt;&#xA;&lt;p&gt;With the external dataset re-created, the next question was: &lt;em&gt;how much data is&#xA;actually being sent to Apple from devices in the first place?&lt;/em&gt;&lt;br&gt;&#xA;I’m writing this from the University of Cambridge, where I’m spending three&#xA;weeks on a research project exploring exactly that.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2&gt;Reverse Engineering the Protocols&lt;/h2&gt;&#xA;&lt;p&gt;Apple relies on a custom RPC format known as &lt;strong&gt;ARPC&lt;/strong&gt;. Unlike typical&#xA;self-describing formats, ARPC does not embed enough information to fully decode&#xA;requests and responses on its own.&lt;/p&gt;&#xA;&lt;p&gt;A standard &lt;strong&gt;request&lt;/strong&gt; has the following structure:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Field&lt;/th&gt;&#xA;&lt;th&gt;Size&lt;/th&gt;&#xA;&lt;th&gt;Type&lt;/th&gt;&#xA;&lt;th&gt;Description&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Version&lt;/td&gt;&#xA;&lt;td&gt;2 bytes&lt;/td&gt;&#xA;&lt;td&gt;uint16&lt;/td&gt;&#xA;&lt;td&gt;Protocol version&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Locale&lt;/td&gt;&#xA;&lt;td&gt;variable&lt;/td&gt;&#xA;&lt;td&gt;pascal string&lt;/td&gt;&#xA;&lt;td&gt;Locale string&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;AppIdentifier&lt;/td&gt;&#xA;&lt;td&gt;variable&lt;/td&gt;&#xA;&lt;td&gt;pascal string&lt;/td&gt;&#xA;&lt;td&gt;Application identifier string&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;OsVersion&lt;/td&gt;&#xA;&lt;td&gt;variable&lt;/td&gt;&#xA;&lt;td&gt;pascal string&lt;/td&gt;&#xA;&lt;td&gt;OS version string&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;FunctionId&lt;/td&gt;&#xA;&lt;td&gt;4 bytes&lt;/td&gt;&#xA;&lt;td&gt;uint32&lt;/td&gt;&#xA;&lt;td&gt;Function ID&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Payload Length&lt;/td&gt;&#xA;&lt;td&gt;4 bytes&lt;/td&gt;&#xA;&lt;td&gt;uint32&lt;/td&gt;&#xA;&lt;td&gt;Length of payload&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Payload&lt;/td&gt;&#xA;&lt;td&gt;variable&lt;/td&gt;&#xA;&lt;td&gt;bytes&lt;/td&gt;&#xA;&lt;td&gt;Payload data&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Responses are trickier: they can contain an arbitrary number of fields of&#xA;unknown sizes. To parse them, one can exploit the fact that the &lt;strong&gt;payload&#xA;length&lt;/strong&gt; encodes the number of bytes from its position to the end of the&#xA;message. Practically, this means scanning the response with a sliding 4-byte&#xA;window, checking if each candidate encodes a valid remaining length. If multiple&#xA;candidates match, the largest valid body size and corresponding header size are&#xA;chosen.&lt;/p&gt;&#xA;&lt;h2&gt;Working Out the Protobuf&lt;/h2&gt;&#xA;&lt;p&gt;The payload itself can contain any arbitrary bytes, including encrypted blobs -&#xA;but in most observed cases it uses &lt;strong&gt;protobuf&lt;/strong&gt;. The next challenge is&#xA;discovering the protobuf definitions.&lt;/p&gt;&#xA;&lt;p&gt;Tools like &lt;a href=&#34;https://github.com/arkadiyt/protodump&#34;&gt;protodump&lt;/a&gt; can extract&#xA;protobuf file descriptors from raw binaries, but they don’t work with Apple’s&#xA;custom compiler, which converts protobuf definitions directly to Objective-C.&lt;/p&gt;&#xA;&lt;p&gt;Most of the protobuf decoding logic isn’t located in the &lt;code&gt;locationd&lt;/code&gt;, &lt;code&gt;geod&lt;/code&gt;, or&#xA;&lt;code&gt;geoanalyticsd&lt;/code&gt; binaries themselves. Instead, it resides in private frameworks&#xA;inside the dyld cache. To extract them, use&#xA;&lt;a href=&#34;https://github.com/keith/dyld-shared-cache-extractor&#34;&gt;dyld-shared-cache-extractor&lt;/a&gt;.&#xA;To fetch the filesystem image for iOS, use&#xA;&lt;a href=&#34;https://github.com/blacktop/ipsw&#34;&gt;ipsw&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ipsw download ipsw --version 18.6.2 --device iPhone15,2&#xA;ipsw extract --files --pattern &amp;quot;.*&amp;quot; iPhone15,2_18.6.2_22G100_Restore.ipsw&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;On macOS, the iOS Simulator runs its system binaries natively, which allows&#xA;attaching &lt;code&gt;lldb&lt;/code&gt;, but only with SIP (System Integrity Protection) disabled.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ps aux | grep simruntime | grep locationd   # Find the PID of locationd running within the simulator&#xA;sudo lldb -p &amp;lt;pid&amp;gt;                          # Attach to process (requires SIP disabled)&#xA;br set -n &amp;quot;-[NSURLSessionTask resume]&amp;quot;      # Set breakpoint on network requests&#xA;po [$x0 originalRequest]                    # Inspect request details (e.g. URL)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The next step is mapping addresses in &lt;strong&gt;Ghidra&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;To find the base load address, run:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;image list &amp;lt;binary name, e.g. geoanalyticsd&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then, from the backtrace, take the top frame address that matches your binary.&#xA;Subtract the base load address to get a file offset, then add &lt;code&gt;0x100000000&lt;/code&gt; to&#xA;obtain the virtual address expected by Ghidra.&lt;/p&gt;&#xA;&lt;p&gt;Example:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;p/x 0x1048ddda0, 0x1048d4000 = 0x9da0&#xA;p/x 0x100000000 + 0x9da0      = 0x100009da0&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Finally, in Ghidra: &lt;code&gt;Navigation &amp;gt; Go To&lt;/code&gt; → paste the calculated address.&lt;/p&gt;&#xA;&lt;img alt=&#34;Screenshot of decompiled C for wloc protobuf&#34; src=&#34;https://r2.duti.dev/blog/images/reversed-wloc-protoc.png&#34; style=&#34;max-height: 40rem;&#34;/&gt;&#xA;&lt;p&gt;Using the index numbers and associated symbols, the protobuf definition can then&#xA;be manually reconstructed. With protobuf definitions in hand, we can now find&#xA;exactly what data is getting sent off to Apple.&lt;/p&gt;&#xA;&lt;h2&gt;&lt;span id=&#34;user-relevant-info&#34;&gt;Privacy Settings and Observed Traffic&lt;/span&gt;&lt;/h2&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Long story short: under &lt;strong&gt;Privacy &amp;amp; Security &amp;gt; Location Services &amp;gt; System&#xA;Services&lt;/strong&gt;, it’s worth turning off at least the following options:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Routing &amp;amp; Traffic&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Apple Pay Merchant Identification&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Improve Maps&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;iPhone Analytics&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h3&gt;So what is collected?&lt;/h3&gt;&#xA;&lt;p&gt;Lets start off with what Apple claims.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Routing &amp;amp; Traffic&lt;/em&gt;&lt;/strong&gt;: While you are in &lt;strong&gt;transit&lt;/strong&gt; (for example, walking or&#xA;driving), your iPhone will periodically send &lt;strong&gt;GPS data, travel speed and&#xA;direction, and barometric pressure&lt;/strong&gt; information in an anonymous and encrypted&#xA;form to Apple... Additionally, when you &lt;strong&gt;open an app&lt;/strong&gt; near a &lt;strong&gt;point of&#xA;interest&lt;/strong&gt; (for example, a business or park) your iPhone will send &lt;strong&gt;location&#xA;data&lt;/strong&gt; in an &lt;strong&gt;anonymous and encrypted&lt;/strong&gt; form...&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Apple Pay Merchant Identification&lt;/em&gt;&lt;/strong&gt;: Your iPhone will use your &lt;strong&gt;current&#xA;location&lt;/strong&gt; to help provide more accurate &lt;strong&gt;merchant names&lt;/strong&gt; when you use your&#xA;physical Apple Card.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Improve Maps&lt;/em&gt;&lt;/strong&gt;: Apple will collect the GPS coordinates obtained through&#xA;the &lt;strong&gt;Significant Locations&lt;/strong&gt; feature on your device and correlate them with&#xA;the &lt;strong&gt;street address associated with your Apple Account&lt;/strong&gt;... Your iOS, iPadOS,&#xA;or visionOS device will also periodically send &lt;strong&gt;locations of where and when&#xA;you launched apps&lt;/strong&gt;, including the &lt;strong&gt;name of the apps&lt;/strong&gt;, in an anonymous and&#xA;encrypted form to Apple in order to improve Maps and other Apple&#xA;location-based products and services.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;So in summary, GPS location, transaction information from NFC, your street&#xA;address even if not present, and when &amp;amp; where apps were launched. While this&#xA;isn&#39;t great, the fact that it is &amp;quot;anonymous&amp;quot;, &amp;quot;encrypted&amp;quot;, and periodic should&#xA;hopefully make traffic impossible to tie to an identity.&lt;/p&gt;&#xA;&lt;p&gt;Of course words mean nothing without data. Lets make use of the reverse&#xA;engineering and decode what Apple is sending off.&lt;/p&gt;&#xA;&lt;h3&gt;Data Collection and Analysis&lt;/h3&gt;&#xA;&lt;p&gt;To analyze Apple&#39;s location service traffic, I intercepted HTTPS requests from&#xA;an iPhone running IOS 18.6.2 with default settings over the course of 7 days&#xA;using &lt;code&gt;mitmproxy&lt;/code&gt;&#39;s WireGuard mode. The device was used as a normal daily driver&#xA;(apps opened, routes walked, payments made).&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;mitmweb --web-host 100.64.0.7 --mode wireguard --set allow_hosts=&amp;quot;mitm\.it|.*\.ls\.apple\.com|gs-loc\.apple\.com&amp;quot; --listen-host 167.99.85.207 -w apple.flow --set view_filter=&amp;quot;mitm\.it|.*\.ls\.apple\.com|gs-loc\.apple\.com&amp;quot;&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Efforts were made to ensure only traffic for location services were captured and&#xA;to prevent decryption of irrelevant data.&lt;/p&gt;&#xA;&lt;h3&gt;Routing &amp;amp; Traffic&lt;/h3&gt;&#xA;&lt;p&gt;The primary endpoints associated with this setting are:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;https://gsp10-ssl.apple.com/pds/pd&lt;/code&gt;. Contains a large array of a structure&#xA;containing GPS coordinates, timestamps, and other sensor measurements.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;https://gsp10-ssl.apple.com/au&lt;/code&gt;. Contains a list of app bundle identifiers,&#xA;GPS coordinates, and timestamps&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Between 3 and 4 requests were observed per day varying from 1.5kb to 124kb in&#xA;size, directly corresponding to the number of steps taken since the previous&#xA;request&lt;/li&gt;&#xA;&lt;li&gt;The GPS locations are highly accurate and not obfuscated with technology like&#xA;differential privacy. I could spot the exact table I sat at events and the&#xA;general room I stayed in.&lt;/li&gt;&#xA;&lt;li&gt;Requests to the 2 endpoints are usually made within milliseconds of each&#xA;other.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Data is collected, aggregated, and sent in batches, tying lots of unique data&#xA;points together.&lt;/p&gt;&#xA;&lt;img src=&#34;https://r2.duti.dev/blog/images/pds-transit-map2.png&#34; width=&#34;400&#34; alt=&#34;Map of me walking home after an event&#34;&gt;&#xA;&lt;img src=&#34;https://r2.duti.dev/blog/images/au-app-map.png&#34; width=&#34;500&#34; alt=&#34;Map of open app locations&#34;&gt;&lt;img src=&#34;https://r2.duti.dev/blog/images/pds-raw-request.png&#34; width=&#34;400&#34; alt=&#34;Screenshot of raw request showing fingerprintable information&#34;&gt;&#xA;&lt;p&gt;The map shows just a single request, really visualizing how densely the points&#xA;are packed.&lt;/p&gt;&#xA;&lt;p&gt;The requests also include device fingerprints (locale, OS version) and a unique&#xA;UUID.&lt;/p&gt;&#xA;&lt;p&gt;In terms of timing, requests tend to be sent while the phone is plugged in and&#xA;idle. Oddly, requests also occur to be triggered when the alarm goes off.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;TODO: Get a rooted phone and ripgrep through where that ID may be stored to be&#xA;tied back to request&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h3&gt;Apple Pay Merchant Information&lt;/h3&gt;&#xA;&lt;p&gt;The only associated endpoint is &lt;code&gt;https://gsp-ssl.ls.apple.com/dispatcher.arpc&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;Decoded data from a collected request:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;6&amp;quot;: {&#xA;  &amp;quot;card_type&amp;quot;: &amp;quot;MasterCard&amp;quot;,&#xA;  &amp;quot;currency&amp;quot;: &amp;quot;EUR&amp;quot;,&#xA;  &amp;quot;16&amp;quot;: 1,&#xA;  &amp;quot;17&amp;quot;: &amp;quot;8D0DDB68-0C2D-4A39-AD20-77454B02D876&amp;quot;,&#xA;  &amp;quot;18&amp;quot;: 0,&#xA;  &amp;quot;merchant&amp;quot;: &amp;quot;2TL BRUSSEL, NOORD&amp;quot;,&#xA;  &amp;quot;21&amp;quot;: &amp;quot;&amp;quot;,&#xA;  &amp;quot;timestamp&amp;quot;: 4739811754110877696,&#xA;  &amp;quot;8&amp;quot;: 0,&#xA;  &amp;quot;9&amp;quot;: 1&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Card provider&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Currency&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Transaction ID&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Merchant name&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Timestamp&lt;/p&gt;&#xA;&lt;p&gt;Surprisingly, disabling the toggle only stops &lt;strong&gt;uploading&lt;/strong&gt; the data, not&#xA;&lt;strong&gt;collection&lt;/strong&gt;. Data is stored locally until you re-enable the setting, at&#xA;which point it’s uploaded. In this case, the timestamp (4739811754110877696 →&#xA;Aug 30, 2025) is from a week before September 6th when this request was&#xA;observed. All analytics settings were turned off at the time.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3&gt;Improve Maps &amp;amp; iPhone Analytics&lt;/h3&gt;&#xA;&lt;p&gt;Endpoint: &lt;code&gt;https://gsp64-ssl.ls.apple.com/hvr/v3/use&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;This endpoint doesn’t use protobuf, so decoding is limited to string analysis.&#xA;Observed payloads include:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Subsets of open apps (Network extensions like WireGuard, Mullvad, Little&#xA;Snitch were observed)&lt;/li&gt;&#xA;&lt;li&gt;Addresses linked to the Apple account even if not currently nearby. For&#xA;example, I observed multiple Cardiff addresses despite being in Cambridge.&#xA;Searching through the phone reveals that they come from the Contacts app.&lt;/li&gt;&#xA;&lt;li&gt;IPs and ports the device is connected to&lt;/li&gt;&#xA;&lt;li&gt;BSSIDs&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;With settings disabled, requests are still sent, but reduced to just home&#xA;location and timestamps.&lt;/p&gt;&#xA;&lt;h3&gt;&lt;span id=&#34;endpoint-summary&#34;&gt;Endpoint summary&lt;/span&gt;&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Domain&lt;/th&gt;&#xA;&lt;th&gt;Path(s)&lt;/th&gt;&#xA;&lt;th&gt;Controlled by Setting&lt;/th&gt;&#xA;&lt;th&gt;Data Sent&lt;/th&gt;&#xA;&lt;th&gt;Notes&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gsp10-ssl.apple.com&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;code&gt;/pds/pd&lt;/code&gt;, &lt;code&gt;/au&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;strong&gt;Routing &amp;amp; Traffic&lt;/strong&gt;&lt;/td&gt;&#xA;&lt;td&gt;GPS coordinates, travel speed, barometric pressure, app launches near POIs&lt;/td&gt;&#xA;&lt;td&gt;Sent continuously; “anonymous” but easily linkable via app/device data&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gsp-ssl.ls.apple.com&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;code&gt;/dispatcher.arpc&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;strong&gt;Apple Pay Merchant Identification&lt;/strong&gt;&lt;/td&gt;&#xA;&lt;td&gt;Card provider, currency, transaction ID, merchant name, timestamp&lt;/td&gt;&#xA;&lt;td&gt;Data still collected locally when disabled; uploaded once re-enabled&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gsp64-ssl.ls.apple.com&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;code&gt;/hvr/v3/use&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;strong&gt;Improve Maps &amp;amp; iPhone Analytics&lt;/strong&gt;&lt;/td&gt;&#xA;&lt;td&gt;Open apps (esp. network tools), Apple account–linked addresses, IPs and ports, BSSIDs, timestamps&lt;/td&gt;&#xA;&lt;td&gt;Requests persist even when disabled, but with reduced detail&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;&lt;code&gt;gsp10-ssl.apple.com&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;code&gt;/hcy/pbcwloc&lt;/code&gt;&lt;/td&gt;&#xA;&lt;td&gt;&lt;strong&gt;Improve Location Services&lt;/strong&gt;&lt;/td&gt;&#xA;&lt;td&gt;Nearby BSSIDs, cell provider, location, movement/activity&lt;/td&gt;&#xA;&lt;td&gt;Passive Wi-Fi/cell scanning; builds Apple’s positioning database&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3&gt;When and where is location data collected?&lt;/h3&gt;&#xA;&lt;p&gt;Apple’s terms use vague phrases like &lt;em&gt;“in transit”&lt;/em&gt; and &lt;em&gt;“points of interest.”&lt;/em&gt;&#xA;In practice, these cover nearly all daily activity:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Transit&lt;/strong&gt; applies whenever your phone is moving, whether you’re walking,&#xA;cycling, or driving.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Points of interest&lt;/strong&gt; applies whenever you stop somewhere with a map label -&#xA;your home, workplace, shops, restaurants, and so on.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The result is that your iPhone sends location data whether you’re moving or&#xA;standing still, essentially &lt;strong&gt;all the time&lt;/strong&gt;. During testing, I saw logs for&#xA;every commute, every stop at a shop or restaurant, and every app I opened&#xA;throughout the week.&lt;/p&gt;&#xA;&lt;h3&gt;Cross-request correlation and deanonymization&lt;/h3&gt;&#xA;&lt;p&gt;Apple’s traffic is highly susceptible to correlation across requests. Analytics&#xA;data is usually sent in batches by background daemons, which means requests&#xA;often share near-identical timestamps. For example, &lt;code&gt;/pds/pd&lt;/code&gt; and &lt;code&gt;/au&lt;/code&gt; are&#xA;almost always transmitted within milliseconds of each other, and their GPS&#xA;coordinates and timestamps can be matched to link app usage with a specific&#xA;route.&lt;/p&gt;&#xA;&lt;p&gt;Beyond timing, every request carries device fingerprints, locale, iOS version,&#xA;user agent, and IP address. Combined with the set of installed apps (which Apple&#xA;already knows for each account), these details form a unique device profile.&#xA;Even if Apple labels traffic as &lt;em&gt;“anonymous,”&lt;/em&gt; the mix of app usage, device&#xA;metadata, and network identifiers makes it straightforward to connect requests&#xA;back to an individual user.&lt;/p&gt;&#xA;&lt;h2&gt;Privacy implications and adversaries&lt;/h2&gt;&#xA;&lt;h3&gt;Passive network observer (ISP, VPN provider, DNS resolver)&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Can see &lt;strong&gt;frequency, timing, and size&lt;/strong&gt; of requests, though the payload is&#xA;encrypted.&lt;/li&gt;&#xA;&lt;li&gt;Because requests are usually sent when the phone is &lt;strong&gt;plugged in and idle&lt;/strong&gt;,&#xA;an observer can infer certain device states from traffic patterns. Over time,&#xA;this rhythm reveals aspects of the user’s daily habits.&lt;/li&gt;&#xA;&lt;li&gt;Request size also correlates with movement: the more you travel, the larger&#xA;the batch of location points, and thus the larger the encrypted request.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Overall, Apple’s choice to delay and batch uploads reduces what passive&#xA;observers can infer in real time, but longer-term patterns still leak useful&#xA;information.&lt;/p&gt;&#xA;&lt;h3&gt;Active network observer (e.g. corporate VPN with MITM)&lt;/h3&gt;&#xA;&lt;p&gt;An active network observer, such as a corporate VPN that performs MITM&#xA;decryption, gains visibility comparable to Apple itself while also linking the&#xA;traffic directly to employee identities.&lt;/p&gt;&#xA;&lt;p&gt;The main risk lies in &lt;strong&gt;aggregation&lt;/strong&gt;: routes and app launches recorded at home&#xA;may not be uploaded immediately but instead transmitted hours later during work&#xA;hours when the VPN is active. Combined across multiple employees, this delayed&#xA;reporting can reveal private relationships, conversations, or off-record&#xA;activities.&lt;/p&gt;&#xA;&lt;h3&gt;Governments and Apple&lt;/h3&gt;&#xA;&lt;p&gt;Consider a scenario: after an attack, investigators want to know who was&#xA;present. They could compel Apple to provide all location-service requests from&#xA;the area, along with the source IP addresses. With those, they can also obtain&#xA;authenticated traffic sent from the same IPs, building a list of potential&#xA;suspects.&lt;/p&gt;&#xA;&lt;p&gt;That dataset would contain:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Device metadata from ARPC requests (locale, iOS version, etc.).&lt;/li&gt;&#xA;&lt;li&gt;Routes taken, technically “anonymous,” but timestamped and precise.&lt;/li&gt;&#xA;&lt;li&gt;Lists of apps opened at specific locations.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;By aligning timing between (2) and (3), investigators can link routes to app&#xA;usage. Then, by cross-referencing with the list of installed apps Apple already&#xA;knows per user, they can map (1) to (3) and deanonymize individuals. The&#xA;reconstructed routes can also point to private addresses, workplaces, or&#xA;safehouses.&lt;/p&gt;&#xA;&lt;p&gt;In the hands of an authoritarian state, the same process could be used to&#xA;monitor everyone who attended a protest, then trace each person back to their&#xA;home. Hence the often-heard advice: &lt;em&gt;“Don’t bring your phone to&#xA;demonstrations.”&lt;/em&gt;&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TODO&lt;/strong&gt;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Cross service correlation: Other Apple system services, possibly&#xA;authenticated, are sent from the same IP address and may contain information&#xA;that could be matched to the &amp;quot;anonymous&amp;quot; data.&lt;/li&gt;&#xA;&lt;li&gt;App uniqueness: Poll on app-install combinations to demonstrate how uniquely&#xA;idenntifying they are within a given population or IP range.&lt;/li&gt;&#xA;&lt;li&gt;Perspective of a network adversary: How much can an ISP glean from use the&#xA;traffic patterns and sizes. Train a ML model. (DEAD! ML model doesn&#39;t work&#xA;for motion activity due to batching and delays. Instead, we can tell &lt;em&gt;how&#xA;much&lt;/em&gt; a user has moved)&lt;/li&gt;&#xA;&lt;li&gt;Match paths together based on start/end points for a multi-day movement&#xA;report&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2&gt;[WIP] Update patterns of Apple&#39;s location database&lt;/h2&gt;&#xA;&lt;p&gt;Over the course of a week, I recorded &lt;strong&gt;436,771 location changes&lt;/strong&gt; and &lt;strong&gt;10,301&#xA;unique BSSIDs&lt;/strong&gt; across 10 evenly distributed groups of 5 tile keys.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Average distance change:&lt;/strong&gt; 0.69 m&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Minimum distance:&lt;/strong&gt; 0.01 m&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Maximum distance:&lt;/strong&gt; 24.87 m&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Standard deviation:&lt;/strong&gt; 1.01 m&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;img alt=&#34;Average distance moved over time&#34; src=&#34;https://r2.duti.dev/blog/images/distance_changes_plot.png&#34; style=&#34;max-height:30rem;&#34; /&gt;&#xA;&lt;p&gt;At first glance, the average distance and tight standard deviation seem&#xA;artificial. Digging deeper, the update behavior looks even stranger:&lt;/p&gt;&#xA;&lt;img alt=&#34;Distribution of time between updates&#34; src=&#34;https://r2.duti.dev/blog/images/unique_locations_analysis.png&#34; style=&#34;max-height:20rem;&#34;/&gt;&#xA;&lt;p&gt;Most changes involved oscillating between just a handful of unique coordinates&#xA;(typically 1–6 based on the number of days of recorded data), even though some&#xA;access points showed up to 93 separate “update” events. This strongly suggests&#xA;that only &lt;strong&gt;one genuine update occurs per day&lt;/strong&gt;, with locations oscillating&#xA;during the update window rather than reflecting real movement.&lt;/p&gt;&#xA;&lt;img alt=&#34;Distribution of time between updates&#34; src=&#34;https://r2.duti.dev/blog/images/update_intervals_analysis.png&#34; style=&#34;max-height:20rem;&#34;/&gt;&#xA;&lt;p&gt;Every day, there is a consistent 5-hour update window, from &lt;strong&gt;05:00 to 09:00&#xA;UTC&lt;/strong&gt;, during which database entries are refreshed. Each access point is&#xA;guaranteed at least one update every 24 hours, though many are updated multiple&#xA;times within the window. This clustering explains the bursts of short intervals&#xA;seen in the update graphs.&lt;/p&gt;&#xA;&lt;p&gt;Importantly, the update windows are not isolated to specific tiles. Instead,&#xA;updates occur across large numbers of tiles simultaneously, implying that Apple&#xA;maintains a centralized database rather than distributed, region-specific&#xA;updates.&lt;/p&gt;&#xA;</description><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2026/location-services/</guid></item><item><title>Wikimedia attack: 2026-03-05</title><link>https://duti.dev/blog/2026/wikimedia-basemetrika/</link><description>&lt;p&gt;Today, Wikipedia was in read-only mode for a couple of hours due to a security&#xA;incident.&lt;/p&gt;&#xA;&lt;p&gt;Post from WMF staff member on Discord:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Hey all - as some of you have seen, we (WMF) were doing a security review of&#xA;the behavior of user scripts, and unintentionally activated one that turned&#xA;out to be malicious. That is what caused the page deletions you saw on the&#xA;Meta log, which are getting cleaned up. We have no reason to believe any&#xA;third-party entity was actively attacking us today, or that any permanent&#xA;damage occurred or any breach of personal information.&lt;/p&gt;&#xA;&lt;p&gt;We were doing this security review as part of an effort to limit the risks of&#xA;exactly this kind of attack. The irony of us triggering this script while&#xA;doing so is not lost on us, and we are sorry about the disruption. But the&#xA;risks in this system are real. We are going to continue working on security&#xA;protections for user scripts – in close consultation with the community, of&#xA;course – to make this sort of thing much harder to happen in the future.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;You can read up on some sources here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wikipediocracy.com/forum/viewtopic.php?f=8&amp;amp;t=14555&#34;&gt;https://wikipediocracy.com/forum/viewtopic.php?f=8&amp;amp;t=14555&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://phabricator.wikimedia.org/T419143&#34;&gt;https://phabricator.wikimedia.org/T419143&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://old.reddit.com/r/wikipedia/comments/1rllcdg/megathread_wikimedia_wikis_locked_accounts/&#34;&gt;https://old.reddit.com/r/wikipedia/comments/1rllcdg/megathread_wikimedia_wikis_locked_accounts/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2&gt;What the script did&lt;/h2&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Wow. This worm is fascinating. It seems to do the following:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Inject itself into the MediaWiki:Common.js page to persist globally, and&#xA;into the User:Common.js page to do the same as a fallback&lt;/li&gt;&#xA;&lt;li&gt;Uses jQuery to hide UI elements that would reveal the infection&lt;/li&gt;&#xA;&lt;li&gt;Vandalizes 20 random articles with a 5000px wide image and another XSS&#xA;script from basemetrika.ru&lt;/li&gt;&#xA;&lt;li&gt;If an admin is infected, it will use the Special:Nuke page to delete 3&#xA;random articles from the global namespace, AND use the Special:Random with&#xA;action=delete to delete another 20 random articles&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;EDIT! The Special:Nuke is really weird. It gets a default list of articles to&#xA;nuke from the search field, which could be any group of articles, and&#xA;rubber-stamps nuking them. It does this three times in a row.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Source: &lt;a href=&#34;https://news.ycombinator.com/item?id=47264202&#34;&gt;nhubbard on ycombinator&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;For some reason, basemetrika.ru was not a registered domain. I have now&#xA;registered it and thus hopefully nobody else will be able to use it for&#xA;malicious purposes.&lt;/p&gt;&#xA;&lt;p&gt;This was not an active attack but simply a mistake by a wikimedia admin.&lt;/p&gt;&#xA;&lt;p&gt;If the wikimedia foundation would like to take the domain off me, they can&#xA;contact me at &lt;a href=&#34;mailto:acheong@duti.dev&#34;&gt;acheong@duti.dev&lt;/a&gt;.&lt;/p&gt;&#xA;</description><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2026/wikimedia-basemetrika/</guid></item><item><title>I&#39;m worried about the future of open-source</title><link>https://duti.dev/blog/2026/oss-ai/</link><description>&lt;img src=&#34;https://r2.duti.dev/blog/images/go-micro-commits.png&#34; alt=&#34;Commit history of go-micro with Copilot commits&#34; height=&#34;400&#34;&gt;&#xA;&lt;p&gt;At first glance, this looks like the commit history of just another random vibe&#xA;coded project. But no, it is in fact the recent commits to&#xA;&lt;a href=&#34;https://github.com/micro/go-micro&#34;&gt;&lt;code&gt;go-micro&lt;/code&gt;&lt;/a&gt;, a project with 22.7k stars that&#xA;started all the way back in 2015.&#xA;&lt;a href=&#34;https://github.com/koverstreet/bcachefs&#34;&gt;BcacheFS&lt;/a&gt;, a major filesystem, now has&#xA;&lt;code&gt;ProofOfConcept&lt;/code&gt; (a custom AI bot) as co-author on the majority of new commits.&#xA;The &lt;a href=&#34;https://github.com/claude&#34;&gt;Claude account&lt;/a&gt; on GitHub now has so many&#xA;commits that its GitHub profile fails to even load.&lt;/p&gt;&#xA;&lt;p&gt;This worries me.&lt;/p&gt;&#xA;&lt;p&gt;When it&#39;s Claude code&#xA;&lt;a href=&#34;https://github.com/anthropics/claude-code/issues/22543&#34;&gt;randomly creating 10GB VM bundles&lt;/a&gt;,&#xA;we point and laugh. When&#xA;&lt;a href=&#34;https://thehackernews.com/2026/02/cline-cli-230-supply-chain-attack.html&#34;&gt;Cline gets supply chain attacked because of a prompt injection&lt;/a&gt;,&#xA;nobody cares because dependence on them is purely individual. Very few build on&#xA;top of those shaky towers.&lt;/p&gt;&#xA;&lt;p&gt;But now AI is coming for the foundations of what we build. I personally don&#39;t&#xA;have much confidence in either the quality or security of what AI pumps out.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/chardet/chardet/issues/327&#34;&gt;&lt;code&gt;chardet&lt;/code&gt;&lt;/a&gt; recently got&#xA;completely rewritten by AI as a means of license washing (getting rid of LGPL)&#xA;while breaking various behavior and generally making the quality worse. Again,&#xA;not a small package. It is a dependency of almost 1 million packages tracked by&#xA;GitHub.&lt;/p&gt;&#xA;&lt;p&gt;I don&#39;t even know what I want to say yet, other than that I do not like this new&#xA;iteration of open-source and I believe this behavior will become more widespread&#xA;over time. Barley anyone gets paid for it, and LLMs are an easy way out of&#xA;spending the effort maintaining a library.&lt;/p&gt;&#xA;</description><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2026/oss-ai/</guid></item><item><title>HackEurope 2026: A short rant on AI and hackathons</title><link>https://duti.dev/blog/2026/spr/</link><description>&lt;p&gt;HackEurope is over. In many ways, it was a complete shitshow (vibe coded&#xA;inaccessible UI for participants, lots of delays, miscommunications, and other&#xA;issues too many to list). But now that the caffeine overdose and sleep&#xA;deprivation is over, I can say that there were actually some important lessons.&lt;/p&gt;&#xA;&lt;p&gt;TL;DR:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Front-end is almost everything. There is 0 burden of proof that your project&#xA;is actually functional or that it has any practical application. As long as&#xA;it looks cool, investors and non-technical people will eat that up.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Choose your track wisely. Make sure that the track sponsor IS ACTUALLY AT&#xA;YOUR LOCATION. Most people were under the impression that tracks were&#xA;per-country when in fact there was a single €1000 prize shared across the 3&#xA;countries and the sponsor wasn&#39;t actually operating in some.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Choose a problem that is easy to explain. There were 2 minutes to explain. It&#xA;is a losing game whether or not you explain context. Non-technical people&#xA;will tune out confused regardless. We were extremely lucky with 2/3 of the&#xA;evaluators actually knowing about open-source supply chain attacks and being&#xA;excited about our solution.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Follow the trends. All winners had &amp;quot;AI&amp;quot; as a significant part of their&#xA;solution.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;That being said, I personally wouldn&#39;t follow my own advice. I went in with the&#xA;goal of building something that I would want to maintain long term. Not just AI&#xA;slop (I fucking hate Lovable).&lt;/p&gt;&#xA;&lt;!--&#xA;&#xA;## So what did we actually build?&#xA;&#xA;### Context&#xA;&#xA;Over the past year, we&#39;ve had all sort of supply chain attacks. From the&#xA;Shai-Hulud worm to Notepad++ being hacked. Developers are the most vulnerable.&#xA;Most people install packages with no verification whatsoever. Meanwhile,&#xA;$BIGCORPs hire expensive security teams with manual reviews that take forever.&#xA;Lots of time wasted and duplicate work done between companies.&#xA;&#xA;(Common misunderstandings: No, we&#39;re not looking at CVEs or vulnerabilities.&#xA;Lots of companies like Snyk or Wiz already do that. There are valid times to use&#xA;insecure but non-malicious software such as for internal tooling)&#xA;&#xA;### The MVP&#xA;&#xA;So what our MVP was is basically a secure package registry that you simply&#xA;`npm config set` and used in place of NPM. We take packages from NPM and&#xA;generate a series of tests that would usually trigger malicious behaviors (if&#xA;any). We then collect a bunch of behavioral data using eBPF (like file accesses,&#xA;DNS, network connections, executed commands, etc.). This is a lot of data, and&#xA;we increase the signal to noise ratio by deduplicating based on a known set of&#xA;safe behavior collected from another real package. From that, we can either use&#xA;&#34;AI&#34; (of course we had to plug that in somewhere. It was the theme of the&#xA;hackathon lol) or historical data to determine whether that behavior is&#xA;malicious or at least anomalous. If everything is clear, it gets uploaded to our&#xA;&#34;secure&#34; registry.&#xA;&#xA;There is still a lot to work on. There are quite a few features we&#39;re also&#xA;working on:&#xA;&#xA;- Reproducible builds&#xA;- Derivative of behavioral changes across time to determine the &#34;normal&#34; amount&#xA;  of deviance&#xA;- Supporting PyPi, Maven, Cargo, and other ecosystems&#xA;- Automatic tracing of behavior to source (line of code, commit introduced, etc.&#xA;  Reverse engineering if necessary).&#xA;- Matching registry releases to exact source code commits&#xA;- Use [`eCapture`](https://github.com/gojue/ecapture) to decrypt HTTPS&#xA;- Have honeypot data to catch exfiltration attempts&#xA;&#xA;I&#39;ve been working on a much larger system over the past few months that tries to&#xA;solve supply chain security. The task now is to take the shit code written&#xA;during the hackathon and integrate the reusable parts.&#xA;&#xA;If you have any comments, or just interested in general, pop me an&#xA;[email](mailto:acheong@duti.dev).&#xA;&#xA;--&gt;&#xA;&lt;h2&gt;AI encourages conformity and kills creativity&lt;/h2&gt;&#xA;&lt;p&gt;A solid 90% of the projects there were just vibe coded slop. Even the ideas were&#xA;AI. You can tell when multiple people implemented the exact same idea with the&#xA;exact same title, description, and implementation.&lt;/p&gt;&#xA;&lt;p&gt;While people call me a luddite, I do not particularly hate AI as a tool. My&#xA;problem is that it has significantly lowered the bar for certain project types&#xA;and therefore incentivize people who would have otherwise built something cool&#xA;to instead fit into a mold constrained by the capabilities of AI.&lt;/p&gt;&#xA;&lt;p&gt;A lot of cool ideas are out of distribution from the training data, and those&#xA;rarely show up at hackathons anymore. The AI says they&#39;re &amp;quot;too hard&amp;quot; and people&#xA;simply avoid these.&lt;/p&gt;&#xA;&lt;p&gt;The grand winner was an idea to use LLMs for predicting wildfires caused by&#xA;lightning strikes, and subsequently using LLMs to orchestrate drones to do cloud&#xA;seeding and prevent wildfires. Cool UI and all, but there was (at least from&#xA;observation), nothing actually behind it.&lt;/p&gt;&#xA;&lt;p&gt;In a now edited post by Anthropic&#39;s head of Startup Sales, he mentioned that the&#xA;winning team (LLM cloud seeding) had only 1 software engineer and 3&#xA;non-technicals. The accessibility is cool to see, but it is not expected at all&#xA;for any of these projects to exist long-term. Just a marketing stunt to claim&#xA;that code is now a commodity.&lt;/p&gt;&#xA;&lt;p&gt;It feels like hackathons used to be a place where real startups are made or at&#xA;least a proxy for the ability of individuals. Now, with everything being&#xA;front-end only demos, there is no expectation at all for any follow up, and&#xA;nothing is said of ability except for pitching and trend chasing.&lt;/p&gt;&#xA;&lt;p&gt;Some other funny ideas I saw:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Stopping AI prompt injecting by scanning every prompt with an LLM (there were&#xA;multiple duplicates of this)&lt;/li&gt;&#xA;&lt;li&gt;Using LLMs to control satellites and move them when a Russian satellite gets&#xA;too close (winner)&lt;/li&gt;&#xA;&lt;li&gt;&amp;quot;Palantir for tech teams&amp;quot;. &amp;quot;A real-time security guardian sitting silently on&#xA;every dev&#39;s machine, scanning their screen, code, and communications to&#xA;proactively prevent vulnerabilities.&amp;quot;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description><pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2026/spr/</guid></item><item><title>The temptation of LLMs as slop generators</title><link>https://duti.dev/blog/2025/ai-is-tempting/</link><description>&lt;p&gt;I recently fell down this trap myself. I had an assignment i was rushing, and I&#xA;suck at writing prose (just look at this post...). I decided to simply note down&#xA;everything as bullet points. The thoughts were mine, and did research the&#xA;luddite way of crawling through Google Scholar, Sci-Hub, and random blog posts.&#xA;I liked the output very much - it conveyed all my arguments and had all the&#xA;relevant facts. However, it was a bit disordered and probably didn&#39;t meet the&#xA;mark scheme.&lt;/p&gt;&#xA;&lt;p&gt;So why not put it into an LLM? Surely a language model would be good at language&#xA;and simply transforming text from one form to another. At first glance, it all&#xA;looked fine. I see the stats, I see my citations, and there are now full&#xA;sentences.&lt;/p&gt;&#xA;&lt;p&gt;But actually reading through the text from top to bottom, it was obvious how&#xA;shit it was. Important points were missing, and unimportant points were&#xA;emphasized. The style was disgusting, full of weird em-dash usage and corporate&#xA;speak I&#39;d never use myself. Every time I corrected it, another mistake would&#xA;either pop up elsewhere, or it&#39;d over correct to some hippy-speak. This was not&#xA;a model-specific problem. No matter which model I used: Kimi K2, GPT-5.2, Claude&#xA;Opus 4.5, Deekseek R2, Gemini 3 Flash, none of them could follow my specific&#xA;preferences.&lt;/p&gt;&#xA;&lt;p&gt;$10 and an unreasonable amount of time later, I finally gave up and started&#xA;rewriting from scratch.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Nobody likes to read AI slop, so why do we keep making more of it?&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;It&#39;s not about the quality of the content, but the aesthetics of delivery.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Not everyone has English as their first language, and even for native speakers,&#xA;it takes effort to construct prose in a way that flows well and sounds eloquent.&#xA;In academics, despite linguistic skill not necessarily reflecting the quality of&#xA;content, bias easily seeps in based on how things are worded.&lt;/p&gt;&#xA;&lt;p&gt;For example:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Even in software engineering where our assignments are usually to write code,&#xA;we are assessed on reflective reports and essays where snaky wording can allow&#xA;a team member with 0 commits to still score a 1st.&lt;/li&gt;&#xA;&lt;li&gt;In 2024, during a hackathon by Huawei, the team that placed first in the&#xA;technical round completely fell out of the top 5 simply because English was&#xA;their second language, whereas my team that scored 4th in the technical round&#xA;got bumped up to 2nd due to presentation skills alone.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Especially when the substance is entirely beyond the ability of the assessor to&#xA;comprehend, writing quality becomes the subject of judgment.&lt;/p&gt;&#xA;&lt;p&gt;LLMs provide an easy way out... It is benchmaxxed on a very specific style&#xA;reminiscent of how we were taught to do so in middle and high school English:&#xA;Vary between sentence structures and lengths to avoid sounding repetitive,&#xA;structure content with numbering to make it easy to keep track, and use bigger&#xA;words to sound well educated.&lt;/p&gt;&#xA;&lt;p&gt;However, using LLMs take the soul out of content. It has no understanding of&#xA;what is important, what&#39;s tangential, and happily makes up details not&#xA;specified. It does put intelligent sounding text though, and that&#39;s what the&#xA;vast majority of non-technical people I&#39;ve met actually prefer anyways. Instead&#xA;of reading an email, business report, or anything of substantial length,&#xA;managers, acquaintances, and even family I know would simply dump it into&#xA;ChatGPT or Gemini and read their hallucination-ridden output instead.&lt;/p&gt;&#xA;&lt;p&gt;Furthermore, using an LLM is like gambling. Maybe you&#39;ll one-shot it, but&#xA;probably not. Maybe the next try will be the one where it gets everything right.&#xA;The probabilistic nature of LLMs as well as the dopamine hit of feeling like&#xA;you&#39;ve been productive without actually doing anything. &amp;quot;Ah I&#39;ve done what would&#xA;usually take me the whole day to do. I can slack off now&amp;quot; is always at the back&#xA;of my mind. It makes you feel an illusion of productivity and forget that most&#xA;of the time spent writing is actually time spent thinking and refining.&lt;/p&gt;&#xA;&lt;p&gt;Heck, put in &amp;quot;Write an essay on why LLM Slop writing is so tempting&amp;quot; to ChatGPT&#xA;and it writes a better blog than I do with all my points and more.&lt;/p&gt;&#xA;</description><pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2025/ai-is-tempting/</guid></item><item><title>2025 in a nutshell</title><link>https://duti.dev/blog/2025/2025-in-a-nutshell/</link><description>&lt;p&gt;Time flies. I can&#39;t believe the year is almost over. The fact that there&#39;s&#xA;barely 6 months left before graduation scares me.&lt;/p&gt;&#xA;&lt;h2&gt;January to April&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Mostly just coursework. I let myself slip a little and didn&#39;t do much work.&#xA;Made a &lt;a href=&#34;https://github.com/acheong08/ganyu-player&#34;&gt;cool music player&lt;/a&gt;&#xA;integrated with &lt;code&gt;yt-dlp&lt;/code&gt; for Android. Unfortunately, I never managed to switch&#xA;to Android and thus never bothered implementing all the features I wanted.&#xA;Maybe next year...&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2&gt;May&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/acheong08/kube&#34;&gt;Kubernetes&lt;/a&gt;. I regret this. It did solve&#xA;my problem of wanting docker-compose but with storage replication and easily&#xA;moving between nodes. However, the amount of time I&#39;ve spent debugging various&#xA;issues since would&#39;ve been enough to DIY my own system less complex and thus&#xA;more reliable for my specific needs. It&#39;s in a stable state now though, so&#xA;hopefully I won&#39;t have to touch it much again.&lt;/li&gt;&#xA;&lt;li&gt;I got into AI again. I tried out all of the popular agents such as Codex,&#xA;Gemini CLI, Goose, and OpenCode. Codex sucked because OpenAI models are dumb.&#xA;Gemini had my favorite degree of developer control, but I can&#39;t figure out how&#xA;to pay Google. They keep declining my debit cards. Goose was buggy. OpenCode&#xA;has good integration with all the providers but lacks control (all changes are&#xA;auto-approved). Gave up and went back to CopilotChat.nvim.&lt;/li&gt;&#xA;&lt;li&gt;Slacked off.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2&gt;Late May to August&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Internship at Huawei&lt;/li&gt;&#xA;&lt;li&gt;Learned a ton about Time Travel Debugging, graphs, and eBPF.&lt;/li&gt;&#xA;&lt;li&gt;Thought about supply chain security but was shut down for being too difficult&#xA;to implement due to company politics.&lt;/li&gt;&#xA;&lt;li&gt;After the internship, I traveled the Netherlands and Belgium&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2&gt;September&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Internship at the University of Cambridge. Continued research on Apple&#39;s&#xA;location services which I started working on all the way back in May 2024.&#xA;&lt;a href=&#34;https://github.com/acheong08/apple-corelocation-experiments&#34;&gt;GitHub&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Also messed with iOS development for data collection, namely creating a&#xA;Charles Proxy-like app to collect Apple analytics data locally without privacy&#xA;concerns of an mitmproxy VPN.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2&gt;October-December&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;More coursework. Learned React Server Components. Horrible stuff. 10.0 CVE&#xA;dropped the day after submission.&lt;/li&gt;&#xA;&lt;li&gt;Failed a bunch of job interviews.&lt;/li&gt;&#xA;&lt;li&gt;Realized that I might end up unemployed after university.&lt;/li&gt;&#xA;&lt;li&gt;Joked about having a startup idea (the thing that got rejected by Huawei)&lt;/li&gt;&#xA;&lt;li&gt;You know what? There might actually be a higher chance of making a successful&#xA;startup than getting a job.&lt;/li&gt;&#xA;&lt;li&gt;Started doing market research and designing the product from the business&#xA;perspective. Hey, if you&#39;re reading this and interested in stopping supply&#xA;chain attacks like &lt;a href=&#34;https://tukaani.org/xz-backdoor/&#34;&gt;xz-utils&lt;/a&gt; or&#xA;&lt;a href=&#34;https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised&#34;&gt;Shai-Hulud the NPM worm&lt;/a&gt;,&#xA;&lt;a href=&#34;mailto:acheong@duti.dev&#34;&gt;email me&lt;/a&gt; and maybe we can have a chat.&lt;/li&gt;&#xA;&lt;li&gt;But of course, I&#39;d still rather be employed and work on my projects part-time&#xA;&amp;amp; open source rather than being worried about monetization.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1&gt;2026. The Plan&lt;/h1&gt;&#xA;&lt;p&gt;Uhh, pray.&lt;/p&gt;&#xA;&lt;p&gt;Applied Software Engineering at Cardiff University actually has one major&#xA;benefit: you get complete control over your final year project and dissertation.&#xA;This means that I get to work on a startup without violating the terms of my&#xA;student visa and graduate with an MVP in hand. From there, I need to run around&#xA;the UK looking for pre-seed funding. I don&#39;t need much, just enough to afford&#xA;basic rent, food, and internet for a year while I actually get customers and&#xA;build revenue. I&#39;d like to bootstrap but unfortunately all the money I&#39;ve earned&#xA;over the years has gone to university. $57k (£46k back in 2023) from security&#xA;bounties + £14k from internships + £10k from freelancing while tuition cost £70k&#xA;over the 3 years means I&#39;ll be graduating with around negative £16k in the bank&#xA;from rent and living expenses.&lt;/p&gt;&#xA;&lt;p&gt;So yeah, things are looking a bit grim.&lt;/p&gt;&#xA;</description><pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2025/2025-in-a-nutshell/</guid></item><item><title>Weird Network Reconnects on Arch</title><link>https://duti.dev/blog/2025/arch-wifi-reconnects/</link><description>&lt;pre&gt;&lt;code&gt;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073420.5523] device (duti): Activation: successful, device activated.&#xA;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073420.5683] device (wlan0): supplicant interface state: internal-starting -&amp;gt; disconnected&#xA;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073420.5684] Wi-Fi P2P device controlled by interface wlan0 created&#xA;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073420.5685] manager: (p2p-dev-wlan0): new 802.11 Wi-Fi P2P device (/org/freedesktop/NetworkManager/Devices/146)&#xA;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073420.5686] device (p2p-dev-wlan0): state change: unmanaged -&amp;gt; unavailable (reason &#39;managed&#39;, managed-type: &#39;external&#39;)&#xA;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;warn&amp;gt;  [1767073420.5686] device (p2p-dev-wlan0): error setting IPv4 forwarding to &#39;0&#39;: Success&#xA;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073420.5689] device (wlan0): state change: unavailable -&amp;gt; disconnected (reason &#39;supplicant-available&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:40 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073420.5692] device (p2p-dev-wlan0): state change: unavailable -&amp;gt; disconnected (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0093] policy: auto-activating connection &#39;MyWifi&#39; (134f6382-9241-46b4-90a0-7e052d168eda)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0100] device (wlan0): Activation: starting connection &#39;MyWifi&#39; (134f6382-9241-46b4-90a0-7e052d168eda)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0100] device (wlan0): state change: disconnected -&amp;gt; prepare (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0468] device (wlan0): set-hw-addr: reset MAC address to de:ad:ba:be:ca:fe (preserve)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0509] device (wlan0): state change: prepare -&amp;gt; config (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0512] device (wlan0): Activation: (wifi) access point &#39;MyWifi&#39; has security, but secrets are required.&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0512] device (wlan0): state change: config -&amp;gt; need-auth (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0517] device (wlan0): supplicant interface state: disconnected -&amp;gt; inactive&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0517] device (p2p-dev-wlan0): supplicant management interface state: disconnected -&amp;gt; inactive&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0528] device (wlan0): state change: need-auth -&amp;gt; prepare (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0531] device (wlan0): state change: prepare -&amp;gt; config (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0535] device (wlan0): Activation: (wifi) connection &#39;MyWifi&#39; has security, and secrets exist.  No new secrets needed.&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0541] Config: added &#39;ssid&#39; value &#39;MyWifi&#39;&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0541] Config: added &#39;scan_ssid&#39; value &#39;1&#39;&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0542] Config: added &#39;bgscan&#39; value &#39;simple:30:-70:86400&#39;&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0542] Config: added &#39;key_mgmt&#39; value &#39;WPA-PSK WPA-PSK-SHA256 SAE&#39;&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.0542] Config: added &#39;psk&#39; value &#39;&amp;lt;hidden&amp;gt;&#39;&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.6283] device (wlan0): supplicant interface state: inactive -&amp;gt; authenticating&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.6283] device (p2p-dev-wlan0): supplicant management interface state: inactive -&amp;gt; authenticating&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.6830] device (wlan0): supplicant interface state: authenticating -&amp;gt; associating&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.6831] device (p2p-dev-wlan0): supplicant management interface state: authenticating -&amp;gt; associating&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.7385] device (wlan0): supplicant interface state: associating -&amp;gt; completed&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.7385] device (wlan0): Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Connected to wireless network &amp;quot;MyWifi&amp;quot;&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.7385] device (p2p-dev-wlan0): supplicant management interface state: associating -&amp;gt; completed&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.7398] device (wlan0): state change: config -&amp;gt; ip-config (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.7401] dhcp4 (wlan0): activation: beginning transaction (timeout in 45 seconds)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.7572] dhcp4 (wlan0): state changed new lease, address=192.168.1.104, acd pending&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.9267] dhcp4 (wlan0): state changed new lease, address=192.168.1.104&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.9425] device (wlan0): state change: ip-config -&amp;gt; ip-check (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.9442] device (wlan0): state change: ip-check -&amp;gt; secondaries (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.9444] device (wlan0): state change: secondaries -&amp;gt; activated (reason &#39;none&#39;, managed-type: &#39;full&#39;)&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.9457] device (wlan0): Activation: successful, device activated.&#xA;Dec 30 05:49:06 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073746.6061] audit: op=&amp;quot;connection-update&amp;quot; uuid=&amp;quot;bbc81535-f6e4-341f-92ef-7df37432204b&amp;quot; name=&amp;quot;Wired connection 1&amp;quot; args=&amp;quot;ipv6.addr-gen-mode,ipv6.method,connection.timestamp&amp;quot; pid=14084 uid=1000 result=&amp;quot;success&amp;quot;&#xA;[ 2327.606933] wlan0: RX AssocResp from de:ad:ba:be:ca:fe (capab=0x1411 status=0 aid=4)&#xA;[ 2327.622592] wlan0: associated&#xA;[ 2327.706904] wlan0: Limiting TX power to 35 (35 - 0) dBm as advertised by de:ad:ba:be:ca:fe&#xA;[ 2331.429787] wlan0: deauthenticating from de:ad:ba:be:ca:fe by local choice (Reason: 3=DEAUTH_LEAVING)&#xA;[ 2333.786872] wlan0: authenticate with de:ad:ba:be:ca:fe (local address=de:ad:ba:be:ca:fe)&#xA;[ 2333.787662] wlan0: send auth to de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2333.821379] wlan0: authenticated&#xA;[ 2333.822532] wlan0: associate with de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2333.930474] wlan0: associate with de:ad:ba:be:ca:fe (try 2/3)&#xA;[ 2333.944659] wlan0: RX AssocResp from de:ad:ba:be:ca:fe (capab=0x1411 status=0 aid=1)&#xA;[ 2333.957576] wlan0: associated&#xA;[ 2333.957667] wlan0: Limiting TX power to 35 (35 - 0) dBm as advertised by de:ad:ba:be:ca:fe&#xA;[ 2338.461674] wlan0: deauthenticating from de:ad:ba:be:ca:fe by local choice (Reason: 3=DEAUTH_LEAVING)&#xA;[ 2340.751310] wlan0: authenticate with de:ad:ba:be:ca:fe (local address=de:ad:ba:be:ca:fe)&#xA;[ 2340.752571] wlan0: send auth to de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2340.786019] wlan0: authenticated&#xA;[ 2340.786568] wlan0: associate with de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2340.800918] wlan0: RX AssocResp from de:ad:ba:be:ca:fe (capab=0x1411 status=0 aid=1)&#xA;[ 2340.838602] wlan0: associated&#xA;[ 2340.838679] wlan0: Limiting TX power to 35 (35 - 0) dBm as advertised by de:ad:ba:be:ca:fe&#xA;[ 2344.497748] wlan0: deauthenticating from de:ad:ba:be:ca:fe by local choice (Reason: 3=DEAUTH_LEAVING)&#xA;[ 2346.767408] wlan0: authenticate with de:ad:ba:be:ca:fe (local address=de:ad:ba:be:ca:fe)&#xA;[ 2346.768118] wlan0: send auth to de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2346.801876] wlan0: authenticated&#xA;[ 2346.802617] wlan0: associate with de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2346.813667] wlan0: RX AssocResp from de:ad:ba:be:ca:fe (capab=0x1411 status=0 aid=1)&#xA;[ 2346.825683] wlan0: associated&#xA;[ 2346.855327] wlan0: Limiting TX power to 35 (35 - 0) dBm as advertised by de:ad:ba:be:ca:fe&#xA;[ 2351.528729] wlan0: deauthenticating from de:ad:ba:be:ca:fe by local choice (Reason: 3=DEAUTH_LEAVING)&#xA;[ 2357.303297] wlan0: authenticate with de:ad:ba:be:ca:fe (local address=de:ad:ba:be:ca:fe)&#xA;[ 2357.304282] wlan0: send auth to de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2357.335522] wlan0: authenticated&#xA;[ 2357.336873] wlan0: associate with de:ad:ba:be:ca:fe (try 1/3)&#xA;[ 2357.344912] wlan0: RX AssocResp from de:ad:ba:be:ca:fe (capab=0x1011 status=0 aid=1)&#xA;[ 2357.358139] wlan0: associated&#xA;[ 2357.412622] wlan0: Limiting TX power to 35 (35 - 0) dBm as advertised by de:ad:ba:be:ca:fe&#xA;Dec 30 05:43:45 fishy NetworkManager[1364]: &amp;lt;info&amp;gt;  [1767073425.9457] device (wlan0): Activation: successful, device activated.&#xA;Dec 30 05:43:49 fishy systemd-networkd[1206]: wlan0: DHCPv4 address 192.168.1.104/24, gateway 192.168.1.1 acquired from 192.168.1.1&#xA;Dec 30 05:43:50 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:43:50 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 172.20.10.5/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:45:11 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:45:11 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 172.20.10.5/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:47:10 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:47:10 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 172.20.10.5/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:48:32 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:48:32 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 172.20.10.5/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:49:06 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:49:06 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 172.20.10.5/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:51:05 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:51:05 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 172.20.10.5/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:52:33 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;Dec 30 05:52:33 fishy tailscaled[1477]: LinkChange: major, rebinding. New state: interfaces.State{defaultRoute=enp0s20f0u1c4i2 ifs={duti:[10.8.0.7/24] enp0s20f0u1c4i2:[172.20.10.2/28 172.20.10.5/28 240e:430:2a11:c5dd:5af6:e64a:6c3d:e896/64 240e:430:2a11:c5dd:b890:47ff:fe0a:8ca8/64 llu6] tailscale0:[100.64.0.8/32 fd7a:115c:a1e0::8/128 llu6] wlan0:[192.168.1.104/24]} v4=true v6=true}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;My internet kept disconnecting and reconnecting in a loop. Only seemed to happen&#xA;on one specific network in China.&lt;/p&gt;&#xA;&lt;p&gt;What ended up being the problem was having both &lt;code&gt;NetworkManager&lt;/code&gt; and&#xA;&lt;code&gt;systemd-networkd&lt;/code&gt; active at the same time. I don&#39;t know why that was the case&#xA;but never had problems before. Probably due to this specific network being&#xA;unstable and thus leading to the race condition.&lt;/p&gt;&#xA;&lt;p&gt;Solution was to simply&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;sudo systemctl disable --now systemd-networkd.service&#xA;sudo systemctl disable --now systemd-resolved.service&#xA;sudo systemctl mask systemd-networkd.service&#xA;sudo systemctl mask systemd-networkd-varlink.socket&#xA;sudo systemctl mask systemd-networkd.socket&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Posting here so the next person that searches these strings from their logs can&#xA;find it.&lt;/p&gt;&#xA;&lt;p&gt;Sometimes, &lt;code&gt;iwd&lt;/code&gt; and &lt;code&gt;wpa_supplicant&lt;/code&gt; are also both active. Just disable &lt;code&gt;iwd&lt;/code&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;sudo pacman -Rcns iwd&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You may need to reboot your laptop and later edit connections using&#xA;&lt;code&gt;nm-connection-editor&lt;/code&gt; to use &lt;code&gt;wlp0s20f3&lt;/code&gt; instead of &lt;code&gt;wlan0&lt;/code&gt;&lt;/p&gt;&#xA;</description><pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2025/arch-wifi-reconnects/</guid></item><item><title>Failing a Java interview by NOT CONFIGURING NEOVIM CORRECTLY</title><link>https://duti.dev/blog/2025/java-no-sdk/</link><description>&lt;p&gt;So I&#39;m not really a Java programmer, but I can write Java since all university&#xA;coursework pretty much mandates it. But since I haven&#39;t written it in a while, I&#xA;didn&#39;t properly set it up when I rewrote&#xA;&lt;a href=&#34;https://github.com/acheong08/nvim&#34;&gt;my Neovim config&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Q: Why are you applying to a Java position if you&#39;re not a Java programmer?&lt;/p&gt;&#xA;&lt;p&gt;A: Those are the only jobs around...&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Before the interview, I set up &lt;code&gt;nvim-java&lt;/code&gt; as usual and tested that it worked&#xA;with a simple Java file, not thinking much of it. Definitions, errors,&#xA;formatting, all working.&lt;/p&gt;&#xA;&lt;p&gt;Then, during the interview, I decided it was probably a good idea to set up a&#xA;proper gradle project with testing and whatnot. But then everything fell apart.&#xA;&lt;code&gt;jdtls&lt;/code&gt; gave me the most random errors such as&#xA;&lt;code&gt;1. String cannot be resolved to a type [16777218]&lt;/code&gt; and&#xA;&lt;code&gt;1. System cannot be resolved [570425394]&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I didn&#39;t know how much time it&#39;d take to solve the issue, and obviously spending&#xA;the entire interview debugging a neovim config was a bad idea. The interviewer&#xA;was already annoyed at that point: &amp;quot;You know when you start working a real job,&#xA;you&#39;d need a real IDE.&amp;quot;. I had already uninstalled IntelliJ a couple months back&#xA;after my last Java coursework, and for such a large package, it was gonna take&#xA;forever to download.&lt;/p&gt;&#xA;&lt;p&gt;So VSCode it was.&lt;/p&gt;&#xA;&lt;p&gt;However, the combination of Java, VSCode, and Google Meet caused my CPU to run&#xA;at 100% and crashed my laptop. For the entire rest of the interview, I was&#xA;thermal throttled with a couple seconds latency between a key press and text&#xA;showing up on screen. I ultimately ran out of time by ~5 seconds which&#xA;infuriates me since that is significantly less than the time wasted by&#xA;performance issues.&lt;/p&gt;&#xA;&lt;p&gt;After the interview, I solved the problem in barely 5 minutes. Shorter than the&#xA;amount of time it took to recover from all the crashes.&lt;/p&gt;&#xA;&lt;p&gt;The reason appears to be that &lt;code&gt;nvim-java&lt;/code&gt; defaults to Java 17 found at&#xA;&lt;code&gt;~/.local/share/nvim/nvim-java/packages/openjdk/17/&lt;/code&gt; while Gradle was using&#xA;Java 25. Some weird combination of the configuration made the JDK fail to load.&lt;/p&gt;&#xA;&lt;p&gt;The solution here was really simple:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-lua&#34;&gt;vim.lsp.config(&amp;quot;jdtls&amp;quot;, {&#xA;  settings = {&#xA;    java = {&#xA;      configuration = {&#xA;        runtimes = {&#xA;          {&#xA;            name = &amp;quot;JavaSE-25&amp;quot;,&#xA;            path = &amp;quot;$HOME/.sdkman/candidates/java/current&amp;quot;,&#xA;            default = true,&#xA;          },&#xA;        },&#xA;      },&#xA;    },&#xA;  },&#xA;})&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The correct solution might actually be to just get a better laptop, but&#xA;unfortunately I&#39;m broke. So Neovim it is.&lt;/p&gt;&#xA;</description><pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2025/java-no-sdk/</guid></item><item><title>Visiting China: Some thoughts and observations</title><link>https://duti.dev/blog/2025/china/</link><description>&lt;h2&gt;Context&lt;/h2&gt;&#xA;&lt;p&gt;I lived in China between approximately 2008-2012 in Beijing and 2018-2023 in&#xA;Chongqing where my father works as an engineer. My point of reference will&#xA;mostly be comparing to Malaysia, my home country, and the UK, where I am&#xA;currently studying for my bachelor&#39;s degree.&lt;/p&gt;&#xA;&lt;h2&gt;Pollution&lt;/h2&gt;&#xA;&lt;p&gt;This was the first thing I noticed as the plane landed in Chongqing. I could&#xA;barely see beyond a hundred meters. Perhaps that is why the country feels so&#xA;dystopian, with everything being the same shade of gray. The problem was meant&#xA;to have been fixed years ago when more than half the cars on the road became&#xA;electric and factories were moved to other provinces. Perhaps it was just that&#xA;week when the wind blew from the wrong direction. I remember it being somewhat&#xA;clearer back in 2023, though of course nothing like the UK or Malaysia. The sun&#xA;in China never felt warm. I doubt its possible to get sunburns when most of the&#xA;UV gets absorbed by dust.&lt;/p&gt;&#xA;&lt;p&gt;Then I fly to Beijing. The skies are a clear blue, a stark contrast to when I&#xA;was last there in 2012. Apparently they planted a whole bunch of trees near the&#xA;Gobi Desert to stop the sandstorms. The buildings were all still the same tall&#xA;communist blocks with no sense of style. Much different to Chongqing&#39;s cyberpunk&#xA;theme.&lt;/p&gt;&#xA;&lt;h2&gt;Hyper-capitalism&lt;/h2&gt;&#xA;&lt;h3&gt;Attention economy&lt;/h3&gt;&#xA;&lt;p&gt;Streamers are the new big thing in China. They promote products on TikTok&#xA;(Douyin) and take a commission. I heard some of them have deals with&#xA;manufacturers to sell branded products that fail QA at a discount. It honestly&#xA;scares me how this has become almost the primary medium for purchasing certain&#xA;products like clothing. Everywhere I go: on public transport, at restaurants, I&#xA;can always hear someone watching a streamer without headphones.&lt;/p&gt;&#xA;&lt;h3&gt;Unsustainable competition&lt;/h3&gt;&#xA;&lt;p&gt;Everything seems to be dirt cheap. Maybe the UK has just had too much inflation&#xA;but I refuse to believe these prices are sustainable. ¥30 (~£3) for a winter&#xA;jacket, ¥16 (£1.7) for a bowl of ramen with beef, and various cheap toys for ¥5.&#xA;How is anyone making a profit? Apparently the Chinese government has even&#xA;stepped in to stop EV prices from dropping even further such that companies must&#xA;compete on quality instead. Surely resources can be better allocated (e.g.&#xA;Fixing their undrinkable tap water) rather than burnt on a bunch of low cost,&#xA;low quality products.&lt;/p&gt;&#xA;&lt;h3&gt;Gambling and loot boxes&lt;/h3&gt;&#xA;&lt;p&gt;Usually I associate this problem with western game companies, but somehow China&#xA;has made it worse. While trying to buy a train ticket the other day, rather than&#xA;simply letting me pay, it became a sort of lottery system where you can pay or&#xA;send an affiliate link to friends to boost your chances of actually getting the&#xA;ticket. This sort of dark pattern shows up everywhere. When calling for a taxi&#xA;via WeChat, various pop ups show up that lead you to gamble for discounts. As&#xA;someone that barely knows Chinese, I keep misclicking on the wrong options to&#xA;close the pop-up. If it really is a government for the people, why haven&#39;t they&#xA;made any European-style regulations on this? China is feeling more and more&#xA;similar to the US.&lt;/p&gt;&#xA;&lt;h3&gt;Low wages&lt;/h3&gt;&#xA;&lt;p&gt;Saw a job posting at a restaurant I was eating at yesterday. ¥25-¥35 per hour&#xA;for a waiter and janitor. I suppose that is survivable based on the low cost of&#xA;living but it really is so much lower than minimum wage. Mind you, this is in&#xA;Beijing, not some rural village.&lt;/p&gt;&#xA;&lt;p&gt;Overall, I don&#39;t like the direction China is moving in. There is nothing&#xA;communist about it &amp;amp; if you have a dictatorship that favors companies over&#xA;people, might as well just become an oligarchic democracy like the US.&lt;/p&gt;&#xA;&lt;h2&gt;Demographic changes in international schools&lt;/h2&gt;&#xA;&lt;p&gt;Ever since the trade war with the US, many international companies have moved to&#xA;Vietnam and Malaysia. Korean companies have downsized and now hire more locally.&#xA;It used to be that international schools would consist of rougly ~70% Koreans,&#xA;20% Chinese, and 10% other. Now, Chinese students make up more than 80%. This is&#xA;weird when you consider that Chinese nationals are banned from attending&#xA;international schools. Apparently many of them pay millions to get a citizenship&#xA;or green card in European countries.&lt;/p&gt;&#xA;&lt;h2&gt;A change in behavior among 富二代&lt;/h2&gt;&#xA;&lt;p&gt;&amp;quot;富不过三代&amp;quot; was a common saying back in the day in reference to how lazy the&#xA;children of the newly rich were. Those kids blew their parent&#39;s money on lavish&#xA;parties and luxury goods while never getting good grades in class. They didn&#39;t&#xA;have to. They could always just live off their parent&#39;s hoard.&lt;/p&gt;&#xA;&lt;p&gt;This seems to have changed recently. Rich kids here are now getting pushed into&#xA;multiple tutoring sessions a day, often with expensive teachers from abroad.&#xA;Beyond academics, they also do violin, ballet, art, all at once. Why this&#xA;change, I have no idea. There were some rare cases of this before, but now it&#xA;has somehow become the norm.&lt;/p&gt;&#xA;&lt;h2&gt;Internet censorship&lt;/h2&gt;&#xA;&lt;p&gt;Well, not much has changed here. Everything is still blocked. GitHub loads&#xA;painfully slow, and I can&#39;t even download anything off PyPi. Thankfully VPNs&#xA;still work. Even Wireguard which makes no effort to conceal its handshake&#xA;protocol is easily accessible and let through by the firewall. Cardiff&#xA;University&#39;s firewall is more strict than this. I swear there is some corruption&#xA;somewhere within Cardiff. Mullvad&#39;s website is blocked while their official&#xA;documentation recommends NordVPN. Who is taking the commission, I want to&#xA;know... (I&#39;ve gone off topic)&lt;/p&gt;&#xA;&lt;p&gt;Anyways, everyone I know here uses VPNs. Even non-technical boomer parents are&#xA;bound to have friends that know. It feels relatively safe to ignore whatever&#xA;government policy exists.&lt;/p&gt;&#xA;&lt;p&gt;In contrast, the UK&#39;s new proposals to require age verification for VPN use&#xA;scares me. In China, if they want to go after you, they must first go after a&#xA;large segment of their own population. In the UK, barely anyone uses VPNs, and&#xA;so there would be significantly less backlash if they started arresting a few&#xA;random tech people.&lt;/p&gt;&#xA;&lt;p&gt;On the topic of backlash, the UK government can seemingly ignore any dissent&#xA;from the general public without repercussion. The population is docile. Rather&#xA;than risking anything in protesting, we&#39;d rather wait till the next election to&#xA;once again vote in the lesser of two evils. Unlike Southeast Asia where&#xA;governments are constantly in fear of simply being overthrown, the UK has a&#xA;bunch of elitist snobs that think &amp;quot;populism&amp;quot; = bad. Off topic again...&lt;/p&gt;&#xA;&lt;p&gt;So yeah, I don&#39;t actually mind the censorship too much if the law is only there&#xA;on a surface level.&lt;/p&gt;&#xA;&lt;h2&gt;TODO&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Add photos&lt;/li&gt;&#xA;&lt;li&gt;I&#39;ll add more random thoughts later. I&#39;m still in China.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Update: I lost my phone along with all my photos. There will be no update...&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;</description><pubDate>Wed, 17 Dec 2025 00:00:00 +0000</pubDate><guid>https://duti.dev/blog/2025/china/</guid></item><item><title>Snippet: GitHub workflow to sync downstream forks</title><link>https://duti.dev/snippets/github-fork/</link><description>&lt;p&gt;Had enough of going through the all the personal forks I have of projects where&#xA;I containerize them and make personal preference changes that will never make it&#xA;into upstream. Here&#39;s a dead simple workflow for rebase and force push.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yml&#34;&gt;name: Sync Fork with Upstream (Force Push)&#xA;&#xA;on:&#xA;  schedule:&#xA;    - cron: &#39;0 1 * * *&#39;&#xA;  workflow_dispatch:&#xA;&#xA;# ============================================&#xA;# CONFIGURATION - Edit these for your fork&#xA;# ============================================&#xA;env:&#xA;  UPSTREAM_REPO: &#39;https://github.com/iv-org/invidious-companion.git&#39;&#xA;  UPSTREAM_BRANCH: &#39;master&#39;&#xA;  FORK_BRANCH: &#39;master&#39;&#xA;# ============================================&#xA;&#xA;jobs:&#xA;  sync:&#xA;    runs-on: ubuntu-latest&#xA;&#xA;    steps:&#xA;      - name: Checkout repository&#xA;        uses: actions/checkout@v4&#xA;        with:&#xA;          ref: ${{ env.FORK_BRANCH }}&#xA;          fetch-depth: 0&#xA;&#xA;      - name: Configure Git identity&#xA;        run: |&#xA;          git config user.email &amp;quot;41898282+github-actions[bot]@users.noreply.github.com&amp;quot;&#xA;          git config user.name &amp;quot;github-actions[bot]&amp;quot;&#xA;&#xA;      - name: Add upstream and fetch&#xA;        run: |&#xA;          git remote add upstream ${{ env.UPSTREAM_REPO }}&#xA;          git fetch upstream&#xA;&#xA;      - name: Rebase fork onto upstream&#xA;        id: rebase&#xA;        run: |&#xA;          git checkout ${{ env.FORK_BRANCH }}&#xA;&#xA;          # Attempt rebase&#xA;          if git rebase upstream/${{ env.UPSTREAM_BRANCH }}; then&#xA;            echo &amp;quot;result=clean&amp;quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT&#xA;          else&#xA;            echo &amp;quot;result=conflict&amp;quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT&#xA;            git rebase --abort || true&#xA;          fi&#xA;&#xA;      - name: Force push if clean&#xA;        if: steps.rebase.outputs.result == &#39;clean&#39;&#xA;        run: |&#xA;          git push --force-with-lease origin HEAD:refs/heads/${{ env.FORK_BRANCH }}&#xA;          echo &amp;quot;Rebase clean — ${{ env.FORK_BRANCH }} updated from upstream/${{ env.UPSTREAM_BRANCH }}.&amp;quot;&#xA;&#xA;      - name: Create or update conflict issue&#xA;        if: steps.rebase.outputs.result == &#39;conflict&#39;&#xA;        uses: peter-evans/create-issue-from-file@v5&#xA;        with:&#xA;          title: &#39;Upstream Sync Conflict&#39;&#xA;          content-filepath: .github/conflict-notice.md&#xA;          labels: upstream-sync, conflict&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You need to create a &lt;code&gt;.github/conflict-notice.md&lt;/code&gt; file with just some&#xA;text/instructions in case rebase fails. It&#39;ll open an issue informing you about&#xA;it.&lt;/p&gt;&#xA;</description><pubDate>Sat, 06 Dec 2025 00:00:00 +0000</pubDate><guid>https://duti.dev/snippets/github-fork/</guid></item></channel></rss>