# SG/Sentinel Tabletop Simulation Part 2: Blocking Malicious Activity

**version** v0.27.58
**date** 18 May 2026
**from** Human (project lead)
**to** Architect, Developer (lead), Security, @Dev
**type** Arch brief (tabletop simulation)

---

## What This Is

Part 2 of the tabletop simulation, covering **blocking malicious activity**. Where part 1 traced benign and logging flows, this part traces attacks: probes, scans, brute-force, exploitation attempts, and the harder cases (in-profile attacks, fast-tracked-then-malicious). The purpose is the same: **surface gaps and contradictions before coding**, against the rules-of-the-game spec.

Run honestly, the attack simulations are where the most interesting gaps appear, particularly around the detect-before-damage window, the anomaly-scoring thresholds, and the cases where an attacker looks legitimate.

## Simulation 6: An Obvious-Bad Probe (Easy Win)

**The request:** `GET /etc/passwd` from IP 185.x.x.x.

**Layer 1:**
- Assigns ID `sim-0006`, logs.
- Deterministic rules: rule 0012 (path-never-valid) fires. `/etc/passwd` is never a valid request. Deterministic-certain.
- Decision: block immediately. Log block with reason "path never valid". Action: drop silently (give the attacker no feedback).

**Outcome:** blocked at Layer 1, sub-millisecond, logged. The easy win works perfectly.

**GAPS FOUND:**

- **GAP 6.1 (minor):** "Drop silently" means no response. But CloudFront/HTTP requires *some* response. What does "drop silently" actually return? A connection reset? A 403 with no body? A long delay then close? **Proposed resolution:** define the concrete "drop silently" behaviour at the HTTP level (likely a 403 with empty body, or a connection close; tarpit is a separate "wild goose chase" action). The block actions need concrete HTTP-level definitions.

## Simulation 7: A WordPress Scan On A Static Site

**The request:** a rapid series of requests: `GET /wp-login.php`, `GET /wp-admin/`, `GET /xmlrpc.php`, from IP 91.x.x.x, over 2 seconds.

**Layer 1 (per request):**
- Each request: assigns ID, logs.
- Deterministic rule 0018 (wp-scan-static-site) fires: WordPress paths on a static site are never valid. Deterministic-certain.
- Decision: block each, log, drop.

**Layer 3 (async):**
- Notices the pattern: same IP, multiple WordPress probes, all blocked.
- Adds IP 91.x.x.x to the banned-IP list (so future requests are blocked even faster at Layer 1, before per-path evaluation).

**Outcome:** each probe blocked at Layer 1; the IP gets banned wholesale after the pattern is detected.

**GAPS FOUND:**

- **GAP 7.1:** The async ban (Layer 3 adds the IP to the banned list) updates the Layer 1 embedded data. But Layer 1's embedded banned-IP list is refreshed by redeploy (the execution-model brief noted this). How fast does the ban take effect? If redeploy takes minutes, the attacker gets minutes of (already-blocked-anyway) probing. **Proposed resolution:** for individually-blocked probes this is fine (they are blocked anyway). But for the wholesale ban to take effect quickly, we need a fast-update path for the banned list that does not require a full redeploy. **This is the same GAP as the execution-model brief's open question (banned-list storage: embedded vs fast-lookup). The simulation confirms it matters.** Resolution: a fast-updating banned-list store that Layer 1 can check (or Layer 2 checks it and Layer 1 handles only the slow-changing embedded list).

- **GAP 7.2:** If every WordPress path is already blocked deterministically at Layer 1, what does the wholesale IP ban add? It saves the per-path evaluation, but the paths were blocked anyway. **Proposed resolution:** the wholesale ban matters for *non-deterministic* probing (an attacker trying paths that are not all on the obvious-bad list), where banning the IP stops them before they find an in-profile path. For pure WordPress scans, the per-path block suffices; the ban is an optimisation. Clarify when a wholesale ban is worth it.

## Simulation 8: A Brute-Force On A Valid Endpoint

**The request:** repeated `POST /api/v1/login` with different credentials, from IP 77.x.x.x, 50 attempts in 30 seconds. The login endpoint IS in the known-good profile (it is a valid endpoint).

**Layer 1 (per request):**
- Assigns ID, logs.
- Deterministic rules: IP not (yet) banned, path is valid (login is a real endpoint), not malformed. Passes.
- Decision: pass-on to Layer 2.

**Layer 2 (per request):**
- Known-good profile: `/api/v1/login` is valid. The request shape is valid.
- Opinion rules: rule 0061 (rapid-repeated-login) adds to the anomaly score. After several attempts, the score climbs.
- Decision: for the first few, pass (score below threshold); after the threshold, flag, then block.

**Layer 3 (async):**
- Analyses: 50 login attempts in 30 seconds from one IP is a brute-force pattern.
- Adds IP to the banned list; possibly flags the targeted account.

