Web Application Firewall (WAF)

Sentinel includes a built-in Web Application Firewall that inspects every incoming HTTP request for malicious payloads. It provides comprehensive coverage for the OWASP Top 10 attack categories, including SQL injection, cross-site scripting (XSS), path traversal, command injection, server-side request forgery (SSRF), XML external entity injection (XXE), local file inclusion (LFI), and open redirect attacks.

The WAF operates as Gin middleware registered by sentinel.Mount(). It runs before your application handlers, so malicious requests are intercepted before they reach your business logic. All detections are recorded as security events and are visible in the Sentinel dashboard.

Zero False Positives by Default

Sentinel ships with carefully tuned regex patterns at multiple strictness levels. Start with the defaults, observe traffic in ModeLog, and adjust sensitivity per category as needed for your application.

Enabling the WAF

The WAF is disabled by default. To enable it, set Enabled: true in your WAFConfig and choose a mode. The simplest configuration enables blocking mode with all default rule sensitivities:

main.gogo
1package main
2
3import (
4 sentinel "github.com/MUKE-coder/sentinel"
5 "github.com/gin-gonic/gin"
6)
7
8func main() {
9 r := gin.Default()
10
11 sentinel.Mount(r, nil, sentinel.Config{
12 WAF: sentinel.WAFConfig{
13 Enabled: true,
14 Mode: sentinel.ModeBlock,
15 },
16 })
17
18 r.GET("/api/data", func(c *gin.Context) {
19 c.JSON(200, gin.H{"status": "ok"})
20 })
21
22 r.Run(":8080")
23}

With this configuration, Sentinel inspects every request against all built-in rule categories at their default strictness levels. Malicious requests receive an HTTP 403 response and are logged as security events.

WAF Modes

The WAF supports two primary operating modes. The mode determines what happens when a malicious payload is detected.

ModeConstantBehaviorUse Case
Logsentinel.ModeLogDetects and records threats but allows the request to proceed. The event is logged and visible in the dashboard.Initial rollout, testing, auditing
Blocksentinel.ModeBlockDetects threats and rejects the request with HTTP 403 Forbidden. The response includes a JSON body with the block reason.Production enforcement

ModeLog (Detect Only)

In log mode, the WAF identifies malicious payloads and creates security events, but it does not block the request. This is ideal for initial deployment when you want to observe what the WAF detects without impacting real traffic.

1WAF: sentinel.WAFConfig{
2 Enabled: true,
3 Mode: sentinel.ModeLog, // Detect and log, but don't block
4}

When a threat is detected in log mode, the request continues to your handler normally. The detection is recorded and appears in the dashboard under the Events page with full details including the matched pattern, severity, and request metadata.

ModeBlock (Block Malicious Requests)

In block mode, detected threats are rejected immediately with an HTTP 403 status code. The request never reaches your application handler. The response body contains a JSON object describing the block:

1WAF: sentinel.WAFConfig{
2 Enabled: true,
3 Mode: sentinel.ModeBlock, // Detect and block with 403
4}
# Example blocked response
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Request blocked by WAF",
"reason": "SQL Injection detected",
"request_id": "abc123"
}

Recommended Rollout Strategy

Start with sentinel.ModeLog in production for 1-2 weeks to observe detections and identify any false positives. Review the dashboard, tune rule sensitivities and exclusions, then switch to sentinel.ModeBlock when you are confident in the configuration.

Built-in Rules

Sentinel ships with eight built-in detection rule categories that cover the most common web attack vectors. Each category has its own set of regex patterns and can be independently configured with a strictness level.

Rule Categories

