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.
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.
Project Goals
Before writing a single line of code, I defined what success looked like:
- Behavioral memory: The system must remember per-IP history across requests, not just evaluate each one in isolation.
- Adaptive response: Security responses must escalate automatically based on accumulated risk - warn first, then throttle, then block.
- Real-time visibility: A security dashboard must show live threat state without requiring a page refresh.
- Fail-open design: For a demo environment, the system should never take down the application. Log and escalate - don't crash.
- Zero external dependencies for core detection: No third-party threat-intel feeds. Pure logic, fully auditable.
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:
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:
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 |
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:
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.
First failed login attempt
POST /login with wrong credentials - failedLogins increments to 1. Login penalty adds +5 pts. Score: 5. Still LOW - request allowed.
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.
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.
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.
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
- Live request feed: Every analyzed request appears in a real-time event log with IP, path, method, detected attack type, risk score, and decision badge.
- IP Threat Map: A sortable table of all tracked IPs showing current risk level, total request count, attack event count, failed logins, and block status.
- Attack Type Breakdown: A donut chart showing the distribution of SQLi, XSS, auth abuse, and clean requests over the monitoring window.
- Score Timeline: A line chart of aggregate risk score over the past 30 minutes, making threat spikes visually obvious.
- Manual Override: Dashboard operators can manually unblock an IP, reset its behavioral history, or permanently escalate its risk level - useful for incident response.
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:
- Parameter context (URL query vs. POST body vs. path segment - different risk weights)
- Pattern complexity (a single keyword = low confidence; keyword + special chars + logical operator = high confidence)
- Encoding variants (detecting
%27,', and'as equivalent)
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:
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.
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.
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.
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.
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.
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
- Start with the data model, not the detection logic. I spent too long writing detection regexes before finalizing the behavioral store schema. The schema shapes everything - what you can score, how you decay, what you can display.
- Build the test harness first. Attack simulation and false-positive testing should have been automated from day one, not manually triggered. A proper test suite with real-world payload corpora would have caught calibration issues 10x faster.
- Add a plugin architecture for detectors. The SQLi and XSS detectors are currently coupled to the engine. A plugin interface would make adding new detection modules (path traversal, SSRF, XXE) much cleaner.
- Design for observability from the start. Adding structured logging and metrics after the fact is painful. Prometheus metrics, distributed tracing (OpenTelemetry), and structured JSON logs should be first-class concerns.
Production Readiness Considerations
Beyond the technical improvements above, production deployment requires:
- Penetration testing the RASP itself. Can an attacker identify that RASP is running and craft requests to avoid detection? The detection logic must be hardened against evasion, not just tested against naive payloads.
- Load testing the middleware overhead. The sub-2ms current latency needs validation under production request volumes (thousands of RPS) with realistic payload sizes.
- Legal and privacy review. Logging IP-level behavioral data has GDPR/CCPA implications. Retention policies, anonymization strategies, and data subject rights need addressing.
- Incident response playbook. Who gets paged when an IP hits CRITICAL? What's the escalation path? What's the false-positive remediation process? RASP is a tool - it needs a process around it.
- Integration with existing security tooling. A standalone RASP in isolation is less valuable than one feeding into a SIEM, correlating with threat intel feeds, and triggering SOAR playbooks.
Business Use Cases
The patterns demonstrated in Adaptive RASP apply across a range of real-world product scenarios:
- SaaS platforms with public-facing APIs: Detect and throttle API credential stuffing and enumeration attacks automatically, without manual IP blocklist management.
- E-commerce applications: Correlate failed payment attempts, account probing, and search abuse patterns to detect card testing bots before they cause financial damage.
- Healthcare portals: Flag unusual access patterns on patient record endpoints - repeated failed auth, unusual parameter structure - for HIPAA-relevant security monitoring.
- Financial services: Per-session behavioral tracking for anomalous trading or account access patterns, feeding into existing fraud detection pipelines.
- Developer platforms / CI-CD APIs: Detect secret scanning bots and automated probes on token endpoints based on behavioral signatures rather than just rate limits.
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
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.