**Outcome:** the first several attempts pass (the endpoint is valid, the shape is valid); the brute-force is detected by rate/anomaly and blocked after a threshold.

**GAPS FOUND:**

- **GAP 8.1 (major):** This is the in-profile-attack case. A brute-force uses a *valid* endpoint with *valid* request shapes. The no-invalid-request principle does NOT catch it (the requests are in-profile). So it relies entirely on anomaly scoring (rate) and async detection. **The detect-before-damage window question becomes critical: how many login attempts before the block?** If the threshold is 10 attempts, an attacker gets 10 guesses; if the password is weak, 10 might be enough for damage. **Proposed resolution:** rate-limiting on sensitive endpoints (login) must be tighter than general anomaly scoring; login specifically needs a low attempt threshold and possibly per-account (not just per-IP) limits. This reveals that **some endpoints need endpoint-specific rules tighter than the general model.** The general detect-before-damage window is too loose for authentication.

- **GAP 8.2:** Per-IP rate limiting is defeated by distributed brute-force (the residential-proxy finding: attackers use many IPs). 50 attempts from 50 different residential IPs would not trip a per-IP rate limit. **Proposed resolution:** rate-limiting must also be per-account (per-target), not just per-IP. A single account receiving 50 login attempts from 50 IPs is the signal. This means the anomaly model needs per-target aggregation, not just per-source. A significant refinement: the evidence graph must key on targets as well as sources.

- **GAP 8.3:** "Damage" for a brute-force is a successful login. But Sentinel does not know if a login succeeded (the application handles auth; Sentinel sees the request, not the result, and with zero-knowledge may not see the result). **Proposed resolution:** Sentinel needs a feedback signal from the application ("this login failed/succeeded") to detect-before-damage on brute-force. This is the app-coupling working in reverse: the app tells Sentinel about auth outcomes so Sentinel can react. Without this feedback, Sentinel is blind to brute-force success. **This is a meaningful coupling requirement: the app must report auth outcomes to Sentinel.**

## Simulation 9: A Fast-Tracked User Turns Malicious

**The request:** a user with a valid fingerprint on the fast-track allowlist (authenticated, established session) starts making out-of-profile requests: `GET /api/v1/admin/users` (an endpoint they should not access), then `GET /vault/someone-elses-vault`.

**Layer 1:**
- Fingerprint matches the allowlist. Per the fast-track behaviour, this user is fast-tracked (skip deeper checks).
- Decision: fast-track. **The malicious request is fast-tracked because the user is on the allowlist.**

**Outcome (per the naive spec):** the out-of-profile request is fast-tracked and reaches the application, because the user was trusted.

**GAPS FOUND:**

- **GAP 9.1 (major):** This is the most serious gap. Fast-tracking a known-good fingerprint means a compromised or malicious-insider session bypasses the checks that would catch the out-of-profile request. The fast-track is a security hole if it skips validation. **This confirms and sharpens GAP 2.2 from part 1:** fast-track must NOT skip the known-good-profile validation. Even a fast-tracked user must have their requests validated against what that user is allowed to do. **Proposed resolution:** fast-track skips threat-intel and heavy analysis, but ALWAYS validates the request against the user's authorised profile. The admin endpoint and someone-else's-vault are out-of-profile for this user and must be blocked regardless of fast-track status. This means the known-good profile is per-user (what THIS user may do), not just global (what endpoints exist). **Significant refinement: the known-good profile has a per-user authorisation dimension, and fast-track never bypasses it.**

- **GAP 9.2:** Fast-tracking must therefore be reconceived: it is not "skip checks for trusted users" but "skip the EXPENSIVE checks (threat-intel, anomaly history) for users whose IDENTITY is established, while always enforcing authorisation". The fast-track is about identity-establishment cost, not authorisation-skipping. This is a fundamental clarification of what fast-track means.

- **GAP 9.3:** When a fast-tracked user makes an out-of-profile request, beyond blocking it, should their fast-track status be revoked? An established user suddenly probing admin endpoints is a compromise signal. **Proposed resolution:** an out-of-profile request from a fast-tracked user should both block the request AND trigger re-evaluation of the fingerprint (downgrade from fast-track, flag for analysis). The anomaly-in-an-allowlisted-user case (flagged in the time brief) needs explicit handling: trust is revocable on anomaly.

## Simulation 10: A Slow, Patient Probe (Detect-Before-Damage Stress Test)

**The request:** an attacker makes out-of-profile requests very slowly: one unusual request every 10 minutes, from rotating IPs, trying to map the API without tripping rate limits.

**Layer 1 (per request):** each request individually looks unremarkable (not an obvious-bad pattern; rotating IPs so no single IP accumulates). Passes to Layer 2.