CategoryFieldDefaultDescription
SQL InjectionSQLInjectionRuleStrictDetects SQL injection attempts including UNION-based, boolean-based, time-based blind, and error-based injection patterns.
Cross-Site ScriptingXSSRuleStrictDetects XSS payloads including script tags, event handlers (onerror, onload), javascript: URIs, and encoded variants.
Path TraversalPathTraversalRuleStrictDetects directory traversal sequences (../, ..%2f) targeting sensitive system files like /etc/passwd or web.config.
Command InjectionCommandInjectionRuleStrictDetects OS command injection via shell metacharacters (;, |, &&, backticks) and common commands (cat, wget, curl).
SSRFSSRFRuleMediumDetects server-side request forgery attempts targeting internal networks (127.0.0.1, 169.254.169.254, localhost, private IP ranges).
XXEXXERuleStrictDetects XML external entity injection via DOCTYPE declarations, ENTITY definitions, and SYSTEM identifiers in request bodies.
LFILFIRuleStrictDetects local file inclusion attempts targeting common sensitive paths (/etc/shadow, /proc/self, php://filter).
Open RedirectOpenRedirectRuleMediumDetects open redirect attempts via URL parameters containing external URLs or protocol-relative URLs (//evil.com).

Strictness Levels

Each rule category supports three strictness levels that control how aggressively patterns are matched. Higher strictness catches more edge cases but may produce more false positives for certain applications.

LevelConstantDescription
Offsentinel.RuleOffCompletely disables this rule category. No patterns are evaluated.
Basicsentinel.RuleBasicMatches only the most obvious and high-confidence attack patterns. Lowest false positive rate.
Strictsentinel.RuleStrictMatches a comprehensive set of patterns including encoded and obfuscated variants. Highest coverage, but may require exclusions for some applications.
config.gogo
1WAF: sentinel.WAFConfig{
2 Enabled: true,
3 Mode: sentinel.ModeBlock,
4 Rules: sentinel.RuleSet{
5 SQLInjection: sentinel.RuleStrict, // Maximum SQL injection coverage
6 XSS: sentinel.RuleStrict, // Maximum XSS coverage
7 PathTraversal: sentinel.RuleStrict, // Catch encoded traversal sequences
8 CommandInjection: sentinel.RuleBasic, // Basic only — reduces false positives for CLI tools
9 SSRF: sentinel.RuleMedium, // Balanced SSRF detection
10 XXE: sentinel.RuleStrict, // Full XXE protection
11 LFI: sentinel.RuleStrict, // Full LFI protection
12 OpenRedirect: sentinel.RuleOff, // Disabled — app handles redirects internally
13 },
14}

Disabling Rules

Setting a rule category to sentinel.RuleOff completely disables that detection class. Only do this if you are certain your application is not vulnerable to that attack vector, or if you have other defenses in place (e.g., a cloud WAF handling that category).

Custom Rules

In addition to the built-in rules, you can define custom rules to detect application-specific attack patterns. Custom rules are defined as WAFRule structs and are evaluated alongside the built-in rules during request inspection.

WAFRule Struct

FieldTypeDescription
IDstringUnique identifier for the rule. Used in event logs and the dashboard. Must be unique across all custom rules.
NamestringHuman-readable name displayed in the dashboard and event details.
PatternstringGo-compatible regular expression. Compiled once at startup for performance. Use raw strings (`...`) to avoid double-escaping.
AppliesTo[]stringWhich parts of the request to inspect. Valid values: "path", "query", "headers", "body". You can specify multiple.
SeveritySeveritySeverity level assigned to matches. Options: sentinel.SeverityLow, sentinel.SeverityMedium, sentinel.SeverityHigh, sentinel.SeverityCritical.
ActionstringAction to take when the pattern matches: "block" (reject with 403) or "log" (record but allow). This overrides the global WAF mode for this specific rule.
EnabledboolWhether the rule is active. Set to false to disable a rule without removing it from the configuration.

AppliesTo Targets

TargetWhat Is Inspected
"path"The URL path (e.g., /api/users/123)
"query"The raw query string (e.g., id=1&name=test)
"headers"All HTTP request headers concatenated (key: value pairs)
"body"The request body (for POST, PUT, PATCH requests)
config.gogo
1CustomRules: []sentinel.WAFRule{
2 {
3 ID: "block-wp-admin",
4 Name: "Block WordPress admin probes",
5 Pattern: `(?i)/(wp-admin|wp-login\.php|xmlrpc\.php|wp-content)`,
6 AppliesTo: []string{"path"},
7 Severity: sentinel.SeverityMedium,
8 Action: "block",
9 Enabled: true,
10 },
11 {
12 ID: "block-sensitive-files",
13 Name: "Block sensitive file access",
14 Pattern: `(?i)\.(env|git|bak|sql|log|ini|cfg|conf|swp)$`,
15 AppliesTo: []string{"path"},
16 Severity: sentinel.SeverityHigh,
17 Action: "block",
18 Enabled: true,
19 },
20 {
21 ID: "block-scanner-bots",
22 Name: "Block known scanner user agents",
23 Pattern: `(?i)(sqlmap|nikto|nessus|dirbuster|gobuster|masscan|nmap)`,
24 AppliesTo: []string{"headers"},
25 Severity: sentinel.SeverityHigh,
26 Action: "block",
27 Enabled: true,
28 },
29 {
30 ID: "log-api-key-exposure",
31 Name: "Detect API key in query string",
32 Pattern: `(?i)(api[_-]?key|apikey|secret|token|password)=.+`,
33 AppliesTo: []string{"query"},
34 Severity: sentinel.SeverityMedium,
35 Action: "log",
36 Enabled: true,
37 },
38}

Rule Action vs Global Mode

Each custom rule has its own Action field that overrides the global WAF mode for that specific rule. This means you can set the WAF to ModeLog globally but have individual custom rules that block, or vice versa. Built-in rules always follow the global mode.

Excluding Routes and IPs

You can exclude specific routes and IP addresses from WAF inspection. Excluded routes and IPs bypass the WAF entirely — no patterns are evaluated and no events are generated for these requests.

ExcludeRoutes

Use ExcludeRoutes to skip WAF inspection for specific URL paths. This is commonly used for health check endpoints, metrics endpoints, or routes that legitimately handle content that might trigger WAF rules (e.g., a CMS editor).

1WAF: sentinel.WAFConfig{
2 Enabled: true,
3 Mode: sentinel.ModeBlock,
4 ExcludeRoutes: []string{
5 "/health", // Health check endpoint
6 "/readiness", // Kubernetes readiness probe
7 "/metrics", // Prometheus metrics
8 "/api/v1/webhooks", // Incoming webhooks may contain arbitrary content
9 },
10}

ExcludeIPs

Use ExcludeIPs to skip WAF inspection for trusted IP addresses or CIDR ranges. This is useful for internal services, monitoring systems, or CI/CD pipelines that might trigger false positives.

1WAF: sentinel.WAFConfig{
2 Enabled: true,
3 Mode: sentinel.ModeBlock,
4 ExcludeIPs: []string{
5 "127.0.0.1", // Localhost
6 "10.0.0.0/8", // Internal network
7 "172.16.0.0/12", // Docker networks
8 "192.168.0.0/16", // Private network
9 "203.0.113.50", // Monitoring server
10 },
11}

Be Careful with IP Exclusions

Excluded IPs bypass all WAF rules including custom rules. Only exclude IPs you fully trust. If an attacker can spoof a trusted IP (e.g., via the X-Forwarded-For header), they could bypass the WAF. Make sure your reverse proxy sets the client IP correctly and that Gin is configured to trust the right proxy headers.

How Detection Works

Understanding the WAF detection pipeline helps you configure it effectively and debug false positives.

Request Inspection Flow

  1. Exclusion Check — The middleware first checks if the request IP or route is in the exclusion lists. If so, the request is passed through without inspection.
  2. Request Decomposition — The classifier extracts four components from the request: the URL path, the raw query string, all HTTP headers (concatenated), and the request body (if present).
  3. Pattern Matching — Each extracted component is evaluated against the compiled regex patterns for all enabled rule categories. The patterns are compiled once at startup for performance. Both built-in rules and custom rules are evaluated.
  4. Threat Event Creation — When a pattern matches, a ThreatEvent is created containing the rule ID, category, severity, matched pattern, the request component that matched, and full request metadata (IP, user agent, timestamp).
  5. Async Pipeline — The threat event is sent to the async event pipeline via a non-blocking ring buffer. Worker goroutines pick up the event and persist it to storage, update threat actor profiles, recompute security scores, and dispatch alerts if the severity threshold is met.
  6. Response — Based on the WAF mode (or the custom rule action), the request is either blocked with a 403 response or allowed to proceed to the next handler.
# The detection flow visualized:
#
# Request ──> Exclusion Check ──> Decompose Request ──> Pattern Matching
# │ │
# │ (excluded) ┌─────────┴─────────┐
# v v v
# Pass Through No Match Match Found
# │ │
# v v
# Pass Through Create ThreatEvent
# │
# ┌──────────┴──────────┐
# v v
# ModeLog ModeBlock
# │ │
# v v
# Log + Continue Log + 403

Non-Blocking Event Pipeline

The WAF detection and event logging are decoupled. Pattern matching happens synchronously in the request path, but event persistence, threat profiling, score computation, and alerting all happen asynchronously via the pipeline. This means the WAF adds minimal latency to your requests even under heavy attack traffic.

Testing the WAF

After enabling the WAF, you should test it to verify that it correctly detects and handles malicious requests. Below are curl commands you can use to test each major attack category.

Testing SQL Injection

# Classic SQL injection in query parameter
curl -v "http://localhost:8080/api/users?id=1'+OR+'1'='1"
# UNION-based SQL injection
curl -v "http://localhost:8080/api/users?id=1+UNION+SELECT+username,password+FROM+users--"
# Boolean-based blind injection
curl -v "http://localhost:8080/api/users?id=1+AND+1=1"
# SQL injection in POST body
curl -v -X POST http://localhost:8080/api/search \
-H "Content-Type: application/json" \
-d '{"query": "test\"; DROP TABLE users; --"}'

Testing XSS

# Script tag injection
curl -v "http://localhost:8080/api/search?q=<script>alert('xss')</script>"
# Event handler injection
curl -v "http://localhost:8080/api/search?q=<img+src=x+onerror=alert(1)>"
# JavaScript URI
curl -v "http://localhost:8080/api/search?q=javascript:alert(document.cookie)"

Testing Path Traversal

# Basic path traversal
curl -v "http://localhost:8080/api/files/../../../../etc/passwd"
# URL-encoded traversal
curl -v "http://localhost:8080/api/files/..%2f..%2f..%2fetc%2fpasswd"
# Double-encoded traversal
curl -v "http://localhost:8080/api/files/..%252f..%252f..%252fetc%252fpasswd"

Testing Command Injection

# Semicolon injection
curl -v "http://localhost:8080/api/ping?host=127.0.0.1;cat+/etc/passwd"
# Pipe injection
curl -v "http://localhost:8080/api/ping?host=127.0.0.1|whoami"
# Backtick injection
curl -v "http://localhost:8080/api/ping?host=\`whoami\`"

Expected Responses

The response you receive depends on the WAF mode:

ModeBlock Response (403 Forbidden)

$ curl -s "http://localhost:8080/api/users?id=1'+OR+'1'='1" | jq .
{
"error": "Request blocked by WAF",
"reason": "SQL Injection detected",
"request_id": "req_abc123def456"
}

ModeLog Response (200 OK, event logged)

$ curl -s "http://localhost:8080/api/users?id=1'+OR+'1'='1" | jq .
{
"users": []
}
# The request succeeds, but a WAF event is logged in the dashboard.
# Check the Sentinel Events page to see the detection.

Testing in Production

Only run these tests against development or staging environments. Running attack payloads against a production system could trigger alerts, IP bans, or lock out your own IP address if Auth Shield or IP reputation features are also enabled.

Custom Rule Examples

Below are practical custom rule examples for common use cases. These demonstrate the flexibility of the WAF rule engine for application-specific security needs.

Block WordPress and CMS Admin Probes

Bots constantly scan for common CMS admin panels. If your application is not WordPress, block these immediately to reduce noise.

1{
2 ID: "block-cms-probes",
3 Name: "Block CMS admin panel probes",
4 Pattern: `(?i)/(wp-admin|wp-login\.php|wp-content|wp-includes|xmlrpc\.php|administrator|phpmyadmin|adminer|cgi-bin)`,
5 AppliesTo: []string{"path"},
6 Severity: sentinel.SeverityMedium,
7 Action: "block",
8 Enabled: true,
9}

Block Sensitive File Extensions

Prevent access to backup files, configuration files, and version control artifacts that should never be served by your application.

1{
2 ID: "block-sensitive-extensions",
3 Name: "Block sensitive file extensions",
4 Pattern: `(?i)\.(env|git|gitignore|bak|sql|log|ini|cfg|conf|swp|old|orig|dist|save|DS_Store|htaccess|htpasswd)$`,
5 AppliesTo: []string{"path"},
6 Severity: sentinel.SeverityHigh,
7 Action: "block",
8 Enabled: true,
9}

Block Known Scanner User Agents

Automated security scanners identify themselves via their User-Agent header. Block known scanner signatures to reduce noise in your security logs.

1{
2 ID: "block-scanner-agents",
3 Name: "Block known vulnerability scanner agents",
4 Pattern: `(?i)(sqlmap|nikto|nessus|nmap|masscan|dirbuster|gobuster|wfuzz|ffuf|burpsuite|zap|acunetix|qualys|openvas)`,
5 AppliesTo: []string{"headers"},
6 Severity: sentinel.SeverityHigh,
7 Action: "block",
8 Enabled: true,
9}

Detect API Keys in Query Strings

API keys should be sent in headers, not query strings (which appear in logs and browser history). This rule logs a warning when credentials appear in URLs.

1{
2 ID: "detect-credentials-in-url",
3 Name: "Detect credentials in query string",
4 Pattern: `(?i)(api[_-]?key|apikey|secret[_-]?key|access[_-]?token|auth[_-]?token|password|passwd)=[^&]+`,
5 AppliesTo: []string{"query"},
6 Severity: sentinel.SeverityMedium,
7 Action: "log", // Log only — don't block, just alert
8 Enabled: true,
9}

Full Custom Rules Configuration

Here is a complete example combining multiple custom rules with the built-in rule set:

main.gogo
1sentinel.Mount(r, nil, sentinel.Config{
2 WAF: sentinel.WAFConfig{
3 Enabled: true,
4 Mode: sentinel.ModeBlock,
5 Rules: sentinel.RuleSet{
6 SQLInjection: sentinel.RuleStrict,
7 XSS: sentinel.RuleStrict,
8 PathTraversal: sentinel.RuleStrict,
9 CommandInjection: sentinel.RuleStrict,
10 SSRF: sentinel.RuleMedium,
11 XXE: sentinel.RuleStrict,
12 LFI: sentinel.RuleStrict,
13 OpenRedirect: sentinel.RuleMedium,
14 },
15 CustomRules: []sentinel.WAFRule{
16 {
17 ID: "block-cms-probes",
18 Name: "Block CMS admin panel probes",
19 Pattern: `(?i)/(wp-admin|wp-login\.php|xmlrpc\.php|phpmyadmin)`,
20 AppliesTo: []string{"path"},
21 Severity: sentinel.SeverityMedium,
22 Action: "block",
23 Enabled: true,
24 },
25 {
26 ID: "block-sensitive-extensions",
27 Name: "Block sensitive file extensions",
28 Pattern: `(?i)\.(env|git|bak|sql|log|swp)$`,
29 AppliesTo: []string{"path"},
30 Severity: sentinel.SeverityHigh,
31 Action: "block",
32 Enabled: true,
33 },
34 {
35 ID: "block-scanner-agents",
36 Name: "Block scanner user agents",
37 Pattern: `(?i)(sqlmap|nikto|nessus|dirbuster|gobuster|nmap)`,
38 AppliesTo: []string{"headers"},
39 Severity: sentinel.SeverityHigh,
40 Action: "block",
41 Enabled: true,
42 },
43 {
44 ID: "detect-credentials-in-url",
45 Name: "Detect credentials in query string",
46 Pattern: `(?i)(api[_-]?key|secret|token|password)=[^&]+`,
47 AppliesTo: []string{"query"},
48 Severity: sentinel.SeverityMedium,
49 Action: "log",
50 Enabled: true,
51 },
52 },
53 ExcludeRoutes: []string{"/health", "/readiness", "/metrics"},
54 ExcludeIPs: []string{"10.0.0.0/8", "172.16.0.0/12"},
55 },
56})

WAF Dashboard

The Sentinel dashboard includes a dedicated WAF page that provides a visual interface for managing and monitoring the Web Application Firewall.

Dashboard Capabilities

  • Rule Management — View all active built-in and custom rules. Enable or disable individual rules, and adjust strictness levels without restarting the application.
  • Live Input Testing — Test arbitrary input strings against the active rule set directly from the dashboard. The tester shows which rules would match, the severity, and what action would be taken.
  • Detection Statistics — View real-time stats on total detections, detections by category, top blocked IPs, most targeted routes, and detection trends over time.
  • Event Log — Browse all WAF events with filtering by category, severity, IP, time range, and rule ID. Each event shows the full request details and matched pattern.
  • Custom Rule Editor — Create, edit, and test custom rules directly from the dashboard UI without modifying Go code.

Access the WAF dashboard page at http://localhost:8080/sentinel/ui and navigate to the WAF section.

Dashboard Rule Changes

Rules created or modified via the dashboard are persisted to storage and take effect immediately. They are evaluated alongside rules defined in your Go configuration. If you restart the application, rules from the Go config are always re-applied, while dashboard-created rules are loaded from storage.

Next Steps

  • Rate Limiting — Layer rate limiting on top of WAF protection
  • Auth Shield — Protect authentication endpoints from brute force attacks
  • Anomaly Detection — Add behavioral analysis for advanced threat detection
  • Alerting — Get notified when the WAF detects high-severity threats
  • Dashboard — Explore the full security dashboard

Built with by JB