Opt-In Software Blog

SSL/TLS Traffic Decryption in ProxyMapService

Most web traffic today is encrypted using HTTPS. While encryption protects data in transit, it also prevents a proxy from inspecting the actual HTTP requests and responses.

This becomes important when you want ProxyMapService to do more than simple forwarding. For example, caching static content such as images, JavaScript files, stylesheets, fonts, or other assets requires access to the decrypted HTTP payload. Without SSL/TLS decryption, the proxy can only see an encrypted byte stream and cannot analyze or cache the content.

ProxyMapService supports SSL/TLS traffic decryption by operating as a man-in-the-middle (MITM) proxy. This allows HTTPS traffic to be inspected, modified, cached, or routed using the same rule engine that is available for regular HTTP traffic.

ProxyMapService already includes support for content caching, but that topic deserves its own article. In this post, we’ll focus only on SSL/TLS decryption and the certificate configuration required to make it work.

Creating a Root CA Certificate

Before ProxyMapService can decrypt HTTPS traffic, it must be able to generate certificates for target hosts dynamically. To do that, a trusted root Certificate Authority (CA) certificate is required.

The easiest way to create one is to use the CreateRootCA console utility included with ProxyMapService.

By default, the utility creates the certificate files in the .proxymap directory located in the current user’s home folder.

The generated files include:

  • A PKCS#12 (.p12) file containing the certificate and private key
  • A public certificate (.cer)
  • A PEM-encoded public certificate (.pem)

Installing the Certificate

The generated certificate must be installed into the appropriate Windows certificate store.

Stand-alone Mode

If ProxyMapService is running as a stand-alone application (ProxyMapService.exe), install the root certificate into the Trusted Root Certification Authorities store of the Current User.

Open the certificate manager:

certmgr.msc

Then import the generated root certificate into:

Current User
 └─ Trusted Root Certification Authorities

IIS Hosting

If ProxyMapService is hosted by IIS, install the certificate into the Personal certificate store of the Local Computer.

Open:

certlm.msc

Then import the certificate into:

Local Computer
 └─ Personal

The application pool identity must have access to the certificate’s private key.

Configuring the Root Certificate

After the certificate has been installed, ProxyMapService must be configured to locate it.

Example configuration:

"SslServerOptions": {
    "EnabledSslProtocols": "Tls11,Tls12,Tls13",
    "ClientCertificateRequired": false,
    "CheckCertificateRevocation": false,
    "CACertificate": {
        "StoreLocation": "LocalMachine",
        "StoreName": "My",
        "Subject": "CN=DO_NOT_TRUST_ProxyMapRoot"
    }
}

In this example, ProxyMapService searches for a certificate with the following subject:

CN=DO_NOT_TRUST_ProxyMapRoot

inside the My (Personal) certificate store of the Local Machine certificate location.

Understanding SslServerOptions

SslServerOptions controls how ProxyMapService behaves when acting as the TLS server toward connecting clients.

EnabledSslProtocols

Specifies which TLS protocol versions are allowed.

"EnabledSslProtocols": "Tls11,Tls12,Tls13"

Only the listed protocol versions will be accepted during TLS negotiation.

In most environments, TLS 1.2 and TLS 1.3 are sufficient. TLS 1.1 is typically enabled only for compatibility with legacy clients.

ClientCertificateRequired

Controls whether the client must present a certificate during the TLS handshake.

"ClientCertificateRequired": false

When set to false, clients authenticate using standard HTTPS behavior.

When set to true, mutual TLS (mTLS) is required and clients must provide a valid certificate.

CheckCertificateRevocation

Controls certificate revocation checking.

"CheckCertificateRevocation": false

When enabled, Windows performs online revocation checks using CRL or OCSP endpoints during certificate validation.

Disabling revocation checks can reduce connection latency and avoid failures in isolated environments where revocation services are unavailable.

CACertificate

Specifies the root CA certificate used by ProxyMapService to generate certificates for intercepted HTTPS hosts.

The certificate is identified using:

  • StoreLocation
  • StoreName
  • Subject