**Layer 2 (per request):** each request is out-of-profile (not a valid endpoint). Per no-invalid-request, each is blocked. Logged.

**Layer 3 (async):** sees a slow pattern of out-of-profile requests from rotating IPs over hours.

**Outcome:** each individual out-of-profile request is blocked (no-invalid-request catches them), even though the slow distributed pattern evades rate-based detection.

**GAPS FOUND:**

- **GAP 10.1 (positive finding):** This is actually a case where the architecture works well. Because of no-invalid-request, each out-of-profile request is blocked individually, regardless of rate or IP rotation. The attacker cannot map the API because every out-of-profile request is rejected. **This validates the no-invalid-request principle against slow/distributed probing.** No gap; a confirmation.

- **GAP 10.2:** But: the attacker's requests are all blocked individually, yet they are not *banned* (rotating IPs, slow rate, no single IP trips a threshold). They can keep probing forever (always blocked, never doing damage, but consuming some resources). Is endless-but-harmless probing acceptable? **Proposed resolution:** probably yes (they never get through), but Layer 3 should still detect the *aggregate* pattern (many out-of-profile requests across rotating IPs targeting one app) and take broader action (challenge, or flag the campaign). The success metric (damage prevented) is met, but there is a resource-consumption and intelligence opportunity. Define whether aggregate-campaign detection is in scope.

- **GAP 10.3:** Detecting the aggregate campaign requires correlating across rotating IPs, which means the evidence graph must correlate by something other than IP (behavioural signature, target, timing pattern). **This reinforces GAP 8.2: the evidence graph needs multi-dimensional keying (source, target, behavioural signature), not just per-IP.**

## Simulation 11: An In-Profile Exploit (The Hardest Case)

**The request:** a request to a valid endpoint with a valid shape, but containing a payload that exploits a vulnerability in the application (e.g. a valid `POST /api/v1/comment` whose body contains an injection that the app mishandles).

**Layer 1:** path valid, not malformed, passes.

**Layer 2:** endpoint is in the known-good profile, request shape is valid (it IS a valid comment post). The injection is in the content. Passes (the shape is valid; the malice is in the payload semantics).

**Application:** receives the request. If the app is vulnerable, damage occurs.

**Outcome (naive):** the exploit reaches the application because it is structurally in-profile; the malice is semantic, not structural.

**GAPS FOUND:**

- **GAP 11.1 (major):** This is the limit of the no-invalid-request principle. A structurally-valid request with a malicious payload is in-profile and passes. No-invalid-request catches malformed and wrong-endpoint requests, but not valid-shape-malicious-content. **Proposed resolution:** this is where the CRS-derived injection rules (SQLi, XSS, etc., from the rule-architecture brief) matter: they inspect the content of valid requests for injection patterns. So the layered model is: no-invalid-request (structural) + injection rules (content) + LLM interpretation (semantic, async). The simulation confirms that **no-invalid-request alone is insufficient; the CRS-pattern content rules are necessary for in-profile exploits.** This validates reusing CRS for exactly this gap.

- **GAP 11.2:** But CRS content rules are evadable (the adversarial finding from the rule-architecture brief). An obfuscated injection might pass the regex. The LLM interpretation (async) might catch it, but async means it is detected *after* the request reached the app. For an in-profile exploit that does instant damage, detect-before-damage fails. **Proposed resolution:** this is the genuine residual risk. The mitigations: (a) the app itself must be secure (Sentinel is defence in depth, not the only defence); (b) the type-safe validation at the app boundary (from the codebase-extension brief) catches malformed content the WAF misses; (c) zero-knowledge limits what an injection can reach (encrypted content needs keys). **The honest conclusion: Sentinel reduces but does not eliminate in-profile exploit risk; the app must also be secure.** This is an important honest limit to document.

## Summary Of Gaps From Part 2

| Gap | Severity | Resolution Direction |
|-----|----------|---------------------|
| 6.1 "Drop silently" HTTP behaviour undefined | Minor | Define block actions at the HTTP level |
| 7.1 Async ban speed (banned-list update) | **Major** | Fast-update banned-list store, not redeploy-only |
| 7.2 Wholesale ban value for deterministic probes | Minor | Ban matters for non-deterministic probing; clarify |
| 8.1 In-profile brute-force needs tight per-endpoint limits | **Major** | Endpoint-specific rules; tight login limits |
| 8.2 Per-IP limits defeated by distributed attack | **Major** | Per-target (per-account) rate limiting; multi-dimensional evidence keying |
| 8.3 Sentinel blind to auth success | **Major** | App must report auth outcomes to Sentinel (reverse coupling) |
| 9.1 Fast-track bypasses authorisation | **Major** | Fast-track never skips authorisation validation |
| 9.2 Fast-track meaning reconceived | **Major** | Fast-track skips expensive checks, not authorisation |
| 9.3 Trust revocation on anomaly | Medium | Out-of-profile from fast-tracked user revokes fast-track |
| 10.1 No-invalid-request beats slow probing | Positive | Confirmation, no gap |
| 10.2 Endless harmless probing | Minor | Aggregate-campaign detection; define scope |
| 10.3 Multi-dimensional evidence keying | Medium | Key evidence by source, target, behaviour |
| 11.1 In-profile exploits need content rules | **Major** | CRS-pattern content rules necessary, not just no-invalid-request |
| 11.2 In-profile exploit residual risk | **Major (honest limit)** | Sentinel is defence in depth; app must also be secure |

