JavaScript Scripting

Chute supports JavaScript scripting for advanced request/response modification, custom rule matching, DNS resolution, and scheduled tasks. Scripts use Apple's JavaScriptCore engine and follow the Surge-compatible script API.

Scripts are defined in the [Script] section of the configuration file.

Configuration

[Script]
MyScript = type=http-request, script-path=/path/to/script.js, pattern=^https?://example\.com, requires-body=true, max-size=262144, timeout=10, argument=myArg, debug=true
CronJob = type=cron, script-path=/path/to/cron.js, cron-expression=* * * * *, wake-system=true

Script Parameters

Parameter Required Default Description
type Yes http-request Script trigger type (see below)
script-path Yes Local file path or HTTP(S) URL to the JS script
pattern No (match all) URL regex pattern to filter when the script triggers
requires-body No auto-detect Force script to receive full request/response body
max-size No 131072 (128KB) Max body size in bytes for body-accessing scripts
timeout No 5.0 seconds Per-script execution timeout
argument No Custom string argument available as $argument in JS
debug No false Skip compilation cache for debugging
cron-expression No Cron schedule expression (for cron type only)
wake-system No false Wake system to execute cron scripts (iOS only)
enable No true Enable or disable this script
script-update-interval No 0 Seconds between remote script updates (-1 = never, 0 = only on start)

Script Types

Type String Enum Description
http-request HTTP Request Intercept and modify HTTP requests before upstream
http-response HTTP Response Intercept and modify HTTP responses before client
http-request-before-send HTTP Request Before Send Modify request after full body collected, before sending
rule Rule Custom rule matching logic
dns DNS Custom DNS resolution
cron Cron Scheduled/timed scripts
event Event System event handlers (e.g., network-changed)

Body auto-detection: If the script source contains $request.body or $response.body, the body will automatically be provided up to max-size. Use requires-body=true to force this behavior.


JavaScript API Reference

Scripts run in a sandboxed JavaScriptCore environment with the following global objects available.

$request (Read-only)

Available in: http-request, http-response, http-request-before-send, rule

Property Type Description
.url String Full request URL
.method String HTTP method (GET, POST, etc.) or QUERY for DNS
.headers Object Request headers as key-value pairs
.body String or null Request body (UTF-8 decoded)
.hostname String Target hostname
.destPort Number Destination port
.processPath String Requesting process path (macOS only)
.userAgent String User-Agent header value
.sourceIP String Source IP address
.listenPort Number Proxy listen port
.requestId String Unique request ID
.dnsResult String Resolved IP address
.srcPort Number Source port
.protocol String Detected protocol: http, https, tcp, dns

$response (Read-only)

Available in: http-response

Property Type Description
.status Number HTTP status code
.headers Object Response headers as key-value pairs
.body String or null Response body (UTF-8 decoded)

$done(value) — Completion Handler

Must be called exactly once at the end of the script to signal completion. Script execution blocks until $done() is called or timeout expires.

$done({})                        // Passthrough — no modifications
$done()                          // Abort the connection
$done({matched: true})           // Rule match result (rule scripts only)
$done({address: "1.2.3.4"})      // DNS result (dns scripts only)

HTTP Request script return values:

$done({
    url: "https://new.example.com/path",     // Rewrite URL
    headers: {"X-Custom": "value"},           // Modify headers
    body: "new request body",                 // Modify body
    response: {                               // Return a synthetic response (skip upstream)
        status: 200,
        headers: {"Content-Type": "text/html"},
        body: "<html>Blocked</html>"
    }
})

When response is provided, the request is short-circuited: Chute returns the synthetic response directly to the client without contacting the upstream server. This is useful for blocking, mocking APIs, or returning cached content.

HTTP Response script return values:

$done({
    status: 200,                              // Modify status code
    headers: {"X-Custom": "value"},           // Modify response headers
    body: "new response body",                // Modify response body
    url: "https://other.example.com"          // Trigger 302 redirect
})

When url is provided, Chute returns a 302 redirect to the given URL instead of the original response.

DNS script return values:

$done({address: "1.2.3.4"})                  // Single IP
$done({addresses: ["1.2.3.4", "5.6.7.8"]})   // Multiple IPs
$done({address: "10.0.0.1", ttl: 300})       // With custom TTL (seconds, default 60)
$done({server: "8.8.8.8"})                   // Forward to specific DNS server

$httpClient — Async HTTP Client

Make HTTP requests from within scripts. All requests are cancelled on $done() or timeout.

$httpClient.get(url, function(error, response, data) {
    if (error) {
        console.log("Request failed: " + error)
    } else {
        console.log("Status: " + response.status)
        console.log("Response: " + data)
    }
})

$httpClient.post(url, {headers: {...}, body: "...", timeout: 5}, callback)
$httpClient.put(url, options, callback)
$httpClient.del(url, options, callback)
$httpClient.head(url, options, callback)
$httpClient.options(url, options, callback)
$httpClient.patch(url, options, callback)

