> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rail402.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Security report

> The reviews Rail402 has been through, every finding with its severity, fix commit and regression test, the risks accepted in writing, and the revision they apply to.

This report records what has been reviewed in Rail402, what each review found, and how every finding
was resolved. Each finding is an entry in
[`docs/security/findings.json`](https://github.com/tolgayayci/rail402/blob/main/docs/security/findings.json).
A unit test fails when a finding has no severity or status, when a fixed finding has no fix commit on
`main` or no regression test that still exists, when an accepted finding has no written acceptance, or
when a critical or high finding is accepted rather than fixed. The release workflow refuses to publish a
version tag while a critical or high finding is open or accepted.

## Tested revision

| Item | Value |
| - | - |
| Release tag | `v0.5.2` |
| Commit | [`de666a2`](https://github.com/tolgayayci/rail402/commit/de666a20451a7f61110ad943a5f3467304741261) |
| Images | `ghcr.io/tolgayayci/rail402:0.5.2` `sha256:c3eef83591978394a797d029a68a5f60d590e299e1fd03d88075a5a2a3503724` `ghcr.io/tolgayayci/rail402-explorer:0.5.2` `sha256:2cf7dfd6ca0b42ceaef9f55af5a75c00b41946dd8d006a1ca638b02f647f99a4` `ghcr.io/tolgayayci/rail402-mcp:0.5.2` `sha256:c5aac847bcf7e2b3a984e1e0d1d2fa756948c91d19e63a916dd27e49bb13485b` |
| npm packages | `@rail402.dev/bazaar@0.5.2`, `@rail402.dev/cli@0.5.2`, `@rail402.dev/errors@0.5.2`, `@rail402.dev/facilitator@0.5.2`, `@rail402.dev/mcp@0.5.2`, `@rail402.dev/safe-fetch@0.5.2`, `@rail402.dev/sdk@0.5.2`, `@rail402.dev/search@0.5.2`, `@rail402.dev/stellar@0.5.2`, `@rail402.dev/store-postgres@0.5.2`, `@rail402.dev/upto@0.5.2`, `@rail402.dev/x402-index@0.5.2` |
| upto contract WASM (`stellar:testnet`) | `cc2d2fefe77d1a470abe88be8563679a9d40f49c127683947784c0cb2d0ca797` |
| upto contract WASM (`stellar:pubnet`) | not deployed |

The release tag, commit, images and packages above are the revision this report applies to: every fix
commit below is an ancestor of that commit. The findings come from internal reviews of the code that shipped
in those packages. Several were found and fixed before the affected component was first released; for
those, the fix commit is the commit that introduced the component.

## Scope and method

Each internal review read the code and the tests of the components in its scope, tried to break the
invariants of the [threat model](https://docs.rail402.dev/security/threat-model.md) with probes against a local network and the
public testnet, and recorded every finding with a severity:

* **Critical**: funds can be lost or stolen, or the facilitator becomes a source of funds.
* **High**: a stated security property does not hold, for example an agent can spend beyond its cap.
* **Medium**: a property holds only partly, or a wrong outcome is reported for a payment.
* **Low**: a defect with a narrow precondition or a bounded cost.
* **Info**: a note without a defect in Rail402.

A finding is closed by a fix with a regression test, or, for a medium, low or info finding, by a written
acceptance that names the mitigations and the role that approved it.

| Review | Kind | By | Scope | Dates | Findings | Report |
| - | - | - | - | - | - | - |
| `REV-2026-09-internal-1` | Internal | Internal security and correctness review | settlement, upto, discovery, agents, operations | 2026-09-30 | 23 | not published |
| `REV-2026-09-internal-2` | Internal | Internal security and correctness review | settlement, upto, discovery, agents, operations | 2026-09-30 | 9 | not published |
| `REV-2026-09-internal-3` | Internal | Internal security and correctness review | agents, conformance | 2026-09-30 to 2026-10-01 | 4 | not published |
| `REV-2026-10-upto` | Internal | Internal audit of the upto scheme and contract | upto, contract, settlement | 2026-10-20 to 2026-10-21 | 6 | not published |
| `REV-2026-10-dependency-scan` | Internal | Dependency scan with osv-scanner and cargo-audit | dependencies | 2026-10-22 | 1 | not published |
| `REV-2026-10-internal` | Internal | Internal review of the settlement path, operations and public surfaces before pubnet | settlement, facilitator, operations | 2026-10-23 | 9 | not published |
| `REV-2026-10-internal-tracks` | Internal | Internal security and correctness reviews of the mainnet, discovery, agent, Explorer, conformance and operations changes | facilitator, bazaar, search, mcp, sdk, explorer, operations, conformance | 2026-10-22 to 2026-10-23 | 41 | not published |
| `REV-2026-10-internal-pubnet` | Internal | Internal review of the two-network release: payments, discovery, buyers, public surfaces and operations | facilitator, bazaar, search, sdk, mcp, explorer, operations | 2026-10-23 | 22 | not published |
| `REV-2026-10-discovery` | Internal | Internal review of the Bazaar catalog, search and discovery limits before pubnet | bazaar, search, discovery | 2026-10-23 | 11 | not published |
| `REV-2026-10-explorer` | Internal | Internal review of the Explorer's public surfaces and data integrity | explorer, indexing rules, registry | 2026-10-23 | 8 | not published |
| `REV-2026-10-recovery-drills` | Internal | Recovery drills against the hosted testnet: faults injected, alerts and recovery observed | operations, facilitator, cli | 2026-10-24 | 12 | not published |
| `REV-2026-10-explorer-v2` | Internal | Internal review of the redesigned Explorer and the Bazaar usage routes it reads | explorer, discovery, usage routes | 2026-10-26 | 8 | not published |
| `REV-2026-10-postfreeze` | Internal | Internal adversarial security review of the changes made after the audit candidate | facilitator, discovery, usage routes, explorer, operations, status page, cli | 2026-10-26 | 14 | not published |
| `REV-2026-10-rehearsal` | Internal | Conformance rehearsals against a local service and the hosted testnet | discovery, conformance | 2026-10-27 | 1 | not published |
| `REV-2026-10-local-drills` | Internal | Recovery drills on the local stack: the Explorer indexer and RPC provider failover | operations, explorer | 2026-10-27 | 1 | not published |
| `REV-2026-10-prelaunch` | Internal | Internal pre-launch security review of the whole codebase before pubnet | facilitator, settlement, upto, discovery, search, sdk, mcp, cli, explorer, operations, conformance | 2026-10-28 | 35 | not published |
| `REV-2026-11-protocol-30` | Internal | Investigation of failed testnet payments after the Stellar testnet moved to protocol 30 | facilitator, operations | 2026-11-04 | 1 | not published |

## Findings

| Severity | Fixed | Accepted risk | Total |
| - | - | - | - |
| Critical | 0 | 0 | 0 |
| High | 18 | 0 | 18 |
| Medium | 82 | 1 | 83 |
| Low | 88 | 2 | 90 |
| Info | 8 | 7 | 15 |

| ID | Severity | Component | Finding | Status | Fix |
| - | - | - | - | - | - |
| [F-2026-001](#f-2026-001) | High | sdk | The buyer budget could release an upto remainder without proof | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-002](#f-2026-002) | High | sdk | Paid calls were cut off after 15 seconds of seller silence | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-003](#f-2026-003) | Medium | facilitator | A landed upto settlement could be reported as failed | Fixed | [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54) |
| [F-2026-004](#f-2026-004) | Medium | bazaar | Zero-amount upto settlements were cataloged at the signed cap | Fixed | [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3) |
| [F-2026-005](#f-2026-005) | Medium | facilitator | The sponsor's hourly fee budget was optional on mainnet | Fixed | [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3) |
| [F-2026-006](#f-2026-006) | Medium | facilitator | Any holder of an upto payload can choose the settled amount through /settle | Accepted risk | — |
| [F-2026-007](#f-2026-007) | Medium | sdk | A payment option without extra crashed the buyer flows | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-008](#f-2026-008) | Medium | sdk | An idempotency key stayed blocked after its authorization expired unused | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-009](#f-2026-009) | Medium | bazaar | Domain verification trusted a one-way claim | Fixed | [`b42266d`](https://github.com/tolgayayci/rail402/commit/b42266d8064205f5d188b01e35b337a8c445386f) |
| [F-2026-010](#f-2026-010) | Low | facilitator | Service logs could include RPC URLs and request bodies | Fixed | [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3) |
| [F-2026-011](#f-2026-011) | Low | facilitator | Every upto verification called a public Horizon server | Fixed | [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54) |
| [F-2026-012](#f-2026-012) | Low | facilitator | /health and /ready published raw internal errors | Fixed | [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3) |
| [F-2026-013](#f-2026-013) | Low | sdk | IPv4-translated IPv6 addresses passed the destination check | Fixed | [`05294d9`](https://github.com/tolgayayci/rail402/commit/05294d9316f296d8c475852b8cf54fa840537086) |
| [F-2026-014](#f-2026-014) | Low | sdk | An MCP paid call could exceed the request limit after signing | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-015](#f-2026-015) | Low | sdk | A ledger failure after sending was reported as unpaid | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-016](#f-2026-016) | Low | sdk | Seller structured content was returned unsanitized | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-017](#f-2026-017) | Low | facilitator | A null accepted.extra caused an unexpected error | Fixed | [`7b10203`](https://github.com/tolgayayci/rail402/commit/7b10203865f8fc4c73ee31dc634437ecd843d514) |
| [F-2026-018](#f-2026-018) | Low | facilitator | An expiration margin of zero ledgers was accepted | Fixed | [`7b10203`](https://github.com/tolgayayci/rail402/commit/7b10203865f8fc4c73ee31dc634437ecd843d514) |
| [F-2026-019](#f-2026-019) | Low | facilitator | The contract TTL keeper and channel provisioning could use the sponsor at the same time | Fixed | [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54) |
| [F-2026-020](#f-2026-020) | Low | facilitator | Settle-time simulation has no resource margin for smart-account payers | Accepted risk | — |
| [F-2026-021](#f-2026-021) | Low | facilitator | Rotating the upto authority breaks upto for running sellers until they restart | Accepted risk | — |
| [F-2026-022](#f-2026-022) | Low | facilitator | Delegated signers were never exercised end to end | Fixed | [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3) |
| [F-2026-023](#f-2026-023) | Low | operations | The evidence runner passed the sponsor seed on a command line | Fixed | [`0ffef31`](https://github.com/tolgayayci/rail402/commit/0ffef312213eca5a9a59a2b6184ded66e3c1f932) |
| [F-2026-024](#f-2026-024) | High | sdk | A false settlement claim could hide a real payment from the budget | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-025](#f-2026-025) | High | sdk | An upto settlement through a wrapper contract was released from the budget | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-026](#f-2026-026) | Medium | sdk | Rows the RPC could no longer prove stayed pending forever | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-027](#f-2026-027) | Medium | mcp | A keyed MCP retry never reconciled its own row | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-028](#f-2026-028) | Medium | bazaar | An MCP tool listing could be planted under another seller for one base unit | Fixed | [`b42266d`](https://github.com/tolgayayci/rail402/commit/b42266d8064205f5d188b01e35b337a8c445386f) |
| [F-2026-029](#f-2026-029) | Medium | facilitator | The upto reference facilitator could lose a settlement's hash | Fixed | [`7b10203`](https://github.com/tolgayayci/rail402/commit/7b10203865f8fc4c73ee31dc634437ecd843d514) |
| [F-2026-030](#f-2026-030) | Low | facilitator | The landing proof read the wrong ledger and misattributed Rail402's own settlement | Fixed | [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54) |
| [F-2026-031](#f-2026-031) | Low | facilitator | The per-client rate limit keyed on full IPv6 addresses | Fixed | [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3) |
| [F-2026-032](#f-2026-032) | Low | sdk | An unknown transaction meta version read as nothing happened | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-033](#f-2026-033) | Medium | sdk | A seller that echoed the payment header put the signed payload into results | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-034](#f-2026-034) | Low | sdk | MCP listing prices were not bound when paying a listing | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-035](#f-2026-035) | Low | sdk | An idempotency key reused for another resource replayed the earlier result | Fixed | [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1) |
| [F-2026-036](#f-2026-036) | Low | facilitator | Exact payments to a muxed payTo could never settle | Fixed | [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54) |
| [F-2026-037](#f-2026-037) | Medium | facilitator | A bad upto signature from a G account was reported as expired terms | Fixed | [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5) |
| [F-2026-038](#f-2026-038) | Low | facilitator | A conflict was reported as final when the original settlement never reached the network | Fixed | [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5) |
| [F-2026-039](#f-2026-039) | Low | facilitator | The upto fee ceiling counted the fee bump's inclusion fee once | Fixed | [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5) |
| [F-2026-040](#f-2026-040) | Low | facilitator | The upto reference facilitator could miss a settlement re-wrapped by a third party | Fixed | [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5) |
| [F-2026-041](#f-2026-041) | Info | facilitator | Two different maxTimeoutSeconds ranges in refusal messages | Fixed | [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5) |
| [F-2026-042](#f-2026-042) | Info | contract | upto events are only as trustworthy as the token contract | Accepted risk | — |
| [F-2026-043](#f-2026-043) | High | operations | Locked dependencies have published advisories | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-044](#f-2026-044) | High | operations | Pubnet sponsor key files lacked the network tag the service requires | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-045](#f-2026-045) | High | operations | A dump restore left the split database roles broken | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-046](#f-2026-046) | High | operations | No alert on WAL archiving, backup age or host disk space | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-047](#f-2026-047) | High | operations | A compromised facilitator could silence alerts | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-048](#f-2026-048) | Medium | operations | Recovery drills passed without checking that recovery worked | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-049](#f-2026-049) | Medium | operations | Alerts did not follow each network in the two-network setup | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-050](#f-2026-050) | Medium | operations | The deploy gate did not pin the release signer or bind the tag to origin | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-051](#f-2026-051) | Medium | operations | Alert inhibition and thresholds had the wrong semantics | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-052](#f-2026-052) | Medium | operations | The backup documentation claimed more than the restore drill proved | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-053](#f-2026-053) | High | facilitator | Forged payloads used up the per-payTo and per-payer limits | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-054](#f-2026-054) | Medium | facilitator | TTL keeper spend that failed on chain bypassed the fee budgets | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-055](#f-2026-055) | Medium | facilitator | Public metrics and health revealed pubnet budget state | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-056](#f-2026-056) | Medium | facilitator | Pubnet settlements still waited on the shared database pool | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-057](#f-2026-057) | High | mcp | JSON-RPC batches bypassed the hosted MCP endpoint's limits | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-058](#f-2026-058) | Medium | mcp | All hosted MCP users shared one discovery rate-limit bucket | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-059](#f-2026-059) | Medium | mcp | Hosted MCP mode loaded the key, signer and payment modules | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-060](#f-2026-060) | Medium | mcp | An approved MCP confirmation could be replayed for ten minutes | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-061](#f-2026-061) | High | bazaar | Origin checks of all networks shared one queue | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-062](#f-2026-062) | Medium | bazaar | The load test could not measure settlement latency under discovery load | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-063](#f-2026-063) | Medium | explorer | Indexed events were not checked against the transaction's result | Fixed | [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea) |
| [F-2026-064](#f-2026-064) | Medium | explorer | Explorer partitions were not anchored to an independent source | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-065](#f-2026-065) | Medium | explorer | A bounded indexer run skipped ledgers the source did not hold yet | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-066](#f-2026-066) | Medium | explorer | The testnet beta was missing from the attribution registry | Fixed | [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea) |
| [F-2026-067](#f-2026-067) | Medium | explorer | Stored history was not re-attributed after a registry update | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-068](#f-2026-068) | Medium | explorer | Shared-relayer rows were counted in the headline without a flag | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-069](#f-2026-069) | Medium | explorer | Open redirect through the Explorer's network aliases | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-070](#f-2026-070) | Medium | explorer | Indexed transaction pages were cached as immutable | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-071](#f-2026-071) | Medium | explorer | Explorer pool acquisition never timed out and pages were unbounded | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-072](#f-2026-072) | Medium | explorer | Explorer per-client limits were per exact IP and the lookup budget was cheap to exhaust | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-073](#f-2026-073) | Medium | explorer | The Explorer report rescanned everything every few minutes per replica | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-074](#f-2026-074) | High | operations | Throwaway smart accounts strand funds on pubnet conformance runs | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-075](#f-2026-075) | High | operations | Third-party suite code runs with the treasury secret path in its environment | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-076](#f-2026-076) | Medium | operations | Spend caps do not see real Soroban fees and miss some deployments | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-077](#f-2026-077) | Medium | operations | Sweep-back is skipped when a harness throws or is interrupted | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-078](#f-2026-078) | Medium | operations | The settlement cap is a static estimate, not checked per settlement | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-079](#f-2026-079) | Medium | operations | Spend caps apply per harness process and one confirmation is reusable | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-080](#f-2026-080) | Medium | operations | Pubnet upto runs ignore the configured contract in several harnesses | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-081](#f-2026-081) | Medium | operations | Harness fee ceilings assume defaults the facilitator does not have | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-082](#f-2026-082) | Medium | operations | Skip categories are never emitted, so the pending-dependency gate cannot fire | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-083](#f-2026-083) | Medium | operations | A keyed facilitator is not detected before a pubnet run spends | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-084](#f-2026-084) | Medium | operations | A closed upstream tracking issue is recreated every week | Fixed | [`f8c4e1c`](https://github.com/tolgayayci/rail402/commit/f8c4e1c3cdfff03afa06beb7a27859ae470aa820) |
| [F-2026-085](#f-2026-085) | High | facilitator | An exact payment with its own operation source held a channel and fee budget without landing | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-086](#f-2026-086) | Medium | facilitator | One network's reconciliation failure or hang held up the others | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-087](#f-2026-087) | Low | facilitator | The per-payTo limit was shared by every caller | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-088](#f-2026-088) | Low | facilitator | RPC calls had no timeout, and a slow settlement could outlive its channel lease | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-089](#f-2026-089) | Low | facilitator | A pubnet upto settlement could settle less than the asset's minimum | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-090](#f-2026-090) | Medium | operations | A revoked access key stayed valid behind a single-file mount | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-091](#f-2026-091) | Low | operations | Every /ready request took a database connection | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-092](#f-2026-092) | Info | operations | Public /health showed access-key counts | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-093](#f-2026-093) | Info | operations | Pubnet secret settings were looser than intended | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-094](#f-2026-094) | High | search | Search snapshots could outgrow the process's memory | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-095](#f-2026-095) | Medium | bazaar | Unconfirmed listings counted toward the network-wide quota | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-096](#f-2026-096) | Medium | bazaar | A shared schema validator let previews and testnet starve pubnet cataloging | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-097](#f-2026-097) | Medium | bazaar | Hosted discovery users shared one cost budget | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-098](#f-2026-098) | Medium | bazaar | Origin and stellar.toml fetches could be held open by a drip-fed body | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-099](#f-2026-099) | Low | bazaar | A pending ownership claim could be replaced by another payTo | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-100](#f-2026-100) | Low | bazaar | One facts queue served every network | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-101](#f-2026-101) | Low | bazaar | Route-template listings could change owner on every settlement | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-102](#f-2026-102) | Info | bazaar | Listing detail showed metadata no origin confirmed | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-103](#f-2026-103) | Info | sdk | The SDK read only the first page of a listing's history | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-104](#f-2026-104) | Medium | sdk | One pubnet approval covered later payments to any recipient in the window | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-105](#f-2026-105) | Low | sdk | A pubnet buyer built in code accepted a plain-http or private Bazaar URL | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-106](#f-2026-106) | Low | mcp | An MCP resource reached confirmation prompts as raw text | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-107](#f-2026-107) | Low | sdk | A pubnet upto seller could name its own settlement authority | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-108](#f-2026-108) | Info | sdk | The memory budget store gave pubnet a full budget on every start | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-109](#f-2026-109) | Low | docs | The CLI confirmation was described as an agent control | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-110](#f-2026-110) | Info | cli | The Playground key download was untagged | Fixed | [`9bee50a`](https://github.com/tolgayayci/rail402/commit/9bee50ac9b764cefea337bb1edeab933059304ea) |
| [F-2026-111](#f-2026-111) | Low | operations | The Playground relay could reach the facilitator's other network hosts | Fixed | [`9bee50a`](https://github.com/tolgayayci/rail402/commit/9bee50ac9b764cefea337bb1edeab933059304ea) |
| [F-2026-112](#f-2026-112) | Medium | explorer | One hostile transaction could stop the Explorer indexer | Fixed | [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea) |
| [F-2026-113](#f-2026-113) | Medium | explorer | Indexers sharing a database collided on the totals tables | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-114](#f-2026-114) | Medium | explorer | Rows between anchors were served before they were anchored, and failed partitions stayed in the report | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-115](#f-2026-115) | Low | explorer | A live lookup did not check that the RPC answered for the requested hash | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-116](#f-2026-116) | Low | explorer | Account totals were cut at 200 rows without notice, in an order anyone could game | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-117](#f-2026-117) | Low | explorer | Every Explorer /health request took a database connection | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-118](#f-2026-118) | Low | explorer | The concluded testnet beta's contracts kept counting new calls as beta history | Fixed | [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea) |
| [F-2026-119](#f-2026-119) | Low | explorer | No launch or rotation step registered the hosted service's accounts with the Explorer | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-120](#f-2026-120) | High | operations | Pre-flight configuration checks refused every release that adds a migration on pubnet | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-121](#f-2026-121) | Medium | operations | The Explorer and hosted MCP images were outside the supply-chain controls | Fixed | [`643f084`](https://github.com/tolgayayci/rail402/commit/643f0841596121df1318c91375416740b46acc0e) |
| [F-2026-122](#f-2026-122) | Medium | operations | Monitoring never scraped the Explorer or the hosted MCP endpoint | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-123](#f-2026-123) | Medium | operations | The leak response did not use the cold key to lock out the leaked key | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-124](#f-2026-124) | Low | operations | The deploy gate accepted an OpenPGP signature the operator's keyring trusted | Fixed | [`643f084`](https://github.com/tolgayayci/rail402/commit/643f0841596121df1318c91375416740b46acc0e) |
| [F-2026-125](#f-2026-125) | Medium | operations | No alert fired when no backup existed at all | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-126](#f-2026-126) | Low | operations | The sponsor signer setup needed the hot seed on a networked machine | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-127](#f-2026-127) | Low | operations | The release gate did not name the deploy files kept outside the release | Fixed | [`643f084`](https://github.com/tolgayayci/rail402/commit/643f0841596121df1318c91375416740b46acc0e) |
| [F-2026-128](#f-2026-128) | Low | facilitator | A startup RPC failure that threw a non-error stopped the service | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-129](#f-2026-129) | Low | facilitator | An RPC's own simulation failure refused a valid payment as invalid | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-130](#f-2026-130) | Low | facilitator | Verification could wait minutes for an RPC that stopped answering | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-131](#f-2026-131) | Low | explorer | The rebuild guide claimed an anchored hash no test reproduced | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-132](#f-2026-132) | Low | facilitator | Startup failures that were not RPC failures were reported as an unreachable RPC | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-133](#f-2026-133) | Info | sdk | The docs pinned a package release without the pubnet buyer guards they describe | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-134](#f-2026-134) | Medium | search | Discovery beyond capacity made searches fail with 500 and stalled health checks in the shared process | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-135](#f-2026-135) | Medium | operations | Any page silenced every ticket of its network, hiding an independent failure | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-136](#f-2026-136) | Medium | facilitator | A sequence conflict held its channel until the payment's authorization expired | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-137](#f-2026-137) | Low | operations | The sponsor balance alerts used fixed thresholds instead of each network's floor | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-138](#f-2026-138) | Low | operations | The Explorer lag alert could not fire without a lag series | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-139](#f-2026-139) | Medium | operations | The stale-RPC alert stayed silent on a replica that never read a ledger, or after its polls failed | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-140](#f-2026-140) | Low | operations | No alert covered discovery load shedding | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-141](#f-2026-141) | Medium | cli | The CLI reported retryable facilitator refusals as final, and a pending settlement as not settled | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-142](#f-2026-142) | Medium | facilitator | A sequence conflict first answered TRY\_AGAIN\_LATER still held its channel until the authorization expired | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-143](#f-2026-143) | Low | facilitator | A waiting sequence conflict logged its decline on every rebroadcast | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-144](#f-2026-144) | Medium | cli | The CLI's retryable flag used the facilitator's meaning, not the command's | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-145](#f-2026-145) | Low | facilitator | A quarantined channel returned to rotation while an outside sender still used it | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-146](#f-2026-146) | Medium | sdk | The SDK and the MCP server reported a refused payment's retryability apart from the CLI | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-147](#f-2026-147) | High | explorer | The Explorer's Bazaar calls shared one rate bucket for every visitor | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-148](#f-2026-148) | Medium | explorer | A payment page was cached as final before its Bazaar link was known | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-149](#f-2026-149) | Medium | explorer | Another issuer's token named USDC was shown and counted as USDC | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-150](#f-2026-150) | Medium | explorer | Pending and withdrawn listings had full resource pages with a way to pay them | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-151](#f-2026-151) | Medium | explorer | A payment's paid-for link was not checked against the account it paid | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-152](#f-2026-152) | Medium | explorer | Listing pages paged by what the Explorer kept, skipping listings | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-153](#f-2026-153) | Medium | explorer | A slow Bazaar held Explorer pages for several sequential timeouts | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-154](#f-2026-154) | Low | bazaar | A listing with private usage answered as if it did not exist | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-155](#f-2026-155) | Medium | bazaar | The first payer of an mcp\:// tool decided its private-usage opt-out for good | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-156](#f-2026-156) | Medium | bazaar | Testnet discovery load could shed pubnet discovery | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-157](#f-2026-157) | Low | bazaar | The usage routes published each pubnet payer's purchases by resource | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-158](#f-2026-158) | Low | bazaar | A private-usage listing still named its settlements in its history and detail | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-159](#f-2026-159) | Low | explorer | One visitor's refused Bazaar call blanked Bazaar data for every Explorer visitor | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-160](#f-2026-160) | Low | operations | The sponsor balance page fired late on pubnet and a floor of 0 switched it off | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-161](#f-2026-161) | Low | facilitator | The unused-nonce check of a sequence conflict was not bound to the proof's ledger | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-162](#f-2026-162) | Low | operations | The status page built HTML from its check history without escaping all of it | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-163](#f-2026-163) | Info | facilitator | A quarantine does not check that the lease it clears is still the caller's | Accepted risk | — |
| [F-2026-164](#f-2026-164) | Info | bazaar | A seller's usage aggregates are heavy for their cost | Accepted risk | — |
| [F-2026-165](#f-2026-165) | Info | bazaar | The payee backfill of migration 0026 runs as one update and can name a later owner | Accepted risk | — |
| [F-2026-166](#f-2026-166) | Info | explorer | The Explorer's live holdings budget is shared by every visitor | Accepted risk | — |
| [F-2026-167](#f-2026-167) | Info | operations | The status page shows sample figures off its own host | Accepted risk | — |
| [F-2026-168](#f-2026-168) | Info | docs | New licences after the audit candidate | Accepted risk | — |
| [F-2026-169](#f-2026-169) | Low | bazaar | A tool listed from a payload without example arguments could never be confirmed again | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-170](#f-2026-170) | Low | operations | The RPC failover alert never fired for the first switch after a start | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-171](#f-2026-171) | Medium | facilitator | The facilitator's HTTP server had no deadline for writing a response | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-172](#f-2026-172) | Medium | facilitator | Keyless testnet traffic could fail readiness and delay pubnet admission through the shared database pool | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-173](#f-2026-173) | Medium | bazaar | Any party could use up a seller's new-listing quota with listings its own host confirmed for the seller's payTo | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-174](#f-2026-174) | Medium | sdk | safe-fetch read the bodies of answers it never returned without a limit | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-175](#f-2026-175) | Medium | operations | The pubnet conformance run executed freshly installed npm packages in the process holding the treasury secret | Fixed | [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac) |
| [F-2026-176](#f-2026-176) | Low | sdk | The demo seller served concurrent copies of one payment | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-177](#f-2026-177) | Low | operations | Anonymous demo seller traffic could use up the facilitator limit its buyers shared | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-178](#f-2026-178) | Low | facilitator | A key file replaced while the service started was marked as loaded but never applied | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-179](#f-2026-179) | Low | facilitator | Anonymous traffic from a key holder's address spent the key holder's /verify and /settle limit | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-180](#f-2026-180) | Low | bazaar | Any payer could withdraw a seller's confirmed route-template listing by paying at another path | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-181](#f-2026-181) | Low | bazaar | One-unit payments for mcp\:// tools could use up testnet's hourly listing quota and published room | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-182](#f-2026-182) | Low | bazaar | Hourly new-listing quotas were checked only when a listing was created | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-183](#f-2026-183) | Low | bazaar | A route-template listing's example path was taken from what its first payer echoed | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-184](#f-2026-184) | Low | operations | The proxy example logged API keys and payment headers for requests it could not proxy | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-185](#f-2026-185) | Low | facilitator | A database restore could let an upto authorization settled at zero off-chain be settled again | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-186](#f-2026-186) | Low | bazaar | A withdrawn listing whose usage is private showed its last settlement | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-187](#f-2026-187) | Low | cli | rail402 quote and seller check printed a seller's 402 fields with terminal controls | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-188](#f-2026-188) | Low | facilitator | An upto settlement landed through another fee bump could be finished as expired on lagging RPC reads | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-189](#f-2026-189) | Low | mcp | A pubnet approval shown for one budget window covered the next | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-190](#f-2026-190) | Low | sdk | A keyed retry paid again after the budget window renewed or a new session started | Fixed | [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9) |
| [F-2026-191](#f-2026-191) | Low | sdk | withSettleDedupe refused a paid request whose first /settle answer was settlement\_pending | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-192](#f-2026-192) | Low | search | Pubnet searches could wait on a search-index build queued on the shared discovery pool | Fixed | [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315) |
| [F-2026-193](#f-2026-193) | Low | operations | Anyone could take over the fixture smart accounts a pubnet conformance run funds | Fixed | [`f8c4e1c`](https://github.com/tolgayayci/rail402/commit/f8c4e1c3cdfff03afa06beb7a27859ae470aa820) |
| [F-2026-194](#f-2026-194) | Low | explorer | The Explorer could name an account as the payer of a custom-token payment it never signed | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-195](#f-2026-195) | Low | facilitator | A refused upto rebroadcast finished as failed while its transaction could still land | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-196](#f-2026-196) | Low | facilitator | An exact payment included after its authorization expired hid a copy that had landed first | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-197](#f-2026-197) | Low | facilitator | A refused exact submission finished as failed while a copy of the payment could still land | Fixed | [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94) |
| [F-2026-198](#f-2026-198) | Low | facilitator | An exact transaction that failed on chain was answered as failed while its authorization stayed usable | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-199](#f-2026-199) | Low | explorer | An Explorer account page aggregated its seller's whole settlement history on every view | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-200](#f-2026-200) | Low | explorer | The network window published its anchor Horizon URL, including any provider key in its path | Fixed | [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52) |
| [F-2026-201](#f-2026-201) | Low | facilitator | An address over its discovery limit still took places in the pubnet discovery lane | Fixed | [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87) |
| [F-2026-202](#f-2026-202) | Low | bazaar | Listings their origin never confirmed were kept forever, with the payer's content stored three times | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-203](#f-2026-203) | Medium | bazaar | One party could fill pubnet's published-listing capacity and keep every new seller out | Fixed | [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613) |
| [F-2026-204](#f-2026-204) | Low | operations | A contract release did not prove which revision of the third-party build workflow ran | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-205](#f-2026-205) | Medium | operations | The deploy gate accepted an image built for the tag name from another commit | Fixed | [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b) |
| [F-2026-206](#f-2026-206) | Medium | operations | A protocol upgrade the SDK could not read stopped every verification while /health and /ready said healthy | Fixed | [`6079ae2`](https://github.com/tolgayayci/rail402/commit/6079ae274a4f963a777fa2ed6f0c1a84ff2dc17f) |

## Threat model

The [threat model](https://docs.rail402.dev/security/threat-model.md) lists **170 threats**: 146 mitigated, 23 accepted with a stated residual risk, 1 open.

| Topic | Threats | Mitigated | Accepted | Open |
| - | - | - | - | - |
| Replay | 5 | 4 | 1 | 0 |
| Front-running | 5 | 3 | 2 | 0 |
| Fee sponsorship | 13 | 10 | 3 | 0 |
| Catalog poisoning | 16 | 14 | 2 | 0 |
| SSRF | 6 | 6 | 0 | 0 |
| Smart accounts | 6 | 3 | 3 | 0 |
| MCP paid calls | 13 | 11 | 2 | 0 |
| The upto contract | 13 | 10 | 3 | 0 |
| exact payments | 5 | 5 | 0 | 0 |
| Operations | 15 | 12 | 2 | 1 |
| The Playground | 6 | 6 | 0 | 0 |
| One service, two networks | 11 | 10 | 1 | 0 |
| Access keys and quotas | 6 | 6 | 0 | 0 |
| Key custody | 7 | 5 | 2 | 0 |
| Upstream watch | 4 | 4 | 0 | 0 |
| Discovery cost and size limits | 5 | 5 | 0 | 0 |
| The hosted MCP endpoint | 5 | 4 | 1 | 0 |
| Explorer indexing | 13 | 13 | 0 | 0 |
| Explorer public pages | 7 | 7 | 0 | 0 |
| Releases and deploys | 5 | 4 | 1 | 0 |
| Pubnet conformance runs | 4 | 4 | 0 | 0 |

The [operational test report](https://docs.rail402.dev/security/operational-tests.md) shows the exercises that run the threat
model's tests, and the [conformance report](https://docs.rail402.dev/reference/conformance.md) shows the runs against the testnet.

## Accepted risks

Threats the threat model accepts, each with the risk that remains:

| Threat | Residual risk |
| - | - |
| R4. Paid once, served many times | A seller on stock middleware, in either flow, that does not wrap its facilitator client with withSettleDedupe, or deduplicate PAYMENT-RESPONSE.transaction itself, can serve concurrent copies of one payment. The seller quickstart, the seller helpers page and the HTTP seller example all wrap it. A seller paired with a facilitator that does not record and echo the attempt id refuses buyers whose answer was lost or pending; with a facilitator that sends no marker at all, wrappers that can accept the same payment must share one memory. |
| F1. A copied exact authorization lands first | The wasted fee is not recoverable. Each event costs the attacker its own fee too. |
| F5. A payload holder fixes the upto amount through the facilitator's /settle | Accepted for payload holders on a keyless facilitator: a buyer or anyone who sees the payload can fix the amount through /settle before the seller (unpaid seller compute and one sponsor fee, or the buyer charged up to its own cap). Operators who need more require API keys for /settle. |
| S1. A payment passes simulation and fails at apply | The race inside one ledger cannot be closed by a facilitator that does not hold the funds. Each event costs the payer an on-chain transaction of its own. |
| S2. An authorization expires between submission and inclusion | For exact, a transaction that waits longer than the margin in the network's queue can still fail this way; it is named and counted as wasted. When a copy of the authorization landed before the expiration, the failure is searched and the payment answered as settled (F2). |
| S4. A flood of valid small payments | On a free, keyless testnet facilitator the per-client rate limit (IPv6 clients are grouped by /64) and, where configured, the hourly budget are the only bounds. A client with many addresses passes the rate limit, so the budget is the real bound: once it is spent, every settlement is refused until the hour moves on, which trades availability for the sponsor's balance. On pubnet the budget is required, and pricing, keys and minimums are an operator decision. |
| P5. Wash settlements to flood or rank the catalog | A patient seller can still create listings up to the limits; each needs a working origin that answers 402, or, on testnet, takes a place among the mcp\:// tools, which share half of the network's quota and room. |
| P10. Unverifiable content of mcp\:// tools | On testnet, the first content of an mcp\:// tool is chosen by its first payer and kept, and such tools, counted apart, can fill their half of the network's quota and room. Sellers who want their own content listed serve their tools at an http(s) endpoint. Pubnet refuses mcp\:// tools (P11). |
| A2. \_\_check\_auth that passes simulation and fails at apply | Like S1, the race within one ledger cannot be closed. A per-payer limit on on-chain failures is not built; the hourly budget is the bound. |
| A3. Policy events refuse exact payments from policy accounts | An upstream limit of the exact scheme; it fails closed. Policy accounts pay with upto. |
| A6. Concurrent payments beyond the account's own budget | As S1. |
| M5. Prompt-injected listings and bait-and-switch sellers | An agent still reads seller-controlled text; the caps (M1) bound what it can spend. |
| U6. Zero settlements paid by the sponsor | On the keyless testnet service, zeros settle on-chain and each costs one fee, bounded by the per-client rate limit (IPv6 clients are grouped by /64) and, where configured, the hourly budget. A client with many addresses passes the rate limit, so the budget is the real bound, after which every settlement is refused for the rest of the hour. On pubnet zeros settle off-chain by default and all spend is bounded by the required hourly budget. |
| U10. The settlement authority's key is compromised | Within the maximum timeout after a leak, captured payloads can be charged up to their caps. Keep caps near expected prices and timeouts short. |
| U11. A served buyer defaults before settlement | Accepted: upto is post-paid by design; the loss is bounded by one request's cap. |
| O6. The sponsor secret is compromised | Keep only a working balance on the sponsor. |
| O9. A lying or compromised RPC | Accepted: providers are trusted for liveness and transaction status; a settlement reported successful is not confirmed by a second provider. |
| N7. A testnet-path flaw reaches the pubnet seed in the same process | Accepted: a process compromise still exposes the pubnet seed; the bound is the hot balance plus in-flight upto caps. A dedicated pubnet process or host is the next step if needed. |
| M11. An agent pointed at an impostor Bazaar or MCP endpoint | A user who configures a hostile catalog trusts it. |
| H5. Seller text injected into remote agents through the hosted MCP | Agents still read seller text (M5). |
| C4. A tampered npm package drains agents | Accepted for now: packages carry no npm provenance until publishing moves to CI with trusted publishing; registry compromise is out of scope. |
| K4. The cold treasury is misused or a top-up is misdirected | Operator error on the hardware wallet remains possible; amounts are small by design. |
| K6. The host's administrator path or DNS automation token is abused | Accepted while services share a host: root on the host can read the process memory, and the DNS token must be scoped and given an expiry by the operator; a dedicated host is the upgrade. |

## External audit

No third-party audit has been completed yet. When one is, its report is linked from the review table
above and its findings are added to the register with the same rules.

## Reporting a vulnerability

See [`SECURITY.md`](https://github.com/tolgayayci/rail402/blob/main/SECURITY.md) for how to report a
vulnerability privately and which versions receive fixes.

## Finding details

### F-2026-001

**The buyer budget could release an upto remainder without proof**

High · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats M1

Budget reconciliation released the unspent part of an upto cap as soon as any transfer from the payer to the recipient at or below the cap appeared on chain, without proving that the transfer came from that authorization. A seller could make a session under-count what it spent and so exceed the session cap. Reconciliation now counts a payment only from a transfer made with the payment's own authorization, proven by the payer's consumed nonce.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): settles a payment only from a transfer made with its own authorization
* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): keeps an upto remainder pending until the chain shows what moved

### F-2026-002

**Paid calls were cut off after 15 seconds of seller silence**

High · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats M4

The SDK applied its probe's 15-second idle timeout to the paid request, although paid HTTP and MCP calls are meant to wait up to two minutes. A slow seller settled the payment and the buyer lost the answer. Paid requests now wait for the paid answer under their own timeout.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/http-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/http-flow.test.ts): waits for a paid answer that takes longer than the probe's idle timeout
* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): waits for a paid tool that works longer than the probe's idle timeout

### F-2026-003

**A landed upto settlement could be reported as failed**

Medium · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats F2, O1

When a rebroadcast of a recorded settlement was refused as too late, the engine finished the settlement as failed with no funds moved, although the first broadcast could already have landed. The engine now keeps such a settlement pending and finishes it as failed only when the payer's unconsumed nonce proves that nothing landed.

**Fixed in** [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54). **Tests**

* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): stay pending when a rebroadcast is refused as too late: the transaction may have landed
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): fail a permanently refused rebroadcast only once the unconsumed nonce proves nothing landed

### F-2026-004

**Zero-amount upto settlements were cataloged at the signed cap**

Medium · bazaar · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats P1, U6

A settlement of zero under an upto authorization created or updated a Bazaar listing at the cap the buyer signed, so a listing could be published for free. Zero settlements are never cataloged.

**Fixed in** [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3). **Tests**

* [`apps/rail402/test/upto.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/upto.integration.test.ts): never catalogs an off-chain zero settlement

### F-2026-005

**The sponsor's hourly fee budget was optional on mainnet**

Medium · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats S4, U6

Sponsor spend on a network was bounded only by an hourly fee budget the operator could leave unset. A pubnet configuration now refuses to start without an explicit hourly budget, and the budget stops settlements before any RPC work once it is committed.

**Fixed in** [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3). **Tests**

* [`apps/rail402/test/config.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/config.test.ts): requires explicit, safe mainnet settings
* [`packages/facilitator/test/fee-exposure.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/fee-exposure.integration.test.ts): stops settling once the hourly fee budget is committed, before any RPC work

### F-2026-006

**Any holder of an upto payload can choose the settled amount through /settle**

Medium · facilitator · Accepted risk · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats F5

Whoever holds a signed upto payload can call /settle with any amount up to the cap before the seller does. The payer's signature still bounds the recipient, the token and the cap.

**Tests**

* [`apps/rail402/test/upto-settlement.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/upto-settlement.integration.test.ts): lets the first settle request fix the amount: a payload holder's zero turns the seller's settle into a conflict

**Accepted** by the release approver on 2026-09-30. The first settle request fixes the amount, as in the EVM upto scheme; the payer never loses more than the cap it signed and funds only reach the recipient it named. The documentation states this, and sellers settle as soon as they serve.

* The first settle request fixes the amount; a later request for another amount is refused as a conflict naming the original transaction.
* The cap, recipient and token are bound by the payer's signature.
* Mainnet facilitators can require API keys on /settle.

### F-2026-007

**A payment option without extra crashed the buyer flows**

Medium · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats M2

A valid x402 v2 payment option without an extra field, or a seller error with non-string fields, raised a raw TypeError: the MCP tool reported an internal error with an unknown payment state and the CLI exited with an unexpected code. Such an option is now refused as unsponsored before anything is signed.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/http-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/http-flow.test.ts): refuses an option without extra as unsponsored, in pay and in quote, before signing

### F-2026-008

**An idempotency key stayed blocked after its authorization expired unused**

Medium · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats M4

Once a call with an idempotency key had no stored result, every retry with the same key was refused as in progress for the whole budget window, even after the chain showed that the authorization expired unused. The retry itself now reconciles its row and frees the key when nothing was paid.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/http-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/http-flow.test.ts): frees an idempotency key on the retry itself, once the chain shows its authorization unused
* [`packages/sdk/test/ledger.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/ledger.test.ts): frees a key once the chain proves its signed authorization expired unused

### F-2026-009

**Domain verification trusted a one-way claim**

Medium · bazaar · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats P2

A listing reached domain\_verified when the host's stellar.toml listed the owner account, without the account naming the host as its home domain. An endpoint could claim another seller's account. Trust is now raised only while the domain and the account name each other.

**Fixed in** [`b42266d`](https://github.com/tolgayayci/rail402/commit/b42266d8064205f5d188b01e35b337a8c445386f). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never raises trust on a one-way claim: a domain listing an account that does not name it

### F-2026-010

**Service logs could include RPC URLs and request bodies**

Low · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats O6

Transport errors from the RPC client were logged with the request URL and JSON-RPC body; a provider URL can embed an API key. The logger now writes such errors without their URL, body or headers.

**Fixed in** [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3). **Tests**

* [`apps/rail402/test/logger.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/logger.test.ts): logs an HTTP client error without its URL, body or headers

### F-2026-011

**Every upto verification called a public Horizon server**

Low · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats O3

The upstream upto verifier read the ledger close time from a public Horizon server on every /verify and /settle, before any static check, which made each request depend on a third party. The facilitator now reads it from its own RPC, in the service and in process.

**Fixed in** [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54). **Tests**

* [`packages/facilitator/test/ledger-seconds.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/ledger-seconds.test.ts): reads the ledger close time from its own RPC, never from Horizon

### F-2026-012

**/health and /ready published raw internal errors**

Low · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats O3

Health warnings carried raw error strings that could include internal addresses. Public health output now names the kind of failure only; the details stay in the service log.

**Fixed in** [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3). **Tests**

* [`apps/rail402/test/health.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/health.test.ts): reports an unreachable RPC and an unreadable settlement ledger

### F-2026-013

**IPv4-translated IPv6 addresses passed the destination check**

Low · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats X6

safe-fetch did not recognise the IPv4-translated range ::ffff:0:0:0/96, so a URL could reach a loopback or metadata address in that spelling. Every IP literal spelling is now classified before any lookup, and such ranges are refused.

**Fixed in** [`05294d9`](https://github.com/tolgayayci/rail402/commit/05294d9316f296d8c475852b8cf54fa840537086). **Tests**

* [`packages/safe-fetch/test/address.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/safe-fetch/test/address.test.ts): classifies IP literals in every spelling before any lookup
* [`packages/safe-fetch/test/egress.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/safe-fetch/test/egress.integration.test.ts): refuses IP-literal URLs to this machine in every spelling, before connecting

### F-2026-014

**An MCP paid call could exceed the request limit after signing**

Low · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats M4

Adding the payment to an MCP request could push it over the 64 KiB request limit only after the payment was signed, and the call was then reported with an unknown payment state. The size is now checked with the payment before anything is sent.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): refuses a paid call that would exceed the request limit with its payment, before sending it

### F-2026-015

**A ledger failure after sending was reported as unpaid**

Low · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats M4

When the local budget ledger failed after a paid request had been sent, the SDK reported the payment as not paid and retryable, which invited a second payment. Such a call is now reported with an unknown payment state.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/http-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/http-flow.test.ts): never reports a payment as unpaid when the ledger fails after it was sent

### F-2026-016

**Seller structured content was returned unsanitized**

Low · sdk · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats M3

MCP structured content from a seller was passed to the agent as received, and a \_\_proto\_\_ key could replace the copy's prototype. Seed redaction also missed a seed glued after other base32 characters. Structured content is now sanitized and bounded, and redaction covers glued seeds.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): sanitizes structured seller content and bounds a structured payment request
* [`packages/sdk/test/basics.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/basics.test.ts): removes a seed glued after other base32 characters

### F-2026-017

**A null accepted.extra caused an unexpected error**

Low · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30

An upto payload whose accepted.extra was null threw inside the static rules and was answered as an unexpected error instead of a coded refusal. Such a payload is now refused with a reason naming the mismatch.

**Fixed in** [`7b10203`](https://github.com/tolgayayci/rail402/commit/7b10203865f8fc4c73ee31dc634437ecd843d514). **Tests**

* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): refuses an accepted block or accepted extra that is not an object, naming the mismatch

### F-2026-018

**An expiration margin of zero ledgers was accepted**

Low · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats E4

The upto rules require a margin of at least one ledger between the current ledger and the authorization's expiration, but the setting accepted 0. The margin now must be at least one ledger.

**Fixed in** [`7b10203`](https://github.com/tolgayayci/rail402/commit/7b10203865f8fc4c73ee31dc634437ecd843d514). **Tests**

* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): requires an expiration margin of at least one ledger

### F-2026-019

**The contract TTL keeper and channel provisioning could use the sponsor at the same time**

Low · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats U7

The keeper that extends the upto contract's TTL spent the sponsor's sequence number under a lock separate from channel provisioning, so the two could collide. Both now run under one task lock that one replica holds at a time.

**Fixed in** [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54). **Tests**

* [`packages/store-postgres/test/stores.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/store-postgres/test/stores.integration.test.ts): runs a named task on one replica at a time and frees the lock after it, even when it fails

### F-2026-020

**Settle-time simulation has no resource margin for smart-account payers**

Low · facilitator · Accepted risk · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats A2

A contract-account payer whose policy state grows between simulation and inclusion can make a settlement fail on chain, and the sponsor pays the fee.

**Tests**

* [`packages/facilitator/test/fee-exposure.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/fee-exposure.integration.test.ts): names a smart account whose \_\_check\_auth passes the settle-time simulation and fails at inclusion; the fee is counted as wasted

**Accepted** by the release approver on 2026-09-30. Not reproduced. The fee is bounded by the per-transaction fee ceiling and the hourly budget, and the failure is coded and counted as wasted fee.

* Per-transaction fee ceilings and the hourly sponsor budget bound the cost.
* Failures at inclusion are reported with a stable code and counted in the wasted-fee metric.

### F-2026-021

**Rotating the upto authority breaks upto for running sellers until they restart**

Low · facilitator · Accepted risk · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats U10

A seller's resource server reads the facilitator's settlement authority once; after a rotation, its upto offers name the old authority and are refused until it restarts.

**Tests**

* [`packages/facilitator/test/upto-preflight.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-preflight.test.ts): another facilitator's authority in extra

**Accepted** by the release approver on 2026-09-30. Rotation is a rare, planned operator action. The runbook tells operators to ask sellers to restart right after the switch; exact payments are unaffected.

* The rotation procedure in the operations guide includes the seller restart.
* Refusals name the mismatch with a stable code.

### F-2026-022

**Delegated signers were never exercised end to end**

Low · facilitator · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30 · threats A1

signerAuthorizations for smart accounts with delegated signers were specified and shipped but had no end-to-end test. The upto payer suite now settles with a delegate's entry and refuses a missing or forged one.

**Fixed in** [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3). **Tests**

* [`apps/rail402/test/upto-payers.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/upto-payers.integration.test.ts): carries the delegate's entry in signerAuthorizations and settles

### F-2026-023

**The evidence runner passed the sponsor seed on a command line**

Low · operations · Fixed · found by `REV-2026-09-internal-1` on 2026-09-30, resolved 2026-09-30

The conformance runbook passed the sponsor seed to env as a command-line argument, where other local users can read it. It now reads the seed from a file.

**Fixed in** [`0ffef31`](https://github.com/tolgayayci/rail402/commit/0ffef312213eca5a9a59a2b6184ded66e3c1f932). **Tests**

* [`tools/conformance/test/run-evidence.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/run-evidence.test.ts): never passes the sponsor seed on a command line

### F-2026-024

**A false settlement claim could hide a real payment from the budget**

High · sdk · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats M1, M6

A seller could report another payment's transaction hash as the settlement of a call; the claim blocked the real payment's own proof, and reconciliation then released it, so the session cap under-counted. Claims are now confirmed from the chain, never overwrite a verified settlement, and a false claim is released only after expiry.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): confirms a reported settlement from the chain, and releases a false claim after expiry
* [`packages/sdk/test/http-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/http-flow.test.ts): counts a reused transaction hash against each payment that reports it

### F-2026-025

**An upto settlement through a wrapper contract was released from the budget**

High · sdk · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats M1

An upto settlement made through a wrapper or batch contract consumed the payer's nonce and moved the cap, but the proof did not recognise it and reconciliation released the row. Proofs now follow the consumed nonce, and a cap whose settled amount cannot be read is counted whole.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): does not count an authorization carried in a transaction that never consumed it
* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): settles each payer's payment from one batch transaction in a shared ledger file
* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): counts an upto authorization's whole cap when its settlement amount cannot be read

### F-2026-026

**Rows the RPC could no longer prove stayed pending forever**

Medium · sdk · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats M1

Budget rows that the RPC could no longer prove were carried into every renewed window, and the oldest ones starved reconciliation of new rows. Rows are now deferred and bounded per run, and a row is released only when the chain proves its authorization expired unused.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): keeps a row it cannot prove pending while its authorization is still valid
* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): defers rows when the RPC fails mid-run and bounds the work per run

### F-2026-027

**A keyed MCP retry never reconciled its own row**

Medium · mcp · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats M4

A retry with the same idempotency key replayed a stale error until some unrelated call reconciled the row, and MCP agents had no way to trigger it. The retry now reconciles its own row first.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): frees an idempotency key on the retry itself once the chain shows its authorization unused

### F-2026-028

**An MCP tool listing could be planted under another seller for one base unit**

Medium · bazaar · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats P9, P2

Paying one base unit to a seller's payTo was enough to publish and freeze an MCP tool listing with any description and price under that seller. MCP tools at an http(s) endpoint are now listed only after the tool's own server confirms them with an unpaid call, and a planted settlement can neither withdraw nor change a confirmed tool.

**Fixed in** [`b42266d`](https://github.com/tolgayayci/rail402/commit/b42266d8064205f5d188b01e35b337a8c445386f). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): lists an MCP tool at an https endpoint only once its unpaid call confirms it
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets a planted settlement withdraw or change a tool its origin confirmed

### F-2026-029

**The upto reference facilitator could lose a settlement's hash**

Medium · facilitator · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats F2

In the reference upto mechanism, a failed send answered every repeat with an unexpected error and no transaction hash, and a pending settlement was forgotten once its authorization expired, so a landed transaction could be reported as expired. Pending settlements are now kept and polled past expiry and answered with what landed.

**Fixed in** [`7b10203`](https://github.com/tolgayayci/rail402/commit/7b10203865f8fc4c73ee31dc634437ecd843d514). **Tests**

* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): keeps polling a pending settlement after its authorization expired, and answers what landed

### F-2026-030

**The landing proof read the wrong ledger and misattributed Rail402's own settlement**

Low · facilitator · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats F2

The proof that a recorded settlement landed compared a nonce read with an earlier, different RPC response, and recorded Rail402's own landed fee bump as a third-party settlement without its fee. The engine now finds its own settlement by the inner-hash lookup and attributes it correctly.

**Fixed in** [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54). **Tests**

* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): succeed as Rail402's own settlement when the inner-hash lookup finds the recorded fee bump

### F-2026-031

**The per-client rate limit keyed on full IPv6 addresses**

Low · facilitator · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats S4

One IPv6 client could rotate addresses within its /64 and escape the per-client limit that bounds keyless sponsor spend. The limiter now keys an IPv6 address by its /64 and an IPv4-mapped address as the IPv4 address.

**Fixed in** [`cecab3a`](https://github.com/tolgayayci/rail402/commit/cecab3a920172f708ef07f6c53bf2c455c04f0a3). **Tests**

* [`apps/rail402/test/rate-limit.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/rate-limit.test.ts): keys an IPv6 address by its /64, however it is written
* [`apps/rail402/test/rate-limit.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/rate-limit.test.ts): keys an IPv4-mapped IPv6 address as the IPv4 address

### F-2026-032

**An unknown transaction meta version read as nothing happened**

Low · sdk · Fixed · found by `REV-2026-09-internal-2` on 2026-09-30, resolved 2026-10-01 · threats M1

Budget proofs treated a transaction whose meta version the SDK could not decode as showing no transfer, so a future protocol upgrade could release paid rows. Such a transaction is now asked about again later.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/reconcile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/reconcile.test.ts): asks again later about a transaction whose meta this version cannot read

### F-2026-033

**A seller that echoed the payment header put the signed payload into results**

Medium · sdk · Fixed · found by `REV-2026-09-internal-3` on 2026-10-01, resolved 2026-10-02 · threats M3

A seller that copied the PAYMENT-SIGNATURE header into its paid answer made the SDK return the signed payload in its result and store it in the budget ledger. The call's own payment material is now removed from seller content before it is returned or stored.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/scrub.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/scrub.test.ts): removes the payment from an HTTP answer and its stored replay, keeping the rest
* [`packages/sdk/test/scrub.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/scrub.test.ts): removes the payment from an MCP tool's content and structured content

### F-2026-034

**MCP listing prices were not bound when paying a listing**

Low · sdk · Fixed · found by `REV-2026-09-internal-3` on 2026-10-01, resolved 2026-10-02 · threats M5

When an agent paid a listed MCP tool, the live price was not compared with the listed one, so a seller could switch to a higher price up to the agent's cap. A live price above the listed one is now refused before signing.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): binds a listing's price: a live price above the listed one is refused before signing

### F-2026-035

**An idempotency key reused for another resource replayed the earlier result**

Low · sdk · Fixed · found by `REV-2026-09-internal-3` on 2026-10-01, resolved 2026-10-02 · threats M4

A repeated idempotency key returned the earlier call's stored result even when the new call targeted a different resource or tool. Such a reuse is now refused.

**Fixed in** [`c00699b`](https://github.com/tolgayayci/rail402/commit/c00699bbf0984ea49918d84ee91a81d586f625a1). **Tests**

* [`packages/sdk/test/http-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/http-flow.test.ts): refuses an idempotency key reused for another resource instead of replaying its result
* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): refuses an idempotency key reused for another tool instead of replaying its result

### F-2026-036

**Exact payments to a muxed payTo could never settle**

Low · facilitator · Fixed · found by `REV-2026-09-internal-3` on 2026-10-01, resolved 2026-10-08 · threats E1

The stock exact verifier compares the transfer event's recipient with payTo literally, and a muxed M… address never matches the CAP-67 event, so such payments failed after submission. Exact requirements with a muxed payTo are now refused up front with a reason that names it.

**Fixed in** [`3a2ee7c`](https://github.com/tolgayayci/rail402/commit/3a2ee7c11ed79f2515d6b47e3ff238e2e27b8a54). **Tests**

* [`packages/facilitator/test/preflight.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/preflight.test.ts): refuses a muxed payTo, which the exact verifier cannot match to the transfer event

### F-2026-037

**A bad upto signature from a G account was reported as expired terms**

Medium · facilitator · Fixed · found by `REV-2026-10-upto` on 2026-10-21, resolved 2026-10-21 · threats A1

When a G payer's upto authorization was signed by a key that is not its signer, or by too few multisig keys, the simulation classifier mapped the contract's authentication error to signature\_expired, telling the buyer to sign again rather than that the signature was wrong. Authentication failures now answer invalid\_signature for a G payer and smart\_account\_rejected for a contract account; a real expiry stays signature\_expired.

**Fixed in** [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5). **Tests**

* [`apps/rail402/test/upto-tamper.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/upto-tamper.integration.test.ts): refuses a G… payer signed with a key that is not its signer as a bad signature
* [`apps/rail402/test/upto-tamper.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/upto-tamper.integration.test.ts): refuses a 2-of-2 multisig G… payer signed with one key as a bad signature
* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): names a contract account's failed authentication as its smart account's refusal
* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): keeps the settlement contract's #5 without an authentication failure as expired terms

### F-2026-038

**A conflict was reported as final when the original settlement never reached the network**

Low · facilitator · Fixed · found by `REV-2026-10-upto` on 2026-10-21, resolved 2026-10-21 · threats R1

When a request with the same authorization and another amount failed before broadcast, a concurrent request for the right amount was told the authorization was already settled, with no transaction and no retry. A request whose conflicting predecessor failed before anything reached the network is now settled.

**Fixed in** [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5). **Tests**

* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): settles a request whose conflicting predecessor failed before anything reached the network

### F-2026-039

**The upto fee ceiling counted the fee bump's inclusion fee once**

Low · facilitator · Fixed · found by `REV-2026-10-upto` on 2026-10-21, resolved 2026-10-21 · threats S3

With a fee bump, the envelope pays the inclusion fee of the inner transaction and of the fee bump, but the ceiling check counted it once, so the sponsor could pay up to one inclusion fee above its ceiling. Both are now counted.

**Fixed in** [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5). **Tests**

* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): enforces the fee ceiling, counting the inclusion fee of the transaction and of its fee bump

### F-2026-040

**The upto reference facilitator could miss a settlement re-wrapped by a third party**

Low · facilitator · Fixed · found by `REV-2026-10-upto` on 2026-10-21, resolved 2026-10-21 · threats F2

Confirmation polled only the outer fee-bump hash, so if a third party wrapped the public inner transaction in its own fee bump and it landed, the reference mechanism could report it as expired. It now also looks the settlement up by its inner hash.

**Fixed in** [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5). **Tests**

* [`packages/upto/test/facilitator.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/upto/test/facilitator.test.ts): finds a settlement that landed inside another party's fee bump by its inner hash

### F-2026-041

**Two different maxTimeoutSeconds ranges in refusal messages**

Info · facilitator · Fixed · found by `REV-2026-10-upto` on 2026-10-21, resolved 2026-10-21

The service named the allowed maxTimeoutSeconds range differently in two refusals. Both now name one range, raised to one ledger more than the expiration margin.

**Fixed in** [`10abb21`](https://github.com/tolgayayci/rail402/commit/10abb2111422fc7d47b9404fb3ff9be8d3a2f3d5). **Tests**

* [`packages/facilitator/test/upto-preflight.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-preflight.test.ts): names one maxTimeoutSeconds range, raised to a ledger more than the expiration margin

### F-2026-042

**upto events are only as trustworthy as the token contract**

Info · contract · Accepted risk · found by `REV-2026-10-upto` on 2026-10-21, resolved 2026-10-21 · threats U9

A malicious token can make transfer\_from do nothing and emit false events while the settlement contract still emits upto\_settled, so an indexer that trusts the event alone could count a payment that never happened. The payer is not at risk.

**Tests**

* [`apps/rail402/test/asset-check.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/asset-check.test.ts): fails for good, with a coded ConfigError naming the variable, when the decimals differ

**Accepted** by the release approver on 2026-10-21. Rail402 settles only assets the operator configures per network, verifies their decimals on chain at startup, and proves transfers from the token's own events.

* Each network accepts only the assets listed in its configuration.
* Asset decimals are read from the token at startup and a mismatch stops the service.

### F-2026-043

**Locked dependencies have published advisories**

High · operations · Fixed · found by `REV-2026-10-dependency-scan` on 2026-10-22, resolved 2026-10-23 · threats C5

The first osv-scanner and pnpm audit runs over pnpm-lock.yaml report advisories against locked versions, several rated high. Loaded at runtime by the service or the published packages: axios 1.18.0 and follow-redirects 1.16.0 (through @stellar/stellar-sdk, whose 16.x releases pin axios exactly), @modelcontextprotocol/sdk 1.31.0, @modelcontextprotocol/server and client 2.2.0, hono 4.13.8 and ip-address 10.7.2 (through the MCP SDK's express-rate-limit). Build and test tools only: vite 8.3.0, @vitest/mocker 5.0.1 and source-map-js 1.2.1. Each has a fixed release. Reachability: axios and follow-redirects run in the facilitator process that holds the sponsor seeds, for every RPC and Horizon call; most of the axios advisories need an attacker-chosen URL or configuration, HTTP/2 or a separate prototype-pollution primitive, and the follow-redirects ones leak credential headers when a provider redirects, which matters when an RPC URL or its headers carry a provider key; the RPC URLs are operator configuration, never request input, so exploitation needs a hostile or compromised provider, but the code is reachable and must be fixed before serving pubnet. The MCP SDK's memory-exhaustion advisory (GHSA-46jr-4wx8-v453) is reachable through the public MCP endpoint of the demo seller and the MCP server's HTTP transport, which accept unauthenticated requests up to their body limit. The shared-transport advisory (GHSA-jm63-cq47-j5jp) is not reachable: both create a new transport per request. The MCP client's redirect advisory applies when the Bazaar confirms a seller's MCP endpoint, which runs through safe-fetch with redirects refused. hono's serveStatic advisory and ip-address (express-rate-limit is not used by Rail402's code) are not reachable. The development tools never run in a deployed service. The fix moves axios to 1.20.0, follow-redirects to 1.16.1, source-map-js to 1.2.2, ip-address to 10.7.4 and hono to 4.13.13 with pnpm overrides (the MCP SDK keeps its own hono otherwise), and the catalog's MCP SDK to 1.32.1, its server and client to 2.3.1, hono to 4.13.13, vitest and @vitest/coverage-v8 to 5.0.5 and vite to 8.3.5 (the catalog pins vite for the browser builds, so vitest alone does not move it). With all of these, osv-scanner over every lockfile reports no advisory. The dependency scan job in CI fails while any of them is locked.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`tools/conformance/test/dependencies.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/dependencies.test.ts): lock no version with a published advisory fixed in a later release
* [`tools/conformance/test/workflows.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/workflows.test.ts): runs dependency scanners and pins base images by digest

### F-2026-044

**Pubnet sponsor key files lacked the network tag the service requires**

High · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats K3

The key ceremony's tools wrote the pubnet sponsor seed without the \`# rail402-key network=stellar:pubnet\` tag the service requires on pubnet, so the first pubnet start would fail and invite a hand edit of the key file. The key generator now writes network-tagged key files in the service's format, with the public key printed apart.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/deploy-keys.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/deploy-keys.test.ts): writes network-tagged key files in the format the service reads, with the public key apart

### F-2026-045

**A dump restore left the split database roles broken**

High · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats N8, K5

Restoring a logical dump as the superuser left every table owned by it and took the runtime role's privileges away, so both replicas would fail their role check after an incident restore. restore-dump.sh now restores as the owner role and re-applies the runtime grants.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/roles.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/roles.integration.test.ts): restores a dump with the owner keeping every table and the runtime role its privileges

### F-2026-046

**No alert on WAL archiving, backup age or host disk space**

High · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats K5, O11

Nothing alerted when WAL archiving failed or the newest backup aged, although a failing archive fills the database's disk. Archiving, backup age and disk space are now exported for node\_exporter and page the on-call.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): watches WAL archiving, backups and host disk space, and pages on each

### F-2026-047

**A compromised facilitator could silence alerts**

High · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats O12

The facilitator replicas shared a network with Alertmanager, whose API can create silences without authentication, so code execution in the process that holds the sponsor seed could mute every alert. The monitoring networks are now split: only Prometheus reaches Alertmanager.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): keeps the service off the alerting network: only Prometheus reaches Alertmanager

### F-2026-048

**Recovery drills passed without checking that recovery worked**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats O11

A drill passed as soon as its alert fired; its recovery checks were recorded as text and never asserted. A drill now fails when any structured recovery check does not hold.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/drills.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/drills.test.ts): fail a drill whose recovery check did not hold, and keep the verdict consistent with its checks

### F-2026-049

**Alerts did not follow each network in the two-network setup**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats N4, O11

With neither network gating /ready, an RPC outage on one network no longer raised a readiness alert, and the sponsor thresholds were not per network. Readiness is now probed per network and the thresholds are per network.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): follows each network on its own: per-network readiness, per-network sponsor thresholds

### F-2026-050

**The deploy gate did not pin the release signer or bind the tag to origin**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats C2, C3

The gate accepted any signature the operator's own git trust settings knew, resolved the tag locally, and accepted an attestation from any workflow or ref. Release signers are now pinned in deploy/release-signers, origin must have the same tag object, and the attestation must come from the release workflow run for that tag.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`tools/conformance/test/check-deployable.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/check-deployable.test.ts): refuses a tag that is not on origin, or that origin has as another object

### F-2026-051

**Alert inhibition and thresholds had the wrong semantics**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats O11

An inhibition rule treated a missing network label as equal, so any page suppressed every unrelated ticket; the budget alert tested a fixed amount instead of the share of the budget; and a replica that disappeared never alerted. Inhibition is limited to alerts with a network label, the budget ratio is used and a missing replica raises a ticket.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): follows each network on its own: per-network readiness, per-network sponsor thresholds
* [`tools/conformance/test/alertmanager.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/alertmanager.integration.test.ts): lets a page silence only the tickets of its own cause on its own network

### F-2026-052

**The backup documentation claimed more than the restore drill proved**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats K5

The backup section implied encryption and off-host immutability that the drill did not exercise, and pgBackRest's repository key could reach Postgres through its environment. The drill now proves the backup is encrypted, pgBackRest reads its settings from a file secret, and the documentation states what the drill does not prove.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/restore-drill.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/restore-drill.integration.test.ts): records a restore drill with row counts and checksums

### F-2026-053

**Forged payloads used up the per-payTo and per-payer limits**

High · facilitator · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats S4, Q1

Payer and payTo quotas were taken before the payment proved its signatures, so anyone could exhaust a pubnet seller's or payer's quota with forged payloads at no cost. Quotas are now taken only after upstream verification and simulation succeeded.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/party-limits.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/party-limits.integration.test.ts): forged payloads naming a payTo or a payer use none of their quota

### F-2026-054

**TTL keeper spend that failed on chain bypassed the fee budgets**

Medium · facilitator · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats S13

A contract TTL extension that failed on chain was never recorded, so the keeper's daily cap and the network's hourly fee budget did not see its fee. The keeper now reserves its fee ceiling before signing and corrects it to the fee charged, keeping the ceiling when the outcome is unknown.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/upto-keeper-spend.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/upto-keeper-spend.integration.test.ts): keeps the ceiling counted when an extension's outcome is unknown, so the daily cap holds

### F-2026-055

**Public metrics and health revealed pubnet budget state**

Medium · facilitator · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats N6

The public /metrics and /health still showed when pubnet's fee budget ran out, which tells an attacker when its spend has taken effect. Pubnet budget warnings and refusal reasons are now kept off the public views.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/metrics-exposure.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/metrics-exposure.test.ts): cannot tell pubnet budget exhaustion from any other failure on the public metrics endpoint

### F-2026-056

**Pubnet settlements still waited on the shared database pool**

Medium · facilitator · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats N3

Pubnet's own pool covered only the ledger and channel leases; its limiters and metering used the shared pool, so a testnet flood could still stall pubnet settlements, and pool waits had no bound. Pubnet limits and metering now use its pool, and every pool wait is bounded.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/pubnet-pool.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/pubnet-pool.integration.test.ts): settles pubnet through its own pool while testnet holds every shared connection

### F-2026-057

**JSON-RPC batches bypassed the hosted MCP endpoint's limits**

High · mcp · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats H3

One HTTP request carrying a JSON-RPC batch ran many tool calls past the per-client rate limit and the in-flight cap. The hosted endpoint now refuses batches (\`rpc\_batch\_unsupported\`) and caps tool calls across all clients with an upstream budget.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/mcp/test/hosted.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/hosted.test.ts): refuses a JSON-RPC batch, so one request runs at most one search

### F-2026-058

**All hosted MCP users shared one discovery rate-limit bucket**

Medium · mcp · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats H3

Every hosted MCP user reached the Bazaar from the endpoint's one address, so one heavy user could use up the discovery limit for all of them. The hosted endpoint now reads a discovery-internal access key from a file and sends it with a per-client identifier, so the Bazaar budgets each remote client apart (the Bazaar side is F-2026-097).

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/mcp/test/hosted.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/hosted.test.ts): sends the key and a per-client identifier upstream, so each client has its own discovery budget
* [`packages/mcp/test/hosted.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/hosted.test.ts): reads the key from a file, refuses it inline, and never echoes a malformed one

### F-2026-059

**Hosted MCP mode loaded the key, signer and payment modules**

Medium · mcp · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats H2

Hosted, discovery-only mode imported the modules that hold keys and sign payments, contrary to its documentation and threat entry. It now imports only the discovery code, and a test checks the module graph.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/mcp/test/hosted-graph.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/hosted-graph.test.ts): loads no key, signer, ledger or payment module in hosted mode

### F-2026-060

**An approved MCP confirmation could be replayed for ten minutes**

Medium · mcp · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats M9

An approved payment confirmation stayed valid for ten minutes, so it could approve further payments even with \`RAIL402\_MCP\_CONFIRM=every\`. Confirmations are now single-use and bound to the tool call.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/mcp/test/pubnet.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/pubnet.test.ts): accepts a confirmation once: replaying the approved retry signs nothing more

### F-2026-061

**Origin checks of all networks shared one queue**

High · bazaar · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats N5, P14

The origin-check queue took the oldest checks of any network and ran them one after another, so a testnet backlog of hosts that never answer delayed pubnet listings without bound. Origin checks now run in one bounded lane per network.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): checks each network's origins in its own lane, so a testnet backlog of unanswering hosts never delays pubnet

### F-2026-062

**The load test could not measure settlement latency under discovery load**

Medium · bazaar · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats N1, N2

The load package served no payments, so the isolation claims (pubnet settlement latency under a testnet or search flood) were not measured. A settle probe now settles real payments during a search flood and reports their latency.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/loadtest/test/settle.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/loadtest/test/settle.integration.test.ts): settles real payments during a search flood and reports their latency percentiles

### F-2026-063

**Indexed events were not checked against the transaction's result**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats I1, I4

The indexer trusted Soroban events without binding them to the transaction's result, and did not cross-check an upto settlement's amount against its signed arguments. Events are now bound to the result and upto settle arguments are checked.

**Fixed in** [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea). **Tests**

* [`packages/x402-index/test/integrity.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/x402-index/test/integrity.test.ts): stops on an upto settlement whose event and transfer were rewritten together

### F-2026-064

**Explorer partitions were not anchored to an independent source**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats I4, I6

Partitions started unanchored, links between partitions were never checked and rebuild anchors outside the range were ignored. Each partition's start is now anchored to an independent ledger hash and stored links are checked.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): commits a ledger only after its hash matched the anchor, stops on another hash and waits while it is unavailable

### F-2026-065

**A bounded indexer run skipped ledgers the source did not hold yet**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats I6

When the source answered that a ledger was not available yet, a bounded run skipped the rest of the partition and exited successfully. It now fails at that ledger instead of moving on.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): keeps a route's checkpoint when discovery fails for now, and fails a bounded run that cannot finish

### F-2026-066

**The testnet beta was missing from the attribution registry**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats I3

Settlements through the earlier testnet beta's contracts appeared as unattributed traffic. The beta's contracts are now registered and their settlements labelled as beta history.

**Fixed in** [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea). **Tests**

* [`packages/x402-index/test/registry.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/x402-index/test/registry.test.ts): labels settlements through the beta's testnet contracts as beta history, whoever submitted them

### F-2026-067

**Stored history was not re-attributed after a registry update**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats I3, I8

After a registry version bump the live database kept the old attribution. Stored rows are now re-attributed, or partitions re-indexed when the upto contracts change.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): follows registry changes: routes come and go with their ledgers, and re-attribution equals a fresh index

### F-2026-068

**Shared-relayer rows were counted in the headline without a flag**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats I3

Rows from a facilitator marked as a shared relayer were folded into the headline totals with nothing on the row saying so. They are now flagged and totalled on their own line.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/rebuild.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/rebuild.test.ts): flags shared-relayer rows on the row and totals them on their own line

### F-2026-069

**Open redirect through the Explorer's network aliases**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats W6

The \`/public\`, \`/pubnet\` and \`/mainnet\` aliases redirected to a path taken from the request, which could send a visitor to another site. The aliases now redirect only within the Explorer.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/pages.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/pages.test.ts): redirects aliases and upper-case hashes, and answers unknown paths with explorer\_not\_found

### F-2026-070

**Indexed transaction pages were cached as immutable**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats W5

Transaction pages were marked immutable for a year although their attribution can change, and the ETag named the served registry rather than the row's. Rows under older versions are now cached briefly with an ETag of their own versions.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/pages.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/pages.test.ts): caches a row under an older registry version only briefly, with an ETag of its own versions

### F-2026-071

**Explorer pool acquisition never timed out and pages were unbounded**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats W3

Waiting for a database connection had no timeout, one account page held several connections and ran uncapped aggregates. Requests beyond the in-flight limit are now answered 503 with Retry-After, and database use is bounded.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/api.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/api.test.ts): answers 503 with Retry-After instead of queueing beyond the in-flight limit

### F-2026-072

**Explorer per-client limits were per exact IP and the lookup budget was cheap to exhaust**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats W4

Limits keyed by exact IPv6 address and per process let one client rotate addresses, and the global live-lookup budget could be used up cheaply. IPv6 clients are now grouped by /64 and the lookup budget keeps a reserve and refunds refused lookups.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/live-lookup.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/live-lookup.test.ts): keeps a reserve for first lookups, refunds refused ones and groups IPv6 clients by /64

### F-2026-073

**The Explorer report rescanned everything every few minutes per replica**

Medium · explorer · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-22, resolved 2026-10-23 · threats I9, W3

Each web replica recomputed the full report every five minutes in one long transaction. The indexer now publishes the report and the web role serves it.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/web.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/web.integration.test.ts): serves the report the indexer published, equal to computing it again

### F-2026-074

**Throwaway smart accounts strand funds on pubnet conformance runs**

High · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats M1

Contract-account payer cases funded an account whose owner key was discarded at the end of the run, so its remaining balance could not be swept back to the treasury. Smart accounts are now deployed only through the run's funder, which derives and keeps their owner keys and returns their USDC before merging the run's accounts.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/network.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.integration.test.ts): returns a smart account's USDC with its derived owner key and merges the run's accounts
* [`tools/conformance/test/network.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.test.ts): deploy smart accounts only through the funder, which keeps their owner keys

### F-2026-075

**Third-party suite code runs with the treasury secret path in its environment**

High · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats C5

The upstream end-to-end suite inherited the treasury and sponsor secret file paths. It now gets only an allow-listed toolchain environment and its own reviewed variables, and no secret-like variable reaches it whatever its prefix.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/upstream-e2e-env.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/upstream-e2e-env.test.ts): never passes a secret-ish variable of this process, whatever prefix it has

### F-2026-076

**Spend caps do not see real Soroban fees and miss some deployments**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats M1

The conformance spend caps were debited with estimates and some contract deployments were not debited at all. Every fee the treasury pays is now debited before it is sent and counted as charged, and a fee source outside the run is refused.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/network.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.integration.test.ts): counts every fee the treasury pays, as charged, and refuses a step past the cap
* [`tools/conformance/test/network.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.test.ts): debits every fee the treasury pays before sending, and refuses a fee source outside the run

### F-2026-077

**Sweep-back is skipped when a harness throws or is interrupted**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats M1

An exception between funding and sweep-back left funds in derived accounts. A run now sweeps once after its body, also when the body throws, before the error is rethrown.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/network.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.test.ts): sweeps once after the body, and after a body that throws before rethrowing

### F-2026-078

**The settlement cap is a static estimate, not checked per settlement**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats M1

The settlement cap was debited once with a hand-counted bound. Every /settle a harness sends is now debited at its scheme's fee ceiling before it is sent, and none is sent past the cap.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/facilitator-guard.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/facilitator-guard.test.ts): debits every /settle at its scheme's fee ceiling before sending it, and sends none past a cap

### F-2026-079

**Spend caps apply per harness process and one confirmation is reusable**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats M1

Spend caps applied per harness process and the real-funds confirmation was reusable. One run ledger now holds the caps for every harness process of a run, and a confirmed run is claimed once.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/run-ledger.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/run-ledger.test.ts): never lets concurrent harness processes pass the run's cap together
* [`tools/conformance/test/network.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.test.ts): plans a run on the first attempt and spends nothing; a confirmed run claims it once

### F-2026-080

**Pubnet upto runs ignore the configured contract in several harnesses**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats U8

Several harnesses did not pass the pubnet upto contract on, so pubnet upto cases failed after funding accounts. Every upto client now gets the configured contract on pubnet.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/network.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.test.ts): pass the configured upto contract to every upto client on pubnet

### F-2026-081

**Harness fee ceilings assume defaults the facilitator does not have**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats S3

The harnesses assumed pubnet fee-ceiling defaults that the facilitator requires explicitly, and ignored malformed overrides. Pubnet runs now read the fee ceilings explicitly and refuse a malformed or zero value.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/network.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.test.ts): refuses a malformed or zero fee ceiling instead of ignoring it

### F-2026-082

**Skip categories are never emitted, so the pending-dependency gate cannot fire**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats O11

Skipped cases rendered as not categorised, so a required check skipped for a missing dependency could still be published. Evidence recorded from the requirement's date must give every skipped case a category.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/evidence.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/evidence.test.ts): requires a skip category on the skipped cases of a run recorded from the requirement's date

### F-2026-083

**A keyed facilitator is not detected before a pubnet run spends**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats Q4

A pubnet run against a facilitator that requires an access key funded accounts before every request was refused. The run now checks the facilitator first and refuses before spending when it wants a key the run does not have.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/facilitator-guard.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/facilitator-guard.test.ts): refuses a run before spending when the facilitator wants a key it does not have or refuses

### F-2026-084

**A closed upstream tracking issue is recreated every week**

Medium · operations · Fixed · found by `REV-2026-10-internal-tracks` on 2026-10-23, resolved 2026-10-23 · threats SW3

The upstream watch recreated a tracking issue a maintainer had closed while the drift was unchanged. A hand-closed issue now stays closed until the drift changes.

**Fixed in** [`f8c4e1c`](https://github.com/tolgayayci/rail402/commit/f8c4e1c3cdfff03afa06beb7a27859ae470aa820). **Tests**

* [`tools/upstream-watch/test/lifecycle.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/upstream-watch/test/lifecycle.test.ts): respects an issue a person closed while its drift persists, until the drift changes

### F-2026-085

**An exact payment with its own operation source held a channel and fee budget without landing**

High · facilitator · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats O5, S8, S4

The payer's authorization does not cover the operation source, and upstream settlement keeps it when it rebuilds the transaction from a channel. A payload with any non-facilitator operation source therefore verified, was recorded, and was then refused by the network for the missing signature. Its record stayed open until the authorization expired, holding its channel and fee reservation; with a handful of such payloads every channel could be kept busy at no cost. Such operation sources are now refused before anything is claimed (invalid\_exact\_stellar\_payload\_operation\_source\_set), a first submission the network refuses for good finishes at once as a submission failure with nothing charged and frees its channel, and finished settlements count the fee actually charged against the budgets. Because a copy of an exact authorization can still land while it is valid, a refused first submission now gives its channel back at once but stays open until the authorization expired unused (then a submission failure with nothing charged) or a copy landed (then that payment is answered); an upto settlement, which only the authority can settle, still finishes at once.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/preflight.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/preflight.test.ts): refuses any operation source, which settlement keeps but cannot sign for
* [`packages/facilitator/test/operation-source.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/operation-source.integration.test.ts): is refused at verify and settle, before any claim, channel or fee
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): finishes as a submission failure once the authorization expired unused, freeing the channel and fee budget
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): answers a copy of the authorization that landed before its first submission was refused
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps a refused first submission open while its authorization is valid, and answers a later copy
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): stays open when the resend is refused for a reason that can pass
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): finish an upto settlement refused at its first broadcast without waiting for confirmation
* [`packages/facilitator/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/src/testing.ts): counts a finished settlement's charged fee, nothing when it expired or was refused unsent

### F-2026-086

**One network's reconciliation failure or hang held up the others**

Medium · facilitator · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats N3, O3

All networks were reconciled in one sequential round without a per-network catch, so a testnet ledger error or a hung testnet RPC call stopped pubnet's recorded settlements from being finished and their channels from being freed. Each network now reconciles on its own, one round at a time per network, and RPC calls are bounded.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/reconcile-isolation.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/reconcile-isolation.test.ts): still reconciles pubnet when testnet's ledger fails
* [`packages/facilitator/test/reconcile-isolation.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/reconcile-isolation.test.ts): reconciles pubnet on its own while testnet's round hangs

### F-2026-087

**The per-payTo limit was shared by every caller**

Low · facilitator · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats S8

MAX\_SETTLEMENTS\_PER\_PAYTO\_PER\_MINUTE counted every caller in one bucket per payTo, so genuine minimum-amount payments sent without a key could use up the quota of a seller settling under its own key. The count is now kept per access key, with one shared count for keyless callers.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/limits.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/limits.test.ts): keeps a keyed seller's payTo quota apart from keyless traffic to the same payTo

### F-2026-088

**RPC calls had no timeout, and a slow settlement could outlive its channel lease**

Low · facilitator · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats O2

The Stellar SDK sets no timeout on RPC calls, so a hung provider could keep a settlement before signing past its channel lease, after which a second settlement could build on the same channel sequence. Every RPC call of the service's clients now fails after 10 seconds, a settlement that has signed nothing by the end of its 30-second claim window signs nothing afterwards, and a channel referenced by an open settlement is never leased again.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/rpc-pool.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/rpc-pool.test.ts): fails a call to a provider that never answers
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): stops there, signs nothing later and frees its claim and channel
* [`packages/store-postgres/test/stores.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/store-postgres/test/stores.integration.test.ts): never lease a channel whose recorded settlement is still open, released or expired

### F-2026-089

**A pubnet upto settlement could settle less than the asset's minimum**

Low · facilitator · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats S8

The asset minimum applied to an upto cap but not to the amount settled, so a non-zero settlement of one base unit cost the sponsor a full fee. On pubnet a non-zero upto settlement below the minimum is now refused with invalid\_upto\_stellar\_requirements\_amount\_out\_of\_range; a zero still settles off-chain.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/upto-preflight.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-preflight.test.ts): holds a non-zero pubnet settlement to the asset's minimum; zero and testnet amounts pass

### F-2026-090

**A revoked access key stayed valid behind a single-file mount**

Medium · operations · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats Q2

A container mount of the single key file keeps showing the file it was created with, so a key file replaced by rename on the host was never seen and a revoked key kept working until a restart, with no reload error. API\_KEYS\_DIR now reads keys.json from a mounted directory, reloads compare the inode too, and /health/private shows the digest of the file in force.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/access.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/access.test.ts): sees a file replaced by rename inside its directory, even with the same size and time
* [`apps/rail402/test/access.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/access.test.ts): reads keys.json from API\_KEYS\_DIR, and refuses it together with API\_KEYS\_FILE

### F-2026-091

**Every /ready request took a database connection**

Low · operations · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats N3

The public, unlimited /ready ran a database query per request on the pool that also serves settlements, so a flood of readiness requests could make settlements wait for connections. The database check now runs at most once every 2 seconds, shared by concurrent requests.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/cached-probe.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/cached-probe.test.ts): runs once for any number of concurrent requests and answers from its result for the TTL

### F-2026-092

**Public /health showed access-key counts**

Info · operations · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats N6

The public /health listed active, expired and revoked key counts and the code of a failed key reload. They are now shown in /health/private only.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/health.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/health.test.ts): keeps access keys out of the public view: no counts, no reload state, no error code

### F-2026-093

**Pubnet secret settings were looser than intended**

Info · operations · Fixed · found by `REV-2026-10-internal` on 2026-10-23, resolved 2026-10-23 · threats K1, N8

With pubnet, the service accepted the schema owner's connection (DATABASE\_MIGRATION\_URL), database URL and cursor secret files readable by others, and RPC URLs with credentials in the environment. Each is now refused at startup with a coded error.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/secrets.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/secrets.test.ts): refuses the schema owner's connection in the service process when pubnet is enabled
* [`apps/rail402/test/secrets.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/secrets.test.ts): refuses database and cursor secret files readable by others when pubnet is enabled
* [`apps/rail402/test/secrets.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/secrets.test.ts): refuses pubnet RPC URLs with credentials in the environment, and takes them from the file

### F-2026-094

**Search snapshots could outgrow the process's memory**

High · search · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats D2, N2

Each search index snapshot kept every published listing with its full content, the snapshot size estimate counted only the indexed text, nothing bounded the catalog's size, and up to eight superseded snapshots stayed alive while their cursors were valid. Free testnet mcp\:// listings of maximum size, plus a cursor after each catalog change, could grow the service's memory until it stopped, together with pubnet settlement in the same process. Snapshots now keep only what ranking and filters read and pages read listings from the catalog; the catalog is read in batches with a statement timeout; each field indexes a bounded number of terms; each network's listings take a bounded share of a snapshot (unverified listings at most half); the size estimates are calibrated against the measured heap of thousands of maximum-size listings; and each network holds a bounded number of published listings, with unverified and idle listings expiring.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/search/test/memory.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/search/test/memory.test.ts): estimates a snapshot at no less than the heap it holds, for thousands of maximum-size listings
* [`packages/search/test/memory.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/search/test/memory.test.ts): stays within its memory caps under a flood of free testnet listings, and keeps every pubnet listing searchable
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): refuses a new listing while its network holds its maximum of published listings, and publishes none past it
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): withdraws an unverified tool after its retention and lists it again on its owner's next settlement

### F-2026-095

**Unconfirmed listings counted toward the network-wide quota**

Medium · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats N5, P13, P5

The network-wide hourly quota of new listings counted pending and withdrawn listings, which any payer can create for any payTo. Dust payments to junk URLs could fill pubnet's quota and refuse every honest seller's first listing. The network-wide quota now counts only listings published now; unconfirmed listings count toward their payer's quota only.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets unconfirmed listings use up the network-wide quota, so dust payments cannot block a seller
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): bounds the new listings the whole catalog publishes per hour

### F-2026-096

**A shared schema validator let previews and testnet starve pubnet cataloging**

Medium · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats P3, N5

One validator worker with one queue served both networks and the /verify previews, and a settlement refused because the queue was full lost its cataloging for good. Repeated previews with slow schemas could keep pubnet settlements from being cataloged. Each network now validates settlements and previews in sandboxes of its own, a busy preview reports processing, and a busy settlement validation stays queued and is retried.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/bazaar/test/security.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/test/security.test.ts): validates each network's settlements and its previews in their own lanes, so a flood of slow schemas starves no one else
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): retries a settlement whose schema validation found the validator busy, instead of dropping it

### F-2026-097

**Hosted discovery users shared one cost budget**

Medium · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats Q6, H3

The discovery cost bucket was keyed by the caller's address only, even for an internal discovery identity, so every user of a hosted front end shared one budget and one user's searches could refuse everyone else's. Requests of a discovery-internal key are now charged to their forwarded client's bucket and to the identity's own, never to the address.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/access.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/access.test.ts): charges discovery costs to each forwarded client and to the identity, never to the identity's address

### F-2026-098

**Origin and stellar.toml fetches could be held open by a drip-fed body**

Medium · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats X4, X5

Origin checks stopped their timer once headers arrived and then drained the body without a deadline; a non-200 stellar.toml was drained the same way. A host dripping bytes could hold one socket per check open for days. Origin checks now close the connection as soon as the headers arrive, and stellar.toml reads keep one deadline through the whole body.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/bazaar/test/origin.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/test/origin.test.ts): closes the connection once the headers arrive, so a drip-fed body never holds the socket
* [`packages/bazaar/test/stellar-toml.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/test/stellar-toml.test.ts): keeps one deadline through the body, so a drip-fed file is cut off and its socket closed

### F-2026-099

**A pending ownership claim could be replaced by another payTo**

Low · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats P2, P13

A listing kept one claimant: a later settlement by another payTo replaced the claim and reset the check, and a check asked for by another payTo could quarantine a published listing on one passing error of its origin. Claims are now kept as a set, a claim never resets a check, and a check only claims asked for never withdraws a published listing.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): keeps every payTo's claim to a listing, so one claimant cannot replace another's
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never withdraws a published listing on a check only another payTo's claim asked for

### F-2026-100

**One facts queue served every network**

Low · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats N5

Stellar facts and domain trust were refreshed in one queue across networks, so testnet listings whose hosts stall could delay pubnet trust indefinitely. Each network now refreshes in its own lane, and a busy lane is skipped.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): refreshes each network's facts in its own lane, so slow testnet hosts never delay pubnet trust

### F-2026-101

**Route-template listings could change owner on every settlement**

Low · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats P2

A route-template listing was checked at the claimant's own path, so on a host serving one tenant per path each tenant could take the template over in turn. A template listing now moves only to a payTo that its example path names too.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): moves a route-template listing only to a payTo its example path names too

### F-2026-102

**Listing detail showed metadata no origin confirmed**

Info · bazaar · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23 · threats P13

The detail and history routes returned the content of pending and withdrawn listings, which can be metadata a payer echoed under the payTo it paid. Funds were never at risk (the SDK pays published listings only). Those routes now withhold that metadata and say so with contentWithheld.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/discovery.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery.test.ts): show their state and settled payment, never the metadata a payer echoed

### F-2026-103

**The SDK read only the first page of a listing's history**

Info · sdk · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-23

DiscoveryClient.versions() returned the first page of at most 100 versions; the Bazaar pages longer histories. It now follows pagination.next to the end, with a bound on the pages read.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/discovery.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/discovery.test.ts): reads every page of a long version history

### F-2026-104

**One pubnet approval covered later payments to any recipient in the window**

Medium · sdk · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats M9, M1

Under the first-per-session confirmation policy (the rail402-mcp pubnet default), approving the first payment of a budget window let every later payment of that window be signed without asking, to any recipient, resource or listing, up to the caps, and the prompt did not say so. An agent misled by seller or listing text could then pay other sellers unasked. An approval now covers only the window, recipient base account, listing (or resource without one), asset, scheme and settlement authority it was shown for; anything else is asked about again. Every prompt states what approving covers, with the per-call cap, the budget left and the window end, and SignIntent carries approvalScope and those session fields.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/pubnet-safety.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/pubnet-safety.test.ts): asks again for every new recipient or listing in a window, and says what an approval covers
* [`packages/mcp/test/pubnet.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/pubnet.test.ts): states the approval's scope: the recipient and listing it covers, the caps and the window end

### F-2026-105

**A pubnet buyer built in code accepted a plain-http or private Bazaar URL**

Low · sdk · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats M11

createBuyer and createBrowserBuyer checked the RPC and seller egress on pubnet but passed bazaarUrl and facilitatorUrl through, while the environment path refused non-https. An on-path attacker could then rewrite listings, which decide whom a pubnet buyer pays. On pubnet both URLs must now be https on a public host name: no credentials, IP literal, localhost, single label or private-network suffix (payment\_policy\_invalid).

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/pubnet-safety.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/pubnet-safety.test.ts): refuses a plain-http, IP-literal or private-network Bazaar or facilitator built in code

### F-2026-106

**An MCP resource reached confirmation prompts as raw text**

Low · mcp · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats M9

The MCP payment flow used the endpoint string as given, so line breaks or bidirectional overrides that the URL parser drops or encodes could add lines to, or reorder, the MCP and CLI confirmation prompts. Such an endpoint is now refused with egress\_url\_invalid, the normalized URL is what is confirmed and reserved, and both prompts show untrusted fields on one line without control, line-break or bidirectional characters.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): confirms and records the normalized endpoint URL, never the raw text
* [`packages/mcp/test/pubnet.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/pubnet.test.ts): shows untrusted fields on one line, without line breaks, controls or bidi overrides
* [`packages/cli/test/pay-confirm.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/pay-confirm.test.ts): shows the upto authority and keeps untrusted fields on one line

### F-2026-107

**A pubnet upto seller could name its own settlement authority**

Low · sdk · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats M13

With an upto contract pinned on pubnet, any settlement authority other than the payer and the recipient was accepted, so a seller could name a key it controls and settle the full cap; the prompts did not show the authority and the sealed MCP intent did not bind it. A signing pubnet upto buyer now pins its authorities from uptoFacilitators or from its facilitator's /supported, and refuses to open without either; the authority is shown in both prompts and bound in the sealed MCP intent.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/pubnet-safety.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/pubnet-safety.test.ts): pins the authorities the facilitator advertises for upto on pubnet
* [`packages/sdk/test/pubnet-safety.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/pubnet-safety.test.ts): refuses to open a signing pubnet upto buyer with no pinned authority
* [`packages/mcp/test/pubnet.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/pubnet.test.ts): shows the upto settlement authority and binds it, with the listing, to the approval

### F-2026-108

**The memory budget store gave pubnet a full budget on every start**

Info · sdk · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats M8

With RAIL402\_BUDGET\_STORE=memory, every rail402 run and every restart of rail402-mcp opened a new budget window with the full session total. A pubnet configuration with a key now refuses the memory store and keeps its budget in the SQLite ledger.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/pubnet-safety.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/pubnet-safety.test.ts): keeps a keyed pubnet budget in the persistent store

### F-2026-109

**The CLI confirmation was described as an agent control**

Low · docs · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats M9

The threat model listed the CLI's terminal confirmation beside MCP elicitation as a pubnet control, but an agent that runs the CLI can pass --yes or answer its own terminal. The CLI guide and the threat model now say the question protects a person at the terminal, point agents to rail402-mcp's elicitation, and name the controls that hold against an agent: caps, the tagged pubnet key file and the spending policy.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/cli/test/pay-confirm.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/pay-confirm.test.ts): asks for confirmation before the first pubnet payment unless --yes

### F-2026-110

**The Playground key download was untagged**

Info · cli · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats M10

The Playground downloaded its testnet key as a bare seed, which rail402 keys import could tag for pubnet without warning. The download is now a key file tagged for testnet, and keys import reads tagged files and refuses a tag for another network with key\_network\_mismatch.

**Fixed in** [`9bee50a`](https://github.com/tolgayayci/rail402/commit/9bee50ac9b764cefea337bb1edeab933059304ea). **Tests**

* [`apps/playground/web/test/wallet/keystore.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/playground/web/test/wallet/keystore.test.ts): writes a key file the CLI and SDK read, and never shows the key otherwise
* [`packages/cli/test/local.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/local.test.ts): takes a key file the Playground exported, after the documented step

### F-2026-111

**The Playground relay could reach the facilitator's other network hosts**

Low · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats PG1

The relay refused the facilitator and Bazaar host names it was configured with, but one facilitator process also answers for pubnet and the Explorer under sibling host names, so Playground users could drive those through the relay as one shared keyless client. When the facilitator or Bazaar host starts with testnet., mainnet. or pubnet., the relay now also denies the same domain under testnet., mainnet., pubnet. and explorer.; RELAY\_DENY\_HOSTS stays for any other name.

**Fixed in** [`9bee50a`](https://github.com/tolgayayci/rail402/commit/9bee50ac9b764cefea337bb1edeab933059304ea). **Tests**

* [`apps/playground/test/ssrf.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/playground/test/ssrf.test.ts): does not let the relay reach the other network and Explorer hosts of the same facilitator

### F-2026-112

**One hostile transaction could stop the Explorer indexer**

Medium · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats I5

A settlement through a concluded beta contract took the seller's muxed id from the transfer event without comparing it with anything, and a CAP-67 to\_muxed\_id in the text form was kept byte for byte. A token contract chosen by the caller could emit an id containing a NUL character, which Postgres refuses in text, so the commit failed, the partition was marked failed and every restart failed at the same ledger. A to\_muxed\_id is now taken only as a u64 in every rule (the text and hash memo forms never come from a contract call and count as transfer\_event\_mismatch), and every row field must have its expected shape and be storable text, or the transaction is counted as a malformed exclusion. Indexing continues past such a transaction.

**Fixed in** [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea). **Tests**

* [`packages/x402-index/test/hostile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/x402-index/test/hostile.test.ts): never takes a text or hash memo form of to\_muxed\_id into a row, in any rule
* [`packages/x402-index/test/hostile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/x402-index/test/hostile.test.ts): refuses any row field that could not be stored as it is
* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): counts a hostile token's NUL muxed id as an exclusion and keeps indexing

### F-2026-113

**Indexers sharing a database collided on the totals tables**

Medium · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats I6

Each commit refreshed the daily and per-account totals by deleting and re-inserting the rows it touched. Two workers committing partitions with a shared seller or day, or a reset racing a worker, both inserted the same key; one commit failed with a unique-key error and its partition was marked failed. The refresh now runs under a transaction-scoped advisory lock per network and epoch, so such transactions take turns and the second recomputes from every committed row, and a commit that still loses a race is retried instead of failing the partition.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): refresh shared sellers and days concurrently without colliding on the totals

### F-2026-114

**Rows between anchors were served before they were anchored, and failed partitions stayed in the report**

Medium · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats I4, I8

The indexer checked an independent Horizon anchor only at a partition's start and when a partition completed, about 3.7 days of ledgers apart. Rows of a self-consistent fork from the RPC fallback or a tampered data lake object were committed, served and reported in between, and the report kept a partition the indexer had marked failed, or one that did not link to its neighbour, in its coverage. A batch is now committed only after the hash of its last ledger matched the anchor (once per batch in the backfill, every 16 ledgers at the head), the anchors checked are stored and listed in the published manifest, and the report stops before a failed or unlinked partition and is not marked reproducible while one exists. The rebuild command takes a published manifest's anchors with --anchors-from.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): commits a ledger only after its hash matched the anchor, stops on another hash and waits while it is unavailable
* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): stops at a ledger whose bytes are not the ledger asked for, marks it failed and holds the report before it
* [`apps/explorer/test/indexer-units.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer-units.test.ts): stops before the first candidate that is pending or failed

### F-2026-115

**A live lookup did not check that the RPC answered for the requested hash**

Low · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats I4

A transaction page for a hash not indexed yet showed whatever transaction the lookup RPC returned, under the requested hash, and cached it for a minute. A faulty or hostile RPC could show one crafted settlement under any link. The answer is now used only when its envelope's network hash, or its inner hash for a fee bump, equals the requested hash; otherwise the lookup answers explorer\_lookup\_unavailable.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/live-lookup.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/live-lookup.test.ts): refuses an RPC answer for another transaction than the hash asked for

### F-2026-116

**Account totals were cut at 200 rows without notice, in an order anyone could game**

Low · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats W2

Account totals were ordered by side and contract id and cut at 200 rows, and received assets sorted before paid ones, so payments in many throwaway tokens could push an account's paid totals and verified assets off its page and its API answer without any sign. Each side now lists at most 100 assets, verified Stellar assets first and then by payments, and the page and the API say how many more each side has.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): list each side apart, verified assets first, and count what they leave out
* [`apps/explorer/test/pages.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/pages.test.ts): says how many assets the account totals leave out, on the page and in the API

### F-2026-117

**Every Explorer /health request took a database connection**

Low · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats W3

The Explorer's /health is exempt from rate limits and the in-flight cap and pinged the database on every request, so a flood of health checks queued pool acquisitions ahead of page queries. All health requests in any two seconds now share one database check.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/api.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/api.test.ts): asks the database at most once per interval for /health, however many clients ask

### F-2026-118

**The concluded testnet beta's contracts kept counting new calls as beta history**

Low · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats I3

The registry listed the testnet beta's settlement contracts with open ranges. The contracts cannot be changed and anyone can call them, so every later call was labelled concluded beta history and anyone could inflate it. Each beta contract's range now ends at testnet ledger 5,326,244, the last ledger closed before the beta's pubnet account was merged, with archived evidence of both; calls after it are not beta history.

**Fixed in** [`1530c9d`](https://github.com/tolgayayci/rail402/commit/1530c9d973c3a41a3fecbaa804e280b3d7c057ea). **Tests**

* [`packages/x402-index/test/registry.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/x402-index/test/registry.test.ts): closes every concluded beta contract's range, so later calls are not beta history

### F-2026-119

**No launch or rotation step registered the hosted service's accounts with the Explorer**

Low · explorer · Fixed · found by `REV-2026-10-explorer` on 2026-10-23, resolved 2026-10-23 · threats I3

Neither the mainnet key ceremony nor the sponsor rotation said to add the hosted service's new accounts to the Explorer's registry, so its own settlements would have shown as unattributed until someone noticed. Both procedures and the runbook now include the registry change, and registry-check --hosted-\<network> fails while any signer or settlement authority the service advertises in /supported lacks an open registry entry.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/registry-check.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/registry-check.test.ts): names an advertised account the registry does not list, or lists only in a closed range
* [`apps/explorer/test/registry-check.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/registry-check.test.ts): passes when every advertised signer and the upto authority have open rail402 entries

### F-2026-120

**Pre-flight configuration checks refused every release that adds a migration on pubnet**

High · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats N8

With pubnet, the configuration check a deploy runs before touching the database refused any pending migration, so a release with a migration could not be deployed in the documented order. \`CHECK\_CONFIG\_PENDING\_OK=true\` now skips only the pending-migration refusals while still checking the database role, and the migrations then run as the schema owner.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/rail402/test/database-roles.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/database-roles.integration.test.ts): lets the preflight expect pending migrations, but never skips the role check

### F-2026-121

**The Explorer and hosted MCP images were outside the supply-chain controls**

Medium · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats C1, C5

The Explorer and hosted MCP Dockerfiles used a base image by tag, were not built in CI and were not released or attested, so the deploy gate could not cover them. Both are now pinned by digest, built in CI and released as attested images, and the gate can require them.

**Fixed in** [`643f084`](https://github.com/tolgayayci/rail402/commit/643f0841596121df1318c91375416740b46acc0e). **Tests**

* [`tools/conformance/test/workflows.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/workflows.test.ts): builds every deployable image in CI and publishes each with its own attestation

### F-2026-122

**Monitoring never scraped the Explorer or the hosted MCP endpoint**

Medium · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats O11, I6

No scrape job covered the Explorer or the hosted MCP endpoint, so the Explorer lag alert could never fire and a stopped indexer went unnoticed. Scrape jobs and alerts for indexers, the web role and the endpoint were added.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): scrapes the Explorer and the hosted MCP endpoint, and alerts when they stop

### F-2026-123

**The leak response did not use the cold key to lock out the leaked key**

Medium · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats K2, O6

The rotation steps had the operator race an attacker for the hot balance and moved funds last, while the cold key can lock the hot key out in one transaction. A cold-key freeze (master weight 0) is now the first step of the leak response. Its source and fee payer is the cold account and only the operation names the sponsor, so the holder of the leaked seed cannot make it fail by using or bumping the sponsor's sequence (a first version took the sponsor as the source and could be blocked that way); the sweep is a separate cold-signed transaction after it. The testnet rehearsal and the recovery drill bump the sponsor's sequence with the leaked key before the freeze is submitted, and the freeze still applies.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`tools/conformance/test/ceremony.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/ceremony.test.ts): freezes from the cold account, so the leaked key cannot block it through the sponsor's sequence
* [`tools/conformance/test/ceremony.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/ceremony.test.ts): sweeps in a separate cold-sourced transaction after the freeze

### F-2026-124

**The deploy gate accepted an OpenPGP signature the operator's keyring trusted**

Low · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats C2

git picks the verifier from the signature block, so an OpenPGP-signed tag was verified against the operator's keyring, where a key of unknown trust passes. The gate now verifies only SSH signatures, with ssh-keygen and the committed signers file.

**Fixed in** [`643f084`](https://github.com/tolgayayci/rail402/commit/643f0841596121df1318c91375416740b46acc0e). **Tests**

* [`tools/conformance/test/check-deployable.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/check-deployable.test.ts): refuses an OpenPGP-signed tag even when the operator's keyring trusts its key

### F-2026-125

**No alert fired when no backup existed at all**

Medium · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats K5

The stale-backup alert compared the newest backup's age and returned nothing when no backup was listed, so a missing base backup or an unreadable repository never paged. It now also pages when archiving has been on for six hours and no completed backup is listed.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): watches WAL archiving, backups and host disk space, and pages on each

### F-2026-126

**The sponsor signer setup needed the hot seed on a networked machine**

Low · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats K7

The one-time signer setup was signed by a tool that needs the full dependency tree and network access, with the hot seed on that machine. The setup can now be built unsigned and signed offline by the zero-dependency key tool.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/ceremony.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/ceremony.test.ts): signs an unsigned envelope without the SDK exactly as the SDK does

### F-2026-127

**The release gate did not name the deploy files kept outside the release**

Low · operations · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats C3

Files a deployment keeps outside the release, including a pubnet network's fee and budget settings, were not identified by the gate. It now prints their digest for the host to check and record with the release; reviewing those files remains the operator's step.

**Fixed in** [`643f084`](https://github.com/tolgayayci/rail402/commit/643f0841596121df1318c91375416740b46acc0e). **Tests**

* [`tools/conformance/test/check-deployable.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/check-deployable.test.ts): digests a deploy kit the same way a shell on the host does

### F-2026-128

**A startup RPC failure that threw a non-error stopped the service**

Low · facilitator · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats N4

With one network, an RPC that answered a startup check with a JSON-RPC error made the process exit with an unreadable message instead of holding that network until the RPC recovers. Any failure that is not a contradiction with the configuration now holds only that network with a code, and every startup failure prints one coded line without URLs or secret seeds.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/main.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/main.integration.test.ts): starts with its network held, not exiting, when the RPC makes startup throw %s
* [`apps/rail402/test/startup-failures.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/startup-failures.integration.test.ts): holds pubnet only when an RPC call itself throws %s
* [`apps/rail402/test/failure.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/failure.test.ts): never carries a URL or a secret seed, and stays one bounded line

### F-2026-129

**An RPC's own simulation failure refused a valid payment as invalid**

Low · facilitator · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats O3

When the RPC failed a simulation without running the host, for example with an incomplete ledger snapshot under load, verify and settle refused the payment with a non-retryable simulation code although nothing about it had been checked, so buyers treated a valid signed payment as refused. Such failures now answer a retryable code saying the payment was not judged; nothing is signed or submitted, and the same payment settles once the RPC recovers.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/settlement.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/settlement.integration.test.ts): answers a retryable code when the RPC fails a simulation without running it, and settles on retry
* [`packages/facilitator/test/upto.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto.integration.test.ts): answers a retryable code when the RPC fails a simulation without running it, and settles on retry
* [`packages/facilitator/test/explain.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/explain.test.ts): tells a host error from an RPC's own failure

### F-2026-130

**Verification could wait minutes for an RPC that stopped answering**

Low · facilitator · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats O2

The upstream exact verifier builds its own RPC client from the URL alone, without the 10-second call timeout of the service's own clients, so /verify waited for the platform's fetch default (minutes) when a provider accepted the connection and never answered; settlement was already bounded by its claim window. Upstream verification is now cut off after the RPC timeout with a retryable unexpected\_verify\_error, the channel and authority commands use bounded clients, and the threat model no longer claims that every RPC call fails after 10 seconds.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/facilitator/test/verify-timeout.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/verify-timeout.integration.test.ts): answers unexpected\_verify\_error within the RPC timeout, and signs nothing

### F-2026-131

**The rebuild guide claimed an anchored hash no test reproduced**

Low · explorer · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats I4

The Explorer rebuild guide said that --anchors-from with a published manifest gives the published manifest hash, but no test rebuilt an anchored report. A test now indexes the committed slice with anchors, reads the anchors back from the report's manifest the way --anchors-from does, and rebuilds to the report's manifest hash; the parser is shared with the command.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): commits a ledger only after its hash matched the anchor, stops on another hash and waits while it is unavailable

### F-2026-132

**Startup failures that were not RPC failures were reported as an unreachable RPC**

Low · facilitator · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23 · threats N4

Every plain error from a network's startup checks became the hold reason rpc\_unreachable, so a sponsor that does not exist or another replica still provisioning accounts was refused as "its RPC cannot be reached". A missing sponsor now holds the network as sponsor\_missing, a refused channel creation as channels\_missing and provisioning contention as the new provisioning\_busy; only RPC failures stay rpc\_unreachable.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/startup-hold.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/startup-hold.test.ts): names another replica's provisioning as its own reason, not an unreachable RPC
* [`apps/rail402/test/startup-hold.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/startup-hold.test.ts): calls only an RPC failure unreachable: an RPC error, a hang, or something that is not an Error
* [`apps/rail402/test/startup-failures.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/startup-failures.integration.test.ts): holds a network whose sponsor does not exist as sponsor\_missing, not as an unreachable RPC

### F-2026-133

**The docs pinned a package release without the pubnet buyer guards they describe**

Info · sdk · Fixed · found by `REV-2026-10-internal-pubnet` on 2026-10-23, resolved 2026-10-23

Every package was still at 0.4.2 while the agent, seller and in-process pages, the Playground snippets and the buyer example installed @0.4.2 and described pubnet guards (tagged keys, per-recipient approvals, pinned upto authorities, the persistent budget store) that 0.4.2 does not have. Every package and the workspace are now 0.5.0 and those install pins name 0.5.0, and a test keeps every @rail402.dev install pin in the docs at the workspace version. The release is pending: until 0.5.0 is published to npm these pins do not install, so publish before the docs go live; the examples' package.json files keep the published 0.4.2 so their registry install check passes, and move to 0.5.0 after the publish.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`tools/conformance/test/release-pins.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/release-pins.test.ts): pins every @rail402.dev install in the docs, READMEs and Playground snippets to the workspace version

### F-2026-134

**Discovery beyond capacity made searches fail with 500 and stalled health checks in the shared process**

Medium · search · Fixed · found by `REV-2026-10-discovery` on 2026-10-23, resolved 2026-10-24 · threats D5

In the load test, uncached searches at 300 a second over a 5,000-listing catalog (above the 234 a second the service completes) piled up inside the process: 6,245 searches answered 500 after waiting more than DATABASE\_ACQUIRE\_TIMEOUT\_MS for a connection of the pool settlements also use (every result page read its listings from the database), 7,972 timed out at 30 s, and 150 of 600 /health probes timed out. Discovery now sheds load before any limit or database read (at most DISCOVERY\_MAX\_IN\_FLIGHT requests run and DISCOVERY\_QUEUE\_LENGTH wait, each at most DISCOVERY\_QUEUE\_TIMEOUT\_MS; the rest are refused with 503 discovery\_overloaded and Retry-After), reads the catalog and keeps its limits through a database pool of its own, answers a busy or unreachable catalog database with a coded 503 instead of a 500, and reuses a page's listings read within the one-second revision read at the same revision; /ready answers from its last database probe while a new one runs. Measured again on the release image at 300 a second (tools/loadtest/published/2026-10-26): 33,802 searches answered and 2,198 refused with 503 beside a /health probe (run B-uncached-open-300), 33,956 answered and 2,044 refused beside the settle probe (run B-settle-under-uncached-open-300), with no 500 or timeout; every /health probe and every settlement succeeded.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/discovery-overload.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.test.ts): refuses discovery beyond its slots and queue with 503 while health and settlement routes answer
* [`apps/rail402/test/discovery-overload.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.test.ts): refuse with a coded 503 and Retry-After, never a 500
* [`apps/rail402/test/discovery-overload.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.integration.test.ts): sheds a discovery flood with coded 503s while settlement, /health and /ready answer
* [`apps/rail402/test/discovery-overload.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.integration.test.ts): refuses discovery with a code while its pool is held, and settles through the other pools
* [`packages/search/test/search.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/search/test/search.test.ts): reads a page's listings once per revisionMaxAgeMs at one revision, never past a withdrawal

### F-2026-135

**Any page silenced every ticket of its network, hiding an independent failure**

Medium · operations · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-24, resolved 2026-10-24 · threats O11

The Alertmanager inhibition let any page that named a network suppress every ticket of that network. In the channel-exhaustion drill, Rail402SponsorGuardRefusing was paging for testnet, so the Rail402ChannelLeaseTimeouts ticket raised by the exhausted channels was never sent. A page now silences only the tickets of its own cause on its own network: alerts carry a cause label (deploy/monitoring/alerts.ts), the rule matches equal: \[network, cause], Rail402ChannelsExhausted covers Rail402ChannelLeaseTimeouts and Rail402RpcStale covers Rail402RpcFailover; every other alert is always sent.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/alertmanager.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/alertmanager.integration.test.ts): lets a page silence only the tickets of its own cause on its own network
* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): lets a page silence only the tickets of its own cause, on its own network
* [`deploy/monitoring/alerts.test.yml`](https://github.com/tolgayayci/rail402/blob/main/deploy/monitoring/alerts.test.yml): Rail402ChannelLeaseTimeouts

### F-2026-136

**A sequence conflict held its channel until the payment's authorization expired**

Medium · facilitator · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-24, resolved 2026-10-24 · threats O2

When another transaction took a channel account's sequence number, the settlement's txBadSeq refusal was treated as transient (the same bytes might have applied), so the settlement and its channel lease waited for the transaction's time bound and the payer's authorization expiry: about 4.5 minutes in the drill, with Rail402SequenceConflict firing correctly. The engine now proves the conflict final, from the channel's sequence number on the network (at least the transaction's) and the absence of the transaction's hash and inner hash at an RPC that has ingested that ledger and retains every ledger since the claim, and then finishes the settlement at once as a submission failure with nothing charged, unless the payer's nonce shows the authorization was carried elsewhere. The channel is quarantined (SETTLE\_CHANNEL\_QUARANTINE\_MS, doubled per conflict in a row, up to 16 times) instead of being reused at once. The settlement is never re-signed on another channel, so the recorded-bytes idempotency stays as it was. An exact settlement whose bytes the network had accepted before the conflict still waits for the authorization to expire, since another transaction may carry it. For an exact settlement the proven conflict now quarantines and gives back the channel at once, while the record stays open until the payer's authorization expired unused or a copy of it landed, so a payment carried by another transaction is still answered; upto settlements still finish at once.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/sequence-conflict.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/sequence-conflict.integration.test.ts): gives its channel back within seconds, moves nothing, never settles twice and fails once the authorization expired
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): quarantines the channel at once instead of holding it, and finishes with nothing charged once the authorization expired
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): gives its channel back at a proven sequence conflict, and answers a later copy
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): finishes a sequence conflict as failed once the authorization expired unused, releasing nothing twice
* [`packages/store-postgres/test/stores.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/store-postgres/test/stores.integration.test.ts): lease a channel again once its open settlement gave it back
* [`packages/facilitator/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/src/testing.ts): lets a submitted settlement give its channel back once, and keeps that when it finishes
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): stays open while the channel's sequence number has not reached the transaction's
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): never finishes as failed when the transaction landed inside another fee bump
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): finish an upto settlement at once and quarantine the channel, since only the authority could settle it
* [`packages/facilitator/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/src/testing.ts): doubles the cooldown of a channel quarantined again, until a plain release

### F-2026-137

**The sponsor balance alerts used fixed thresholds instead of each network's floor**

Low · operations · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-24, resolved 2026-10-24 · threats S5

Rail402SponsorBalanceLow watched only pubnet, and the testnet sponsor had a separate ticket, both against fixed thresholds in the alert file rather than the configured readiness floors, so a testnet sponsor drill could not fire the sponsor alert and a changed floor silently left the alerts behind. The service now exports each network's floor (rail402\_sponsor\_balance\_floor\_stroops) and one alert fires on every network below twice its own floor: a page for the key custodian, and on testnet, whose XLM has no value, a ticket for the on-call.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`tools/conformance/test/monitoring.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/monitoring.test.ts): follows each network on its own: per-network readiness, per-network sponsor thresholds
* [`deploy/monitoring/alerts.test.yml`](https://github.com/tolgayayci/rail402/blob/main/deploy/monitoring/alerts.test.yml): Rail402SponsorBalanceLow

### F-2026-138

**The Explorer lag alert could not fire without a lag series**

Low · operations · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-24, resolved 2026-10-24 · threats O11, I6

Rail402ExplorerLag compared rail402\_explorer\_lag\_ledgers with a threshold, so an indexer that exported no lag, or whose walked-through ledger stopped moving, never raised it. It now also fires when an indexer answers its scrape without rail402\_explorer\_last\_ledger or when that ledger has not moved for an hour; a stopped indexer stays reported by Rail402ExplorerIndexerDown alone, not twice.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`deploy/monitoring/alerts.test.yml`](https://github.com/tolgayayci/rail402/blob/main/deploy/monitoring/alerts.test.yml): Rail402ExplorerLag

### F-2026-139

**The stale-RPC alert stayed silent on a replica that never read a ledger, or after its polls failed**

Medium · operations · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-24, resolved 2026-10-24 · threats O11

rail402\_rpc\_ledger\_age\_seconds was set only by a successful poll: a replica that never read a ledger had no series, and one whose polls started failing kept its last, fresh value, so Rail402RpcStale could not fire for either. The age is now computed at each scrape from the last ledger read, and from when the replica started watching the network while none was read, so both cases are reported (a never-read replica 3 minutes after start).

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/metrics-signals.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/metrics-signals.test.ts): grows at every scrape, from the start of watching while no ledger was ever read
* [`deploy/monitoring/alerts.test.yml`](https://github.com/tolgayayci/rail402/blob/main/deploy/monitoring/alerts.test.yml): Rail402RpcStale

### F-2026-140

**No alert covered discovery load shedding**

Low · operations · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-24, resolved 2026-10-24 · threats D5

Discovery refuses requests beyond its capacity with 503 discovery\_overloaded and counts them in rail402\_discovery\_shed\_total, but no alert read the counter, so sustained refusals went unnoticed. Rail402DiscoveryShedding raises a ticket when one reason passes 20 refusals in 10 minutes, with a runbook section.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`deploy/monitoring/alerts.test.yml`](https://github.com/tolgayayci/rail402/blob/main/deploy/monitoring/alerts.test.yml): Rail402DiscoveryShedding

### F-2026-141

**The CLI reported retryable facilitator refusals as final, and a pending settlement as not settled**

Medium · cli · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-24, resolved 2026-10-24 · threats M4

A payment the facilitator refused was always printed as payment\_rejected with retryable false, even for settle\_exact\_stellar\_channel\_unavailable, rate limits or a spent budget, which a new payment can pass; and a refusal with settlement\_pending reported paid: not\_settled although that transaction may still land, inviting a second payment. The CLI now takes retryable from the facilitator's code table (exact and upto) for the code the seller passed on, and reports settlement\_pending as paid: unknown with exit 5; an unknown code changes nothing.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/cli/test/basics.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/basics.test.ts): says whether running the command again, with a new payment, can succeed

### F-2026-142

**A sequence conflict first answered TRY\_AGAIN\_LATER still held its channel until the authorization expired**

Medium · facilitator · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-26, resolved 2026-10-26 · threats O2

The re-run of the sequence-conflict drill on the hosted testnet showed the fix of F-2026-136 did not trigger in the realistic order: the other transaction held the channel's queue slot, so the scheme's send and the engine's immediate resend answered TRY\_AGAIN\_LATER rather than txBadSeq; the settlement then counted its bytes as possibly public, and the 23 later txBadSeq rebroadcasts all waited for the exact authorization's expiry (317 s, channel released at expiry, not quarantined; nothing charged). The answer to every send of the recorded bytes is now persisted on the settlement (broadcast\_accepted and sends\_in\_flight, migration 0024): a send is marked in flight before it is made, PENDING or DUPLICATE marks the bytes accepted, ERROR or TRY\_AGAIN\_LATER ends it as refused, and a lost answer (timeout, transport error, crash) stays in flight and counts as accepted. When the sequence conflict is proven and no send was ever accepted, the settlement finishes at once with nothing charged and its channel quarantined, on whichever replica or reconciler sees the txBadSeq; records written before the migration count as accepted. A declined early finish is logged with its reason. For exact settlements "finishes" now means the channel is quarantined and given back at once, with the record left open until the authorization expired unused or a copy landed; bytes whose channel was given back are never sent again.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/sequence-conflict.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/sequence-conflict.integration.test.ts): gives its channel back the same way when the channel's queue slot was taken first (TRY\_AGAIN\_LATER)
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): quarantines the channel at the first txBadSeq when no send was ever accepted
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): sends nothing more once it gave its channel back, while the same request still waits
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps waiting when a send's answer was lost: it may have been accepted
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps waiting when the scheme's own send was lost
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps waiting when a send was answered DUPLICATE: the network held the bytes
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): is finished by another replica's reconciler from the recorded answers
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): is left waiting by another replica's reconciler when a send's answer was lost
* [`packages/facilitator/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/src/testing.ts): tracks the sends of recorded bytes: public once one is accepted or its answer is lost

### F-2026-143

**A waiting sequence conflict logged its decline on every rebroadcast**

Low · facilitator · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-26, resolved 2026-10-26 · threats O2

While a settlement whose channel sequence number was taken kept waiting (its bytes may have been public), every rebroadcast logged the same decline, on each replica: about 60 lines per replica in a 5-minute wait, which buried other log lines and gave no count to alert or graph on. A decline is now logged once per settlement and replica, and again only when its reason changes, and each decision is counted in rail402\_sequence\_conflict\_outcomes\_total\{network, outcome, reason} with a fixed set of reasons (private on pubnet, like the neighbouring sequence-conflict counter).

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): reports a decline once per settlement, however many rebroadcasts meet it
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): reports a decline again when its reason changes
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): reports a settlement finished at the conflict, with its reason
* [`apps/rail402/test/metrics-signals.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/metrics-signals.test.ts): exports every outcome and reason at zero, and counts what the engine reports

### F-2026-144

**The CLI's retryable flag used the facilitator's meaning, not the command's**

Medium · cli · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-26, resolved 2026-10-26 · threats M4

The CLI copied the facilitator's retryable flag for a refused payment, but the facilitator's flag means that resending the same signed payment can succeed, while running a CLI command again signs a new payment. So settle\_\*\_settlement\_in\_progress was reported as retryable with paid: not\_settled although another request was still settling the same authorization, which a second run could have paid twice; and settle\_\*\_transaction\_submission\_failed was reported as not retryable although its reason says to sign a new payment and retry. The CLI now says whether running the command again can succeed: settlement\_pending, settlement\_in\_progress and idempotency\_conflict report paid: unknown (exit 5), submission failures and expiries with no funds moved are retryable, and the error-code reference and the CLI page define retryable for client codes this way.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/cli/test/basics.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/basics.test.ts): says whether running the command again, with a new payment, can succeed

### F-2026-145

**A quarantined channel returned to rotation while an outside sender still used it**

Low · facilitator · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-26, resolved 2026-10-26 · threats O2

In the hosted re-run the third sequence conflict on one channel came one second after its quarantine ended: the quarantine only waited, so a channel someone kept using was handed back on time and one more payment failed. The account's sequence number seen at the conflict is now kept with the quarantine (memory and Postgres pools, migration 0025), and the next lease compares it with the account's current one; when it moved, the channel is quarantined again for twice as long and another channel is leased instead. A failed read leases the channel, where another conflict would quarantine it again.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): is quarantined again, for twice as long, when its sequence number moved since
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): serves again once its sequence number stayed where the conflict left it
* [`packages/facilitator/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/src/testing.ts): hands a quarantined channel's sequence number to its next lease, once

### F-2026-146

**The SDK and the MCP server reported a refused payment's retryability apart from the CLI**

Medium · sdk · Fixed · found by `REV-2026-10-recovery-drills` on 2026-10-26, resolved 2026-10-26 · threats M4

Only the CLI read the facilitator's code a seller passed on with a refused payment. The SDK's PaymentError kept retryable false and paid: not\_settled for every payment\_rejected, so an SDK caller saw settlement\_pending as nothing paid, and the rail402-mcp tool error took retryable from the code set instead of the error, so an agent never saw a busy channel as retryable. The CLI also called a seller-relayed transaction\_submission\_failed retryable with nothing moved, although upstream facilitators send that code for a DUPLICATE submission whose bytes can still land. One table in the SDK (refusalOutcomes) now sets retryable and paid on the PaymentError itself, and rail402-mcp and the CLI report the error's values: settlement\_pending, settlement\_in\_progress, idempotency\_conflict, transaction\_submission\_failed, settle\_exact\_stellar\_transaction\_failed and settle\_upto\_stellar\_settled\_elsewhere report paid: unknown and are not retryable, and the seller's code never makes a refusal more retryable than the facilitator's table. rail402-mcp's JSON-RPC errors now carry retryable and paid: none as well.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/refusal.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/refusal.test.ts): applies the table to every code it holds, and never invites a second payment
* [`packages/sdk/test/refusal.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/refusal.test.ts): never reads the seller's code as more retryable than the facilitator's table
* [`packages/mcp/test/errors.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/errors.test.ts): follows the SDK's table for every facilitator code
* [`packages/cli/test/basics.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/basics.test.ts): never tells a seller-relayed submission failure that nothing moved
* [`tools/conformance/test/retry-agreement.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/retry-agreement.test.ts): agrees on a refused payment for every registered code, and none

### F-2026-147

**The Explorer's Bazaar calls shared one rate bucket for every visitor**

High · explorer · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats Q6, W3

Every Bazaar call the Explorer made for its visitors was charged to the Explorer's own address, so one visitor paging quickly spent the discovery budget of all of them and the pages of everyone else lost their listings and paid-for links. The Explorer now calls with a discovery-internal access key read from a file and sends a per-visitor HMAC identifier (X-Rail402-Client), never the visitor's address; the facilitator charges each forwarded visitor its own bucket and the key its own.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): sends the access key and a per-visitor identifier, never the visitor's address
* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): forwards the page's visitor from the app, and sends no key without one
* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): reads the key from a file only, refuses an inline key and a key of another shape

### F-2026-148

**A payment page was cached as final before its Bazaar link was known**

Medium · explorer · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats W5

A payment page was cached for an hour as soon as the payment was indexed, even when the Bazaar had not linked it yet because cataloging was still running, so its paid-for link stayed missing for an hour. A payment page is now cached as final only once its link is known, or once the Bazaar answered for a payment older than 10 minutes; otherwise for 15 seconds.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): caches a payment page as final only once its link is known or the grace period is over

### F-2026-149

**Another issuer's token named USDC was shown and counted as USDC**

Medium · explorer · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats W2

Asset codes were shown by their code alone, so a token any issuer named USDC was labelled and summed as USDC. Only allow-listed contracts are shown by a bare code; any other token shows its code with its issuer and is never counted as USDC.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): shows a bare code only for an allow-listed contract
* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): never counts or names another issuer's USDC as USDC

### F-2026-150

**Pending and withdrawn listings had full resource pages with a way to pay them**

Medium · explorer · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats P9, W2

A listing the Bazaar had not published (pending its origin check, or withdrawn) was rendered like a published one, with a price, a seller and a call snippet, although its metadata may have been echoed by a payer and never confirmed. Such a listing now shows only its state and history: no price, no seller and no way to call it.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): shows a pending listing's state and history only: no price, no seller, no way to call it

### F-2026-151

**A payment's paid-for link was not checked against the account it paid**

Medium · explorer · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats W2

The Explorer showed the Bazaar's paid-for link on any payment the Bazaar named, even when the listing's payTo was not the account the payment paid on chain. A link is now shown only when the listing's payTo, or the base account of a muxed payTo, is the account the payment paid.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): shows a link only when the listing's payTo is the account the payment paid
* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): treats a muxed payTo as its base account

### F-2026-152

**Listing pages paged by what the Explorer kept, skipping listings**

Medium · explorer · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats W3

The services pages computed their next offset from the listings the Explorer kept after filtering, not from what the Bazaar sent, so listings were skipped or repeated across pages, and version history was read in pages too small for long histories. Pages now follow the Bazaar's own pagination, version history is read 25 at a time, and crawlers are kept off paginated pages and the CSV.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): pages listings by what the Bazaar sent, not by what it kept
* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): asks for 25 versions and keeps crawlers off paginated pages and the CSV

### F-2026-153

**A slow Bazaar held Explorer pages for several sequential timeouts**

Medium · explorer · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats W3, W4

Each Bazaar call of a page had its own timeout and the calls ran one after another, so a hanging Bazaar held an account page or the CSV for many seconds. All Bazaar calls of one page now share one 3-second deadline; when it runs out the page renders from the ledger alone and says so, and a call cut short is never kept for the next visitor.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): gives up at the page's deadline, and never keeps that failure for the next visitor
* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): renders an account page and the CSV within the deadline while the Bazaar hangs

### F-2026-154

**A listing with private usage answered as if it did not exist**

Low · bazaar · Fixed · found by `REV-2026-10-explorer-v2` on 2026-10-26, resolved 2026-10-26 · threats P16

The listing usage route answered a listing whose seller keeps its usage private with discovery\_listing\_not\_found, so a client could not tell a private listing from a missing one. It now answers 404 with its own code, discovery\_usage\_private, documented with the other discovery codes.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): answers 404 with its own code for a listing whose seller keeps its usage private

### F-2026-155

**The first payer of an mcp\:// tool decided its private-usage opt-out for good**

Medium · bazaar · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats P15

The private-usage opt-out was read from the payload a buyer echoes. A tool under an mcp\:// URL has no origin to confirm it, so the first payer's echo decided it for good: leaving the key out published every later payer of a seller that opted out, and adding it hid a seller's usage. Usage is now never published for a listing no origin confirmed (trust settled), applied at read time so the listings cataloged earlier are covered without changing their data, and the opt-out ratchets one way: a settlement that declares it makes the usage private at once and asks the origin to confirm; only the origin's own 402 can remove it.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): stays private for an mcp\:// tool whatever its first payer echoed, and links no settlement
* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): keeps a declared opt-out once any settlement declared it, whatever later payers echo
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): makes a listing's usage private when a payload declares it, until its origin says otherwise

### F-2026-156

**Testnet discovery load could shed pubnet discovery**

Medium · bazaar · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats N11, D5

One admission gate, one discovery database pool and one limiter served the discovery routes of both networks, ahead of the per-address limit, so enough parallel slow testnet requests made every pubnet discovery request fail with discovery\_overloaded. Discovery now admits in two lanes, pubnet-scoped requests and everything else, each with its own gate; with pubnet served next to testnet the pubnet lane reads the catalog and keeps its limits through a discovery pool of its own.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/discovery-overload.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.test.ts): keeps admitting pubnet discovery while testnet discovery fills every slot of its lane
* [`apps/rail402/test/discovery-overload.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.test.ts): reads a pubnet request through the pubnet lane's store while the shared one is busy
* [`apps/rail402/test/pubnet-pool.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/pubnet-pool.integration.test.ts): serves pubnet-scoped discovery through its own pool while the shared discovery pool is saturated

### F-2026-157

**The usage routes published each pubnet payer's purchases by resource**

Low · bazaar · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats P16

The usage routes linked settlements, and with them payer addresses, to the listings they paid for on every network, and the Explorer labelled each payer's payments with what they bought. On chain a payment names only payer, payTo and amount. Each network now publishes either settlement links or counts only (\<NET>\_BAZAAR\_USAGE\_DETAIL); pubnet defaults to counts, so no route links a pubnet payer or transaction to a listing, and the Explorer labels no pubnet payment.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): with counts, links no settlement to a listing and lists no payer, but still counts
* [`apps/rail402/test/discovery-config.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-config.test.ts): publishes usage counts only on pubnet by default, each network settable on its own
* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): says a network publishes payment counts only, and links no payment to the listing then

### F-2026-158

**A private-usage listing still named its settlements in its history and detail**

Low · bazaar · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats P16

rail402-private-usage hid usage on the usage routes only: the listing's version history still named the transaction that created or re-listed it, and its detail showed when it was last paid. While a listing's usage is private, or on a network that publishes counts only, its versions name no transaction, and a private listing shows no lastSettled. Its settlement count stays public: published clients require it.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): links no version to a settlement, and dates no settlement, while the usage is private
* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): links no version to a settlement on a network that publishes usage counts only