The private key associated with this certificate is required for successful SSL/TLS interception.

Understanding SslClientOptions

SslClientOptions controls how ProxyMapService authenticates when connecting to upstream HTTPS servers.

Example configuration:

"SslClientOptions": {
    "EnabledSslProtocols": "Tls11,Tls12,Tls13",
    "CheckCertificateRevocation": false
}

EnabledSslProtocols

Specifies which TLS protocol versions ProxyMapService may use when establishing outbound HTTPS connections.

"EnabledSslProtocols": "Tls11,Tls12,Tls13"

Only the configured protocols will be offered during negotiation with the remote server.

CheckCertificateRevocation

Controls revocation checking for certificates presented by upstream servers.

"CheckCertificateRevocation": false

When enabled, certificate revocation status is verified before the server certificate is accepted.

This helps detect certificates that have been revoked by the issuing authority.

Enabling SSL/TLS Decryption

Once the certificate has been installed and the SSL options are configured, SSL/TLS decryption can be enabled using the DecryptSSL setting.

Decryption can be enabled globally for a listening port or selectively for specific host rules.

Enabling Decryption on a Listening Port

Example:

"Listen": {
    "Port": 10001,
    "RejectHttpProxy": false,
    "DecryptSSL": true
}

With this configuration, all HTTPS traffic received on port 10001 will be decrypted and processed by ProxyMapService.

This is usually the simplest configuration when all traffic arriving on the listener should be inspected.

Enabling Decryption for Specific Hosts

Sometimes only a subset of HTTPS traffic needs to be decrypted.

In that case, decryption can be enabled within host rules.

Example:

"HostRules": {
    "Items": [
        {
            "Pattern": "google\\.com$",
            "HostPort": 443,
            "DecryptSSL": true
        }
    ]
}

In this configuration, only HTTPS traffic matching google.com on port 443 will be decrypted.

All other HTTPS traffic will continue to pass through without interception.

This approach is useful when SSL inspection is required only for selected destinations.

Verifying the Configuration

After enabling SSL/TLS decryption:

  1. Start ProxyMapService.
  2. Configure a browser or application to use the proxy.
  3. Open an HTTPS website.
  4. Verify that no certificate warnings are displayed.
  5. Check ProxyMapService logs to confirm that decrypted HTTP requests and responses are being processed.

If certificate warnings appear, verify that:

  • The root CA certificate is installed in the correct certificate store.
  • The configured certificate subject matches the installed certificate.
  • The certificate includes a private key.
  • The service account has permission to access the private key.

Conclusion

SSL/TLS decryption is a prerequisite for advanced HTTPS traffic processing in ProxyMapService. Once a trusted root CA certificate is installed and configured, the service can inspect encrypted traffic just as it does regular HTTP traffic.

This capability enables scenarios such as request inspection, response modification, traffic analysis, selective routing, and content caching. In a future post, we’ll take a closer look at the caching subsystem and how it can be used to efficiently serve static content directly from ProxyMapService.

Session API for Sticky Proxy Management in ProxyMapService

In a previous article, Making Any Scraper Work with Sticky Proxies, we discussed how ProxyMapService maps local ports to independent sticky proxy sessions.

Each local port automatically receives its own session ID and keeps it for the configured StickyProxyLifetime. This allows applications that only support simple IP:PORT proxies to work with modern session-based proxy providers.

Until now, session management in ProxyMapService was completely automatic. While this works well for many use cases, some applications need direct control over when sessions are created, rotated, or discarded.

The latest version of ProxyMapService introduces a Session API that allows applications and scripts to inspect, reset, and create sessions on demand.

How sessions work

Assume the following configuration:

{
  "Listen": {
    "PortRange": {
      "Start": 10201,
      "End": 10300
    },
    "StickyProxyLifetime": 10
  }
}

When traffic is sent through port 10201, ProxyMapService generates a session ID:

10201 → session LZpmRqdu

All requests made through this port will use the same sticky session until it expires.

The new Session API exposes this information and allows manual control over the session lifecycle.

Enabling Session API

