Summary
Harden score-registry for production: oracle admin rotation, emergency pause, and operational runbooks. The current contract sets admin once at init with no recovery path if the oracle key is compromised.
Problem
| Risk |
Current behavior |
| Oracle key leaked |
Attacker can set arbitrary scores forever |
| Incident / bug |
No way to stop attestations without redeploying |
| Key rotation |
Must deploy new contract |
Production oracle secret lives in Vercel (ORACLE_SECRET_KEY) — rotation must be supported on-chain and off-chain.
Proposed contract functions
File: Contracts/score-registry/src/lib.rs
const PAUSED_KEY: &str = "PAUSED";
pub fn transfer_admin(env: Env, new_admin: Address) {
let admin = /* current admin */;
admin.require_auth();
env.storage().instance().set(&ADMIN_KEY, &new_admin);
}
pub fn pause(env: Env) {
admin.require_auth();
env.storage().instance().set(&PAUSED_KEY, &true);
}
pub fn unpause(env: Env) {
admin.require_auth();
env.storage().instance().set(&PAUSED_KEY, &false);
}
pub fn paused(env: Env) -> bool {
env.storage().instance().get(&PAUSED_KEY).unwrap_or(false)
}
Modify set_score / batch:
if paused(env) { panic!("attestation paused"); }
Operational runbook (add to Contracts/README.md)
Rotate oracle key
- Generate new Stellar keypair (secure enclave / password manager)
- Fund new public key with XLM for fees
- Call
transfer_admin(new_public_key) signed by old oracle
- Update Vercel
ORACLE_SECRET_KEY to new secret
- Redeploy not required if only key changes
- Verify attest with test wallet
Emergency pause
- Call
pause() via current admin
- API returns 503 with clear message (update
contracts.controller.ts)
- Investigate incident
- Call
unpause() when safe
Server changes
File: Server/src/controllers/contracts.controller.ts
When simulation/ submit fails due to pause:
{
"success": false,
"error": "On-chain attestation paused by oracle. Try again later."
}
Optional admin-only HTTP route to check pause state (read-only RPC call).
Future enhancement (separate issue)
Two-step admin transfer: propose_admin + accept_admin prevents typo bricking.
Tests
Acceptance criteria
Related
References
- OpenZeppelin pause patterns (conceptual analog)
- ZCore incident:
.env leaked on first Vercel deploy — rotate all secrets
Summary
Harden
score-registryfor production: oracle admin rotation, emergency pause, and operational runbooks. The current contract sets admin once atinitwith no recovery path if the oracle key is compromised.Problem
Production oracle secret lives in Vercel (
ORACLE_SECRET_KEY) — rotation must be supported on-chain and off-chain.Proposed contract functions
File:
Contracts/score-registry/src/lib.rsModify
set_score/ batch:Operational runbook (add to Contracts/README.md)
Rotate oracle key
transfer_admin(new_public_key)signed by old oracleORACLE_SECRET_KEYto new secretEmergency pause
pause()via current admincontracts.controller.ts)unpause()when safeServer changes
File:
Server/src/controllers/contracts.controller.tsWhen simulation/ submit fails due to pause:
{ "success": false, "error": "On-chain attestation paused by oracle. Try again later." }Optional admin-only HTTP route to check pause state (read-only RPC call).
Future enhancement (separate issue)
Two-step admin transfer:
propose_admin+accept_adminprevents typo bricking.Tests
set_scorefails when pausedtransfer_adminchanges auth requirementpaused()public read accurateAcceptance criteria
Contracts/README.mdRelated
References
.envleaked on first Vercel deploy — rotate all secrets