Part 2 surfaced fourteen gaps, seven major. The most consequential cluster around three realisations:

1. **Fast-track must never skip authorisation** (9.1, 9.2): this changes the fast-track design fundamentally. Fast-track is about identity-establishment cost, not authorisation-skipping.
2. **Per-IP is insufficient** (8.2, 10.3): the evidence graph and rate-limiting need multi-dimensional keying (source, target, behaviour), confirmed by the residential-proxy reality.
3. **No-invalid-request is necessary but not sufficient** (11.1, 11.2): structural validation catches malformed/wrong-endpoint, but in-profile exploits need content rules (CRS) and ultimately a secure app. Sentinel is defence in depth, not a sole defence.

## The Combined Gap Picture

Across both parts, the major gaps that must be resolved before coding:

| Gap | Why It Must Be Resolved First |
|-----|-------------------------------|
| Fingerprint storage (2.1) + fast-track meaning (9.1, 9.2) | Changes where fast-track lives and what it does; architectural |
| Symmetry blocks deploys (5.1) | Changes the deploy pipeline coupling; operational |
| Banned-list update speed (7.1) | Changes the banned-list storage architecture |
| Per-target keying (8.2, 10.3) | Changes the evidence graph schema |
| App reports auth outcomes (8.3) | New coupling requirement |
| In-profile exploits need content rules + secure app (11.1, 11.2) | Defines Sentinel's honest limits |

These are exactly the decisions the voice memo wanted surfaced before coding. **Resolving them now, in the spec, means the implementation has far fewer open questions and a much more solid acceptance criteria.**

## What The Tabletop Establishes

The tabletop did its job: it found real gaps. The architecture is sound in its core (no-invalid-request beats slow probing; easy wins work; logging works), but the simulation surfaced:

- A fundamentally important clarification of fast-track (never skip authorisation)
- The need for multi-dimensional evidence keying (not just per-IP)
- A new coupling requirement (app reports auth outcomes)
- The honest limit (in-profile exploits need content rules and a secure app)
- Several operational couplings (atomic deploy, fast banned-list updates)

None of these are fatal; all are resolvable; all are far cheaper to resolve now than during or after implementation. **This is the value of the tabletop: the gaps are found on paper, not in production.**

## Relationship To Previous Briefs

| Date | Document | Relationship |
|---|---|---|
| 18 May | `v0.27.58__arch-brief__sg-sentinel-tabletop-simulation-1-generic-and-logging.md` | Part 1; this is part 2 |
| 18 May | `v0.27.58__arch-brief__sg-sentinel-rules-of-the-game-behavioural-spec.md` | The spec these simulations test and refine |
| 18 May | `v0.27.58__arch-brief__sg-sentinel-time-as-first-class-dimension.md` | The fast-track and detect-before-damage probed here |
| 18 May | `v0.27.58__arch-brief__sg-sentinel-rule-architecture-strategy.md` | The CRS content rules gap 11.1 needs |
| 18 May | `v0.27.58__arch-brief__sg-sentinel-as-codebase-extension.md` | The type-safe app boundary that backs up gap 11.2 |
| 18 May | `v0.27.58__research-brief__sentinel-learning-from-standards-and-services.md` | The residential-proxy reality behind gap 8.2 |

---

## Acceptance Criteria

| # | Criterion | Verification |
|---|-----------|-------------|
| 1 | Attack scenarios are traced layer-by-layer | Simulations 6-11 complete |
| 2 | Gaps are surfaced honestly | 14 gaps found, 7 major |
| 3 | The fast-track authorisation gap is identified | 9.1, 9.2 flagged as architectural |
| 4 | The per-IP insufficiency is identified | 8.2, 10.3 flagged |
| 5 | The auth-outcome coupling is identified | 8.3 flagged as new requirement |
| 6 | The in-profile exploit limit is honestly stated | 11.1, 11.2 documented |
| 7 | The combined pre-coding gap list is clear | Summary table complete |
| 8 | Resolutions fold back into the spec | Spec refined before coding |

---

This document is released under the Creative Commons Attribution 4.0 International licence (CC BY 4.0).