For security and compatibility reasons, Session API is disabled by default.

To enable it, add the following section to your appsettings.json:

{
  "SessionAPI": {
    "Enabled": true,
    "Domain": "portmapper"
  }
}

Configuration Options

Option Description
Enabled Enables Session API endpoints.
Domain Virtual domain used to access Session API endpoints through a mapped proxy port.

With the configuration above, Session API can be accessed using requests such as:

curl -x http://127.0.0.1:10201 http://portmapper/session/

Using an Empty Domain

The Domain setting is optional and may be set to an empty string:

{
  "SessionAPI": {
    "Enabled": true,
    "Domain": ""
  }
}

When Domain is empty, Session API endpoints become available directly on the mapped proxy port.

For example:

curl http://127.0.0.1:10201/session/

This can be convenient when working with local tools or scripts that do not support custom hostnames.

Get Current Session

Returns information about the session currently assigned to a port.

Example:

curl -x http://127.0.0.1:10201 http://portmapper/session/

Response:

{
  "session_id": "LZpmRqdu",
  "expires_at": "Wed, 10 Jun 2026 16:09:43 GMT",
  "expired": false
}

If the session is still active, repeated calls return the same information.

When the sticky session expires, ProxyMapService automatically generates a new session ID and the endpoint will return the new session details.

This can be useful for monitoring session state or checking how much time remains before rotation.

Reset Current Session

Sometimes you want to force a session change without waiting for the expiration timer.

The /session/reset endpoint removes the current session associated with the port.

Example:

curl -x http://127.0.0.1:10201 http://portmapper/session/reset

Response:

{
  "success": true
}

After the reset, the next request through port 10201 will automatically create a new session.

Typical use cases:

  • detected or blocked IP address
  • captcha escalation
  • failed scraping workflow
  • manual IP rotation

Create a New Session Immediately

The /session/new endpoint creates a new session immediately and assigns it to the port.

Example:

curl -x http://127.0.0.1:10201 http://portmapper/session/new

Response:

{
  "session_id": "uawxmttg",
  "expires_at": "Wed, 10 Jun 2026 16:19:48 GMT",
  "expired": false
}

Unlike /session/reset, which simply removes the current session, /session/new generates and activates a replacement session instantly.

This is useful when an application wants to rotate identities proactively while continuing to use the same local proxy port.

Why Not Just Change Ports?

A common question is why you would need a Session API if every port already has its own sticky session.

In practice, many scraping and automation systems assign a specific role to each local proxy port. For example:

127.0.0.1:10201 → Worker A
127.0.0.1:10202 → Worker B
127.0.0.1:10203 → Worker C

Workers may maintain their own queues, browser profiles, cookies, account assignments, or internal state tied to a particular proxy endpoint.

Changing ports is not always practical because it may require updating application configuration, restarting workers, or reassigning tasks.

With Session API, the application can keep using the same local proxy port while rotating the underlying sticky session whenever necessary.

Practical Examples

Session API can be useful when:

  • a target website starts returning captchas
  • an IP address becomes blocked or rate-limited
  • a scraping task needs a fresh identity immediately
  • monitoring systems need visibility into session expiration
  • applications want explicit control over session rotation timing

All of this can be done without restarting ProxyMapService and without changing the local proxy endpoint used by the application.

Summary

ProxyMapService was originally designed to make sticky proxies available to software that only understands simple IP:PORT proxy lists.

The new Session API adds direct control over the sticky sessions behind each mapped port.

Available endpoints:

  • GET /session/ — get information about the current session
  • GET /session/reset — remove the current session
  • GET /session/new — create and activate a new session immediately

This makes it easier to build scraping, automation, and data collection systems that need predictable control over proxy identities while continuing to use ordinary local proxy ports.

Using ProxyMapService to Fix pip Behind SOCKS5 Proxy

I ran into an issue where pip wasn’t working due to network restrictions. At the same time, I had access to a SOCKS5 proxy at:

192.168.1.100:1080

The problem was that pip doesn’t support SOCKS proxies out of the box. Normally, you’d install PySocks to enable that — but since pip itself wasn’t working, that wasn’t an option.

