Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
# Add Rate Limiting Fields to Team Sessions

1. Changes
- Add `failed_attempts` (integer, default 0) - Counter for failed flag submissions
- Add `last_failed_attempt` (timestamptz) - Timestamp of last failed attempt
- Add `rate_limit_locked_until` (timestamptz) - Lockout expiration time
- Add `rate_limit_level` (integer, default 0) - Backoff level for exponential increase

2. Purpose
- Track failed flag submission attempts per team
- Implement progressive rate limiting with exponential backoff
- Prevent brute force attacks on challenge flags
- Lock teams out temporarily after threshold exceeded
- Reset lockout automatically when time expires

3. Rate Limiting Strategy
- Threshold: 5 failed attempts triggers lockout
- Initial lockout: 30 seconds
- Exponential backoff: lockout_duration = 30 * (2 ^ rate_limit_level)
- Max lockout: ~8 hours (level 10)
- Reset after successful submission or 24 hours of inactivity

4. Indexes
- Index on rate_limit_locked_until for efficient lockout checks
- Index on team_id for rate limit lookups
*/

ALTER TABLE team_sessions
ADD COLUMN IF NOT EXISTS failed_attempts integer DEFAULT 0,
ADD COLUMN IF NOT EXISTS last_failed_attempt timestamptz,
ADD COLUMN IF NOT EXISTS rate_limit_locked_until timestamptz,
ADD COLUMN IF NOT EXISTS rate_limit_level integer DEFAULT 0;

-- Create indexes for rate limit queries
CREATE INDEX IF NOT EXISTS idx_team_sessions_rate_limit_locked_until
ON team_sessions(rate_limit_locked_until)
WHERE rate_limit_locked_until IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_team_sessions_last_failed_attempt
ON team_sessions(last_failed_attempt);

-- Add comment documenting rate limiting fields
COMMENT ON COLUMN team_sessions.failed_attempts
IS 'Counter for consecutive failed flag submission attempts';

COMMENT ON COLUMN team_sessions.last_failed_attempt
IS 'Timestamp of the most recent failed flag submission attempt';

COMMENT ON COLUMN team_sessions.rate_limit_locked_until
IS 'Timestamp until which the team is locked out from submitting flags';

COMMENT ON COLUMN team_sessions.rate_limit_level
IS 'Exponential backoff level - lockout_duration = 30 * (2 ^ level) seconds';
101 changes: 101 additions & 0 deletions Databases/supabase/migrations/20260207_create_rate_limit_logs.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
# Create Abuse Detection Logs Table

1. New Tables
- `rate_limit_logs`
- `id` (uuid, primary key) - Unique identifier
- `team_name` (text) - Team identifier
- `challenge_id` (text) - Challenge being attempted
- `attempt_count` (integer) - Number of failed attempts
- `lockout_level` (integer) - Current exponential backoff level
- `locked_until` (timestamptz) - When lockout expires
- `ip_address` (text, optional) - Requester IP for pattern analysis
- `user_agent` (text, optional) - User agent for anomaly detection
- `severity` (text) - Abuse severity (low, medium, high, critical)
- `action_taken` (text) - Description of action taken by system
- `created_at` (timestamptz) - Log timestamp

2. Purpose
- Track all rate limit violations for audit and analysis
- Identify patterns of abuse and suspicious activity
- Support admin investigation of security incidents
- Enable automated alerting on critical patterns
- Maintain compliance logs for security review

3. Indexes
- Index on team_name for quick lookup by team
- Index on created_at for time-based queries
- Index on severity for filtering by alert level
- Composite index on (team_name, severity) for pattern analysis

4. Retention
- Keep logs for 90 days by default
- Archive older logs for historical analysis
- Support compliance requirements
*/

CREATE TABLE IF NOT EXISTS rate_limit_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
team_name text NOT NULL,
challenge_id text,
attempt_count integer NOT NULL,
lockout_level integer NOT NULL,
locked_until timestamptz,
ip_address text,
user_agent text,
severity text NOT NULL CHECK (severity IN ('low', 'medium', 'high', 'critical')),
action_taken text NOT NULL,
created_at timestamptz DEFAULT now()
);

-- Create indexes for efficient querying
CREATE INDEX IF NOT EXISTS idx_rate_limit_logs_team_name ON rate_limit_logs(team_name);
CREATE INDEX IF NOT EXISTS idx_rate_limit_logs_created_at ON rate_limit_logs(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_rate_limit_logs_severity ON rate_limit_logs(severity);
CREATE INDEX IF NOT EXISTS idx_rate_limit_logs_team_severity ON rate_limit_logs(team_name, severity);

-- Create index for finding recent suspicious activity
CREATE INDEX IF NOT EXISTS idx_rate_limit_logs_recent_abuse
ON rate_limit_logs(team_name, created_at DESC)
WHERE severity IN ('high', 'critical');

-- Add comment explaining the table
COMMENT ON TABLE rate_limit_logs
IS 'Audit log for rate limiting violations and abuse detection patterns';

COMMENT ON COLUMN rate_limit_logs.severity
IS 'Severity level of the abuse attempt: low (1-2 attempts), medium (3-5), high (6+), critical (lockout level > 2)';

COMMENT ON COLUMN rate_limit_logs.action_taken
IS 'Description of action taken by the system (e.g., "Team locked out for 30 seconds", "Alert threshold reached")';

-- Optional: Create view for suspicious activity within last 24 hours
CREATE OR REPLACE VIEW suspicious_activity_24h AS
SELECT
team_name,
COUNT(*) as violation_count,
MAX(severity) as max_severity,
MAX(lockout_level) as max_lockout_level,
MAX(created_at) as last_violation,
ARRAY_AGG(DISTINCT challenge_id) as attempted_challenges
FROM rate_limit_logs
WHERE created_at > now() - interval '24 hours'
GROUP BY team_name
HAVING COUNT(*) >= 3
ORDER BY violation_count DESC;

-- Optional: Create view for critical abuse patterns
CREATE OR REPLACE VIEW critical_abuse_patterns AS
SELECT
team_name,
COUNT(*) as critical_incidents,
COUNT(DISTINCT challenge_id) as unique_challenges,
MAX(lockout_level) as max_backoff_level,
MIN(created_at) as first_incident,
MAX(created_at) as last_incident,
EXTRACT(EPOCH FROM (MAX(created_at) - MIN(created_at))) / 3600 as incident_span_hours
FROM rate_limit_logs
WHERE severity = 'critical'
AND created_at > now() - interval '7 days'
GROUP BY team_name
ORDER BY critical_incidents DESC;
Loading
Loading