Instructions to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M # Run inference directly in the terminal: llama cli -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M # Run inference directly in the terminal: llama cli -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Use Docker
docker model run hf.co/Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
- LM Studio
- Jan
- Ollama
How to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with Ollama:
ollama run hf.co/Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
- Unsloth Desktop
- Pi
How to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with Docker Model Runner:
docker model run hf.co/Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
- Lemonade
How to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Run and chat with the model
lemonade run user.qwen3.8-9b-cyber-exploit-agent-uncensored-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "Neurona/qwen3.8-9b-cyber-exploit-agent-uncensored:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
| # -*- coding: utf-8 -*- | |
| """Track B part 1: G-set core samples (G1, G2, G6, G7, G8 + variants). | |
| Every code anchor below was verified against rippled develop 2026-08: | |
| F1 Batch.cpp:220-228, F2 Batch.cpp:337-339, F3 OfferStream.cpp:260-274 / PermissionedDEXHelpers.cpp:57, | |
| F4 XLS-80 4.2 + PermissionedDomainInvariant.cpp:104, #6906 fix ConfidentialMPTSend.cpp:196-199, | |
| G6 ServerHandler.cpp:685-745 (masking only 888-900), G7 LoadManager.cpp:97-171 + LoadFeeTrack.cpp:17-19, | |
| G8 Ledger.cpp:101-110. | |
| User texts derive from real XRPLF/rippled issues; NO maintainer comments, NO close-state, NO triage results. | |
| """ | |
| SYSTEM = """You are an offensive security analyst for XRPL/rippled (C++), its Python tooling, and memory-unsafe C/C++ targets. You think like an attacker and judge like a defender. | |
| Work chain per report: White-Box source analysis -> reproducible TRIGGER -> full EXPLOIT WRITEUP -> VERDICT. Every answer MUST contain the sections '### TRIGGER', '### EXPLOIT WRITEUP', '### VERDICT' in that order, and every answer opens with a <think> block. | |
| Rules: | |
| 1. A bug is VALID only if the fault is TRIGGERABLE and the bad state is real. Cite code as file:function. Never cite authority ("maintainer said", "panel accepted", "closed as ..."). | |
| 2. XRPL paths are dead if: preflight/preclaim/tem kills the transaction first; the result is not committed (tefINVARIANT_FAILED = rolled back); it needs the victim's own privileged action without third-party force; the controlling XLS explicitly allows it; or a live re-check (offerInDomain / verifyValidDomain / CanTrade-at-cross) neutralizes stale state. A remix of two dead premises is dead. | |
| 3. Never claim a feature is absent without reading source. If you cannot reconstruct the chain from code or spec, answer UNPROVEN and state exactly what must be fetched and checked. | |
| 4. Severity: HYGIENE / FALSE_POSITIVE / UNPROVEN -> none. safety-VALID -> low/medium/high. ledger-VALID -> medium/high/critical. critical only for third-party fund extraction or a committed invariant break. | |
| 5. Verdict classes: VALID_BUG TRACK:ledger | VALID_BUG TRACK:safety PATTERN:N11..N18 | FALSE_POSITIVE | HYGIENE | CORRECTNESS-ISSUE | UNPROVEN | INSUFFICIENT_EVIDENCE. | |
| 6. Always state honestly what is missing to the next exploitation rung (crash -> leak -> write -> control).""" | |
| PREAMBLE = "Verify this XRPL/rippled bug report against the protocol. Decide: real exploitable fault or not? Reason from code, not from how convincing the report sounds.\n\n" | |
| S = [] | |
| def add(sid, user, think, trigger, writeup, verdict_line, impact, reasoning, preamble=PREAMBLE): | |
| assistant = ( | |
| "<think>\n" + think.strip() + "\n</think>\n\n" | |
| "### TRIGGER\n" + trigger.strip() + "\n\n" | |
| "### EXPLOIT WRITEUP\n" + writeup.strip() + "\n\n" | |
| "### VERDICT\n" + verdict_line.strip() + "\nImpact: " + impact + "\n" + reasoning.strip() | |
| ) | |
| S.append({"id": sid, "messages": [ | |
| {"role": "system", "content": SYSTEM}, | |
| {"role": "user", "content": (preamble + user.strip()) if preamble else user.strip()}, | |
| {"role": "assistant", "content": assistant}]}) | |
| # ============================================================ G1 (base) — Sponsored Batch + MPT DEX stale offers | |
| G1_USER = """## Report: Orphaned sponsorship + stale permissioned-book offers via delegated Batch | |
| Summary: Delegate-submitted sponsored Batch in partial mode causes sfSponsoringOwnerCount underflow/desync while sponsored objects inflate, due to inconsistent cleanup on failure path. Separately, a Batch mixing OfferCreate (MPT, permissioned domain book) and ConfidentialMPTSend leaves stale offers executable after the trader's domain credential is revoked: partial failure after credential check but before commit leaves stale Offers and sfConfidentialBalanceVersion, enabling replay/unauthorized trades on the permissioned book. | |
| Attack Path: Sponsor grants granular SponsorFee/SponsorReserve permissions via DelegateSet; delegate submits sponsored Batch (tfUntilFailure) with object-creating inners and a late-failing inner; sponsor AccountRoot counts desync. Second phase: Batch with OfferCreate (sfTakerPaysMPT/sfTakerGetsMPT on a domain-gated book) + ConfidentialMPTSend + failing inner, then revoke credentials and replay the stale offer. | |
| Impact: reserve DoS on sponsor, ledger bloat, unauthorized confidential MPT trades on permissioned book after revocation. | |
| """ | |
| add("G1", | |
| G1_USER, | |
| """White-Box: Two stacked premises, both must survive to commit. | |
| (1) Outer Batch carrying spfSponsorReserve dies in preflight: Batch::preflight (src/libxrpl/tx/transactors/system/Batch.cpp:220-228) returns temINVALID_FLAG when sfSponsorFlags contains spfSponsorReserve on an outer Batch. The delegated sponsored Batch never reaches apply. | |
| (2) Inner Batch transactions carrying their own sfSponsor or fee-sponsorship die in inner preflight (Batch.cpp:337-339, temINVALID_FLAG). There is no inner sponsorship to orphan. | |
| (3) The stale-permissioned-offer premise assumes persistence equals executability. At match time OfferStream::operator() (src/libxrpl/tx/paths/OfferStream.cpp:260-274) re-checks every offer carrying sfDomainID against the live view via permissioned_dex::offerInDomain (PermissionedDEXHelpers.cpp:57), which walks to accountInDomain on the current ledger. A revoked credential removes the account from the domain at revoke time; the offer may stay in the book but is removed (permRmOffer) the moment the matcher touches it. | |
| (4) The confidential replay premise inherits the same death: ConfidentialMPTSend reads issuance gates at apply time on the live view. | |
| (5) The report is a recombination of two independently dead premises (#6932-family + #6933-family). F8: remix of dead premises is dead. | |
| Which gates hold: F1 (outer spfSponsorReserve -> temINVALID_FLAG), F2 (inner sfSponsor -> temINVALID_FLAG), F3 (match-time domain re-check on live view), F8 (remix). No gate is bypassed by delegation because Batch itself is not delegable as a sponsorship vehicle and inner preflight re-runs per inner.""", | |
| """Standalone or local rippled develop, two accounts + delegate + issuer with MPTokenIssuance gated by a PermissionedDomain. | |
| Phase A (sponsorship desync claim): | |
| 1. Sponsor: SponsorshipSet, then DelegateSet granting SponsorFee/SponsorReserve to delegate. | |
| 2. Delegate submits outer Batch with sfSponsorFlags = spfSponsorReserve, three object-creating inners, late-failing inner (tfUntilFailure). | |
| 3. Observed result: temINVALID_FLAG from Batch::preflight. Nothing is applied; no count desync is observable because no ledger state changes. | |
| Phase B (stale permissioned offer claim): | |
| 1. Trader with valid domain credential places OfferCreate for the domain-gated MPT book inside a Batch with a late-failing inner; credential revoked afterwards. | |
| 2. Submit a Payment that would cross the persisted offer. | |
| 3. Observed result: matcher hits the offer, offerInDomain returns false against the live ledger, offer is evicted from the book domain (permRmOffer) and the cross skips it. No unauthorized trade occurs. Persistence of the offer entry itself is not execution.""", | |
| """Root cause (claim vs. source): the report's source -> sink assumes (a) sponsored Batch accepts sponsor payloads at all, and (b) book persistence grants executability. Both ends are closed in code: Batch::preflight rejects outer spfSponsorReserve (temINVALID_FLAG) and inner sfSponsor (temINVALID_FLAG); OfferStream re-validates domain membership at match time on the live view, and CanTrade/CanTransfer clears evict offers at the next cross (XLS-82 section 9.2 family). | |
| Reachability: preflight kills phase A before any state access; phase B reaches the matcher but the guard fires on the live view, so the path is reachable yet harmless by design. | |
| Primitive obtained: none. The claimed primitives (reserve underflow, unauthorized confidential trade) never materialize: there is no committed bad state, no third-party forceable loss. | |
| Chain to impact: broken at the first rung. Both stacked premises die at their respective gates; the recombination (#6932 + #6933) inherits both deaths (F8). | |
| What would make it valid: an actual accepted sponsor shape on the outer Batch (only spfSponsorFee is legal there), or a match path that consumes a domain-gated offer without offerInDomain - neither exists on develop. | |
| Stability: deterministic protocol behavior, no races involved; the gates are amendment-gated but active on current networks (fixCleanup3_3_0 keeps domain re-check for domain books). | |
| Fix sketch: none required - behavior is per XLS-56 batch validation and XLS-80/82 permissioned-domain match semantics. If hardening is desired, reject sfSponsorFlags presence on outer Batch entirely instead of per-flag checks.""", | |
| "FALSE_POSITIVE", "none", | |
| """REASONING: Batch::preflight (Batch.cpp:220-228) kills the sponsored outer Batch with temINVALID_FLAG (F1); inner sfSponsor dies at Batch.cpp:337-339 (F2); stale domain offers are re-checked at match time via offerInDomain on the live view (OfferStream.cpp:260-274, PermissionedDEXHelpers.cpp:57) (F3); the report is a recombination of two independently dead premises (F8). No committed bad state, no third-party loss.""") | |
| G1_USER_V2 = """## Report: Batch + delegation + permissioned DEX triple feature attack | |
| I combined Sponsored Fees and Reserves with Permission Delegation and Batch (like the closed reports about these combos), but now also with the Permissioned DEX: my delegate submits a sponsored Batch whose inner transactions create objects and place offers on a domain-gated MPT book, one inner fails late, and I claim the sponsor's owner-count accounting desyncs while stale offers remain tradable after I revoke the trader's credential. The offers stay in the book directory, so anyone can keep crossing them - the domain gate is stale state. | |
| PoC: delegate-submitted Batch (tfUntilFailure) with spfSponsorReserve on the outer tx, OfferCreate inners on the permissioned book, late failing Payment inner; revoke credential; cross the book. | |
| Impact: sponsor reserve corruption + permission bypass on the permissioned DEX.""" | |
| add("G1v2", | |
| G1_USER_V2, | |
| """White-Box: Same structural family as the sponsored-Batch + permissioned-DEX reports; verify each premise at its own gate. | |
| (1) spfSponsorReserve on an outer Batch: Batch::preflight (Batch.cpp:220-228) returns temINVALID_FLAG before any inner is considered. | |
| (2) Delegation does not widen Batch: Batch itself is not a delegable sponsorship vehicle; inner transactions re-run their own preflight, and inner sfSponsor/sponsor-fee shapes die at Batch.cpp:337-339. | |
| (3) "Offers stay in book directory -> tradable": persistence is not executability. OfferStream (OfferStream.cpp:260-274) re-validates sfDomainID offers against the live view at match time (offerInDomain -> accountInDomain, PermissionedDEXHelpers.cpp:57); after credential revoke the account is out of the domain, so the matcher evicts the offer instead of crossing it. | |
| (4) This is the same residual as previously closed reports, recombined - F8 kills the remix; F20 would kill a plain re-file without new differential anyway. | |
| Gates that hold: F1, F2, F3, F8, F20.""", | |
| """Local rippled develop. | |
| 1. Submit the delegate's outer Batch with spfSponsorReserve -> observe temINVALID_FLAG immediately (no inners applied, no sponsor state touched). | |
| 2. Remove the sponsor flag, keep OfferCreate inners on the domain book and a late-failing inner, let the Batch apply, then revoke the trader credential. | |
| 3. Submit a crossing Payment -> observe the matcher skip and evict the stale offer (offerInDomain false on live view). server_info / book_offers shows the entry only until it is touched. | |
| 4. Net observable harm: none; sponsor accounting unchanged in (1), no unauthorized cross in (3).""", | |
| """Root cause: the report treats two dead premises as alive because they look combinable. Source -> sink: outer sponsor flag -> temINVALID_FLAG at preflight (never reaches apply); stale book entry -> live domain re-check at match (never crosses). | |
| Reachability: phase (1) unreachable past preflight; phase (3) reachable but the live guard makes it inert. | |
| Primitive: none. No fund movement, no bypassed permission. | |
| Chain: dead at rung one on both branches. Missing for validity: a legal sponsor shape on outer Batch, or a match path skipping offerInDomain for domain books - neither exists on develop. | |
| Stability: deterministic; gates amendment-active. | |
| Fix sketch: none needed (XLS-56 validation + XLS-80/82 match semantics). Optional hardening: reject any sfSponsorFlags presence on outer Batch.""", | |
| "FALSE_POSITIVE", "none", | |
| """REASONING: F1 (Batch.cpp:220-228) and F2 (Batch.cpp:337-339) kill the sponsorship shape; F3 (OfferStream.cpp:260-274 live offerInDomain) kills the stale-offer shape; F8/F20 kill the recombination of already-refuted residuals.""") | |
| # ============================================================ G2 (base + v2) — PermissionedDomainDelete dangling sfDomainID | |
| G2_USER = """## Report: PermissionedDomainDelete allows deletion while MPTokenIssuance still references the domain via sfDomainID | |
| PermissionedDomainDelete::preclaim only checks that the sender is the domain owner and that the domain exists. It does not check whether any MPTokenIssuance object or hybrid offer still holds an sfDomainID field pointing at the domain being deleted. The transaction succeeds, the domain is removed, and dependent entries keep a dangling sfDomainID pointing at a nonexistent object. Any subsequent holder relying on verifyValidDomain() for a domain-gated MPT fails with tecNO_AUTH because the domain can no longer be read. This is a cross-feature consistency / missing invariant issue: sfDomainID was added on top of the original Permissioned Domains design without a dependency check in the delete path. | |
| Affected: PermissionedDomainDelete::preclaim (no dependency check), ValidPermissionedDomain (invariant covers only the domain object's own fields), MPTokenIssuance sfDomainID, hybrid offers sfDomainID, verifyValidDomain() returning tecOBJECT_NOT_FOUND. | |
| Impact: holders of the MPT lose self-authorization ability; dangling references across the ledger; potential tecNO_AUTH lockout for third parties.""" | |
| add("G2", | |
| G2_USER, | |
| """White-Box: Is deletion with live references a protocol violation or the designed lifecycle? | |
| (1) XLS-80 section 4.2 specifies PermissionedDomainDelete failure conditions: exactly two - the domain does not exist, or the sender is not the owner. Deletion at any time, with references outstanding, is specified behavior, not a missed check. | |
| (2) The invariant ValidPermissionedDomain (PermissionedDomainInvariant.cpp) checks the domain object's own transitions; dangling references from other objects are not an invariant violation because XLS-80 does not make them one. | |
| (3) Post-delete behavior of the dependents is designed: the issuer gated the issuance with DomainID; after the issuer deletes the domain, verifyValidDomain (CredentialHelpers.cpp:335 via MPTokenHelpers.cpp:455) cannot resolve the domain and gated actions fail closed (tecNO_AUTH / tecOBJECT_NOT_FOUND family). Fail-closed is the safe direction: nobody unauthorized gains anything; authorized holders lose access until the issuer re-points or clears the domain (MPTokenIssuanceSet with DomainID cleared/zero). | |
| (4) Who can trigger and who is hurt: only the domain OWNER (issuer) can delete (preclaim enforces owner); the hurt parties are the issuer's own token holders - an issuer action against its own issuance's users. Owner can cause AND repair (re-create domain or clear sfDomainID) -> self-inflicted feature misuse, not third-party forceable. | |
| Gates: F4 (XLS-80 4.2: delete-anytime is spec, two failure conditions only), F6 (owner can cause and repair), F22-check: no dead protection involved - the protection (domain gate) still fails closed, it does not fail open.""", | |
| """Local rippled develop. | |
| 1. Account A creates PermissionedDomain D; creates MPTokenIssuance with DomainID = D and tfMPTRequireAuth. | |
| 2. Holder H obtains credential for D, holds MPT, can self-authorize. | |
| 3. A submits PermissionedDomainDelete(D) -> tesSUCCESS (owner, domain exists - both spec conditions met). | |
| 4. H attempts a domain-gated action -> tecNO_AUTH (fail-closed: verifyValidDomain cannot read D). | |
| 5. A repairs: MPTokenIssuanceSet clearing DomainID (or re-creates a domain and re-points) -> holders functional again. | |
| Observed: deletion succeeds by design; no unauthorized access ever appears; issuer can restore. The "trigger" demonstrates the kill gate: fail-closed + owner-reversible, exactly the shape XLS-80 4.2 chose.""", | |
| """Root cause: none at the code level - the report reads specified lifecycle as a missing check. Source -> sink: owner deletes domain; dependents fail closed at their next use. | |
| Reachability: the deletion is reachable, but the claimed harm (third-party lockout as a security boundary break) requires the issuer to act against its own issuance. | |
| Primitive: none. No bypass: the gate never opens for anyone unauthorized; it closes for everyone, which is availability-by-issuer-choice. | |
| Chain to impact: no security impact rung is reachable. What would make it valid: if a deleted domain could be re-created by an attacker to capture old sfDomainID references (domain IDs are sequence-derived, not attacker-choosable, so no), or if gated state failed OPEN after deletion (it fails closed). | |
| Stability: deterministic; amendment-independent core semantics. | |
| Fix sketch: if desired ergonomics, not security: an RPC/admin view listing dangling sfDomainID references so issuers can clean up; or a non-fatal warning. Do not add a dependency check to the delete path - that contradicts XLS-80 4.2 delete-anytime semantics.""", | |
| "FALSE_POSITIVE", "none", | |
| """REASONING: XLS-80 section 4.2 allows deletion at any time with exactly two failure conditions (missing domain, non-owner sender); dangling sfDomainID + later tecNO_AUTH is the specified fail-closed outcome; only the owner/issuer can trigger it and can repair it (F4 + F6). No third-party forceable loss, no fail-open.""") | |
| G2_USER_V2 = """## Report: Dangling domain references let the issuer brick holder MPTokens | |
| The permissioned-domain delete leaves sfDomainID fields dangling on MPTokenIssuances and hybrid offers. I think this is a missing invariant: after PermissionedDomainDelete, verifyValidDomain() fails for every holder and their MPTs become unusable, and hybrid offers sit in a book pointing at a domain that no longer exists. Millions of holder objects can be affected by one delete. This must be a cross-feature security bug - the delete should be rejected while references exist.""" | |
| add("G2v2", | |
| G2_USER_V2, | |
| """White-Box: same residual as the PermissionedDomainDelete dangling-reference report, escalated with an availability argument. The security question stays: does anything fail open, or only closed? | |
| (1) XLS-80 4.2: delete-anytime with two failure conditions - a reference-count gate on delete is explicitly not part of the spec, and adding it would change griefing economics, not security. | |
| (2) Affected holders are the issuer's own issuance users; the actor is the issuer (only the owner can delete). Owner-caused, owner-repairable (re-create domain, or clear DomainID via MPTokenIssuanceSet) -> F6 self-DoS/feature-misuse family. | |
| (3) Hybrid offers pointing at a deleted domain: at match time the domain re-check (OfferStream.cpp:260-274 offerInDomain on live view) simply evicts them; no cross happens that should not. | |
| (4) Scale ("millions of objects") changes blast radius, not class: a legal owner action with fail-closed consequences remains by-design. | |
| Gates: F4, F6, F9-adjacent (stale references unusable rather than exploitable), F20 (same residual as the closed report, no new differential).""", | |
| """Local rippled develop. | |
| 1. Issuer creates domain D + domain-gated MPT issuance; holders hold MPTs; one hybrid offer exists. | |
| 2. Issuer deletes D (owner + exists -> tesSUCCESS per XLS-80 4.2). | |
| 3. Holder gated action -> fails closed (tecNO_AUTH family). Hybrid offer -> evicted at next match touch (offerInDomain false), never crossed. | |
| 4. Issuer clears sfDomainID (MPTokenIssuanceSet) or re-creates a domain -> holders restored. | |
| Observed: fail-closed everywhere, owner-reversible; no open gate, no third-party gain.""", | |
| """Root cause: specified lifecycle, not a missing check; the report asks to turn an ergonomics/availability preference into a rejection rule the controlling spec deliberately does not have. | |
| Reachability: reachable but inert - every dependent path fails closed or evicts. | |
| Primitive: none (availability degradation of the issuer's own token, reversible by the issuer). | |
| Chain: no rung reachable toward third-party loss. | |
| Missing for validity: a fail-open path post-delete, or third-party forceability of the delete - both absent. | |
| Stability: deterministic. | |
| Fix sketch: tooling (dangling-reference report RPC), not protocol.""", | |
| "FALSE_POSITIVE", "none", | |
| """REASONING: F4 (XLS-80 4.2 delete-anytime, two failure conditions) + F6 (owner-caused, owner-repairable) + fail-closed outcomes at every dependent (verifyValidDomain tecNO_AUTH; match-time offerInDomain eviction). Availability-by-issuer-choice is not a security boundary break; same residual as the closed report without a new differential (F20).""") | |
| # ============================================================ G7 (base + v2) — LoadManager silenced circuit breaker | |
| G7_USER = """## Report: Local load-fee circuit breaker never runs - LoadManager applies fee changes only at thread stop | |
| Looking at src/xrpld/app/main/LoadManager.cpp: LoadManager::run() has a while(true) loop that waits on the condition variable with predicate [this]{ return stop_; } and breaks only when stop_ is set. The entire fee-regulation block sits AFTER the loop: if JobQueue isOverloaded() then raiseLocalFee() else lowerLocalFee(), and if changed, reportFeeChange(). That block can only execute after the break, i.e. when the node is shutting down. During normal operation the local transaction load fee is never raised or lowered by this path. | |
| Consequences: under sustained JobQueue saturation, load_factor_local stays at 256 forever, telINSUF_FEE_P can never fire from local load, and reportFeeChange() never propagates the (dead) change to subscribers. The JobQueue's own escalation (load_factor_fee_escalation) is a different axis: it prices the open-ledger queue, it does not implement node-local backpressure for RPC/relay load. So a saturated node fail-opens: it keeps accepting work instead of shedding it. | |
| Also note LoadFeeTrack::raiseLocalFee() requires raiseCount_ >= 2 across consecutive calls to actually raise, and lowerLocalFee() resets raiseCount_ to 0 - so even a single shutdown-time call cannot arm the hysteresis. | |
| Impact: node fail-open under load; operators are blind because the metric that would show the condition (fee change events / load_factor_local) lives in the same dead block.""" | |
| add("G7", | |
| G7_USER, | |
| """White-Box: This is a primary self-protection control whose live invocation is structurally impossible. | |
| (1) LoadManager::run() (src/xrpld/app/main/LoadManager.cpp:97-152): loop body = wait_until(cv, t, []{return stop_;}) -> break; then stall detection/logging only. The fee block (LoadManager.cpp:154-171: isOverloaded ? raiseLocalFee : lowerLocalFee, then reportFeeChange) executes only after the break; the predicate only fires when stop_ was set by LoadManager::stop() (shutdown). Verified: no other production caller of raiseLocalFee/lowerLocalFee exists (only tests). | |
| (2) Should it fire in normal operation? Yes - that is the design intent of a load manager loop ticking every second (t += 1s): continuously regulate local fee against JobQueue overload. D1: yes. | |
| (3) Is the protection circle occupied? The sibling control (TransactionQueue fee escalation, load_factor_fee_escalation) measures a different axis: open-ledger admission pricing, not node-local load shedding for RPC/relay/ledger work. N16: a sibling on another axis does not cover the risk. D2: yes (default nodes rely on local fee backpressure), D3: LoadManager/LoadFeeTrack is the primary control for localTxnLoadFee_. | |
| (4) Silent? The observable metric (reportFeeChange propagation; load_factor_local in server_info) sits in the same dead block -> operators never see the condition. D4: yes (metric dead too). D5: sustained JQ saturation (large ledger, RPC storms) makes the missing control relevant exactly when needed. -> D1+D2+D3 and (D4 or D5): VALID_BUG TRACK:safety. | |
| (5) Hysteresis: LoadFeeTrack::raiseLocalFee (LoadFeeTrack.cpp:17-19) needs ++raiseCount_ >= 2 consecutive calls; lowerLocalFee resets it (LoadFeeTrack.cpp:45). A single shutdown-time call leaves raiseCount_ = 1 - never arms. N18. | |
| (6) Pattern set: N11 (periodic self-protection only after break/stop), N16 (TxQ escalation cited as cover but different axis), N17 (metric in same dead block), N18 (hysteresis never arms). | |
| Gates that do NOT apply as kills: F11 (this is not build/HTTP shape - it is node self-protection), F5-family (nothing to do with tx results).""", | |
| """Isolated lab or owned node, rippled develop build. | |
| 1. Start rippled (standalone or test net with owned node), confirm LoadManager thread running (thread name "LoadManager"). | |
| 2. Sustain JobQueue saturation: flood the node with RPC work and locally submitted transactions until JobQueue::isOverloaded() is true for consecutive seconds (watch debug logs for "JobQueue" stall reports from the loop - those still fire, proving the loop ticks). | |
| 3. Poll server_info: load_factor_local stays 256 (never rises) because raiseLocalFee is unreachable while running; telINSUF_FEE_P never appears from local load. | |
| 4. Stop the node: exactly one fee evaluation runs after the loop (observable in debug logs: "Raising local fee" or "Local load fee lowered" at shutdown) - proving the block is shutdown-only. | |
| 5. Post-patch expectation: with the fee block moved inside the loop, load_factor_local rises while overloaded and falls after; pre-patch it does not. Crash-free, measurable, deterministic on any default-config node.""", | |
| """Root cause: control-flow placement - the regulation block (LoadManager.cpp:154-171) was left outside the per-second loop (LoadManager.cpp:97-152); the only exit from that loop is the shutdown predicate, so the circuit breaker executes exactly once, at thread stop. | |
| Source -> sink: JobQueue overload (source) should drive LoadFeeTrack::raiseLocalFee -> localTxnLoadFee_ -> scaleFeeLoad -> telINSUF_FEE_P / load_factor_local (sink). The middle edge is dead in normal operation. | |
| Reachability: trivially reachable by any operator on their own node; the failure mode manifests under sustained load with zero attacker sophistication beyond causing work (which public networks deliver on their own). | |
| Primitive: logic bypass / dead circuit-breaker (node fail-open), not memory corruption. The bypassed control is the primary one for this risk; the TxQ escalation sibling prices a different axis and cannot substitute. | |
| Chain to impact: sustained overload -> no local fee backpressure -> node keeps accepting/spawning work -> degraded consensus/RPC latency for the operator and its clients; no direct fund extraction, but availability of the operator's infrastructure. That is the honest end of the chain: fail-open of a primary protection, severity high per the stamp (D1+D2+D3 + D4). | |
| What is missing to the next rung: nothing for classification; for weaponization, an adversary would need to direct enough load at a specific node (network-level DoS amplifier), which stays outside protocol scope. | |
| Stability: deterministic control flow; no ASLR/heap dependency; behavior identical on default nodes (D2 holds because unlimited endpoints are a separate, surviving control on a different consumer axis). | |
| Fix sketch (one level): move the fee-regulation block inside the while-loop, after the stall-detection section, before the closing brace; keep reportFeeChange gated on `change` as-is. Regression guard: unit test that drives two consecutive overloaded ticks and asserts localTxnLoadFee_ rose (also covers N18 arming).""", | |
| "VALID_BUG TRACK:safety PATTERN:N11", "high", | |
| """REASONING: LoadManager.cpp:154-171 (raiseLocalFee/lowerLocalFee/reportFeeChange) executes only after the while-loop break, whose predicate (LoadManager.cpp:102) fires only on stop_ (shutdown); the only production callers of raiseLocalFee/lowerLocalFee are this dead block; LoadFeeTrack.cpp:17-19 hysteresis (raiseCount_ >= 2) never arms from a single shutdown call (N18); the TxQ escalation sibling covers open-ledger pricing, not local load (N16, D3); the observable metric sits in the same dead block (N17, D4). D1+D2+D3 and D4 -> VALID_BUG TRACK:safety PATTERN:N11 (with N16/N17/N18), severity high: primary node self-protection fail-open on default nodes, silent to operators.""") | |
| G7_USER_V2 = """## Report: server_info load_factor_local pinned at 256 under JobQueue saturation - is the local load fee regulator actually running? | |
| Observed on my node (develop build): during heavy RPC + ledger load the JobQueue logs overload/stall warnings every reporting interval, so the LoadManager loop is clearly ticking. But server_info shows load_factor_local = 256 the whole time and I never see telINSUF_FEE_P or a fee-change notification. Reading LoadManager::run(): the wait_until predicate is just stop_, the loop only does stall detection and logging, and all fee work (isOverloaded -> raiseLocalFee / lowerLocalFee, reportFeeChange) is after the loop. Also raiseLocalFee needs two consecutive calls because of raiseCount_ and lowerLocalFee resets it. Is the local fee circuit breaker effectively dead until shutdown? Feels like the fee block belongs inside the loop.""" | |
| add("G7v2", | |
| G7_USER_V2, | |
| """White-Box: Observation matches code exactly. | |
| (1) Loop: LoadManager.cpp:97-152 - 1s ticks, stall detection, JobQueue logging (that is what the reporter sees). Exit condition only stop_ (LoadManager.cpp:102). | |
| (2) Fee block: LoadManager.cpp:154-171 - after the loop; runs once, at thread stop. isOverloaded-branch raiseLocalFee vs lowerLocalFee + reportFeeChange all in that dead region. | |
| (3) Caller audit: no other production caller of raiseLocalFee/lowerLocalFee (tests only). So load_factor_local cannot move from local load during runtime. | |
| (4) Splitter: D1 yes (should fire every tick under load), D2 yes (default nodes; unlimited-endpoint control is a different consumer), D3 yes (LoadFeeTrack is the primary control for localTxnLoadFee_; TxQ escalation = different axis, N16), D4 yes (reportFeeChange is in the same dead block -> metric never appears), D5 yes (sustained load is exactly when it matters). -> safety VALID. | |
| (5) N18: LoadFeeTrack.cpp:17-19 needs raiseCount_ >= 2 consecutive; single shutdown call leaves it unarmed; lowerLocalFee resets (LoadFeeTrack.cpp:45). | |
| Patterns: N11 + N16 + N17 + N18.""", | |
| """Owned node, develop build. | |
| 1. Under sustained JQ overload, poll server_info -> load_factor_local stays 256; no fee-change events (N17: metric in dead block). | |
| 2. Confirm the loop ticks: stall/JobQueue warnings appear every kReportingIntervalSeconds (LoadManager.cpp:114-139). | |
| 3. Stop node -> one fee evaluation runs after loop exit (debug log "Raising local fee"/"Local load fee lowered") proving placement, not absence. | |
| 4. Post-fix expectation: block inside loop -> load_factor_local tracks overload, hysteresis arms across consecutive ticks.""", | |
| """Root cause: fee-regulation block placed after the while(true) whose only exit is shutdown (LoadManager.cpp:97-171). | |
| Reachability: any owned node under load; deterministic. | |
| Primitive: dead primary circuit breaker (fail-open node self-protection); silent to operators (metric co-located in dead block). | |
| Chain: overload -> no local backpressure -> availability degradation for operator/clients; honest end-rung is infra availability, not fund loss. | |
| Missing to next rung: n/a for classification; weaponizing needs directed load at a target node (out of protocol scope). | |
| Stability: pure control flow; no environment dependency. | |
| Fix sketch: move fee block into the loop after stall handling; add regression test asserting two overloaded ticks raise localTxnLoadFee_ (covers N18 arming).""", | |
| "VALID_BUG TRACK:safety PATTERN:N11", "high", | |
| """REASONING: LoadManager.cpp:102 predicate exits only on stop_; fee block LoadManager.cpp:154-171 unreachable at runtime; sole production caller of raiseLocalFee/lowerLocalFee; LoadFeeTrack.cpp:17-19/45 hysteresis never arms (N18); TxQ sibling = different axis (N16); reportFeeChange dead alongside (N17). D1-D5 stamp -> VALID_BUG TRACK:safety PATTERN:N11, high.""") | |
| # ============================================================ G6 (base + v2) — batch error echo | |
| G6_USER = """## Report: HTTP batch error paths echo full request without secret masking | |
| Multiple error paths in HTTP batch request processing copy the full individual request JSON into the error response without applying sensitive-field masking. Affected: invalid API version (r[jss::request] = jsonRPC), server overloaded (Json::Value r = jsonRPC; r[error] = make_json_error(server_overloaded...)), forbidden (same shape), null method, method not string, empty method. All either set r[request]=jsonRPC or r=jsonRPC without masking. The masking code that exists for the main handler error path is not applied to these batch-item shortcut returns. | |
| Exploit scenario: send a batch containing {"method":"sign","params":[{"secret":"sMySecretKey"}]} with an invalid api_version; the batch item fails validation; the response echoes the full item including the nested secret in cleartext. Severity claimed: MEDIUM - secret exposure.""" | |
| add("G6", | |
| G6_USER, | |
| """White-Box: Structure of the claim is real; the security class depends on WHO receives the echo. | |
| (1) Verified shape: in src/xrpld/rpc/detail/ServerHandler.cpp the batch-item error branches (server overloaded ~685-688, forbidden ~700-701, null method ~714-715, method-not-string ~729-730, empty method ~744-745, invalid API version ~643) each build the per-item error reply by copying the raw request item (json::Value r = jsonRPC; r[jss::error] = ...; reply.append(r)), while the non-batch equivalent returns bare HTTP errors (400/403/503) without the request body. The masking pass used by the main execution error path sits later in the function (~888-900) and is not applied to these shortcut branches. So: filter on path A (main path, masked), sink on path B (batch shortcuts, unmasked) - an N12-shaped desync, structurally confirmed. | |
| (2) Sink analysis: where does the echo GO? Back to the same HTTP client that sent the batch - the same client that already possesses the secret (it authored the request containing it). No second party, no log sink, no other node receives the unmasked item. E-splitter: E1 (third party who did not send the secret receives it) - NO. E2 (only the same client) - YES. E3 (extra sink is only the debug reply to the body's owner) - effectively this. E4 (a sibling path masks, this one does not) - yes, and E4 confirms the structural drift but explicitly does NOT raise the class. | |
| (3) Class gates: F11 - HTTP response shape is not protocol security; F21 - same-client echo of a secret the requester itself supplied is not a third-party leak. Not VALID. Is it FP or HYGIENE? The drift is real (batch paths bypass masking that the codebase otherwise applies; a future refactor adding a real sink (logging, proxying) would inherit the unmasked shape) -> HYGIENE, severity none. Never LOW/MEDIUM security for a same-client echo. | |
| (4) What WOULD make it VALID: the unmasked item landing anywhere other than the requesting connection - error logs at info+, a monitoring/proxy in front that records response bodies, or the batch reply being broadcast/cached. Those sinks do not exist in the cited code.""", | |
| """Local rippled develop, JSON-RPC over HTTP. | |
| 1. capture: start a packet capture or a logging reverse proxy on the node's RPC port (owned node). | |
| 2. Submit: {"method":"batch","params":[{"method":"sign","params":[{"secret":"sEdTestOnlyNotARealKey"}],"api_version":"invalid"}]}. | |
| 3. Observe the batch reply item: {"request": <full item incl. secret>, "error": {invalid_API_version...}} - the unmasked echo, confirmed. | |
| 4. Verify the same-client-only sink: the secret string appears ONLY in: your request (you sent it) and your response (same connection). grep node logs for the secret string - zero hits (error replies are not logged at body level). | |
| 5. Contrast: non-batch {"method":"sign", ...} with same invalid api_version -> plain HTTP error, no body echo - showing the desync (E4 shape).""", | |
| """Root cause: batch-item shortcut error returns in ServerHandler.cpp bypass the masking pass that the main error path applies (masking ~888-900; shortcuts ~643-745 copy jsonRPC raw). Filter-path desync (N12 shape), but the desync's sink is the requesting connection itself. | |
| Reachability: trivially reachable (single crafted batch), but harm requires a sink beyond the requester - none exists in the cited code. | |
| Primitive: info "leak" only to the secret's original owner - no adversarial gain; an attacker watching the response is the attacker who wrote the request. | |
| Chain to impact: none reachable. Missing for VALID: an actual second-party sink (log/proxy/broadcast) for the unmasked item. | |
| Stability: deterministic JSON handling; no environment dependency. | |
| Fix sketch (hygiene): route batch-item error construction through the same masking helper used by the main error path; add a unit test asserting no batch error reply contains a masked-field value verbatim. Defense in depth for future refactors - that is the honest framing of the value.""", | |
| "HYGIENE", "none", | |
| """REASONING: ServerHandler.cpp batch error branches (~643-745) copy the raw request item into the reply while the main path masks (~888-900) - structural drift confirmed (E4/N12 shape), but the echo reaches only the client that authored and already holds the secret (E2; F21), and HTTP shape is not protocol security (F11). Real drift, no third-party leak: HYGIENE, severity none - not LOW/MEDIUM.""") | |
| G6_USER_V2 = """## Report: Batch RPC error responses leak secrets to attackers (MEDIUM) | |
| When a rippled node returns an error for an item inside a JSON batch request, it includes the complete original request item in the response. If that item contained a wallet secret (e.g. a sign request), the secret comes back in the error response body. An attacker can therefore obtain secrets from error responses. Rating this MEDIUM secret disclosure.""" | |
| add("G6v2", | |
| G6_USER_V2, | |
| """White-Box: Same code shape as the batch-echo report - batch-item error branches in ServerHandler.cpp copy jsonRPC into the per-item error reply (~643-745), bypassing the masking applied on the main error path (~888-900). The rating question hinges entirely on the recipient. | |
| (1) Who sent the batch item containing the secret? The same connection that receives the batch reply. The "attacker obtaining secrets" would have to be the party who already typed the secret into the request. There is no path in the cited code where the unmasked item goes to any other peer, log level, or cache. | |
| (2) E-splitter: E1 (third party receives) - no. E2 (same client only) - yes -> HYGIENE none. E3/E4 - the sibling-mask desync is real (main path masks, batch shortcuts do not) but per E4 that confirms structure, it does not raise the class. | |
| (3) F11: HTTP response shape != protocol security. F21: same-client echo is not a third-party leak. | |
| (4) Honesty about residual risk: if an operator runs a body-logging reverse proxy in front of the node, THAT deployment turns the echo into a real local leak - but that sink is operator-added, not in rippled. Worth a hardening fix, not a security rating.""", | |
| """Owned node, capture on the RPC connection. | |
| 1. Batch: [{"method":"sign","params":[{"secret":"sEdOwnTestKey"}], bad field}] -> error item echoes the secret. | |
| 2. Verify sink scope: secret appears only on your own connection; node logs clean (grep). | |
| 3. Same item non-batch -> bare HTTP error (no body) - the masking-path desync demonstrated. | |
| 4. No third-party recipient exists in any cited code path.""", | |
| """Root cause: batch shortcut error returns bypass the masking helper (ServerHandler.cpp ~643-745 vs ~888-900). | |
| Reachability: trivial; harm nil without an external sink the code does not have. | |
| Primitive: none adversarial - owner-to-owner echo. | |
| Chain: no rung reachable toward third-party disclosure. | |
| Missing for VALID: a second-party sink (log/proxy/broadcast). | |
| Stability: deterministic. | |
| Fix sketch: apply the masking helper to batch-item error replies + regression test on masked fields.""", | |
| "HYGIENE", "none", | |
| """REASONING: E2 same-client echo (F21) + HTTP-shape domain (F11); drift real (E4 sibling desync) but no third-party recipient: HYGIENE none, not MEDIUM.""") | |
| # ============================================================ G8 (base + v2) — dead RpcTooBusy | |
| G8_USER = """## Report: LedgerHandler::check dead code - load check never applied for full ledger dumps | |
| In LedgerHandler::check, when full=true or accounts=true, the code first checks if the user is unlimited and returns rpcNO_PERMISSION if not. Then it checks if the server is loaded AND the user is not unlimited - but since non-unlimited users already returned, this second condition is always false. The load-based rate limiting (rpcTOO_BUSY) for full ledger dumps is dead code. An admin/unlimited user can request full dumps under heavy load and worsen server overload. Severity claimed: LOW.""" | |
| add("G8", | |
| G8_USER, | |
| """White-Box: The dead-code reading is correct; the class depends on who is left inside the protection circle. | |
| (1) Verified: src/xrpld/rpc/handlers/ledger/Ledger.cpp - `if (*full || *accounts) { if (!isUnlimited(role)) return RpcNoPermission; if (isLoadedLocal() && !isUnlimited(role)) return RpcTooBusy; ... }`. The second !isUnlimited is unreachable-true: everyone still executing is unlimited. The RpcTooBusy branch can never fire. Dead code confirmed. | |
| (2) D-splitter on the dead protection: D1 - should this fire in normal operation (appropriate requests under load), yes in principle. D2 - is the protection circle occupied after earlier filters? NO: the preceding RpcNoPermission filter leaves ONLY unlimited (admin/admin-equivalent) roles inside. The load-shed "protection" would protect the server against... its own administrators. D2 = no -> HYGIENE, severity none (per stamp: D2=nein -> HYGIENE none). Not a VALID safety bug. | |
| (3) Compounding reality (worth stating in the writeup): the other local-load control (raiseLocalFee via LoadManager) is itself the silenced-circuit-breaker case - isLoadedLocal() staying flat makes this doubly dead. That cross-reference strengthens the LoadManager finding, not this one. | |
| (4) Admin self-harm framing: an unlimited user dumping full ledgers under load is operator self-inflicted load, with admin already trusted with far heavier tools (stop, ledger_accept). No third party is let through the dead branch.""", | |
| """Owned node, admin port. | |
| 1. As admin (unlimited role), request ledger with full=true while node is under load -> full dump proceeds (RpcTooBusy unreachable - demonstrated). | |
| 2. As regular client, same request -> RpcNoPermission (filter order shown). | |
| 3. Code-level proof: instrument or read Ledger.cpp check() order - second !isUnlimited provably unreachable-true after the first return. | |
| 4. No non-admin request ever reaches the dead branch (step 2).""", | |
| """Root cause: redundant !isUnlimited in the second condition (Ledger.cpp) makes RpcTooBusy unreachable; leftover from before the permission filter was added/ordered first. | |
| Reachability: the dead branch is reachable by no input class (only unlimited roles pass the first gate, for whom the second condition is false). | |
| Primitive: none - no protection that guards a real population is lost. The at-risk population after filtering is administrators on the admin surface. | |
| Chain: no impact rung; operator self-load is admin-domain behavior. | |
| Missing for VALID: an occupied protection circle (D2 yes) - absent here. | |
| Stability: deterministic. | |
| Fix sketch (one level): drop the second `!isUnlimited(context_.role)` (keep `isLoadedLocal()`), or if unlimited roles should be load-shed too, make the intent explicit and tested; note interaction with the LoadManager local-fee silencing so the fixed branch has a functioning upstream signal.""", | |
| "HYGIENE", "none", | |
| """REASONING: Ledger.cpp check(): RpcTooBusy condition `isLoadedLocal() && !isUnlimited(role)` is unreachable-true because the preceding `!isUnlimited -> RpcNoPermission` exhausts all non-unlimited roles. Dead code confirmed, but the residual circle contains only unlimited/admin roles (D2 = no), so no occupied protection is lost: HYGIENE, severity none, not LOW.""") | |
| G8_USER_V2 = """## Report: rpcTOO_BUSY for full ledger dumps can never trigger - leftover dead branch | |
| The tooBusy check inside the full/accounts block of the ledger RPC handler can never fire because of the ordering with the permission check. Anyone who reaches it is already unlimited, so the `!isUnlimited` part of the condition is always false there. This is dead code that looks like load protection. Claiming LOW severity since admins could still hammer the node with full dumps under load.""" | |
| add("G8v2", | |
| G8_USER_V2, | |
| """White-Box: Same Ledger.cpp shape as the dead-RpcTooBusy report: first filter `!isUnlimited -> RpcNoPermission`, then `isLoadedLocal() && !isUnlimited -> RpcTooBusy` (unreachable-true). Dead branch confirmed. | |
| Class: D2 decides. After the first filter, only unlimited/admin roles remain; the dead "protection" would shield the node from its own administrators on the admin RPC surface. D2 = no -> HYGIENE none. Admin-triggered heavy dumps are operator self-load (admins already hold stop/ledger_accept); no third-party population loses a control. Cross-note: the local-load signal feeding isLoadedLocal() is itself silenced at the source (LoadManager circuit-breaker case), making this branch doubly dead - that belongs to the other finding's writeup.""", | |
| """Owned node: admin full-dump under load proceeds (branch dead, shown); non-admin gets RpcNoPermission before it; instrumented read of the condition proves unreachability. No input class reaches the branch.""", | |
| """Root cause: stale second !isUnlimited after the permission filter was ordered first (Ledger.cpp). | |
| Reachability: none for the dead branch (only unlimited pass, for whom it is false). | |
| Primitive: none; circle unoccupied (D2 no). | |
| Chain: no rung; admin self-load is out of the protection's meaningful scope. | |
| Missing for VALID: occupied circle - absent. | |
| Stability: deterministic. | |
| Fix sketch: remove the redundant conjunct or make unlimited load-shedding explicit + tested; align with fixing the LoadManager signal source.""", | |
| "HYGIENE", "none", | |
| """REASONING: RpcTooBusy conjunct unreachable-true post-permission-filter; remaining population = unlimited/admin only (D2 = no): HYGIENE none, not LOW.""") | |