<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GYPengDev</title>
    <description>The latest articles on DEV Community by GYPengDev (@yp_g_c8b59bf0f030337b4592).</description>
    <link>https://dev.to/yp_g_c8b59bf0f030337b4592</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4020853%2F84275bfb-d680-4a14-bf83-ada72f05e72d.png</url>
      <title>DEV Community: GYPengDev</title>
      <link>https://dev.to/yp_g_c8b59bf0f030337b4592</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yp_g_c8b59bf0f030337b4592"/>
    <language>en</language>
    <item>
      <title>Deobfuscating AES-Encrypted H5 API Parameters Directly in Your Proxy — No Manual Decryption Needed</title>
      <dc:creator>GYPengDev</dc:creator>
      <pubDate>Sat, 18 Jul 2026 14:07:06 +0000</pubDate>
      <link>https://dev.to/yp_g_c8b59bf0f030337b4592/deobfuscating-aes-encrypted-h5-api-parameters-directly-in-your-proxy-no-manual-decryption-needed-mgh</link>
      <guid>https://dev.to/yp_g_c8b59bf0f030337b4592/deobfuscating-aes-encrypted-h5-api-parameters-directly-in-your-proxy-no-manual-decryption-needed-mgh</guid>
      <description>&lt;p&gt;You're debugging an H5 page, and every API request body looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"6be9792e5ed250fc1a19e5bf2e7d0a3d78e7d0eb3d46b8c21a5c..."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, the actual parameters are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"keyword"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request body is AES-encrypted end-to-end. Your proxy shows you the hex gibberish. The backend says "your userId is wrong," and you have no idea what you actually sent.&lt;/p&gt;

&lt;p&gt;This is a common pattern for embedded H5 pages: the entire request body is symmetrically encrypted, and the backend decrypts it on arrival. It makes sense for security. But during development and debugging, it's a nightmare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can't write Mock rules because you can't see the params&lt;/li&gt;
&lt;li&gt;You can't replay with modified values because you'd need to re-encrypt manually&lt;/li&gt;
&lt;li&gt;You keep asking the backend team to temporarily disable encryption (famous last words before a production incident)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The root problem:&lt;/strong&gt; your proxy sees bytes but doesn't understand their meaning. You've set up DevPeek, configured the proxy, and trusted the HTTPS certificate — but if the payload is encrypted, you're still flying blind.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DevPeek Does Differently
&lt;/h2&gt;

&lt;p&gt;DevPeek's &lt;strong&gt;param transform&lt;/strong&gt; feature turns your proxy into a decryption engine. You tell it the key and algorithm once. From that point on, every matching request shows both the raw ciphertext and the decrypted plaintext side by side. If you turn on &lt;strong&gt;two-way transform&lt;/strong&gt;, you can edit the plaintext, and DevPeek re-encrypts it before sending.&lt;/p&gt;

&lt;p&gt;This is different from Charles or Fiddler — they're great at capturing traffic, but they don't know your encryption scheme. Every time you need to decode a request, you copy the hex, paste it into a script, run it, read the output, modify it, run the encrypt script, then paste the result back. DevPeek bakes this pipeline into the proxy itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Configure the Encrypt/Decrypt Rule
&lt;/h2&gt;