I used the ProxyMapService to expose the SOCKS5 proxy as a local HTTP proxy. Here’s the rule I configured:

{
  "Listen": {
    "Port": 10000,
    "RejectHttpProxy": false
  },
  "Authentication": {
    "Required": false,
    "Verify": false,
    "SetAuthentication": false
  },
  "ProxyServers": {
    "Items": [
      {
        "Host": "192.168.1.100",
        "Port": 1080,
        "ProxyType": "Socks5"
      }
    ]
  }
}

After that, pip started working by pointing it to the local HTTP proxy:

pip install cython --proxy http://127.0.0.1:10000
Collecting cython
  Using cached cython-3.2.4-cp311-cp311-win_amd64.whl.metadata (7.7 kB)
Using cached cython-3.2.4-cp311-cp311-win_amd64.whl (2.8 MB)
Installing collected packages: cython
Successfully installed cython-3.2.4

🎉

Making Any Scraper Work with Sticky Proxies

How to Use Sticky Proxies in Applications That Don’t Support Them

Many modern proxy providers sell so-called sticky proxies (also known as session-based proxies). This is a very popular solution for scraping, automation, data collection, and working around anti-bot protections.

What is a sticky proxy?

A sticky proxy is a proxy server that “locks” one IP address to you for a certain amount of time (for example, 5 or 10 minutes) using a session. Usually, the session is defined via the username, for example:

user-session-ABC123-sessTime-10:password@proxy_host:proxy_port

While the session is active, you will access the internet from the same IP address. When the time expires or you change the session ID, you get a new IP.

Today, most large proxy providers (residential, mobile, ISP) work exactly this way: you connect to a single host and port, and IP rotation is controlled via parameters in the username.

The problem: not all software supports sticky proxies

Many applications (scraping software, old parsers, bots, automation tools):

  • ❌ cannot work with proxies that require authentication
  • ❌ cannot manage sessions
  • ❌ expect just a list of IP:PORT, without username and password

Because of this, they cannot be directly connected to modern sticky proxy providers.

The solution: map local TCP ports through sticky proxies

The idea is very simple:

  • We run a local service
  • It opens a range of local ports
  • Each local port:
    • automatically connects to the sticky proxy
    • uses its own separate session
  • As a result:
    • 127.0.0.1:5001 → sticky proxy session AAA1
    • 127.0.0.1:5002 → sticky proxy session AAA2
    • 127.0.0.1:5003 → sticky proxy session AAA3
    • and so on

And your software just works with regular proxies without authentication:

127.0.0.1:5001
127.0.0.1:5002
127.0.0.1:5003

Ready-made solution: ProxyMapService

For this purpose, the following open-source project works perfectly:

👉 ProxyMapService
https://github.com/optinsoft/ProxyMapService

It can:

  • map port ranges
  • automatically manage sticky sessions
  • work with HTTP / SOCKS proxies
  • modify / generate username parameters

Configuration example

Here is an example config:

"ProxyMappings": [
  {
    "Listen": {
      "PortRange": {
        "Start": 10001,
        "End": 10100
      },
      "RejectHttpProxy": false,
      "StickyProxyLifetime": 10
    },
    "Authentication": {
      "Required": false,
      "Verify": false,
      "SetAuthentication": false,
      "RemoveAuthentication": false
    },
    "ProxyServers": {
      "Items": [
        {
          "Host": "PROXY_IP",
          "Port": PROXY_PORT,
          "ProxyType": "Http",
          "Username": "USERNAME",
          "Password": "PASSWORD",
          "UsernameParameters": [
            {
              "Name": "zone",
              "Value": "custom"
            },
            {
              "Name": "region",
              "Value": "US"
            },
            {
              "Name": "session",
              "Value": "^[A-Za-z]{8}",
              "SessionId": true
            },
            {
              "Name": "sessTime",
              "Value": "$sessTime",
              "Default": "10",
              "SessionTime": true
            }
          ]
        }
      ]
    }
  }
]

