Proxy Captain ← Back

Privacy Policy — Proxy Captain

Last updated: 14 August 2026

Proxy Captain is a browser extension that decides which proxy your browser uses for which site. This document says exactly what it stores, where it stores it, and what leaves your machine.

It is written against the code, not against an intention. Every claim below names the file that implements it, so you can check it yourself in the packaged extension or in the source.


The short version


What is stored, and where

Everything Proxy Captain remembers lives in chrome.storage.local — the browser's own per-extension storage on your disk. It is one JSON document under one key (proxycaptain_state_v1) plus a backup copy (proxycaptain_state_v1.backup), written by core/storage.js through engine/adapter.js.

StoredContainsWhere
ProxiesLabel, scheme (http / https / socks4 / socks5), host, port, user name, password, remote-DNS flagchrome.storage.local
RulesType, pattern, target proxy id, enabled flagchrome.storage.local
ProfilesLabel, rule ids, default targetchrome.storage.local
SettingsYour chosen connection-check address, auth behaviour, UI preferenceschrome.storage.local
Licence cachePlan, feature list, expiry, a device tokenchrome.storage.local (proxycaptain_licence_v1)

chrome.storage.local is not chrome.storage.sync. Nothing here is copied into your browser profile sync, so your proxy passwords do not travel to Google's or Mozilla's servers with your bookmarks.

Browsing history is not stored. Proxy Captain never writes down which sites you visited. Routing decisions are made request-by-request, in memory, and are not logged anywhere that survives them. There is no history view in this extension because there is no history.


Credentials are stored in plain text

Proxy user names and passwords are stored unencrypted in chrome.storage.local.

There is no encryption step anywhere in this codebase — you can confirm that by grepping the package for crypto.subtle, which appears nowhere. The stored document is a JSON string containing your password as you typed it.

This is the same thing SwitchyOmega does, and the same thing every browser proxy switcher we are aware of does, for a reason worth stating plainly: there is nowhere better to put it. A proxy password has to be readable by the extension at the moment a request needs authenticating, with no human present to unlock anything. Encrypting it with a key that also lives in chrome.storage.local moves the plaintext one indirection to the left and protects nobody. A real answer needs an OS keychain, and extensions have no access to one.

What that means for you, concretely:

If that is not acceptable for a particular proxy, use a proxy that authenticates by IP allow-list instead of by password, or run a local relay that holds the credential outside the browser.

Exports redact passwords by default. exportJson and exportCsv (core/transfer.js) strip passwords unless you explicitly ask for them to be included. A redacted password is omitted rather than replaced with a placeholder — a placeholder that round-tripped through an import would become a real, wrong password — and the record is marked passwordRedacted: true so the import can tell you which proxies need a password re-entered. Export also redacts any setting whose name looks credential-shaped — the exact test is /(pass|secret|token|licen[cs]e|credential|cookie|auth|session|apikey|api_key|\bkey\b)/i in core/transfer.js, deliberately over-broad: redacting a preference that was not a secret costs you one re-entry, and missing one costs you the secret.

Credentials are never written to a log or a diagnostic panel. engine/auth.js records host, port and proxy id only. A password in a diagnostics screenshot is a password in a support ticket.


What leaves your machine

