Dynamic Firewall Using Reinforced Learning (RL)
Dynamic Firewall Using Reinforced Learning (RL)
LEARNING (RL)
A project report submitted to Dwaraka Doss Goverdhan Doss Vaishnav College in partial
fulfilment of the requirements for the degree
By
Assistant Professor,
April 2026
DWARAKA DOSS GOVERDHAN DOSS VAISHNAV
COLLEGE (AUTONOMOUS)
Arumbakkam, Chennai- 600106
BONAFIDE CERTIFICATE
Ms. P. SUGANYA MCA, [Link], SET., Dwaraka Doss Goverdhan Doss Vaishnav
College for her continuous support and her help in compiling the project.
This project would not have been a success without my internal guide. So, I would extend
my deep sense of gratitude to my Internal Guide Ms. [Link] [Link]., NET for the
effort she took in guiding me in all the stages of completion of my project work.
My special thanks to all faculty members and staff members of the Department of
Computer Science UG & PG for their support and encouragement for the successful
completion of the project.
I also thank my friends for providing moral support and timely help to finish the project.
I wholeheartedly express my sincere thanks to my lovable parents and relatives who
encouraged me with moral and economic support.
RESHMA B P
TABLE OF CONTENTS
[Link] TITLE PAGE NO
ABSTRACT 1
1 INTRODUCTION 2
A. Sample Code 22
B. Snapshot 73
FUTURE ENHANCEMENTS 77
SCOPE OF FUTURE DEVELOPMENT 78
CONCLUSION 79
BIBLIOGRAPHY 80
1
ABSTRACT:
The RL Dynamic Firewall is an intelligent network security system that combines Reinforcement
Learning (RL) with real-time traffic analysis and file scanning to protect internal networks from
port scanning attacks and malicious file uploads.
Traditional firewalls rely on static, manually maintained rule sets that cannot adapt to new or
evolving attack patterns. This project addresses that limitation by deploying a Deep Q-Network
(DQN) agent that observes live network traffic features and file analysis results, learns from each
security decision through a reward signal, and continuously improves its policy without human
intervention.
The system processes two primary threat vectors: port scanning (sequential, random, and slow
scan patterns) and malicious file uploads (PDF, DOCX, and EXE files containing embedded
executables, macro code, or JavaScript payloads). For each event the agent selects one of five
enforcement actions — Allow, Block, Rate-Limit, Quarantine, or Log-Only — and receives a
reward that penalizes false positives and breaches while rewarding correct decisions.
The prototype is fully containerized using Docker and Docker Compose, exposes a REST +
WebSocket API, and includes a live dark-mode dashboard displaying real-time decisions, Q-
value confidence bars, reward history, and file scan results. All 35 unit and integration tests pass,
confirming the correctness of the agent, feature extractor, and file scanner components.
2
CHAPTER 1: INTRODUCTION
Adaptive Threat Detection: Automatically detect port scanning (sequential, random, and
slow patterns) without manually written rules.
Malicious File Analysis: Identify dangerous content inside PDF, DOCX, and EXE files
using entropy analysis, macro detection, and embedded executable scanning.
Online Learning: Improve the firewall policy continuously from live traffic using the
DQN training loop and experience replay.
Live Monitoring Dashboard: Provide a real-time web interface showing every decision,
Q-value breakdown, reward signal, and file scan result.
Containerised Deployment: Ship the entire system as Docker containers for reproducible,
one-command deployment on any Linux host.
The system architecture consists of three cooperating layers: a Python capture layer that extracts
features from packet events and file uploads; a Python RL enforcer layer (Flask API) that runs
the DQN agent and maintains enforcement state; and an HTML/JavaScript dashboard layer that
visualises decisions in real time over WebSocket.
3
Backend
Python 3.11
Flask 3.0 — REST API framework
Flask-SocketIO 5.3 — WebSocket broadcasting
Flask-CORS 4.0 — cross-origin resource sharing
NumPy 1.26 — Q-network matrix operations
Eventlet 0.35 — async server mode for SocketIO
Frontend
1. Technical Feasibility
Deep Q-Networks have been demonstrated in complex decision environments (Atari games,
robotic control) and are well-suited to the firewall action-selection problem. The state space is
low-dimensional (17 features), the action space is small (5 discrete actions), and training
transitions are cheap to generate from network traffic. NumPy-based implementation removes
the need for GPU hardware, making the prototype deployable on commodity servers.
2. Economic Feasibility
The entire stack uses open-source components (Python, Flask, NumPy, Docker, Nginx). No
commercial licences, cloud services, or specialist hardware are required. Deployment cost is
limited to the host machine running Docker Desktop.
3. Operational Feasibility
The system ships as a Docker Compose stack that starts with a single command. The live
dashboard gives operators full visibility into agent decisions without requiring knowledge of
machine learning. The kill switch and hard override rules ensure the RL agent can be disabled
instantly if its behaviour degrades.
4. Legal Feasibility
The project processes only metadata and structural features of uploaded files; it does not store or
transmit file content outside the local container. No personal data is collected. The system
operates within the boundaries of standard network monitoring practices.
6
DISADVANTAGES
Cannot detect new or mutated attack patterns without manual rule updates.
Static rules accumulate over time, creating management overhead and potential conflicts.
High false positive rates from overly broad rules disrupt legitimate user traffic.
A feature extractor observes incoming packet events and computes rolling statistics
(packet rate, unique port count, SYN ratio, sequential scan indicator) over 1-second and
10-second windows. For file uploads, a binary analyser computes Shannon entropy,
detects macro code, embedded executables, and JavaScript payloads, and encodes these
as five additional state features.
The DQN agent receives the combined 17-feature state vector, selects an enforcement
action, and stores the transition in an experience replay buffer. A mini-batch of 64
transitions is sampled each step to compute a Bellman loss and update the Q-network
weights. This online learning loop means the agent's policy continuously adapts to the
traffic it sees.
ADVANTAGES
Adapts automatically to new scanning patterns and file obfuscation techniques without
manual rule updates.
Learns from every decision — blocked events improve future threat detection
Reward function balances security (penalise breaches) with usability (penalise false
positives).
Live dashboard gives operators real-time visibility into agent confidence and decision
rationale.
8
2.5 MODULES:
The RL Dynamic Firewall is composed of five cooperating modules:
The enforcer container is the central component. It loads the DQN agent weights on startup,
exposes the REST API to receive packet events and file uploads, runs inference and training in
the same thread, and broadcasts decisions to all connected dashboard clients over WebSocket.
The dashboard container serves a single static HTML file over HTTP. The browser opens a
[Link] connection directly to the enforcer on port 5000, receiving a stream of decision events.
All chart updates and Q-value visualisations are computed client-side in JavaScript.
A shared Docker named volume (models/) is mounted into the enforcer container. The DQN
agent saves its Q-network weights to this volume every 100 training steps, ensuring the trained
policy survives container restarts.
1. Packet event arrives at POST /packet with fields: src_ip, dst_port, protocol, flags, size,
success.
2. PacketEvent object is passed to FeatureExtractor.record_packet(), updating the source
IP's event deque.
3. FeatureExtractor.get_state() computes the 17-feature state vector from rolling window
statistics.
4. DQNAgent.select_action(state) applies epsilon-greedy policy: with probability ε select
random action, otherwise select argmax Q(s, a).
5. Action is applied: BLOCK updates blocked_ips table with a 300-second expiry;
RATE_LIMIT updates rate_limited table; QUARANTINE adds IP to quarantine list;
ALLOW and LOG_ONLY update counters only.
6. compute_reward(action, ground_truth) returns a scalar reward based on whether the
action was correct.
7. Transition (s, a, r, s') is stored in the replay buffer.
8. DQNAgent.train_step() samples 64 transitions, computes Bellman targets, and updates
Q-network weights via SGD.
9. Decision JSON (action, reward, Q-values, epsilon, scan_type) is emitted over [Link]
to all connected dashboard clients.
13. Risk score (0.0–1.0) is computed; verdict is assigned: safe / suspicious / malicious.
14. Five file features are assembled and injected into state vector positions [10–14].
15. RL agent selects action; reward is computed using the file verdict as ground truth.
16. Transition is stored, training step is run, decision is broadcast to dashboard.
13
CHAPTER 4 IMPLEMENTATION
NumPy 1.26
The DQN Q-network is implemented entirely in NumPy without PyTorch or TensorFlow. This
eliminates heavy ML framework dependencies, reduces the Docker image size, and makes the
training logic fully transparent. The three-layer MLP uses matrix multiplication (@), ReLU
activation ([Link]), and an SGD update rule. The target network is a separate QNetwork
instance whose weights are copied from the online network every 100 steps.
[Link] + [Link]
The dashboard connects to the enforcer API using the [Link] JavaScript client. On
connection, the server emits the last 20 decisions as a "history" event so the dashboard populates
immediately. Subsequent "decision" events are rendered as new rows in the scrolling log.
[Link] renders a doughnut chart for action distribution and a line chart for reward history, both
updated with animation disabled for smooth real-time streaming.
14
Dashboard Output:
The dashboard presents: a row of five stat counters (total decisions, blocks, allows, rate-limits,
quarantines); a doughnut chart of action distribution; a reward-over-time line chart; a scrolling
decision log (last 80 events); a Q-value bar panel showing confidence for the last decision; an
agent state panel (epsilon, buffer size, average loss, active blocks); and a file drag-and-drop
scanner with colour-coded verdict display.
16
4.3 SECURITY:
1. Hard Override Rules:
A set of hard-coded rules in [Link] cannot be overridden by the RL agent. These include:
management traffic from localhost is always allowed; known command-and-control IPs from the
threat intel feed are always blocked; and the /health, /status, and /events endpoints are always
accessible regardless of agent state.
2. Kill Switch
The system monitors the agent's block rate and false-positive rate against rolling baselines. If the
block rate exceeds 95% of all decisions (indicating the agent is blocking everything) or falls
below 1% (indicating the agent is allowing everything), a kill switch activates that reverts
enforcement to static rules until the operator intervenes. The kill switch can also be triggered
manually via a POST request to /killswitch.
4. Container Security
All containers run as non-root users. The enforcer container has no capability to modify host
iptables rules; it returns enforcement decisions as JSON that a host-level agent (not included in
this prototype) would act upon. The dashboard container runs in a read-only filesystem with only
the HTML directory mounted.
CHAPTER 5: TESTING
The test suite consists of 35 automated tests covering unit, integration, and end-to-end pipeline
scenarios. Tests are implemented using Python's built-in unittest module and are also compatible
with pytest.
APPENDICES
A. SAMPLE CODE
The following excerpts illustrate the core implementation patterns used in the RL Dynamic
Firewall.
This coding displays the dashboard of the firewall:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RL Firewall · Control Center</title>
<script src="[Link]
<script src="[Link]
<style>
@import url('[Link]
family=JetBrains+Mono:wght@300;400;500;700&family=Syne:wght@400;700;800&display=s
wap');
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #080c10;
--surface: #0d1117;
--card: #111820;
--border: #1e2d3d;
--teal: #00e5c0;
--red: #ff3b5c;
--amber: #ffaa00;
--blue: #2979ff;
--purple: #9c6fff;
--dim: #4a6070;
--text: #c9d8e8;
--muted: #556b7d;
23
--allow: #00e5c0;
--block: #ff3b5c;
--rate: #ffaa00;
--quar: #9c6fff;
--log: #2979ff;
}
html, body { height: 100%; background: var(--bg); color: var(--text);
font-family: 'JetBrains Mono', monospace; overflow-x: hidden; }
/* ── Header ── */
header {
display: flex; align-items: center; justify-content: space-between;
padding: 0 2rem; height: 56px;
background: var(--surface); border-bottom: 1px solid var(--border);
position: sticky; top: 0; z-index: 100;
}
.logo { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 1.1rem;
letter-spacing: 0.04em; color: var(--teal); }
.logo span { color: var(--text); }
.live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--teal);
animation: pulse 1.4s ease-in-out infinite; display: inline-block; margin-right: 6px; }
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%
{opacity:.4;transform:scale(1.3)} }
.header-right { display: flex; align-items: center; gap: 1.5rem; font-size: .75rem; color: var(--
muted); }
#epsilon-badge {
background: #0d2010; border: 1px solid #1a4020; color: var(--teal);
padding: 3px 10px; border-radius: 4px; font-size: .7rem;
}
/* ── Layout ── */
.main { display: grid; grid-template-columns: 1fr 340px; gap: 0; height: calc(100vh - 56px); }
.left { overflow-y: auto; padding: 1.5rem; display: flex; flex-direction: column; gap: 1rem; }
.right { border-left: 1px solid var(--border); overflow-y: auto; padding: 1rem; display: flex;
flex-direction: column; gap: 1rem; }
24
/* ── Cards ── */
.card {
background: var(--card); border: 1px solid var(--border); border-radius: 8px;
padding: 1rem 1.25rem;
}
.card-title {
font-family: 'Syne', sans-serif; font-size: .7rem; font-weight: 700;
letter-spacing: .12em; text-transform: uppercase; color: var(--muted);
margin-bottom: .75rem;
}
/* ── Stats row ── */
.stats-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: .75rem; }
.stat {
background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
padding: .75rem 1rem; text-align: center;
}
.stat-val { font-family: 'Syne', sans-serif; font-size: 1.6rem; font-weight: 800; line-height: 1; }
.stat-label { font-size: .62rem; color: var(--muted); margin-top: .3rem; letter-spacing: .08em; }
.c-teal { color: var(--teal); }
.c-red { color: var(--red); }
.c-amber { color: var(--amber); }
.c-purple { color: var(--purple); }
.c-blue { color: var(--blue); }
/* ── Event log ── */
#event-log { display: flex; flex-direction: column; gap: .4rem; max-height: 420px; overflow-y:
auto; }
.event-row {
display: grid; grid-template-columns: 60px 110px 90px 90px 1fr auto;
align-items: center; gap: .5rem;
background: var(--surface); border: 1px solid var(--border); border-radius: 5px;
padding: .4rem .75rem; font-size: .72rem;
25
/* ── Charts ── */
.chart-wrap { position: relative; height: 180px; }
/* ── Q-values panel ── */
#q-panel { font-size: .72rem; }
.q-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.q-label { width: 90px; color: var(--muted); flex-shrink: 0; }
.q-bar-wrap { flex: 1; height: 14px; background: var(--surface); border-radius: 3px; overflow:
hidden; }
26
/* ── File upload ── */
.upload-area {
border: 1px dashed var(--border); border-radius: 6px; padding: 1rem;
text-align: center; cursor: pointer; transition: border-color .2s;
}
.upload-area:hover { border-color: var(--teal); }
.upload-area input { display: none; }
.upload-area p { font-size: .72rem; color: var(--muted); margin-top: .4rem; }
.btn {
background: transparent; border: 1px solid var(--teal); color: var(--teal);
padding: .4rem 1rem; border-radius: 4px; font-family: inherit; font-size: .72rem;
cursor: pointer; transition: background .15s;
}
.btn:hover { background: #001f1a; }
.btn-danger { border-color: var(--red); color: var(--red); }
.btn-danger:hover { background: #1f000a; }
#upload-result { font-size: .7rem; margin-top: .75rem; line-height: 1.6; }
/* ── Agent info ── */
.info-row { display: flex; justify-content: space-between; font-size: .72rem;
padding: .3rem 0; border-bottom: 1px solid var(--border); }
.info-row:last-child { border: none; }
.info-key { color: var(--muted); }
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
</style>
</head>
<body>
<header>
<div class="logo">RL<span>Firewall</span></div>
<div class="header-right">
<span><span class="live-dot"></span>LIVE</span>
<span id="epsilon-badge">ε = 1.000</span>
<span id="buf-size" style="color:var(--muted)">buf: 0</span>
<span id="clock" style="font-size:.7rem"></span>
</div>
</header>
<div class="main">
<!-- ════ LEFT PANEL ════ -->
<div class="left">
// ── Chart setup ──
const actionCounts = { ALLOW: 0, BLOCK: 0, RATE_LIMIT: 0, QUARANTINE: 0,
LOG_ONLY: 0 };
const rewardHistory = [];
31
// ── Helpers ──
function updateActionChart() {
[Link][0].data = [Link](actionCounts);
[Link]('none');
}
function addReward(r) {
[Link](r);
if ([Link] > MAX_REWARD_PTS) [Link]();
[Link] = [Link]((_,i) => i);
[Link][0].data = rewardHistory;
[Link]('none');
}
function updateQValues(qv) {
if (!qv) return;
const vals = [Link](qv);
const min = [Link](...vals);
const max = [Link](...vals);
const range = max - min || 1;
for (const [name, val] of [Link](qv)) {
const row = [Link](`qr-${name}`);
if (!row) continue;
const pct = ((val - min) / range * 100).toFixed(1);
const bar = [Link]('.q-bar');
const span = [Link]('.q-val');
[Link] = pct + '%';
[Link] = [Link](3);
33
}
}
actionCounts[[Link]] = (actionCounts[[Link]] || 0) + 1;
updateActionChart();
addReward([Link]);
updateQValues(ev.q_values);
34
function clearLog() {
[Link]('event-log').innerHTML = '';
}
// ── Clock ──
setInterval(() => {
[Link]('clock').textContent = new Date().toLocaleTimeString();
}, 1000);
// ── Status polling ──
async function fetchStatus() {
try {
const r = await fetch(`${API}/status`);
const d = await [Link]();
const s = [Link] || {};
[Link]('s-total').textContent = s.total_decisions || 0;
[Link]('s-blocks').textContent = [Link] || 0;
[Link]('s-allows').textContent = [Link] || 0;
[Link]('s-rate').textContent = s.rate_limits || 0;
[Link]('s-quar').textContent = [Link] || 0;
[Link]('r-eps').textContent = ([Link] || 0).toFixed(4);
[Link]('r-buf').textContent = `${d.buffer_size || 0} transitions`;
[Link]('r-loss').textContent = (d.avg_loss || 0).toFixed(4);
[Link]('r-blk').textContent = d.active_blocks || 0;
[Link]('r-quar').textContent = [Link] || 0;
[Link]('epsilon-badge').textContent = `ε = ${([Link]||0).toFixed(3)}`;
[Link]('buf-size').textContent = `buf: ${d.buffer_size||0}`;
} catch(e) {}
}
setInterval(fetchStatus, 3000);
35
fetchStatus();
// ── WebSocket ──
[Link]('connect', () => [Link]('[ws] connected'));
[Link]('decision', ev => renderEvent(ev, true));
[Link]('history', evs => [Link](ev => renderEvent(ev, false)));
// ── File upload ──
const fileInput = [Link]('file-input');
[Link]('change', async () => {
const f = [Link][0];
if (!f) return;
const result = [Link]('upload-result');
[Link] = '<span style="color:var(--muted)">Scanning...</span>';
// ── Train episode ──
async function runEpisode() {
const btn = [Link]('train-btn');
const res = [Link]('train-result');
[Link] = true; [Link] = 'Training...';
try {
const r = await fetch(`${API}/train/episode`, { method: 'POST' });
const d = await [Link]();
[Link] = `Done · avg_loss=${d.avg_loss} · ε=${[Link]}`;
fetchStatus();
} catch(e) {
[Link] = `Error: ${[Link]}`;
}
[Link] = false; [Link] = 'Run 200 steps';
}
</script>
</body>
</html>
37
This coding runs the background of State, Action, Replay Buffer, Q-network,
Reward function and DQN Agent of the firewall:
[Link]:
import numpy as np
import random
import json
import os
from collections import deque
from dataclasses import dataclass, asdict
from typing import List, Tuple, Optional
STATE_DIM = 17
# Actions
ACTION_ALLOW =0
ACTION_BLOCK =1
ACTION_RATE_LIMIT = 2
ACTION_QUARANTINE = 3
ACTION_LOG_ONLY = 4
NUM_ACTIONS =5
ACTION_NAMES = {
ACTION_ALLOW: "ALLOW",
ACTION_BLOCK: "BLOCK",
ACTION_RATE_LIMIT: "RATE_LIMIT",
ACTION_QUARANTINE: "QUARANTINE",
ACTION_LOG_ONLY: "LOG_ONLY",
}
@dataclass
class Transition:
state: List[float]
38
action: int
reward: float
next_state: List[float]
done: bool
# ---------------------------------------------------------------------------
# Replay Buffer
# ---------------------------------------------------------------------------
class ReplayBuffer:
def __init__(self, capacity: int = 50_000):
[Link]: deque = deque(maxlen=capacity)
def __len__(self):
return len([Link])
# ---------------------------------------------------------------------------
# Q-Network (pure numpy — no PyTorch dependency for the container demo)
# ---------------------------------------------------------------------------
class QNetwork:
"""
2-hidden-layer MLP implemented in pure NumPy.
Weights shape: [STATE_DIM → 64 → 64 → NUM_ACTIONS]
Swap for [Link] in production.
"""
39
# Forward
h1 = self._relu(x @ self.W1 + self.b1)
h2 = self._relu(h1 @ self.W2 + self.b2)
40
q = h2 @ self.W3 + self.b3
# Backprop W3 / b3
dW3 = h2.T @ dq / len(acts)
db3 = [Link](axis=0)
# Backprop W2 / b2
dh2 = dq @ self.W3.T * (h2 > 0)
dW2 = h1.T @ dh2 / len(acts)
db2 = [Link](axis=0)
# Backprop W1 / b1
dh1 = dh2 @ self.W2.T * (h1 > 0)
dW1 = x.T @ dh1 / len(acts)
db1 = [Link](axis=0)
d = [Link](path + ".npz")
self.W1, self.b1 = d["W1"], d["b1"]
self.W2, self.b2 = d["W2"], d["b2"]
self.W3, self.b3 = d["W3"], d["b3"]
# ---------------------------------------------------------------------------
# Reward function
# ---------------------------------------------------------------------------
def compute_reward(action: int, ground_truth: str, latency_ms: float = 0.0) -> float:
"""
ground_truth: "malicious" | "safe" | "scan" | "normal"
"""
# True positive: correctly blocking a threat
if ground_truth in ("malicious", "scan"):
if action == ACTION_BLOCK: return +10.0
if action == ACTION_QUARANTINE: return +8.0
if action == ACTION_RATE_LIMIT: return +4.0
if action == ACTION_LOG_ONLY: return -2.0
if action == ACTION_ALLOW: return -15.0 # breach — heavy penalty
# ---------------------------------------------------------------------------
# DQN Agent
# ---------------------------------------------------------------------------
class DQNAgent:
def __init__(
self,
epsilon: float = 1.0,
epsilon_min: float = 0.05,
epsilon_decay: float = 0.995,
gamma: float = 0.99,
batch_size: int = 64,
target_update_freq: int = 100,
model_path: str = "/app/models/dqn_weights",
):
[Link] = epsilon
self.epsilon_min = epsilon_min
self.epsilon_decay = epsilon_decay
[Link] = gamma
self.batch_size = batch_size
self.target_update_freq = target_update_freq
self.model_path = model_path
self.q_net = QNetwork(seed=42)
self.target_net = QNetwork(seed=42) # target network (lags behind)
[Link] = ReplayBuffer()
self.step_count = 0
self.episode_rewards: List[float] = []
self.q_net.load(model_path)
self.target_net.load(model_path)
[Link] = self.epsilon_min # already trained — exploit
print(f"[Agent] Loaded weights from {model_path}.npz")
else:
print("[Agent] Starting fresh — weights will be trained online")
batch = [Link](self.batch_size)
states = [[Link] for t in batch]
actions = [[Link] for t in batch]
rewards = [[Link] for t in batch]
next_states = [t.next_state for t in batch]
dones = [[Link] for t in batch]
[Link](r)
else:
[Link](r + [Link] * float([Link](next_q[i])))
# Epsilon decay
[Link] = max(self.epsilon_min, [Link] * self.epsilon_decay)
def _sync_target(self):
self.target_net.W1 = self.q_net.[Link]()
self.target_net.b1 = self.q_net.[Link]()
self.target_net.W2 = self.q_net.[Link]()
self.target_net.b2 = self.q_net.[Link]()
self.target_net.W3 = self.q_net.[Link]()
self.target_net.b3 = self.q_net.[Link]()
def save(self):
[Link]([Link](self.model_path), exist_ok=True)
self.q_net.save(self.model_path)
45
This coding runs the background of Training RL online, File Upload and scanning,
Extract packet features and Status of the firewall:
[Link]:
import sys, os
[Link](0, [Link]([Link]([Link](__file__))))
import time
import json
import threading
import uuid
from typing import Optional
from flask import Flask, request, jsonify
from flask_socketio import SocketIO, emit
from flask_cors import CORS
app = Flask(__name__)
[Link]["SECRET_KEY"] = "rl-firewall-secret"
CORS(app, resources={r"/*": {"origins": "*"}})
socketio = SocketIO(app, cors_allowed_origins="*", async_mode="threading")
agent = DQNAgent(model_path="/app/models/dqn_weights")
47
extractor = FeatureExtractor()
# Stats counters
stats = {
"total_decisions": 0,
"blocks": 0,
"allows": 0,
"rate_limits": 0,
"quarantines": 0,
"log_onlys": 0,
"true_positives": 0,
"false_positives": 0,
"total_loss": 0.0,
"loss_count": 0,
}
# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------
event_log.pop(0)
[Link]("decision", event)
def _apply_action(action: int, src_ip: str, reason: str, duration: int = 300, context: dict = None):
"""Side-effect: update block/rate-limit tables."""
until = [Link]() + duration
if context and [Link]("type") == "file":
# Don't block IPs for file uploads - handle per-file
pass
else:
if action == ACTION_BLOCK:
blocked_ips[src_ip] = {"until": until, "reason": reason}
stats["blocks"] += 1
elif action == ACTION_RATE_LIMIT:
rate_limited[src_ip] = {"until": until}
stats["rate_limits"] += 1
elif action == ACTION_QUARANTINE:
if src_ip not in quarantined:
quarantined[src_ip] = {"files": [], "since": [Link]()}
quarantined[src_ip]["files"].append(reason)
stats["quarantines"] += 1
if action == ACTION_ALLOW:
stats["allows"] += 1
elif action == ACTION_LOG_ONLY:
49
stats["log_onlys"] += 1
stats["total_decisions"] += 1
def _decide(src_ip: str, state: list, ground_truth: str, context: dict) -> dict:
"""Core decision loop: action → reward → train."""
# For files, use policy regardless of IP block status
if [Link]("type") == "file":
action = [Link]("forced_action")
if action is None:
action = _file_policy_action([Link]("file_type", ""), ground_truth)
# Hard override: already blocked
elif _is_blocked(src_ip):
action = ACTION_BLOCK
else:
action = agent.select_action(state)
t = Transition(
50
event = {
"id": str(uuid.uuid4())[:8],
"timestamp": [Link]("%H:%M:%S"),
"src_ip": src_ip,
"action": ACTION_NAMES[action],
"action_id": action,
"reward": round(reward, 2),
"ground_truth": ground_truth,
"epsilon": round([Link], 3),
"loss": round(loss, 4) if loss else None,
"q_values": agent.q_values(state),
**context,
}
_log_event(event)
return event
# ---------------------------------------------------------------------------
# Routes — Traffic / port scan
# ---------------------------------------------------------------------------
@[Link]("/health")
51
def health():
return jsonify({"status": "ok", "epsilon": [Link]})
@[Link]("/packet", methods=["POST"])
def packet():
"""
Receive a packet event and decide what to do.
Body: { src_ip, dst_port, protocol, flags, size, success }
"""
body = request.get_json(force=True)
ev = PacketEvent(
src_ip=[Link]("src_ip", "[Link]"),
dst_port=int([Link]("dst_port", 80)),
protocol=[Link]("protocol", "tcp"),
flags=[Link]("flags", "SYN"),
size=int([Link]("size", 64)),
success=bool([Link]("success", False)),
)
extractor.record_packet(ev)
state = extractor.get_state(ev.src_ip)
ip_stats = extractor.ip_stats(ev.src_ip)
ground_truth = extractor.ground_truth(ev.src_ip)
@[Link]("/scan", methods=["POST"])
def scan():
"""
Receive a file upload and decide: safe / quarantine / block.
Form-data: file=<binary>
"""
try:
if "file" not in [Link]:
return jsonify({"error": "No file uploaded"}), 400
f = [Link]["file"]
data = [Link]()
filename = [Link] or "unknown"
"file_type": scan_result["file_type"],
"verdict": scan_result["verdict"],
"risk_score": scan_result["risk_score"],
"entropy": scan_result["entropy"],
"findings": scan_result["findings"],
"reason": f"file:{filename}:{scan_result['verdict']}",
"forced_action": forced_action,
})
result["scan"] = scan_result
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e), "traceback": __import__("traceback").format_exc()}), 500
# ---------------------------------------------------------------------------
# Routes — Status & dashboard data
# ---------------------------------------------------------------------------
@[Link]("/status")
def status():
avg_loss = (stats["total_loss"] / stats["loss_count"]
if stats["loss_count"] > 0 else 0.0)
return jsonify({
"stats": stats,
"avg_loss": round(avg_loss, 4),
"epsilon": round([Link], 4),
"buffer_size": len([Link]),
"active_blocks": len(blocked_ips),
"rate_limited": len(rate_limited),
"quarantined": len(quarantined),
"blocked_ips": list(blocked_ips.keys()),
})
@[Link]("/events")
def events():
54
@[Link]("/unblock/<ip>", methods=["POST"])
def unblock(ip):
blocked_ips.pop(ip, None)
rate_limited.pop(ip, None)
return jsonify({"ok": True, "ip": ip})
@[Link]("/train/episode", methods=["POST"])
def train_episode():
"""Run a synthetic training episode with simulated traffic."""
import random
losses = []
for _ in range(200):
# Simulate a mix of scan and normal traffic
is_attack = [Link]() < 0.4
state = [[Link]() for _ in range(17)]
if is_attack:
state[2] = [Link](0.5, 1.0) # high unique ports
state[4] = [Link](0.7, 1.0) # high SYN ratio
state[5] = [Link](0.0, 1.0) # sequential
gt = "scan"
else:
state[2] = [Link](0.0, 0.1)
gt = "normal"
action = agent.select_action(state)
reward = compute_reward(action, gt)
[Link](Transition(state, action, reward, state, False))
loss = agent.train_step()
55
if loss:
[Link](loss)
# ---------------------------------------------------------------------------
# WebSocket
# ---------------------------------------------------------------------------
@[Link]("connect")
def on_connect():
with event_lock:
recent = event_log[-20:]
emit("history", recent)
# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------
if __name__ == "__main__":
[Link]("/app/models", exist_ok=True)
print("[Enforcer] Starting RL Firewall API on :5000")
[Link](app, host="[Link]", port=5000, debug=False)
56
This coding tests the given files to check the status (ALLOW, BLOCK, etc.) of the
application:
import requests
import os
print(f"Action: {data['action']}")
print(f"Risk Score: {data['risk_score']:.3f}")
print(f"Reward: {data['reward']}")
print()
print(f"File: [Link]")
print(f"Verdict: {data['verdict']}")
print(f"Action: {data['action']}")
print(f"Risk Score: {data['risk_score']:.3f}")
print(f"Reward: {data['reward']}")
print()
print('=' * 60)
print('SUMMARY')
print('=' * 60)
print('✓ Safe files should get positive rewards')
print('✓ Malicious files should get penalized')
print('✓ Agent learns to ALLOW safe / BLOCK malicious')
59
import sys, os
[Link](0, [Link]([Link](__file__)))
import math
import time
import unittest
#
───────────────────────────────────────────────────────
──────
# Agent tests
#
───────────────────────────────────────────────────────
──────
class TestQNetwork([Link]):
def setUp(self):
[Link] = QNetwork(seed=0)
def test_output_shape(self):
state = [0.0] * STATE_DIM
60
q = [Link](state)
[Link](len(q), NUM_ACTIONS)
def test_forward_deterministic(self):
state = [0.5] * STATE_DIM
q1 = [Link](state)
q2 = [Link](state)
[Link](all(abs(a-b) < 1e-6 for a, b in zip(q1, q2)))
def test_update_changes_weights(self):
import numpy as np
state = [[0.1] * STATE_DIM for _ in range(10)]
actions = [0] * 10
targets = [1.0] * 10
w_before = [Link]()
[Link](state, actions, targets)
[Link](all(a == b for a, b in zip(w_before.flat, [Link])))
class TestReplayBuffer([Link]):
def test_capacity(self):
buf = ReplayBuffer(capacity=5)
for i in range(10):
[Link](Transition([0.0]*STATE_DIM, 0, 0.0, [0.0]*STATE_DIM, False))
[Link](len(buf), 5)
def test_sample_size(self):
buf = ReplayBuffer()
for i in range(20):
[Link](Transition([float(i)]*STATE_DIM, i % NUM_ACTIONS, float(i),
[0.0]*STATE_DIM, False))
batch = [Link](10)
61
[Link](len(batch), 10)
def test_sample_smaller_than_buffer(self):
buf = ReplayBuffer()
[Link](Transition([0.0]*STATE_DIM, 0, 0.0, [0.0]*STATE_DIM, False))
batch = [Link](100)
[Link](len(batch), 1)
class TestRewardFunction([Link]):
def test_breach_is_heavily_penalised(self):
r = compute_reward(ACTION_ALLOW, "malicious")
[Link](r, -10)
def test_correct_block_rewarded(self):
r = compute_reward(ACTION_BLOCK, "scan")
[Link](r, 5)
def test_false_positive_penalised(self):
r = compute_reward(ACTION_BLOCK, "safe")
[Link](r, 0)
def test_correct_allow_rewarded(self):
r = compute_reward(ACTION_ALLOW, "safe")
[Link](r, 0)
def test_quarantine_on_malicious(self):
r = compute_reward(ACTION_QUARANTINE, "malicious")
[Link](r, 5)
class TestDQNAgent([Link]):
62
def setUp(self):
# Use a temp model path that doesn't exist
[Link] = DQNAgent(model_path="/tmp/test_dqn_weights",
epsilon=1.0, batch_size=8)
def test_select_action_range(self):
state = [0.1] * STATE_DIM
for _ in range(50):
a = [Link].select_action(state)
[Link](a, range(NUM_ACTIONS))
def test_store_and_train(self):
for _ in range(20):
t = Transition([0.1]*STATE_DIM, 0, 1.0, [0.1]*STATE_DIM, False)
[Link](t)
loss = [Link].train_step()
[Link](loss)
[Link](loss, 0)
def test_epsilon_decays(self):
[Link] = 1.0
for _ in range(200):
t = Transition([0.1]*STATE_DIM, 0, 1.0, [0.1]*STATE_DIM, False)
[Link](t)
for _ in range(100):
[Link].train_step()
[Link]([Link], 1.0)
def test_q_values_dict(self):
state = [0.0] * STATE_DIM
qv = [Link].q_values(state)
63
[Link](set([Link]()),
{"ALLOW","BLOCK","RATE_LIMIT","QUARANTINE","LOG_ONLY"})
#
───────────────────────────────────────────────────────
──────
# Feature extractor tests
#
───────────────────────────────────────────────────────
──────
class TestFeatureExtractor([Link]):
def setUp(self):
[Link] = FeatureExtractor()
def test_state_dim(self):
state = [Link].get_state("[Link]")
[Link](len(state), STATE_DIM)
def test_state_normalised(self):
self._make_scan("[Link]", 30)
state = [Link].get_state("[Link]")
for v in state:
[Link](v, 0.0)
[Link](v, 1.0 + 1e-6)
64
def test_scan_detection(self):
self._make_scan("[Link]", 25)
is_scan, _ = [Link]["[Link]"].is_scanning()
[Link](is_scan)
def test_normal_traffic_not_scan(self):
for port in [80, 443, 8080]:
ev = PacketEvent("[Link]", port, "tcp", "SYN", 500, success=True)
[Link].record_packet(ev)
is_scan, _ = [Link]["[Link]"].is_scanning()
[Link](is_scan)
def test_ground_truth_scan(self):
self._make_scan("[Link]", 25)
gt = [Link].ground_truth("[Link]")
[Link](gt, "scan")
def test_ground_truth_safe(self):
ev = PacketEvent("[Link]", 443, "tcp", "ACK", 1200, success=True)
[Link].record_packet(ev)
gt = [Link].ground_truth("[Link]", file_label="safe")
[Link](gt, "safe")
def test_file_features_injected(self):
file_feats = [0.9, 1.0, 1.0, 0.0, 0.0]
state = [Link].get_state("[Link]", file_features=file_feats)
[Link](state[10], 0.9)
[Link](state[11], 1.0)
[Link](state[12], 1.0)
65
#
───────────────────────────────────────────────────────
──────
# File scanner tests
#
───────────────────────────────────────────────────────
──────
class TestShannonEntropy([Link]):
def test_zero_entropy_uniform(self):
data = bytes([0x41] * 1000) # all 'A'
e = shannon_entropy(data)
[Link](e, 0.0)
def test_max_entropy_random(self):
import random as rnd
data = bytes([[Link](0, 255) for _ in range(10000)])
e = shannon_entropy(data)
[Link](e, 6.0)
def test_empty(self):
[Link](shannon_entropy(b""), 0.0)
class TestFileScanner([Link]):
def test_safe_pdf(self):
data = b"%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\n%%EOF"
result = scan_file(data, "[Link]")
[Link](result["verdict"], ("safe", "suspicious"))
[Link](len(result["features"]), 5)
def test_pdf_with_javascript(self):
66
def test_exe_detected(self):
# Minimal PE header
data = b"MZ" + b"\x00" * 58 + b"\x40\x00\x00\x00" + b"\x00" * 64
data += b"PE\x00\x00"
result = scan_file(data, "[Link]")
[Link](result["file_type"], "exe")
[Link](result["verdict"], ("suspicious", "malicious"))
def test_docx_no_macro(self):
import io, zipfile
buf = [Link]()
with [Link](buf, "w") as z:
[Link]("word/[Link]", "<w:document/>")
[Link]("[Content_Types].xml", '<Types xmlns="..."/>')
data = [Link]()
result = scan_file(data, "[Link]")
[Link](result["details"]["has_macro"])
def test_docx_with_macro(self):
import io, zipfile
buf = [Link]()
with [Link](buf, "w") as z:
[Link]("word/[Link]", b"vbaProject\x00" * 10)
[Link]("[Content_Types].xml", '<Types xmlns="..."/>')
data = [Link]()
result = scan_file(data, "macro_doc.docx")
[Link](result["details"]["has_macro"])
67
def test_pdf_with_embedded_exe(self):
data = b"%PDF-1.4\n" + b"\x00" * 200 + b"MZ\x00\x00" + b"\x00" * 100 + b"%%EOF"
result = scan_file(data, "[Link]")
[Link](result["details"]["has_embedded_exe"])
[Link](result["verdict"], ("suspicious", "malicious"))
def test_features_length(self):
data = b"%PDF-1.4 basic doc%%EOF"
result = scan_file(data, "[Link]")
[Link](len(result["features"]), 5)
for f in result["features"]:
[Link](f, 0.0)
[Link](f, 1.0 + 1e-6)
def test_risk_score_range(self):
data = b"MZ" + b"\x00" * 100
result = scan_file(data, "[Link]")
[Link](result["risk_score"], 0.0)
[Link](result["risk_score"], 1.0)
#
───────────────────────────────────────────────────────
──────
# Integration smoke test (no server required)
#
───────────────────────────────────────────────────────
──────
class TestIntegration([Link]):
def test_full_pipeline_port_scan(self):
68
"""Simulate a port scan going through the full feature + agent pipeline."""
ext = FeatureExtractor()
agent = DQNAgent(model_path="/tmp/integ_test", epsilon=0.0, batch_size=4)
src_ip = "[Link]"
state = ext.get_state(src_ip)
[Link](len(state), STATE_DIM)
action = agent.select_action(state)
gt = ext.ground_truth(src_ip)
reward = compute_reward(action, gt)
[Link](action, range(NUM_ACTIONS))
[Link](reward, float)
def test_full_pipeline_malicious_file(self):
"""Simulate a malicious PDF upload going through scanner + agent."""
ext = FeatureExtractor()
agent = DQNAgent(model_path="/tmp/integ_test2", epsilon=0.0, batch_size=4)
if __name__ == "__main__":
print("=" * 60)
print(" RL Firewall — Test Suite")
print("=" * 60)
loader = [Link]()
suite = [Link]([Link][__name__])
runner = [Link](verbosity=2)
result = [Link](suite)
[Link](0 if [Link]() else 1)
70
This Docker coding stores the programs as containers and starts the services:
version: "3.9"
services:
enforcer:
build:
context: .
dockerfile: docker/[Link]
container_name: rl-firewall-enforcer
ports:
- "5000:5000"
volumes:
- models:/app/models # persist trained weights
environment:
- PYTHONUNBUFFERED=1
healthcheck:
test: ["CMD", "curl", "-f", "[Link]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
restart: unless-stopped
dashboard:
build:
context: .
dockerfile: docker/[Link]
container_name: rl-firewall-dashboard
ports:
- "8080:8080"
depends_on:
enforcer:
condition: service_healthy
restart: unless-stopped
71
volumes:
models:
driver: local
72
import os
from [Link] import _file_policy_action, _decide
def test_file_policy_action():
assert _file_policy_action('exe', 'malicious') == 1 # BLOCK
assert _file_policy_action('txt', 'safe') == 4 # LOG_ONLY
assert _file_policy_action('pdf', 'safe') == 0 # ALLOW
assert _file_policy_action('bin', 'suspicious') == 3 # QUARANTINE
def test_decide_file_policy_states():
state = [0.0] * 17
res = _decide('[Link]', state, 'malicious', {'type': 'file', 'file_type': 'exe', 'forced_action':
None})
assert res['action'] == 'BLOCK'
res = _decide('[Link]', state, 'safe', {'type': 'file', 'file_type': 'txt', 'forced_action': None})
assert res['action'] == 'LOG_ONLY'
res = _decide('[Link]', state, 'safe', {'type': 'file', 'file_type': 'pdf', 'forced_action': None})
assert res['action'] == 'ALLOW'
B. SNAPSHOTS
The following section documents the system's visual output and runtime behaviour as observed during
testing.
FUTURE ENHANCEMENTS:
Detect complex attack patterns such as zero-day attacks and Advanced Persistent Threats
(APT).
Integrate with SIEM tools for centralized monitoring and faster incident handling.
Provide detailed reports on detected threats, blocked IPs, and system performance.
CONCLUSION
The RL Dynamic Firewall successfully demonstrates that a Reinforcement Learning agent can
perform effective network security enforcement without manually authored rules. The DQN
agent, trained online from live traffic and file scan events, learns to distinguish port scanning
attacks and malicious file uploads from legitimate network activity, adapting its policy
continuously through experience replay and Bellman Q-value updates.
The prototype achieves its primary objectives: port scan detection (sequential, random, and slow
patterns) using rolling traffic statistics; malicious file classification using entropy analysis, macro
detection, and embedded executable scanning; online policy learning with epsilon decay from
full exploration (ε=1.0) toward exploitation (ε=0.05); full containerisation with one-command
deployment; and real-time monitoring through a WebSocket dashboard.
All 35 unit and integration tests pass, validating the correctness of the Q-network, replay buffer,
reward function, feature extractor, and file scanner components. The training scenarios confirm
that the agent's BLOCK rate increases after malicious traffic exposure and that its ALLOW rate
increases after normal traffic exposure, demonstrating correct reward-driven policy
improvement.
Future enhancements could include replacing the NumPy MLP with a PyTorch network to
enable GPU-accelerated training on larger replay buffers; integrating a real-time threat
intelligence feed for IP reputation lookup; extending the file scanner with YARA rule matching
for known malware families; and deploying a host-level enforcement agent that applies the
system's decisions directly to iptables rules.
80
BIBLIOGRAPHY
[1] Mnih, V., Kavukcuoglu, K., Silver, D., et al. "Human-level control through deep
reinforcement learning." Nature, 518(7540), 529–533, 2015.
[2] Sutton, R. S., & Barto, A. G. Reinforcement Learning: An Introduction (2nd ed.). MIT Press,
2018.
[3] Schaul, T., Quan, J., Antonoglou, I., & Silver, D. "Prioritized Experience Replay." ICLR,
2016.
[4] Buczak, A. L., & Guven, E. "A survey of data mining and machine learning methods for
cyber security intrusion detection." IEEE Communications Surveys & Tutorials, 18(2), 1153–
1176, 2016.
[5] Yin, C., Zhu, Y., Fei, J., & He, X. "A deep learning approach for intrusion detection using
recurrent neural networks." IEEE Access, 5, 21954–21961, 2017.
[6] Lopez-Martin, M., Carro, B., & Sanchez-Esguevillas, A. "Application of deep reinforcement
learning to intrusion detection for supervised problems." Expert Systems with Applications, 141,
112963, 2020.
[7] Cisco Systems. "2023 Cybersecurity Threat Trends Report." Cisco Talos Intelligence Group,
2023.
[8] Docker Inc. Docker Documentation — Compose file reference.
[Link] 2024.
[9] Flask Development Team. Flask Documentation (3.0.x). [Link]
2024.
[10] NumPy Development Team. NumPy Reference Documentation.
[Link] 2024.