⚠️ In this example you need to replace:

  • PROXY_IP
  • PROXY_PORT
  • USERNAME
  • PASSWORD

with the values provided by your proxy provider.

How does it work in practice?

  • ProxyMapService opens local ports 10001–10100

  • Each port:

    • automatically generates a new sticky session
    • keeps it for StickyProxyLifetime = 10 minutes
  • You just set in your software:

127.0.0.1:10001
127.0.0.1:10002
127.0.0.1:10003
...

Conclusion

If your software:

  • doesn’t support proxy authentication
  • doesn’t support sessions
  • only works with IP:PORT lists

➡️ ProxyMapService solves this problem completely, allowing you to use modern sticky proxies without modifying your software.

Useful for:

  • scraping software
  • bots
  • automation tools
  • legacy tools
  • and basically any software with poor proxy support

ProxyMapService: ParseUsernameParameters

We’re excited to announce a new, optional feature in the ProxyMapService application. This feature enhances the flexibility of the authentication process by allowing you to simplify username validation.

The Challenge: Managing Complex Usernames

Sometimes, proxy usernames need to encode more information than just the account identifier.

For example, a proxy might look like this:

http://user-zone-custom-region-US-session-1234567-sessionTime-5:1111@127.0.0.1:8888

Username part of the proxy is user-zone-custom-region-US-session-1234567-sessionTime-5.

While this format might be necessary for other parts of the system or for identification, it complicates the standard authentication process by requiring the system to ignore the extra parts (zone, region, session, etc.) and focus solely on the actual username.

The Solution: Introducing ParseUsernameParameters

To address this need for cleaner authentication, we’ve introduced a new configuration parameter called ParseUsernameParameters. This boolean parameter resides within the Authentication section of the appsettings.json file.

  • Parameter Name: ParseUsernameParameters
  • Allowed Values: true or false (default is false)

What Does This Parameter Do?

When set to true, the ProxyMapService will parse the username string during authentication. It splits the username using the hyphen (-) delimiter. Crucially, only the first segment (the part before the first hyphen) is used for the authentication check. The remaining segments are ignored.

How It Works (Example)

Let’s take a look at the authentication logic with ParseUsernameParameters=true:

  1. Input Username (part of the proxy):

    "user-zone-custom-region-US-session-1234567-sessionTime-5"
  2. Authentication Logic (with ParseUsernameParameters=true):

    • The username string user-zone-custom-region-US-session-1234567-sessionTime-5 is split using the - character.
    • The resulting parts are: ["user", "zone", "custom", "region", "US", "session", "1234567", "sessionTime", "5"]
    • Authentication Check: The system compares the first part, user, to the configured Username value (e.g., "user" from the example config). The rest of the parts are completely ignored for authentication.

Configuration Example (appsettings.json)

Here’s how you would enable this feature in your appsettings.json:

{
  "Authentication": {
    // ... other authentication settings ...
    "Required": true,
    "Verify": true,
    "ParseUsernameParameters": true,
    "Username": "user",  // This is the base username to compare against
    "Password": "1111"    // Password
  }
}

Web Proxy Checker v2.111 Released

Enhanced Authentication for Problematic Proxies

We’re pleased to announce the release of Web Proxy Checker version 2.111 – our reliable tool for testing proxy server functionality now includes improved handling for CONNECT (HTTPS) proxies with non-standard authentication behavior.

Standard Proxy Authentication Flow

When connecting to a proxy requiring Basic Authentication, the expected protocol is:

  1. Client (browser or our tool) connects without credentials
  2. Proxy responds with “407 Proxy Authentication Required”
  3. Client reconnects with the Proxy-Authorization HTTP header
  4. Connection proceeds if credentials are valid

This standard flow is used by all major browsers (Chrome, Firefox, Edge) and has been Web Proxy Checker’s default behavior.

The Challenge: Non-Compliant Proxies

During testing, we identified proxies that don’t follow authentication standards. Instead of returning the proper 407 error, they immediately close connections when credentials are missing. This creates a compatibility issue where legitimate authentication attempts fail.

New Feature: “Connect Basic Authentication” Setting

