VaultWatch is a hackathon project. We apply security fixes to the latest
main branch only.
| Version | Supported |
|---|---|
| 4.x | ✅ (current) |
| < 4.0 | ❌ |
If you discover a security vulnerability in VaultWatch:
- DO NOT open a public GitHub issue.
- Email the maintainer at
dev@vaultwatch.iowith:- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
- You will receive an acknowledgment within 48 hours.
- A fix will be developed and disclosed responsibly.
- NEVER commit signing keys (
.pem), recovery codes, or mnemonics to git. - The
.gitignoreexcludessecret_key.pem,.env, and similar patterns. - If you accidentally commit a key, treat it as COMPROMISED and rotate immediately. GitHub's secret scanning will also flag known patterns.
- Use a dedicated testnet key for contract deployments. Do not reuse mainnet keys.
- The deploy script (
scripts/deploy_contracts_live.py) reads the key path fromCASPER_KEY_PATHenv var only — it never accepts inline keys. - After deployment, the key file should be stored offline or in a secret manager (1Password, AWS Secrets Manager, etc.), not in the repo.
- API keys are read from environment variables (
GROQ_API_KEY,CSPR_CLOUD_API_KEY, etc.). - The
.env.examplefile documents required variables without containing real values. - In CI, secrets are injected via GitHub Actions secrets — never echoed to logs.
The CSPR.cloud REST API (https://api.testnet.cspr.cloud) requires a
Bearer access token. Previously this key was hardcoded in
dashboard/src/liveApi.js (browser-exposed — anyone with devtools open
could lift it) and in several Python scripts. The leaked key has been
revoked and rotated.
Mitigations now in place:
-
The key lives only in the
CSPR_CLOUD_API_KEYenvironment variable. It is never committed to source, never logged, and never echoed in API responses. The.env.exampledocuments the variable with a placeholder. -
The dashboard never reads the key directly. All CSPR.cloud REST calls go through the VaultWatch FastAPI reverse proxy:
- Browser calls
GET /api/cspr_cloud/blocks?page_size=1(in dev, the Vite dev server proxies/api/*and/cspr_cloud/*tohttp://localhost:8000; in production, setVITE_API_URLto the deployed API origin). - The FastAPI app (
api/main.py→cspr_cloud_proxy_get) readsCSPR_CLOUD_API_KEYfrom env, injectsAuthorization: Bearer $CSPR_CLOUD_API_KEY, and forwards the request tohttps://api.testnet.cspr.cloud/<path>?<query>. The upstream response body, content-type, and status code are returned verbatim. - The key never appears in the browser bundle, the network tab, or client-side JS.
- Browser calls
-
Server-side scripts read the key from env directly.
scripts/broadcast_interactions.py,scripts/broadcast_transfers.py,scripts/deploy_live.py,scripts/deploy_new_account.py,scripts/broadcast_deploys.py, andscripts/verify_contract_entrypoints.pyall useos.getenv("CSPR_CLOUD_API_KEY", ""). They run in a trusted environment (deployer machine, CI), so they do not need the proxy. Empty string is safe — the public Casper testnet node (node.testnet.casper.network/rpc) does not require the header. -
The proxy exposes a
/cspr_cloud/statushealth endpoint that reports whetherCSPR_CLOUD_API_KEYis set and what the upstream URL is — without ever echoing the key itself. Useful for debugging 401s and for the dashboard health-check. -
A test (
tests/integration/test_cspr_cloud_proxy.py) verifies:- The leaked key prefix does not appear in any tracked source file.
- The proxy injects the Bearer header from env when forwarding.
- The
/cspr_cloud/statusendpoint does not leak the key. dashboard/src/liveApi.jsdoes not contain the key and does not callapi.testnet.cspr.clouddirectly.
If the CSPR.cloud key is ever compromised again:
- Revoke it immediately at https://cspr.cloud/account/settings/tokens.
- Generate a new token with the same scope.
- Update
CSPR_CLOUD_API_KEYin the server environment (.envlocally, Vercel/Render env vars in production, GitHub Actions secret in CI). - Restart the FastAPI app (uvicorn picks up the new env on reload — no code change or redeploy needed).
- Run
pytest tests/integration/test_cspr_cloud_proxy.pyto verify.
The 8 VaultWatch contracts use Odra 2.8.0 and follow these patterns:
- Owner-only mutations: every state-changing entry point calls
assert_owner()to verify the caller. - No arbitrary calls: contracts do not have
delegate_callor generic call facilities. - Checked arithmetic: Odra's
U512type panics on overflow. - No reentrancy: contracts do not call back into untrusted code.
Known limitations (documented in docs/RED_TEAM_CHECKLIST.md):
- Owner key is a single signer (multisig is a future upgrade).
- Reputation formula has 4 partial-resistance attack vectors (Checks 2, 5, 10, 11) — all documented with mitigations.
- CodeQL runs on every push and PR (
.github/workflows/codeql.yml). - Dependabot opens PRs for outdated dependencies weekly
(
.github/dependabot.yml). - All CodeQL alerts with High or Critical severity MUST be resolved
before merging to
main.
- Python packages: published to PyPI as
casper-sentinel(built fromsdk/). - npm packages: published as
casper-sentinel-mcp(built fromvaultwatch_mcp/). - npm provenance is enabled for the MCP package.
- Contract WASM is built reproducibly via CI
(
.github/workflows/build-contracts.yml) with pinned Rust nightly and verified zero bulk-memory opcodes.