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, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

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-key

Secrets 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

  1. Static Analysis: Automated code scanning

  2. Dynamic Analysis: Runtime security testing

  3. Penetration Testing: Simulated attacks

  4. Code Review: Manual security review

  5. 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