There are exactly three outbound paths. Every fetch( in the shipped code belongs to one of them; there is no other network call, no XMLHttpRequest, no sendBeacon and no WebSocket anywhere in the package.

1. Your own browsing, through the proxy you chose

This is the product. When a rule sends example.com through your SOCKS5 proxy, your request to example.com goes to that proxy, which is a machine you chose and we have nothing to do with. Proxy Captain does not operate a proxy, does not resell one, and has no relationship with whoever runs yours. We never see this traffic.

Two details you should know:

2. The connection check — only when you press it

The check that tells you whether your browser is actually proxied works by reading your egress IP address twice: once with the proxy in the path, once with it deliberately out of the path. If the two readings are the same, the proxy is not carrying your traffic, whatever the settings say.

Reading an egress IP means asking somebody outside your machine what address your request arrived from. That is an outbound request to a third party, so:

An egress-IP check reveals to that endpoint that this browser is running a proxy tool. Doing it silently would be a telemetry channel nobody asked for. That is why it is a button and not a background job.

3. The licence check

Proxy Captain asks https://api.montereystudio.com whether this browser holds a paid licence (shared/licence.js). This happens when you pair a browser to a licence, on a periodic refresh (hourly alarm, cached for six hours), and when you open the upgrade screen.

Payment, if you ever buy anything, is handled by Stripe. We never see your card number.


Permissions, and why each one is needed

PermissionWhy
proxyThe whole product. Applies the generated PAC through chrome.proxy (Chromium) or answers proxy.onRequest (Firefox).
storageSaves your proxies, rules and profiles locally. Nothing else.
webRequestCarries one listener and only one: onAuthRequired. There is no onBeforeRequest handler, no onBeforeSendHeaders handler, and nothing that reads a request or response body.
webRequestAuthProvider (Chromium) / webRequestBlocking (Firefox)Answers the HTTP 407 Proxy Authentication Required challenge with the credentials you stored, for your own proxy only — the reply goes out only when details.isProxy is true and the challenging host and port match a proxy you saved, so a website or a captive portal can never be handed them. The two stores' builds differ here because the platforms do: Chromium MV3 exposes proxy auth through webRequestAuthProvider, Firefox through a blocking listener.
<all_urls>A proxy rule can be written for any host, so the routing decision has to be available for any host. <all_urls> is also what makes onAuthRequired reach us for the proxy challenge. It is not used to inject scripts or read pages — there is no content script in this extension at all.
tabsExactly one thing needs this permission: popup/popup.js reads the host name of the active tab so the popup can offer "add a rule for this site". Non-web tabs (chrome://, about:, file://) are dropped before anything is read. Tab history and tab content are never accessed. (shared/licence.js also calls chrome.tabs.create twice, to open a purchase or pairing page in a new tab. That call needs no permission at all and cannot read anything — it is listed here only so that grepping for tabs. and finding three hits does not look like an omission.)
alarmsUsed only by the licence client: an hourly alarm that re-checks the cached licence answer only once it is more than six hours old, and a short-lived poll while you are pairing a purchase. Nothing about routing uses a timer, and the connection check cannot be scheduled at all.

Requesting <all_urls> is close to the heaviest permission a browser extension can ask for, and we would rather explain it than hope you did not notice.


Data we do not have


Deleting everything

Uninstalling Proxy Captain removes chrome.storage.local for the extension, which is every proxy, rule, profile and setting it ever held. There is no server-side copy to delete because there was never a server-side copy.

If you paired a paid licence, that pairing exists on the licence service. Write to the address below to have the licence record removed.


Children

Proxy Captain is a developer and network tool. It is not directed at children and does not knowingly collect anything from anybody, of any age.

Changes

Material changes to this document will be published in the extension's release notes with the version they take effect in. The "last updated" date above is the authority.

Contact

Monterey Studio — privacy@montereystudio.com


Verify this yourself

Every claim above is checkable in the packaged extension:

grep -ran --exclude=PRIVACY.md \
  "fetch(\|globalThis.fetch\|XMLHttpRequest\|sendBeacon\|WebSocket" .
grep -ran --exclude=PRIVACY.md "crypto.subtle"   # no hits — nothing is encrypted
grep -ran --exclude=PRIVACY.md "storage.sync"    # one hit: a comment in engine/adapter.js
                                  # explaining why sync is NOT used. Nothing syncs.
grep -n "content_scripts" manifest.json    # no hits — there is no content script

Neither flag is decoration.

-a, as insurance. No file in this package needs it today — on the current build grep -a and plain grep return identical results, and you should check that rather than take our word for it. It stays in the recipe because the failure it guards against is silent: a binary byte anywhere in a file causes grep to report nothing for that file instead of reporting a problem, and a verification recipe that can go quietly blind is worse than no recipe at all.

--exclude=PRIVACY.md, because this file ships inside the package you are grepping — it is not in the packaging exclusion list, so the zip you install carries it — and the prose you are reading quotes every term the recipe searches for. Without the exclusion the first command returns three groups rather than two, crypto.subtle returns two hits rather than none, and storage.sync returns three rather than one: all of the extra hits this document talking about itself, none of them code. Excluding it is the difference between a command whose output matches the sentence under it and one that does not.

The first grep returns two groups and no third:

core/, popup/, options/ and sw.js contain no network call of any kind.