Adaptive RASP: Building a Runtime Application Self-Protection System from Scratch

How I built a behavior-driven, adaptive security layer that inspects HTTP traffic in real time, scores risk per IP, and automatically escalates responses - no WAF, no third-party service, no black box.

Jun 10, 2026 18 min read Security Engineering

Most applications trust that their security perimeter will hold. They're wrong - and the statistics prove it. This is the engineering story behind Adaptive RASP, a project I built to explore what application self-defense actually looks like when you implement it from first principles.

Adaptive RASP is a Runtime Application Self-Protection system built on Node.js, Express, and React. It wraps a fictional rental platform called MyNest, inspecting every HTTP request in real time, assigning per-IP behavioral risk scores, detecting SQL Injection and Cross-Site Scripting patterns, and automatically escalating security responses when behavior becomes persistent. The result is a system that doesn't just detect threats - it adapts to them.

This article is a deep technical case study: why I built it, how it works, the engineering decisions I made, and what I'd do differently in Version 2.

The Problem with "Set It and Forget It" Security

Traditional web application security is fundamentally reactive. A firewall rule blocks a known bad IP. A WAF matches a known attack signature. A rate-limiter caps requests per minute. These tools are necessary - but they share a critical blind spot: they treat every request as stateless and make binary decisions based on a single snapshot of traffic.

The real world doesn't work that way. Attackers probe gradually. They mix malicious requests with legitimate-looking ones. They rotate payloads to avoid signature matching. They stay under rate-limit thresholds. A system that evaluates each request in isolation will miss the cumulative pattern that screams threat actor.

Real-world example: An attacker sends 5 failed login attempts, then a mild SQLi probe in a search parameter, then 3 more failed logins over 10 minutes. Each individual event looks borderline. Together, they paint a clear picture - one that a stateless firewall will never see.

This is the gap that motivated this project. I wanted to understand - and demonstrate - what it looks like to close it.

Why Traditional Security Approaches Fall Short

Let's be precise about what existing tools do and don't do:

What They Do Well

  • Block known-bad IPs and CVE-matched payloads
  • Terminate connections from flagged countries/ASNs
  • Rate-limit brute-force at the network edge
  • Enforce TLS and header security policies
  • Alert on static signature matches (SQLi, XSS)

What They Miss

  • Distributed slow attacks below rate thresholds
  • Context-aware behavioral patterns across sessions
  • Gradual escalation by a single actor over time
  • Correlation between failed logins and probe attempts
  • Application-layer business logic abuse

RASP's value proposition is that it lives inside the application runtime. It sees decoded requests after URL-parsing, after authentication, after session resolution - at the point where the application logic operates. A network-layer WAF sees raw TCP streams. RASP sees semantics.

Understanding Runtime Application Self-Protection (RASP)

RASP is a security technology that instruments the application itself - rather than sitting in front of it - to detect and block attacks at runtime. The term was coined by Gartner in 2014, but the concept has matured significantly in the years since.

At its core, RASP operates on a simple premise: the application knows things about its own context that a network device never can. It knows which user is logged in. It knows which database query is being constructed. It knows what the decoded parameter values are after deserialization. A RASP system exploits this contextual advantage.

Commercially, RASP agents exist for Java (Contrast Security, OpenRASP), Python, and .NET runtimes. They hook into framework internals - JDBC calls, template engines, file system access - and intercept dangerous operations at the point of execution. My implementation takes a higher-level approach, operating at the HTTP middleware layer rather than the bytecode level, which is both more portable and more instructive from an engineering-learning perspective.

Key distinction: A WAF asks "Does this request look suspicious?" A RASP system asks "Given everything I know about this client's history, what should I do with this request right now?"

Project Goals

Before writing a single line of code, I defined what success looked like:

These goals deliberately pushed against comfort. "Behavioral memory" means managing mutable in-memory state carefully. "Adaptive response" means designing a state machine, not just if/else chains. "Fail-open" means every error path needs explicit handling.

System Architecture

Adaptive RASP is organized into five distinct components with clear separation of concerns:

┌─────────────────────────────────────────────────────────────────┐ │ ADAPTIVE RASP - SYSTEM OVERVIEW │ └─────────────────────────────────────────────────────────────────┘ ┌──────────────────┐ HTTP Request ┌──────────────────────┐ │ User Application│ ──────────────────► │ RASP Agent │ │ (React + Vite) │ │ (Express Middleware) │ └──────────────────┘ └──────────┬───────────┘ │ Inspects & scores ▼ ┌──────────────────────────────────────────────────────────────┐ │ RASP ENGINE │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────────┐ │ │ │ SQLi Detector│ │ XSS Detector │ │ Behavioral Store │ │ │ │ (regex + heu)│ │ (regex + heu)│ │ (in-memory per-IP)│ │ │ └──────────────┘ └──────────────┘ └───────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Risk Score Calculator │ │ │ │ base_score + attack_weight + history_multiplier │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Response Decision Engine │ │ │ │ ALLOW → WARN → LOG → THROTTLE → BLOCK │ │ │ └──────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ │ │ │ Decision │ Events (SSE) ▼ ▼ ┌───────────────────────┐ ┌─────────────────────────┐ │ Protected Demo App │ │ Security Dashboard │ │ (MyNest – Rental │ │ (React + real-time │ │ Platform) │ │ SSE feed) │ └───────────────────────┘ └─────────────────────────┘

Component Breakdown

1. RASP Engine - The analytical core. It receives request context from the agent, runs detection algorithms, updates behavioral state, computes a risk score, and returns a security decision. It is intentionally stateful - the behavioral store is the engine's memory.

2. RASP Agent Middleware - An Express middleware layer inserted before application route handlers. It extracts request metadata (IP, headers, body, query params, path), passes it to the engine, and enforces the returned decision - blocking the request, adding warning headers, or letting it through transparently.

3. Security Dashboard - A React frontend that consumes a Server-Sent Events (SSE) stream from the engine. It displays live threat activity, per-IP risk scores, attack type breakdowns, and request timelines. No WebSockets, no polling - pure SSE for simplicity.

4. Protected Demo App (MyNest) - A fictional rental platform with login, property search, and listing endpoints. It exists to provide realistic attack surface for demonstration - SQL-queryable search fields, a login form, and browsable listing pages.

5. User Application - The React frontend for MyNest that end users interact with. It is entirely unaware of RASP operating beneath it.

Request Flow: From Browser to Security Decision

Understanding the exact path a request takes through the system is essential to understanding why RASP works the way it does. Here's the complete lifecycle:

CLIENT REQUEST │ ▼ [1] Express Router receives incoming HTTP request │ ▼ [2] RASP Agent Middleware intercepts BEFORE route handlers │ Extracts: { ip, method, path, headers, query, body } │ ▼ [3] RASP Engine.analyze(requestContext) │ ├──► [3a] Attack Pattern Detection │ • SQLi scan: regex + heuristic (url, body, headers) │ • XSS scan: regex + heuristic (url, body, headers) │ • Returns: { type, confidence, matchedPattern } │ ├──► [3b] Behavioral Store Lookup (keyed by IP) │ • Retrieve: requestCount, attackCount, lastSeen, │ failedLogins, riskLevel, blockUntil │ ├──► [3c] Risk Score Calculation │ • base = attack confidence weight │ • multiplier = f(attackCount, failedLogins) │ • decay = time-based cooldown factor │ • finalScore = base × multiplier × (1 - decay) │ ├──► [3d] State Update │ • Increment requestCount, attackCount │ • Update lastSeen timestamp │ • Apply score to riskLevel enum │ └──► [3e] Decision LOW (0–29) → ALLOW MED (30–59) → ALLOW + warning header HIGH (60–79) → LOG + 429 if repeated CRIT (80+) → BLOCK (403) + set blockUntil │ ▼ [4] Agent enforces decision: • ALLOW → next() - request continues normally • WARN → next() + X-RASP-Warning header injected • THROTTLE→ 429 Too Many Requests response • BLOCK → 403 Forbidden + JSON threat descriptor │ ▼ [5] SSE Event broadcast to Security Dashboard { ip, score, decision, attackType, timestamp } │ ▼ [6] Request reaches application route handler (if not blocked)

The entire pipeline adds under 2ms of overhead in typical operation - the detection regexes are compiled once at startup and the behavioral store is a simple in-memory Map, so lookups are O(1). For a production system, this would be backed by Redis for distributed deployments.