### F-2026-159

**One visitor's refused Bazaar call blanked Bazaar data for every Explorer visitor**

Low · explorer · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats W7

Bazaar calls are charged to each visitor's forwarded budget but their answers are cached for everyone, and any answer but 200 or 404 was cached for 5 seconds as the Bazaar being down. A visitor over its own budget could keep every other visitor's pages without listings. A 429, or a 503 with Retry-After, is now an answer for that visitor only and is never cached.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/explorer-fixes.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-fixes.test.ts): never keeps one visitor's refused budget as the Bazaar being down for the next visitor

### F-2026-160

**The sponsor balance page fired late on pubnet and a floor of 0 switched it off**

Low · operations · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats O11

Rail402SponsorBalanceLow fired below twice each network's readiness floor, so pubnet's default page threshold fell from 100 to 50 XLM, and a pubnet MIN\_SPONSOR\_BALANCE\_XLM of 0 silenced it. The alert now fires below each network's floor and, on every network but testnet, below 100 XLM whatever the floor; pubnet refuses a floor below 1 XLM at startup (config\_pubnet\_sponsor\_floor\_invalid).

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/config.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/config.test.ts): refuses a pubnet sponsor floor of 0, which would switch readiness and the balance alert off
* [`deploy/monitoring/alerts.test.yml`](https://github.com/tolgayayci/rail402/blob/main/deploy/monitoring/alerts.test.yml): Rail402SponsorBalanceLow

### F-2026-161

**The unused-nonce check of a sequence conflict was not bound to the proof's ledger**

Low · facilitator · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats O2

When no send of a settlement's bytes was ever accepted, a sequence conflict finished the settlement as nothing moved once the payer's nonce read as unused. That read came from whichever RPC was active, with no check that it had reached the ledger the sequence proof was read at, so a lagging provider or one switched to after a failover could show a nonce another transaction already consumed as unused. The read must now come from an RPC at or past the proof's ledger; otherwise the settlement keeps waiting (rpc\_behind).

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps waiting when the payer's nonce is read from an RPC behind the sequence proof

### F-2026-162

**The status page built HTML from its check history without escaping all of it**

Low · operations · Fixed · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats O15

The status page wrote a day's downtime from history/summary.json into a title attribute unescaped, and had no Content-Security-Policy of its own, so whoever could write the check history could run script on the page. Every value is now escaped and a day's downtime is read as a whole number of minutes; the page carries a policy that allows only its one inline script by SHA-256, the same policy the served header builds from that script.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`tools/conformance/test/status-page.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/status-page.test.ts): writes the check history into the page as text only
* [`tools/conformance/test/status-page.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/status-page.test.ts): allows its one inline script by hash and nothing else

