# Rewrite

> Change headers, query parameters, the URL, bodies and the status code of matching requests and responses in Cloak Proxy, without writing a script.

Source: https://docs.subcloak.com/cloak-proxy/tools/rewrite/

Added in Cloak Proxy 0.2.0.

Rewrite changes requests and responses as they pass through Cloak Proxy, using a list of actions instead of code. Each action sets, adds, removes or replaces one thing: a header, a query parameter, text in the URL or a body, or the response status. Use it for the edits you'd otherwise write a short script for, such as adding an `Authorization` header, stripping a tracking parameter, turning on a feature flag in a response, or making an endpoint return `503`.

## Add a rewrite rule

1. Right-click a request in the traffic list and choose **Tools › Rewrite…**.

   Cloak Proxy creates a rule that matches the request's method and URL (without its query string), with subpaths off, turns Rewrite on and opens the rule. To start from scratch instead, choose **Tools › Edit Rewrite Rules…** (<kbd>⌥</kbd> <kbd>⌘</kbd> <kbd>R</kbd>) and click **+**. A new rule is named **New Rewrite** and matches nothing until you fill in its URL.

2. Check the matcher. To cover a whole API, use a pattern such as `api.example.com/v1/*`. See [Rules and matching](https://docs.subcloak.com/cloak-proxy/tools/rules-and-matching/).

3. Under **Actions**, click **Add Action** and choose what to change: **Request header**, **Query parameter**, **URL**, **Request body**, **Response header**, **Response body** or **Response status**.

4. Pick an operation in the second menu and fill in the fields. Add as many actions as you need.

5. Make the request again in your app. When a rule changed it, the [traffic list](https://docs.subcloak.com/cloak-proxy/inspect/traffic-list/) shows a `Rewrite` badge before the path.

Changes apply right away; there's no Save button. The rule list shows each rule's name and the method and URL it matches.

*[Screenshot: A Rewrite rule for GET https://api.example.com/v1/users limited to MyApp, with four actions: set the Authorization request header, remove the debug query parameter, set the response status to 503, and replace active true with active false in the response body. One rule, four actions. They run top to bottom on every request the rule matches.]*

## Targets and operations

Each action has a target (what it changes) and an operation (how). The operation menu only offers the operations that make sense for the target. A new action starts with the first operation in its list.

| Target | Operations | Changes |
|---|---|---|
| **Request header** | **Set**, **Add**, **Remove**, **Find and replace** | A header the server receives. |
| **Query parameter** | **Set**, **Remove**, **Find and replace** | A parameter in the request URL's query string. |
| **URL** | **Find and replace** | The full request URL: scheme, host, port, path and query. |
| **Request body** | **Find and replace** | Text in the body the server receives. |
| **Response header** | **Set**, **Add**, **Remove**, **Find and replace** | A header the app receives. |
| **Response body** | **Find and replace** | Text in the body the app receives. |
| **Response status** | **Set** | The status code the app receives. |

### What each operation does

| Operation | Fields | On a header | On a query parameter | On the URL or a body | On the status |
|---|---|---|---|---|---|
| **Set** | A name and a value (for the status, only the code) | Sets the header to the value. If the header appears more than once, one copy is kept, in the first one's position. If it's missing, it's added at the end. | Removes every parameter with that name and adds one with the value at the end. | | Sets the status code, for example `503`. |
| **Add** | A name and a value | Adds another header with that name, keeping any that are already there. | | | |
| **Remove** | A name | Removes every header with that name. | Removes every parameter with that name. | | |
| **Find and replace** | A name (headers and parameters only; empty means all), **Find**, **Replace with** and **Regex** | Replaces text in the value of the named header, or of every header when the name is empty. | Replaces text in the value of the named parameter, or of every parameter when the name is empty. | Replaces every occurrence in the URL or body. | |

The name field's placeholder says **Header** or **Parameter**, or **Name (empty: all)** for **Find and replace**. The value field says **Value**, or **Status code, e.g. 503** for the status. How names and values are read:

- **Header names** match regardless of case, so `authorization` and `Authorization` are the same header.
- **Query parameter names** match exactly, including case.
- **Values** are sent exactly as you type them. Cloak Proxy doesn't fill in variables, so `{{token}}` is sent as the literal text `{{token}}`.
- **Find** is case-sensitive. An empty **Find** leaves the text unchanged.
- **Response status** accepts a number from 100 to 599. Anything else is ignored and the server's status is kept.
- **Set**, **Add** and **Remove** on a header or parameter do nothing while the name is empty.

### Turn actions on and off

The checkbox at the start of each action turns it on or off. A turned-off action is dimmed and skipped. The minus button at the end of the row removes the action. To pause the whole rule, clear its checkbox in the rule list; to pause every rule, use the tool's **On**/**Off** switch.

## Replace text with a regular expression

Turn on **Regex** to treat **Find** as a regular expression. Every match is replaced, and **Replace with** can use `$1`, `$2` and so on for the parts of the match in parentheses (`$0` is the whole match).

| Find | Replace with | Before | After |
|---|---|---|---|
| `MyApp/([0-9.]+)` | `MyApp/$1-debug` | `MyApp/4.2.0 (iPhone)` | `MyApp/4.2.0-debug (iPhone)` |
| `"email":\s*"[^"]*"` | `"email": "ada@example.com"` | `"email": "someone@example.org"` | `"email": "ada@example.com"` |
| `/v1/([a-z]+)/` | `/v2/$1/` | `https://api.example.com/v1/users/42` | `https://api.example.com/v2/users/42` |

Matching is case-sensitive unless you start the pattern with `(?i)`. A pattern that isn't a valid regular expression leaves the text unchanged. To put a literal `$` in the replacement, write `\$`.

> **Tip:**
> Test a pattern on a small piece of text first. A broad pattern on a response body, such as `.*`, can replace far more than you meant.

## Examples

### Add an auth header to every request

Send a test token to your staging API without changing the app.

| Matcher | Action |
|---|---|
| **ANY** `staging.example.com/*` | **Request header** · **Set** · `Authorization` → `Bearer sk_test_xxxx` |

**Set** replaces any `Authorization` header the app already sends. Use **Add** to send a second header with the same name instead.

### Strip a tracking parameter

Remove `utm_source` and `utm_campaign` before requests reach the server, for example so cached responses match.

| Matcher | Actions |
|---|---|
| **ANY** `api.example.com/*` | **Query parameter** · **Remove** · `utm_source` |
| | **Query parameter** · **Remove** · `utm_campaign` |

Each action removes one parameter name. Add one action per parameter.

### Force an error to test your error screen

| Matcher | Actions |
|---|---|
| **GET** `https://api.example.com/v1/users` | **Response status** · **Set** · `503` |
| | **Response header** · **Set** · `Retry-After` → `30` |

The request still reaches the server, and the app receives the server's body with status `503`. To answer without contacting the server at all, use [Map Local](https://docs.subcloak.com/cloak-proxy/tools/map-local/) instead.

### Turn on a feature flag

Change a flag in a JSON response so the app shows a feature that's off on the server.

| Matcher | Action |
|---|---|
| **GET** `https://api.example.com/v1/flags` | **Response body** · **Find and replace** · `"newCheckout": false` → `"newCheckout": true` |

Find and replace works on the body's text, so **Find** must match the spacing the server uses. If the server sends `"newCheckout":false` without a space, match that, or use **Regex** with `"newCheckout":\s*false`.

### Point the app at a new API version

| Matcher | Action |
|---|---|
| **ANY** `api.example.com/v1/*` | **URL** · **Find and replace** · `/v1/` → `/v2/` |

This changes the path but not the server. A URL action doesn't update the `Host` header, so to send requests to a different host, use [Map Remote](https://docs.subcloak.com/cloak-proxy/tools/map-remote/), which does.

## When rewrites run

Rewrite works on HTTP traffic Cloak Proxy can read: plain HTTP, and HTTPS with [SSL Proxying](https://docs.subcloak.com/cloak-proxy/capture/ssl-proxying/) on. It changes the request on the way out and the response on the way back:

| Side | Runs | So |
|---|---|---|
| Request actions (**Request header**, **Query parameter**, **URL**, **Request body**) | Right after the [Block List](https://docs.subcloak.com/cloak-proxy/tools/block-list/), before [Network Conditions](https://docs.subcloak.com/cloak-proxy/tools/network-conditions/), request [scripts](https://docs.subcloak.com/cloak-proxy/tools/scripting/) and the request [breakpoint](https://docs.subcloak.com/cloak-proxy/tools/breakpoint/). | Every later tool, including [Map Local](https://docs.subcloak.com/cloak-proxy/tools/map-local/), [Mock Sets](https://docs.subcloak.com/cloak-proxy/tools/mock-sets/) and [Map Remote](https://docs.subcloak.com/cloak-proxy/tools/map-remote/), sees the rewritten request. A paused request already shows your rewrites. |
| Response actions (**Response header**, **Response body**, **Response status**) | After the response arrives (and after [No Caching](https://docs.subcloak.com/cloak-proxy/tools/no-caching/) strips its headers), before response scripts and the response breakpoint. | Response scripts and a paused response see the rewritten response. |

More details:

- Rules match the request as the app sent it, for both the request and the response actions. A rule whose **URL** action changes the path still applies its response actions.
- Every enabled rule that matches applies, from the top of the list to the bottom, and each rule's actions run in order. Each one works on the result of the one before.
- Response actions also apply to responses from Map Local and Mock Sets. They don't apply to the `403` the Block List sends, or to a request aborted at a breakpoint.
- [Repeat, Edit and Repeat and Compose](https://docs.subcloak.com/cloak-proxy/tools/repeat-and-compose/) requests go through Rewrite like live traffic.
- The traffic list records the request as it was finally sent and the response as the app received it.

> **Streamed responses:**
> A rule that changes the response body holds a streamed response (SSE, JSON lines) until it ends, so the app gets all its events at once. Header and status rewrites keep streaming. See [Streaming responses](https://docs.subcloak.com/cloak-proxy/inspect/streams/#streams-pass-through-as-they-arrive).

The full order of every tool is in [What happens when several tools match](https://docs.subcloak.com/cloak-proxy/tools/rules-and-matching/#what-happens-when-several-tools-match).

### Compressed and binary bodies

Before a **Request body** or **Response body** action runs, Cloak Proxy decompresses a gzip, deflate or Brotli body and removes the `Content-Encoding` header, so **Find** sees readable text. When the rule changes anything, the body is sent on uncompressed. When it changes nothing, the original body is sent as it was. Bodies are only decompressed for rules that have a body action.

A body that isn't UTF-8 text, such as an image, is left unchanged. Cloak Proxy recalculates `Content-Length` after any change.

## Rewrite, Scripting or Map Remote?

All three change traffic. Pick the simplest one that does the job.

| Use | When you want to | Examples |
|---|---|---|
| **Rewrite** | Make fixed edits you can describe as "set this", "remove that" or "replace this text". | Add a header, drop a parameter, force a status, flip a flag in a body. |
| [Scripting](https://docs.subcloak.com/cloak-proxy/tools/scripting/) | Decide in code: edit JSON fields as objects, compute values, change the method, keep state between requests, or act only sometimes. | Fail every third request, sign a body, reuse a token from an earlier response. |
| [Map Remote](https://docs.subcloak.com/cloak-proxy/tools/map-remote/) | Send requests to a different server, with the `Host` header updated to match. | Production to staging, or to `localhost:3000`. |

Rewrite runs before scripts, so a script sees what Rewrite changed. You can combine them: a Rewrite rule for the fixed headers, and a script for the logic.

## Limit a rule to one app

The **Only these apps** field under the URL limits a rule to requests from the apps you list, by name as shown in the [sidebar](https://docs.subcloak.com/cloak-proxy/inspect/sidebar/), separated by commas. Leave it empty to match every app. For example, `MyApp` adds a debug header to your app's requests to `api.example.com` but not to the same requests from Safari. See [Limit a rule to apps](https://docs.subcloak.com/cloak-proxy/tools/rules-and-matching/#limit-a-rule-to-apps).

## Export and use rules elsewhere

- **Tools › Export Rules…** includes your Rewrite rules in the JSON file, under `rewrites`, with the on/off switch in `rewritesEnabled`.
- **Tools › Import Rules…** adds the Rewrite rules from a file to yours; a rule with the same `id` replaces the existing one. Your Rewrite switch stays as it is.
- [`cloakctl run --tools RULES.json`](https://docs.subcloak.com/cloak-proxy/automation/headless-and-ci/) applies the Rewrite rules in an exported file to a headless run.

> **Rules can hold secrets:**
> A token you put in a **Value** field is saved with your rules, and is written into every rules export. Use test tokens, and read an export before you share or commit it.

## Create rewrite rules from an AI assistant

The [MCP server](https://docs.subcloak.com/cloak-proxy/automation/mcp-server/) manages Rewrite rules like any other tool:

- `list_rules` with `tool` set to `rewrite` lists the rules, whether the tool is on, and an example rule with every field.
- `upsert_rule` with `tool` set to `rewrite` creates a rule, or changes one when you pass its `id`. It turns Rewrite on.
- `rule_from_request` with `tool` set to `rewrite` creates an empty rule matching a captured request.
- `set_tool_enabled` and `delete_rule` turn the tool on or off and delete a rule.

An `upsert_rule` call that adds the auth header and strips a tracking parameter for one app:

```json title="upsert_rule arguments"
{
  "tool": "rewrite",
  "rule": {
    "name": "Staging auth",
    "matcher": {
      "url": "staging.example.com/*",
      "method": "ANY",
      "apps": ["MyApp"]
    },
    "actions": [
      { "target": "requestHeader", "operation": "set", "name": "Authorization", "value": "Bearer sk_test_xxxx" },
      { "target": "queryParameter", "operation": "remove", "name": "utm_source" },
      { "target": "responseBody", "operation": "replace", "find": "\"beta\": false", "value": "\"beta\": true" },
      { "target": "status", "operation": "set", "value": "503", "isEnabled": false }
    ]
  }
}
```

The action fields:

| Field | Values | Default |
|---|---|---|
| `target` | `requestHeader`, `queryParameter`, `url`, `requestBody`, `responseHeader`, `responseBody`, `status` | Required |
| `operation` | `set`, `add`, `remove`, `replace` | The target's first operation: `set` for headers, parameters and the status, `replace` for the URL and bodies |
| `name` | Header or parameter name | `""` |
| `value` | The new value, the replacement text, or the status code as a string | `""` |
| `find` | The text or regular expression to find, for `replace` | `""` |
| `isRegex` | `true` or `false` | `false` |
| `isEnabled` | `true` or `false` | `true` |

> **Note:**
> `actions` replaces the rule's whole action list. When you create a rule, always pass `actions`: a new rule starts from the example rule, which has two sample actions (an `X-Debug` request header and a `beta` body edit) that stay unless you replace them.

See the [MCP tools reference](https://docs.subcloak.com/cloak-proxy/automation/mcp-tools-reference/) for every argument.

> **Turn rewrites off when you're done:**
> Rewrite rules keep working after you close the Tools window and after a restart. A forgotten rule can make an app behave strangely later. While Rewrite is on with an enabled rule, it's listed when you hover **N tools on** in the bottom bar; click that button and then **Rewrite** to open the rules.

## Related

  - [Scripting](https://docs.subcloak.com/cloak-proxy/tools/scripting/): Change requests and responses with JavaScript when fixed edits aren't enough.
  - [Map Remote](https://docs.subcloak.com/cloak-proxy/tools/map-remote/): Send matching requests to another server.
  - [Rules and matching](https://docs.subcloak.com/cloak-proxy/tools/rules-and-matching/): Patterns, app limits and the order tools run in.
  - [Breakpoint](https://docs.subcloak.com/cloak-proxy/tools/breakpoint/): Make a one-off edit by hand instead.