Callback signature: callback(error, response, data)

  • error: Error string or null
  • response: {status: Number, headers: Object} or null
  • data: UTF-8 string response body or null

$persistentStore — Key-Value Storage

Persistent key-value storage that survives script and process restarts. Backed by NSUserDefaults.

$persistentStore.write(data, key)   // Store a value
$persistentStore.read(key)          // Retrieve a value
$persistentStore.remove(key)        // Remove a value

$notification — Local Notifications

Post local system notifications.

$notification.post("Title", "Subtitle", "Notification body text")

$network — Network Info

Read-only network state information.

$network.dns   // Array of DNS server IPs
$network.wifi  // {ssid: "WiFiName", bssid: "aa:bb:cc:dd:ee:ff"}

$environment — Runtime Info

$environment.system     // "iOS" or "macOS"
$environment.appVersion // App version string

$utils — Utilities

$utils.geoip("1.2.3.4")   // Country code (e.g., "US")
$utils.ipasn("1.2.3.4")   // ASN number (e.g., "13335")
$utils.ungzip(data)       // Decompress gzip data

$klne — Proxy Control API

Control the proxy runtime from scripts.

$klne.policyGroups                    // Get all policy groups
$klne.selectPolicy("Group", "Proxy")  // Switch policy for a group
$klne.getActiveConnections()          // List active connections
$klne.closeConnection("id")           // Close a connection
$klne.flushDNS()                      // Purge DNS cache
$klne.startURLTest("Group")           // Trigger URL test for a group
$klne.reloadConfiguration()           // Reload all configuration
$klne.setOutboundMode("rule")         // Set mode: "global", "proxy", "direct", "rule"
$klne.setHTTPCaptureEnabled(true)     // Enable/disable MITM

$script — Script Metadata

$script.name       // Script name from config
$script.type       // Script type string
$script.startTime  // Epoch timestamp

Per-Execution Globals

The following variables are injected per script execution and are specific to certain script types.

$argument — Script Argument

The string value from the argument= parameter in the script configuration. Available in: http-request, http-response, http-request-before-send, rule, dns, cron.

console.log("Argument: " + $argument)

$domain — DNS Domain (DNS Script Only)

The domain name being queried. Available only in dns scripts.

var domain = $domain  // e.g. "example.com"

$cronexp — Cron Expression (Cron Script Only)

The cron schedule expression from the script configuration. Available only in cron scripts.

console.log("Schedule: " + $cronexp)  // e.g. "*/30 * * * *"

$event — Event Info (Event Script Only)

Information about the triggering event. Currently only network-changed is supported.

console.log("Event: " + $event.name)  // "network-changed"

console — Logging

console.log("Debug message")    // Verbose log
console.warn("Warning message")  // Warning log
console.error("Error message")   // Warning log with [JS-ERROR] prefix

setTimeout(fn, seconds) — Timer

Schedule a function to run after a delay.

setTimeout(function() {
    console.log("Delayed execution")
}, 2.5)  // 2.5 seconds

$script(subScriptPath) — Sub-script Loader

Load and evaluate another JavaScript file.

$script("/path/to/helper.js")
$script("https://example.com/remote-script.js")

// Pass data between scripts using $persistentStore

Script Type Details

HTTP Request Script

Executed when request headers are received. Can modify URL, headers, and body before the request is forwarded.

[Script]
ModifyHeaders = type=http-request, script-path=modify.js, pattern=^https://api\.example\.com

HTTP Response Script

Executed when response headers are received. Can modify status, headers, and body before returning to the client.

[Script]
ModifyResponse = type=http-response, script-path=response.js, pattern=^https://api\.example\.com

HTTP Request Before Send Script

Executed after the full request body has been collected, just before sending upstream. Useful for modifying POST/PUT request bodies.

[Script]
BeforeSend = type=http-request-before-send, script-path=before-send.js, pattern=^https://api\.example\.com, requires-body=true

Rule Script

Custom rule matching. The script must call $done({matched: true}) or $done({matched: false}).

[Rule]
SCRIPT,MyRuleScript,DIRECT

[Script]
MyRuleScript = type=rule, script-path=rule.js

DNS Script

Custom DNS resolution. Receives $domain and returns resolved address(es).

// dns.js
var domain = $domain
if (domain === "internal.example.com") {
    $done({address: "10.0.0.1", ttl: 300})
} else {
    $done({})  // Passthrough to normal DNS resolution
}

Cron Script

Scheduled execution using cron expressions. Minimum interval is 60 seconds.

[Script]
HourlyTask = type=cron, script-path=hourly.js, cron-expression=0 * * * *

Simplified cron syntax is supported (e.g., */30 * * * * for every 30 minutes, comma-separated values).

Event Script

Triggered by system events. Currently supports the network-changed event (fires when WiFi or cellular network changes).

[Script]
NetChange = type=event, script-path=network-changed.js

The $event object is available:

$event.name  // "network-changed"

Practical Examples

Redirect Mobile Devices