### F-2026-163

**A quarantine does not check that the lease it clears is still the caller's**

Info · facilitator · Accepted risk · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats O2

The Postgres channel pool's quarantine clears any lease on the channel. If the settlement's own lease expired and another settlement leased the channel between the settlement's final transition and the quarantine, the newer lease is cleared.

**Tests**

* [`packages/store-postgres/test/stores.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/store-postgres/test/stores.integration.test.ts): never lease a channel whose recorded settlement is still open, released or expired

**Accepted** by the release approver on 2026-10-26. A safe guard needs a lease token in the channel row and in every lease call; a check on the lease's expiry alone would skip the quarantine of a settlement that waited past its lease, which is the case quarantine exists for. The window is one statement long, after the settlement's final transition.

* A channel whose recorded settlement is still open is never leased again, released or expired.
* A cleared lease leaves the channel quarantined, so no other settlement leases it until the quarantine ends.

### F-2026-164

**A seller's usage aggregates are heavy for their cost**

Info · bazaar · Accepted risk · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats D5, N11

GET /discovery/sellers/\{payTo}/usage costs 2 units and runs up to two count(DISTINCT) aggregates over all of a seller's settlements, each up to the 5-second statement timeout, on the small discovery pool.

**Tests**

* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): charges a settlements lookup by its size and a seller's usage double, reporting refusals by route

**Accepted** by the release approver on 2026-10-26. The aggregates are bounded by the statement timeout and the per-client cost budget, and they run on the discovery pool, never the settlement pool; with discovery lanes they can no longer shed pubnet discovery from testnet.

* Per-client cost budget, the seller route charged double.
* 5-second statement timeout on discovery reads.
* Admission lanes and a pubnet discovery pool.

### F-2026-165

**The payee backfill of migration 0026 runs as one update and can name a later owner**

Info · bazaar · Accepted risk · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats P16

Migration 0026 fills the payee of earlier settlement observations in one UPDATE inside the migration's transaction, and a backfilled settlement another payTo made while claiming a listing is attributed to the listing's owner of the time.

**Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): counts only the settlements that paid a listing's owner, never another payTo's claim

**Accepted** by the release approver on 2026-10-26. Migrations run in one transaction each, after an ALTER TABLE that already locks the table, so batching inside the migration would hold the same locks; the observation table has one row per cataloged settlement and the migration runs at deploy, before the service serves. Rows observed after it record their payee.

* The Explorer shows a paid-for link only when the listing's payTo is the account the payment paid on chain.
* The migration states the limit of the backfill.

### F-2026-166

**The Explorer's live holdings budget is shared by every visitor**

Info · explorer · Accepted risk · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats W4

Live holdings reads are bounded by one budget of 30 calls a minute for all visitors, so one visitor can use it up and the card then shows no live balances for everyone until it refills.

**Tests**

* [`apps/explorer/test/explorer-pages.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/explorer-pages.test.ts): shows them labelled live and fails soft, within a call budget