Adaptive Risk Scoring Explained

The risk scoring algorithm is the intellectual heart of the project. It deliberately avoids binary thinking and instead models threat level as a continuous, time-decaying, history-amplified signal.

Score Composition

Each incoming request generates a score from three additive components:

Component Formula Max Contribution Description
Base Attack Score confidence × weight 50 pts Severity of the detected attack pattern (SQLi = 40, XSS = 30, failed auth = 20)
History Multiplier 1 + (0.15 × attackCount) 3.0× Score is amplified for IPs with a history of prior attacks - persistence is penalized
Failed Login Penalty failedLogins × 5 +25 pts Failed auth attempts are correlated with attack probes to detect credential stuffing
Time Decay Factor e^(-λt) Reduces to 0 Historical score decays exponentially - a clean hour resets most accumulated risk

Risk Level Thresholds

Risk Level Score Range System Response Dashboard Color
LOW 0 – 29 Allow - no action Green
MEDIUM 30 – 59 Allow + inject X-RASP-Warning header Yellow
HIGH 60 – 79 Log event + return 429 on repeated requests Orange
CRITICAL 80+ Block (403) + set IP block window (15 min default) Red
Design insight: The history multiplier is the key innovation. A first-time SQLi probe that scores 40 base points becomes 46 points - worrying, but not blocking. The same attack from an IP with 10 prior attack events scores 40 × (1 + 1.5) = 100 - instant block. Persistence transforms the response.

Live Attack Escalation: A Step-by-Step Walkthrough

To make this concrete, here's how the system responds to a simulated attacker probing MyNest's search endpoint over 15 minutes:

T+0:00

Attacker sends normal search request

GET /search?q=apartment - score: 0. No pattern match. Request allowed. Behavioral record created for IP 192.168.1.105 with requestCount = 1.

T+0:45

First failed login attempt

POST /login with wrong credentials - failedLogins increments to 1. Login penalty adds +5 pts. Score: 5. Still LOW - request allowed.

T+2:10

SQLi probe detected in search parameter

GET /search?q=1'+OR+'1'%3D'1 - SQLi pattern matched, confidence 0.85. Base score: 34. No prior attacks so multiplier = 1.0. Final: 34. MEDIUM - request allowed but X-RASP-Warning: sql-injection-detected header injected. Event emitted to dashboard.

T+3:30

Three more failed logins

Failed login count now at 4. Penalty: +20 pts. Score on next request: 54. Dashboard updates IP risk badge to MEDIUM. Security analyst is alerted via live feed.

T+5:15

Second SQLi probe - escalation to HIGH

New variant: GET /search?q='; DROP TABLE listings;-- - attackCount = 2, multiplier = 1.3. Base 40 × 1.3 + failedLogin penalty 20 = 72. HIGH - 429 returned. Dashboard flashes HIGH alert for this IP.

T+8:00

Critical threshold reached - IP blocked

Third attack attempt: attackCount = 3, multiplier = 1.45. Score 40 × 1.45 + 20 = 78. Crosses 80 threshold on cumulative evaluation - CRITICAL. 403 Forbidden returned. blockUntil = now + 15 minutes set. All subsequent requests from this IP return 403 immediately without analysis. Dashboard shows RED block status.

The attacker spent 8 minutes probing - in a static WAF world, they might have continued for hours. RASP detected the behavioral pattern and automatically escalated to a hard block with zero human intervention.

Security Dashboard: Real-Time Threat Intelligence

The dashboard is the operational layer of the system - where a security team would monitor and respond. I built it in React with a Server-Sent Events connection to the RASP Engine.

Dashboard Capabilities

Why SSE over WebSockets? Server-Sent Events are unidirectional, HTTP/1.1 compatible, and don't require a separate handshake. For a monitoring dashboard where the server pushes events to clients (not the other way around), SSE is simpler, lighter, and perfectly sufficient. WebSockets would add complexity with no benefit here.

Technology Stack

Node.js + Express

Core server runtime. Express middleware pipeline is the insertion point for the RASP Agent.

React + Vite

Security Dashboard and MyNest User Application. Vite for fast HMR during development.

Server-Sent Events