&lt;p&gt;Right-click any encrypted request → &lt;strong&gt;Param Transform&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bed53iijq9g9yceng0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0bed53iijq9g9yceng0w.png" width="371" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Param Transform Rules dialog, go to the &lt;strong&gt;Encrypt/Decrypt Rules&lt;/strong&gt; tab → Add → &lt;strong&gt;Built-in Transform&lt;/strong&gt; → select &lt;strong&gt;AES-GCM&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fszlpcxi0tk6g3jqzevbg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fszlpcxi0tk6g3jqzevbg.png" width="764" height="811"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the easiest place to mess up. Double-check every field:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Algorithm&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aes-gcm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key&lt;/td&gt;
&lt;td&gt;&lt;code&gt;342e668900166e5f6731f7a172f52862e3a43da54611519dec5246dadb6e7429&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SHA-256 derived 256-bit key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key Encoding&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Hex&lt;/strong&gt; ⚠️&lt;/td&gt;
&lt;td&gt;Key is a hex string, not UTF-8 text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IV / Nonce&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a1b2c3d4e5f6a7b8c9d0e1f2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed 12-byte IV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IV / Nonce Encoding&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Hex&lt;/strong&gt; ⚠️&lt;/td&gt;
&lt;td&gt;Same as above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ciphertext Input Encoding&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Hex&lt;/strong&gt; ⚠️&lt;/td&gt;
&lt;td&gt;Our demo outputs hex, not base64 (DevPeek defaults to base64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output Encoding&lt;/td&gt;
&lt;td&gt;UTF-8&lt;/td&gt;
&lt;td&gt;Plaintext encoding after decryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth Tag Length&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;GCM default (16 bytes)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The three ⚠️ rows are the most common pitfalls. DevPeek's built-in AES-GCM defaults to &lt;code&gt;keyEncoding: utf8&lt;/code&gt;, &lt;code&gt;ivEncoding: utf8&lt;/code&gt;, &lt;code&gt;inputEncoding: base64&lt;/code&gt;. Our demo uses hex for all three. If any one of these is wrong, decryption silently produces garbage. &lt;strong&gt;When decryption fails, check these three encodings first.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Save the rule. You should now see this AES-GCM rule in the Encrypt/Decrypt Rules list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Configure the Param Transform Rule
&lt;/h2&gt;

&lt;p&gt;Back in the Param Transform Rules window, add a new rule. This tells DevPeek three things: &lt;strong&gt;which request to match&lt;/strong&gt;, &lt;strong&gt;which field to transform&lt;/strong&gt;, and &lt;strong&gt;which rule to apply&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Match Conditions
&lt;/h3&gt;

&lt;p&gt;Toggle the match conditions to filter the target request. The right panel previews the current request's characteristics in real time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F29kg0sewyuncj48o18sc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F29kg0sewyuncj48o18sc.png" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URL Match&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/query&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Start with "Manual" to limit to the current request; switch to automatic once you're confident&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Transform Target
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Body → JSON → data&lt;/strong&gt; — tells DevPeek to decrypt the value of the &lt;code&gt;data&lt;/code&gt; field in the JSON body.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referenced Rule
&lt;/h3&gt;

&lt;p&gt;Select the AES-GCM rule you created in Step 1. The preview panel instantly shows the decrypted result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fb5gvj7wdge5e938fp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fb5gvj7wdge5e938fp0.png" width="799" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything is correct, you'll see &lt;code&gt;6be9792e...&lt;/code&gt; decoded to &lt;code&gt;{"userId":"123","keyword":"hello"}&lt;/code&gt;. That green light means it's working.&lt;/p&gt;

&lt;p&gt;Save the rule. Go back to the request list and re-inspect the same request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6p0vh6otutmtkuu8a0oj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6p0vh6otutmtkuu8a0oj.png" width="799" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;data&lt;/code&gt; field now shows the plaintext beneath the ciphertext. No more manual copy-paste decoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Decryption: Two-Way Transform and Replay
&lt;/h2&gt;

&lt;p&gt;Decryption is step one. The full debug loop is: &lt;strong&gt;see the params → spot the problem → edit → verify&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The param transform rule has a &lt;strong&gt;two-way transform&lt;/strong&gt; toggle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqyg22gcy3tnmdrv1kzsu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqyg22gcy3tnmdrv1kzsu.png" width="754" height="1203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before enabling two-way:&lt;/strong&gt; the ciphertext is editable and the plaintext is read-only. You can manually tweak the encrypted blob, but you'd need to construct valid encrypted data — which defeats the purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After enabling two-way:&lt;/strong&gt; the ciphertext becomes read-only and the plaintext becomes editable. Edit the plaintext, click Send, and DevPeek automatically re-encrypts using the same key and IV before dispatching:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ea4jp6h31pcwfhl1xoo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ea4jp6h31pcwfhl1xoo.png" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Real-world example: you suspect a specific error code only triggers when &lt;code&gt;userId&lt;/code&gt; is a certain value. With two-way transform enabled, modify &lt;code&gt;userId&lt;/code&gt; in the plaintext, DevPeek handles the encryption, and you can verify the hypothesis in one click — no scripting, no manual hex encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing With Non-Standard Encryption Schemes
&lt;/h2&gt;

&lt;p&gt;The example above uses fixed IV, auth tag appended to ciphertext, hex output — an AES-256-GCM setup. Real projects vary wildly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic IV&lt;/strong&gt;: the IV isn't hardcoded but sent alongside the request (e.g., in a header or another field)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote key fetch&lt;/strong&gt;: the key comes from a KMS service or a preliminary API call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different algorithms&lt;/strong&gt;: AES-CBC, AES-ECB, RSA, or custom schemes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When built-in transforms aren't enough, DevPeek has &lt;strong&gt;script transforms&lt;/strong&gt; — a sandboxed JavaScript runner with access to &lt;code&gt;util.crypto&lt;/code&gt;, &lt;code&gt;util.encode&lt;/code&gt;, &lt;code&gt;axios&lt;/code&gt;, &lt;code&gt;URL&lt;/code&gt;, and more. You can write a script that extracts the IV from a request header, fetches a key from a remote service, or calls an external decryption endpoint. See the &lt;a href="https://dev.to/docs/scripts-breakpoints/"&gt;Script Transforms docs&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works (In 30 Seconds)
&lt;/h2&gt;

&lt;p&gt;Param transform is a pipeline: &lt;strong&gt;match → extract → decrypt → display&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request hits DevPeek proxy
    │
    ▼ Match rules (URL + Method)
    │
    ▼ Extract target field (body:json:data)
    │
    ▼ Run referenced crypto rule (AES-256-GCM)
    │
    ▼ Store result in paramTransformDerived
    │   Original request body untouched
    │
    ▼ Display: request detail panel, Mock matching, replay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key design point: DevPeek &lt;strong&gt;never modifies the original request body&lt;/strong&gt;. The ciphertext stays as-is. Decrypted plaintext lives in a separate &lt;code&gt;paramTransformDerived&lt;/code&gt; field, used only for display and downstream features like Mock rule matching. A failed decryption won't corrupt your captured data.&lt;/p&gt;

&lt;p&gt;Built-in transforms cover AES-CBC, AES-ECB, AES-GCM, DES-CBC, 3DES-CBC, RSA, and Base64.&lt;/p&gt;




&lt;p&gt;API parameter encryption is increasingly common in H5 development. Debugging shouldn't be the bottleneck. A one-time rule configuration eliminates hours of repetitive manual decoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Docs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/docs/param-transform/"&gt;Param Transform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/docs/scripts-breakpoints/"&gt;Script Transforms &amp;amp; Breakpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/docs/mock/"&gt;Mock Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/docs/debug-replay/"&gt;Debug &amp;amp; Replay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/docs/quick-start/"&gt;Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're tired of manually decoding encrypted request bodies during debugging, &lt;a href="https://dev.to/"&gt;download DevPeek&lt;/a&gt; and set up your first decrypt rule using the steps above. Or start a discussion on &lt;a href="https://github.com/GYPengDev/devpeek/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; — I'd love to hear about your encryption setup.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>H5 Debug in Practice (2): Android WebView White Screen—From Console Remote Debug to Mock Validation</title>
      <dc:creator>GYPengDev</dc:creator>
      <pubDate>Tue, 14 Jul 2026 03:52:39 +0000</pubDate>
      <link>https://dev.to/yp_g_c8b59bf0f030337b4592/h5-debug-in-practice-2-android-webview-white-screen-from-console-remote-debug-to-mock-validation-318m</link>
      <guid>https://dev.to/yp_g_c8b59bf0f030337b4592/h5-debug-in-practice-2-android-webview-white-screen-from-console-remote-debug-to-mock-validation-318m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A campaign H5 white-screened on some Android devices after a button tap. All requests returned 200, but the page showed nothing. Using DevPeek Console to capture the live WebView runtime logs, remote eval to confirm a polyfill override, and Mock to verify the fallback UI under error conditions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If you've ever done H5 joint debugging, you've probably been there: the API returned 200, the data looked fine, but the page just wouldn't render. Worse still, the issue only appeared on one specific Android device's WeChat WebView—no vConsole on hand, and remote debug wouldn't connect.&lt;/p&gt;

&lt;p&gt;This article walks through the real investigation—from packet capture confirming the network was fine, to Console remotely locating the JS runtime exception, to Mock validating the fallback UI—all without repackaging the H5.&lt;/p&gt;




&lt;h2&gt;
  
  
  White screen
&lt;/h2&gt;

&lt;p&gt;It happened during a campaign H5 stress test.&lt;/p&gt;

&lt;p&gt;QA posted a screen recording in the group:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Samsung S23, Android 14, opened the campaign page in WeChat, tapped &lt;strong&gt;Join Now&lt;/strong&gt;—screen goes white."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What made it tricky was that the page wasn't completely broken.&lt;/p&gt;

&lt;p&gt;It loaded fine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Home content displayed normally;&lt;/li&gt;
&lt;li&gt;The button was tappable;&lt;/li&gt;
&lt;li&gt;A loading indicator appeared after tapping;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But then the screen went white.&lt;/p&gt;

&lt;p&gt;iOS was fine. Other Android devices were fine too.&lt;/p&gt;

&lt;p&gt;First suspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript runtime error?&lt;/li&gt;
&lt;li&gt;API response breaking the render?&lt;/li&gt;
&lt;li&gt;Android WebView compatibility issue?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Started with the network layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture looked normal, page still crashed
&lt;/h2&gt;

&lt;p&gt;First, the API request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/campaign/join
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;200 OK
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request parameters checked out. Response structure was as expected.&lt;/p&gt;

&lt;p&gt;At least the API wasn't the problem. But the page was still white.&lt;/p&gt;

&lt;p&gt;This is the tricky kind of issue: the request succeeded, the page loaded, but something failed mid-execution. Without runtime logs, you're guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  vConsole and Remote Debug weren't a good fit here
&lt;/h2&gt;

&lt;p&gt;For H5 debugging, vConsole is usually the first thought. But this campaign page was already live—we couldn't repack it with vConsole injected. And the issue only happened on specific Android devices.&lt;/p&gt;

&lt;p&gt;Chrome Remote Debug was tried too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome://inspect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It could connect to the WebView. But the error happened &lt;em&gt;after&lt;/em&gt; the button tap. By the time the page refreshed, the error was gone.&lt;/p&gt;

&lt;p&gt;On top of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebView debug flag requires client-side support;&lt;/li&gt;
&lt;li&gt;USB connection has environment dependencies;&lt;/li&gt;
&lt;li&gt;The live version couldn't be repeatedly adjusted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem was: the WeChat WebView on the phone had already hit an error, and I needed to see what it saw at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the WebView Console first
&lt;/h2&gt;

&lt;p&gt;The test device connected to DevPeek through the proxy.&lt;/p&gt;

&lt;p&gt;Steps completed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install the HTTPS certificate;&lt;/li&gt;
&lt;li&gt;Configure the proxy;&lt;/li&gt;
&lt;li&gt;Enable SSL decryption for the target domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full checklist: &lt;a href="https://devpeek.ypgao.com/en/docs/debug-replay/" rel="noopener noreferrer"&gt;mobile web debugging&lt;/a&gt;; if the preview stays on "Waiting for device connection", check the &lt;a href="https://devpeek.ypgao.com/en/docs/faq/" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the DevPeek &lt;strong&gt;Debug&lt;/strong&gt; tab, select the device. QA reopened the campaign page.&lt;/p&gt;

&lt;p&gt;A red line appeared immediately in the Console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Uncaught&lt;/span&gt; &lt;span class="nx"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;

&lt;span class="nf"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;IntersectionObserver&lt;/span&gt; &lt;span class="nx"&gt;polyfill&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;vendors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1847&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspz60nq0lgxl8yxvhqiy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspz60nq0lgxl8yxvhqiy.png" alt="DevPeek Debug Console: errors and remote eval" width="799" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Real device console output streams to the debug panel.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The direction was clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root cause: polyfill overrode the native implementation
&lt;/h2&gt;

&lt;p&gt;This involves &lt;code&gt;IntersectionObserver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Android 14's WebView natively supports this API. But the project still loaded an old polyfill, which overrode the native implementation. So the page was running the polyfill version instead of the native &lt;code&gt;IntersectionObserver&lt;/code&gt;—and the two implementations had compatibility differences.&lt;/p&gt;

&lt;p&gt;A quick remote eval in Console confirmed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Android&lt;/span&gt;&lt;span class="se"&gt;\s([\d&lt;/span&gt;&lt;span class="sr"&gt;.&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// "14"  ——confirmed Android version&lt;/span&gt;

&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// "function"  ——confirmed API exists&lt;/span&gt;

&lt;span class="nx"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;native code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// false  ——confirmed not running native implementation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Root cause confirmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue located, but testing wasn't done
&lt;/h2&gt;

&lt;p&gt;The frontend team started fixing the polyfill. But the stress test window was still open.&lt;/p&gt;

&lt;p&gt;QA needed to confirm: if the API returns error states, does the page fall back correctly?&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Campaign ended;&lt;/li&gt;
&lt;li&gt;User not authorized;&lt;/li&gt;
&lt;li&gt;Insufficient inventory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These scenarios can't wait for backend cooperation every time. We needed to simulate API responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Mock to verify error scenarios
&lt;/h2&gt;

&lt;p&gt;In the DevPeek capture list, find &lt;code&gt;POST /api/campaign/join&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a Mock rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match conditions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL contains &lt;code&gt;campaign/join&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Method is POST&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Response modification:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10086&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Campaign ended"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and enable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkcsnjhdhbu9mw6kpo9yg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkcsnjhdhbu9mw6kpo9yg.png" alt="DevPeek Mock rule editor: match conditions and response modification" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mock rules take effect immediately, no proxy restart.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;QA tapped the button again. This time, no white screen—the page showed "Campaign ended" as expected. Fallback logic verified.&lt;/p&gt;

&lt;p&gt;To test other error scenarios, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Campaign not available"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just update the Mock response and quickly verify different error states.&lt;/p&gt;

&lt;p&gt;No need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify the backend API;&lt;/li&gt;
&lt;li&gt;Wait for API deployment;&lt;/li&gt;
&lt;li&gt;Repack the H5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mock rules are persistent—they survive closing and reopening DevPeek. Remember to disable or delete temporary rules after the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full investigation flow
&lt;/h2&gt;

&lt;p&gt;The complete debug chain for this case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Button tap
    ↓
Capture confirms API returned 200
    ↓
Console captures WebView runtime error
    ↓
Remote eval confirms polyfill override
    ↓
Fix confirmed
    ↓
Mock simulates error API responses
    ↓
Fallback UI verified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throughout the entire process, no H5 repack was needed, and no client-side debug mode was required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this went smoothly
&lt;/h2&gt;

&lt;p&gt;In similar past situations, multiple tools were usually needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture tool to confirm requests;&lt;/li&gt;
&lt;li&gt;vConsole to check runtime logs;&lt;/li&gt;
&lt;li&gt;Another tool to modify API responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each tool solved part of the problem. But during investigation, you'd often wonder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where was that error log again?&lt;/li&gt;
&lt;li&gt;What did that API return?&lt;/li&gt;
&lt;li&gt;Did the phone re-trigger the request after the Mock change?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Information was scattered across different tools.&lt;/p&gt;

&lt;p&gt;This time, everything was on the same debug chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Network
   ↓
Console
   ↓
Mock
   ↓
Real device page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For mobile H5 debugging, fewer tool switches means less context loss—and that alone reduces investigation cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Console and Mock aren't silver bullets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Page completely crashed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Infinite loops, stack overflow, main thread blocking—the injected debug script may not connect either. Fall back to resource loading and network checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. iframe pages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Console runs in the current page context by default. If the issue is inside an iframe, open that URL separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Mock may not intercept all requests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mock depends on the proxy chain. Service Worker cache, native network interception, or certain WebView kernel behaviors may bypass it. Verify the request goes through the proxy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Remote eval context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remote scripts share the page's execution context. Quick checks are fine, but avoid heavy IndexedDB iteration or long synchronous tasks—use async patterns instead.&lt;/p&gt;




&lt;p&gt;This is &lt;strong&gt;Part 2&lt;/strong&gt; of the &lt;strong&gt;H5 Debug in Practice&lt;/strong&gt; series. &lt;a href="https://devpeek.ypgao.com/en/blog/wechat-h5-storage-debug/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; covered debugging WeChat H5 localStorage on real devices using the Session panel.&lt;/p&gt;

&lt;p&gt;The tool used in this article is &lt;a href="https://devpeek.ypgao.com/" rel="noopener noreferrer"&gt;DevPeek&lt;/a&gt;, a desktop packet capture and debugging tool that integrates proxy capture, page debugging, and Mock rules on the same chain.&lt;/p&gt;

&lt;p&gt;More resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devpeek.ypgao.com/en/docs/debug-replay/" rel="noopener noreferrer"&gt;Mobile web debugging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devpeek.ypgao.com/en/docs/mock/" rel="noopener noreferrer"&gt;Mock rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devpeek.ypgao.com/en/docs/capture/" rel="noopener noreferrer"&gt;Capture and request details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devpeek.ypgao.com/en/docs/proxy-ssl/" rel="noopener noreferrer"&gt;Proxy &amp;amp; SSL certificates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devpeek.ypgao.com/en/docs/faq/" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've run into similar issues—API OK but page broken, Android WebView compatibility, or debugging production H5 without injecting debug code—feel free to share your experience in the comments or discuss on &lt;a href="https://github.com/GYPengDev/devpeek/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>vue</category>
      <category>electron</category>
      <category>rrweb</category>
    </item>
    <item>
      <title>Why We Built DevPeek (1): HTTPS Decrypted, Body Still Gibberish</title>
      <dc:creator>GYPengDev</dc:creator>
      <pubDate>Fri, 10 Jul 2026 03:57:21 +0000</pubDate>
      <link>https://dev.to/yp_g_c8b59bf0f030337b4592/why-we-built-devpeek-1-https-decrypted-body-still-gibberish-b50</link>
      <guid>https://dev.to/yp_g_c8b59bf0f030337b4592/why-we-built-devpeek-1-https-decrypted-body-still-gibberish-b50</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 1 of a short series on why we built &lt;a href="https://devpeek.ypgao.com/" rel="noopener noreferrer"&gt;DevPeek&lt;/a&gt;—a proxy tool that keeps capture, business-layer crypto, and replay on one chain.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9:40 PM, pinged in the group again
&lt;/h2&gt;

&lt;p&gt;Night before a release. QA dropped a line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Staging login is broken—production users are reporting it too.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not a scheduled joint-debug session. People were already chasing updates in the thread.&lt;/p&gt;

&lt;p&gt;Proxy on, HTTPS decrypting fine. That &lt;code&gt;POST /api/user/login&lt;/code&gt; showed &lt;strong&gt;200&lt;/strong&gt;—the path looked healthy. I opened the request body anyway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"U2FsdGVkX1+8K3v… (long string continues)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a8f3c2…"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TLS was fine. &lt;code&gt;payload&lt;/code&gt; was still a Base64 blob. We had been shipping this style of API for years: gateway-wrapped body, AES on the fields, a sign on the side. Normal on a slow day. That night was not slow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ag8t664pcp1gqx55kcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ag8t664pcp1gqx55kcy.png" alt="DevPeek HTTPS capture list with per-client tabs" width="760" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TLS is decrypted in the list—but fields like &lt;code&gt;payload&lt;/code&gt; can still be ciphertext.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine most days. Miserable when you are rushed.
&lt;/h2&gt;

&lt;p&gt;My usual loop looked something like this:&lt;/p&gt;

&lt;p&gt;Copy the ciphertext. Open an online decrypt page—I keep three of those bookmarked. Paste, get plaintext. Backend says “try field X,” so I edit it, encrypt again, and replay in Postman or a script.&lt;/p&gt;

&lt;p&gt;Charles handles HTTPS. Business-layer crypto still lives somewhere else. When I am not in a hurry, hopping windows is annoying but survivable. When time is tight, bouncing between capture, browser, and terminal gets old fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  That night
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;payload&lt;/code&gt; in the request was still ciphertext. I could not tell where login was failing. I dropped a screenshot in the backend channel: “Can you help read the body plaintext?”&lt;/p&gt;

&lt;p&gt;Message sent. Reply not in yet. The decrypt script in my terminal was already running—done this so many times it is basically reflex.&lt;/p&gt;

&lt;p&gt;Then the reply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Staging uses key set B. Do not decrypt with production keys.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The API itself was fine. Time went to matching keys and environment first. Until the plaintext lines up, there is nothing useful to debug.&lt;/p&gt;

&lt;p&gt;Then a small ask: “Change &lt;code&gt;userId&lt;/code&gt; in the request to 0 and run login again—see if auth is misjudging.”&lt;/p&gt;

&lt;p&gt;That means touching the login request the app actually sends. Same encrypted body. Same grind: decrypt, edit the field, encrypt again, let the app emit something the server will accept.&lt;/p&gt;

&lt;p&gt;I tried Breakpoints in Charles to edit it in flight. Open the body—ciphertext. Cannot edit. Back to the Python script under &lt;code&gt;scripts/debug/&lt;/code&gt;, untouched for three months. Postman can send a version, but headers, cookies, and signing order never quite match the real app path, so you never fully trust the result.&lt;/p&gt;

&lt;p&gt;QA kept asking: “So can we log in on staging?”&lt;/p&gt;

&lt;p&gt;I burned nearly an hour in that manual loop. Looking back, they wanted to check one or two fields.&lt;/p&gt;

&lt;p&gt;Before standup, product added: “Check signup too—same encryption as login.” New URL. Copy the script. Tweak it. Same grind again.&lt;/p&gt;

&lt;p&gt;A new backend teammate asked which key each endpoint uses. The doc is in Confluence. Not something you see in the capture UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  We were never stuck on TLS
&lt;/h2&gt;

&lt;p&gt;Charles and Fiddler earn their keep stripping HTTPS. The grind sits one layer up: request bodies still ciphertext, and every edit or replay still done by hand—decrypt, change, encrypt again.&lt;/p&gt;

&lt;p&gt;Certs are fine. The endpoint returns 200. The thread is still blowing up. And somehow you still cannot skip copying ciphertext, matching staging vs production keys, or the decrypt–edit–re-encrypt–replay loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  After that night
&lt;/h2&gt;

&lt;p&gt;Before standup, my head was still on last night’s login request.&lt;/p&gt;

&lt;p&gt;The proxy already sat on the path. TLS was stripped. Business-layer crypto still lived outside the proxy—external sites, terminal scripts, Postman, each doing its own thing. Change one &lt;code&gt;userId&lt;/code&gt;? They wanted one or two fields checked, and the whole manual loop had to run again.&lt;/p&gt;

&lt;p&gt;After grinding through that night, a blunt thought surfaced: &lt;strong&gt;what if one tool owned capture and business-layer crypto together?&lt;/strong&gt; The proxy is already in the chain—decrypt scripts, environment keys, edit-and-replay should not mean hopping across four windows.&lt;/p&gt;

&lt;p&gt;That is roughly where DevPeek started.&lt;/p&gt;

&lt;p&gt;The first piece we wanted to pull in was the part that night would not let go of—&lt;strong&gt;outbound requests&lt;/strong&gt;. Charles handles TLS; we would handle the layer above: read ciphertext in the proxy, edit plaintext, encrypt again on the way out—and request-only first, because that was the slowest, hardest-to-skip slice of joint debugging.&lt;/p&gt;

&lt;p&gt;That became &lt;strong&gt;param transform&lt;/strong&gt;. It maps onto the same manual steps, except you never leave the capture UI:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversion config&lt;/strong&gt; handles how to decrypt and re-encrypt. Staging key set B vs production key set A lives next to your capture context—no more Confluence hunting. The Python under &lt;code&gt;scripts/debug/&lt;/code&gt; can move into script conversion; common AES setups can use built-in algorithms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Param transform rules&lt;/strong&gt; decide which requests and which parameters get converted. Right-click &lt;code&gt;POST /api/user/login&lt;/code&gt;, pick URL + &lt;code&gt;payload&lt;/code&gt;, bind the conversion config—save once, matching traffic hits it automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyomqrze37tit6f6dvfsx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyomqrze37tit6f6dvfsx.png" alt="Param transform rule wizard in DevPeek" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pick URL and encrypted fields from a captured request; bind a conversion config once.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once that is in place, you mostly work in plaintext: decrypted fields show up in details; when backend says “try &lt;code&gt;userId&lt;/code&gt; = 0,” edit in Mock tamper or the “Debug API” drawer and send—DevPeek re-encrypts on the way out while headers, cookies, and signing stay on the real app path. The Mock tamper dialog is &lt;strong&gt;not limited by client tab&lt;/strong&gt;—whether you are on Capture or Debug, it auto-opens while items are pending, on the same proxy chain as capture and Debug tab Network. Signup? Change the URL match, reuse the config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5qxkvfebwqhmawpt3ob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg5qxkvfebwqhmawpt3ob.png" alt="DevPeek Mock tamper dialog: editing the request phase" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On manual Mock hit, edit plaintext in the dialog—Continue re-encrypts on the way out, no Postman hop.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The list still records what the client actually sent; plaintext lives in details for comparison. Postman and that three-month-old Python script can step back.&lt;/p&gt;

&lt;p&gt;An hour that night was really about one or two fields. DevPeek grew from there; param transform was the first piece to land—turning that verification into “edit the value, hit send.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://devpeek.ypgao.com/en/blog/why-we-built-devpeek-h5-debug/" rel="noopener noreferrer"&gt;Why We Built DevPeek (2): That H5 Page in the App—Debug It on Your PC&lt;/a&gt;&lt;/strong&gt;—mobile pages opened through the proxy, mirrored in the Debug tab, with our own panel for DOM, console, and network—not just squinting at the phone.&lt;/p&gt;




&lt;p&gt;If you are still hand-rolling request bodies under pressure, &lt;a href="https://devpeek.ypgao.com/" rel="noopener noreferrer"&gt;try DevPeek&lt;/a&gt; or say hi on &lt;a href="https://github.com/GYPengDev/devpeek/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;—param transform guide: &lt;a href="https://devpeek.ypgao.com/docs/param-transform/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://devpeek.ypgao.com/en/blog/why-we-built-devpeek/" rel="noopener noreferrer"&gt;DevPeek blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>proxy</category>
      <category>webdev</category>
      <category>api</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Encrypted Mobile API Debugging? No More Copying Ciphertext Across Multiple Tools</title>
      <dc:creator>GYPengDev</dc:creator>
      <pubDate>Wed, 08 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/yp_g_c8b59bf0f030337b4592/encrypted-mobile-api-debugging-no-more-copying-ciphertext-across-multiple-tools-1da4</link>
      <guid>https://dev.to/yp_g_c8b59bf0f030337b4592/encrypted-mobile-api-debugging-no-more-copying-ciphertext-across-multiple-tools-1da4</guid>
      <description>&lt;p&gt;I'm an independent developer who spends a lot of time debugging mobile H5 pages through a local HTTPS proxy.&lt;/p&gt;

&lt;p&gt;The part that never gets easier: &lt;strong&gt;the request body looks fine in the proxy, but every important field is still encrypted.&lt;/strong&gt; You copy a Base64 blob into a Node REPL, tweak a phone number, re-encode it, paste it into Postman, realize the client also signs headers, close Postman, open Charles again…&lt;/p&gt;

&lt;p&gt;Existing tools are good at &lt;strong&gt;transport&lt;/strong&gt; (TLS MITM) or &lt;strong&gt;API replay&lt;/strong&gt; (Postman/Insomnia). Neither really owns the loop of &lt;em&gt;capture → decrypt → edit in context → re-encrypt → mock or resend&lt;/em&gt;. That's the gap I kept hitting for months.&lt;/p&gt;

&lt;p&gt;This post walks through that manual workflow first — then the layered config model I used to automate it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two different "decryptions"
&lt;/h2&gt;

&lt;p&gt;Before any app-layer trickery, you need plaintext HTTP.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;th&gt;Typical setup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport (HTTPS MITM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Readable HTTP headers/body&lt;/td&gt;
&lt;td&gt;Proxy + trusted root CA on device + SSL decrypt allowlist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Application (param transform)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Readable business fields inside JSON/query&lt;/td&gt;
&lt;td&gt;Rules that decode Base64/AES/etc. on specific keys&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your capture list is full of &lt;code&gt;CONNECT&lt;/code&gt; tunnels and empty bodies, fix layer 1 first. Param transform only helps once you can already see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YWxpY2U6c2VjcmV0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Pain point 1: Decrypting once is easy; doing it fifty times a day is not
&lt;/h2&gt;

&lt;p&gt;Here's the manual loop I used to run for a single login field:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Capture in the proxy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/v1/login&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.example.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"YWxpY2U6c2VjcmV0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Decode in a one-off script&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// decode.mjs — run every time you need to inspect a value&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YWxpY2U6c2VjcmV0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// alice:secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Change the value and encode back&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice:new-secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// YWxpY2U6bmV3LXNlY3JldA==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Paste into Postman&lt;/strong&gt; — which doesn't know your client's header signing, cookie jar, or device fingerprint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Give up and edit in Charles&lt;/strong&gt; — but Charles has no first-class "this JSON key is always Base64" rule that also feeds your mock matcher.&lt;/p&gt;

&lt;p&gt;The userId, phone, device token… each field repeats the same five steps. Teams share Postman collections but rarely share the half-broken decrypt scripts that live in someone's &lt;code&gt;~/tmp&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pain point 2: Mocking on ciphertext is fragile
&lt;/h2&gt;

&lt;p&gt;Say you want to mock "phone = 13800138000" on an encrypted API.&lt;/p&gt;

&lt;p&gt;Without transform rules you either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-compute ciphertext offline and hard-code it in the mock (breaks when keys rotate), or&lt;/li&gt;
&lt;li&gt;Write a Charles rewrite script that decrypts before matching (works, but nobody on the team wants to maintain it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you actually want is: &lt;strong&gt;match on plaintext, send ciphertext&lt;/strong&gt;. That implies decrypt-before-match in the proxy pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;incoming request
  → apply param transform (derive plaintext)
  → mock matcher reads plaintext features
  → (optional) re-encrypt before forwarding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order matters. If mock runs on raw body only, your &lt;code&gt;13800138000&lt;/code&gt; rule never fires.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pain point 3: AES is where copy-paste really breaks
&lt;/h2&gt;

&lt;p&gt;Base64 is annoying. AES-CBC with a fixed IV is where I lost afternoons.&lt;/p&gt;

&lt;p&gt;Minimal Node version that must match the mobile client byte-for-byte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decryptField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b64Cipher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1234567890123456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 16 bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abcdefghijklmnop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 16 bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDecipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aes-128-cbc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;decipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAutoPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// PKCS7&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b64Cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;decipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encryptField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1234567890123456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abcdefghijklmnop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aes-128-cbc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One wrong assumption — key as hex vs utf8, IV prepended to ciphertext, GCM auth tag placement — and you get garbage. Now multiply that by three environments (staging keys, prod keys, feature-flag keys).&lt;/p&gt;

&lt;p&gt;Multi-step pipelines are worse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// common pattern: base64 wrapper around AES output&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertDecrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;decryptField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's when I stopped trusting one-liner snippets and started wanting &lt;strong&gt;versioned, exportable rules&lt;/strong&gt; tied to URL patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pain point 4: Postman isn't on the phone's code path
&lt;/h2&gt;

&lt;p&gt;Postman is great for contract testing. It's a poor stand-in when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app adds a timestamp + HMAC over the sorted body&lt;/li&gt;
&lt;li&gt;Tokens come from a previous capture-only handshake&lt;/li&gt;
&lt;li&gt;You need to see what &lt;strong&gt;this&lt;/strong&gt; WebView actually sent, then nudge one field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The useful workflow is: &lt;strong&gt;same captured request, same headers, one edited field, resend through the proxy&lt;/strong&gt;. Not "reconstruct the request from docs."&lt;/p&gt;




&lt;h2&gt;
  
  
  A cleaner model: separate "how" from "where"
&lt;/h2&gt;

&lt;p&gt;The model I ended up with splits config into two layers — &lt;strong&gt;conversion rules&lt;/strong&gt; (how to decrypt/encrypt) and &lt;strong&gt;param transform rules&lt;/strong&gt; (which request + which param key):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Conversion rule     → HOW to decrypt/encrypt (algorithm or script)
        ↑ referenced by
Transform rule      → WHICH request + WHICH param key
        ↓ produces
paramTransformDerived (plaintext sidecar — original capture untouched)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why keep the raw capture?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audit: you still see what the client actually sent&lt;/li&gt;
&lt;li&gt;Diff: compare ciphertext drift across builds&lt;/li&gt;
&lt;li&gt;Safety: failed transforms don't corrupt stored payloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example transform target for the login JSON above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// conceptual config — not a literal file format&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bodyParams&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bodyKind&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apply&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ruleId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cr_login_b64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bodyKind&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a builtin Base64 conversion rule on &lt;code&gt;cr_login_b64&lt;/code&gt;, every matching capture automatically gets a derived plaintext row — no REPL, no clipboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Script escape hatch (when builtin isn't enough)
&lt;/h2&gt;

&lt;p&gt;For custom signing, chained transforms, or calling an internal decrypt API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertDecrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// input: the param string; request: read-only snapshot (host, headers, body, query)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aesDecrypt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0123456789abcdef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0123456789abcdef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ivEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;outputEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertEncrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aesEncrypt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0123456789abcdef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0123456789abcdef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ivEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plaintextEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;outputEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;convertEncrypt&lt;/code&gt; is optional — only needed if you want to &lt;strong&gt;edit plaintext and send it back&lt;/strong&gt; (mock tamper, resend drawer). MD5/SHA/HMAC-style rules are view-only by design.&lt;/p&gt;

&lt;p&gt;That's the full layered model. I built a desktop proxy tool around it so the decrypt → edit → re-encrypt loop stays inside the capture workflow instead of bouncing between a REPL and three other apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I wired this into — DevPeek
&lt;/h2&gt;

&lt;p&gt;The app is called &lt;strong&gt;&lt;a href="https://devpeek.ypgao.com" rel="noopener noreferrer"&gt;DevPeek&lt;/a&gt;&lt;/strong&gt; (Electron + Vue 2 main/renderer, Node proxy in the middle). I used the pain points above as a checklist. Three places param transform actually matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Capture detail&lt;/strong&gt; — a dedicated tab shows ciphertext, derived plaintext, and success/failure per rule. Day-to-day debugging reads plaintext first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mock&lt;/strong&gt; — matchers can use decrypted features; tamper UI edits plaintext and re-encrypts on continue. Fixes pain point 2 without maintaining Charles scripts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resend / debug API drawer&lt;/strong&gt; — start from the captured request, edit a JSON field as plain text, send through the proxy with auto re-encrypt. Fixes pain point 4.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Builtins cover Base64, AES-ECB/CBC/GCM, DES/3DES, RSA; scripts run sandboxed with helpers for crypto, compression, and HTTP if you need a remote decrypt service.&lt;/p&gt;

&lt;p&gt;Core desktop features (proxy, SSL decrypt, param transform, mock, search) are &lt;strong&gt;free&lt;/strong&gt; — no subscription wall on the workflow described here.&lt;/p&gt;

&lt;p&gt;I'm curious — what's the most annoying encrypted param debugging workflow you've had to deal with? Feel free to share your pain points below; I may add native support for those cases in future updates.&lt;/p&gt;

&lt;p&gt;I've released Windows builds for anyone who wants to test this param transform workflow out — macOS and Linux builds are actively in development based on community feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://devpeek.ypgao.com/docs/param-transform" rel="noopener noreferrer"&gt;Param transform guide&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://devpeek.ypgao.com" rel="noopener noreferrer"&gt;devpeek.ypgao.com&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting cheatsheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Body still opaque gibberish&lt;/td&gt;
&lt;td&gt;HTTPS MITM not set up (cert / decrypt allowlist)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transform tab empty&lt;/td&gt;
&lt;td&gt;Rule disabled or URL/body pattern mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;builtin_error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key/IV encoding, padding, or algorithm mismatch vs client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mock never matches plaintext&lt;/td&gt;
&lt;td&gt;Transform didn't run before mock; check rule order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field read-only in resend UI&lt;/td&gt;
&lt;td&gt;Re-encrypt not enabled, or algorithm is one-way (hash/HMAC)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>showdev</category>
      <category>electron</category>
      <category>developertools</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
