{"id":13775775,"url":"https://github.com/roglew/guppy-proxy","last_synced_at":"2026-02-15T23:34:15.502Z","repository":{"id":73622597,"uuid":"117880091","full_name":"roglew/guppy-proxy","owner":"roglew","description":"The Guppy Proxy (GUI Pappy)","archived":false,"fork":false,"pushed_at":"2019-06-28T20:16:39.000Z","size":17472,"stargazers_count":144,"open_issues_count":1,"forks_count":18,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-11-17T10:40:21.828Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roglew.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-01-17T19:05:42.000Z","updated_at":"2024-11-05T20:47:34.000Z","dependencies_parsed_at":"2024-01-07T22:45:17.748Z","dependency_job_id":"7ac6942b-0de3-4a6b-a294-d40edbf06b32","html_url":"https://github.com/roglew/guppy-proxy","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roglew%2Fguppy-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roglew%2Fguppy-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roglew%2Fguppy-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roglew%2Fguppy-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roglew","download_url":"https://codeload.github.com/roglew/guppy-proxy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253540438,"owners_count":21924522,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T17:01:49.420Z","updated_at":"2026-02-15T23:34:15.473Z","avatar_url":"https://github.com/roglew.png","language":"Python","funding_links":[],"categories":["Python","\u003ca id=\"d03d494700077f6a65092985c06bf8e8\"\u003e\u003c/a\u003e工具"],"sub_categories":["\u003ca id=\"0ff94312f3ab4898f5996725133ea9d1\"\u003e\u003c/a\u003e未分类"],"readme":"# Guppy Proxy\n\nThe Guppy Proxy is an intercepting proxy for performing web application security testing. Its features are often similar to, or straight up rippoffs from [Burp Suite](https://portswigger.net/burp/). However, Burp Suite has its own issues (search, licensing) which led to the creation of Guppy.\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_main.png)\n\n# Installation\n\n## Dependencies\n\nMake sure the following commands are available:\n\n* `python3`\n* `pip`\n* `virtualenv` (can be installed with pip)\n\n## Installing\n\n### Mac\n\n1. Download the .app of version of guppy [available here](https://guppydist.s3-us-west-2.amazonaws.com/GuppyProxy-0.0.15.zip)\n1. Start the application\n1. Add the CA cert in `~/.guppy/certs` to your browser as a CA\n1. Configure your browser to use `localhost:8080` as a proxy\n1. Navigate to a site and look at the history in the main window\n\n### Linux / Alternative for Mac\n\n1. Clone this repo somewhere it won't get deleted: `git clone https://github.com/roglew/guppy-proxy.git`\n1. `cd /path/to/guppy-proxy`\n1. `./install.sh` to use pre-built binary or `./install.sh -p` to compile the go component from source (requires a [go installation](https://golang.org/doc/install))\n1. Test that the application starts up and generate certs: `./start` (keep the window open and continue to test it works)\n1. Copy/symlink the generated `start` script somewhere in your PATH (i.e. `~/bin` if you have that included) and rename it to `guppy` if you want\n1. Add the CA cert in `~/.guppy/certs` to your browser as a CA\n1. Configure your browser to use `localhost:8080` as a proxy\n1. Navigate to a site and look at the history in the main window\n\n## Updating\n\n1. Navigate to the guppy-proxy folder with this repo in it\n1. `git pull` to pull the latest version\n1. run `./install.sh` again\n\nThe same start script as before should still work\n\n## Uninstalling\n\n1. Delete the guppy-proxy directory you made during installation\n1. Delete `~/.guppy`\n1. Remove the start script from wherever you put it\n\n# How to Use Guppy\n\n## History View\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_main.png)\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_pretty_view.png)\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_tree.png)\n\nThe first thing you see when you open Guppy is the history view. As requests pass through the proxy they are displayed in the lower half of the window. You can click a request to view the full request/response in the windows on the upper half or right click them for more options. The tabs on the upper half will let you view additional information about the selected request:\n\n* Messages - The full request/response\n* Info - A list of values associated with the message\n* Tags - Lets you view/edit the tags currently associated with the request\n\nThe bottom half has tabs which relate to all of the requests that have been recorded by the proxy:\n\n* List - A list of all of the requests that have been recorded by the proxy\n* Tree - A site map of all of the endpoints visited\n* Filters - An advanced search interface which is described below in the Filters section\n\n## Filters and Search\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_search.png)\n\nGuppy's main selling point over other similar proxies is its search. You can search for a wide variety of fields within a request or response and apply more than one search condition at the same time. This allows you to perform complex searches over your history so that you can always find the request that you want. You would be surprised what you can find when searching for paths, headers, and body contents. For example you can find potential CSRF targets by finding requests which are not GET requests and also do not have a header with \"CSRF\" in it.\n\nHow to apply a filter to your search:\n\n1. Select the field you want to search by\n1. Select how you want to search it (whether it contains a value, matches a regexp, is an exact value, etc)\n1. Enter the value to search by in the text box\n1. Click \"Ok\" or press enter in the text box\n\nOnce you apply a filter, the \"list\" and \"tree\" tabs will only include requests which match ALL of the active filters.\n\nIn addition, you can apply different filters for the key and value of key/value fields (such as headers or request parameters). This can be done by:\n\n1. Select a key/value field such as \"Rsp. Header\" or \"URL Param\"\n1. Click the \"+\" button on the right\n1. Enter the filter for the key on the left and the filter for the value on the right\n1. Click \"Ok\" or press enter in one of the text boxes\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_search_kv.png)\n\nAnd that's it! The filter tab has the following additional controls:\n\n1. Clear - Delete all active filters\n1. Pop - Delete the most recent filter\n1. Scope - Set the active search to your project's scope (see below)\n1. Save Scope - Set your project's scope to the currently active filters (see below)\n1. Apply a built-in filter dropdown - Guppy has a list of commonly useful filters. Select one from this list to apply it\n\n### Text Filter Entry\n\nAlong with the provided dropdowns you can manually type in a filter by clicking the `\u003e` button. In some cases it may be faster to type your filter out rather than clicking on dropdowns. In addition it allows you to create filter statements that contain an `OR` and will pass a request that matches any one of the given filters. In fact, all the dropdown input does is generate these strings for you.\n\nMost filter strings have the following format:\n\n```\n\u003cfield\u003e \u003ccomparer\u003e \u003cvalue\u003e\n```\n\nWhere `\u003cfield\u003e` is some part of the request/response, `\u003ccomparer\u003e` is some comparison to `\u003cvalue\u003e`. For example, if you wanted a filter that only matches requests to `target.org`, you could use the following filter string:\n\n```\nhost is target.org\n\nfield = \"host\"\ncomparer = \"is\"\nvalue = \"target.org\"\n```\n\nFor fields that are a list of key/value pairs (headers, get params, post params, and cookies) you can use the following format:\n\n```\n\u003cfield\u003e \u003ccomparer1\u003e \u003cvalue1\u003e[ \u003ccomparer2\u003e \u003cvalue2\u003e]\n```\n\nThis is a little more complicated. If you don't give comparer2/value2, the filter will pass any pair where the key or the value matches comparer1 and value1. If you do give comparer2/value2, the key must match comparer1/value1 and the value must match comparer2/value2 For example:\n\n```\nFilter A:\n    cookie contains Session\n\nFilter B:\n    cookie contains Session contains 456\n\nFilter C:\n    inv cookie contains Ultra\n\nCookie: SuperSession=abc123\nMatches A and C but not B\n\nCookie: UltraSession=abc123456\nMatches both A and B but not C\n```\n\n#### List of fields\n\n| Field Name | Aliases | Description | Format |\n|:--------|:------------|:-----|:------|\n| all | all | Anywhere in the request, response, or a websocket message | String |\n| reqbody | reqbody, reqbd, qbd, qdata, qdt | The body of the request | String |\n| rspbody | rspbody, rspbd, sbd, sdata, sdt | The body of the response | String |\n| body | body, bd, data, dt | The body in either the request or the response | String |\n| wsmessage | wsmessage, wsm | In a websocket message | String |\n| method | method, verb, vb | The request method (GET, POST, etc) | String |\n| host | host, domain, hs, dm | The host that the request was sent to | String |\n| path | path, pt | The path of the request | String |\n| url | url | The full URL of the request | String |\n| statuscode | statuscode, sc | The status code of the response (200, 404, etc) | String |\n| tag | tag | Any of the tags of the request | String |\n| reqheader | reqheader, reqhd, qhd | A header in the request | Key/Value |\n| rspheader | rspheader, rsphd, shd | A header in the response | Key/Value |\n| header | header, hd | A header in the request or the response | Key/Value |\n| param | param, pm | Either a URL or a POST parameter | Key/Value |\n| urlparam | urlparam, uparam | A URL parameter of the request | Key/Value |\n| postparam | postparam, pparam | A post parameter of the request | Key/Value |\n| rspcookie | rspcookie, rspck, sck | A cookie set by the response | Key/Value |\n| reqcookie | reqcookie, reqck, qck | A cookie submitted by the request | Key/Value |\n| cookie | cookie, ck | A cookie sent by the request or a cookie set by the response | Key/Value |\n\n#### List of comparers\n\n| Field Name | Aliases | Description |\n|:--------|:------------|:-----|\n| is | is | Exact string match | \n| contains | contains, ct | A contain B is true if B is a substring of A |\n| containsr | containsr, ctr | A containr B is true if A matches regexp B |\n| leneq | leneq | A Leq B if A's length equals B (B must be a number) |\n| lengt | lengt | A Lgt B if A's length is greater than B (B must be a number ) |\n| lenlt | lenlt | A Llt B if A's length is less than B (B must be a number) |\n\n#### Special form filters\n\nA few filters don't conform to the field, comparer, value format. You can still negate these.\n\n| Format | Aliases | Description |\n|:--|:--|:--|\n| invert \u003cfilter string\u003e | invert, inv | Inverts a filter string. Anything that matches the filter string will not pass the filter. |\n\nExamples:\n\n```\nShow state-changing requests\n  inv method is GET\n\nShow requests without a csrf parameter\n  inv param ct csrf\n```\n\n#### Using OR\n\nIf you want to create a filter that will pass a request if it matches any of one of a few filters you can create `OR` statements. This is done by entering in each filter on the same line and separating them with an `OR` (It's case sensitive!).\n\nExamples:\n\n```\nShow requests to target.org or example.com:\n    host is target.org OR host is example.com\n\nShow requests that either are to /foobar or have foobar in the response or is a 404\n    path is /foobar OR sbd ct foobar OR sc is 404\n```\n\n### Scope\n\nThe scope of your project describes which requests should be recorded as they pass through the proxy. Guppy allows you to define a set of filters which describe which requests are in scope. For example, if your scope is just `host ctr example.com$` only requests to example.com will be recorded in history.\n\nTo set the scope of your project:\n\n1. Enter the filters you want to be your scope\n1. Press the \"Save Scope\" button\n\nAnd you're done! Requests that do not match this set of filters will no longer be saved. You can also set your current search to your scope by clicking the \"Scope\" button. The scope can be deleted by pressing the \"Clear\" button to delete all active filters and then clicking \"Save Scope\".\n\n# Repeater\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_repeater.png)\n\nThe repeater lets you repeatedly tweak and submit a request. You can use a request in the repeater by:\n\n1. Find the request you which to resubmit in the history list view\n1. Right click the request and click \"Send to Repeater\"\n1. Navigate to the repeater tab\n1. Edit the request on the left\n1. Click the submit button\n\nWhen you click submit:\n\n* The request will be submitted\n* The request and response will be saved in history\n* Any tags under the \"tag\" tab will be applied to the request\n\n# Interceptor\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_interceptor.png)\n\nThe interceptor lets you edit requests and responses as they pass through the proxy. To use this:\n\n1. Navigate to the interceptor tab\n1. Click the \"Int. Requests\" and/or the \"Int. Responses\" buttons\n1. Wait for a request to pass through the proxy\n1. Edit the message in the text box then click \"Forward\" to forward the edited message or click \"Cancel\" to just drop the message altogether\n\n# Decoder\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_decoder.png)\n\nThe decoder allows you to perform common encoding/decoding actions. You use the decoder by:\n\n1. Paste the data that you want to encode/decode\n1. Select how you wish to encode/decode it\n1. Press \"Go!\"\n\nThe text will be processed and it will appear in the same text box. Easy!\n\n# Macros\n\nGuppy includes support for loading and executing Python scripts in order to allow for more complex attacks than can be performed by hand with the repeater. It is worth noting that **this feature is not user friendly. Use it at your own risk.** No attempt is made to make this feature user-friendly, stable, or good. The main reason it exists is to make it easier to write python scripts which integrate with Guppy history and to provide some way to extend Guppy's features without a pull request. If you haven't been scared away yet, read on.\n\nThere are two types of macros that you can write:\n\n* Active macros: Take requests as an input, make more requests, edit the input requests, etc, then output a new set of requests for review\n* Intercepting macros: Modify requests and responses as they pass through the proxy\n\nMost features that you want will fall into one of those categories. Both macros are created by creating a `.py` file and defining specific functions which will be run when the macro is executed. For example an active macro must define `run_macro` and an intercepting macro must define `mangle_request` and/or `mangle_response`. See their respective sections below for more details.\n\n## The API\n\nUnfortunately since this feature was pretty much just thrown together for my own use, the documentation is looking at the source. Hopefully it will become more stable once Guppy development slows down, but for now you'll have to look at the relevant classes to figure out how to do stuff on your own. The following classes are the most important when writing a macro:\n\n### MacroClient\n\n`MacroClient` is defined in `(guppyproxy/macro.py)` and is the interface that macros use to submit requests, save requests to history, and produce output. At the time of writing the class provides:\n\n```\nMacroClient.submit(req, save=False): Submits a request to the server and sets req.response to the response. req is the HTTPRequest to submit. req.dest_host, req.dest_port, and req.use_tls will be used to determine the location to submit the request to\nMacroClient.save(req): Permenantly saves an HTTPRequest to history\nMacroClient.output(s): Prints a string to the output tab in the macros interface\nMacroClient.output_req(req): Adds a request to the output request table in the macros interface\nMacroClient.new_request(method=\"GET\", path=\"/\", proto_major=1, proto_minor=1,\n                        headers=None, body=bytes(), dest_host=\"\", dest_port=80,\n                        use_tls=False, tags=None): Creates a new HTTPRequest from scratch that can be submitted with the client\n```\n\n### HTTPRequest and HTTPResponse\n\n`HTTPRequest` and `HTTPResponse` are defined in `guppyproxy/proxy.py`. These classes represent HTTP messages. `HTTPRequest` contains both the contents of the message and information about its intended destination (host, port, whether to use TLS). Below are a few examples on how to use these classes, however for more deails you will need to consult `proxy.py`:\n\n```python\nreq = HTTPRequest()\nrsp = HTTPResponse()\n\nreq2 = req.copy() # Copy a request\nrsp2 = rsp.copy() # Copy a response\n\n# Refer to the messages associated with a messages\nrsp3 = req.response # Response to a request, will be `None` if there was no response\nunm = req.unmangled # Unmangled version of a request, is `None` if none exist\nunm2 = rsp.unmangled # Unmangled version of a response, is `None` if none exist\n\n# Get the full message of an object (is a bytes())\nfull_req = req.full_message()\nfull_rsp = rsp.full_message()\n\n# Get timing info for a request\ntstart = req.time_start # datetime.datetime when the request was made\ntend = req.time_end # datetime.datetime when the request's response was received\n\n# Get destination info from a request\ndest_host = req.dest_host\ndest_port = req.dest_port\nuse_tls = req.use_tls\n\n# Get/set the method of a request\nm = req.method # Get the method of the request\nreq.method = \"POST\" # Set the method of the request\n\n# Get/set url info of a request\nrequrl = req.full_url() # get the full URL of a request\npath = req.url.path # get the path of a request\nreq.url.path = \"/foo/bar/baz\" # set the path of a request\nv = req.url.get_param(\"foo\") # get the value of the \"foo\" URL parameter\nreq.url.set_param(\"foo\", \"bar\") # set the value of the \"foo\" URL parameter to \"bar\"\nreq.url.add_param(\"foo\", \"bar2\") # add a URL parameter allowing duplicates\nreq.url.del_param(\"foo\") # delete a url parameter\n[(k, v) for k, v in req.url.param_iter] # iterate over all the key/value pairs in the URL parameters\nfrag = req.url.fragment # get the fragment of the url (the bit after the #)\nreq.url.fragment = \"frag\" # set the url fragment of the request\n\n# Manage headers in a message\nreq.headers.set(\"Foo\", \"Bar\") # set a header, repalcing existing value\nhd = req.headers.get(\"Foo\") # get the value of a header (for duplicates, returns first value)\nreq.headers.add(\"Foo\", \"Bar2\") # add a header without replacing an existing one\npairs = req.headers.pairs() # returns all the key/value pairs of the headers in the message\nreq.headers.delete(\"Foo\") # delete a header\nreq.headers.dict() # Returns a dict of the headers in the form of {\"key1\": [\"val1\", \"val2\"], \"key2\": [\"val3\", \"val4\"]}\n# Same for responses\nrsp.headers.set(\"Foo\", \"Bar\")\nhd = rsp.headers.get(\"Foo\")\nrsp.headers.add(\"Foo\", \"Bar2\")\npairs = rsp.headers.pairs()\nrsp.headers.delete(\"Foo\")\nrsp.headers.dict()\n\n# Manage body of a message\nreq.body = \"foo=bar\" # set the body of the message to a string\nreq.body = b\"\\x01\\x02\\x03\" # set the body to bytes\nbd = req.body # Get the value of the body (always is bytes())\n# Same for responses\nrsp.body = \"foo=bar\"\nrsp.body = b\"\\x01\\x02\\x03\"\nbd = rsp.body\n\n# Manage POST parameters of a request\nparams = req.parameters() # Returns a dict of the POST parameters in the form of {\"key1\": [\"val1\", \"val2\"], \"key2\": [\"val3\", \"val4\"]}\n[(k, v) for k, v in req.param_iter()] # Iterate through all the key/value pairs of the request parameters\nreq.set_param(\"Foo\", \"Bar\") # Set the \"Foo\" parameter to \"Bar\"\nreq.add_param(\"Foo\", \"Bar2\") # Add a POST parameter to the request allowing duplicates\nreq.del_param(\"Foo\") # Delete a parameter from the request\n# NOTE: Setting a POST parameter will not change the request method to POST\n\n# Managing the cookies of a message\ncookie = req.cookies() # Returns an http.cookies.BaseCookie representing the request's cookies\nreq.set_cookie(\"foo\", \"bar\") # set a cookie in the request\nreq.del_cookie(\"foo\") # delete a cookie from the request\n[(k, v) for k, v in req.cookie_iter()] # Iterate over the key/value pairs of the cookies in a request\nreq.set_cookies({\"cookie1\": \"val1\", \"cookie2\": \"val2\"}) # Set the cookies in the request\nreq.set_cookies(req2) # Set the requests on req to the cookies in req2\nreq.add_cookies({\"cookie1\": \"val1\", \"cookie2\": \"val2\"}) # Add cookies to the request replacing existing values\nreq.add_cookies(req2) # Add cookies from req2 to the request replacing existing values\n# Same for responses\ncookie = rsp.cookies()\nrsp.set_cookie(\"foo\", \"bar\")\nrsp.del_cookie(\"foo\")\n[(k, v) for k, v in rsp.cookie_iter()]\nrsp.set_cookies({\"cookie1\": \"val1\", \"cookie2\": \"val2\"})\nrsp.set_cookies(rsp2)\nrsp.add_cookies({\"cookie1\": \"val1\", \"cookie2\": \"val2\"})\nrsp.add_cookies(rsp2)\n\n# Manage tags of a request\nhastag = (\"tagname\" in req.tags) # check if a request has a tag\nreq.tags.add(\"tagname\") # add a tag to a request\nreq.tags.remove(\"tagname\") # remove a tag from the request\n# NOTE: req.tags is a regular set() and you can do whatever you want to it\n```\n\n## Macro Arguments\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_macro_args.png)\n\nBoth active and intercepting macros can optionally have Guppy prompt for a set of arguments before running. These arguments will be passed as a dict in the `args` variable when calling the relevant function. A macro can request arguments by defining a `get_args` function and returning a list of strings. For example if a macro defines the following `get_args` function:\n\n```python\ndef get_args():\n    return [\"foo\", \"bar\"]\n```\n\nthe proxy will prompt for values for foo and bar. If the user enters \"FOOARG\" and \"BARARG\" for the values `args` will have a value of:\n\n```python\n{\"foo\": \"FOOARG\", \"bar\": \"BARARG\"}\n```\n\nSee below for examples on how to use arguments in macros. If `get_args` is not defined, `None` will be passed in for `args`.\n\n## Active Macros\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_macro_active_in.png)\n\nActive macros are a Python script that define a `run_macro` function that takes in two arguments. A `MacroClient` (as defined in `guppyproxy/macros.py`) and a list of requests (`HTTPRequest` and `HTTPResponse` are defined in `guppyproxy/proxy.py`). The following is an example of a macro that resubmits all of the input requests but adds a new header:\n\n```python\n# addheader.py\n\ndef get_args():\n    return [\"header_key\", \"header_val\"]\n\ndef run_macro(client, args, reqs):\n    for req in reqs:\n        client.output(\"Submitting request to %s...\" % req.full_url())\n        req.headers.set(args[\"header_key\"], args[\"header_val\"])\n        client.submit(req)\n        client.output_req(req)\n```\n\nMacros such as this can be used for things such as testing auth controls or brute forcing paths/filenames.\n\n## Intercepting Macros\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_macro_int.png)\n\nIntercepting macros are used to look at/modify requests as they pass through the proxy. This is done by defining `mangle_request` and/or `mangle_response`:\n\n```\nmangle_request(client, req): Takes in a client and an HTTPRequest and returns an HTTPRequest. The returned HTTPRequest will be sent to the server instead of the original.\nmangle_response(client, req, rsp): Takes in a client, HTTPRequest, and HTTResponse and returns an HTTPResponse. The returned HTTPResponse will be sent to the browser instead of the original.\n```\n\nAs an example, the following macro will ask for a find/replace value. When run, it will set the `session` cookie in the request to `bar` before submitting it to the server and then perform the given find and replace on the body of the response.\n\n```python\n# intexample.py\n\ndef get_args():\n    return [\"find\", \"replace\"]\n\ndef mangle_request(client, args, req):\n    req.set_cookie(\"session\", \"bar\")\n    return req\n\ndef mangle_response(client, args, req, rsp):\n    rsp.body = rsp.body.replace(args['find'].encode(), args['replace'].encode())\n    return rsp\n```\n\n# Settings\n\n![screenshot](https://github.com/roglew/guppy-static/blob/master/ss_settings.png)\n\nThis tab allows you to edit your proxy settings. It lets you select a file to store your history/settings in and configure what ports the proxy listens on. It also allows you to configure an upstream proxy to use. You can add a listener by entering the interface and port into the text boxes and clicking the \"+\" button. They can be deleted by selecting them from the list and clicking the \"-\" button.\n\nYou can also specify settings for an upstream proxy by checking the \"Use Proxy\" box, filling out the appropriate info, and clicking \"confirm\".\n\n## Data Files\n\nYour entire request history and your settings can be stored in a data file on disk. This allows you to save your work for later and even send your work to someone else. You can start a new project with a new data file by clicking the \"New\" button in the settings tab. Once you do this, your settings, scope, and all the messages that pass through the proxy will be saved to the specified file. You can also load an existing project by using the \"Open\" button. Finally, you can specify a data file by typing the path into the text box and clicking \"Go!\"\n\n# Keybindings\n\nGuppy has the following keybindings:\n\n| Key | Action |\n|:--------|:------------|\n| `Ctrl+J` | Navigate to request list |\n| `Ctrl+T` | Navigate to tree view |\n| `Ctrl+R` | Navigate to repeater |\n| `Ctrl+N` | Navigate to interceptor |\n| `Ctrl+D` | Navigate to decoder |\n| `Ctrl+U` | Navigate to filter text input |\n| `Ctrl+I` | Navigate to filter dropdown input |\n| `Ctrl+P` | Navigate to filters and pop most recent filter |\n| `Ctrl+Shift+D` | Navigate to decoder and fill with clipboard |\n| `Ctrl+Shift+N` | Create new datafile |\n| `Ctrl+Shift+O` | Open existing datafile |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froglew%2Fguppy-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froglew%2Fguppy-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froglew%2Fguppy-proxy/lists"}