Real-time push protocol from RASP Engine to Dashboard. No WebSocket complexity needed.

In-Memory Store (Map)

Behavioral state tracked per-IP using a Node.js Map. O(1) lookup, zero external dependencies.

Regex + Heuristics

Custom-built pattern library for SQLi and XSS detection. No external signature feeds.

Chart.js

Score timeline and attack distribution charts in the Security Dashboard.

Engineering Challenges

1. False Positive Management in Attack Detection

The hardest part of building any security detection system is calibrating sensitivity. A regex pattern like /UNION\s+SELECT/i catches obvious SQLi - but what about a legitimate search for "Select Union Jack flags"? I had to build a confidence scoring layer on top of raw pattern matching, factoring in:

Getting this tuned required building a test harness with both attack payloads from OWASP's testing guide and legitimate user inputs from real web traffic samples.

2. Memory Management for the Behavioral Store

An in-memory Map that tracks every client IP is a memory leak waiting to happen. In production, you'd use Redis with TTL-based expiry. For this project, I implemented a cleanup scheduler that periodically evicts IP records that haven't been seen in over 2 hours and whose risk score has decayed below the LOW threshold. This keeps memory bounded without sacrificing accuracy for active actors.

3. The Fail-Open Architecture Decision

If the RASP engine throws an unexpected exception - a regex catastrophic backtrack, an OOM error, a corrupted state - what happens to the protected application? I made the explicit decision to fail open: the middleware catches all engine errors, logs them, and calls next() so the application continues serving. This is the right choice for a demo environment. In production, the calculus changes - you might want fail-closed for high-security endpoints. This tradeoff needed a deliberate decision, not a default.

4. Real-Time Dashboard State Synchronization

The SSE stream delivers individual events, but the dashboard needs to reconstruct aggregate state (total blocked IPs, current risk distribution, score history). I built a local React state reducer that processes the SSE event stream and maintains a derived view - rather than re-fetching a full state snapshot on each event. This keeps the dashboard responsive even under high-volume attack simulations.

5. Time Decay Implementation

Implementing exponential decay for risk scores sounds simple - it's just e^(-λt). But the behavioral store doesn't recalculate continuously (that would require a background timer per IP). Instead, decay is calculated lazily on access: when an IP's record is retrieved, the current score is recalculated based on the time elapsed since the last event. This pattern (lazy evaluation with timestamp-based recalculation) is elegant but requires careful handling of the first-access edge case.

Design Decisions and Tradeoffs

Decision Choice Made Alternative Considered Rationale
Detection approach Regex + heuristics ML classifier Fully auditable, zero training data dependency, no inference latency
Behavioral storage In-memory Map Redis / Postgres Zero infrastructure dependency for MVP; abstraction layer ready for Redis migration
Real-time protocol Server-Sent Events WebSockets, polling Unidirectional push fits the use case perfectly; simpler than WebSockets
Error behavior Fail-open Fail-closed Demo environment; application availability prioritized over security maximalism
Risk model Additive with multiplier Probabilistic / Bayesian Simpler to tune and audit; Bayesian would require labeled training data
Block granularity Per-IP Per-session, per-user Most applicable at the network layer; session-level requires auth integration

Future Improvements & Version 2 Roadmap

Toward Production Readiness

Adaptive RASP in its current form is a proof-of-concept with intentional simplifications. Turning it into a production-grade system requires addressing these gaps:

Distributed State

Replace in-memory Map with Redis

A multi-instance Node.js deployment can't share in-memory state. Redis with per-IP TTL keys and atomic increment operations (INCR, EXPIRE) solves this. The abstraction layer is already designed for this migration.

ML Enhancement

Anomaly detection as a second detection layer

Regex catches known patterns. A lightweight anomaly model (LSTM or Isolation Forest on request sequence features) could catch novel attack variants that don't match existing signatures. This would run asynchronously and feed back into the risk score over subsequent requests.

Session-Level Tracking

Integrate with authentication layer

Currently, blocking is IP-based. An attacker behind a CGNAT or VPN shares an IP with legitimate users. Session and user-ID-level tracking, integrated with the auth middleware, would allow more surgical responses.

Distributed Attack Detection

Coordinate across IP space

