Candidate: Githaiga22
Version: 1.0
This document identifies attack surfaces in the FiatRails system and documents mitigations.
Risk Rating:
- Critical: Direct loss of funds or total system compromise
- High: Significant degradation or partial fund loss
- Medium: Service disruption or data corruption
- Low: Minor inconvenience or information disclosure
Attack Vector: Malicious contract calls back into MintEscrow during token transfer, potentially double-minting.
// Vulnerable pattern
escrow.executeMint(intentId);
→ transfer tokens to user
→ user's receive() calls executeMint againLikelihood: Medium (if external calls before state updates)
Impact: Critical (double-spend)
Mitigation:
- Checks-Effects-Interactions pattern
- ReentrancyGuard on public functions
- State updated before external calls
// Example mitigation
function executeMint(bytes32 intentId) external nonReentrant {
MintIntent storage intent = intents[intentId];
require(intent.status == MintStatus.Pending, "Already executed");
intent.status = MintStatus.Executed; // State change first
_mintCountryToken(intent.user, intent.amount); // Then external call
}Testing:
- Unit test with malicious contract that reenters
- Invariant test: "total mints ≤ total deposits"
Attack Vector: Deploying a broken implementation contract that can't be upgraded further, permanently locking funds.
Likelihood: Low (if proper testing)
Impact: Critical (funds locked forever)
Mitigation:
_authorizeUpgrade()requires UPGRADER role- Storage layout preserved (use storage gap)
_disableInitializers()in implementation constructor- Test upgrade path on testnet before production
function _authorizeUpgrade(address newImplementation)
internal
override
onlyRole(UPGRADER_ROLE)
{
// Additional checks on newImplementation
require(newImplementation != address(0), "Invalid implementation");
}Testing:
- Test upgrade to dummy V2 contract
- Verify storage slots unchanged
- Test that implementation can't be initialized
Attack Vector: Unauthorized user gains ADMIN or UPGRADER role, allowing them to pause system or steal funds via malicious upgrade.
Likelihood: Low (if access control correct)
Impact: Critical (full system compromise)
Mitigation:
- AccessControl from OpenZeppelin
- Multi-sig for admin operations (in production)
- Time-delay for sensitive operations (optional)
- Events for all role grants
function grantRole(bytes32 role, address account)
public
override
onlyRole(getRoleAdmin(role))
{
super.grantRole(role, account);
emit RoleGranted(role, account, msg.sender); // Audit trail
}Testing:
- Unauthorized users can't grant roles
- Only DEFAULT_ADMIN_ROLE can grant ADMIN
- Events emitted for auditing
Attack Vector: Searchers see compliance approval transaction and front-run with mint, or sandwich compliance updates.
Likelihood: High (public mempool)
Impact: Medium (user experience degradation, not fund loss)
Mitigation:
- Compliance check happens atomically with mint
- Flashbots/private RPC for sensitive transactions (production)
- Alternative: Commit-reveal for compliance attestations
Testing:
- Simulate front-running in test (fork mode)
Attack Vector: If permits or signed messages used, replay on different chains or after nonce increment.
Likelihood: Medium (if using EIP-712 signatures)
Impact: Medium (duplicate operations)
Mitigation:
- Include chainId in signature domain
- Include nonce per user
- Mark signature as used after consumption
Testing:
- Test signature can't be replayed
- Test signature invalid on different chainId
Attack Vector: Arithmetic operations overflow, causing incorrect balances.
Likelihood: Very Low (Solidity 0.8+ has built-in checks)
Impact: Critical (if it happened)
Mitigation:
- Solidity ^0.8.0 (checked arithmetic by default)
- SafeMath not needed for 0.8+
Testing:
- Fuzz tests with extreme values
Attack Vector: Attacker guesses or brute-forces HMAC secret, forges valid requests to drain funds.
Likelihood: Low (if secret strong)
Impact: Critical (unauthorized mints)
Mitigation:
- HMAC secret ≥ 256 bits entropy (from seed.json)
- Verify HMAC on all authenticated endpoints
- Constant-time comparison to prevent timing attacks
- Secret never logged or exposed in errors
const crypto = require('crypto');
function verifyHMAC(request, secret) {
const payload = request.timestamp + request.body;
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(request.signature, 'hex'),
Buffer.from(expected, 'hex')
);
}Testing:
- Valid HMAC accepted
- Invalid HMAC rejected (401)
- Timing attack resistance (all failures take same time)
Attack Vector: Attacker captures M-PESA webhook payload and replays it multiple times to mint repeatedly.
Likelihood: Medium (webhook URLs often leak)
Impact: High (double-mints)
Mitigation:
- Check timestamp freshness (reject if > 5min old)
- Idempotency via
txRef(store in DB) - Verify HMAC includes timestamp
function validateTimestamp(timestamp) {
const now = Date.now() / 1000;
const age = now - timestamp;
if (age > 300) { // 5 minutes
throw new Error('Replay: timestamp too old');
}
if (age < -60) { // 1 minute in future (clock skew)
throw new Error('Timestamp in future');
}
}Testing:
- Old timestamp rejected
- Duplicate txRef returns 200 but doesn't mint again
Attack Vector: Two different operations hash to same idempotency key, causing wrong response returned.
Likelihood: Very Low (if crypto hash used)
Impact: Medium (user confusion, not fund loss)
Mitigation:
- Use SHA-256 (collision-resistant)
- Include all distinguishing fields in hash
- UUIDs as idempotency keys (client-generated)
Testing:
- Different payloads → different keys
- Birthday attack resistance (hash space analysis)
Attack Vector:
Attacker front-runs your transaction with higher gas, incrementing nonce and causing nonce too low errors in your service.
Likelihood: Low (requires attacker knowing your address)
Impact: Medium (service degradation, not fund loss)
Mitigation:
- Retry with fresh nonce on
nonce too low - Nonce management library (e.g., ethers.js manages this)
- Monitor for anomalous nonce gaps
async function sendTransaction(tx) {
const nonce = await provider.getTransactionCount(signer.address, 'pending');
tx.nonce = nonce;
try {
return await signer.sendTransaction(tx);
} catch (err) {
if (err.code === 'NONCE_EXPIRED') {
// Retry with fresh nonce
return await sendTransaction(tx);
}
throw err;
}
}Testing:
- Simulate
nonce too lowerror - Verify retry logic
Attack Vector: Malicious RPC provider censors transactions or returns false data.
Likelihood: Medium (centralized RPC risk)
Impact: High (service unavailable)
Mitigation:
- Fallback RPC endpoints
- Health checks on RPC (measure latency, block lag)
- Circuit breaker pattern
- Future: Run own node
Testing:
- Simulate RPC timeout
- Verify fallback to secondary RPC
Attack Vector: User-controlled input in SQL query allows data exfiltration or corruption.
Likelihood: Low (if parameterized queries used)
Impact: High (database compromise)
Mitigation:
- Parameterized queries / ORM
- Input validation (whitelists, not blacklists)
- Least privilege DB user
// Safe
const result = await db.query(
'SELECT * FROM intents WHERE intentId = $1',
[intentId]
);
// Vulnerable
const result = await db.query(
`SELECT * FROM intents WHERE intentId = '${intentId}'`
);Testing:
- Injection payloads (
' OR '1'='1) rejected
Attack Vector: Private key for transaction signing committed to Git, leaked in logs, or stolen from server.
Likelihood: Medium (common mistake)
Impact: Critical (attacker drains funds)
Mitigation:
- Private keys in environment variables only
.gitignorefor.envfiles- Secrets manager (AWS Secrets, Vault) in production
- Audit logs for key access
Testing:
git log | grep -i "private"→ no results- Secrets not in Docker image layers
Attack Vector: Malicious base image or dependency contains backdoor that exfiltrates keys.
Likelihood: Low (but high impact)
Impact: Critical (keys stolen)
Mitigation:
- Pin base image SHA (not
latest) - Scan images with Trivy/Snyk
- Minimal base image (Alpine, Distroless)
- Multi-stage builds (no build tools in final image)
# Pinned
FROM node:18-alpine@sha256:abc123...
# Vulnerable
FROM node:latestTesting:
- Image scan in CI
- No high/critical CVEs
Attack Vector: Chain reorgs after you've marked a mint as executed, leading to double-mint.
Likelihood: Low (on mainnet with many confirmations)
Impact: High (duplicate mints)
Mitigation:
- Wait for N confirmations before finality (N=12 for Ethereum)
- Monitor for reorgs (compare blockhashes)
- Mark intents as
pending_confirmationuntil final
Testing:
- Simulate reorg in Anvil (fork + rewind)
- Verify no double-execution
Attack Vector: Attacker floods API with requests, exhausting resources.
Likelihood: High (public endpoints)
Impact: Medium (service unavailable for legit users)
Mitigation:
- Rate limiting by IP (e.g., 100 req/min)
- Authentication required for sensitive endpoints
- CAPTCHA for public endpoints (if needed)
- Auto-scaling/load balancer
Testing:
- Exceed rate limit → 429 response
- Legit traffic unaffected
Attack Vector: User with high risk score bypasses check and mints tokens.
Likelihood: Medium (if logic flawed)
Impact: Critical (regulatory violation)
Mitigation:
- Compliance check in
executeMint(), not justsubmitIntent() - Atomic check-and-mint (no TOCTOU)
- Events log all compliance decisions
function executeMint(bytes32 intentId) external {
MintIntent storage intent = intents[intentId];
// Check compliance at execution time
require(userRegistry.isCompliant(intent.user), "User not compliant");
_mint(intent.user, intent.amount);
}Testing:
- Non-compliant user can't mint
- Compliance revoked mid-flight → mint fails
Attack Vector: User provides fake attestation hash (e.g., hash of random data).
Likelihood: Medium (if off-chain verification weak)
Impact: High (non-KYC'd users get access)
Mitigation:
- Attestation hash is hash of signed ZK proof or doc
- On-chain: only verify hash existence (not content)
- Off-chain: compliance officer verifies original doc
- Future: ZK proofs for privacy-preserving KYC
Testing:
- Bogus hash doesn't grant compliance (must be set by admin)
Attack Vector: NPM/cargo package with known vulnerability (e.g., prototype pollution).
Likelihood: Medium (new CVEs daily)
Impact: Varies (RCE in worst case)
Mitigation:
npm audit/cargo auditin CI- Dependabot for auto-updates
- Pin exact versions in
package-lock.json
Testing:
- CI fails on high/critical vulnerabilities
[Steal Funds]
|
+----------------+----------------+
| |
[On-Chain] [Off-Chain]
| |
+---+---+ +---+---+
| | | |
Reentrancy Upgrade HMAC Key
Brick Forge Leak
[Optionally: Include visual diagram using Excalidraw or similar]
Accepted Risks (for this trial):
- Single-key signature (no multi-sig)
- SQLite instead of HA database
- No formal audit
Production TODO:
- Multi-sig for admin operations
- Hardware wallet for private keys
- Third-party audit (Trail of Bits, OpenZeppelin)
- Bug bounty program
| Threat ID | Category | Severity | Mitigation Status |
|---|---|---|---|
| T-001 | Reentrancy | Mitigated | |
| T-002 | Upgrade Brick | Mitigated | |
| T-003 | Role Escalation | Mitigated | |
| T-101 | HMAC Forgery | Mitigated | |
| T-102 | Replay Attack | Mitigated | |
| T-201 | Key Leakage | Mitigated | |
| T-203 | Chain Reorg | Partially (needs confirmation depth) | |
| T-301 | Compliance Bypass | Mitigated |
Signed: Githaiga22
Date: 03/11/2025