<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[zkevm.dev]]></title><description><![CDATA[The unspent text output of a neurotic phd game theorist.]]></description><link>https://blog.zkevm.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1746078217259/d6ee0e44-431f-440d-9c98-249b840b24bf.png</url><title>zkevm.dev</title><link>https://blog.zkevm.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 10 Apr 2026 13:42:02 GMT</lastBuildDate><atom:link href="https://blog.zkevm.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Exploiting the zkEVM: Uncovering Soundness and Composition Vulnerabilities in ZK Circuits]]></title><description><![CDATA[zkevm.dev
Lately, I’ve been diving deeper into Validity Proofs and Zero-Knowledge (ZK) circuits. It's fascinating stuff, especially with its relevance to scaling solutions like the zkEVM (zkRollups). I wanted to jot down some thoughts, examples, and ...]]></description><link>https://blog.zkevm.dev/exploiting-the-zkevm-uncovering-soundness-and-composition-vulnerabilities-in-zk-circuits</link><guid isPermaLink="true">https://blog.zkevm.dev/exploiting-the-zkevm-uncovering-soundness-and-composition-vulnerabilities-in-zk-circuits</guid><category><![CDATA[Python]]></category><category><![CDATA[zkevm]]></category><category><![CDATA[zk-snark]]></category><category><![CDATA[zkrollup]]></category><category><![CDATA[Ethereum]]></category><category><![CDATA[defi]]></category><category><![CDATA[Security]]></category><category><![CDATA[AI]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Smart Contracts]]></category><category><![CDATA[Bitcoin]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Solidity]]></category><category><![CDATA[Cryptocurrency]]></category><dc:creator><![CDATA[zkevm]]></dc:creator><pubDate>Thu, 01 May 2025 05:39:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746077927376/910b6194-cd26-4571-8473-f90252796c8a.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-zkevmdevhttpszkevmdev"><a target="_blank" href="https://zkevm.dev"><strong>zkevm.dev</strong></a></h3>
<p>Lately, I’ve been diving deeper into <a target="_blank" href="https://zkproof.org/about/?amp"><strong>Validity Proofs</strong></a> and Zero-Knowledge (ZK) <a target="_blank" href="https://tlu.tarilabs.com/cryptography/rank-1">circuits</a>. It's fascinating stuff, especially with its relevance to scaling solutions like the <a target="_blank" href="https://scroll.io/blog/zkevm"><strong>zkEVM (zkRollups)</strong></a>. I wanted to jot down some thoughts, examples, and potential vulnerabilities / attack vectors – partly notes for myself, partly sharing my research. The core idea is proving a computation happened correctly, often without revealing <em>all</em> the inputs, which is key for both privacy and scalability.</p>
<hr />
<h2 id="heading-the-basics-sorting-numbers-from-code-to-constraints">The Basics: Sorting Numbers (From Code to Constraints)</h2>
<p>Let's start with a familiar task: sorting three numbers, <code>A</code>, <code>B</code>, <code>C</code>.</p>
<p>In Python, it's procedural:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Assume A, B, C are defined</span>
<span class="hljs-keyword">if</span> A &gt; B:
  A, B = B, A
<span class="hljs-keyword">if</span> A &gt; C:
  A, C = C, A
<span class="hljs-keyword">if</span> B &gt; C:
  B, C = C, B

<span class="hljs-comment"># Now A, B, C are sorted</span>
print(A, B, C)
</code></pre>
<p>This code <em>executes</em> the sort.</p>
<p>Now, think about proving this sort happened correctly using a ZK circuit, similar to how computations are verified in various <a target="_blank" href="https://decrypt.co/resources/what-is-zkevm?amp=1">Ethereum Layer 2 networks</a>. We shift from <em>how</em> to sort to <em>enforcing properties</em> of the result.</p>
<pre><code class="lang-plaintext">// Circuit: SortVerifier
input: A, B, C // Public inputs

// Witness: A', B', C' (Provided by the ZK prover)
// These represent the claimed sorted output