A coordinated botnet attack from 500 different IPs won't trigger per-IP thresholds. Adding aggregate signal analysis - high global attack rate, geolocation clustering, ASN concentration - would catch distributed attacks that per-IP RASP misses.

Structured Audit Logging

SIEM-ready event output

Every RASP decision should emit a structured JSON log event compatible with SIEM ingestion (Splunk, Elastic SIEM). This includes full request context, matched patterns, score breakdown, and decision rationale - fully auditable for compliance.

Configurable Rule Engine

Runtime-configurable thresholds and patterns

Score thresholds, detection patterns, and block durations are currently code constants. A YAML/JSON rule configuration layer would let security teams tune behavior without code deploys - essential for operational use.

What I'd Do Differently in Version 2

Production Readiness Considerations

Important: Adaptive RASP is a portfolio and learning project. The following considerations would be mandatory before deploying any RASP system in a production environment.

Beyond the technical improvements above, production deployment requires:

Business Use Cases

The patterns demonstrated in Adaptive RASP apply across a range of real-world product scenarios:

Key Learnings

Building Adaptive RASP taught me more about application security than any course or certification I've completed. The specific lessons that stuck:

Security is a systems problem, not a feature problem. You can't bolt it on - it has to be designed into the request lifecycle from the architecture stage.

Detection is the easy part. Matching a SQL injection payload with regex is straightforward. Calibrating sensitivity, managing false positives, handling encoding variants, building the scoring model, designing the response escalation - that's where 90% of the real work lives.

State management matters in security. Most of my distributed systems instincts lean toward stateless architectures. RASP forced me to think carefully about when statefulness is not just acceptable but required for the problem to be solvable.

Real-time observability changes operational dynamics. Building the SSE-powered dashboard showed me viscerally how much a real-time view changes what's possible in incident response. The gap between "logs you query after the fact" and "a live feed of threat events" is enormous in practice.

Fail-open vs. fail-closed is a business decision, not a technical one. I initially thought fail-open was obviously wrong for a security system. After thinking through the operational implications - a crashed RASP engine taking down the protected application - I understood why the answer depends entirely on the threat model and business context.

Final Thoughts

Adaptive RASP is not a production security product. It's a proof of concept, a learning vehicle, and a demonstration of a specific class of security thinking: that application self-defense, grounded in behavioral context and adaptive response, is both feasible and valuable at a scale that a single developer can prototype.

The system I built can detect two attack types, track behavioral history per-IP, adaptively escalate responses, and display all of that in real time. That's a meaningful subset of what commercial RASP products do - and building it from scratch gave me a working understanding of every component that I couldn't have gotten any other way.

If you're a security engineer or backend developer thinking about application-layer defenses, I'd encourage you to build something like this. Not to ship it - but to understand it. The process of designing detection logic, scoring models, state management, and response escalation will permanently change how you think about the attack surface of every application you build or audit.


Project Highlights

Real-Time Attack Detection

SQLi and XSS pattern detection with confidence scoring and encoding-variant awareness

Adaptive Risk Scoring

Time-decaying, history-amplified scoring model that rewards persistent attacker tracking

Behavioral Memory

Per-IP state machine with failed login correlation and attack history persistence

Automatic Escalation

Allow → Warn → Throttle → Block pipeline triggered by cumulative behavioral signals

Live SSE Dashboard

React dashboard with Server-Sent Events showing real-time threat events and IP risk maps

Full-Stack Implementation

Node.js engine, Express middleware, React dashboard - complete system built from scratch


Skills Demonstrated

Runtime Application Self-Protection (RASP) Node.js / Express React + Vite Middleware Architecture Behavioral Security SQLi Detection XSS Detection Adaptive Risk Scoring Server-Sent Events (SSE) State Machine Design Security System Design Real-Time Dashboards DevSecOps Threat Modeling Full-Stack Engineering Application Security

Let's Build Secure Systems Together

If you're a recruiter, engineering manager, or founder looking for a developer who thinks deeply about security, system design, and full-stack engineering - I'd love to connect. If you're building a product that needs this kind of thinking baked in from day one, let's talk.

Pratismith Gogoi - Security Engineer

Pratismith Gogoi

Software Developer & cybersecurity enthusiast. Full-stack at Phoenix Solutions; co-founder of MyNest (900+ clients).