Version 2.111 introduces a solution on the HTTP tab: the new “Connect Basic Authentication” option.

How it works:

  • Disabled (Default): Standard behavior – waits for 407 response before sending credentials
  • Enabled: Immediately includes Proxy-Authorization header in the first connection attempt
  • Result: Compatibility with proxies that require immediate authentication

Usage Recommendations:

  • Most users should keep this setting disabled (works with standard proxies)
  • Enable only when encountering proxies that close connections without 407 responses
  • Particularly useful for certain corporate or custom proxy setups

Quick Setup:

  1. Launch Web Proxy Checker v2.111
  2. Go to the HTTP tab
  3. Find “Connect Basic Authentication” checkbox
  4. Enable for problematic proxies, disable for standard ones

What This Means for You:

  • ✓ Wider proxy compatibility
  • ✓ No more failed connections with non-standard proxies
  • ✓ Backward compatibility maintained
  • ✓ Easy toggle between authentication modes

Upgrade Today

Download Web Proxy Checker v2.111 and experience improved compatibility with all your proxy servers!

ProxyMapService Diagnostics in Windows Event Log

ProxyMapService now supports outputting diagnostic messages directly to the Windows Event Log.


1. Enabling Event Log Logging

To enable writing events to the Windows Event Log, you need to make a small change in your project’s configuration file — appsettings.json.

Add the EventLog section within the Logging block, specifying the desired minimum logging level. The example below sets the level to “Information”:

{
  "Logging": {
    "EventLog": {
      "LogLevel": {
        "Default": "Information"
      }
    }
  }
}

💡 Note: The Default level within the EventLog section determines the minimum level (Trace, Debug, Information, Warning, Error, Critical) at which messages will be written to the log.


2. Mandatory Step: Registering the Event Source (PowerShell)

Attention! For messages to be successfully displayed in the Event Log and for the system to correctly identify their source and formatting, it is necessary to register the event source with the name ProxyMapService beforehand.

Event source registration is only required once upon installation or update of the service.

Use PowerShell run as administrator and execute the following command. This command creates the necessary registry key:

New-EventLog -LogName Application -Source ProxyMapService
  • New-EventLog: The cmdlet used to create a new event source.
  • -LogName Application: Specifies that the source will write to the standard “Application” log.
  • -Source ProxyMapService: Sets the source name, which must match the name used within the service code.

3. Viewing the Events

After configuring appsettings.json and registering the source, restart the ProxyMapService. All events at the Information level and above will now be available for viewing in the Windows Event Viewer under Windows Logs $\rightarrow$ Application.

Look for messages with the Source: ProxyMapService.

🔗 Source Code and Discussion: As always, you can find the latest version of the project on GitHub: https://github.com/optinsoft/ProxyMapService

CountryTimezone

If you’ve worked with timezones in Python, you’ve probably used pytz to get IANA timezone names for specific countries:

tz_names = pytz.country_timezones.get(country.upper(), [])

When I needed similar functionality in Delphi, I was surprised to find that there wasn’t a readily available library to get IANA timezone names for countries.

After searching without success, I decided to create my own solution:

https://github.com/optinsoft/CountryTimezone

Here’s a practical example that demonstrates how to get a random timezone name and its UTC offset for a given country:

uses CountryTimezone, TZDB;

type
   TTimezoneAndOffset = record
        name: String;
        offset: Integer;
   end;

function GetRandomTimezoneAndOffset(country: String): TTimezoneAndOffset;
var
  LCTZDict: TCountryTimezoneDictionary;
  LCTZList: TStringList;
  tz: TBundledTimeZone;
begin
  LCTZDict := TCountryTimezoneDictionary.Create();
  LCTZList := TStringList.Create;
  try
    LCTZDict.GetCountryTimezones(country, LCTZList);
    Result.name := LCTZList[Random(LCTZList.Count)];
    tz := TBundledTimeZone.GetTimeZone(Result.name);
    Result.offset := Trunc(tz.UtcOffset.TotalMinutes);
  finally
    LCTZList.Free;
    LCTZDict.Free;
  end;