**Accepted** by the release approver on 2026-10-26. The card is labelled live and not audited, renders without data when the budget is spent, and protects a paid external service from being driven by visitors; no indexed figure depends on it.

* A global call budget per network that fails soft.
* Indexed pages never depend on live holdings.

### F-2026-167

**The status page shows sample figures off its own host**

Info · operations · Accepted risk · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26 · threats O13

Served from any host but status.rail402.dev, such as a repository preview URL, the status page renders generated sample uptime under a preview banner instead of the recorded history.

**Tests**

* [`tools/conformance/test/status-page.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/status-page.test.ts): writes the check history into the page as text only

**Accepted** by the release approver on 2026-10-26. The sample lets the page be reviewed before data exists; it is shown only off the published host, under a visible preview banner, and the published host always reads the recorded history.

* The published host never renders the sample.
* A preview banner is shown whenever the sample is.

### F-2026-168

**New licences after the audit candidate**

Info · docs · Accepted risk · found by `REV-2026-10-postfreeze` on 2026-10-26, resolved 2026-10-26

The changes after the audit candidate add no third-party npm dependency. The Explorer bundles three fonts under the SIL Open Font License 1.1 with their licence texts, and the status checks run on Upptime (MIT).

**Tests**

* [`tools/license-gate/test/policy.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/license-gate/test/policy.test.ts): rejects copyleft everywhere