// Constraint 1: Permutation Check
// Ensures the output contains the same elements as the input
enforce({A', B', C'} is a permutation of {A, B, C})

// Constraint 2: Ordering Check
// Ensures the output is actually sorted
enforce(A' &lt;= B')
enforce(B' &lt;= C')

output: A', B', C' // The claimed sorted values
</code></pre>
<p>The key difference? The circuit defines <em>constraints</em>. These <code>enforce</code> statements are the heart of it – they get compiled down into polynomial equations or other mathematical forms that the underlying <a target="_blank" href="https://docs.gnark.consensys.io/Concepts/zkp">ZK-SNARK</a> or <a target="_blank" href="https://docs.starknet.io/">ZK-STARK</a> system uses. The <em>prover</em> finds the <strong>witness</strong> values (<code>A'</code>, <code>B'</code>, <code>C'</code>) that satisfy these constraints and generates a compact ZK proof. The <em>verifier</em> (e.g., a smart contract on L1 for a zkEVM rollup) just needs to check this proof against the public inputs and the <a target="_blank" href="https://tlu.tarilabs.com/cryptography/r1cs-bulletproofs/mainreport.html#arithmetic-circuits">circuit</a> definition, without re-executing the sort.</p>
<p>Our goal is to design constraints such that:</p>
<ol>
<li><p>Valid executions <em>always</em> satisfy all constraints (allowing a proof).</p>
</li>
<li><p>Invalid executions can <em>never</em> satisfy all constraints (preventing a proof).</p>
</li>
</ol>
<hr />
<h2 id="heading-a-more-involved-example-data-validation-in-zk">A More Involved Example: Data Validation in ZK</h2>
<p>Let's apply this to data validation. Imagine validating a data structure <code>h</code> composed of 'B' and 'V' elements (<em>max 5 of each, maybe zero</em>).</p>
<p><strong>Step 1: Decomposition Circuit</strong></p>
<p>Break down <code>h</code> – the prover provides the components as witness data.</p>
<pre><code class="lang-plaintext">// Circuit: Decomposer
input: data_structure h // Public input

// Witness: B[], V[] (Private data provided by prover)
output: elements_B B[], elements_V V[]

// Constraint: h must be composed *only* from the provided B and V arrays
enforce(h is composed from B[] and V[]) // Constraint on inputs &amp; witness
</code></pre>
<p><strong>Step 2: Element Validation Circuit (Type B)</strong></p>
<p>Check each 'B' element against a property, say <code>is_valid(b)</code>. This involves state transitions, much like stepping through execution traces in a zkEVM.</p>
<pre><code class="lang-plaintext">// Circuit: CheckerB
input: elements_B B[] // Could be output from Decomposer

// Uses intermediate variables (part of the witness)
// Assume max 5 elements

for i in range(5) {
  // Intermediate witness: flag_i, current_b_i, remaining_B_i+1
  // State B_i transitions to B_i+1

  // Constraint: Set flag based on whether B_i is non-empty
  enforce(if B_i is nonempty then flag_i is true, else flag_i is false)

  // Constraint: Pop element if flag is true
  enforce(if flag_i is true, then (current_b_i, B_i+1) = pop(B_i))

  // Constraint: Check validity if flag is true
  enforce(if flag_i is true, then is_valid(current_b_i) is true)
}

// No explicit output; satisfaction of constraints IS the result.
output: None
</code></pre>
<p>These intermediate variables (<code>flag_i</code>, <code>current_b_i</code>, etc.) are crucial parts of the <strong>witness</strong> the prover must generate. They don't exist in the original Python code but are necessary to link the steps together arithmetically for the proof system.</p>
<p>We'd need a similar <code>CheckerV</code> circuit.</p>
<p><strong>Step 3: The Main Validation Circuit (Composition)</strong></p>
<p>Combine the circuits. <code>fit(circuit, input, output)</code> means the inputs/outputs satisfy that circuit's constraints. This composition is fundamental in complex ZK systems like zkEVMs, where proofs for individual opcodes or steps are combined to prove a whole transaction or block.</p>
<pre><code class="lang-plaintext">// Circuit: MainValidator
input: data_structure h // Public input

// Intermediate witness: B[], V[] from Decomposer
intermediate: elements_B B[], elements_V V[]

// Constraint 1: Decompose h correctly using Decomposer circuit
enforce(fit(Decomposer, h, (B[], V[])))

// Constraint 2: Check B elements if needed, using CheckerB circuit
enforce(if B[] is nonempty, then fit(CheckerB, B[], ()))

// Constraint 3: Check V elements if needed, using CheckerV circuit
enforce(if V[] is nonempty, then fit(CheckerV, V[], ()))

output: None // Validity proven by the existence of the ZK proof itself
</code></pre>
<p><strong>Why</strong> <code>output: None</code> Still Makes Sense</p>
<p>In ZK proofs, you don't typically get a boolean "<em>true/false</em>" output from the circuit logic itself. The ZK proof <em>is</em> the output:</p>
<ul>
<li><p>If the prover can generate a valid proof that the verifier accepts, it means all constraints were satisfied for the given public inputs and the (potentially private) witness. The statement is implicitly "<em>true</em>".</p>
</li>
<li><p>If no such proof can be generated (because constraints conflict), the statement is implicitly "<em>false</em>".</p>
</li>
</ul>
<p><strong>Here's a point to consider:</strong></p>
<p>Look at <code>CheckerB</code> / <code>CheckerV</code> and <code>MainValidator</code>. Assumption: max 5 elements each. Where's the potential bug in this setup?</p>
<blockquote>
<p><em>Pause and think... what if the input data violates the assumptions?</em></p>
<p>…</p>
<p><em>Consider the loop boundary</em></p>
<p>…</p>
<p><em>Have an idea?</em></p>
</blockquote>
<p><strong>The potential vulnerability here is related to assumptions.</strong> The bug hinges on that "<em>max 5 elements</em>" assumption. What if the <em>actual</em> input <code>h</code> can yield <em>6</em> 'B' elements, and the 6th one is invalid (<code>is_valid(b6)</code> is false)?</p>
<p>Our <code>CheckerB</code> only loops 5 times. It checks <code>b0</code> through <code>b4</code> and stops. The 6th invalid element <code>b6</code> is never examined. A prover could still potentially satisfy all constraints for the first 5 steps and generate a proof, effectively vouching for an invalid <code>h</code>.</p>
<p>This is a <strong>soundness bug</strong>. An invalid state/input can lead to an accepted proof. In a zkEVM context, this would be catastrophic – allowing invalid transactions or state changes to be finalized on L1.</p>
<p><strong>The "Fix" and its Trade-off</strong></p>
<p>What if we add <code>enforce(B5 is empty)</code> to <code>CheckerB</code>? Now, the 6-element invalid input fails the check. Good.</p>
<p>But... what if a <em>valid</em> <code>h</code> genuinely has 6 <em>valid</em> 'B' elements? Now, our modified circuit <em>cannot</em> prove this valid input, because <code>B5</code> won't be empty.</p>
<p>This is a <strong>completeness bug</strong>. A valid state/input cannot be proven. For a zkEVM, this means valid transactions could be unfairly rejected or censored. Balancing soundness and completeness under all input conditions is critical.</p>
<hr />
<h2 id="heading-a-zk-processing-pipeline">A ZK Processing Pipeline</h2>
<p>Let's model a simple processing pipeline, analogous to proving sequential operations in a zkEVM (like executing <a target="_blank" href="https://ethereum.org/en/developers/docs/evm/">EVM</a> opcodes one after another). Max limit 5 again.</p>
<p><strong>Inputs:</strong> <code>raw_B[]</code>, <code>raw_V[]</code></p>
<p><strong>Circuits:</strong></p>
<ol>
<li><p><code>ProcessorB</code>: Processes <code>raw_B[]</code> -&gt; <code>cooked_B[]</code>. (Think: Prove execution of some function/opcode)</p>
</li>
<li><p><code>ProcessorV</code>: Processes <code>raw_V[]</code> -&gt; <code>cooked_V[]</code>.</p>
</li>
<li><p><code>Assembler</code>: Combines <code>cooked_B[]</code>, <code>cooked_V[]</code> -&gt; <code>output_H</code>. (Think: Prove final state assembly)</p>
</li>
</ol>
<pre><code class="lang-plaintext">// Circuit: ProcessorB
input: raw_elements_B rB[]
// ... (loop 5 times, process rb_i -&gt; cb_i, push to cB) ...
// Constraints define the state transition for each step
enforce(if flag_i is true, then cb_i = process_element_B(rb_i)) // The core logic
enforce(if flag_i is true, then cB_i+1 = cB_i.push(cb_i)) // State update
// ... other constraints for flags, popping, empty checks ...
output: cooked_elements_B cB[]
</code></pre>
<pre><code class="lang-plaintext">// Circuit: ProcessorV (Similar)
input: raw_elements_V rV[]
output: cooked_elements_V cV[]
</code></pre>
<pre><code class="lang-plaintext">// Circuit: Assembler
input: cooked_elements_B cB[], cooked_elements_V cV[]
enforce(output_H is assembled_from(cB[], cV[])) // Final check
output: final_structure output_H
</code></pre>
<p><strong>The Main Pipeline Circuit (Composition)</strong></p>
<p>This circuit proves the correct sequence and data flow between the processing steps.</p>
<pre><code class="lang-plaintext">// Circuit: MainPipeline
input: raw_elements_B rB[], raw_elements_V rV[] // Initial state/input

// Intermediate witness: cB[], cV[] (Results of processing steps)
intermediate: cooked_elements_B cB[], cooked_elements_V cV[]

// Constraint: Prove ProcessorB execution if needed
// Recall: fit(Circuit, inputs, outputs) checks constraint satisfaction
enforce(if rB[] is nonempty, then fit(ProcessorB, rB[], cB[]))
// Add constraint: if rB[] is empty, enforce cB[] is empty too?

// Constraint: Prove ProcessorV execution if needed
enforce(if rV[] is nonempty, then fit(ProcessorV, rV[], cV[]))
// Add constraint: if rV[] is empty, enforce cV[] is empty too?

// Constraint: Prove Assembler execution using intermediate results
enforce(fit(Assembler, (cB[], cV[]), output_H))

output: final_structure output_H // Final proven state/output
</code></pre>
<p><strong>Now, let's examine the pipeline:</strong></p>
<p>Focus on this processing structure. Assume <code>process_element_B</code>, <code>process_element_V</code>, <code>assembled_from</code> are correct <em>in isolation</em>.</p>
<blockquote>
<p><em>Where might a bug lie in the composition or state handling between these provable steps?</em></p>
<p>It's not the length bug again. Think about what the prover supplies and what constraints ensure the integrity <em>across</em> the steps. This kind of inter-circuit consistency is a major challenge when building complex systems like any of the various <a target="_blank" href="https://decrypt.co/resources/what-is-zkevm?amp=1">Ethereum Layer 2 networks</a>.</p>
<p><em>(Hint: What guarantees that the</em> <code>cB[]</code> used by the <code>Assembler</code> is <em>actually</em> the one produced by <code>ProcessorB</code>?)</p>
<p><em>The potential issue here is subtle and relates to the composition of these circuits. Think about the guarantees (or lack thereof) regarding data passed between them.</em></p>
</blockquote>
<hr />
<p><strong><mark>Can you spot the bug?</mark></strong></p>
<p><em>(Since this is an open scenario, you might find some answers that seem reasonable. However, if you're not 100% confident in your answer, it's likely you haven't found the one I hope you to find.)</em></p>
<p>That's the brain dump for now! Building robust ZK circuits, especially for something as complex as a zkEVM, requires meticulous handling of constraints, witness data, assumptions, and the composition of proofs. It's where cryptography, logic, and software engineering meet security.</p>
<h3 id="heading-zkevmdevhttpszkevmdev-1"><a target="_blank" href="https://zkevm.dev"><strong>zkevm.dev</strong></a></h3>
]]></content:encoded></item><item><title><![CDATA[Cursor Version Archive: Official Download Links for Mac, Windows, and Linux]]></title><description><![CDATA[In the rapidly evolving landscape of AI-powered developer tools, Cursor AI has become a favorite for many looking to supercharge their coding productivity. As an AI-first fork of VS Code, it offers deep integration of artificial intelligence into the...]]></description><link>https://blog.zkevm.dev/cursor-version-archive-official-download-links-for-mac-windows-and-linux</link><guid isPermaLink="true">https://blog.zkevm.dev/cursor-version-archive-official-download-links-for-mac-windows-and-linux</guid><category><![CDATA[cursor]]></category><category><![CDATA[cursor IDE]]></category><category><![CDATA[cursor ai]]></category><category><![CDATA[release]]></category><category><![CDATA[download]]></category><category><![CDATA[links]]></category><category><![CDATA[version]]></category><category><![CDATA[archive]]></category><category><![CDATA[macOS]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Python]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><dc:creator><![CDATA[zkevm]]></dc:creator><pubDate>Thu, 03 Apr 2025 18:58:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743706561765/8d0d1cc2-743f-4ea7-834b-8d91f0b1a4a7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the rapidly evolving landscape of AI-powered developer tools, <a target="_blank" href="https://cursor.com">Cursor AI</a> has become a favorite for many looking to supercharge their coding productivity. As an AI-first fork of VS Code, it offers deep integration of artificial intelligence into the coding workflow.</p>
<p>However, Cursor typically auto-updates, and the official website doesn't always provide easy access to older installers or even the latest installers immediately upon release. This can be frustrating if you encounter a bug in a new version, prefer an older UI, need a specific feature set, or simply can't find the download link easily.</p>
<p><strong>This post aims to solve that problem by serving as a living archive of official download links for various Cursor AI versions.</strong> Whether you need to downgrade, rollback, or simply find a specific stable release, you should find the links you need here for Mac, Windows, and Linux.</p>
<h2 id="heading-what-is-cursor">What is Cursor?</h2>
<p>Cursor is an innovative code editor built upon the foundation of VS Code but heavily modified to prioritize AI integration. It helps developers write, understand, debug, and refactor code faster using built-in AI chat, code generation, and analysis tools. Learn more at <a target="_blank" href="http://cursor.com">cursor.com</a>.</p>
<h2 id="heading-why-this-archive-exists-and-why-you-might-need-it">Why This Archive Exists (And Why You Might Need It)</h2>
<p>Maintaining this archive / list is crucial because:</p>
<ol>
<li><p><strong>Official Older Versions Are Hard to Find:</strong> Cursor's site focuses on the latest release.</p>
</li>
<li><p><strong>Release Access:</strong> Sometimes even the newest links aren't immediately prominent or easy to locate for all users after a release.</p>
</li>
<li><p><strong>Stability:</strong> Rollback to a known stable version if a new release introduces issues.</p>
</li>
<li><p><strong>User Preference:</strong> Stick with a version whose UI or features you prefer.</p>
</li>
<li><p><strong>Bug Avoidance:</strong> Downgrade to avoid specific bugs present in a newer version.</p>
</li>
<li><p><strong>Controlled Updates:</strong> Manually installing allows you (at least temporarily) to control your version, although you may need to manage update settings to prevent auto-updates if you wish to stay on an older version long-term.</p>
</li>
</ol>
<p>This archive empowers users to choose the exact Cursor AI version that fits their needs.</p>
<h2 id="heading-cursor-changelog">Cursor Changelog</h2>
<p>For the most detailed release notes, always consult the <a target="_blank" href="https://www.cursor.com/changelog"><strong>Official Cursor Changelog</strong></a>.</p>
<blockquote>
<h2 id="heading-important-security-notice"><mark>Important Security Notice</mark></h2>
<p><em>Your security is paramount. Download links have not been altered in any way</em>:</p>
<ul>
<li><p>✅ <strong>Official Links:</strong> All links compiled here point directly to official binaries published by the Cursor team (hosted on <a target="_blank" href="http://downloads.cursor.com"><code>downloads.cursor.com</code></a> or <a target="_blank" href="http://downloader.cursor.sh"><code>downloader.cursor.sh</code></a>).</p>
</li>
<li><p>💾 <strong>Backups:</strong> Before installing any version (older or newer), it's <em>strongly advised</em> to back up your Cursor settings and critical projects. Better safe than sorry!</p>
</li>
</ul>
</blockquote>
<h2 id="heading-official-cursor-version-download-archive">Official Cursor Version Download Archive</h2>
<p>Find the version you need below. Use <strong>Ctrl+F</strong> or <strong>Cmd+F</strong> in your browser to quickly search for a specific version number (e.g., <code>0.48.7</code>).</p>
<div class="hn-embed-widget" id="cursor-archive"></div>]]></content:encoded></item><item><title><![CDATA[Say Goodbye to Free Trial Limits: Reset Your Cursor AI Machine ID with This Python Script]]></title><description><![CDATA[I. The Spark: When AI Hits the Paywall
Cursor’s AI features are a game-changer, supercharging development on top of the familiar VS Code base. That intelligent code completion, the quick refactoring, the chat-based assistance – it feels like magic......]]></description><link>https://blog.zkevm.dev/say-goodbye-to-trial-limits-reset-your-cursor-ai-machine-id-with-this-python-script</link><guid isPermaLink="true">https://blog.zkevm.dev/say-goodbye-to-trial-limits-reset-your-cursor-ai-machine-id-with-this-python-script</guid><category><![CDATA[free trial bypass]]></category><category><![CDATA[Trial Limit]]></category><category><![CDATA[bypass trial]]></category><category><![CDATA[Cursor free trial]]></category><category><![CDATA[Reset Machine ID]]></category><category><![CDATA[Machine ID]]></category><category><![CDATA[cursor]]></category><category><![CDATA[Python]]></category><category><![CDATA[vulnerability]]></category><category><![CDATA[Python script]]></category><category><![CDATA[Linux]]></category><category><![CDATA[bypass]]></category><category><![CDATA[hack]]></category><category><![CDATA[appimage]]></category><category><![CDATA[cursor ai]]></category><dc:creator><![CDATA[zkevm]]></dc:creator><pubDate>Thu, 03 Apr 2025 05:36:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743654044491/24e1dc0e-608d-4112-84c9-c1e3c795388c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-i-the-spark-when-ai-hits-the-paywall">I. The Spark: When AI Hits the Paywall</h2>
<p><a target="_blank" href="https://www.cursor.com/">Cursor’s</a> AI features are a game-changer, supercharging development on top of the familiar <a target="_blank" href="https://code.visualstudio.com/">VS Code</a> base. That intelligent code completion, the quick refactoring, the chat-based assistance – it feels like magic... until the magic runs out.</p>
<p>For Linux users running the AppImage version, there's a common hurdle: the free trial limit. Hit that quota (currently seems to be 3 accounts) tied to your unique machine ID, and you're suddenly staring at that frustrating message:</p>
<blockquote>
<p>You’ve exceeded the number of free trial accounts allowed for machine-id: 12345XXXXX-XXXXXXX…</p>
</blockquote>
<p>Suddenly, your options narrow:</p>
<ol>
<li><p><strong>Jump through hoops setting up your entire dev environment on a new machine and enjoy a few more free trial accounts before having to do it all over again</strong> (<em>assuming you even have access to an unlimited number of dev servers</em>).</p>
</li>
<li><p><strong>Shell out for a subscription</strong>, (around <strong>$384/per user/per year</strong> at the time of writing).</p>
</li>
</ol>
<p>or watch your powerful AI assistant revert to a standard VS Code experience.</p>
<p>Sound familiar? I hit this wall too. Faced with losing the AI capabilities or paying up, and considering Cursor's relationship with the open-source community, I got curious. How exactly <em>does</em> Cursor's AppImage identify a machine? Could that mechanism be... adjusted? After some digging into the AppImage internals, I found that the answer was yes. The vulnerability involves more than just clearing config files, touching on how the application itself references system identifiers.</p>
<p>The good news? I've automated the entire reset process into a <a target="_blank" href="https://gist.github.com/gweidart/81a89c0873b2426515fa9f0c9f535c99"><strong>Python script</strong></a>. This post is your deep dive into how that script works, exploiting the way Cursor handles machine IDs to give you a fresh start with a single command, and how you can use it yourself. Let's get started.</p>
<h2 id="heading-ii-what-is-a-machine-id-how-cursor-identifies-your-machine">II. What is a Machine ID? How Cursor Identifies Your Machine</h2>
<p>Software often needs a way to distinguish between different installations or machines. On Linux, a common system-wide unique identifier is found in <code>/etc/machine-id</code>. Applications might read this file directly or use libraries that access it.</p>
<p>Cursor, based on its configuration file (<code>storage.json</code>), appears to use several identifiers:</p>
<ul>
<li><p><code>telemetry.machineId</code>: Likely a primary identifier for telemetry.</p>
</li>
<li><p><code>telemetry.macMachineId</code>: Another ID, potentially mimicking a MAC address format.</p>
</li>
<li><p><code>telemetry.devDeviceId</code>: Seems like a standard device identifier (UUID).</p>
</li>
<li><p><code>telemetry.sqmId</code>: Another UUID, often related to software quality metrics.</p>
</li>
</ul>
<p>These values are stored in <code>~/.config/Cursor/User/globalStorage/storage.json</code>. If Cursor <em>also</em> uses the static <code>/etc/machine-id</code> directly within its code, simply changing <code>storage.json</code> might not be enough for a complete identity reset. The script I developed addresses both the stored configuration <em>and</em> how the application determines one of these IDs internally.</p>
<h2 id="heading-iii-under-the-hood-how-the-script-works">III. Under the Hood: How the Script Works</h2>
<p>The script tackles the machine ID reset with a two-pronged attack / approach: it generates new IDs and updates the configuration file, <em>and</em> it patches the Cursor application code within the AppImage itself.</p>
<h3 id="heading-part-1-refreshing-the-configuration-storagejson">Part 1: Refreshing the Configuration (<code>storage.json</code>)</h3>
<ol>
<li><p><strong>Generate New IDs:</strong> The script uses Python's <code>uuid</code> module to create fresh, random identifiers:</p>
<ul>
<li><p>A long random ID for <code>telemetry.machineId</code> (combining two UUIDv4 hex strings).</p>
</li>
<li><p>A MAC-like ID for <code>telemetry.macMachineId</code> (using parts of a UUIDv4).</p>
</li>
<li><p>Standard UUIDv4s for <code>telemetry.devDeviceId</code> and <code>telemetry.sqmId</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Locate &amp; Backup:</strong> It finds the <code>storage.json</code> file in the standard Cursor config path. Crucially, it creates a backup (<code>storage.json.bak</code>) before making any changes.</p>
</li>
<li><p><strong>Update JSON:</strong></p>
<ul>
<li><p>It reads the existing JSON data.</p>
</li>
<li><p>It updates the values for the four specific keys (<code>telemetry.machineId</code>, <code>telemetry.macMachineId</code>, <code>telemetry.devDeviceId</code>, <code>telemetry.sqmId</code>) with the newly generated IDs.</p>
</li>
<li><p>It offers an <em>option</em> to use the external command-line tool <code>jq</code> for this update if it's installed and the user prefers it (as <code>jq</code> can sometimes be more robust for complex JSON manipulation via CLI).</p>
</li>
<li><p>If <code>jq</code> isn't used or available, it falls back to Python's built-in <code>json</code> module to write the updated data back to <code>storage.json</code>, ensuring proper formatting.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-part-2-patching-the-application-appimage-internals">Part 2: Patching the Application (AppImage Internals)</h3>
<p>This is the more invasive, but potentially necessary, step.</p>
<ol>
<li><p><strong>Extract AppImage:</strong> The script runs the Cursor AppImage file with the <code>--appimage-extract</code> argument. This unpacks the entire application into a temporary directory, typically named <code>squashfs-root</code>.</p>
</li>
<li><p><strong>Modify JavaScript:</strong> It targets specific JavaScript files within the extracted application core, namely:</p>
<ul>
<li><p><code>squashfs-root/usr/share/cursor/resources/app/out/main.js</code></p>
</li>
<li><p><code>squashfs-root/usr/share/cursor/resources/app/out/vs/code/node/cliProcessMain.js</code> <em>(Note: These paths might change in future Cursor versions)</em></p>
</li>
</ul>
</li>
<li><p><strong>Apply Regex Patch:</strong> Using Python's <code>re.sub</code> (regular expressions), it searches these files for code patterns that look like string literals containing <code>/etc/machine-id</code> (e.g., <code>".../etc/machine-id..."</code>). It replaces these occurrences with the literal string <code>"uuidgen"</code>.</p>
<ul>
<li><strong>Why?</strong> This changes Cursor's behavior. Instead of trying to read the <em>static</em> system ID from <code>/etc/machine-id</code>, the modified code will now execute the <code>uuidgen</code> command <em>whenever</em> it needs that specific ID. <code>uuidgen</code> generates a <em>brand new</em> random UUID each time it's run, effectively decoupling this part of Cursor's identification from the static system ID.</li>
</ul>
</li>
<li><p><strong>Repack AppImage:</strong> The script uses the <code>appimagetool</code> utility (it downloads a compatible version using <code>requests</code> if not found at <code>/tmp/appimagetool</code>) to repack the modified <code>squashfs-root</code> directory back into an AppImage. This newly created AppImage overwrites the original one provided by the user.</p>
</li>
</ol>
<h3 id="heading-behind-the-scenes-helper-functions">Behind the Scenes: Helper Functions</h3>
<p>The script also includes several helper functions: <code>argparse</code> to handle the <code>--appimage</code> command-line argument; <code>os</code>, <code>pwd</code>, and <code>shutil</code> for file operations (path validation, finding home dir, copying backups, checking for <code>uuidgen</code>/<code>jq</code>); <code>psutil</code> to find and forcefully kill any running Cursor processes <em>before</em> making changes (to prevent conflicts); and <code>time.sleep</code> for brief pauses.</p>
<h2 id="heading-iv-get-started-running-the-reset-script">IV. Get Started: Running the Reset Script</h2>
<p>Ready to give it a try? Here's how:</p>
<h3 id="heading-prerequisites-what-youll-need">Prerequisites: What You'll Need</h3>
<ul>
<li><p>A <strong>Linux</strong> environment.</p>
</li>
<li><p><strong>Python 3</strong> installed.</p>
</li>
<li><p>The Python libraries <code>psutil</code> and <code>requests</code>. Install them if needed:</p>
<pre><code class="lang-bash">  pip install psutil requests
</code></pre>
</li>
<li><p>Your downloaded <strong>Cursor AppImage</strong> file (e.g., <code>Cursor-0.xx.x.AppImage</code>).</p>
<blockquote>
<p><em>You can download the official Cursor AppImage</em> <a target="_blank" href="https://www.cursor.com/downloads"><em>here</em></a></p>
</blockquote>
</li>
<li><p>The <code>uuidgen</code> command available (usually installed by default on most Linux distributions).</p>
</li>
<li><p><strong>(Optional)</strong> The <code>jq</code> command-line JSON processor if you want to use that update method.</p>
</li>
</ul>
<p>Here is the complete Python script <code>cursor_reset.py</code>):</p>
<div class="hn-embed-widget" id="cursor-reset"></div><p> </p>
<h3 id="heading-step-by-step-guide">Step-by-Step Guide</h3>
<ol>
<li><p><strong>Save the Script:</strong> Copy the Python script code (you can copy it directly from the gist embed above 👆) and save it to a file, for <code>cursor_reset.py</code>.</p>
</li>
<li><p><strong>Close Cursor:</strong> Ensure Cursor is <strong>completely closed</strong>. The script tries to kill processes, but it's best practice to close it manually first.</p>
</li>
<li><p><strong>Open Terminal:</strong> Launch your terminal.</p>
</li>
<li><p><strong>Navigate:</strong> Use <code>cd</code> to go to the directory where you saved <code>cursor_reset.py</code>.</p>
</li>
<li><p><strong>Run the Command:</strong> Execute the script, providing the path to your Cursor AppImage:</p>
<pre><code class="lang-bash"> python cursor_reset.py --appimage /path/to/your/Cursor-0.xx.x.AppImage
</code></pre>
<p> <em>(Replace</em> <code>/path/to/your/Cursor-0.xx.x.AppImage</code> with the actual path).</p>
</li>
<li><p><strong>Follow Prompts:</strong> If <code>jq</code> is detected, the script will ask if you want to use it (Y/N).</p>
</li>
<li><p><strong>Wait:</strong> The script will output its progress (killing processes, extracting, modifying, repacking). Extraction and repacking can take a minute or two.</p>
</li>
<li><p><strong>Check Success Message:</strong> Look for the "Successfully updated all IDs" message and the list of new IDs printed at the end.</p>
</li>
<li><p><strong>Launch Modified AppImage:</strong> Run your modified Cursor AppImage file as usual. It should now appear as a fresh installation regarding these specific IDs. Most importantly, you should now be able to <strong>create and use new free trial accounts</strong> over and over again on the same machine, even after hitting your initial <code>trial account limit</code>.</p>
<blockquote>
<p>Remember to run the Python script to patch your Cursor AppImage anytime you:</p>
<ul>
<li><p><strong>Exceed your machine's</strong> trial account limit</p>
</li>
<li><p>Update <strong>or</strong> download <strong>a new Cursor AppImage version.</strong></p>
</li>
</ul>
</blockquote>
</li>
</ol>
<h3 id="heading-quick-notes">Quick Notes:</h3>
<ul>
<li><p>The script <strong>will attempt to kill running Cursor processes</strong>. Save your work first!</p>
</li>
<li><p>It modifies your original AppImage file <strong>in place</strong>. Consider backing up the original AppImage beforehand if you're cautious.</p>
</li>
<li><p>A backup of your configuration is created (<code>storage.json.bak</code>) in the same directory as <code>storage.json</code>.</p>
</li>
</ul>
<h2 id="heading-v-behind-the-code-design-decisions">V. Behind the Code: Design Decisions</h2>
<p>Why go through the trouble of patching the AppImage?</p>
<h3 id="heading-why-patch-the-appimage-directly">Why Patch the AppImage Directly?</h3>
<p>Simply changing <code>storage.json</code> is not sufficient. This leads me to believe that Cursor's code <em>also</em> directly reads the static <code>/etc/machine-id</code> (or uses a library that does) for identification. The patch intercepts this retrieval <em>within the application code</em>, forcing it to use the dynamic <code>uuidgen</code> command instead, making that part of the identity reset more robust.</p>
<h3 id="heading-python-the-right-tool-for-the-job">Python: The Right Tool for the Job</h3>
<p>Python excels at this kind of task: managing files (<code>os</code>, <code>shutil</code>), handling JSON (<code>json</code>), running external processes (<code>subprocess</code>, <code>psutil</code>), making web requests (<code>requests</code>), and text manipulation (<code>re</code>).</p>
<h3 id="heading-adding-robustness-jq-psutil-requests">Adding Robustness (<code>jq</code>, <code>psutil</code>, <code>requests</code>)</h3>
<p>Using <code>psutil</code> provides a reliable way to find and terminate processes. Offering <code>jq</code> gives an alternative, often very robust, method for CLI JSON editing. Downloading <code>appimagetool</code> via <code>requests</code> makes the script more self-contained for users who don't have it installed system-wide.</p>
<h2 id="heading-vi-read-before-running-risks-and-responsibilities">VI. Read Before Running: Risks and Responsibilities</h2>
<p>Before you run this script, please understand:</p>
<ul>
<li><p><strong>Potential Instability:</strong> Modifying application internals (the JavaScript files) carries risks. Future Cursor updates might change the code structure, breaking the script or causing the modified AppImage to become unstable or fail to launch. <strong>Use at your own risk.</strong></p>
</li>
<li><p><strong>App Updates:</strong> When you download a <em>new</em> Cursor AppImage version, <strong>this patch will be gone</strong>. You will need to re-run the script on the new AppImage file if you want to apply the changes again.</p>
</li>
<li><p><strong>Terms of Service:</strong> Using scripts like this specifically to circumvent trial limitations or usage restrictions <strong>almost certainly violates Cursor's Terms of Service.</strong> Be aware of this. This post provides the script for technical exploration and understanding; how you use it is your responsibility.</p>
</li>
<li><p><strong>Linux AppImage Only:</strong> This script is tailored <em>specifically</em> for the Linux AppImage distribution of Cursor. It relies on Linux paths (<code>/etc/machine-id</code>), AppImage structure, and tools (<code>uuidgen</code>, <code>appimagetool</code>). It <strong>have not tested it</strong> on Windows, macOS, or other installation methods.</p>
</li>
<li><p><strong>Security Note:</strong> The script downloads <code>appimagetool</code> directly from its official GitHub releases page if needed. While this is standard practice for obtaining <code>appimagetool</code>, be aware you are downloading and executing code from the internet.</p>
</li>
</ul>
<h2 id="heading-vii-wrapping-up">VII. Wrapping Up</h2>
<p>This Python script offers a way to reset various identifiers (machine IDs) used by Cursor Linux AppImage, employing a combination of configuration file updates and direct patching of the application code. It automates a process that would be tedious and error-prone to do manually.</p>
<p>It serves as an interesting proof of concept for a vulnerability I found within Cursor’s Linux AppImages as well as exploiting that vulnerability and effectively modifying application behavior through Python scripting. However, remember the caveats – particularly regarding potential instability and the Terms of Service. Use it responsibly and understand the risks involved.</p>
]]></content:encoded></item></channel></rss>