end;

This function:

  1. Creates a country timezone dictionary
  2. Retrieves all timezone names for the specified country
  3. Selects a random timezone from the list
  4. Uses TZDB to get the UTC offset in minutes for that timezone

renamedecl

🔄 Renaming Variables and Functions in JavaScript Using ASTs

When working with JavaScript, there are times you might want to automatically rename variables or functions across your code — perhaps for obfuscation, code transformation, or refactoring. Doing this safely, without accidentally breaking your program, requires understanding how your code is structured — and that’s where the Abstract Syntax Tree (AST) comes in.

In this post, we’ll look at how to rename variable and function declarations using ASTs, with a simple utility renamedecl.


🌳 What Is an AST?

An Abstract Syntax Tree (AST) is a structured, tree-like representation of your source code.
Instead of dealing with raw text, you work with objects that represent program constructs like variables, functions, loops, and expressions.

For example, the code:

const x = 5;
console.log(x);

can be represented (simplified) as:

{
  type: "Program",
  body: [
    {
      type: "VariableDeclaration",
      declarations: [
        {
          type: "VariableDeclarator",
          id: { type: "Identifier", name: "x" },
          init: { type: "Literal", value: 5 }
        }
      ]
    },
    {
      type: "ExpressionStatement",
      expression: {
        type: "CallExpression",
        callee: {
          object: { name: "console" },
          property: { name: "log" }
        },
        arguments: [{ type: "Identifier", name: "x" }]
      }
    }
  ]
}

By working with this structure, you can programmatically modify parts of your code (e.g. change x to width) and then regenerate valid JavaScript source code.


🧩 Tools: Acorn, Escodegen

  • Acorn — a fast, lightweight JavaScript parser. It converts JS source code into an AST.
  • Escodegen — generates readable JavaScript code from an AST.

Scopes

Scopes — just like in real code, variables and functions in an AST exist within scopes (e.g., global, function, block). When renaming identifiers, you need to make sure you don’t accidentally rename a variable from an outer scope that has the same name.


🚀 Introducing renamedecl

renamedecl is the lightweight tool for renaming variables and functions in JS code based on an AST.

The function renameDeclarations(ast, rename, initscope?) walks through your AST, finds all declarations (variables, function names, parameters, etc.), and lets you rename them safely according to the rules you define.

It can be used in two main ways:


1. Automatic Renaming

You can rename every variable and function in a consistent way — for example, to obfuscate or anonymize code.

const acorn = require('acorn');
const escodegen = require('escodegen');
const { renameDeclarations } = require('renamedecl');

const originalCode = `
function add(x, y) {
    const result = x + y;
    return result;
}
`;

const ast = acorn.parse(originalCode, {
    ecmaVersion: 2020,
    sourceType: 'script'
});

// Rename all identifiers sequentially
renameDeclarations(
    ast,
    (id, scope) => `v${++scope.varNum}`,
    scope => { scope.varNum = 0; }
);

const newCode = escodegen.generate(ast);
console.log(newCode);

Output:

function v1(v2, v3) {
    const v1 = v2 + v3;
    return v1;
}

Here, every declared identifier got renamed with a consistent numbering scheme, respecting the correct scope hierarchy.


2. Custom Rename Mapping

You can also define your own rename map to transform specific variable or function names.

const renameMap = {
    x: 'width',
    y: 'height',
    result: 'sum'
};

renameDeclarations(ast, id => renameMap[id.name]);

This is great for refactoring, such as renaming variables to more meaningful names.


🧠 How Scoping Works

renamedecl automatically tracks scopes as it traverses the AST.
When it enters a new function or block, it creates a new scope object.
This ensures that two variables named x in different functions don’t get mixed up:

function outer() {
    const x = 10;
    function inner() {
        const x = 20;
        console.log(x);
    }
    inner();
}

Both x variables live in different scopes. The renaming logic keeps that distinction intact — so each gets its own renamed version.