An http-request script that redirects mobile users based on User-Agent:

[Script]
MobileRedirect = type=http-request, script-path=mobile-redirect.js, pattern=^https://example\\.com
// mobile-redirect.js
var ua = $request.headers["User-Agent"] || ""
if (/Mobile|Android|iPhone/.test(ua)) {
    $done({
        response: {
            status: 302,
            headers: {"Location": "https://m.example.com" + $request.url.replace(/.*example\\.com/, "")},
            body: ""
        }
    })
} else {
    $done({})
}

Block Content in API Responses

An http-response script that removes ads and sponsored content from a JSON API response:

[Script]
RemoveAds = type=http-response, script-path=remove-ads.js, pattern=^https://api\\.example\\.com/feed, requires-body=true
// remove-ads.js
var body = JSON.parse($response.body)
if (body.ads) {
    delete body.ads
}
if (body.recommendations) {
    body.recommendations = body.recommendations.filter(function(r) {
        return !r.sponsored
    })
}
$done({body: JSON.stringify(body)})

Modify Request Body Before Sending

An http-request-before-send script that sanitizes a POST payload:

[Script]
SanitizePayload = type=http-request-before-send, script-path=sanitize.js, pattern=^https://api\\.example\\.com/submit, requires-body=true
// sanitize.js
var body = JSON.parse($request.body)
body.clientSecret = "[REDACTED]"
body.timestamp = Math.floor(Date.now() / 1000)
$done({body: JSON.stringify(body)})

Custom Rule: Time-Based Routing

A rule script that selects a different proxy depending on the time of day:

[Rule]
SCRIPT,TimeBasedRule,ProxyA

[Script]
TimeBasedRule = type=rule, script-path=time-rule.js
// time-rule.js
var hour = new Date().getHours()
if (hour >= 9 && hour < 18) {
    $done({matched: false})  // Fall through to next rule during work hours
} else {
    $done({matched: true})   // Use ProxyA during off hours
}

Custom DNS for Internal Domains

A dns script that resolves internal hostnames to local IPs:

[Script]
InternalDNS = type=dns, script-path=internal-dns.js
// internal-dns.js
var internalHosts = {
    "gitlab.local": "10.0.0.10",
    "registry.local": "10.0.0.11",
    "monitor.local": "10.0.0.12"
}
if (internalHosts[$domain]) {
    $done({address: internalHosts[$domain], ttl: 3600})
} else {
    $done({})  // Passthrough to normal DNS
}

Auto-Switch Policy on Network Change

An event script that switches to a conservative policy group when on cellular:

[Script]
NetSwitch = type=event, script-path=network-switch.js
// network-switch.js
if (!$network.wifi.ssid) {
    // On cellular — use low-data group
    $klne.selectPolicy("MainGroup", "LowDataProxy")
    console.log("Switched to cellular profile")
} else if ($network.wifi.ssid === "Office") {
    $klne.selectPolicy("MainGroup", "DIRECT")
    console.log("Switched to office profile")
}

Periodic Health Check

A cron script that checks proxy health every 30 minutes:

[Script]
HealthCheck = type=cron, script-path=health-check.js, cron-expression=*/30 * * * *
// health-check.js
$httpClient.head("https://www.google.com/generate_204", {timeout: 10},
    function(error, response, data) {
        if (error || response.status !== 204) {
            console.error("Health check failed: " + (error || "status " + response.status))
            $notification.post("Chute Alert", "Health Check", "Cannot reach Google")
        } else {
            console.log("Health check OK")
        }
    }
)
$done()

Enrich API Responses with External Data

An http-response script that enriches user data by calling a secondary API:

[Script]
EnrichUsers = type=http-response, script-path=enrich.js, pattern=^https://api\\.example\\.com/users, requires-body=true
// enrich.js
var users = JSON.parse($response.body)
var pending = users.length
if (pending === 0) { $done({}) }

users.forEach(function(user, index) {
    $httpClient.get("https://internal-api.example.com/avatar/" + user.id,
        function(error, resp, data) {
            if (!error && resp.status === 200) {
                users[index].avatar = JSON.parse(data).url
            }
            pending--
            if (pending === 0) {
                $done({body: JSON.stringify(users)})
            }
        }
    )
})

Execution Model

  • All scripts run on a dedicated serial queue for thread safety.
  • Each script execution has a per-script timeout; if $done() is not called within the timeout, the script is treated as passthrough.
  • A JSContext pool (size 3) is used for performance; contexts are reused across executions.
  • Script compilation is cached by default; use debug=true to bypass the cache.
  • On iOS/tvOS, scripts are subject to a 10MB memory limit; on macOS, 512MB.
  • Remote scripts (HTTP/HTTPS paths) are fetched on start and optionally re-fetched at script-update-interval seconds.

Module Script Integration

Scripts can also be defined in Module files (.sgmodule) under the [Script] section. ```

S. Smart Rabbit LLC © All Rights Reserved            updated 2026-06-28 02:09:17

results matching ""

    No results matching ""