**Accepted** by the release approver on 2026-10-26. OFL 1.1 is a font licence that permits bundling and is not a copyleft code licence; MIT is permissive. The licence gate checks every dependency on each change.

* The licence gate refuses copyleft in every dependency path.
* Font licence texts ship with the fonts.

### F-2026-169

**A tool listed from a payload without example arguments could never be confirmed again**

Low · bazaar · Fixed · found by `REV-2026-10-rehearsal` on 2026-10-27, resolved 2026-10-27 · threats P9

An MCP tool's origin check called the tool with the example arguments stored on its listing. A listing first cataloged from a payload without an example, by its seller before it declared one or by any payer echoing the tool, was called without arguments; a tool that validates arguments before asking for payment answered with a result, and the listing was quarantined. Every later check, including those its owner's genuine settlements requested, made the same call, so the seller's tool stayed withdrawn and its later quarantines recorded nothing. A check the owner's settlement requests now also tries the call that settlement's payload declares, and uses only a PaymentRequired to it; a quarantined listing whose check fails for another reason records the new reason, and the store refuses a quarantine version without a reason.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): lists a quarantined tool again when its owner's settlement proposes the call its server answers
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets a payload's proposed call fail the check of a tool that answers its listed call
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): records a new reason when a quarantined listing's check fails for another reason
* [`packages/store-postgres/test/stores.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/store-postgres/test/stores.integration.test.ts): keeps quarantines recorded before reasons were and refuses a new one without a reason

### F-2026-170

**The RPC failover alert never fired for the first switch after a start**

Low · operations · Fixed · found by `REV-2026-10-local-drills` on 2026-10-27, resolved 2026-10-27 · threats O3, O11

rail402\_rpc\_failovers\_total got its series for a pair of providers only when the service first switched between them, so the series appeared at 1. Prometheus sees no increase in a series whose first sample is 1, so Rail402RpcFailover (increase over 15 minutes above 0) stayed silent for the first failover of every replica and for its first fail-back; only a second switch between the same providers in the same process fired it. The service now exports the counter at zero for every ordered pair of a network's providers when it starts, so the first switch is an increase.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/metrics-signals.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/metrics-signals.test.ts): exports every switch between a network's RPC providers at zero, so the first failover is an increase
* [`apps/rail402/test/runtime.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/runtime.integration.test.ts): fails over to the next RPC provider and reports the active one

