← All Scripts
Demo Data Script

Network IDS Alerts

Generate 3,000 Suricata-style intrusion detection alerts with realistic IP distributions, severity spikes, and protocol mix. 30 days of synthetic SOC data.

GENERATED DATA 3,000 ROWS SECURITY SCATTER + CITY VIEW

The Script

This script generates realistic IDS alert data. Source IPs include repeat offenders from internal ranges and random external addresses. Severity follows a pyramid distribution: mostly low, few critical. Alert categories span recon, port scans, malware C2, brute force, and lateral movement.

-- demo_security.lua — Network IDS alerts (3,000 alerts over 30 days)
--
-- Simulates Suricata-style alert data with realistic IP distributions,
-- severity spikes, and protocol mix. Great for timeline scrubbing
-- and City View (height=severity, color=alert_category, district=action).

ds.log("=== Network Security Alerts Demo ===")

ds.query([[
    SELECT
        -- Timestamps spread over 30 days with some clustering
        timestamp '2025-12-01 00:00:00'
            + INTERVAL (floor(random() * 30)::int) DAY
            + INTERVAL (floor(random() * 86400)::int) SECOND     AS timestamp,

        -- Source IPs: mix of internal ranges with a few repeat offenders
        CASE
            WHEN random() < 0.15 THEN '10.0.1.' || (100 + (i % 5))
            WHEN random() < 0.35 THEN '192.168.' || (1 + (i % 3)) || '.' || (10 + floor(random() * 240)::int)
            WHEN random() < 0.60 THEN '172.16.' || floor(random() * 16)::int || '.' || floor(random() * 254 + 1)::int
            ELSE                      floor(random() * 223 + 1)::int || '.' || floor(random() * 254 + 1)::int || '.' || floor(random() * 254 + 1)::int || '.' || floor(random() * 254 + 1)::int
        END AS src_ip,

        -- Destination IPs: mostly internal servers
        CASE
            WHEN random() < 0.40 THEN '10.0.0.' || (1 + (i % 20))
            WHEN random() < 0.70 THEN '10.0.1.' || (1 + floor(random() * 50)::int)
            ELSE                      '10.0.2.' || (1 + floor(random() * 10)::int)
        END AS dst_ip,

        -- Ports
        CASE
            WHEN random() < 0.30 THEN 443
            WHEN random() < 0.50 THEN 80
            WHEN random() < 0.65 THEN 22
            WHEN random() < 0.75 THEN 53
            WHEN random() < 0.85 THEN 8080
            ELSE                      floor(random() * 60000 + 1024)::int
        END AS dst_port,

        -- Protocol
        CASE
            WHEN random() < 0.55 THEN 'TCP'
            WHEN random() < 0.80 THEN 'UDP'
            WHEN random() < 0.92 THEN 'ICMP'
            ELSE                      'DNS'
        END AS protocol,

        -- Alert severity 1-4 (most are low)
        CASE
            WHEN random() < 0.05 THEN 1   -- critical
            WHEN random() < 0.20 THEN 2   -- high
            WHEN random() < 0.55 THEN 3   -- medium
            ELSE                      4    -- low/info
        END AS severity,

        -- Alert category
        CASE
            WHEN random() < 0.20 THEN 'Attempted Recon'
            WHEN random() < 0.35 THEN 'Port Scan'
            WHEN random() < 0.50 THEN 'Malware C2'
            WHEN random() < 0.62 THEN 'Policy Violation'
            WHEN random() < 0.72 THEN 'Brute Force'
            WHEN random() < 0.82 THEN 'Data Exfiltration'
            WHEN random() < 0.90 THEN 'Lateral Movement'
            ELSE                      'Anomalous Traffic'
        END AS alert_category,

        -- Bytes transferred (log-normal-ish distribution)
        round(
            CASE
                WHEN random() < 0.6 THEN random() * 5000
                WHEN random() < 0.9 THEN random() * 50000
                ELSE                      random() * 500000 + 50000
            END, 0
        )::int AS bytes_transferred,

        -- Action taken
        CASE
            WHEN random() < 0.40 THEN 'Allowed'
            WHEN random() < 0.75 THEN 'Blocked'
            WHEN random() < 0.90 THEN 'Alerted'
            ELSE                      'Quarantined'
        END AS action

    FROM generate_series(0, 2999) AS t(i)
    ORDER BY timestamp
]])

ds.log("Generated " .. ds.data.row_count .. " alerts over 30 days")
ds.log("Columns: " .. table.concat(ds.data.column_names, ", "))

-- Line chart: alert count over time
ds.chart.type  = "scatter"
ds.chart.x     = 1      -- timestamp
ds.chart.y     = {6}    -- severity
ds.chart.title = "Alert Severity Over Time"

ds.log("=== Chart ready — switch to City View for 3D exploration ===")
ds.log("    Height: severity or bytes | Color: alert_category | District: action")

Columns

10 fields: timestamp, src_ip, dst_ip, dst_port, protocol, severity, alert_category, bytes_transferred, action.

Realistic IPs

Internal ranges (10.x, 192.168.x, 172.16.x) with repeat offenders. External IPs are randomized. Destination IPs are mostly internal servers.

Try These Queries

SELECT alert_category, count(*) FROM _cl_result WHERE severity = 1 GROUP BY 1 ORDER BY 2 DESC

City View

Set height to severity or bytes_transferred, color to alert_category, district to action. Critical alerts tower over noise.


Run this script in ColumnLens

Paste it into the Script Console. No data download needed.

Download on the Mac App Store
Or try the free version (up to 100 MB)