Every finance app gets asked the same question eventually: where does my data go? The two answers people are used to are a privacy policy nobody reads and "trust us." Neither is good enough for software that holds your brokerage history.
Below is every internet address the Pyx desktop app can reach, nine of them as of version 0.2.12, with what each request carries, what it never carries, and a check you can run on your own Mac in about ten minutes without trusting a word of this.
Nothing here needs a technical background. I have tried to keep it as simple as the subject allows: every term is explained the first time it appears, and the one section written for engineers is marked so you can skip it.
A host is an address, and nine is the whole story#
A host is an address on the internet, like query1.finance.yahoo.com. A program can only send information to addresses it names. So if you know every address a program can name, you know everywhere its data can go.
Most software handles this by letting everyone through and stopping the ones that look wrong. Pyx works from a guest list. The app carries a short, fixed list of addresses, and a request to any address that is not on the list is refused before it is sent. Not caught afterward, not logged and allowed. Refused.
For the engineer: the list is a hash set in one file, allowlist.rs. Every HTTP client the app creates is checked against it, and the release build has no code path that can add to it. The mechanism gets its own section further down.
The rule matters more than the number#
As of this writing there are nine hosts on the allowlist. That will change as Pyx adds data sources, so the number is worth less than the rule behind it. The rule has three parts.
Every address Pyx can reach is written into the app and published, here and in the privacy policy, with what it is for. Nobody can type a new address into this version of the app, including me. And the only addresses that are on by default are the ones a feature you chose needs: prices need Yahoo, linking a brokerage needs SnapTrade, subscribing needs the license service, opening a company's research page needs the SEC.
As new sources are added, each one will be a fixed address published in the same table, it will start off, and it will have its own switch, with a sentence saying what that source can see. A per-ticker data provider, for example, sees the symbols you look up. The base set stays small.
Over time I want Pyx to reach more data sources, more of the useful public APIs, and eventually sources you point it at yourself. Those will always be switchable, and the rule holds in both directions: nothing Pyx ships turns on an address you did not choose, and an address you add yourself is one you typed, named on screen, and can turn off in the same place. What will not happen is the app acquiring an address on its own.
A list of nine you cannot change is a fact about this version. A rule that every future address is published, off by default, and yours to turn on is a fact about every version after it. Hold Pyx to the rule.
The nine hosts, as of 0.2.12#
| Host | Run by | When Pyx contacts it | What the request carries | What it never carries |
|---|---|---|---|---|
query1.finance.yahoo.com | Yahoo | Prices, price history, exchange rates | The ticker symbols and currency pairs on screen | Quantities, values, your name |
query2.finance.yahoo.com | Yahoo | Quotes, company fundamentals | Ticker symbols | Same |
fc.yahoo.com | Yahoo | Once, to collect a consent cookie Yahoo requires before it answers profile questions | Nothing beyond the request itself | Anything about you |
api.snaptrade.com | SnapTrade | Only if you link a brokerage | A signed request with a random per-install id | Your brokerage password, ever |
license.pyx.finance | Pyx (Cure AI) | Only if you subscribe | First activation: the license key, the app version, the operating system name. Every later check: the key alone | Your portfolio, your email |
pyx-license.fly.dev | Pyx (Cure AI) | Fallback address for the same service | Same as above | Same |
www.pyx.finance | Pyx (Cure AI) | About once a day, to ask for the newest version number. And once, when you click Update | Daily: nothing. Update click: the license key | Anything else |
release-assets.githubusercontent.com | GitHub | Only after you click Update, to download the package | A link that expires | Anything beyond the link |
data.sec.gov | U.S. SEC | When you open a company's research page | The company's public id number, plus the app name, version, and support email in the request header | Anything about your holdings |
Every request also carries what any internet request carries: your IP address. There is no way to send a request without one, and the policy says so plainly.
Here is each service in the order a new user meets them.
Yahoo Finance, three addresses. Every price, chart, and exchange rate in the app comes from Yahoo. A request carries the ticker symbols you are looking at and, for multi-currency portfolios, the currency pairs. It does not carry how many shares you own, what they are worth, or who you are. The third address, fc.yahoo.com, exists because Yahoo hands out a small token before it will answer questions about a company's sector or an ETF's holdings; Pyx collects it once and reuses it. One more detail, because anyone watching the traffic will see it: Yahoo answers only requests that look like they came from a web browser, so Pyx identifies itself to Yahoo with a generic browser signature. It says nothing about you or your machine.
SnapTrade, one address. Pyx never contacts SnapTrade unless you link a brokerage. When you do, you log in on SnapTrade's own page in your web browser, and Pyx never sees the password. What the app keeps is a random id for your install and a secret, sealed in a file only your Mac's keychain can open, and every request is signed with that secret so SnapTrade knows it came from your copy. The part worth reading twice: the SnapTrade code inside Pyx holds a fixed table of the five kinds of question it can ask (accounts, positions, options, balances, activity), and it refuses to form any other request before touching the network. There is no "place an order" request in the app to be triggered. Read-only is not a promise, it is the shape of the code. What SnapTrade itself sees is covered by SnapTrade's privacy policy.
The license service, two addresses, one service. Pyx is free to use with imported or hand-entered data. If you subscribe for brokerage sync, in-app updates, and agent access, the app talks to the license service. The first time, it sends three things: the key, the app version, and the operating system name. Every later check sends the key alone. The service keeps no database and does not write the key to its logs; it asks Polar, the payment provider, whether the subscription is active and hands back a signed note that the app can check on its own, without asking again, for 35 days. The second address is a fallback for the same service in case the first one's domain has a bad day. Nothing about your portfolio ever goes to either.
The Pyx website, one address. About once a day the app asks the website one question: what is the newest version? The request carries nothing about you. There is no setting to turn that check off, and I would rather say so here than have you find it. When you click Update, a single request carries your license key so the site can hand back a download link.
GitHub, one address. The update package itself downloads from GitHub's release servers, only after you click Update, from a link that expires. Before anything installs, the file is checked against a published fingerprint and against Apple's code signature, so a tampered package fails and a real one passes.
The SEC, one address. When you open a company's research page, Pyx fetches its filings index and ten years of financial statements from EDGAR, the SEC's public database. The request carries the company's public id number (Apple's is 320193) and, because the SEC requires every program to introduce itself, a header naming the app, its version, and a support email. Never anything about your holdings. The filing documents themselves open in your browser, not inside the app.
What is not on the list#
The things people assume a finance app does, and Pyx does not:
- No analytics, no crash reporting, no telemetry. There is no such library in the app. Not disabled, not opted out: absent from the code, so even a bug could not phone home to one.
- No payment processor. Subscribing happens in your browser at Polar. The app never contacts Polar and never sees a card.
- No AI service. Turning on agent access opens a socket on your own Mac for tools like Claude Code and adds no addresses to the list.
- Not even the obvious neighbors.
pyx.financewithout thewwwis excluded, because it redirects and redirects are refused.www.sec.govis excluded, because filing documents open in your browser.
How the list is enforced#
Skip this section if you only want to run the check. It is here for the reader who wants the mechanism.
Nine of the ten outbound clients in the app are bound to one fixed base address, and that address is checked against the list when the client is created. A client bound to https://data.sec.gov can only ever append a path to that base; it cannot be pointed elsewhere later. Ten clients exist: Yahoo four ways, SnapTrade, the license service, the release check, the updater twice, and EDGAR. Seven are wrapped by the allowlist itself, and two check their addresses against it before they are built.
The tenth is the interesting one, and writing this post is what found it. The client that fetches quotes and price history comes from a third-party Rust library that owns its own addresses, so there was nothing for the allowlist to be handed, and in 0.2.12 it checked nothing. The addresses themselves were never in doubt: they are fixed constants in the library's source, and they are the three Yahoo addresses already on the list. But "on the list because the dependency happens to agree" is weaker than "checked", so I fixed it rather than publish a footnote. From the next release, that client resolves every hostname through the allowlist itself: a name that is not on the list fails to resolve, so no socket is opened and nothing leaves the machine. The library's version is pinned exactly, its addresses are checked against the list when the client is built, and a test fails the build if the pin moves without someone re-reading those addresses. If you are reading this on 0.2.12, that one client is the place where the list described the traffic instead of gating it.
Two smaller rules close the usual gaps. Redirects are refused, so a signed request can never be bounced from an allowed address to a different one. The minimum TLS version is 1.2. There is a debug-only function that can add an address for local testing; it is compiled out of release builds, and the tests assert that a URL cannot smuggle an allowed name into the user-info slot (https://www.pyx.finance@evil.example.com/ is refused).
Now the honest caveat. This is a code convention backed by tests, not an operating-system sandbox. A future mistake could create a client that skips the check, which is exactly the mistake the section above describes finding, and no amount of unit testing proves a negative about code that has not been written yet. That is exactly why the list is published and why the next section exists: the promise is falsifiable from outside the app, on your machine, without my help.
Check it yourself in ten minutes#
Three ways, cheapest first. Any one of them is enough.
1. Nothing to install: Terminal. Open Terminal while Pyx is running and paste:
lsof -i -P | grep -i pyx
Here is the real output, captured on the shipping 0.2.12 build a few seconds after opening Apple's research page:
pyx 54774 jaredwatson 23u IPv4 0x4f29334a77c81e98 0t0 TCP 10.0.0.214:56511->e2-bmr.ycpi.vip.ata.yahoo.com:443 (ESTABLISHED)
pyx 54774 jaredwatson 28u IPv4 0x26c074bb87b797b1 0t0 TCP 10.0.0.214:56263->a173-223-63-168.deploy.static.akamaitechnologies.com:443 (ESTABLISHED)
pyx 54774 jaredwatson 31u IPv4 0xa141f06a20523780 0t0 TCP 10.0.0.214:56114->e1-bmr.ycpi.vip.ata.yahoo.com:443 (ESTABLISHED)
Each line is one open connection. The left address is your Mac, the right one is where the connection goes, and the process name on the left is pyx, lowercase, which is the program inside Pyx.app.
Now the part a lot of "check your own traffic" guides skip. The name on the right is not the address the app asked for. It is whatever name that IP address answers to, and big services answer from a content network. The two Yahoo rows say ycpi.vip.ata.yahoo.com, which is Yahoo's edge; all three Yahoo hosts in the table resolve to the same two edge addresses, so this view cannot even tell them apart. The Akamai row is the SEC: data.sec.gov is served through Akamai, so its address reverse-resolves to akamaitechnologies.com. Some rows have no name at all. The Pyx website sits behind an address with no reverse record, so the daily version check shows up bare:
pyx 54774 jaredwatson 23u IPv4 0x2071c303c50254dd 0t0 TCP 10.0.0.214:57219->216.198.79.65:443 (ESTABLISHED)
So read it the other way around. Take a host from the table and ask where it lives:
dig +short data.sec.gov
www.sec.gov.edgekey.net.
e9076.dscb.akamaiedge.net.
173.223.63.168
That is the same address as the Akamai row above, which is how you attribute it. Run dig +short on all nine and you have the map. On the day I wrote this, on my network, the nine resolved to Yahoo's edge (three hosts, two shared addresses), Akamai for the SEC, 216.198.79.65 and 64.29.17.65 for the website, Fly.io for both license addresses, AWS in Canada for SnapTrade, and GitHub's four 185.199.x asset addresses. Those change over time and by location; the command does not.
For a live view that updates as you click around the app:
nettop -p pyx -J bytes_in,bytes_out
One row per connection, redrawn every second with the byte counts moving. This is a real refresh, triggered with Sync Now:
time bytes_in bytes_out
00:02:01.309612 pyx.54774 12 KiB 2672 B
00:02:01.304575 tcp4 10.0.0.214:57580<->e2-bmr.ycpi.vip.ata.yahoo.com:443 2923 B 911 B
00:02:01.304529 tcp4 10.0.0.214:57581<->e2-bmr.ycpi.vip.ata.yahoo.com:443 10143 B 1761 B
Note the process name again: nettop -p pyx works, nettop -p Pyx silently matches nothing.
The byte counts are worth watching, because they say how much is moving as well as where. A price refresh for a whole portfolio is the twelve kilobytes above. The daily version check was under a kilobyte each way. The largest thing Pyx downloads in normal use is a single SEC file, about seven megabytes for Apple: the complete company-facts document, every number the company has reported in its filings, which the research page reads once and stores locally.
One limitation, because it is the reason to keep reading. Both commands show only the connections open at the instant you look. A request that takes 200 milliseconds, such as the daily version check, is usually over before you can type. To catch those you want something that records instead of samples.
2. A free app. Little Snitch Mini shows a live list of every connection each app makes, and its monitoring, connection list, and traffic history are free; blocking is the paid part, and it needs macOS 12 or later. LuLu from Objective-See is free, open source, and also blocks. Either one answers the question, and both have an advantage over the Terminal: they record what the app asked for, so the rows read query1.finance.yahoo.com and data.sec.gov rather than the content network that answered.
3. Little Snitch. The full product is paid. Its demo runs for three hours at a time and can be restarted as often as you like, which is more than this audit needs.
Whichever you pick, run the app through a session that touches every service:
- Install Pyx from the public download and go through onboarding.
- Import a CSV or OFX statement. This touches nothing on the internet except Yahoo, to name the securities in the file.
- Open Holdings, then open one company's research page. That is Yahoo and the SEC.
- Open a chart.
- Choose Check for Updates from the Pyx menu. That is the website.
- If you subscribe, link a brokerage. That is SnapTrade and the license service.
Every row attributed to Pyx should be one of the nine, or the content network that serves one of the nine.
You will also see rows that are not Pyx. When any signed app launches, macOS itself checks Apple's servers, usually ocsp.apple.com and, on first launch, api.apple-cloudkit.com, to confirm the developer's certificate and the notarization ticket. Those rows belong to system processes such as trustd and syspolicyd, not to the Pyx process, and they happen for every app on your Mac. A first-time Little Snitch user can easily mistake them for the app. They are Apple's traffic, not mine.
If you ever see anything else#
The list changes only with a release, a line in the changelog, an update to the table in the privacy policy, and an updated date on this post. Any address added for a new data source starts off and has its own switch. If you see an address that is not on the list and not one you turned on, email support@pyx.finance, and the finding goes in the changelog with your name if you want it there. No bounty program, no form. Just the commitment, in writing, on a page you can hold me to.
Pyx is a local-first desktop portfolio app that keeps your data in files you own. The privacy and security guide covers what the rest of the app does with your data once it is on disk.