You want a status page built on private data. Wallet balances, account numbers, an API key that does the lookups. The page has to show the numbers. It must never show what produced them. That split has to exist on disk, not just in your head. Everything under the web root is public whether you linked it or not.
Everything under the web root is served. Not everything you linked. Everything.
So make the rule simple. The web root holds generated output and nothing else.
Put the file with the real identifiers somewhere else. Lock it down with command chmod 600 /opt/app/addresses.json
A sync script reads that file, does the lookups, and writes a second file into the web root with only the numbers in it.
The page fetches the second file.
The page has no idea an address exists anywhere.
Run it on a timer with command crontab -e and the page gets fresh numbers without ever holding a credential.
Editing a live page means leaving a backup next to it. Everybody does it.
index.html.bak. index.html.old. index.html.bak-1786763926.
Those get served.
Nothing links them, so nobody stumbles onto one, and search engines will not index them either. None of that makes them private. Anyone who guesses the name gets the entire old page, including whatever you took out of the new one.
Check yours with command curl -s -o /dev/null -w "%{http_code}\n" https://example.com/index.html.bak
A 200 means it is public right now.
See what is really in there with command ls -la /srv/yoursite/
I found six on my own dashboard. Every edit I had made to that page was sitting there, fetchable, going back to the day it was built.
Move them out. Keep them if you want them, just not there.
Do not assume the move worked. Ask for every one of them.
for p in /balances.json /onchain.json /index.html.bak; do
printf "%-24s %s\n" "$p" "$(curl -s -o /dev/null -w '%{http_code}' https://example.com$p)"
done
You want 404 on every line.
Put the retired files somewhere only root can open with command chmod 700 /opt/app/retired
Every one of these lookups goes to something you do not control. Public APIs blip. Rate limits hit. One of mine returns 403 to the default Python user agent and 200 to literally any other string.
So decide now what the page shows when a lookup fails.
Do not show zero.
A balance that reads 0 because an API timed out looks exactly like a balance that really is 0. You will believe it. It is the one wrong answer that does not look wrong.
Keep the last known number instead and mark the row.
try:
entry["amount"] = lookup(row)
entry["stale"] = False
except Exception as exc:
errors.append(f"{row['symbol']}: {exc}")
entry["amount"] = previous.get(key)
entry["stale"] = True
Then show that mark on the page, so a stale row looks stale.
One dead public API should not blank a row that has money in it.
Take a list of endpoints instead of one. Fall through it. Only fail when they all fail.
def try_all(urls, fn):
last = None
for u in urls:
try:
return fn(u)
except Exception as exc:
last = exc
raise last
Publish the error count alongside the data. Then the page can say two lookups failed, instead of quietly showing eleven rows where there should be thirteen.
Read the file you are serving. Not the script that wrote it.
Hunt for anything long and random with command grep -oE '[a-zA-Z0-9]{26,}' /srv/yoursite/data.json
Addresses, keys and tokens are long random strings. Numbers and coin names are not.
If that comes back empty, you published numbers.
Do the same to the page itself before you call it done with command curl -s https://example.com/data.json | grep -oE '[a-zA-Z0-9]{26,}'
Check the served copy, because that is the one strangers get.