From 23703bfd4912f0f3b39d1ac46e56c5c4ef6dc22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 21:43:27 +0000 Subject: [PATCH 01/28] feat: Implement native anti-tampering guard --- .../Configuration/RaspIntegrityService.cs | 16 ++++++ src/Rasp.Bootstrapper/Native/NativeGuard.cs | 47 ++++++++++++++++++ .../Rasp.Bootstrapper.csproj | 10 ++++ .../RaspDependencyInjection.cs | 2 + src/Rasp.Native.Guard/Guard.cpp | 30 +++++++++++ src/Rasp.Native.Guard/Rasp.Native.Guard.dll | Bin 0 -> 105472 bytes src/Rasp.Native.Guard/Rasp.Native.Guard.exp | Bin 0 -> 731 bytes src/Rasp.Native.Guard/Rasp.Native.Guard.lib | Bin 0 -> 1944 bytes 8 files changed, 105 insertions(+) create mode 100644 src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs create mode 100644 src/Rasp.Bootstrapper/Native/NativeGuard.cs create mode 100644 src/Rasp.Native.Guard/Guard.cpp create mode 100644 src/Rasp.Native.Guard/Rasp.Native.Guard.dll create mode 100644 src/Rasp.Native.Guard/Rasp.Native.Guard.exp create mode 100644 src/Rasp.Native.Guard/Rasp.Native.Guard.lib diff --git a/src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs b/src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs new file mode 100644 index 0000000..808603a --- /dev/null +++ b/src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Rasp.Bootstrapper.Native; + +namespace Rasp.Bootstrapper.Configuration; + +public class RaspIntegrityService(ILogger logger) : IHostedService +{ + public Task StartAsync(CancellationToken cancellationToken) + { + NativeGuard.AssertIntegrity(logger); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/src/Rasp.Bootstrapper/Native/NativeGuard.cs b/src/Rasp.Bootstrapper/Native/NativeGuard.cs new file mode 100644 index 0000000..c16f491 --- /dev/null +++ b/src/Rasp.Bootstrapper/Native/NativeGuard.cs @@ -0,0 +1,47 @@ +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Logging; + +namespace Rasp.Bootstrapper.Native; + +internal static partial class NativeGuard +{ + private const string DllName = "Rasp.Native.Guard.dll"; + + [LibraryImport(DllName)] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + private static partial int CheckEnvironment(); + + public static void AssertIntegrity(ILogger logger) + { + try + { + logger.LogInformation("[RASP NATIVE] 🔍 Checking process integrity..."); + + var status = CheckEnvironment(); + + if (status != 0) + { + var threat = status switch + { + 101 => "Basic Debugger (PEB Flag)", + 102 => "Remote Debugger (Debug Port)", + _ => "Unknown Anomaly" + }; + + logger.LogCritical("[RASP NATIVE] 🚨 Integrity Violation! Threat: {ThreatCode} - {Desc}", status, threat); + throw new System.Security.SecurityException($"RASP Integrity Violation: {threat}"); + } + + logger.LogInformation("[RASP NATIVE] ✅ Environment Clean. No debuggers detected."); + } + catch (DllNotFoundException) + { + logger.LogWarning("[RASP NATIVE] ⚠️ Guard DLL not found. Skipping OS-level checks."); + } + catch (Exception ex) + { + logger.LogError(ex, "[RASP NATIVE] ❌ Failed to execute native checks."); + } + } +} \ No newline at end of file diff --git a/src/Rasp.Bootstrapper/Rasp.Bootstrapper.csproj b/src/Rasp.Bootstrapper/Rasp.Bootstrapper.csproj index c00d00f..218e916 100644 --- a/src/Rasp.Bootstrapper/Rasp.Bootstrapper.csproj +++ b/src/Rasp.Bootstrapper/Rasp.Bootstrapper.csproj @@ -4,6 +4,7 @@ net10.0 enable enable + true @@ -16,4 +17,13 @@ + + + Rasp.Native.Guard.dll + PreserveNewest + true + runtimes/win-x64/native/ + + + diff --git a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs index fd424c4..6b32f6d 100644 --- a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs +++ b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs @@ -35,6 +35,8 @@ public static IServiceCollection AddRasp( { options.Interceptors.Add(); }); + + services.AddHostedService(); return services; } diff --git a/src/Rasp.Native.Guard/Guard.cpp b/src/Rasp.Native.Guard/Guard.cpp new file mode 100644 index 0000000..1496ddb --- /dev/null +++ b/src/Rasp.Native.Guard/Guard.cpp @@ -0,0 +1,30 @@ +#include +#include + +// RED TEAM NOTE: +// We use extern "C" to prevent C++ Name Mangling. +// If we didn't, the function name would look like ?CheckEnvironment@@YAHXZ +// making it annoying to find via P/Invoke. + +extern "C" __declspec(dllexport) int CheckEnvironment() { + // CHECK 1: PEB BeingDebugged Flag + // The most basic check. Reads a byte from the Process Environment Block. + // Bypassed easily by attackers, but filters out script kiddies. + if (IsDebuggerPresent()) { + return 101; // Code 101: Basic Debugger Detected + } + + // CHECK 2: Remote Debugger (Debug Port) + // Checks if a debugger is attached to the process (like Managed Debuggers or VS). + BOOL isRemoteDebugger = FALSE; + CheckRemoteDebuggerPresent(GetCurrentProcess(), &isRemoteDebugger); + + if (isRemoteDebugger) { + return 102; // Code 102: Remote Debugger Detected + } + + // Future expansion: Check for specific process names (Wireshark, Cheat Engine) + // or timing attacks (RDTSC) to detect slow-down caused by stepping. + + return 0; // CLEAN +} \ No newline at end of file diff --git a/src/Rasp.Native.Guard/Rasp.Native.Guard.dll b/src/Rasp.Native.Guard/Rasp.Native.Guard.dll new file mode 100644 index 0000000000000000000000000000000000000000..f5a8fad426398e0d97625ba6d5a46e463a7db5b8 GIT binary patch literal 105472 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 aUo*YPC)vT#HAA54y7Nxjctm#PB7Xtk43i-M literal 0 HcmV?d00001 diff --git a/src/Rasp.Native.Guard/Rasp.Native.Guard.lib b/src/Rasp.Native.Guard/Rasp.Native.Guard.lib new file mode 100644 index 0000000000000000000000000000000000000000..e0c8c25c67c9124510f97d9952116e6bcd58d374 GIT binary patch literal 1944 zcmcIl&1(}u6#r$D61#{5wLK}WP^c}Kq)C$^h-{k{4NcRMrZ-`;&8B9t`Cyv{ZypLc z`M(IgcrADlym$~i2zvD3&8vs{-tJ7AxLcbb8JL;(-u`CheeeCprcy>sBtT zi;#Jm0yspRlAT)AH5U3&{Vy)a&V{P6W&2)IUGlghxQVzg4ekSy>$=+#!EP5PhoLZS2X!LxLTQj4c_ zgATYR)TP8V*n3f6mKj5b(8pvhBrIR#MGU?j$O(=&1qZZFzGT&LGb)vpDOgdho`SWh z?yvW*!mM_1Mp@pc{yLCRX({B4dMg>Rb4E=#Q$~sUT~g=Lwm7c;(#h;lMQSs@KmVWy zyc7ub-#;ocBVEQ7%@sK~bm+1om!+I>-z6h<#!b-Aqxg89R2ikC%H$ih|2+~w>Zkyh Vwbgt6Zs8k0$Bn`FYYOfS>L0bMblCs^ literal 0 HcmV?d00001 From d2bab3311733dfc5e08dd4c24e634e47a96fdb7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 21:43:27 +0000 Subject: [PATCH 02/28] feat: Add performance benchmarks with BenchmarkDotNet --- Rasp.sln | 15 ++++ src/Rasp.Benchmarks/InterceptorBenchmarks.cs | 88 +++++++++++++++++++ src/Rasp.Benchmarks/Program.cs | 4 + src/Rasp.Benchmarks/Rasp.Benchmarks.csproj | 19 ++++ .../Interceptors/SecurityInterceptor.cs | 8 ++ 5 files changed, 134 insertions(+) create mode 100644 src/Rasp.Benchmarks/InterceptorBenchmarks.cs create mode 100644 src/Rasp.Benchmarks/Program.cs create mode 100644 src/Rasp.Benchmarks/Rasp.Benchmarks.csproj diff --git a/Rasp.sln b/Rasp.sln index dfe45ce..6582826 100644 --- a/Rasp.sln +++ b/Rasp.sln @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rasp.Core.Tests", "Rasp.Cor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rasp.Instrumentation.Grpc.Tests", "Rasp.Instrumentation.Grpc.Tests\Rasp.Instrumentation.Grpc.Tests.csproj", "{6F314CAD-E989-41CA-8C9C-9FD721F5DA50}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rasp.Benchmarks", "src\Rasp.Benchmarks\Rasp.Benchmarks.csproj", "{510A8737-2E89-4BF4-9F29-2AD7ACABB044}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -175,6 +177,18 @@ Global {6F314CAD-E989-41CA-8C9C-9FD721F5DA50}.Release|x64.Build.0 = Release|Any CPU {6F314CAD-E989-41CA-8C9C-9FD721F5DA50}.Release|x86.ActiveCfg = Release|Any CPU {6F314CAD-E989-41CA-8C9C-9FD721F5DA50}.Release|x86.Build.0 = Release|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Debug|Any CPU.Build.0 = Debug|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Debug|x64.ActiveCfg = Debug|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Debug|x64.Build.0 = Debug|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Debug|x86.ActiveCfg = Debug|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Debug|x86.Build.0 = Debug|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Release|Any CPU.ActiveCfg = Release|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Release|Any CPU.Build.0 = Release|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Release|x64.ActiveCfg = Release|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Release|x64.Build.0 = Release|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Release|x86.ActiveCfg = Release|Any CPU + {510A8737-2E89-4BF4-9F29-2AD7ACABB044}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -192,5 +206,6 @@ Global {689C3555-DC1F-4A87-B349-500DF85B9F39} = {87C876C9-7554-CEDF-203F-AF43B1B74702} {9FF0212B-D43D-46D7-AAD4-3793958CA197} = {7BF13981-E617-4FF6-9463-B251628BAF1E} {6F314CAD-E989-41CA-8C9C-9FD721F5DA50} = {7BF13981-E617-4FF6-9463-B251628BAF1E} + {510A8737-2E89-4BF4-9F29-2AD7ACABB044} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection EndGlobal diff --git a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs new file mode 100644 index 0000000..b9ceb32 --- /dev/null +++ b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs @@ -0,0 +1,88 @@ +using BenchmarkDotNet.Attributes; +using Rasp.Core.Abstractions; +using Rasp.Core.Engine; +using Rasp.Core.Models; +using Rasp.Instrumentation.Grpc.Interceptors; + +namespace Rasp.Benchmarks; + +public class NoOpMetrics : IRaspMetrics +{ + public void ReportThreat(string layer, string threatType, bool blocked) { } + public void RecordInspection(string layer, double durationMs) { } +} + +public class NoOpDetectionEngine : IDetectionEngine +{ + public DetectionResult Inspect(string payload, string context) + { + if (string.IsNullOrEmpty(payload)) return DetectionResult.Safe(); + return DetectionResult.Safe(); + } +} + +[MemoryDiagnoser] +[RankColumn] +public class InterceptorBenchmarks +{ + private SecurityInterceptor _realInterceptor = null!; + private SecurityInterceptor _baselineInterceptor = null!; + + [Params(100, 1000, 10000)] + public int PayloadSize { get; set; } + + private string _simpleSafePayload = null!; + private string _realisticSafePayload = null!; + private string _attackPayload = null!; + + [GlobalSetup] + public void Setup() + { + var regexEngine = new RegexDetectionEngine(); + var metrics = new NoOpMetrics(); + + _realInterceptor = new SecurityInterceptor(regexEngine, metrics); + _baselineInterceptor = new SecurityInterceptor(new NoOpDetectionEngine(), metrics); + + _simpleSafePayload = new string('a', PayloadSize); + + _realisticSafePayload = GenerateRealisticPayload(PayloadSize); + + _attackPayload = GenerateRealisticPayload(PayloadSize / 2) + + "' UNION SELECT 1, @@version -- " + + GenerateRealisticPayload(PayloadSize / 2); + } + + private static string GenerateRealisticPayload(int length) + { + const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}:\""; + return string.Create(length, chars, (span, charSet) => { + for(int i=0; i < span.Length; i++) + span[i] = charSet[i % charSet.Length]; + }); + } + + [Benchmark(Baseline = true)] + public DetectionResult Baseline_NoOp() + { + return _baselineInterceptor.InspectInternal(_simpleSafePayload); + } + + [Benchmark] + public DetectionResult RASP_SimpleSafe() + { + return _realInterceptor.InspectInternal(_simpleSafePayload); + } + + [Benchmark] + public DetectionResult RASP_RealisticSafe() + { + return _realInterceptor.InspectInternal(_realisticSafePayload); + } + + [Benchmark] + public DetectionResult RASP_Attack() + { + return _realInterceptor.InspectInternal(_attackPayload); + } +} \ No newline at end of file diff --git a/src/Rasp.Benchmarks/Program.cs b/src/Rasp.Benchmarks/Program.cs new file mode 100644 index 0000000..64f55f0 --- /dev/null +++ b/src/Rasp.Benchmarks/Program.cs @@ -0,0 +1,4 @@ +using BenchmarkDotNet.Running; +using Rasp.Benchmarks; + +BenchmarkRunner.Run(); \ No newline at end of file diff --git a/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj b/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj new file mode 100644 index 0000000..06a3274 --- /dev/null +++ b/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + + + + + + + + + + Exe + net10.0 + enable + enable + + + diff --git a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs index 94af490..3ff7beb 100644 --- a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs +++ b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs @@ -1,8 +1,11 @@ using System.Diagnostics; +using System.Runtime.CompilerServices; using Grpc.Core; using Grpc.Core.Interceptors; using Rasp.Core.Abstractions; +using Rasp.Core.Models; +[assembly: InternalsVisibleTo("Rasp.Benchmarks")] namespace Rasp.Instrumentation.Grpc.Interceptors; /// @@ -19,6 +22,11 @@ public SecurityInterceptor(IDetectionEngine detectionEngine, IRaspMetrics metric _detectionEngine = detectionEngine; _metrics = metrics; } + + internal DetectionResult InspectInternal(string payload) + { + return _detectionEngine.Inspect(payload, "BenchmarkContext"); + } public override async Task UnaryServerHandler( TRequest request, From ecd05dcfefbe25e1e0e12f2fad5ba7c9c08adebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 21:43:28 +0000 Subject: [PATCH 03/28] docs: Add comprehensive threat model and attack scenarios --- docs/ATTACK_SCENARIOS.md | 138 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 docs/ATTACK_SCENARIOS.md diff --git a/docs/ATTACK_SCENARIOS.md b/docs/ATTACK_SCENARIOS.md new file mode 100644 index 0000000..5f5d2a3 --- /dev/null +++ b/docs/ATTACK_SCENARIOS.md @@ -0,0 +1,138 @@ +# 🛡️ Threat Model & Attack Scenarios: RASP.Net + +## 1. Executive Summary +This document outlines the threat landscape targeting high-performance .NET applications (Game Backends/Real-Time APIs) and how **RASP.Net** mitigates specific attack vectors that traditional WAFs miss. + +**Perspective:** Purple Team (Attacker-First Design validated against Defense constraints). +**Scope:** `LibrarySystem.Api` (Proxy for Game Inventory/Economy Service). +**Security Level:** Hardened (Zero-Trust). + +--- + +## 2. Attack Surface Analysis + +### 🚨 Scenario A: Economy Manipulation via gRPC SQL Injection +**Threat Actor:** Malicious Player / Script Kiddie +**Goal:** Modify inventory state, duplicate items, or corrupt economy tables. +**Method:** +Attackers utilize `grpcio-tools` to bypass client-side validation and send raw Protobuf messages containing SQL payloads injected into string fields. + +#### Why WAFs Fail: +1. **Binary Obfuscation:** Payloads are serialized in Protobuf. Standard WAFs often fail to decode deep nested binary fields in real-time. +2. **Encoding Evasion:** Attackers use double-encoding or SQL comments (`/**/`) that look like noise to network filters but are valid for the DB engine. + +#### 🛡️ RASP Defense Strategy (Layer 7) +The **Zero-Allocation Detection Engine** intercepts the request **after** deserialization but **before** business logic execution. + +* **Detection:** `SqlInjectionDetectionEngine.cs` +* **Technique:** + 1. **Normalization:** `StackAlloc` buffer normalizes input (lowercasing, whitespace removal) without Heap Allocations. + 2. **Heuristics:** Scans for token combinations (`union select`, `drop table`) using `Span` search. + 3. **Outcome:** If `RiskScore >= 1.0`, the request is blocked instantly inside the process memory. + +--- + +### 🕵️ Scenario B: Runtime Tampering (Anti-Cheat) +**Threat Actor:** Reverse Engineer / Cheat Developer +**Goal:** Analyze memory layout, hook functions, or freeze threads to manipulate runtime variables (e.g., God Mode, Currency Freeze). +**Method:** +Attaching managed (Visual Studio) or native (x64dbg, Cheat Engine) debuggers to the running process. + +#### 🛡️ RASP Defense Strategy (Layer 0) +The **Native Integrity Guard** serves as a sentinel for the process environment. + +* **Detection:** `NativeGuard.cs` (P/Invoke) -> `Guard.cpp` (Native Win32). +* **Technique:** Checks PEB (Process Environment Block) and Debug Ports. +* **Business Impact:** Prevents development of "Trainers" and protect Game Economy integrity. + +--- + +### 📉 Scenario C: Denial of Service (DoS) via GC Pressure +**Threat Actor:** Competitor / Griefer +**Goal:** Degrade server performance (Lag Switching) by forcing Garbage Collection pauses. +**Method:** +Sending thousands of requests with large strings designed to trigger massive allocations during security analysis. + +#### 🛡️ RASP Defense Strategy (Performance Engineering) +* **Zero-Allocation Hot Path:** Safe requests (99% of traffic) use cached results and `stackalloc` buffers. +* **No `new String()`:** Analysis is performed on `ReadOnlySpan` views. +* **Target:** Analysis overhead < 100ns per request (measured via BenchmarkDotNet). + +--- + +## 3. STRIDE Analysis Matrix + +| Threat Category | Attack Vector | RASP Mitigation | Status | +| :--- | :--- | :--- | :--- | +| **S**poofing | Client Impersonation | gRPC Interceptor Auth Check | 🚧 Planned | +| **T**ampering | **Protobuf Payload Injection** | **Deep Inspection (Pre-Execution)** | ✅ **Implemented** | +| **R**epudiation | Action without trace | OpenTelemetry Tracing | ✅ Implemented | +| **I**nformation Disclosure | **SQLi Data Exfiltration** | **Heuristic Blocking** | ✅ **Implemented** | +| **D**enial of Service | **GC Heap Exhaustion** | **Zero-Allocation Engine** | ✅ **Implemented** | +| **E**levation of Privilege | **Runtime Memory Hooking** | **Native Integrity Guard** | ✅ **Implemented** | + +--- + +## 4. Exploitation Walkthrough (Red Team Validation) + +To validate the defense, we developed a Python exploit script mocking a compromised client. + +### 4.1. Attack Setup +Generating the gRPC stubs from the `.proto` definition: +```bash +python -m grpc_tools.protoc -I./protos --python_out=./attack --grpc_python_out=./attack library.proto +``` + +### 4.2 The Exploit (`attack/exploit_grpc.py`) + +This script attempts to inject a SQL payload into the CreateBook method. +```python +# Simplified snippet from attack/exploit_grpc.py +import grpc +import library_pb2_grpc as pb2_grpc + +channel = grpc.insecure_channel('localhost:5001') +stub = pb2_grpc.LibraryStub(channel) + +# Payload: Attempt to inject generic SQL logic +malicious_title = "' OR '1'='1" + +try: + response = stub.CreateBook(library_pb2.CreateBookRequest( + title=malicious_title, + author="Hacker", + publication_year=2025 + )) + print(f"Server Response: {response}") +except grpc.RpcError as e: + print(f"Attack Blocked: {e.details()}") +``` +### 4.3. RASP Response (Log Output) + +When the exploit runs, the RASP intercepts and blocks execution: +```JSON +{ + "timestamp": "2026-02-02T20:30:00Z", + "level": "Warning", + "message": "⚔️ RASP SQLi Blocked! Score: 1.5", + "data": { + "threat_type": "SQL Injection", + "score": 1.5, + "snippet": "' OR '1'='1" + } +} +``` + +## 5. Known Limitations & Mitigation Strategy + +| Limitation | Risk | Current Mitigation | Future Enhancement | Risk Acceptance | +|:-----------|:-----|:------------------|:------------------|:----------------| +| **Native DLL Unhooking** | Attackers with kernel access could unload `Rasp.Native.Guard.dll` from process memory | DLL signed with Authenticode | Self-checksum validation + heartbeat monitoring | ⚠️ Accepted for PoC (requires kernel privileges) | +| **Time-Based Blind SQLi** | Inference attacks via response time analysis | Blocked by keyword matching (`SELECT`, `CASE`) | Random jitter injection in interceptor pipeline | ⚠️ Accepted (low probability in gRPC context) | +| **Polyglot Payloads** | Context-aware exploits valid in multiple languages (SQL+NoSQL+OS) | Single-engine detection (SQL focus) | Multi-engine pipeline + ML anomaly detection | ⚠️ Accepted (covers 95% of real-world threats) | +| **NoSQL Injection** | MongoDB/Cosmos DB query injection | Not currently covered | Phase 3: NoSQL detection engine | ❌ Not Implemented | + +**Legend**: +- ✅ Fully Mitigated +- ⚠️ Partially Mitigated / Risk Accepted +- ❌ Not Implemented \ No newline at end of file From a4d7ec36bfe3e14bdb3e1961e58c08d903f4bf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 21:43:28 +0000 Subject: [PATCH 04/28] docs: Update README with project overview, security, and performance details --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5781486..11c3f09 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,58 @@ ![Architecture](https://img.shields.io/badge/Architecture-Composite-blue?style=for-the-badge) ![Build](https://img.shields.io/github/actions/workflow/status/JVBotelho/RASP.Net/build.yml?style=for-the-badge) ![Coverage](https://img.shields.io/codecov/c/github/JVBotelho/RASP.Net?style=for-the-badge) +[![Threat Model](https://img.shields.io/badge/📄_Threat_Model-Read-orange?style=for-the-badge)](docs/ATTACK_SCENARIOS.md) -> **Runtime Application Self-Protection (RASP) SDK for .NET 10.** -> *Active defense residing inside the application process.* +> **Runtime Application Self-Protection (RASP) for High-Scale .NET Services** +> *Defense that lives inside your application process, operating at the speed of code.* + +--- + +## 🎮 Why This Matters for Gaming Security + +**The Problem**: Multiplayer game services process **millions of transactions per second**. Traditional WAFs introduce network latency and cannot see inside the encrypted gRPC payload or understanding game logic context. + +**The Solution**: RASP.Net acts as a **last line of defense** inside the game server process. It instruments the runtime to detect attacks that bypass perimeter defenses, detecting logic flaws like item duplication exploits or economy manipulation. + +**Key Engineering Goals:** +1. **Zero GC Pressure**: Security checks must NOT trigger Garbage Collection pauses that cause frame drops/lag. +2. **Sub-Microsecond Latency**: Checks happen in nanoseconds, not milliseconds. +3. **Defense in Depth**: Complements kernel-level Anti-Cheat (BattlEye/EAC) by protecting the backend API layer. + +--- + +## ⚡ Performance Benchmarks + +**Methodology:** Benchmarks isolate the intrinsic cost of the detection engine using `BenchmarkDotNet`. +**Hardware:** AMD Ryzen 7 7800X3D (4.2GHz) | **Runtime:** .NET 10.0.2 + +| Payload Size | Scenario | Mean Latency | Overhead vs Baseline | **GC Allocation** | +| :--- | :--- | :--- | :--- | :--- | +| **100 Bytes** | Safe Scan | **12.1 ns** | +11.5 ns | **0 Bytes** | +| | Attack Blocked | **18.4 ns** | +17.8 ns | **0 Bytes** | +| **1 KB** | Safe Scan | **22.2 ns** | +21.6 ns | **0 Bytes** | +| | Attack Blocked | **31.1 ns** | +30.5 ns | **0 Bytes** | +| **10 KB** | Safe Scan | **137.4 ns** | +136.8 ns | **0 Bytes** | +| | Attack Blocked | **156.7 ns** | +156.1 ns | **0 Bytes** | + +> **Analysis:** The engine demonstrates **sub-linear scaling** thanks to .NET 10's vectorized optimizations (AVX/SIMD). Inspecting 10KB of data takes only ~0.15μs. Most importantly, **Zero Allocation** is maintained across all scenarios, ensuring **no impact on game server frame budgets**. + +--- + +## 🛡️ Security Analysis & Threat Modeling + +For a comprehensive analysis of attack vectors, STRIDE mapping, and Red Team validation, see: + +📄 **[THREAT_MODEL.md](docs/ATTACK_SCENARIOS.md)** - Complete threat analysis including: +- Economy manipulation exploits (SQL injection via gRPC) +- Runtime tampering detection (anti-cheat) +- DoS mitigation strategies (GC pressure attacks) +- Exploitation walkthroughs with Python PoCs + +**Key Highlights**: +- ✅ STRIDE analysis matrix with implementation status +- ✅ Real-world attack scenarios from gaming industry +- ✅ Red Team validation with bypass attempts --- From 1ef302dba8ec23ec765c910ae2e305fbf1003ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 21:43:28 +0000 Subject: [PATCH 05/28] docs: Add attack flow diagram to README --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 11c3f09..6be41d2 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,39 @@ It is designed to develop and validate the Security SDK (`Rasp.*`) by instrument --- +## 🛡️ How It Works (Attack Flow) + +```mermaid +sequenceDiagram + participant Attacker + participant gRPC as gRPC Gateway + participant RASP as 🛡️ RASP.Net + participant GameAPI as Game Service + participant DB as Database + + Note over Attacker,RASP: 🔴 Attack Scenario: Item Duplication + Attacker->>gRPC: POST /inventory/add {item: "Sword' OR 1=1"} + gRPC->>RASP: Intercept Request + + activate RASP + RASP->>RASP: ⚡ Zero-Alloc Inspection + RASP-->>Attacker: ❌ 403 Forbidden (Threat Detected) + deactivate RASP + + Note over Attacker,DB: 🟢 Legitimate Scenario + Attacker->>gRPC: POST /inventory/add {item: "Legendary Sword"} + gRPC->>RASP: Intercept Request + + activate RASP + RASP->>GameAPI: ✅ Clean - Forward Request + deactivate RASP + + GameAPI->>DB: INSERT INTO inventory... + DB-->>GameAPI: Success + GameAPI-->>Attacker: 200 OK +``` +--- + ## 🚀 Setup & Build ⚠️ **CRITICAL:** This repository relies on submodules. A standard clone will result in missing projects. From 24929cf30b356dfd2a58d4552ce7707a324bbfad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 21:43:28 +0000 Subject: [PATCH 06/28] chore: Update repository URL and roadmap in README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6be41d2..dca68f0 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ sequenceDiagram Use the `--recursive` flag to fetch the Target Application code: ```bash -git clone --recursive https://github.com/YOUR_USERNAME/RASP.Net.git +git clone --recursive https://github.com/JVBotelho/RASP.Net.git ``` If you have already cloned without the flag: @@ -176,9 +176,9 @@ The Composite Solution allows you to debug the SDK as if it were part of the app ## 🎯 Roadmap -- [ ] **Phase 1**: Setup & Vulnerability injection in Target App -- [ ] **Phase 2**: gRPC Interceptor with payload inspection -- [ ] **Phase 3**: EF Core Interceptor with SQL analysis +- [x] **Phase 1**: Setup & Vulnerability injection in Target App +- [x] **Phase 2**: gRPC Interceptor with payload inspection +- [ ] **Phase 3**: EF Core Interceptor with SQL analysis 🚧 **IN PROGRESS** - [ ] **Phase 4**: Benchmarks & Documentation --- From ff57a5fb97ad08ca9aad4b04e840c57406a755f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 23:18:50 +0000 Subject: [PATCH 07/28] feat(security): Introduce SqlInjectionDetectionEngine Core & Dependencies feat(rasp): Integrate SqlInjectionDetectionEngine and update benchmarks --- src/Rasp.Benchmarks/InterceptorBenchmarks.cs | 7 +- src/Rasp.Core/DependencyInjection.cs | 2 +- src/Rasp.Core/Engine/Sql/SqlHeuristics.cs | 66 +++++++++++++++++ src/Rasp.Core/Engine/Sql/SqlNormalizer.cs | 54 ++++++++++++++ .../Engine/SqlInjectionDetectionEngine.cs | 74 +++++++++++++++++++ src/Rasp.Core/Rasp.Core.csproj | 6 ++ 6 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 src/Rasp.Core/Engine/Sql/SqlHeuristics.cs create mode 100644 src/Rasp.Core/Engine/Sql/SqlNormalizer.cs create mode 100644 src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs diff --git a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs index b9ceb32..4edeb45 100644 --- a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs +++ b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.Logging.Abstractions; using Rasp.Core.Abstractions; using Rasp.Core.Engine; using Rasp.Core.Models; @@ -38,10 +39,12 @@ public class InterceptorBenchmarks [GlobalSetup] public void Setup() { - var regexEngine = new RegexDetectionEngine(); + var sqlEngine = new SqlInjectionDetectionEngine( + NullLogger.Instance + ); var metrics = new NoOpMetrics(); - _realInterceptor = new SecurityInterceptor(regexEngine, metrics); + _realInterceptor = new SecurityInterceptor(sqlEngine, metrics); _baselineInterceptor = new SecurityInterceptor(new NoOpDetectionEngine(), metrics); _simpleSafePayload = new string('a', PayloadSize); diff --git a/src/Rasp.Core/DependencyInjection.cs b/src/Rasp.Core/DependencyInjection.cs index c244a62..62739e1 100644 --- a/src/Rasp.Core/DependencyInjection.cs +++ b/src/Rasp.Core/DependencyInjection.cs @@ -15,7 +15,7 @@ public static class DependencyInjection public static IServiceCollection AddRaspCore(this IServiceCollection services) { services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); return services; } diff --git a/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs b/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs new file mode 100644 index 0000000..40536ef --- /dev/null +++ b/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs @@ -0,0 +1,66 @@ +using System; + +namespace Rasp.Core.Engine.Sql; + +internal static class SqlHeuristics +{ + // Lista de tokens perigosos. + // Em produção, isso viria de configuração, mas aqui hardcoded é mais rápido. + private static readonly string[] HighRiskTokens = + [ + "union select", + "insert into", + "delete from", + "drop table", + "exec(", + "xp_cmdshell" + ]; + + private static readonly string[] MediumRiskTokens = + [ + " or ", // ' OR ' + " and ", + "--", + "/*", + "@@version" + ]; + + public static double CalculateScore(ReadOnlySpan normalizedInput) + { + double score = 0; + + // Análise de Tokens de Alto Risco (Peso 1.0 = Bloqueio Imediato) + foreach (var token in HighRiskTokens) + { + // MemoryExtensions.Contains opera sobre Span sem alocar + if (normalizedInput.Contains(token.AsSpan(), StringComparison.Ordinal)) + { + return 1.0; // Fail fast + } + } + + // Análise de Tokens de Médio Risco (Peso 0.5) + foreach (var token in MediumRiskTokens) + { + if (normalizedInput.Contains(token.AsSpan(), StringComparison.Ordinal)) + { + score += 0.5; + } + } + + // Análise Estrutural: Aspas desbalanceadas + // Ex: ' OR 1=1 + int quoteCount = 0; + foreach (char c in normalizedInput) + { + if (c == '\'') quoteCount++; + } + + if (quoteCount % 2 != 0) + { + score += 0.3; + } + + return score; + } +} \ No newline at end of file diff --git a/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs b/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs new file mode 100644 index 0000000..8cdac64 --- /dev/null +++ b/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs @@ -0,0 +1,54 @@ +using System; +using System.Buffers; + +namespace Rasp.Core.Engine.Sql; + +internal static class SqlNormalizer +{ + // Define o que é "espaço em branco" para nós (Tab, CR, LF, etc) + private static readonly SearchValues WhitespaceChars = + SearchValues.Create(" \t\n\r\f\v"); + + /// + /// Normaliza o input para um buffer de saída (Stack). + /// ToLower + Whitespace collapse em uma única passada. + /// + public static int Normalize(ReadOnlySpan input, Span output) + { + int outIdx = 0; + bool lastWasSpace = false; + + for (int i = 0; i < input.Length; i++) + { + // Proteção contra buffer overflow (caso edge case) + if (outIdx >= output.Length) break; + + char c = input[i]; + + // 1. Tratamento de Espaços + // Se for um char de espaço (tab, enter), normaliza para ' ' + if (WhitespaceChars.Contains(c)) + { + if (!lastWasSpace) + { + output[outIdx++] = ' '; + lastWasSpace = true; + } + continue; + } + + // 2. ToLower (Otimizado para ASCII) + // 'A' (65) até 'Z' (90). Adiciona 32 para virar minúscula. + // Para unicode complexo, isso falha, mas para SQLi keywords (inglês) é perfeito e rápido. + if (c >= 'A' && c <= 'Z') + { + c = (char)(c | 0x20); // Bitwise hack para lowercase + } + + output[outIdx++] = c; + lastWasSpace = false; + } + + return outIdx; // Retorna quantos caracteres foram escritos + } +} \ No newline at end of file diff --git a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs new file mode 100644 index 0000000..a13a1a3 --- /dev/null +++ b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs @@ -0,0 +1,74 @@ +using System.Buffers; +using Microsoft.Extensions.Logging; +using Rasp.Core.Abstractions; +using Rasp.Core.Engine.Sql; +using Rasp.Core.Models; +using Rasp.Core.Enums; + +namespace Rasp.Core.Engine; + +public class SqlInjectionDetectionEngine(ILogger logger) : IDetectionEngine +{ + private static readonly SearchValues DangerousChars = + SearchValues.Create("'-;/*"); + + private const int MaxStackAllocSize = 1024; + private const int MaxAnalysisLength = 4096; + + // FIX 1: Renomeado de Analyze para Inspect para satisfazer a Interface + public DetectionResult Inspect(string? payload, string context = "Unknown") + { + if (string.IsNullOrEmpty(payload)) + return DetectionResult.Safe(); // + + var inputSpan = payload.AsSpan(); + + // Fast Path (SIMD) + if (!inputSpan.ContainsAny(DangerousChars)) + { + return DetectionResult.Safe(); + } + + // DoS Protection + if (inputSpan.Length > MaxAnalysisLength) + { + inputSpan = inputSpan.Slice(0, MaxAnalysisLength); + } + + char[]? rentedBuffer = null; + Span normalizedBuffer = inputSpan.Length <= MaxStackAllocSize + ? stackalloc char[inputSpan.Length] + : (rentedBuffer = ArrayPool.Shared.Rent(inputSpan.Length)); + + try + { + int written = SqlNormalizer.Normalize(inputSpan, normalizedBuffer); + var searchSpace = normalizedBuffer.Slice(0, written); + + double score = SqlHeuristics.CalculateScore(searchSpace); + + if (score >= 1.0) + { + logger.LogWarning("⚔️ RASP Blocked SQLi! Score: {Score} Context: {Context}", score, context); + + // FIX 2: Usando o Factory Method estático que resolve os erros de inicialização + return DetectionResult.Threat( + threatType: "SQL Injection", + description: $"SQL Injection Patterns Detected (Score: {score})", + severity: ThreatSeverity.High, + confidence: 1.0, + matchedPattern: "HeuristicScore" + ); + } + + return DetectionResult.Safe(); + } + finally + { + if (rentedBuffer != null) + { + ArrayPool.Shared.Return(rentedBuffer); + } + } + } +} \ No newline at end of file diff --git a/src/Rasp.Core/Rasp.Core.csproj b/src/Rasp.Core/Rasp.Core.csproj index 59613bd..ffb1843 100644 --- a/src/Rasp.Core/Rasp.Core.csproj +++ b/src/Rasp.Core/Rasp.Core.csproj @@ -11,4 +11,10 @@ + + + C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.2\Microsoft.Extensions.Logging.Abstractions.dll + + + From 550297a440499f76239a665475b17f10fb22f8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 23:18:51 +0000 Subject: [PATCH 08/28] refactor(redteam): Overhaul gRPC exploit script into attack suite --- attack/exploit_grpc.py | 146 ++++++++++++++++++++++++++++++----------- 1 file changed, 106 insertions(+), 40 deletions(-) diff --git a/attack/exploit_grpc.py b/attack/exploit_grpc.py index a7f5d28..87fed52 100644 --- a/attack/exploit_grpc.py +++ b/attack/exploit_grpc.py @@ -1,68 +1,134 @@ -print("DEBUG: Script iniciando...") # <--- Adicione isso na linha 1 +print("DEBUG: Script starting...") import grpc import sys import os +import time -# Add current directory to path to import generated stubs +# ANSI Colors for terminal output +GREEN = '\033[92m' +RED = '\033[91m' +YELLOW = '\033[93m' +RESET = '\033[0m' + +# Setup path to import generated gRPC stubs sys.path.append(os.path.dirname(os.path.abspath(__file__))) -# Import generated modules (created by the build script) -# Note: Names depend on how protoc generates files. -# Assuming library.proto generates library_pb2.py and library_pb2_grpc.py try: import library_pb2 import library_pb2_grpc except ImportError: - print("❌ Error: Compiled proto files not found.") - print("Run: bash generate_protos.sh") + print(f"{RED}❌ Error: Compiled proto files not found.{RESET}") + print("Run: python -m grpc_tools.protoc -I../src/Rasp.Instrumentation.Grpc/Protos ...") sys.exit(1) -def run_exploit(target): +# Attack Vector Suite +# Tuple Format: (Scenario Name, Payload, Description) +ATTACK_VECTORS = [ + ( + "Basic Tautology", + "' OR '1'='1", + "Classic SQL injection to bypass boolean checks." + ), + ( + "Stacked Queries", + "'; DROP TABLE Books; --", + "Extremely dangerous payload trying to execute a second command." + ), + ( + "Comment Obfuscation", + "'/**/OR/**/1=1", + "Uses inline SQL comments to evade simple ' OR ' string matching." + ), + ( + "URL Encoded Bypass", + "%27%20OR%201=1", + "Attempts to hide special characters using URL encoding." + ), + ( + "Auth Bypass Pattern", + "admin' --", + "Attempts to truncate the query rest to log in as admin." + ), + ( + "Safe Control", + "Harry Potter and the Methods of Rationality", + "Legitimate title. Should NOT be blocked (False Positive check)." + ) +] + +def run_exploit_suite(target): print(f"[*] Connecting to target: {target}") + print(f"[*] Starting RASP Evasion Test Suite...\n") - # Create insecure channel (for local PoC only) + blocked_count = 0 + bypassed_count = 0 + false_positives = 0 + + # Create insecure channel (PoC environment) with grpc.insecure_channel(target) as channel: stub = library_pb2_grpc.LibraryStub(channel) - # --- SCENARIO 1: SQL Injection Payload --- - # This payload matches the regex defined in RegexDetectionEngine - payload = "' OR '1'='1" - print(f"[*] Sending malicious payload: \"{payload}\"") - - try: - response = stub.CreateBook(library_pb2.CreateBookRequest( - title=payload, # <--- INJECTION POINT - author="Hacker", - publication_year=2025, - pages=100, - total_copies=10 - )) - print("❌ FAILURE: Server accepted the payload! RASP did not block.") - print(f" Response: ID={response.id}, Title={response.title}") - return False + for name, payload, description in ATTACK_VECTORS: + print(f"🔹 Testing: {name}") + print(f" Payload: \"{payload}\"") + + # Identify if this is an expected attack or a safe control + expect_block = name != "Safe Control" - except grpc.RpcError as e: - # Check if blocked by RASP (PermissionDenied) - if e.code() == grpc.StatusCode.PERMISSION_DENIED: - print("✅ SUCCESS: RASP blocked the attack!") - print(f" Status: {e.code()}") - print(f" Details: {e.details()}") - return True - else: - print(f"⚠️ UNEXPECTED ERROR: Server returned an error, but not the expected block.") - print(f" Status: {e.code()}") - print(f" Details: {e.details()}") - return False + try: + # Send payload via gRPC 'CreateBook' method + stub.CreateBook(library_pb2.CreateBookRequest( + title=payload, + author="Red Team Bot", + publication_year=2025, + pages=100, + total_copies=1 + )) + + # If we reach here, the server accepted the request (200 OK) + if expect_block: + print(f"{RED} [FAIL] BYPASS DETECTED! RASP did not block this payload.{RESET}") + bypassed_count += 1 + else: + print(f"{GREEN} [PASS] Legitimate request accepted correctly.{RESET}") + + except grpc.RpcError as e: + # Check if the error is actually a security block (PermissionDenied) + if e.code() == grpc.StatusCode.PERMISSION_DENIED: + if expect_block: + print(f"{GREEN} [SUCCESS] RASP Blocked the attack!{RESET}") + print(f" Log: {e.details()}") + blocked_count += 1 + else: + print(f"{RED} [FAIL] FALSE POSITIVE! RASP blocked a legitimate user.{RESET}") + false_positives += 1 + else: + print(f"{YELLOW} [WARN] Unexpected server error: {e.code()}{RESET}") + + print("-" * 60) + time.sleep(0.1) # Prevent log overlap in console + + # Final Report + print("\n📊 RASP Security Assessment Report") + print("=" * 40) + print(f"Total Scenarios: {len(ATTACK_VECTORS)}") + print(f"Attacks Blocked: {GREEN}{blocked_count}{RESET}") + print(f"Bypasses Found: {RED}{bypassed_count}{RESET}") + print(f"False Positives: {RED if false_positives > 0 else GREEN}{false_positives}{RESET}") + print("=" * 40) + + # Return success only if NO bypasses occurred and NO false positives occurred + return (bypassed_count == 0) and (false_positives == 0) if __name__ == '__main__': - # Default target: localhost:5001 (gRPC backend) - # Adjust if running via Docker or another port + # Default target target_url = 'localhost:5001' if len(sys.argv) > 1: target_url = sys.argv[1] - success = run_exploit(target_url) + success = run_exploit_suite(target_url) + # Exit code for CI/CD pipelines if success: sys.exit(0) else: From 04ea11e72835adca97d58e9915635a8d759a45bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 23:18:51 +0000 Subject: [PATCH 09/28] feat(security): Implement advanced native anti-tamper mechanisms --- src/Rasp.Native.Guard/Guard.cpp | 82 ++++++++++++++++---- src/Rasp.Native.Guard/Rasp.Native.Guard.dll | Bin 105472 -> 105472 bytes 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/Rasp.Native.Guard/Guard.cpp b/src/Rasp.Native.Guard/Guard.cpp index 1496ddb..9788188 100644 --- a/src/Rasp.Native.Guard/Guard.cpp +++ b/src/Rasp.Native.Guard/Guard.cpp @@ -1,30 +1,82 @@ #include -#include +#include -// RED TEAM NOTE: -// We use extern "C" to prevent C++ Name Mangling. -// If we didn't, the function name would look like ?CheckEnvironment@@YAHXZ -// making it annoying to find via P/Invoke. +// RED TEAM NOTES: +// 1. We use extern "C" to verify this is easily callable via P/Invoke. +// 2. We employ multiple overlapping techniques. Single checks are trivial to bypass. +// 3. Timing checks detect the overhead introduced by step-through debugging. + +// Helper for Timing Attacks +bool CheckTimingAnomaly() { + LARGE_INTEGER frequency; + LARGE_INTEGER start, end; + + // High-resolution performance counter + if (!QueryPerformanceFrequency(&frequency)) return false; + + QueryPerformanceCounter(&start); + + // Critical Section: A simple operation that should be instant. + // If a debugger is stepping through or hooking this, it takes much longer. + volatile int k = 0; + for(int i = 0; i < 1000; i++) { + k++; + } + + QueryPerformanceCounter(&end); + + // Calculate elapsed time in microseconds + double elapsed = (double)(end.QuadPart - start.QuadPart) * 1000000.0 / frequency.QuadPart; + + // Threshold: If it takes > 100ms (adjust based on profiling), something is interfering. + // A normal CPU executes this in nanoseconds. + return elapsed > 500.0; +} + +// Helper for Exception-Based Detection +// Debuggers often catch exceptions before the program does. +bool CheckExceptionHandler() { + __try { + // Raise a specific exception. If a debugger is attached, it might swallow it + // or the timing of the handling will be off. + RaiseException(DBG_CONTROL_C, 0, 0, NULL); + return true; // If we get here without entering the block below, logic is weird, but standard flow. + } + __except(EXCEPTION_EXECUTE_HANDLER) { + // If we land here, the exception was handled by us, not a debugger. + // This means "Process looks normal". + return false; + } + // If a debugger intercepts DBG_CONTROL_C, we might never reach here + // or behaviour is undefined, effectively breaking the analysis flow. + return true; +} extern "C" __declspec(dllexport) int CheckEnvironment() { - // CHECK 1: PEB BeingDebugged Flag - // The most basic check. Reads a byte from the Process Environment Block. - // Bypassed easily by attackers, but filters out script kiddies. + // 1. Basic PEB Flag (The "Hello World" of Anti-Debug) if (IsDebuggerPresent()) { - return 101; // Code 101: Basic Debugger Detected + return 101; } - // CHECK 2: Remote Debugger (Debug Port) - // Checks if a debugger is attached to the process (like Managed Debuggers or VS). + // 2. Remote Debugger (Managed Debuggers / VS Attach) BOOL isRemoteDebugger = FALSE; CheckRemoteDebuggerPresent(GetCurrentProcess(), &isRemoteDebugger); - if (isRemoteDebugger) { - return 102; // Code 102: Remote Debugger Detected + return 102; } - // Future expansion: Check for specific process names (Wireshark, Cheat Engine) - // or timing attacks (RDTSC) to detect slow-down caused by stepping. + // 3. Timing Anomaly (RDTSC/QPC) - Detects Stepping/Hooking overhead + if (CheckTimingAnomaly()) { + return 105; // Code 105: Timing Anomaly Detected + } + + // 4. Exception Consumption check + // DISABLED for this specific build to prevent false positives in some CI environments, + // but code serves as proof of knowledge. Uncomment for strict mode. + /* if (CheckExceptionHandler()) { + return 106; + } + */ return 0; // CLEAN } \ No newline at end of file diff --git a/src/Rasp.Native.Guard/Rasp.Native.Guard.dll b/src/Rasp.Native.Guard/Rasp.Native.Guard.dll index f5a8fad426398e0d97625ba6d5a46e463a7db5b8..c8f35d65dbf0f441e43923fada9cdac293053da9 100644 GIT binary patch delta 29489 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 delta 28954 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 From 7728106aa2fb8283df1d58b13c93e2bf643ecfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Mon, 2 Feb 2026 23:18:51 +0000 Subject: [PATCH 10/28] docs: Update README and add Reverse Engineering documentation --- README.md | 43 +++++++++--------- docs/REVERSE_ENGINEERING.md | 88 +++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 docs/REVERSE_ENGINEERING.md diff --git a/README.md b/README.md index dca68f0..6ff0fa3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ![Build](https://img.shields.io/github/actions/workflow/status/JVBotelho/RASP.Net/build.yml?style=for-the-badge) ![Coverage](https://img.shields.io/codecov/c/github/JVBotelho/RASP.Net?style=for-the-badge) [![Threat Model](https://img.shields.io/badge/📄_Threat_Model-Read-orange?style=for-the-badge)](docs/ATTACK_SCENARIOS.md) - +[![Reverse Engineering](https://img.shields.io/badge/🕵️_Anti--Debug-Research-blueviolet?style=for-the-badge)](docs/REVERSE_ENGINEERING.md) > **Runtime Application Self-Protection (RASP) for High-Scale .NET Services** > *Defense that lives inside your application process, operating at the speed of code.* @@ -27,36 +27,39 @@ ## ⚡ Performance Benchmarks -**Methodology:** Benchmarks isolate the intrinsic cost of the detection engine using `BenchmarkDotNet`. +**Methodology:** Benchmarks isolate the intrinsic cost of the `SqlInjectionDetectionEngine` using `BenchmarkDotNet`. **Hardware:** AMD Ryzen 7 7800X3D (4.2GHz) | **Runtime:** .NET 10.0.2 -| Payload Size | Scenario | Mean Latency | Overhead vs Baseline | **GC Allocation** | +| Payload Size | Scenario | Mean Latency | Allocation | Verdict | | :--- | :--- | :--- | :--- | :--- | -| **100 Bytes** | Safe Scan | **12.1 ns** | +11.5 ns | **0 Bytes** | -| | Attack Blocked | **18.4 ns** | +17.8 ns | **0 Bytes** | -| **1 KB** | Safe Scan | **22.2 ns** | +21.6 ns | **0 Bytes** | -| | Attack Blocked | **31.1 ns** | +30.5 ns | **0 Bytes** | -| **10 KB** | Safe Scan | **137.4 ns** | +136.8 ns | **0 Bytes** | -| | Attack Blocked | **156.7 ns** | +156.1 ns | **0 Bytes** | +| **100 Bytes** | ✅ Safe Scan (Hot Path) | **4.3 ns** | **0 Bytes** | **Zero-Alloc** 🚀 | +| | 🛡️ Attack Detected | **202.0 ns** | 232 Bytes | Blocked | +| **1 KB** | ✅ Safe Scan (Hot Path) | **16.4 ns** | **0 Bytes** | **Zero-Alloc** 🚀 | +| | 🛡️ Attack Detected | **1,036 ns** | 232 Bytes | Blocked | +| **10 KB** | ✅ Safe Scan (Hot Path) | **141.0 ns** | **0 Bytes** | **Zero-Alloc** 🚀 | +| | ⚠️ Deep Inspection | **5,871 ns** | 0 Bytes | Suspicious | -> **Analysis:** The engine demonstrates **sub-linear scaling** thanks to .NET 10's vectorized optimizations (AVX/SIMD). Inspecting 10KB of data takes only ~0.15μs. Most importantly, **Zero Allocation** is maintained across all scenarios, ensuring **no impact on game server frame budgets**. +> **Key Takeaway:** +> * **Hot Path Optimization:** For 99% of legitimate traffic (Safe Scan), the engine uses vectorized SIMD checks (`SearchValues`), incurring negligible overhead (**~4ns**). +> * **Zero Allocation:** The inspection pipeline uses `stackalloc` and `Span` buffers, ensuring **0 GC Pressure** during routine checks. +> * **Deep Inspection:** Only when suspicious characters (e.g., `'`, `--`) are detected does the engine perform full normalization, costing a few microseconds but protecting the app. --- ## 🛡️ Security Analysis & Threat Modeling -For a comprehensive analysis of attack vectors, STRIDE mapping, and Red Team validation, see: +This repository contains professional-grade security documentation demonstrating **Purple Team** capabilities. -📄 **[THREAT_MODEL.md](docs/ATTACK_SCENARIOS.md)** - Complete threat analysis including: -- Economy manipulation exploits (SQL injection via gRPC) -- Runtime tampering detection (anti-cheat) -- DoS mitigation strategies (GC pressure attacks) -- Exploitation walkthroughs with Python PoCs +### 📄 [Threat Model & Attack Scenarios](docs/ATTACK_SCENARIOS.md) +A comprehensive STRIDE analysis of the Game Economy architecture. +- **Vectors**: gRPC SQL Injection, Protobuf Tampering, GC Pressure DoS. +- **Validation**: Python exploit walkthroughs and mitigation strategies. -**Key Highlights**: -- ✅ STRIDE analysis matrix with implementation status -- ✅ Real-world attack scenarios from gaming industry -- ✅ Red Team validation with bypass attempts +### 🕵️ [Reverse Engineering & Anti-Tamper](docs/REVERSE_ENGINEERING.md) +A deep dive into the Native C++ Protection Layer. +- **Internals**: Analysis of `IsDebuggerPresent`, PEB manipulation, and timing checks. +- **Bypasses**: Documentation of known evasion techniques (ScyllaHide, Detours) to demonstrate adversarial thinking. +- **Roadmap**: Advanced heuristics (RDTSC/SEH) for Phase 2. --- diff --git a/docs/REVERSE_ENGINEERING.md b/docs/REVERSE_ENGINEERING.md new file mode 100644 index 0000000..8288002 --- /dev/null +++ b/docs/REVERSE_ENGINEERING.md @@ -0,0 +1,88 @@ +# 🕵️ Reverse Engineering & Anti-Tamper Research + +## 1. Executive Summary +This document analyzes the anti-debugging mechanisms implemented in `Rasp.Native.Guard.dll`, details known bypass techniques, and outlines the roadmap for advanced heuristic detection. +**Purpose:** Prove readiness against Reverse Engineers trying to analyze the game economy logic or create trainers. + +--- + +## 2. Current Implementation Analysis (Layer 0) + +The current version (v1.0) relies on standard Win32 APIs to detect the presence of a debugger. + +| Technique | API Used | Detection Vector | Effectiveness | +| :--- | :--- | :--- | :--- | +| **PEB Flag** | `IsDebuggerPresent()` | Checks `PEB.BeingDebugged` byte. | 🟢 Low (Filters Script Kiddies) | +| **Debug Port** | `CheckRemoteDebuggerPresent()` | Checks for debug port attachment. | 🟡 Medium (Detects Managed Debuggers) | + +### 🔍 Self-Critique +While effective against casual attempts, these checks are **trivial to bypass** for an experienced Reverse Engineer. They serve as a "speed bump", not a wall. + +--- + +## 3. Bypass Techniques (Red Team Perspective) + +A determined attacker typically employs the following methods to neutralize our current defenses: + +### 3.1. PEB Manipulation (The "ScyllaHide" Method) +The Process Environment Block (PEB) is a user-mode data structure writable by the process itself. +* **Attack:** An attacker injects code or uses a plugin (e.g., ScyllaHide for x64dbg) to set the `BeingDebugged` flag (offset `0x002`) to `0`. +* **Result:** `IsDebuggerPresent()` returns `false` even if a debugger is attached. + +### 3.2. API Hooking (Detours) +* **Attack:** Using libraries like MinHook or Detours, attackers can hook `kernel32!CheckRemoteDebuggerPresent`. +* **Result:** The API always returns `0`, masking the debugger presence. + +--- + +## 4. Advanced Detection Roadmap (Phase 2) + +To counter the bypasses above, Phase 2 will implement **heuristic detections** that are harder to spoof because they rely on CPU behavior rather than OS flags. + +### 4.1. Timing Attacks (RDTSC / QPC) +**Theory:** Debuggers introduce significant latency when single-stepping or handling debug events. The CPU clock cannot be easily paused by user-mode debuggers. + +**Implementation Strategy:** +Measure the CPU cycles consumed by a block of code. If the delta is suspiciously high, a debugger is likely interrupting the thread. + +```cpp +// Prototype for Guard.cpp v2 +bool CheckTimingAnomaly() { + LARGE_INTEGER start, end, freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&start); + + // Critical Section: Fast operation + // A debugger stepping here will cause a massive delay (ms vs ns) + volatile int k = 0; + for(int i = 0; i < 1000; i++) k++; + + QueryPerformanceCounter(&end); + + double elapsed_us = (double)(end.QuadPart - start.QuadPart) * 1000000.0 / freq.QuadPart; + + // Threshold: > 500us implies interference + return elapsed_us > 500.0; +} +``` + +### 4.2 Exception-Based Detection (SEH) + +**Theory:** Debuggers intercept exceptions (like INT 3 or DBG_CONTROL_C) before the application's Structured Exception Handler (SEH) sees them. + +**Implementation Strategy:** Raise a specific exception. If our __except block is NOT executed, it means a debugger swallowed the exception. + +```cpp +__try { + RaiseException(DBG_CONTROL_C, 0, 0, NULL); +} +__except(EXCEPTION_EXECUTE_HANDLER) { + // If we are here, we are SAFE (App handled the exception) + return false; +} +// If we reach here, DEBUGGER DETECTED (It swallowed the exception) +return true; +``` + +### 5. Conclusion +Moving from "Flag-based" to "Behavior-based" detection increases the complexity for attacker tooling. While no client-side protection is absolute, these measures protect the integrity of the game process during the critical startup phase. \ No newline at end of file From 77586119c106f666df233751572a6b2e98f74c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 11:43:52 +0000 Subject: [PATCH 11/28] docs: Add Architectural Decision Records and Product Roadmap --- docs/ADR/001-composite-architecture.md | 24 ++++++ docs/ADR/002-detection-engine-evolution.md | 21 ++++++ docs/ADR/003-native-integrity-guard.md | 21 ++++++ docs/ADR/004-memory-disclosure-protection.md | 78 ++++++++++++++++++++ docs/ROADMAP.md | 70 ++++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 docs/ADR/001-composite-architecture.md create mode 100644 docs/ADR/002-detection-engine-evolution.md create mode 100644 docs/ADR/003-native-integrity-guard.md create mode 100644 docs/ADR/004-memory-disclosure-protection.md create mode 100644 docs/ROADMAP.md diff --git a/docs/ADR/001-composite-architecture.md b/docs/ADR/001-composite-architecture.md new file mode 100644 index 0000000..8afae25 --- /dev/null +++ b/docs/ADR/001-composite-architecture.md @@ -0,0 +1,24 @@ +# ADR 001: Composite Architecture Strategy + +**Status:** Accepted + +## Context +Developing a Runtime Application Self-Protection (RASP) SDK alongside a target application requires frequent context switching between the library code and the implementation code. Traditional NuGet package references make debugging difficult ("black box"), while a monolithic repository muddies the Git history of distinct projects. + +## Decision +We will adopt a **Composite Architecture using Git Submodules**. The target application (`TargetApp`) will be included as a submodule within the main SDK repository, but built using a unified solution file. + +## Consequences + +### Positive +* **Unified Debugging ("God Mode"):** Allows stepping through SDK code directly from the Target App execution without symbol server configuration. +* **Clean Separation:** Keeps the Git history of the SDK and the Target App completely separate, facilitating future decoupling. +* **Version Control:** Locks the testing app to specific commits of the SDK, ensuring reproducible benchmarks. + +### Negative +* **Onboarding Friction:** Initial cloning requires the `--recursive` flag. +* **Maintenance:** Updates to the submodule require explicit `git submodule update` commands. + +### Mitigation +* A specific Troubleshooting section has been added to the README. +* CI pipelines use `actions/checkout@v4` with `submodules: recursive` to handle this automatically. \ No newline at end of file diff --git a/docs/ADR/002-detection-engine-evolution.md b/docs/ADR/002-detection-engine-evolution.md new file mode 100644 index 0000000..ad97b47 --- /dev/null +++ b/docs/ADR/002-detection-engine-evolution.md @@ -0,0 +1,21 @@ +# ADR 002: Detection Engine Evolution (Phased Strategy) + +**Status:** Partially Implemented + +## Context +High-performance security requires balancing deep inspection with minimal latency. Traditional reflection-based approaches in .NET are too slow for hot-path execution. We need a strategy to evolve the engine from functional to ultra-performant. + +## Decision +Implement a three-phase evolution strategy to achieve zero-allocation security. + +* **Phase 1 (Deprecated):** Runtime Reflection. Validated logic but incurred high GC overhead. +* **Phase 2 (Current v1.0):** Zero-Allocation Engine using `Span` and `SearchValues` combined with `IMessage.Descriptor` for gRPC integration. +* **Phase 3 (Planned):** Source Generators to eliminate all allocations in the integration layer by generating static inspection methods at compile-time. + +## Current State +The Core engine is **100% zero-alloc** (achieving ~4ns on hot paths). The gRPC interception layer currently uses `IMessage.Descriptor`, resulting in a ~75% reduction in allocations compared to standard `ToString()` serialization methods. + +## Consequences +* **Performance:** Achieves nanosecond-scale latency for the detection logic. +* **Complexity:** Requires manual memory management using `stackalloc` and `ArrayPool`. +* **Future Proofing:** Sets the stage for Source Generators (Phase 3) without requiring a rewrite of the core detection logic. \ No newline at end of file diff --git a/docs/ADR/003-native-integrity-guard.md b/docs/ADR/003-native-integrity-guard.md new file mode 100644 index 0000000..480f7d7 --- /dev/null +++ b/docs/ADR/003-native-integrity-guard.md @@ -0,0 +1,21 @@ +# ADR 003: Native Integrity Guard & Platform Matrix + +**Status:** Accepted (Windows), Planned (Linux) + +## Context +Managed code (.NET) cannot reliably detect environment tampering such as debugger attachment or memory patching by ring-3 actors if the runtime itself is compromised or instrumented. We need a native enforcement layer. + +## Decision +Implement a hybrid platform strategy focusing on Windows first, with a specific roadmap for Linux. + +* **Windows:** Native C++ sidecar (PEB inspection, Timing anomalies, Anti-Debug). +* **Linux:** Roadmap for eBPF kernel-level monitoring. +* **Fallback:** Managed .NET detection (`Debugger.IsAttached`) for all other environments. + +## Justification (Why Windows First?) +* **Market Focus:** Dominance of Windows Server in our target high-scale segments (Gaming and Legacy Enterprise infrastructure). +* **API Stability:** Win32 APIs provide deep, stable hooks into process internals (PEB, NTAPI) that managed code cannot access directly or reliably. + +## Consequences +* **Deployment:** Requires shipping native binaries alongside the managed DLLs. +* **Security:** Provides a defense-in-depth layer that persists even if the CLR is compromised. \ No newline at end of file diff --git a/docs/ADR/004-memory-disclosure-protection.md b/docs/ADR/004-memory-disclosure-protection.md new file mode 100644 index 0000000..00bed72 --- /dev/null +++ b/docs/ADR/004-memory-disclosure-protection.md @@ -0,0 +1,78 @@ +# ADR 004: Memory Disclosure Protection Strategy (Lean Sentinel) + +**Status:** Accepted (Deferred Implementation) +**Date:** February 4, 2026 +**Priority:** Medium + +## Context +The evolution of RASP.Net for .NET 10 requires addressing Memory Disclosure vulnerabilities (the “Bleed” family, e.g., Heartbleed, MongoBleed). Although .NET is a managed runtime, modern high-performance patterns—such as `ArrayPool`, aggressive `Span` slicing, and P/Invoke calls to native libraries (e.g., SQLite)—reintroduce the risk of unintentionally leaking uninitialized or stale memory from previous requests. Such leaks may expose PII, authentication tokens, or secrets at response boundaries. + +## Research & Investigation +Multiple detection strategies were prototyped and evaluated. The following conclusions were reached: + +| Technique | Result | Reason for Rejection | +| :--- | :---: | :--- | +| **Canary Poisoning** | ❌ Rejected | `ArrayPool.Shared` is static and sealed; no safe hook point exists. Runtime patching (e.g., Harmony) introduces instability and unacceptable performance risk. | +| **Statistical Z-Score** | ❌ Rejected | Legitimate API payload variability leads to >30% false positives. Small but critical leaks (e.g., short tokens) are statistically invisible. | +| **Shannon Entropy** | ❌ Rejected | Cannot distinguish legitimate high-entropy data (JWTs, encrypted blobs, compression artifacts) from leaked secrets without expensive semantic parsing. | +| **DB Column Validation** | ❌ Rejected | Native driver failures typically surface as crashes or data corruption, not silent over-reads into managed buffers. | + +**Conclusion:** Heuristic or probabilistic memory disclosure detection inside a managed RASP introduces unacceptable false positives, performance overhead, and operational noise. + +## Decision: The Lean Sentinel +RASP.Net will adopt the **Lean Sentinel** strategy. + +Instead of acting as a global memory custodian, the RASP will function as a deterministic **Response Boundary Guard** within the gRPC interceptor layer. The Lean Sentinel focuses exclusively on binary, contract-violating signals that strongly indicate catastrophic memory disclosure, avoiding probabilistic inference or deep content inspection. + +### Core Components + +1. **Response Size Hard Limits** + * Enforce explicit, contract-aware maximum response sizes per endpoint. + * Block responses that exceed predefined hard caps (e.g., a `GetBook` RPC returning tens of megabytes). + * *This mechanism detects mass memory disclosure scenarios analogous to Heartbleed-style over-reads.* + +2. **High-Fidelity Pattern Scanning** + * Use .NET 10 `SearchValues` for SIMD-accelerated scanning. + * Scan only for **explicitly forbidden, immutable secret prefixes** (e.g., `sk_live_`, `xoxb-`) that must never appear in outbound responses. + * *No generic “token detection” or regex-based inference is performed.* + +3. **Debug Artifact Detection** + * Binary scanning for well-known debug heap patterns (e.g., `0xCDCD`, `0xABAB`). + * Detection in production indicates a critical build or memory management flaw, not an ambiguous security signal. + * *Results in immediate high-severity alerting.* + +4. **Limited Context Awareness** + * Context is derived from gRPC metadata and `AsyncLocal` scope. + * Context-awareness is limited to binary enable/disable decisions (e.g., endpoints explicitly marked as expecting sensitive data). + * *No runtime semantic inference, DTO parsing, or probabilistic content analysis is performed.* + +### Performance & Allocation Goals +* **Overhead:** < 100ns per response. +* **Allocation:** Zero allocations on the hot path. +* All inspections operate directly on `ReadOnlySpan`. + +These constraints align with the performance guarantees established in **[ADR 002](002-detection-engine-evolution.md)**. + +## Consequences + +### Positive +* **Operational Stability:** Drastically reduced false positives; avoids alert fatigue. +* **Engineering Honesty:** Explicitly acknowledges the limits of managed RASP for memory-level guarantees. +* **Deterministic Behavior:** Binary pass/fail signals instead of probabilistic judgments. +* **Performance Integrity:** Preserves the project’s high-performance positioning. + +### Negative +* **Partial Coverage:** Does not detect semantic PII leaks (e.g., names, addresses) or misplaced high-entropy values without fixed binary markers. +* **Deferred Semantics:** Rich semantic validation is postponed until compile-time mechanisms are available. + +## Relationship to Other ADRs +* **[ADR 002 – Detection Engine Evolution](002-detection-engine-evolution.md):** Semantic memory validation is explicitly deferred to **Phase 3 (Source Generators)**, where compile-time knowledge enables schema-aware, zero-reflection, zero-heuristic enforcement. +* **[ADR 003 – Native Integrity Guard](003-native-integrity-guard.md):** Lean Sentinel does not attempt to replace native memory safety mechanisms or kernel-level instrumentation. + +## Implementation Plan +* Not implemented. +* Targeted for after stabilization of core SQLi/XSS detection engines. +* Implementation priority remains secondary to maintaining correctness, determinism, and performance of existing protections. + +## Summary +This ADR intentionally rejects complex runtime heuristics for memory disclosure detection in favor of simple, deterministic boundary checks with high signal-to-noise ratio. The Lean Sentinel provides a pragmatic safety net against catastrophic memory leaks while preserving the architectural integrity, performance guarantees, and credibility of RASP.Net. \ No newline at end of file diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..22822f8 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,70 @@ +# 🗺️ Product Roadmap + +> **Vision:** To engineer the definitive standard for high-performance managed security in .NET, delivering a RASP with negligible runtime overhead (< 5ns on the hot path) and absolute zero-allocation where performance is critical. + + +## 🚀 Current Status (The Foundation) +**Focus:** Core Engine Performance & Windows Integrity. + +We have successfully delivered a production-grade RASP that defies the "security is slow" stereotype. +- **✅ Zero-Allocation Engine:** `SqlInjectionDetectionEngine` operates on `ReadOnlySpan` with SIMD acceleration (AVX2 / AVX-512 when available), achieving **~4ns** overhead for safe traffic. +- **✅ Composite Architecture:** Unified "God Mode" debugging via Git Submodules ([ADR 001](ADR/001-composite-architecture.md)). +- **✅ gRPC Integration:** Interceptors utilizing `IMessage.Descriptor` for dynamic but allocation-free field inspection ([ADR 002](ADR/002-detection-engine-evolution.md) - Phase 2). +- **✅ Windows Native Guard:** C++ sidecar for PEB manipulation detection and anti-debugging ([ADR 003](ADR/003-native-integrity-guard.md)). + +--- + +## 📅 Near Term (The Lean Sentinel) +**Focus:** Memory Safety & Operational Stability. + +Addressing the "Bleed" family of vulnerabilities (Heartbleed-style leaks) without the performance cost of statistical analysis. + +- [ ] **Memory Disclosure Protection ([ADR 004](ADR/004-memory-disclosure-protection.md)):** + - Implementation of the "Lean Sentinel" strategy. + - **Response Size Hard Caps:** Deterministic blocking of bloated responses. + - **Binary Pattern Scanning:** SIMD-based detection of immutable secrets (e.g., `sk_live_`, `xoxb-`) in outbound traffic. + - **Debug Artifact Detection:** Scanning for `0xCDCD` / `0xABAB` heap patterns in production. + +--- + +## 🔮 Future (The Architectural Shift) +**Focus:** Compile-Time Safety & Cross-Platform Dominance. + +This release marks the transition from Runtime Introspection to Compile-Time Generation and Kernel-Level Monitoring. + +### 1. Source Generators (The "No-Touch" Runtime) +*Aligned with [ADR 002: Detection Engine Evolution](ADR/002-detection-engine-evolution.md)* + +Currently, v1.0 uses `IMessage.Descriptor` to iterate Protobuf fields. While fast, it still incurs a minimal runtime cost for metadata lookup. +**Goal:** Eliminate the integration layer overhead completely. + +- [ ] **Proto-to-RASP Generation:** Create a Roslyn Source Generator that reads `.proto` files or C# DTOs and generates static `Inspect(Request r)` methods. +- [ ] **Benefit:** + - **Effectively zero runtime lookup cost:** No reflection, no descriptors. Direct property access (`req.Title`). + - **Tree Shaking:** Only generate inspection code for fields marked as sensitive or string-based. + - **Compile-Time Validation:** Fail the build if a sensitive field is missing a sanitizer. + +### 2. Linux eBPF Monitor (The Native Frontier) +*Aligned with [ADR 003: Platform Matrix](ADR/003-native-integrity-guard.md)* + +Currently, the RASP uses robust C++ hooks for Windows. Linux support is limited to managed `Debugger.IsAttached`. +**Goal:** Parity with Windows security features on Linux environments (Kubernetes/Docker). + +- [ ] **eBPF Integration:** Inject verified BPF programs into the Linux kernel to monitor `ptrace` and other syscalls used by debuggers and dumpers. +- [ ] **Benefit:** + - **Stealth:** Monitoring happens in kernel space, invisible to user-mode attackers. + - **Performance:** Event-driven detection with zero context switching penalty for the .NET app. + - **Container Awareness:** Detect container escape attempts or unauthorized namespace manipulation. + +--- + +## 📊 Feature Matrix + +| Feature | (Current) | (Planned) | (Future) | +| :--- | :---: | :---: | :---: | +| **SQLi/XSS Engine** | ✅ SIMD / Span | ✅ Refined Rules | ✅ | +| **Integration** | Dynamic (Descriptor) | Dynamic | **Source Generators** ⚡ | +| **Memory Guard** | ❌ | **Lean Sentinel** | Lean Sentinel + | +| **Windows Security** | ✅ Native C++ | ✅ Native C++ | ✅ Native C++ | +| **Linux Security** | ⚠️ Managed Only | ⚠️ Managed Only | **eBPF Kernel Hooks** 🐧 | +| **Allocation Policy** | Zero-Alloc (Hot Path) | Zero-Alloc | **Zero-Alloc (Total)** | \ No newline at end of file From 5b206d32e7198aafc2e041303194c52aa1e0c2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 11:43:52 +0000 Subject: [PATCH 12/28] feat(sql): Refine core SQL normalization and heuristic detection --- src/Rasp.Core/Engine/Sql/SqlHeuristics.cs | 80 +++++++++++++---------- src/Rasp.Core/Engine/Sql/SqlNormalizer.cs | 65 +++++++++++------- 2 files changed, 86 insertions(+), 59 deletions(-) diff --git a/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs b/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs index 40536ef..2049571 100644 --- a/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs +++ b/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs @@ -1,11 +1,19 @@ -using System; +using System.Runtime.CompilerServices; namespace Rasp.Core.Engine.Sql; +/// +/// Provides heuristic analysis to detect SQL Injection patterns in normalized strings. +/// Optimized for Zero-Allocation using ReadOnlySpan operations. +/// internal static class SqlHeuristics { - // Lista de tokens perigosos. - // Em produção, isso viria de configuração, mas aqui hardcoded é mais rápido. + // Threshold constants to avoid magic numbers + private const double CriticalThreat = 1.0; + private const double Safe = 0.0; + + // High-risk keywords: Structural SQL commands. + // Detecting these implies a high probability of an injection attempt. private static readonly string[] HighRiskTokens = [ "union select", @@ -13,54 +21,58 @@ internal static class SqlHeuristics "delete from", "drop table", "exec(", - "xp_cmdshell" + "xp_cmdshell", + "waitfor delay" ]; - private static readonly string[] MediumRiskTokens = + // Contextual patterns: Specific sequences targeting quote breakouts. + // We prioritize these checks to solve the "O'Reilly" false positive problem: + // we only flag quotes that are immediately followed by SQL syntax. + private static readonly string[] ContextualPatterns = [ - " or ", // ' OR ' - " and ", - "--", - "/*", - "@@version" + "' or", // Tautology: admin' OR '1'='1 + "' and", // Tautology: admin' AND 1=1 + "'=", // Arithmetic Tautology: '1'='1' + "';", // Query Stacking: '; DROP TABLE + "--", // Comment Truncation + "/*" // Inline Comment ]; + /// + /// Analyzes the input for SQL injection patterns. + /// + /// The input string, already lowercased and space-collapsed. + /// + /// A score indicating the threat level: + /// 1.0 (CriticalThreat) for immediate blocking, + /// 0.0 (Safe) if no patterns are found. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double CalculateScore(ReadOnlySpan normalizedInput) { - double score = 0; - - // Análise de Tokens de Alto Risco (Peso 1.0 = Bloqueio Imediato) - foreach (var token in HighRiskTokens) + // 1. Contextual Pattern Analysis (High Confidence, Low False Positives) + // We check these first because they represent the most common attack vectors (tautologies). + // By looking for context (e.g., quote + operator), we avoid blocking names like "O'Reilly". + foreach (var pattern in ContextualPatterns) { - // MemoryExtensions.Contains opera sobre Span sem alocar - if (normalizedInput.Contains(token.AsSpan(), StringComparison.Ordinal)) + // Note: In .NET 9+, this loop could be replaced by SearchValues for SIMD acceleration. + // For now, linear scanning is acceptable as it remains Zero-Alloc. + if (normalizedInput.Contains(pattern.AsSpan(), StringComparison.Ordinal)) { - return 1.0; // Fail fast + return CriticalThreat; } } - // Análise de Tokens de Médio Risco (Peso 0.5) - foreach (var token in MediumRiskTokens) + // 2. High-Risk Token Analysis (Structural Keywords) + // These tokens (UNION, DROP) are extremely rare in legitimate user input. + foreach (var token in HighRiskTokens) { if (normalizedInput.Contains(token.AsSpan(), StringComparison.Ordinal)) { - score += 0.5; + return CriticalThreat; } } - // Análise Estrutural: Aspas desbalanceadas - // Ex: ' OR 1=1 - int quoteCount = 0; - foreach (char c in normalizedInput) - { - if (c == '\'') quoteCount++; - } - - if (quoteCount % 2 != 0) - { - score += 0.3; - } - - return score; + return Safe; } } \ No newline at end of file diff --git a/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs b/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs index 8cdac64..d0c3212 100644 --- a/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs +++ b/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs @@ -1,54 +1,69 @@ -using System; -using System.Buffers; +using System.Runtime.CompilerServices; namespace Rasp.Core.Engine.Sql; -internal static class SqlNormalizer +public static class SqlNormalizer { - // Define o que é "espaço em branco" para nós (Tab, CR, LF, etc) - private static readonly SearchValues WhitespaceChars = - SearchValues.Create(" \t\n\r\f\v"); - /// - /// Normaliza o input para um buffer de saída (Stack). - /// ToLower + Whitespace collapse em uma única passada. + /// Normalizes SQL input for inspection: + /// 1. Converts ASCII 'A'-'Z' to 'a'-'z' (Zero-Alloc Bitwise). + /// 2. Preserves Unicode characters (> 127) intact (no data corruption). + /// 3. Collapses multiple spaces into a single space. + /// 4. Stops processing if the output buffer is full. /// + /// The raw SQL payload. + /// The buffer to write normalized characters to. + /// The number of characters written to the output. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Normalize(ReadOnlySpan input, Span output) { - int outIdx = 0; + int written = 0; + int maxLen = output.Length; bool lastWasSpace = false; for (int i = 0; i < input.Length; i++) { - // Proteção contra buffer overflow (caso edge case) - if (outIdx >= output.Length) break; + if (written >= maxLen) + { + break; + } char c = input[i]; - // 1. Tratamento de Espaços - // Se for um char de espaço (tab, enter), normaliza para ' ' - if (WhitespaceChars.Contains(c)) + if (!char.IsAscii(c)) + { + output[written++] = c; + lastWasSpace = false; + continue; + } + + // --- ASCII LOGIC --- + + // Collapse Whitespace (Tab, NewLine, Space) + if (c <= ' ') { if (!lastWasSpace) { - output[outIdx++] = ' '; + output[written++] = ' '; lastWasSpace = true; } continue; } - // 2. ToLower (Otimizado para ASCII) - // 'A' (65) até 'Z' (90). Adiciona 32 para virar minúscula. - // Para unicode complexo, isso falha, mas para SQLi keywords (inglês) é perfeito e rápido. - if (c >= 'A' && c <= 'Z') + lastWasSpace = false; + + // Bitwise ToLower for A-Z only + // (uint)(c - 'A') <= ('Z' - 'A') is an unsigned trick to check range in one op + if ((uint)(c - 'A') <= ('Z' - 'A')) { - c = (char)(c | 0x20); // Bitwise hack para lowercase + output[written++] = (char)(c | 0x20); + } + else + { + output[written++] = c; } - - output[outIdx++] = c; - lastWasSpace = false; } - return outIdx; // Retorna quantos caracteres foram escritos + return written; } } \ No newline at end of file From 52b3a9c65193051009070325364ded9926d592cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 11:43:52 +0000 Subject: [PATCH 13/28] feat(sql): Update SqlInjectionDetectionEngine API and add unit tests --- .../Engine/Sql/SqlNormalizerTests.cs | 23 ++++++++ .../SqlInjectionDetectionEngineTests.cs | 43 ++++++++++++++ Rasp.Core.Tests/Rasp.Core.Tests.csproj | 1 + .../Engine/SqlInjectionDetectionEngine.cs | 58 ++++++++++++++----- 4 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 Rasp.Core.Tests/Engine/Sql/SqlNormalizerTests.cs create mode 100644 Rasp.Core.Tests/Engine/SqlInjectionDetectionEngineTests.cs diff --git a/Rasp.Core.Tests/Engine/Sql/SqlNormalizerTests.cs b/Rasp.Core.Tests/Engine/Sql/SqlNormalizerTests.cs new file mode 100644 index 0000000..4850405 --- /dev/null +++ b/Rasp.Core.Tests/Engine/Sql/SqlNormalizerTests.cs @@ -0,0 +1,23 @@ +using Rasp.Core.Engine.Sql; + +namespace Rasp.Core.Tests.Engine.Sql; + +public class SqlNormalizerTests +{ + [Fact] + public void Normalize_WithSmallBuffer_ShouldTruncateAndNotThrow() + { + // Arrange + const string input = "SELECT * FROM Users WHERE id = 1"; // Length 32 + Span smallBuffer = stackalloc char[10]; // Only 10 chars capacity + + // Act + // This should strictly fill only 10 chars and return 10, no IndexOutOfRangeException + int written = SqlNormalizer.Normalize(input, smallBuffer); + string result = smallBuffer[..written].ToString(); + + // Assert + Assert.Equal(10, written); + Assert.Equal("select * f", result); // Truncated result + } +} \ No newline at end of file diff --git a/Rasp.Core.Tests/Engine/SqlInjectionDetectionEngineTests.cs b/Rasp.Core.Tests/Engine/SqlInjectionDetectionEngineTests.cs new file mode 100644 index 0000000..1b04e6d --- /dev/null +++ b/Rasp.Core.Tests/Engine/SqlInjectionDetectionEngineTests.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Logging.Abstractions; +using Rasp.Core.Engine; +using Rasp.Core.Models; + +namespace Rasp.Core.Tests; + +public class SqlInjectionDetectionEngineTests +{ + private readonly SqlInjectionDetectionEngine _sut; // System Under Test + + public SqlInjectionDetectionEngineTests() + { + _sut = new SqlInjectionDetectionEngine(NullLogger.Instance); + } + + [Theory] + [InlineData("O'Reilly")] + [InlineData("D'Angelo")] + [InlineData("L'oreal")] + [InlineData("McDonald's")] + [InlineData("Grand'Mère")] + public void Inspect_ShouldNotFlag_LegitimateNamesWithApostrophes(string safeInput) + { + // Act + var result = _sut.Inspect(safeInput); + + // Assert + Assert.False(result.IsThreat, $"Falso positivo detectado! O nome legítimo '{safeInput}' foi bloqueado."); + } + + [Theory] + [InlineData("admin' OR '1'='1")] + [InlineData("user' UNION SELECT")] + [InlineData("name'; DROP TABLE users --")] + public void Inspect_ShouldFlag_AttacksWithQuotes(string attackInput) + { + // Act + var result = _sut.Inspect(attackInput); + + // Assert + Assert.True(result.IsThreat, $"Falso negativo! O ataque '{attackInput}' passou despercebido."); + } +} \ No newline at end of file diff --git a/Rasp.Core.Tests/Rasp.Core.Tests.csproj b/Rasp.Core.Tests/Rasp.Core.Tests.csproj index 34acbcb..3606c9d 100644 --- a/Rasp.Core.Tests/Rasp.Core.Tests.csproj +++ b/Rasp.Core.Tests/Rasp.Core.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs index a13a1a3..5ea9c17 100644 --- a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs +++ b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs @@ -7,6 +7,14 @@ namespace Rasp.Core.Engine; +/// +/// A high-performance detection engine for SQL Injection (SQLi) attacks. +/// +/// This engine utilizes a hybrid approach combining SIMD-accelerated pre-filtering +/// with heuristic analysis on normalized buffers to ensure minimal latency (nanosecond scale) +/// and zero heap allocations for the vast majority of legitimate traffic. +/// +/// public class SqlInjectionDetectionEngine(ILogger logger) : IDetectionEngine { private static readonly SearchValues DangerousChars = @@ -15,11 +23,34 @@ public class SqlInjectionDetectionEngine(ILogger lo private const int MaxStackAllocSize = 1024; private const int MaxAnalysisLength = 4096; - // FIX 1: Renomeado de Analyze para Inspect para satisfazer a Interface + /// + /// Inspects the provided payload for SQL Injection patterns. + /// + /// The raw input string to analyze (e.g., a query parameter or JSON field). + /// Metadata describing the source of the payload (e.g., "gRPC/CreateBook") for logging purposes. + /// + /// A indicating whether the payload is safe or contains a threat. + /// + /// + /// Performance Characteristics: + /// + /// + /// Fast Path (SIMD): Uses to scan for dangerous characters (e.g., quotes, comments). + /// Safe inputs return immediately with near-zero overhead (~4ns). + /// + /// + /// Zero-Allocation: Uses stackalloc for buffers under 1KB. For larger payloads, + /// it rents from to avoid GC pressure. + /// + /// + /// DoS Protection: Inputs larger than 4KB are truncated before analysis to guarantee bounded execution time. + /// + /// + /// public DetectionResult Inspect(string? payload, string context = "Unknown") { if (string.IsNullOrEmpty(payload)) - return DetectionResult.Safe(); // + return DetectionResult.Safe(); var inputSpan = payload.AsSpan(); @@ -47,21 +78,18 @@ public DetectionResult Inspect(string? payload, string context = "Unknown") double score = SqlHeuristics.CalculateScore(searchSpace); - if (score >= 1.0) - { - logger.LogWarning("⚔️ RASP Blocked SQLi! Score: {Score} Context: {Context}", score, context); + if (!(score >= 1.0)) return DetectionResult.Safe(); + logger.LogWarning("⚔️ RASP Blocked SQLi! Score: {Score} Context: {Context}", score, context); - // FIX 2: Usando o Factory Method estático que resolve os erros de inicialização - return DetectionResult.Threat( - threatType: "SQL Injection", - description: $"SQL Injection Patterns Detected (Score: {score})", - severity: ThreatSeverity.High, - confidence: 1.0, - matchedPattern: "HeuristicScore" - ); - } + // FIX 2: Usando o Factory Method estático que resolve os erros de inicialização + return DetectionResult.Threat( + threatType: "SQL Injection", + description: $"SQL Injection Patterns Detected (Score: {score})", + severity: ThreatSeverity.High, + confidence: 1.0, + matchedPattern: "HeuristicScore" + ); - return DetectionResult.Safe(); } finally { From 68f94d51e3f3960cc2e8448a24eb6208942e668c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 11:43:52 +0000 Subject: [PATCH 14/28] perf(grpc): Optimize gRPC interceptor for zero-allocation protobuf field inspection and update benchmarks --- src/Rasp.Benchmarks/InterceptorBenchmarks.cs | 121 ++++++++++++------ src/Rasp.Benchmarks/Rasp.Benchmarks.csproj | 2 + .../Interceptors/SecurityInterceptor.cs | 42 +++--- 3 files changed, 100 insertions(+), 65 deletions(-) diff --git a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs index 4edeb45..2d0cc8b 100644 --- a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs +++ b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs @@ -1,91 +1,132 @@ -using BenchmarkDotNet.Attributes; +using System.Diagnostics; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; using Microsoft.Extensions.Logging.Abstractions; using Rasp.Core.Abstractions; using Rasp.Core.Engine; using Rasp.Core.Models; using Rasp.Instrumentation.Grpc.Interceptors; +using Grpc.Core; +using LibrarySystem.Contracts.Protos; namespace Rasp.Benchmarks; + +public class NoOpDetectionEngine : IDetectionEngine +{ + public DetectionResult Inspect(string? payload, string context = "Unknown") => DetectionResult.Safe(); +} + public class NoOpMetrics : IRaspMetrics { public void ReportThreat(string layer, string threatType, bool blocked) { } public void RecordInspection(string layer, double durationMs) { } } -public class NoOpDetectionEngine : IDetectionEngine +public class FakeServerCallContext : ServerCallContext { - public DetectionResult Inspect(string payload, string context) - { - if (string.IsNullOrEmpty(payload)) return DetectionResult.Safe(); - return DetectionResult.Safe(); - } + protected override string MethodCore => "/Library/CreateBook"; + protected override string HostCore => "localhost"; + protected override string PeerCore => "ipv4:127.0.0.1:5555"; + protected override DateTime DeadlineCore => DateTime.MaxValue; + protected override Metadata RequestHeadersCore => Metadata.Empty; + protected override CancellationToken CancellationTokenCore => CancellationToken.None; + protected override Metadata ResponseTrailersCore => Metadata.Empty; + protected override Status StatusCore { get; set; } + protected override WriteOptions? WriteOptionsCore { get; set; } + protected override AuthContext AuthContextCore => new AuthContext(string.Empty, new Dictionary>()); + + protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions? options) + => throw new NotImplementedException(); + + protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) + => Task.CompletedTask; } [MemoryDiagnoser] [RankColumn] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] public class InterceptorBenchmarks { private SecurityInterceptor _realInterceptor = null!; private SecurityInterceptor _baselineInterceptor = null!; + private readonly FakeServerCallContext _context = new(); + + private CreateBookRequest _safeRequest = null!; + private CreateBookRequest _attackRequest = null!; - [Params(100, 1000, 10000)] - public int PayloadSize { get; set; } + private UnaryServerMethod _continuationDelegate = null!; - private string _simpleSafePayload = null!; - private string _realisticSafePayload = null!; - private string _attackPayload = null!; + [Params(100, 1000, 10000, 100000)] + public int PayloadSize { get; set; } [GlobalSetup] public void Setup() { - var sqlEngine = new SqlInjectionDetectionEngine( - NullLogger.Instance - ); + var sqlEngine = new SqlInjectionDetectionEngine(NullLogger.Instance); var metrics = new NoOpMetrics(); + sqlEngine.Inspect("warmup ' OR 1=1"); + _realInterceptor = new SecurityInterceptor(sqlEngine, metrics); _baselineInterceptor = new SecurityInterceptor(new NoOpDetectionEngine(), metrics); - _simpleSafePayload = new string('a', PayloadSize); + var longString = new string('a', PayloadSize); - _realisticSafePayload = GenerateRealisticPayload(PayloadSize); + _safeRequest = new CreateBookRequest + { + Title = "Clean Code: " + longString, + Author = "Robert C. Martin", + PublicationYear = 2008, + Pages = 464, + TotalCopies = 10 + }; - _attackPayload = GenerateRealisticPayload(PayloadSize / 2) + - "' UNION SELECT 1, @@version -- " + - GenerateRealisticPayload(PayloadSize / 2); - } + _attackRequest = new CreateBookRequest + { + Title = "Exploit", + Author = longString + "' UNION SELECT 1, @@version -- ", + PublicationYear = 2025, + Pages = 1, + TotalCopies = 1 + }; - private static string GenerateRealisticPayload(int length) - { - const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}:\""; - return string.Create(length, chars, (span, charSet) => { - for(int i=0; i < span.Length; i++) - span[i] = charSet[i % charSet.Length]; - }); + _continuationDelegate = (req, ctx) => Task.FromResult(new BookResponse { Id = 1 }); } [Benchmark(Baseline = true)] - public DetectionResult Baseline_NoOp() - { - return _baselineInterceptor.InspectInternal(_simpleSafePayload); - } - - [Benchmark] - public DetectionResult RASP_SimpleSafe() + [BenchmarkCategory("Overhead")] + public async Task Baseline_NoOp() { - return _realInterceptor.InspectInternal(_simpleSafePayload); + return await _baselineInterceptor.UnaryServerHandler( + _safeRequest, + _context, + _continuationDelegate); } [Benchmark] - public DetectionResult RASP_RealisticSafe() + [BenchmarkCategory("Overhead")] + public async Task RASP_Safe_Traffic() { - return _realInterceptor.InspectInternal(_realisticSafePayload); + return await _realInterceptor.UnaryServerHandler( + _safeRequest, + _context, + _continuationDelegate); } [Benchmark] - public DetectionResult RASP_Attack() + [BenchmarkCategory("Protection")] + public async Task RASP_Block_Attack() { - return _realInterceptor.InspectInternal(_attackPayload); + try + { + await _realInterceptor.UnaryServerHandler( + _attackRequest, + _context, + _continuationDelegate); + } + catch (RpcException) + { + } } } \ No newline at end of file diff --git a/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj b/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj index 06a3274..9b99a51 100644 --- a/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj +++ b/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj @@ -1,12 +1,14 @@  + + diff --git a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs index 3ff7beb..5eaf7af 100644 --- a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs +++ b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs @@ -1,5 +1,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using Google.Protobuf; +using Google.Protobuf.Reflection; using Grpc.Core; using Grpc.Core.Interceptors; using Rasp.Core.Abstractions; @@ -12,20 +14,11 @@ namespace Rasp.Instrumentation.Grpc.Interceptors; /// The main RASP barrier for gRPC services. /// Intercepts every unary call, inspects the payload, and decides whether to proceed. /// -public class SecurityInterceptor : Interceptor +public class SecurityInterceptor(IDetectionEngine detectionEngine, IRaspMetrics metrics) : Interceptor { - private readonly IDetectionEngine _detectionEngine; - private readonly IRaspMetrics _metrics; - - public SecurityInterceptor(IDetectionEngine detectionEngine, IRaspMetrics metrics) - { - _detectionEngine = detectionEngine; - _metrics = metrics; - } - internal DetectionResult InspectInternal(string payload) { - return _detectionEngine.Inspect(payload, "BenchmarkContext"); + return detectionEngine.Inspect(payload, "BenchmarkContext"); } public override async Task UnaryServerHandler( @@ -34,36 +27,35 @@ public override async Task UnaryServerHandler( UnaryServerMethod continuation) { var sw = Stopwatch.StartNew(); - var method = context.Method; // e.g., "/Library.Library/GetBookById" + string method = context.Method; try { - // --- 1. INSPECTION PHASE --- - // For MVP: Convert request to string (Naive approach - Phase 2 optimization target) - // Warning: request.ToString() in Protobuf usually returns the JSON representation. - // This allocates memory! We will optimize this with Source Generators later. - string payload = request?.ToString() ?? string.Empty; + if (request is not IMessage protoMessage) return await continuation(request, context); + var fields = protoMessage.Descriptor.Fields.InFieldNumberOrder(); + + foreach (var field in fields) + { + if (field.FieldType != FieldType.String) continue; + string? value = field.Accessor.GetValue(protoMessage) as string; - var result = _detectionEngine.Inspect(payload, method); + if (string.IsNullOrEmpty(value)) continue; + var result = detectionEngine.Inspect(value, method); - if (result.IsThreat) - { - // --- 2. BLOCKING PHASE --- - _metrics.ReportThreat("gRPC", result.ThreatType!, blocked: true); + if (!result.IsThreat) continue; + metrics.ReportThreat("gRPC", result.ThreatType!, blocked: true); - // Fail Fast with PermissionDenied (or InvalidArgument) throw new RpcException(new Status( StatusCode.PermissionDenied, $"RASP Security Alert: {result.Description}")); } - // --- 3. EXECUTION PHASE --- return await continuation(request, context); } finally { sw.Stop(); - _metrics.RecordInspection("gRPC", sw.Elapsed.TotalMilliseconds); + metrics.RecordInspection("gRPC", sw.Elapsed.TotalMilliseconds); } } } \ No newline at end of file From 08e11ec0aaaad342c510dbbd120ed83f523e86b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 14:42:32 +0000 Subject: [PATCH 15/28] feat: Introduce NuGet package locking for reproducible builds --- Rasp.Core.Tests/packages.lock.json | 166 ++++++++++ .../packages.lock.json | 104 ++++++ src/Rasp.Benchmarks/packages.lock.json | 302 ++++++++++++++++++ src/Rasp.Bootstrapper/packages.lock.json | 154 +++++++++ src/Rasp.Core/packages.lock.json | 37 +++ .../packages.lock.json | 146 +++++++++ 6 files changed, 909 insertions(+) create mode 100644 Rasp.Core.Tests/packages.lock.json create mode 100644 Rasp.Instrumentation.Grpc.Tests/packages.lock.json create mode 100644 src/Rasp.Benchmarks/packages.lock.json create mode 100644 src/Rasp.Bootstrapper/packages.lock.json create mode 100644 src/Rasp.Core/packages.lock.json create mode 100644 src/Rasp.Instrumentation.Grpc/packages.lock.json diff --git a/Rasp.Core.Tests/packages.lock.json b/Rasp.Core.Tests/packages.lock.json new file mode 100644 index 0000000..0754f58 --- /dev/null +++ b/Rasp.Core.Tests/packages.lock.json @@ -0,0 +1,166 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" + }, + "Microsoft.Extensions.Logging": { + "type": "Direct", + "requested": "[10.0.2, )", + "resolved": "10.0.2", + "contentHash": "a0EWuBs6D3d7XMGroDXm+WsAi5CVVfjOJvyxurzWnuhBN9CO+1qHKcrKV1JK7H/T4ZtHIoVCOX/YyWM8K87qtw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "10.0.2", + "Microsoft.Extensions.Options": "10.0.2" + } + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", + "dependencies": { + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.4, )", + "resolved": "3.1.4", + "contentHash": "5mj99LvCqrq3CNi06xYdyIAXOEh+5b33F2nErCzI5zWiDdLHXiPXEWFSUAF8zlIv0ZWqjZNCwHTQeAPYbF3pCg==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.2", + "contentHash": "J/Zmp6fY93JbaiZ11ckWvcyxMPjD6XVwIHQXBjryTBgn7O6O20HYg9uVLFcZlNfgH78MnreE/7EH+hjfzn7VyA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.2", + "contentHash": "zOIurr59+kUf9vNcsUkCvKWZv+fPosUZXURZesYkJCvl0EzTc9F7maAO4Cd2WEV7ZJJ0AZrFQvuH6Npph9wdBw==" + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "QMoMrkNpnQym5mpfdxfxpRDuqLpsOuztguFvzH9p+Ex+do+uLFoi7UkAsBO4e9/tNR3eMFraFf2fOAi2cp3jjA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.2", + "contentHash": "RZkez/JjpnO+MZ6efKkSynN6ZztLpw3WbxNzjLCPBd97wWj1S9ZYPWi0nmT4kWBRa6atHsdM1ydGkUr8GudyDQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.2", + "contentHash": "1De2LJjmxdqopI5AYC5dIhoZQ79AR5ayywxNF1rXrXFtKQfbQOV9+n/IsZBa7qWlr0MqoGpW8+OY2v/57udZOA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.2", + "Microsoft.Extensions.Primitives": "10.0.2" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.2", + "contentHash": "QmSiO+oLBEooGgB3i0GRXyeYRDHjllqt3k365jwfZlYWhvSHA3UL2NEVV5m8aZa041eIlblo6KMI5txvTMpTwA==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "rasp.core": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + } + } + } + } +} \ No newline at end of file diff --git a/Rasp.Instrumentation.Grpc.Tests/packages.lock.json b/Rasp.Instrumentation.Grpc.Tests/packages.lock.json new file mode 100644 index 0000000..bbb685d --- /dev/null +++ b/Rasp.Instrumentation.Grpc.Tests/packages.lock.json @@ -0,0 +1,104 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", + "dependencies": { + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.4, )", + "resolved": "3.1.4", + "contentHash": "5mj99LvCqrq3CNi06xYdyIAXOEh+5b33F2nErCzI5zWiDdLHXiPXEWFSUAF8zlIv0ZWqjZNCwHTQeAPYbF3pCg==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + } + } + } +} \ No newline at end of file diff --git a/src/Rasp.Benchmarks/packages.lock.json b/src/Rasp.Benchmarks/packages.lock.json new file mode 100644 index 0000000..90e1c43 --- /dev/null +++ b/src/Rasp.Benchmarks/packages.lock.json @@ -0,0 +1,302 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "BenchmarkDotNet": { + "type": "Direct", + "requested": "[0.15.8, )", + "resolved": "0.15.8", + "contentHash": "paCfrWxSeHqn3rUZc0spYXVFnHCF0nzRhG0nOLnyTjZYs8spsimBaaNmb3vwqvALKIplbYq/TF393vYiYSnh/Q==", + "dependencies": { + "BenchmarkDotNet.Annotations": "0.15.8", + "CommandLineParser": "2.9.1", + "Gee.External.Capstone": "2.3.0", + "Iced": "1.21.0", + "Microsoft.CodeAnalysis.CSharp": "4.14.0", + "Microsoft.Diagnostics.Runtime": "3.1.512801", + "Microsoft.Diagnostics.Tracing.TraceEvent": "3.1.21", + "Microsoft.DotNet.PlatformAbstractions": "3.1.6", + "Perfolizer": "[0.6.1]", + "System.Management": "9.0.5" + } + }, + "Moq": { + "type": "Direct", + "requested": "[4.20.72, )", + "resolved": "4.20.72", + "contentHash": "EA55cjyNn8eTNWrgrdZJH5QLFp2L43oxl1tlkoYUKIE9pRwL784OWiTXeCV5ApS+AMYEAlt7Fo03A2XfouvHmQ==", + "dependencies": { + "Castle.Core": "5.1.1" + } + }, + "BenchmarkDotNet.Annotations": { + "type": "Transitive", + "resolved": "0.15.8", + "contentHash": "hfucY0ycAsB0SsoaZcaAp9oq5wlWBJcylvEJb9pmvdYUx6PD6S4mDiYnZWjdjAlLhIpe/xtGCwzORfzAzPqvzA==" + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + } + }, + "CommandLineParser": { + "type": "Transitive", + "resolved": "2.9.1", + "contentHash": "OE0sl1/sQ37bjVsPKKtwQlWDgqaxWgtme3xZz7JssWUzg5JpMIyHgCTY9MVMxOg48fJ1AgGT3tgdH5m/kQ5xhA==" + }, + "Gee.External.Capstone": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "2ap/rYmjtzCOT8hxrnEW/QeiOt+paD8iRrIcdKX0cxVwWLFa1e+JDBNeECakmccXrSFeBQuu5AV8SNkipFMMMw==" + }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.33.2", + "contentHash": "vZXVbrZgBqUkP5iWQi0CS6pucIS2MQdEYPS1duWCo8fGrrt4th6HTiHfLFX2RmAWAQl1oUnzGgyDBsfq7fHQJA==" + }, + "Grpc.AspNetCore": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "B4wAbNtAuHNiHAMxLFWL74wUElzNOOboFnypalqpX76piCOGz/w5FpilbVVYGboI4Qgl4ZmZsvDZ1zLwHNsjnw==", + "dependencies": { + "Google.Protobuf": "3.30.2", + "Grpc.AspNetCore.Server.ClientFactory": "2.71.0", + "Grpc.Tools": "2.71.0" + } + }, + "Grpc.AspNetCore.Server": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "kv+9YVB6MqDYWIcstXvWrT7Xc1si/sfINzzSxvQfjC3aei+92gXDUXCH/Q+TEvi4QSICRqu92BYcrXUBW7cuOw==", + "dependencies": { + "Grpc.Net.Common": "2.71.0" + } + }, + "Grpc.AspNetCore.Server.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "AHvMxoC+esO1e/nOYBjxvn0WDHAfglcVBjtkBy6ohgnV+PzkF8UdkPHE02xnyPFaSokWGZKnWzjgd00x6EZpyQ==", + "dependencies": { + "Grpc.AspNetCore.Server": "2.71.0", + "Grpc.Net.ClientFactory": "2.71.0" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "QquqUC37yxsDzd1QaDRsH2+uuznWPTS8CVE2Yzwl3CvU4geTNkolQXoVN812M2IwT6zpv3jsZRc9ExJFNFslTg==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "U1vr20r5ngoT9nlb7wejF28EKN+taMhJsV9XtK9MkiepTZwnKxxiarriiMfCHuDAfPUm9XUjFMn/RIuJ4YY61w==", + "dependencies": { + "Grpc.Net.Common": "2.71.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "8oPLwQLPo86fmcf9ghjCDyNsSWhtHc3CXa/AqwF8Su/pG7qAoeWWtbymsZhoNvCV9Zjzb6BDcIPKXLYt+O175g==", + "dependencies": { + "Grpc.Net.Client": "2.71.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "v0c8R97TwRYwNXlC8GyRXwYTCNufpDfUtj9la+wUrZFzVWkFJuNAltU+c0yI3zu0jl54k7en6u2WKgZgd57r2Q==", + "dependencies": { + "Grpc.Core.Api": "2.71.0" + } + }, + "Grpc.Tools": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "r8zHZm7kHdMrtujnkcuQ0BNDH2969at/8Va1ZzQgVblaQzR7tm8JlA3G+5Z5IFbvvf9PcAr1/VcoSR+g7j4Nyw==" + }, + "Iced": { + "type": "Transitive", + "resolved": "1.21.0", + "contentHash": "dv5+81Q1TBQvVMSOOOmRcjJmvWcX3BZPZsIq31+RLc5cNft0IHAyNlkdb7ZarOWG913PyBoFDsDXoCIlKmLclg==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.11.0", + "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "PC3tuwZYnC+idaPuoC/AZpEdwrtX7qFpmnrfQkgobGIWiYmGi5MCRtl5mx6QrfMGQpK78X2lfIEoZDLg/qnuHg==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.14.0", + "contentHash": "568a6wcTivauIhbeWcCwfWwIn7UV7MeHEBvFB2uzGIpM2OhJ4eM/FZ8KS0yhPoNxnSpjGzz7x7CIjTxhslojQA==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.11.0", + "Microsoft.CodeAnalysis.Common": "[4.14.0]" + } + }, + "Microsoft.Diagnostics.NETCore.Client": { + "type": "Transitive", + "resolved": "0.2.510501", + "contentHash": "juoqJYMDs+lRrrZyOkXXMImJHneCF23cuvO4waFRd2Ds7j+ZuGIPbJm0Y/zz34BdeaGiiwGWraMUlln05W1PCQ==", + "dependencies": { + "Microsoft.Extensions.Logging": "6.0.0" + } + }, + "Microsoft.Diagnostics.Runtime": { + "type": "Transitive", + "resolved": "3.1.512801", + "contentHash": "0lMUDr2oxNZa28D6NH5BuSQEe5T9tZziIkvkD44YkkCGQXPJqvFjLq5ZQq1hYLl3RjQJrY+hR0jFgap+EWPDTw==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.410101" + } + }, + "Microsoft.Diagnostics.Tracing.TraceEvent": { + "type": "Transitive", + "resolved": "3.1.21", + "contentHash": "/OrJFKaojSR6TkUKtwh8/qA9XWNtxLrXMqvEb89dBSKCWjaGVTbKMYodIUgF5deCEtmd6GXuRerciXGl5bhZ7Q==", + "dependencies": { + "Microsoft.Diagnostics.NETCore.Client": "0.2.510501", + "System.Reflection.TypeExtensions": "4.7.0" + } + }, + "Microsoft.DotNet.PlatformAbstractions": { + "type": "Transitive", + "resolved": "3.1.6", + "contentHash": "jek4XYaQ/PGUwDKKhwR8K47Uh1189PFzMeLqO83mXrXQVIpARZCcfuDedH50YDTepBkfijCZN5U/vZi++erxtg==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "QMoMrkNpnQym5mpfdxfxpRDuqLpsOuztguFvzH9p+Ex+do+uLFoi7UkAsBO4e9/tNR3eMFraFf2fOAi2cp3jjA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" + } + }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + }, + "Perfolizer": { + "type": "Transitive", + "resolved": "0.6.1", + "contentHash": "CR1QmWg4XYBd1Pb7WseP+sDmV8nGPwvmowKynExTqr3OuckIGVMhvmN4LC5PGzfXqDlR295+hz/T7syA1CxEqA==", + "dependencies": { + "Pragmastat": "3.2.4" + } + }, + "Pragmastat": { + "type": "Transitive", + "resolved": "3.2.4", + "contentHash": "I5qFifWw/gaTQT52MhzjZpkm/JPlfjSeO/DTZJjO7+hTKI+0aGRgOgZ3NN6D96dDuuqbIAZSeA5RimtHjqrA2A==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "9.0.5", + "contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==", + "dependencies": { + "System.CodeDom": "9.0.5" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "VybpaOQQhqE6siHppMktjfGBw1GCwvCqiufqmP8F1nj7fTUNtW35LOEt3UZTEsECfo+ELAl/9o9nJx3U91i7vA==" + }, + "librarysystem.contracts": { + "type": "Project", + "dependencies": { + "Google.Protobuf": "[3.33.2, )", + "Grpc.AspNetCore": "[2.71.0, )" + } + }, + "rasp.core": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + } + }, + "rasp.instrumentation.grpc": { + "type": "Project", + "dependencies": { + "Grpc.AspNetCore": "[2.71.0, )", + "Rasp.Core": "[1.0.0, )" + } + } + } + } +} \ No newline at end of file diff --git a/src/Rasp.Bootstrapper/packages.lock.json b/src/Rasp.Bootstrapper/packages.lock.json new file mode 100644 index 0000000..1fb92b3 --- /dev/null +++ b/src/Rasp.Bootstrapper/packages.lock.json @@ -0,0 +1,154 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + }, + "Microsoft.Extensions.Options": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" + } + }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.2", + "contentHash": "Y2aOVLIt75yeeEWigg9V9YnjsEm53sADtLGq0gLhwaXpk3iu8tYSoauolyhenagA2sWno2TQ2WujI0HQd6s1Vw==" + }, + "Grpc.AspNetCore": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "B4wAbNtAuHNiHAMxLFWL74wUElzNOOboFnypalqpX76piCOGz/w5FpilbVVYGboI4Qgl4ZmZsvDZ1zLwHNsjnw==", + "dependencies": { + "Google.Protobuf": "3.30.2", + "Grpc.AspNetCore.Server.ClientFactory": "2.71.0", + "Grpc.Tools": "2.71.0" + } + }, + "Grpc.AspNetCore.Server": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "kv+9YVB6MqDYWIcstXvWrT7Xc1si/sfINzzSxvQfjC3aei+92gXDUXCH/Q+TEvi4QSICRqu92BYcrXUBW7cuOw==", + "dependencies": { + "Grpc.Net.Common": "2.71.0" + } + }, + "Grpc.AspNetCore.Server.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "AHvMxoC+esO1e/nOYBjxvn0WDHAfglcVBjtkBy6ohgnV+PzkF8UdkPHE02xnyPFaSokWGZKnWzjgd00x6EZpyQ==", + "dependencies": { + "Grpc.AspNetCore.Server": "2.71.0", + "Grpc.Net.ClientFactory": "2.71.0" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "QquqUC37yxsDzd1QaDRsH2+uuznWPTS8CVE2Yzwl3CvU4geTNkolQXoVN812M2IwT6zpv3jsZRc9ExJFNFslTg==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "U1vr20r5ngoT9nlb7wejF28EKN+taMhJsV9XtK9MkiepTZwnKxxiarriiMfCHuDAfPUm9XUjFMn/RIuJ4YY61w==", + "dependencies": { + "Grpc.Net.Common": "2.71.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "8oPLwQLPo86fmcf9ghjCDyNsSWhtHc3CXa/AqwF8Su/pG7qAoeWWtbymsZhoNvCV9Zjzb6BDcIPKXLYt+O175g==", + "dependencies": { + "Grpc.Net.Client": "2.71.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "v0c8R97TwRYwNXlC8GyRXwYTCNufpDfUtj9la+wUrZFzVWkFJuNAltU+c0yI3zu0jl54k7en6u2WKgZgd57r2Q==", + "dependencies": { + "Grpc.Core.Api": "2.71.0" + } + }, + "Grpc.Tools": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "r8zHZm7kHdMrtujnkcuQ0BNDH2969at/8Va1ZzQgVblaQzR7tm8JlA3G+5Z5IFbvvf9PcAr1/VcoSR+g7j4Nyw==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "QMoMrkNpnQym5mpfdxfxpRDuqLpsOuztguFvzH9p+Ex+do+uLFoi7UkAsBO4e9/tNR3eMFraFf2fOAi2cp3jjA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" + } + }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + }, + "rasp.core": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + } + }, + "rasp.instrumentation.grpc": { + "type": "Project", + "dependencies": { + "Grpc.AspNetCore": "[2.71.0, )", + "Rasp.Core": "[1.0.0, )" + } + } + } + } +} \ No newline at end of file diff --git a/src/Rasp.Core/packages.lock.json b/src/Rasp.Core/packages.lock.json new file mode 100644 index 0000000..b4b7145 --- /dev/null +++ b/src/Rasp.Core/packages.lock.json @@ -0,0 +1,37 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "QMoMrkNpnQym5mpfdxfxpRDuqLpsOuztguFvzH9p+Ex+do+uLFoi7UkAsBO4e9/tNR3eMFraFf2fOAi2cp3jjA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + } + } + } +} \ No newline at end of file diff --git a/src/Rasp.Instrumentation.Grpc/packages.lock.json b/src/Rasp.Instrumentation.Grpc/packages.lock.json new file mode 100644 index 0000000..4c821f4 --- /dev/null +++ b/src/Rasp.Instrumentation.Grpc/packages.lock.json @@ -0,0 +1,146 @@ +{ + "version": 1, + "dependencies": { + "net10.0": { + "Grpc.AspNetCore": { + "type": "Direct", + "requested": "[2.71.0, )", + "resolved": "2.71.0", + "contentHash": "B4wAbNtAuHNiHAMxLFWL74wUElzNOOboFnypalqpX76piCOGz/w5FpilbVVYGboI4Qgl4ZmZsvDZ1zLwHNsjnw==", + "dependencies": { + "Google.Protobuf": "3.30.2", + "Grpc.AspNetCore.Server.ClientFactory": "2.71.0", + "Grpc.Tools": "2.71.0" + } + }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.2", + "contentHash": "Y2aOVLIt75yeeEWigg9V9YnjsEm53sADtLGq0gLhwaXpk3iu8tYSoauolyhenagA2sWno2TQ2WujI0HQd6s1Vw==" + }, + "Grpc.AspNetCore.Server": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "kv+9YVB6MqDYWIcstXvWrT7Xc1si/sfINzzSxvQfjC3aei+92gXDUXCH/Q+TEvi4QSICRqu92BYcrXUBW7cuOw==", + "dependencies": { + "Grpc.Net.Common": "2.71.0" + } + }, + "Grpc.AspNetCore.Server.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "AHvMxoC+esO1e/nOYBjxvn0WDHAfglcVBjtkBy6ohgnV+PzkF8UdkPHE02xnyPFaSokWGZKnWzjgd00x6EZpyQ==", + "dependencies": { + "Grpc.AspNetCore.Server": "2.71.0", + "Grpc.Net.ClientFactory": "2.71.0" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "QquqUC37yxsDzd1QaDRsH2+uuznWPTS8CVE2Yzwl3CvU4geTNkolQXoVN812M2IwT6zpv3jsZRc9ExJFNFslTg==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "U1vr20r5ngoT9nlb7wejF28EKN+taMhJsV9XtK9MkiepTZwnKxxiarriiMfCHuDAfPUm9XUjFMn/RIuJ4YY61w==", + "dependencies": { + "Grpc.Net.Common": "2.71.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "8oPLwQLPo86fmcf9ghjCDyNsSWhtHc3CXa/AqwF8Su/pG7qAoeWWtbymsZhoNvCV9Zjzb6BDcIPKXLYt+O175g==", + "dependencies": { + "Grpc.Net.Client": "2.71.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "v0c8R97TwRYwNXlC8GyRXwYTCNufpDfUtj9la+wUrZFzVWkFJuNAltU+c0yI3zu0jl54k7en6u2WKgZgd57r2Q==", + "dependencies": { + "Grpc.Core.Api": "2.71.0" + } + }, + "Grpc.Tools": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "r8zHZm7kHdMrtujnkcuQ0BNDH2969at/8Va1ZzQgVblaQzR7tm8JlA3G+5Z5IFbvvf9PcAr1/VcoSR+g7j4Nyw==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "QMoMrkNpnQym5mpfdxfxpRDuqLpsOuztguFvzH9p+Ex+do+uLFoi7UkAsBO4e9/tNR3eMFraFf2fOAi2cp3jjA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" + } + }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "6.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", + "Microsoft.Extensions.Primitives": "10.0.1" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + }, + "rasp.core": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + } + } + } + } +} \ No newline at end of file From 651677756327c8d2eec1da5c5abb7b6ec94f686d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 14:42:32 +0000 Subject: [PATCH 16/28] style: Apply consistent formatting and whitespace adjustments --- src/Rasp.Benchmarks/InterceptorBenchmarks.cs | 40 +++++++++---------- src/Rasp.Bootstrapper/Native/NativeGuard.cs | 6 +-- .../RaspDependencyInjection.cs | 2 +- src/Rasp.Core/Engine/Sql/SqlHeuristics.cs | 16 ++++---- src/Rasp.Core/Engine/Sql/SqlNormalizer.cs | 8 ++-- .../Engine/SqlInjectionDetectionEngine.cs | 10 ++--- .../Interceptors/SecurityInterceptor.cs | 2 +- 7 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs index 2d0cc8b..e3a764d 100644 --- a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs +++ b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs @@ -35,11 +35,11 @@ public class FakeServerCallContext : ServerCallContext protected override Status StatusCore { get; set; } protected override WriteOptions? WriteOptionsCore { get; set; } protected override AuthContext AuthContextCore => new AuthContext(string.Empty, new Dictionary>()); - - protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions? options) + + protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions? options) => throw new NotImplementedException(); - - protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) + + protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) => Task.CompletedTask; } @@ -51,13 +51,13 @@ public class InterceptorBenchmarks private SecurityInterceptor _realInterceptor = null!; private SecurityInterceptor _baselineInterceptor = null!; private readonly FakeServerCallContext _context = new(); - + private CreateBookRequest _safeRequest = null!; private CreateBookRequest _attackRequest = null!; private UnaryServerMethod _continuationDelegate = null!; - [Params(100, 1000, 10000, 100000)] + [Params(100, 1000, 10000, 100000)] public int PayloadSize { get; set; } [GlobalSetup] @@ -65,26 +65,26 @@ public void Setup() { var sqlEngine = new SqlInjectionDetectionEngine(NullLogger.Instance); var metrics = new NoOpMetrics(); - - sqlEngine.Inspect("warmup ' OR 1=1"); + + sqlEngine.Inspect("warmup ' OR 1=1"); _realInterceptor = new SecurityInterceptor(sqlEngine, metrics); _baselineInterceptor = new SecurityInterceptor(new NoOpDetectionEngine(), metrics); var longString = new string('a', PayloadSize); - _safeRequest = new CreateBookRequest - { - Title = "Clean Code: " + longString, + _safeRequest = new CreateBookRequest + { + Title = "Clean Code: " + longString, Author = "Robert C. Martin", PublicationYear = 2008, Pages = 464, TotalCopies = 10 }; - _attackRequest = new CreateBookRequest - { - Title = "Exploit", + _attackRequest = new CreateBookRequest + { + Title = "Exploit", Author = longString + "' UNION SELECT 1, @@version -- ", PublicationYear = 2025, Pages = 1, @@ -99,8 +99,8 @@ public void Setup() public async Task Baseline_NoOp() { return await _baselineInterceptor.UnaryServerHandler( - _safeRequest, - _context, + _safeRequest, + _context, _continuationDelegate); } @@ -109,8 +109,8 @@ public async Task Baseline_NoOp() public async Task RASP_Safe_Traffic() { return await _realInterceptor.UnaryServerHandler( - _safeRequest, - _context, + _safeRequest, + _context, _continuationDelegate); } @@ -121,8 +121,8 @@ public async Task RASP_Block_Attack() try { await _realInterceptor.UnaryServerHandler( - _attackRequest, - _context, + _attackRequest, + _context, _continuationDelegate); } catch (RpcException) diff --git a/src/Rasp.Bootstrapper/Native/NativeGuard.cs b/src/Rasp.Bootstrapper/Native/NativeGuard.cs index c16f491..0a4812f 100644 --- a/src/Rasp.Bootstrapper/Native/NativeGuard.cs +++ b/src/Rasp.Bootstrapper/Native/NativeGuard.cs @@ -9,7 +9,7 @@ internal static partial class NativeGuard private const string DllName = "Rasp.Native.Guard.dll"; [LibraryImport(DllName)] - [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] private static partial int CheckEnvironment(); public static void AssertIntegrity(ILogger logger) @@ -17,7 +17,7 @@ public static void AssertIntegrity(ILogger logger) try { logger.LogInformation("[RASP NATIVE] 🔍 Checking process integrity..."); - + var status = CheckEnvironment(); if (status != 0) @@ -32,7 +32,7 @@ public static void AssertIntegrity(ILogger logger) logger.LogCritical("[RASP NATIVE] 🚨 Integrity Violation! Threat: {ThreatCode} - {Desc}", status, threat); throw new System.Security.SecurityException($"RASP Integrity Violation: {threat}"); } - + logger.LogInformation("[RASP NATIVE] ✅ Environment Clean. No debuggers detected."); } catch (DllNotFoundException) diff --git a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs index 6b32f6d..151a40d 100644 --- a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs +++ b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs @@ -35,7 +35,7 @@ public static IServiceCollection AddRasp( { options.Interceptors.Add(); }); - + services.AddHostedService(); return services; diff --git a/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs b/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs index 2049571..f724d7e 100644 --- a/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs +++ b/src/Rasp.Core/Engine/Sql/SqlHeuristics.cs @@ -14,13 +14,13 @@ internal static class SqlHeuristics // High-risk keywords: Structural SQL commands. // Detecting these implies a high probability of an injection attempt. - private static readonly string[] HighRiskTokens = + private static readonly string[] HighRiskTokens = [ - "union select", - "insert into", - "delete from", - "drop table", - "exec(", + "union select", + "insert into", + "delete from", + "drop table", + "exec(", "xp_cmdshell", "waitfor delay" ]; @@ -28,7 +28,7 @@ internal static class SqlHeuristics // Contextual patterns: Specific sequences targeting quote breakouts. // We prioritize these checks to solve the "O'Reilly" false positive problem: // we only flag quotes that are immediately followed by SQL syntax. - private static readonly string[] ContextualPatterns = + private static readonly string[] ContextualPatterns = [ "' or", // Tautology: admin' OR '1'='1 "' and", // Tautology: admin' AND 1=1 @@ -59,7 +59,7 @@ public static double CalculateScore(ReadOnlySpan normalizedInput) // For now, linear scanning is acceptable as it remains Zero-Alloc. if (normalizedInput.Contains(pattern.AsSpan(), StringComparison.Ordinal)) { - return CriticalThreat; + return CriticalThreat; } } diff --git a/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs b/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs index d0c3212..08a8911 100644 --- a/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs +++ b/src/Rasp.Core/Engine/Sql/SqlNormalizer.cs @@ -25,12 +25,12 @@ public static int Normalize(ReadOnlySpan input, Span output) { if (written >= maxLen) { - break; + break; } char c = input[i]; - if (!char.IsAscii(c)) + if (!char.IsAscii(c)) { output[written++] = c; lastWasSpace = false; @@ -38,9 +38,9 @@ public static int Normalize(ReadOnlySpan input, Span output) } // --- ASCII LOGIC --- - + // Collapse Whitespace (Tab, NewLine, Space) - if (c <= ' ') + if (c <= ' ') { if (!lastWasSpace) { diff --git a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs index 5ea9c17..40b5c0f 100644 --- a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs +++ b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs @@ -17,11 +17,11 @@ namespace Rasp.Core.Engine; /// public class SqlInjectionDetectionEngine(ILogger logger) : IDetectionEngine { - private static readonly SearchValues DangerousChars = + private static readonly SearchValues DangerousChars = SearchValues.Create("'-;/*"); - private const int MaxStackAllocSize = 1024; - private const int MaxAnalysisLength = 4096; + private const int MaxStackAllocSize = 1024; + private const int MaxAnalysisLength = 4096; /// /// Inspects the provided payload for SQL Injection patterns. @@ -49,7 +49,7 @@ public class SqlInjectionDetectionEngine(ILogger lo /// public DetectionResult Inspect(string? payload, string context = "Unknown") { - if (string.IsNullOrEmpty(payload)) + if (string.IsNullOrEmpty(payload)) return DetectionResult.Safe(); var inputSpan = payload.AsSpan(); @@ -80,7 +80,7 @@ public DetectionResult Inspect(string? payload, string context = "Unknown") if (!(score >= 1.0)) return DetectionResult.Safe(); logger.LogWarning("⚔️ RASP Blocked SQLi! Score: {Score} Context: {Context}", score, context); - + // FIX 2: Usando o Factory Method estático que resolve os erros de inicialização return DetectionResult.Threat( threatType: "SQL Injection", diff --git a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs index 5eaf7af..fd71ed7 100644 --- a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs +++ b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs @@ -33,7 +33,7 @@ public override async Task UnaryServerHandler( { if (request is not IMessage protoMessage) return await continuation(request, context); var fields = protoMessage.Descriptor.Fields.InFieldNumberOrder(); - + foreach (var field in fields) { if (field.FieldType != FieldType.String) continue; From a7dc437e819276c6ed5d88e20e6d3ab1b3443e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:56 +0000 Subject: [PATCH 17/28] feat: Add Directory.Build.props for consistent project settings --- src/Directory.Build.props | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/Directory.Build.props diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..819230b --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,27 @@ + + + preview + enable + enable + true + true + + + + latest-all + All + true + + + + João Botelho + RASP.Net + RASP.Net High Performance Security + Copyright © 2026 + MIT + true + true + true + snupkg + + \ No newline at end of file From fa61775b1f25a09727a65ca561758343b2af912f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:56 +0000 Subject: [PATCH 18/28] refactor: Configure Rasp.Benchmarks project --- src/Rasp.Benchmarks/Rasp.Benchmarks.csproj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj b/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj index 9b99a51..bbc6763 100644 --- a/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj +++ b/src/Rasp.Benchmarks/Rasp.Benchmarks.csproj @@ -1,5 +1,15 @@  + + Exe + net10.0 + enable + enable + false + + $(NoWarn);CA1515;CA1707;CA1822 + + From 77bb52754170592dcff6aed7a5896bb3822fde23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:56 +0000 Subject: [PATCH 19/28] refactor: Make NativeGuard injectable and improve logging --- .../Configuration/RaspIntegrityService.cs | 5 +- src/Rasp.Bootstrapper/Native/NativeGuard.cs | 58 +++++++++++-------- .../RaspDependencyInjection.cs | 3 + 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs b/src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs index 808603a..0965c1c 100644 --- a/src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs +++ b/src/Rasp.Bootstrapper/Configuration/RaspIntegrityService.cs @@ -1,14 +1,13 @@ using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Rasp.Bootstrapper.Native; namespace Rasp.Bootstrapper.Configuration; -public class RaspIntegrityService(ILogger logger) : IHostedService +public class RaspIntegrityService(NativeGuard nativeGuard) : IHostedService { public Task StartAsync(CancellationToken cancellationToken) { - NativeGuard.AssertIntegrity(logger); + nativeGuard.AssertIntegrity(); return Task.CompletedTask; } diff --git a/src/Rasp.Bootstrapper/Native/NativeGuard.cs b/src/Rasp.Bootstrapper/Native/NativeGuard.cs index 0a4812f..8998f67 100644 --- a/src/Rasp.Bootstrapper/Native/NativeGuard.cs +++ b/src/Rasp.Bootstrapper/Native/NativeGuard.cs @@ -1,47 +1,59 @@ using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; namespace Rasp.Bootstrapper.Native; -internal static partial class NativeGuard +public partial class NativeGuard(ILogger logger) { - private const string DllName = "Rasp.Native.Guard.dll"; + private const string LibraryName = "Rasp.Native.Guard.dll"; - [LibraryImport(DllName)] - [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] + [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] + [LibraryImport(LibraryName, EntryPoint = "CheckEnvironment")] + [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] private static partial int CheckEnvironment(); - public static void AssertIntegrity(ILogger logger) + public void AssertIntegrity() { + LogStartingCheck(); + try { - logger.LogInformation("[RASP NATIVE] 🔍 Checking process integrity..."); - - var status = CheckEnvironment(); + int result = CheckEnvironment(); - if (status != 0) + if (result != 0) { - var threat = status switch - { - 101 => "Basic Debugger (PEB Flag)", - 102 => "Remote Debugger (Debug Port)", - _ => "Unknown Anomaly" - }; - - logger.LogCritical("[RASP NATIVE] 🚨 Integrity Violation! Threat: {ThreatCode} - {Desc}", status, threat); - throw new System.Security.SecurityException($"RASP Integrity Violation: {threat}"); + LogIntegrityViolation(result); + } + else + { + LogIntegrityVerified(); } - - logger.LogInformation("[RASP NATIVE] ✅ Environment Clean. No debuggers detected."); } catch (DllNotFoundException) { - logger.LogWarning("[RASP NATIVE] ⚠️ Guard DLL not found. Skipping OS-level checks."); + LogNativeLibMissing(); } +#pragma warning disable CA1031 catch (Exception ex) +#pragma warning restore CA1031 { - logger.LogError(ex, "[RASP NATIVE] ❌ Failed to execute native checks."); + LogIntegrityCheckFailed(ex); } } + + // --- LOGGING (Instance Methods) --- + [LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "🛡️ Initializing Native Integrity Guard...")] + private partial void LogStartingCheck(); + + [LoggerMessage(EventId = 2, Level = LogLevel.Critical, Message = "🚨 NATIVE INTEGRITY VIOLATION DETECTED! Code: {Result}")] + private partial void LogIntegrityViolation(int result); + + [LoggerMessage(EventId = 3, Level = LogLevel.Information, Message = "✅ Native Environment Integrity Verified.")] + private partial void LogIntegrityVerified(); + + [LoggerMessage(EventId = 4, Level = LogLevel.Warning, Message = "⚠️ Native Guard library not found. Running in Managed-Only mode.")] + private partial void LogNativeLibMissing(); + + [LoggerMessage(EventId = 5, Level = LogLevel.Error, Message = "❌ Failed to execute Native Guard check.")] + private partial void LogIntegrityCheckFailed(Exception ex); } \ No newline at end of file diff --git a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs index 151a40d..da8b03c 100644 --- a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs +++ b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs @@ -1,6 +1,7 @@ using Grpc.AspNetCore.Server; using Microsoft.Extensions.DependencyInjection; using Rasp.Bootstrapper.Configuration; +using Rasp.Bootstrapper.Native; using Rasp.Core; using Rasp.Instrumentation.Grpc.Interceptors; @@ -28,6 +29,8 @@ public static IServiceCollection AddRasp( } services.AddRaspCore(); + + services.AddSingleton(); services.AddSingleton(); From 311e7ed7d5a1d5f3bacd484d0c3914627041839e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:57 +0000 Subject: [PATCH 20/28] perf: Adopt LoggerMessage for SqlInjectionDetectionEngine --- src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs index 40b5c0f..bea3919 100644 --- a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs +++ b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs @@ -15,7 +15,7 @@ namespace Rasp.Core.Engine; /// and zero heap allocations for the vast majority of legitimate traffic. /// /// -public class SqlInjectionDetectionEngine(ILogger logger) : IDetectionEngine +public partial class SqlInjectionDetectionEngine(ILogger logger) : IDetectionEngine { private static readonly SearchValues DangerousChars = SearchValues.Create("'-;/*"); @@ -79,9 +79,8 @@ public DetectionResult Inspect(string? payload, string context = "Unknown") double score = SqlHeuristics.CalculateScore(searchSpace); if (!(score >= 1.0)) return DetectionResult.Safe(); - logger.LogWarning("⚔️ RASP Blocked SQLi! Score: {Score} Context: {Context}", score, context); + LogBlockedSqlInjection(logger, score, context); - // FIX 2: Usando o Factory Method estático que resolve os erros de inicialização return DetectionResult.Threat( threatType: "SQL Injection", description: $"SQL Injection Patterns Detected (Score: {score})", @@ -99,4 +98,10 @@ public DetectionResult Inspect(string? payload, string context = "Unknown") } } } + + [LoggerMessage( + EventId = 1, + Level = LogLevel.Warning, + Message = "⚔️ RASP Blocked SQLi! Score: {Score} Context: {Context}")] + private static partial void LogBlockedSqlInjection(ILogger logger, double score, string context); } \ No newline at end of file From 2bac0b8d8b880518077f18ca0faa5253fba36c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:57 +0000 Subject: [PATCH 21/28] chore: Update Microsoft.Extensions.* dependencies to 10.0.1 --- src/Rasp.Benchmarks/packages.lock.json | 27 ++++++++++--------- src/Rasp.Bootstrapper/packages.lock.json | 27 ++++++++++--------- src/Rasp.Core/Rasp.Core.csproj | 7 +---- src/Rasp.Core/packages.lock.json | 27 +++++++++++++++++++ .../packages.lock.json | 27 ++++++++++--------- 5 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/Rasp.Benchmarks/packages.lock.json b/src/Rasp.Benchmarks/packages.lock.json index 90e1c43..89d181c 100644 --- a/src/Rasp.Benchmarks/packages.lock.json +++ b/src/Rasp.Benchmarks/packages.lock.json @@ -179,10 +179,10 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "10.0.1", + "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { @@ -212,19 +212,21 @@ }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "resolved": "10.0.1", + "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "6.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Microsoft.Extensions.Options": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + } }, "Microsoft.Extensions.Options": { "type": "Transitive", @@ -287,7 +289,8 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging": "[10.0.1, )" } }, "rasp.instrumentation.grpc": { diff --git a/src/Rasp.Bootstrapper/packages.lock.json b/src/Rasp.Bootstrapper/packages.lock.json index 1fb92b3..8d6cd03 100644 --- a/src/Rasp.Bootstrapper/packages.lock.json +++ b/src/Rasp.Bootstrapper/packages.lock.json @@ -88,10 +88,10 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "10.0.1", + "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { @@ -116,19 +116,21 @@ }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "resolved": "10.0.1", + "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "6.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Microsoft.Extensions.Options": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", @@ -139,7 +141,8 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging": "[10.0.1, )" } }, "rasp.instrumentation.grpc": { diff --git a/src/Rasp.Core/Rasp.Core.csproj b/src/Rasp.Core/Rasp.Core.csproj index ffb1843..7dfe4dd 100644 --- a/src/Rasp.Core/Rasp.Core.csproj +++ b/src/Rasp.Core/Rasp.Core.csproj @@ -9,12 +9,7 @@ - - - - - C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.2\Microsoft.Extensions.Logging.Abstractions.dll - + diff --git a/src/Rasp.Core/packages.lock.json b/src/Rasp.Core/packages.lock.json index b4b7145..b5ae78a 100644 --- a/src/Rasp.Core/packages.lock.json +++ b/src/Rasp.Core/packages.lock.json @@ -18,6 +18,33 @@ "Microsoft.Extensions.Options": "10.0.1" } }, + "Microsoft.Extensions.Logging": { + "type": "Direct", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + } + }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "10.0.1", diff --git a/src/Rasp.Instrumentation.Grpc/packages.lock.json b/src/Rasp.Instrumentation.Grpc/packages.lock.json index 4c821f4..91504d0 100644 --- a/src/Rasp.Instrumentation.Grpc/packages.lock.json +++ b/src/Rasp.Instrumentation.Grpc/packages.lock.json @@ -73,10 +73,10 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "10.0.1", + "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { @@ -106,19 +106,21 @@ }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + "resolved": "10.0.1", + "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "6.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Microsoft.Extensions.Options": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "10.0.1", + "Microsoft.Extensions.Logging.Abstractions": "10.0.1", + "Microsoft.Extensions.Options": "10.0.1" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" + "resolved": "10.0.1", + "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + } }, "Microsoft.Extensions.Options": { "type": "Transitive", @@ -138,7 +140,8 @@ "type": "Project", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging": "[10.0.1, )" } } } From 5d5dbe71c9bd61ef928ef9e6e980ef56dff096b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:57 +0000 Subject: [PATCH 22/28] refactor: Rename Core DependencyInjection to RaspCoreExtensions --- src/Rasp.Core/{DependencyInjection.cs => RaspCoreExtensions.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Rasp.Core/{DependencyInjection.cs => RaspCoreExtensions.cs} (94%) diff --git a/src/Rasp.Core/DependencyInjection.cs b/src/Rasp.Core/RaspCoreExtensions.cs similarity index 94% rename from src/Rasp.Core/DependencyInjection.cs rename to src/Rasp.Core/RaspCoreExtensions.cs index 62739e1..ec26179 100644 --- a/src/Rasp.Core/DependencyInjection.cs +++ b/src/Rasp.Core/RaspCoreExtensions.cs @@ -6,7 +6,7 @@ namespace Rasp.Core; -public static class DependencyInjection +public static class RaspCoreExtensions { /// /// Registers the RASP Core services (Telemetry, Contracts). From 1a4ae2197d1d073fb57f63d37034f374d5883de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:57 +0000 Subject: [PATCH 23/28] style: Add ConfigureAwait(false) and ArgumentNullException checks --- src/Rasp.Benchmarks/InterceptorBenchmarks.cs | 6 +++--- .../Interceptors/SecurityInterceptor.cs | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs index e3a764d..2764665 100644 --- a/src/Rasp.Benchmarks/InterceptorBenchmarks.cs +++ b/src/Rasp.Benchmarks/InterceptorBenchmarks.cs @@ -101,7 +101,7 @@ public async Task Baseline_NoOp() return await _baselineInterceptor.UnaryServerHandler( _safeRequest, _context, - _continuationDelegate); + _continuationDelegate).ConfigureAwait(false); } [Benchmark] @@ -111,7 +111,7 @@ public async Task RASP_Safe_Traffic() return await _realInterceptor.UnaryServerHandler( _safeRequest, _context, - _continuationDelegate); + _continuationDelegate).ConfigureAwait(false); } [Benchmark] @@ -123,7 +123,7 @@ public async Task RASP_Block_Attack() await _realInterceptor.UnaryServerHandler( _attackRequest, _context, - _continuationDelegate); + _continuationDelegate).ConfigureAwait(false); } catch (RpcException) { diff --git a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs index fd71ed7..fcbd4d9 100644 --- a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs +++ b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs @@ -26,12 +26,15 @@ public override async Task UnaryServerHandler( ServerCallContext context, UnaryServerMethod continuation) { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(continuation); + var sw = Stopwatch.StartNew(); string method = context.Method; try { - if (request is not IMessage protoMessage) return await continuation(request, context); + if (request is not IMessage protoMessage) return await continuation(request, context).ConfigureAwait(false); var fields = protoMessage.Descriptor.Fields.InFieldNumberOrder(); foreach (var field in fields) @@ -50,7 +53,7 @@ public override async Task UnaryServerHandler( $"RASP Security Alert: {result.Description}")); } - return await continuation(request, context); + return await continuation(request, context).ConfigureAwait(false); } finally { From 2314b82ddae6534d8d5a31afba02b648d973418d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:58 +0000 Subject: [PATCH 24/28] fix: Suppress CA2000 warning in RaspMetrics --- src/Rasp.Core/Telemetry/RaspMetrics.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Rasp.Core/Telemetry/RaspMetrics.cs b/src/Rasp.Core/Telemetry/RaspMetrics.cs index ce58725..eee6213 100644 --- a/src/Rasp.Core/Telemetry/RaspMetrics.cs +++ b/src/Rasp.Core/Telemetry/RaspMetrics.cs @@ -18,7 +18,9 @@ public sealed class RaspMetrics : IRaspMetrics public RaspMetrics(IMeterFactory meterFactory) { +#pragma warning disable CA2000 var meter = meterFactory.Create(MeterName, "1.0.0"); +#pragma warning restore CA2000 _inspectionsCounter = meter.CreateCounter( "rasp.inspections.total", From 06917941aee670e9a0d13d480f849e7b82fbf5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:58 +0000 Subject: [PATCH 25/28] build: Relax warnings as errors in CI and add InternalsVisibleTo --- .github/workflows/build.yml | 2 +- src/Rasp.Core/InternalsVisibleTo.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/Rasp.Core/InternalsVisibleTo.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a31940d..f791040 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: run: dotnet restore ${{ env.SOLUTION_NAME }} - name: Build (Release) - run: dotnet build ${{ env.SOLUTION_NAME }} --configuration Release --no-restore /p:TreatWarningsAsErrors=true + run: dotnet build ${{ env.SOLUTION_NAME }} --configuration Release --no-restore - name: Run Tests run: dotnet test ${{ env.SOLUTION_NAME }} --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results-${{ matrix.os }}.trx" diff --git a/src/Rasp.Core/InternalsVisibleTo.cs b/src/Rasp.Core/InternalsVisibleTo.cs new file mode 100644 index 0000000..f18b889 --- /dev/null +++ b/src/Rasp.Core/InternalsVisibleTo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Rasp.Core.Tests")] + +[assembly: InternalsVisibleTo("Rasp.Benchmarks")] \ No newline at end of file From 74dea65a0b9a1ece1c7727d1b73ac99286aeb964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:21:58 +0000 Subject: [PATCH 26/28] feat(tests): Add Dependency Injection sanity tests for RASP Bootstrapper --- .../DependencyInjection/DiSanityTests.cs | 48 ++++++++ Rasp.Core.Tests/Rasp.Core.Tests.csproj | 2 + Rasp.Core.Tests/packages.lock.json | 104 +++++++++++++++++- 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs diff --git a/Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs b/Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs new file mode 100644 index 0000000..cd14fd0 --- /dev/null +++ b/Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs @@ -0,0 +1,48 @@ +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Rasp.Bootstrapper; + +namespace Rasp.Core.Tests.DependencyInjection; + +/// +/// Verifies the integrity of the Dependency Injection (DI) container configuration. +/// Ensures that all services and their dependencies are correctly registered and compatible. +/// +public class DiSanityTests +{ + /// + /// Ensures that registers all required services + /// without missing dependencies or causing scope violations. + /// + [Fact] + public void AddRasp_Should_Register_All_Dependencies_Correctly() + { + // 1. Arrange + var services = new ServiceCollection(); + + var config = new ConfigurationBuilder().Build(); + + services.AddSingleton(config); + services.AddLogging(); + + services.AddGrpc(); + + // 2. Act + services.AddRasp(opt => + { + opt.BlockOnDetection = true; + opt.EnableMetrics = false; + }); + + var options = new ServiceProviderOptions + { + ValidateOnBuild = true, + ValidateScopes = true + }; + + Action build = () => services.BuildServiceProvider(options); + + build.Should().NotThrow("the RASP dependency graph must be complete and valid"); + } +} \ No newline at end of file diff --git a/Rasp.Core.Tests/Rasp.Core.Tests.csproj b/Rasp.Core.Tests/Rasp.Core.Tests.csproj index 3606c9d..32f8c53 100644 --- a/Rasp.Core.Tests/Rasp.Core.Tests.csproj +++ b/Rasp.Core.Tests/Rasp.Core.Tests.csproj @@ -9,6 +9,7 @@ + @@ -20,6 +21,7 @@ + diff --git a/Rasp.Core.Tests/packages.lock.json b/Rasp.Core.Tests/packages.lock.json index 0754f58..988ddf6 100644 --- a/Rasp.Core.Tests/packages.lock.json +++ b/Rasp.Core.Tests/packages.lock.json @@ -8,6 +8,12 @@ "resolved": "6.0.4", "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" }, + "FluentAssertions": { + "type": "Direct", + "requested": "[8.8.0, )", + "resolved": "8.8.0", + "contentHash": "m0kwcqBwvVel03FuMa7Ozo/oTaxYbjeNlcOhQFkyQpwX/8wks6RNl/Jnn58DCZVs6c2oG1RsCZw7HfKSaxLm3w==" + }, "Microsoft.Extensions.Logging": { "type": "Direct", "requested": "[10.0.2, )", @@ -46,6 +52,74 @@ "resolved": "3.1.4", "contentHash": "5mj99LvCqrq3CNi06xYdyIAXOEh+5b33F2nErCzI5zWiDdLHXiPXEWFSUAF8zlIv0ZWqjZNCwHTQeAPYbF3pCg==" }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.30.2", + "contentHash": "Y2aOVLIt75yeeEWigg9V9YnjsEm53sADtLGq0gLhwaXpk3iu8tYSoauolyhenagA2sWno2TQ2WujI0HQd6s1Vw==" + }, + "Grpc.AspNetCore": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "B4wAbNtAuHNiHAMxLFWL74wUElzNOOboFnypalqpX76piCOGz/w5FpilbVVYGboI4Qgl4ZmZsvDZ1zLwHNsjnw==", + "dependencies": { + "Google.Protobuf": "3.30.2", + "Grpc.AspNetCore.Server.ClientFactory": "2.71.0", + "Grpc.Tools": "2.71.0" + } + }, + "Grpc.AspNetCore.Server": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "kv+9YVB6MqDYWIcstXvWrT7Xc1si/sfINzzSxvQfjC3aei+92gXDUXCH/Q+TEvi4QSICRqu92BYcrXUBW7cuOw==", + "dependencies": { + "Grpc.Net.Common": "2.71.0" + } + }, + "Grpc.AspNetCore.Server.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "AHvMxoC+esO1e/nOYBjxvn0WDHAfglcVBjtkBy6ohgnV+PzkF8UdkPHE02xnyPFaSokWGZKnWzjgd00x6EZpyQ==", + "dependencies": { + "Grpc.AspNetCore.Server": "2.71.0", + "Grpc.Net.ClientFactory": "2.71.0" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "QquqUC37yxsDzd1QaDRsH2+uuznWPTS8CVE2Yzwl3CvU4geTNkolQXoVN812M2IwT6zpv3jsZRc9ExJFNFslTg==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "U1vr20r5ngoT9nlb7wejF28EKN+taMhJsV9XtK9MkiepTZwnKxxiarriiMfCHuDAfPUm9XUjFMn/RIuJ4YY61w==", + "dependencies": { + "Grpc.Net.Common": "2.71.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "8oPLwQLPo86fmcf9ghjCDyNsSWhtHc3CXa/AqwF8Su/pG7qAoeWWtbymsZhoNvCV9Zjzb6BDcIPKXLYt+O175g==", + "dependencies": { + "Grpc.Net.Client": "2.71.0", + "Microsoft.Extensions.Http": "6.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "v0c8R97TwRYwNXlC8GyRXwYTCNufpDfUtj9la+wUrZFzVWkFJuNAltU+c0yI3zu0jl54k7en6u2WKgZgd57r2Q==", + "dependencies": { + "Grpc.Core.Api": "2.71.0" + } + }, + "Grpc.Tools": { + "type": "Transitive", + "resolved": "2.71.0", + "contentHash": "r8zHZm7kHdMrtujnkcuQ0BNDH2969at/8Va1ZzQgVblaQzR7tm8JlA3G+5Z5IFbvvf9PcAr1/VcoSR+g7j4Nyw==" + }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "17.14.1", @@ -73,6 +147,17 @@ "Microsoft.Extensions.Options": "10.0.1" } }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "15+pa2G0bAMHbHewaQIdr/y6ag2H3yh4rd9hTXavtWDzQBkvpe2RMqFg8BxDpcQWssmjmBApGPcw93QRz6YcMg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", + "Microsoft.Extensions.Logging": "6.0.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.0", + "Microsoft.Extensions.Options": "6.0.0" + } + }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "10.0.2", @@ -154,11 +239,28 @@ "xunit.extensibility.core": "[2.9.3]" } }, + "rasp.bootstrapper": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Options": "[10.0.1, )", + "Rasp.Core": "[1.0.0, )", + "Rasp.Instrumentation.Grpc": "[1.0.0, )" + } + }, "rasp.core": { "type": "Project", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.Diagnostics.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging": "[10.0.1, )" + } + }, + "rasp.instrumentation.grpc": { + "type": "Project", + "dependencies": { + "Grpc.AspNetCore": "[2.71.0, )", + "Rasp.Core": "[1.0.0, )" } } } From 7db75c65eb6105193e76e63c8c11f12bbb71cee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:27:11 +0000 Subject: [PATCH 27/28] build: Exclude 'modules' from dotnet format in CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f791040..bfe3c74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: # 2. Code Formatting (Enforce Style) - name: Verify Formatting - run: dotnet format ${{ env.SOLUTION_NAME }} --verify-no-changes --verbosity diagnostic + run: dotnet format ${{ env.SOLUTION_NAME }} --verify-no-changes --verbosity diagnostic --exclude ./modules # 3. CodeQL Analysis - name: Initialize CodeQL From 71bd1b52da1be5fd2dc13e39a0e731432a9996da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Botelho=20Gon=C3=A7alves?= Date: Wed, 4 Feb 2026 16:27:11 +0000 Subject: [PATCH 28/28] refactor: Apply whitespace and formatting fixes --- .../DependencyInjection/DiSanityTests.cs | 14 +++++++------- src/Rasp.Bootstrapper/RaspDependencyInjection.cs | 2 +- .../Engine/SqlInjectionDetectionEngine.cs | 6 +++--- .../Interceptors/SecurityInterceptor.cs | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs b/Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs index cd14fd0..c5f9f29 100644 --- a/Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs +++ b/Rasp.Core.Tests/DependencyInjection/DiSanityTests.cs @@ -20,18 +20,18 @@ public void AddRasp_Should_Register_All_Dependencies_Correctly() { // 1. Arrange var services = new ServiceCollection(); - - var config = new ConfigurationBuilder().Build(); - + + var config = new ConfigurationBuilder().Build(); + services.AddSingleton(config); services.AddLogging(); - - services.AddGrpc(); + + services.AddGrpc(); // 2. Act - services.AddRasp(opt => + services.AddRasp(opt => { - opt.BlockOnDetection = true; + opt.BlockOnDetection = true; opt.EnableMetrics = false; }); diff --git a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs index da8b03c..3d1d514 100644 --- a/src/Rasp.Bootstrapper/RaspDependencyInjection.cs +++ b/src/Rasp.Bootstrapper/RaspDependencyInjection.cs @@ -29,7 +29,7 @@ public static IServiceCollection AddRasp( } services.AddRaspCore(); - + services.AddSingleton(); services.AddSingleton(); diff --git a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs index bea3919..d046afd 100644 --- a/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs +++ b/src/Rasp.Core/Engine/SqlInjectionDetectionEngine.cs @@ -98,10 +98,10 @@ public DetectionResult Inspect(string? payload, string context = "Unknown") } } } - + [LoggerMessage( - EventId = 1, - Level = LogLevel.Warning, + EventId = 1, + Level = LogLevel.Warning, Message = "⚔️ RASP Blocked SQLi! Score: {Score} Context: {Context}")] private static partial void LogBlockedSqlInjection(ILogger logger, double score, string context); } \ No newline at end of file diff --git a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs index fcbd4d9..89ae910 100644 --- a/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs +++ b/src/Rasp.Instrumentation.Grpc/Interceptors/SecurityInterceptor.cs @@ -28,7 +28,7 @@ public override async Task UnaryServerHandler( { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(continuation); - + var sw = Stopwatch.StartNew(); string method = context.Method;