🔧 Why Use This?

  • Safe — Respects JavaScript scoping rules
  • 🔍 Customizable — You control the renaming logic
  • 🧰 Tool-friendly — Works directly with standard AST tools
  • ⚙️ Flexible — Use it for obfuscation, refactoring, or static analysis

🧩 Try It Out

You can install it via npm:

npm install git+https://github.com/optinsoft/renamedecl.git

Then import and use it as shown above.


💡 Conclusion

Working with ASTs may sound intimidating at first, but with tools like Acorn, Escodegen, and utilities such as renamedecl, transforming JavaScript code becomes powerful and approachable.

By controlling variable and function names at the AST level, you can safely build tools for code analysis, transpilation, refactoring, or obfuscation — all while preserving the correctness of your original code.

How to download a Chrome extension without installing it

  1. In the Chrome Web Store, find the extension you need, for example, uBlock Origin.
  2. The extension’s URL looks like this: https://chromewebstore.google.com/detail/ublock/epcnnfbjfcgphgdmggkamkmgojdagdnn?hl=en-US&utm_source=ext_sidebar. The part epcnnfbjfcgphgdmggkamkmgojdagdnn is the extension’s unique ID.
  3. Find out your version of Chrome, for example, 141.0.7390.55.
  4. Use CURL to get the extension’s download link, replacing the extension ID (epcnnfbjfcgphgdmggkamkmgojdagdnn) and Chrome version (141.0.7390.55) with your own values:
    curl "https://clients2.google.com/service/update2/crx?response=redirect&os=win&arch=x64&os_arch=x86_64&prod=chromecrx&prodchannel=&prodversion=141.0.7390.55&lang=en-US&acceptformat=crx3,puff&x=id%3Depcnnfbjfcgphgdmggkamkmgojdagdnn%26installsource%3Dondemand%26uc&authuser=0"
  5. The result of the previous CURL command will look something like this:
    <HTML>
    <HEAD>
    <TITLE>Moved Temporarily</TITLE>
    </HEAD>
    <BODY BGCOLOR="#FFFFFF" TEXT="#000000">
    <!-- GSE Default Error -->
    <H1>Moved Temporarily</H1>
    The document has moved <A HREF="https://clients2.googleusercontent.com/crx/blobs/AcLY-yQ8Sxe7u0UiOxVYs1X2QxXnl4NGRAS6t4jJr_viP7fvXl9ARlV4ETIu1woz6O7cdB3-RBD9cYdV3APCdHY8S6kBmmk5mSnRTo0LZ8FUJQEkbk_6NnrX0EPLsAaqxNK-AMZSmuUQPLnhgEjCRn4So0hrUrRuyQSHOA/EPCNNFBJFCGPHGDMGGKAMKMGOJDAGDNN_25_5_0_0.crx?authuser=0">here</A>.
    </BODY>
    </HTML>
    The URL from which you can download the extension is: https://clients2.googleusercontent.com/crx/blobs/AcLY-yQ8Sxe7u0UiOxVYs1X2QxXnl4NGRAS6t4jJr_viP7fvXl9ARlV4ETIu1woz6O7cdB3-RBD9cYdV3APCdHY8S6kBmmk5mSnRTo0LZ8FUJQEkbk_6NnrX0EPLsAaqxNK-AMZSmuUQPLnhgEjCRn4So0hrUrRuyQSHOA/EPCNNFBJFCGPHGDMGGKAMKMGOJDAGDNN_25_5_0_0.crx?authuser=0
  6. Use CURL to download the CRX file:
    curl "https://clients2.googleusercontent.com/crx/blobs/AcLY-yQ8Sxe7u0UiOxVYs1X2QxXnl4NGRAS6t4jJr_viP7fvXl9ARlV4ETIu1woz6O7cdB3-RBD9cYdV3APCdHY8S6kBmmk5mSnRTo0LZ8FUJQEkbk_6NnrX0EPLsAaqxNK-AMZSmuUQPLnhgEjCRn4So0hrUrRuyQSHOA/EPCNNFBJFCGPHGDMGGKAMKMGOJDAGDNN_25_5_0_0.crx?authuser=0" --output uBlock.crx