HUNCH Security Documentation
Security Overview
HUNCH implements comprehensive security measures across all layers of the application stack, from smart contracts to frontend interfaces. This document outlines our security architecture, practices, and guidelines.
Smart Contract Security
Access Control
Role-Based Access Control (RBAC)
// Oracle contract access control
mapping(address => bool) public authorizedResolvers;
modifier onlyOwner() {
require(msg.sender == owner, "Unauthorized: caller is not the owner");
_;
}
modifier onlyAuthorized() {
require(
msg.sender == owner || authorizedResolvers[msg.sender],
"Unauthorized: caller is not authorized"
);
_;
}Multi-Signature Requirements
Critical operations require multiple signatures
Owner functions protected by timelock
Emergency pause capabilities for all contracts
Reentrancy Protection
Implementation
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Market is ReentrancyGuard {
function buyShares(Side _side, uint256 _shares)
external
payable
nonReentrant
onlyActive
{
// Implementation with reentrancy protection
}
}Checks-Effects-Interactions Pattern
function sellShares(Side _side, uint256 _shares) external {
// 1. CHECKS
require(_shares > 0, "Invalid share amount");
require(userShares[msg.sender][_side] >= _shares, "Insufficient shares");
// 2. EFFECTS
userShares[msg.sender][_side] -= _shares;
totalShares[_side] -= _shares;
// 3. INTERACTIONS
(bool success, ) = msg.sender.call{value: payout}("");
require(success, "Transfer failed");
}Input Validation
Parameter Validation
function createMarket(
string memory _question,
string memory _description,
uint256 _endTime,
string memory _category
) external returns (uint256) {
require(bytes(_question).length >= 10, "Question too short");
require(bytes(_question).length <= 200, "Question too long");
require(_endTime > block.timestamp, "End time must be in future");
require(_endTime <= block.timestamp + 365 days, "End time too far");
require(bytes(_category).length > 0, "Category required");
// Market creation logic
}Overflow Protection
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
}Emergency Controls
Circuit Breakers
contract Market {
bool public paused = false;
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
function emergencyPause() external onlyOwner {
paused = true;
emit EmergencyPause(block.timestamp);
}
function unpause() external onlyOwner {
paused = false;
emit Unpause(block.timestamp);
}
}Fund Recovery
function emergencyWithdraw() external onlyOwner {
require(paused, "Contract must be paused");
require(block.timestamp > emergencyWithdrawDelay, "Timelock not expired");
uint256 balance = address(this).balance;
(bool success, ) = owner.call{value: balance}("");
require(success, "Withdrawal failed");
emit EmergencyWithdraw(balance, block.timestamp);
}Frontend Security
Authentication & Authorization
Wallet-Based Authentication
class AuthService {
async authenticateWallet(address: string, signature: string): Promise<boolean> {
try {
// Verify signature against expected message
const message = `Login to HUNCH at ${Date.now()}`;
const recoveredAddress = ethers.utils.verifyMessage(message, signature);
if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
throw new Error('Invalid signature');
}
// Create authenticated session
return this.createSession(address);
} catch (error) {
console.error('Authentication failed:', error);
return false;
}
}
}Session Management
class SessionManager {
private sessionTimeout = 24 * 60 * 60 * 1000; // 24 hours
createSession(address: string): string {
const token = this.generateSecureToken();
const expiry = Date.now() + this.sessionTimeout;
localStorage.setItem('auth_token', token);
localStorage.setItem('auth_expiry', expiry.toString());
localStorage.setItem('wallet_address', address);
return token;
}
validateSession(): boolean {
const token = localStorage.getItem('auth_token');
const expiry = localStorage.getItem('auth_expiry');
if (!token || !expiry) return false;
return Date.now() < parseInt(expiry);
}
}Input Validation & Sanitization
Form Validation
import { z } from 'zod';
const CreateMarketSchema = z.object({
question: z.string()
.min(10, 'Question must be at least 10 characters')
.max(200, 'Question cannot exceed 200 characters')
.refine(val => !val.includes('<script'), 'Invalid characters detected'),
description: z.string()
.max(1000, 'Description cannot exceed 1000 characters')
.optional(),
endTime: z.date()
.min(new Date(), 'End time must be in the future')
.max(new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), 'End time too far in future'),
category: z.string()
.min(1, 'Category is required')
.max(50, 'Category cannot exceed 50 characters'),
});
function validateMarketInput(input: unknown) {
try {
return CreateMarketSchema.parse(input);
} catch (error) {
throw new Error(`Validation failed: ${error.message}`);
}
}XSS Prevention
function sanitizeUserInput(input: string): string {
return input
.replace(/[<>]/g, '') // Remove potential HTML tags
.replace(/javascript:/gi, '') // Remove javascript: protocols
.replace(/on\w+=/gi, '') // Remove event handlers
.trim();
}
function escapeHtml(unsafe: string): string {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}Content Security Policy (CSP)
CSP Headers
// Vercel configuration
export const securityHeaders = [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' *.vercel.app;
style-src 'self' 'unsafe-inline' fonts.googleapis.com;
font-src 'self' fonts.gstatic.com;
img-src 'self' data: https: *.supabase.co;
connect-src 'self' *.supabase.co *.infura.io *.alchemy.com wss:;
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
`.replace(/\s{2,}/g, ' ').trim()
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
}
];Database Security
Row Level Security (RLS)
User-Specific Data Access
-- Only users can access their own X connections
CREATE POLICY "Users can view their own X connections"
ON x_connections FOR SELECT
USING (user_address = (current_setting('request.jwt.claims', true)::json->>'wallet_address'));
-- Only market creators can update their markets
CREATE POLICY "Market creators can update their markets"
ON markets_enhanced FOR UPDATE
USING (creator_address = (current_setting('request.jwt.claims', true)::json->>'wallet_address'));Data Isolation
-- Prevent cross-user data access
CREATE POLICY "Strict user isolation"
ON user_portfolios FOR ALL
USING (wallet_address = auth.jwt()->>'wallet_address');
-- Public read access for market data
CREATE POLICY "Public market access"
ON markets_enhanced FOR SELECT
USING (true);API Security
Rate Limiting
class RateLimiter {
private attempts = new Map<string, number[]>();
private readonly maxAttempts = 100;
private readonly windowMs = 60 * 1000; // 1 minute
isAllowed(identifier: string): boolean {
const now = Date.now();
const userAttempts = this.attempts.get(identifier) || [];
// Remove old attempts outside the window
const validAttempts = userAttempts.filter(
timestamp => now - timestamp < this.windowMs
);
if (validAttempts.length >= this.maxAttempts) {
return false;
}
validAttempts.push(now);
this.attempts.set(identifier, validAttempts);
return true;
}
}API Key Management
class APIKeyManager {
private keys = new Map<string, APIKeyInfo>();
validateKey(apiKey: string): boolean {
const keyInfo = this.keys.get(apiKey);
if (!keyInfo) return false;
if (keyInfo.expired) return false;
if (keyInfo.rateLimit && !this.checkRateLimit(apiKey)) return false;
return true;
}
rotateKey(oldKey: string): string {
const newKey = this.generateSecureKey();
const keyInfo = this.keys.get(oldKey);
if (keyInfo) {
this.keys.set(newKey, keyInfo);
this.keys.delete(oldKey);
}
return newKey;
}
}Encryption
Data at Rest
class EncryptionService {
private algorithm = 'aes-256-gcm';
private keySize = 32;
encrypt(data: string, key: Buffer): EncryptedData {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(this.algorithm, key);
cipher.setAAD(Buffer.from('HUNCH-DATA'));
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
};
}
decrypt(encryptedData: EncryptedData, key: Buffer): string {
const decipher = crypto.createDecipher(this.algorithm, key);
decipher.setAAD(Buffer.from('HUNCH-DATA'));
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}Data in Transit
// HTTPS enforcement
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
// TLS configuration
const tlsOptions = {
minVersion: 'TLSv1.2',
ciphers: [
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-SHA256',
'ECDHE-RSA-AES256-SHA384'
].join(':'),
honorCipherOrder: true
};Operational Security
Environment Management
Secure Environment Variables
# Production environment
VITE_SUPABASE_URL=https://secure-project.supabase.co
VITE_SUPABASE_ANON_KEY=eyJ...secure-key
VITE_WALLETCONNECT_PROJECT_ID=secure-project-id
# Database credentials (server-side only)
DATABASE_URL=postgresql://user:pass@host:5432/db
JWT_SECRET=ultra-secure-random-string
ENCRYPTION_KEY=32-byte-encryption-keySecrets Management
class SecretsManager {
private secrets = new Map<string, SecretInfo>();
async getSecret(name: string): Promise<string> {
const secret = this.secrets.get(name);
if (!secret) {
throw new Error(`Secret ${name} not found`);
}
if (this.isExpired(secret)) {
await this.rotateSecret(name);
return this.getSecret(name);
}
return this.decrypt(secret.value);
}
async rotateSecret(name: string): Promise<void> {
const newValue = await this.generateNewSecret(name);
await this.updateSecret(name, newValue);
await this.notifyRotation(name);
}
}Monitoring & Alerting
Security Event Detection
class SecurityMonitor {
private alertThresholds = {
failedLogins: 5,
suspiciousTransactions: 3,
unusualApiUsage: 100
};
async monitorSuspiciousActivity() {
const events = await this.getSecurityEvents();
for (const event of events) {
await this.analyzeEvent(event);
}
}
private async analyzeEvent(event: SecurityEvent) {
switch (event.type) {
case 'failed_login':
await this.handleFailedLogin(event);
break;
case 'suspicious_transaction':
await this.handleSuspiciousTransaction(event);
break;
case 'api_abuse':
await this.handleApiAbuse(event);
break;
}
}
private async handleFailedLogin(event: SecurityEvent) {
const attempts = await this.getFailedLoginCount(event.userAddress);
if (attempts >= this.alertThresholds.failedLogins) {
await this.sendAlert({
type: 'SECURITY_ALERT',
message: `Multiple failed login attempts for ${event.userAddress}`,
severity: 'HIGH'
});
await this.temporaryBan(event.userAddress);
}
}
}Incident Response
Automated Response
class IncidentResponse {
async handleSecurityIncident(incident: SecurityIncident) {
// 1. Immediate containment
await this.containThreat(incident);
// 2. Alert security team
await this.alertSecurityTeam(incident);
// 3. Preserve evidence
await this.preserveEvidence(incident);
// 4. Begin investigation
await this.startInvestigation(incident);
}
private async containThreat(incident: SecurityIncident) {
switch (incident.type) {
case 'CONTRACT_EXPLOIT':
await this.pauseAffectedContracts();
break;
case 'API_COMPROMISE':
await this.revokeCompromisedKeys();
break;
case 'DATA_BREACH':
await this.isolateAffectedSystems();
break;
}
}
}Security Testing
Automated Security Testing
Smart Contract Tests
describe('Market Security Tests', () => {
it('should prevent reentrancy attacks', async () => {
const attacker = await ReentrancyAttacker.deploy(market.address);
await expect(
attacker.attack({ value: ethers.utils.parseEther('1') })
).to.be.reverted;
});
it('should validate all inputs', async () => {
await expect(
market.createMarket('', 'desc', 0, 'cat')
).to.be.revertedWith('Question too short');
await expect(
market.createMarket('valid question', 'desc', 0, 'cat')
).to.be.revertedWith('End time must be in future');
});
it('should enforce access controls', async () => {
await expect(
market.connect(nonOwner).emergencyPause()
).to.be.revertedWith('Unauthorized');
});
});Frontend Security Tests
describe('Frontend Security', () => {
it('should sanitize user inputs', () => {
const maliciousInput = '<script>alert("xss")</script>';
const sanitized = sanitizeUserInput(maliciousInput);
expect(sanitized).not.toContain('<script>');
});
it('should validate wallet signatures', async () => {
const fakeSignature = '0xinvalidsignature';
const isValid = await authService.validateSignature(
userAddress,
'Login message',
fakeSignature
);
expect(isValid).toBe(false);
});
it('should enforce rate limits', async () => {
for (let i = 0; i < 101; i++) {
await apiService.makeRequest();
}
await expect(
apiService.makeRequest()
).toThrow('Rate limit exceeded');
});
});Manual Security Reviews
Code Review Checklist
Security Audit Process
Static Analysis: Automated code scanning
Dynamic Analysis: Runtime security testing
Penetration Testing: Simulated attacks
Code Review: Manual security review
Documentation Review: Security documentation audit
Compliance & Standards
Security Standards Compliance
SOC 2 Type II: Security, availability, and confidentiality
ISO 27001: Information security management
GDPR: Data protection and privacy
PCI DSS: Payment card industry standards (if applicable)
Regular Security Practices
Quarterly security audits
Monthly penetration testing
Weekly dependency updates
Daily security monitoring
Continuous threat assessment
Bug Bounty Program
Scope: All HUNCH infrastructure and smart contracts
Rewards: $500 - $50,000 based on severity
Disclosure: Responsible disclosure required
Timeline: 90-day disclosure timeline
This comprehensive security documentation ensures HUNCH maintains the highest security standards across all components of the platform.
Last updated