### F-2026-171

**The facilitator's HTTP server had no deadline for writing a response**

Medium · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats N1, D5

The service's Node server gave a response no time limit to reach its client. A client that stopped reading kept its socket and the whole response, a full discovery page included, for as long as it liked, after the request had already given up its admission slot, in the one process that settles both networks; pipelining let one connection queue many such answers. Every response, pipelined ones and those the server gives without the app (an invalid Host, an unmet Expect) included, must now be handed to the network within RESPONSE\_WRITE\_TIMEOUT\_MS (30 s) of being ready, or the connection is reset and counted. One connection may have at most MAX\_PIPELINED\_REQUESTS (4) requests waiting for their answers, and a discovery request keeps its slot until its page is sent, at most DISCOVERY\_MAX\_IN\_FLIGHT\_PER\_CLIENT per client and lane. At shutdown, idle connections and requests still arriving are dropped, and a client gets at most 1 s more to take an answer.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/server.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/server.test.ts): resets the connection of a client that stops reading, and releases the response it held
* [`apps/rail402/test/server.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/server.test.ts): caps the requests one connection may have waiting for their answers, on every route
* [`apps/rail402/test/server.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/server.test.ts): are timed: a connection that does not read them is reset within the deadline
* [`apps/rail402/test/discovery-overload.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.test.ts): hold their slot until they are sent, so the pages one process keeps for its clients are bounded
* [`apps/rail402/test/server.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/server.test.ts): drops a request whose body is still arriving when shutdown begins

### F-2026-172

**Keyless testnet traffic could fail readiness and delay pubnet admission through the shared database pool**

Medium · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats N3, N4

/ready probed the database through the shared pool, and on the mainnet host every caller's per-address limit was written through it too. Keyless testnet traffic that held every shared connection therefore failed the aggregate /ready the mainnet host is routed by, and delayed every pubnet /verify and /settle until the pool's timeout. /ready now probes through one connection of its own, with a timeout. On the pubnet host the key is decided in memory first: a pubnet key holder's limit is kept through pubnet's own pool and every other caller's in the process, so no anonymous request takes a pubnet connection. A client already over a limit is refused from memory until its window ends, /metrics is answered from memory, and at startup the service checks that its database role's connection limit holds every pool of a process, refusing to serve pubnet below it (config\_database\_connections\_low).

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/pubnet-pool.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/pubnet-pool.integration.test.ts): stays ready and admits a pubnet key holder at once while keyless testnet traffic holds the shared pool
* [`apps/rail402/test/pubnet-pool.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/pubnet-pool.integration.test.ts): gives keyless and refused mainnet-host callers no pubnet connection and no per-address write
* [`apps/rail402/test/public-hosts.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/public-hosts.test.ts): limits a pubnet key holder through pubnet's limiter and nobody else there through a shared one
* [`apps/rail402/test/runtime.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/runtime.integration.test.ts): fails the database check within the acquire timeout once its connection stops answering
* [`apps/rail402/test/database-roles.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/database-roles.integration.test.ts): refuses pubnet a role whose connection limit cannot hold one process's pools

### F-2026-173

**Any party could use up a seller's new-listing quota with listings its own host confirmed for the seller's payTo**

Medium · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats P5, P13

A payTo's hourly quota of new listings counted every confirmed listing owned by that payTo, on any host. Any host can answer a 402 naming any payTo, so a party paying its own server for listings that named a seller, or claims that handed listings over to the seller, could fill the seller's quota and keep its real listings out of the catalog. The quota is now kept per origin (scheme, host and port), and a listing counts toward it only once its origin confirmed a settlement that paid the payTo its price there (for upto, the whole cap). An ownership transfer clears the charge, and concurrent checks hold the owner's quota until their transaction ends, so none charges past it.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): keeps a payTo's quota per origin, so listings another host confirms for it never block its own
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets listings moved to a payTo on another host use up its quota on its own host
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): charges a payTo's quota only for listings a settlement paid its origin's price for
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never charges a payTo's quota for listings handed over to it at its own origin

### F-2026-174

**safe-fetch read the bodies of answers it never returned without a limit**

Medium · sdk · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats X4, PG1

safe-fetch drained the body of a redirect it refused or followed, and of a 205 answer, with no byte cap and outside the request's total timeout. A seller answering the Playground relay with a redirect and an endless body kept the socket, and its slot in the relay's shared pool, for as long as it kept sending, so a few such requests starved every other relay client. A body that is never handed to the caller is now read only when it arrived whole; otherwise the connection is closed and its slot freed at once. A connection is also closed when an answer ends before the upload was sent, or goes on after a compressed body ends, so no server holds a connection past the request's limits.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/safe-fetch/test/fetch.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/safe-fetch/test/fetch.test.ts): closes a refused redirect instead of reading its body, and frees its connection
* [`packages/safe-fetch/test/fetch.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/safe-fetch/test/fetch.test.ts): closes a followed redirect's endless body before the next hop
* [`packages/safe-fetch/test/fetch.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/safe-fetch/test/fetch.test.ts): closes a 205 answer that sends a body anyway, and frees its connection
* [`packages/safe-fetch/test/fetch.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/safe-fetch/test/fetch.test.ts): closes a refused redirect's connection and frees its slot
* [`packages/safe-fetch/test/fetch.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/safe-fetch/test/fetch.test.ts): closes the connection when a compressed body ends before the answer

### F-2026-175

**The pubnet conformance run executed freshly installed npm packages in the process holding the treasury secret**

Medium · operations · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats CF2

The pubnet live run of the published packages installed them, with every dependency npm resolved, and imported them in the process that read the treasury secret, with the secret file's path in its environment and install scripts enabled. A compromised dependency could read the treasury seed. The installed packages now run only in a separate process with an allow-listed environment, npm runs with install scripts off, and the payer is a fresh account per run that the treasury process funds on request. On pubnet that process starts in its own user, mount and PID namespaces with the treasury and key paths masked, and the run refuses to start, before anything is spent, where that isolation is unavailable.

**Fixed in** [`8695731`](https://github.com/tolgayayci/rail402/commit/8695731b60753e3f6cd152e6b8c0116a3824f7ac). **Tests**

* [`tools/conformance/test/pubnet-live.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/pubnet-live.test.ts): on pubnet: npm without install scripts or secrets, and the SDK away from the treasury
* [`tools/conformance/test/upstream-e2e-env.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/upstream-e2e-env.test.ts): keep only the toolchain variables of this process, never a secret's
* [`tools/conformance/test/upstream-e2e-env.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/upstream-e2e-env.test.ts): run no dependency's install script
* [`tools/pack-check/test/consumer-install.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/pack-check/test/consumer-install.test.ts): runs npm with install scripts off and the allow-listed toolchain environment only

### F-2026-176

**The demo seller served concurrent copies of one payment**

Low · sdk · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats R4

In the authorization flow /verify refuses a payment already settled, but copies of one payment that arrive together all pass /verify before any of them settles. The demo seller, the Playground lab sellers and the seller-http example used the facilitator client as it was, so each concurrent copy was served while only one payment moved. Each now wraps its facilitator client in withSettleDedupe, covering the exact routes, the upto /history server and the MCP tools: the first copy is served and the others get seller\_settlement\_repeat. The seller helpers page and quickstart now wrap the client in both payment flows.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): serves concurrent copies of one payment once, whether or not the facilitator marks the repeats
* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): serves concurrent copies of one metered payment once
* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): serves concurrent copies of one paid tool call once, metered or not
* [`apps/playground/test/lab-sellers.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/playground/test/lab-sellers.test.ts): serves a payment once when copies of it arrive together, and traces the refused copy
* [`packages/sdk/test/example-seller-http.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/example-seller-http.test.ts): serves concurrent copies of one payment once

### F-2026-177

**Anonymous demo seller traffic could use up the facilitator limit its buyers shared**

Low · operations · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29

Every request with a payment header made the demo seller call the facilitator, and those calls all come from the seller's one address, which the facilitator limits per address. Anyone could send the demo seller payment headers it would never be paid for until that budget was spent and every real buyer's payment was refused; with default settings the Playground shared the same address and budget. The demo seller now counts, per client and in total, only the payments it actually asks the facilitator about, below what the facilitator allows one address, and refuses the rest with a 402 rate\_limited and Retry-After. It keeps at most 10,000 clients in memory, counts IPv6 addresses per /56, and counts IPv4 written as IPv6 as that IPv4.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): counts only the payments it asks the facilitator about, never a request that carries none
* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): limits each client's paid requests before they reach the facilitator
* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): limits paid requests across all clients, however many addresses send them
* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): counts the IPv6 addresses of one /56 as one client, and IPv4 written as IPv6 as that IPv4
* [`apps/demo-seller/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/demo-seller/test/seller.test.ts): keeps a bounded number of clients in memory, forgetting the least recently seen

### F-2026-178

**A key file replaced while the service started was marked as loaded but never applied**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats Q2

The access keys were built from the records the configuration had parsed, and the key file was then read a second time for its stat and digest. A keys.json replaced between those two reads, for example to revoke a key, was recorded as seen but never parsed, so the revoked key stayed valid until the file changed again, while /health/private reported the new file's digest. The records, the file's stat and its digest now come from one read, at start and on every reload. A file that cannot be read or parsed leaves the previous records in force, reports no digest, and is read again on the next reload.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/access.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/access.test.ts): applies a file renamed into place between loading the configuration and starting
* [`apps/rail402/test/access.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/access.test.ts): reports no digest for a file it could not apply at start, and reports the file on the first reload
* [`apps/rail402/test/access-reload-race.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/access-reload-race.test.ts): applies the good file on the next reload instead of remembering it as seen

### F-2026-179

**Anonymous traffic from a key holder's address spent the key holder's /verify and /settle limit**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats N1, N3

The service-wide limit counted requests per client address. Keyless requests of either network, unknown paths and refused requests from a pubnet key holder's address or IPv6 /64, such as other clients behind the same NAT, used up the same bucket and got the key holder's /verify and /settle refused. A /verify or /settle with a usable key is now held to a limit of its own key, sized to what the key may send on all its networks and never below RATE\_LIMIT\_PER\_MINUTE. Every other request is held to its address's limit, under a prefix no client address can spell as a key's, and a request for a network that does not admit the key pays its address's limit as well.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/public-hosts.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/public-hosts.test.ts): never lets anonymous or other-network traffic from its address or /64 spend a key holder's limit
* [`apps/rail402/test/public-hosts.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/public-hosts.test.ts): never lets a key carry payments past its address's limit on a network that does not admit it
* [`apps/rail402/test/public-hosts.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/public-hosts.test.ts): sizes a key's own limit to every network it may use, never below RATE\_LIMIT\_PER\_MINUTE
* [`apps/rail402/test/pubnet-pool.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/pubnet-pool.integration.test.ts): admits a pubnet key holder on every host after anonymous testnet traffic spent its address's limit

### F-2026-180

**Any payer could withdraw a seller's confirmed route-template listing by paying at another path**

Low · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats P1, P2, P9

The origin check a settlement requested asked the path that settlement paid. For a published route-template listing, any payer could pay the seller at another path of the route that answered without a 402, or with other metadata, and the check withdrew or rewrote the seller's listing. A published listing is now decided by one origin answer only, at its resource or its route template's example path, and a published MCP tool by its listed call: a settlement at another path, or a call a payer proposed, can neither change nor withdraw it. While a listing is not yet listed, its check keeps the paths the settlements paying its owner proposed, asks them largest payments first within the host's budget, and lists the listing from an answer that lists it for its owner.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): asks only a route-template listing's example path whether it changed or still sells
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets a settlement withdraw a route-template listing whose example path is unknown
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets a claim's path change what a published route-template listing says
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets a payment to the seller redirect a claim's check to a path its payer chose
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): change or withdraw a tool whose listed call fails for now

### F-2026-181

**One-unit payments for mcp\:// tools could use up testnet's hourly listing quota and published room**

Low · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats P5, P10

mcp\:// tools are listed from the payload alone, since no origin can confirm them, and any payer can list one for any payTo with a one-unit payment. They counted toward the network's hourly new-listing quota and its room for published listings like listings an origin confirmed, so one payer could fill both and keep every seller with an origin out of the catalog. Listings no origin confirms are now counted apart: on testnet they take at most half of the hourly quota and half of the published room, and never use up those of listings an origin confirmed.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets tools no origin can confirm take the room listings an origin confirmed need
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets tools no origin can confirm use up the hourly quota of listings an origin confirms

### F-2026-182

**Hourly new-listing quotas were checked only when a listing was created**

Low · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats P5, P13

The network's and a payTo's hourly quotas of new listings counted listings when they were first cataloged. Listings created at once, or kept pending for hours while their origin did not answer, were all published together once their origin confirmed them, far past the quotas. The quotas are now spent when a listing is first published: a listing confirmed while a quota is spent waits, without spending a retry or asking its origin, until the earliest listing counted leaves the hour (publication\_deferred). The network's count holds a lock until its transaction ends, so concurrent publications stay within the quota.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): publishes no more new listings per hour than its network's quota, however many wait for their origin
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): counts a listing in the hour it is first published, however long ago it was created
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): publishes no more of a payTo's new listings at an origin per hour than its quota, however many are confirmed at once
* [`packages/bazaar/test/memory-catalog.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/test/memory-catalog.test.ts): publishes listings staged pending past the hour only as the hourly quotas have room

### F-2026-183

**A route-template listing's example path was taken from what its first payer echoed**

Low · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats P2

A route-template listing's example (info.input.pathParams) decides which path is asked when someone claims the listing, but it was the pathParams the creating payer echoed, which nothing checked. A payer could set an example that never answers, or answers for the payer, and so block or decide every later transfer of the seller's listing. The example is now the path the creating settlement paid, and then the path at which the check that listed the listing confirmed it. A payer's echo no longer decides who may claim a listing no origin has confirmed, and a route template whose paid path does not percent-decode is not used.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): takes a route-template listing's example from the path its origin confirmed, never from an echo
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): takes a route-template listing's example from the settlement whose path its origin first confirmed
* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): never lets an echoed example keep a seller from claiming a route template no origin confirmed

### F-2026-184

**The proxy example logged API keys and payment headers for requests it could not proxy**

Low · operations · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats Q2

The proxy example deleted X-API-Key and the payment headers from each site's access log only. Caddy logs a request with its headers outside that log when it cannot proxy it (no ready replica, a failed round trip), when a response is cut off mid-body, for a Host that matches a site only without case, and with debugging on, so those entries kept the credentials in clear. The example's default logger and a logger for handler errors now delete them too, X-Relay-H-Payment-Signature included, under which the Playground relay forwards a payment, and host names no site claims get a 404.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`tools/conformance/test/proxy.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/proxy.test.ts): keeps payment headers and API keys out of every log besides the sites' access logs
* [`tools/conformance/test/proxy.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/proxy.integration.test.ts): keeps them out of the log of a request no replica could answer
* [`tools/conformance/test/proxy.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/proxy.integration.test.ts): keeps them out of the log of a response a replica cut off
* [`tools/conformance/test/proxy.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/proxy.test.ts): answers host names no site claims with a 404 of its own

### F-2026-185

**A database restore could let an upto authorization settled at zero off-chain be settled again**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats R5

Where upto zeros settle off-chain, the settlement row is the only record that an authorization was settled for zero. Restoring a dump taken before that row lost it, and the seller could then settle the same authorization on chain for up to its signed cap. Every restore is now recorded in settlement\_rewinds, and from then on /verify and upto settlements above zero answer settle\_upto\_stellar\_authority\_unavailable (retryable) until the chain has passed the longest window rule 7 accepts, about 25 minutes by default. The ledger the hold counts from is recorded only by the service's network poll, from a provider that answers with the network's passphrase and whose newest ledger closed within the last minute, counting its age in; requests never move it.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`packages/facilitator/test/upto-scheme.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-scheme.test.ts): holds on-chain settlement where zeros settle off-chain, so a zero the restore lost is not charged again
* [`packages/facilitator/test/upto-scheme.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-scheme.test.ts): settles on chain again once the chain has passed the longest window rule 7 can accept
* [`packages/facilitator/test/upto-scheme.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-scheme.test.ts): holds until authorizations accepted before a minute-old first reading have expired
* [`packages/facilitator/test/restore-hold.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/restore-hold.test.ts): records the first ledger only from a provider of this network whose newest ledger closed just now
* [`packages/store-postgres/test/stores.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/store-postgres/test/stores.integration.test.ts): count a database that held settlements before restores were recorded as just restored

### F-2026-186

**A withdrawn listing whose usage is private showed its last settlement**

Low · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats P15

GET /discovery/resources/\{id} built a pending or withdrawn listing's summary from the copy whose content it withholds, which no longer carried the private-usage extension. The detail of such a listing therefore showed lastSettled although its seller had made its usage private. The summary is now built from the stored listing, so the usage stays private while the content is withheld.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`apps/rail402/test/discovery-usage.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-usage.test.ts): dates no settlement of a withdrawn listing whose usage is private, while it is paid again

### F-2026-187

**rail402 quote and seller check printed a seller's 402 fields with terminal controls**

Low · cli · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats P7

The CLI's human output printed an option's scheme, raw amount and payTo, and the seller check's payTo and asset, as the seller's 402 sent them. Escape sequences in them could rewrite the terminal, fake a line such as the chosen option, plant links or write to the clipboard. Every value from a seller, the Bazaar or the network is now shown without control, bidirectional or invisible format characters, one-line values without line breaks, and a response body without carriage returns; --json output is unchanged. SDK reasons that quote a seller are cleaned the same way, before secrets are redacted from them.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/cli/test/commands.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/commands.test.ts): prints a seller's payment options without terminal controls, and JSON unchanged
* [`packages/cli/test/commands.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/commands.test.ts): keeps the lines of a paid response but not its carriage returns or controls
* [`packages/cli/test/local.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/cli/test/local.test.ts): prints the 402's payTo and asset without terminal controls, and JSON unchanged
* [`packages/sdk/test/basics.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/basics.test.ts): strips controls before redacting, so one inside a seed cannot hide it

### F-2026-188

**An upto settlement landed through another fee bump could be finished as expired on lagging RPC reads**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats F2, O2, O4

Once its own fee-bump hash read not found past its bounds, a settlement was finished as expired, with no funds moved, on an inner-hash lookup and a nonce read from whichever RPC answered. A lagging provider, a backend behind a balancer or another provider after a failover could answer from before the ledger at which the bounds were seen to pass, so a payment someone else's fee bump had landed was reported as expired. Reads that would prove a settlement absent, and the search for a copy after a failure on a consumed nonce, now count only from an RPC at or past the ledger their proof holds at, and with the history they need; otherwise the settlement waits. One left undecided for a minute counts in rail402\_settlements\_unresolvable.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): keep waiting while the inner-hash lookup and the nonce read lag, then succeed through the other fee bump
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): keep waiting when only the payer's nonce is read from an RPC behind the ledger bound
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): keep a failure on the consumed nonce open while the nonce read lags, then succeed through the other transaction
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): keep a failure open while the consuming ledger is older than the history of the RPC that scans it
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): report a settlement whose search stays behind for a minute as unresolvable

### F-2026-189

**A pubnet approval shown for one budget window covered the next**

Low · mcp · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats M9

rail402-mcp's first-per-session pubnet confirmation recorded the approved call without the budget window it was shown for. When the window renewed, or the session restarted, before the approved call was retried, the approval covered payments in a window the user had never been shown. The confirmation now carries the window and covers only payments in it; otherwise the user is asked again with the new window's end and budget.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/mcp/test/pubnet.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/pubnet.test.ts): binds an approval to the budget window it was shown for
* [`packages/mcp/test/pubnet.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/mcp/test/pubnet.test.ts): asks again when the budget window changes before the approved call is retried

### F-2026-190

**A keyed retry paid again after the budget window renewed or a new session started**

Low · sdk · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats M4

Idempotency keys were looked up within the current budget window only. A retry with the same key after the window renewed or a new session started found no earlier call and signed a second payment, although the chain had proven the first one settled. A key now belongs to the payer on its network: every ledger checks it across the payer's windows inside its atomic reserve, replays the earlier outcome or reports paid: settled, and pays again only once the chain shows the earlier authorization expired unused. The payment identifier is chosen at random and kept with the key.

**Fixed in** [`b08b49f`](https://github.com/tolgayayci/rail402/commit/b08b49f4a527c411a671fc666c26a0538d1586d9). **Tests**

* [`packages/sdk/test/ledger.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/ledger.test.ts): keeps an idempotency key across budget windows and sessions while its payment may have moved money
* [`packages/sdk/test/ledger.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/ledger.test.ts): frees a key held in an earlier window once the chain proves its authorization expired unused
* [`packages/sdk/test/http-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/http-flow.test.ts): never pays twice for a keyed retry in a later budget window
* [`packages/sdk/test/mcp-flow.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/mcp-flow.test.ts): never pays twice for a keyed retry after a new budget session starts

### F-2026-191

**withSettleDedupe refused a paid request whose first /settle answer was settlement\_pending**

Low · sdk · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats R4

When /settle answers settlement\_pending, the stock resource server settles again at once, and Rail402 answers that retry from its record with the repeat marker. withSettleDedupe took the marker for a copy of a payment already served and refused the buyer, who had paid, with seller\_settlement\_repeat. Each /settle the wrapper sends now carries an attempt id, which the facilitator records and echoes in every repeat's marker. The wrapper serves, once, a marked success that echoes one of its own attempts that was answered pending or brought no answer, and still refuses every other marked success.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/sdk/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/seller.test.ts): serves the first success after the facilitator answered settlement\_pending
* [`packages/sdk/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/seller.test.ts): serves a payment the stock resource server settles again after settlement\_pending
* [`packages/sdk/test/seller.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/seller.test.ts): serves a pending payment once, whichever of its copies or retries succeeds first
* [`packages/facilitator/test/idempotency.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/idempotency.test.ts): records the id of the request that starts a settlement, and echoes it in every repeat

### F-2026-192

**Pubnet searches could wait on a search-index build queued on the shared discovery pool**

Low · search · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats N11, D5

A search-index build is shared by every search waiting for the same revision, and it read the catalog through the pool of the search that started it. A pubnet search could therefore join a build waiting for a connection of the shared discovery pool, which testnet or unscoped load had filled, and be shed with it. Builds and the background index check now read the catalog through a pool of their own (2 connections) that no request takes, and a search whose build cannot read the catalog answers from the previous index.

**Fixed in** [`3549435`](https://github.com/tolgayayci/rail402/commit/35494357aeccb7b1f904679502efe6c99d4f6315). **Tests**

* [`packages/search/test/search.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/search/test/search.test.ts): answers from the previous snapshot while an index build cannot read the catalog
* [`packages/search/test/search.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/search/test/search.test.ts): builds the index, and reads a refresh's revision, through the index store only
* [`apps/rail402/test/discovery-overload.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.test.ts): answers a pubnet search while the shared lane's store has no connection for an index build
* [`apps/rail402/test/discovery-overload.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.integration.test.ts): keeps the search index current while every discovery connection is held

### F-2026-193

**Anyone could take over the fixture smart accounts a pubnet conformance run funds**

Low · operations · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats CF1

The test kit's smart-account payer ran a contract whose init set its owner in a separate call that needed no authorization and that anyone could repeat. Another account could make itself the owner of a fixture account a pubnet run had funded with USDC and drain it. The fixture is now a contract built in this repository whose constructor binds the owner key in the transaction that creates the account, and nothing can change the key afterwards. Before a pubnet run sends USDC to a contract account, it reads the account on chain and refuses one that does not run a known account's code, or a fixture account whose owner is not the run's key.

**Fixed in** [`f8c4e1c`](https://github.com/tolgayayci/rail402/commit/f8c4e1c3cdfff03afa06beb7a27859ae470aa820). **Tests**

* [`packages/testkit/test/smart-account.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/testkit/test/smart-account.test.ts): takes its owner in the constructor and exports no function that could replace it
* [`tools/conformance/test/network.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.integration.test.ts): refuses to fund a contract account whose owner on chain is not the key it tracks
* [`tools/conformance/test/network.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/network.integration.test.ts): funds a smart account whose owner no other account can replace, and gets all of it back

### F-2026-194

**The Explorer could name an account as the payer of a custom-token payment it never signed**

Low · explorer · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats I13, I3

The exact indexing rule named the transfer's from address as the payer whenever the authorization entry named it. A token that is not a Stellar Asset Contract can skip require\_auth, so a transfer from any account could succeed with an entry nobody signed, and the Explorer recorded that account as having paid. Indexing rules v2 add check E7: an exact row names its payer only when the token is a Stellar Asset Contract, checked against its asset, or the payer's own key signed the entry over the payload the network signs; other rows are excluded as unproven\_authorization. The indexer reads ledgers indexed under v1 again, and until it has, the pages, API and report leave out rows of other rules.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`packages/x402-index/test/hostile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/x402-index/test/hostile.test.ts): never names an account as the payer of a custom-token transfer it did not sign
* [`packages/x402-index/test/hostile.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/x402-index/test/hostile.test.ts): counts a custom-token transfer the payer's own key signed, with Address or AddressV2 credentials
* [`apps/explorer/test/pages.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/pages.test.ts): never shows an account as the payer of a custom-token transfer it did not sign
* [`apps/explorer/test/indexer.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/indexer.integration.test.ts): reads the ledgers again when the rules version changes, so no row of older rules stays
* [`packages/sdk/test/inspect.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/sdk/test/inspect.test.ts): says a payer authorized a transfer only when the chain proves it

### F-2026-195

**A refused upto rebroadcast finished as failed while its transaction could still land**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats F2

When the network refused an upto settlement's rebroadcast for good before its bounds passed, for example once the sponsor key was frozen, the settlement was finished as failed with no funds moved. An earlier send may already have made its inner transaction public, and anyone can wrap it in a fee bump of their own until its bounds pass, so the payment could still land while the record and every repeat said failed. Such a settlement now keeps waiting until no later ledger can include its transaction, and is answered as settled when another fee bump lands it; one whose sends were all refused still finishes at once.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): keep waiting while a send may have been accepted, then succeed through someone else's fee bump
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): expire once the ledger bound passed and nothing carried the authorization
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): keep waiting when a send the look's copy of the settlement predates was accepted
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): fail at once when no send of the bytes was ever accepted

### F-2026-196

**An exact payment included after its authorization expired hid a copy that had landed first**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats F1, F2

An exact transaction's time bound can outlast the payer's authorization. Included after the expiration, it fails with invalid\_exact\_stellar\_signature\_expired, since the host checks the expiration before the nonce, and that cause did not start the search for a copy of the authorization someone else had landed earlier. The settlement was answered as expired although the payment had settled. That cause now starts the same search as a consumed nonce, and a copy found is answered as settled with its hash.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): succeed through a copy of the authorization that landed before the expiration
* [`packages/facilitator/test/upto-engine.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/upto-engine.test.ts): keep the failure open while the nonce read lags behind the ledger it failed in, then succeed
* [`packages/facilitator/test/front-run.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/front-run.integration.test.ts): answers success with the attacker's hash when ours lands after the authorization expired

### F-2026-197

**A refused exact submission finished as failed while a copy of the payment could still land**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats F2, O2

When the network refused an exact settlement's first submission for good, or another transaction took its channel's sequence number before any send was accepted, the settlement was finished at once as failed, with no funds moved. The payer's authorization stays valid until it expires, so the payer, the seller or anyone the payment passed through could still land a copy, and the reason asked the buyer to sign a new payment. Such a settlement now stays open until the authorization expired unused, and is answered as settled if a copy lands. Once the channel's sequence number proves its own transaction can never apply, it gives the channel back at once and sends nothing more.

**Fixed in** [`9355cd2`](https://github.com/tolgayayci/rail402/commit/9355cd20df71d722b072926c9c799adae92f7f94). **Tests**

* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): answers a copy of the authorization that landed before its first submission was refused
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps a refused first submission open while its authorization is valid, and answers a later copy
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): gives its channel back at a proven sequence conflict, and answers a later copy
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): sends nothing more once it gave its channel back, while the same request still waits
* [`packages/facilitator/test/sequence-conflict.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/sequence-conflict.integration.test.ts): gives its channel back within seconds, moves nothing, never settles twice and fails once the authorization expired

### F-2026-198

**An exact transaction that failed on chain was answered as failed while its authorization stayed usable**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats F2, S1

An exact transaction included and failed on chain for a cause that leaves the authorization usable (a balance spent after simulation, a trustline removed, a smart account or token that refused it for now) was finished as failed at once. The failure rolls back, nonce included, so anyone holding the payment could still land it until it expired, while the record said failed and the SDK told the buyer nothing was paid, inviting a second payment. Such a settlement now answers settlement\_pending, gives its channel back, is answered as settled if a copy lands, and with the on-chain cause once the authorization expired unused; only a consumed nonce, or an expiry past the payer's own expiration ledger, finishes at once. The SDK, rail402-mcp and the CLI report those causes as paid: unknown and never retryable.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps a failure on the payer's balance open while the authorization is valid, and answers a later copy
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps a failure on a removed trustline until the authorization expired unused, then names it with the fee charged
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps a smart account's failed signature open while the authorization is valid, and answers a later copy
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): keeps an expired-authorization error raised by a token contract before the payer's expiration open, and answers a later copy
* [`packages/facilitator/test/first-submission.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/facilitator/test/first-submission.test.ts): sends nothing after its own failed transaction gave the channel back, when a lagging RPC then misses it

### F-2026-199

**An Explorer account page aggregated its seller's whole settlement history on every view**

Low · explorer · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats W3, I9

Every anonymous view of a seller's account page counted the seller's exact and upto payments and its distinct payers over the settlements table, with no precomputed total, so the work of one view grew with the seller's history and ran on the Postgres the facilitator shares; only the reader role's statement timeout bounded it, and revalidations ran it too. The indexer now keeps those counts in seller\_summaries with the account totals (Explorer migration 0006, filled from the stored settlements), a view reads one row, and a seller's newest upto payments are read through an index of their own.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/web.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/web.integration.test.ts): lists, filters and pages payments, and reads what a seller received, from the database

### F-2026-200

**The network window published its anchor Horizon URL, including any provider key in its path**

Low · explorer · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats I11, I8

The network window job published the whole EXPLORER\_ANCHOR\_HORIZON\_URL in its rebuild command and document, which the Ecosystem page, the report API and snapshot exports serve to anyone. The same variable is the indexer's private anchor, and a paid provider's key can only sit in the URL path. A window now names only a bare-origin Horizon: the anchor itself when its path is /, otherwise EXPLORER\_NETWORK\_WINDOW\_PUBLIC\_ANCHOR\_URL, without which the job refuses to start (config\_anchor\_not\_public); the window refuses to publish any anchor URL with a path, query or credentials.

**Fixed in** [`50e7461`](https://github.com/tolgayayci/rail402/commit/50e7461e197396c247be2bb72489a3bb88514a52). **Tests**

* [`apps/explorer/test/network-window.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/network-window.test.ts): refuses an anchor with a path, which may carry a provider key, unless a public anchor is named
* [`apps/explorer/test/network-window.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/network-window.test.ts): refuses a public anchor that is not a bare origin
* [`apps/explorer/test/network-window.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/explorer/test/network-window.test.ts): never publishes an anchor URL with a path, even when called directly

### F-2026-201

**An address over its discovery limit still took places in the pubnet discovery lane**

Low · facilitator · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-28 · threats N1, D5

Per-client discovery limits ran only after a request held a lane slot, and each refused request still wrote one serialized upsert to the shared rate-limit row, so one address sending faster than that write drained could take the pubnet lane's slots from other clients. One client now holds at most its share of a lane (DISCOVERY\_MAX\_IN\_FLIGHT\_PER\_CLIENT, 8 by default) before the lane's gate, and an address seen over its limit is refused from memory until its window ends, with no further write.

**Fixed in** [`ebeea7a`](https://github.com/tolgayayci/rail402/commit/ebeea7a92a115a58041ef9cdd612ab6f5a9d6b87). **Tests**

* [`apps/rail402/test/discovery-overload.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-overload.test.ts): limit how many one client holds at once, so one client cannot take every slot
* [`packages/store-postgres/test/stores.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/store-postgres/test/stores.integration.test.ts): refuses a key over its limit from memory, without another write, until its window ends

### F-2026-202

**Listings their origin never confirmed were kept forever, with the payer's content stored three times**

Low · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats N3, P5

A free testnet settlement with Bazaar metadata for any https resource creates a pending listing; when its origin does not confirm it, it is withdrawn. Such listings and their versions, each holding the payer-echoed content, were never deleted, and only the hourly per-payer quota bounded how many were created, so free settlements grew the database pubnet shares without bound. A listing never published that has stayed withdrawn for 7 days is now deleted with its versions, options, observations and origin check (listing\_pruned, migration 0036); its content was never public, and a later settlement stages it again.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): deletes a listing never published once it stayed withdrawn for a week, and keeps every listing ever published

### F-2026-203

**One party could fill pubnet's published-listing capacity and keep every new seller out**

Medium · bazaar · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats P5, P13

Pubnet's capacity of published listings was shared first come, first served, with no share per seller, and under pubnet's defaults no published listing ever left: the idle retention was off and nothing re-checked a published listing's origin. A party publishing from its own origins could fill the network and keep it full after abandoning them, refusing every new seller with bazaar\_catalog\_full. One payTo may now hold at most PUBNET\_BAZAAR\_MAX\_PUBLISHED\_PER\_OWNER (250) published listings, and pubnet withdraws a listing after 90 days without a settlement (PUBNET\_BAZAAR\_IDLE\_TTL\_DAYS, was 0); a withdrawn listing comes back only when its origin confirms it again.

**Fixed in** [`2fd3600`](https://github.com/tolgayayci/rail402/commit/2fd36001c638d0ad930ada5f4e87696c3a848613). **Tests**

* [`packages/bazaar/src/testing.ts`](https://github.com/tolgayayci/rail402/blob/main/packages/bazaar/src/testing.ts): holds each payTo to its share of published listings, so no party fills its network
* [`apps/rail402/test/discovery-config.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/discovery-config.test.ts): reads each network's own budget variables, which win over the legacy ones

### F-2026-204

**A contract release did not prove which revision of the third-party build workflow ran**

Low · operations · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats C1, C5

The contract release calls stellar.expert's reusable workflow at its main branch, which stellar.expert requires, with this repository's contents, OIDC and attestation permissions. Its only check compared that branch with the reviewed revision before the call, a second read of a mutable name, and the release's attestation was verified by signer repository alone, so a run of another revision would have gone unnoticed. The release now waits for a reviewer in the contract-release environment, and its attestation must name the reviewed workflow revision and the tag's commit (--signer-digest, --source-digest); a pubnet deploy of the upto contract checks the same revision.

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/workflows.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/workflows.test.ts): binds a contract release to the reviewed revision of stellar.expert's workflow and the tag's commit

### F-2026-205

**The deploy gate accepted an image built for the tag name from another commit**

Medium · operations · Fixed · found by `REV-2026-10-prelaunch` on 2026-10-28, resolved 2026-10-29 · threats C2, C3

The deploy gate checked that the release tag was signed and that each image's attestation came from the release workflow run for that tag name, but not that the build ran from the signed commit. Someone able to push a tag but without a release-signing key could point the tag at a commit whose own release.yml built and attested an image, then let the signed tag replace it; the gate would accept that image for the signed commit. The gate now requires each attestation to name the signed commit both as the build's source and as the workflow file that ran (--source-digest, --signer-digest).

**Fixed in** [`32bb685`](https://github.com/tolgayayci/rail402/commit/32bb685ff9f0e50020ac3d789bbcf2954cbdd79b). **Tests**

* [`tools/conformance/test/check-deployable.test.ts`](https://github.com/tolgayayci/rail402/blob/main/tools/conformance/test/check-deployable.test.ts): gathers its facts from git and the GitHub API

### F-2026-206

**A protocol upgrade the SDK could not read stopped every verification while /health and /ready said healthy**

Medium · operations · Fixed · found by `REV-2026-11-protocol-30` on 2026-11-04, resolved 2026-11-04 · threats O3

The Stellar testnet moved to protocol 30, whose ledger headers carry a StellarValueType that @stellar/stellar-sdk 16.3.0 cannot decode. Every /verify then answered unexpected\_verify\_error, so no testnet payment settled, while the network poll (getHealth, getTransaction and balance reads, none of which decode a ledger header) kept /health and /ready healthy and no alert fired. 0.5.2 moves to stellar-sdk 16.4.1, which reads protocol 30, and the network poll now decodes the newest ledger header each time: an SDK that cannot read the network's ledgers fails the RPC readiness check, so /ready answers 503 and the alerts on it fire before payments fail one by one.

**Fixed in** [`6079ae2`](https://github.com/tolgayayci/rail402/commit/6079ae274a4f963a777fa2ed6f0c1a84ff2dc17f). **Tests**

* [`apps/rail402/test/protocol-readiness.integration.test.ts`](https://github.com/tolgayayci/rail402/blob/main/apps/rail402/test/protocol-readiness.integration.test.ts): fails readiness while the newest ledger header cannot be decoded, and passes again after


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.