{
  "title": "XRPL rippled 3.1.3 live-mainnet-enabled high/critical evidence manifest",
  "target": {
    "repo": "XRPLF/rippled",
    "tag": "3.1.3",
    "commit": "46b241ace8b30d9c9775d60ffba7d24b21903896"
  },
  "proof": {
    "runner": "run_definitive_proof.sh",
    "source": "OpenP0Repro_test.cpp",
    "log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
    "sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
    "result": "Live-mainnet eligible extract: 22 findings (Wave 3 added 3 family-extension findings on 2026-05-28: NFTOKEN-OFFER-ISSUER-SELF-FREEZE-001, AMMBID-DISALLOW-INCOMING-REFUND-001, NFTOKEN-ACCEPT-DEPOSITAUTH-001). TRUSTLINE-POSITIVE-BALANCE-RESERVE-001 expanded to twelve markers. Original 19-record run: OpenP0Repro full suite 70 cases / 16752 tests / 0 failures; OpenP0ReproCrash 1 case / 12 tests / 0 failures. Wave 3 supplementary run (2026-05-28): OpenP0Repro 3 cases / 166 tests / 0 failures on the internal/bug-hunt-plan branch binary (commit 7a1d3e3abbd4f34aec7e822d3fd0f6f63e0f3d28)."
  },
  "records": [
    {
      "id": "MPT-LOCK-UNAUTH-001",
      "title": "MPT locked holder lock-state deletion",
      "category": "Current feature-bound",
      "level": "High",
      "score": 8.2,
      "affected": "rippled 3.1.3 MPT lock-state handling",
      "exploit_type": "Feature-gated lock-state deletion",
      "markers": [
        "MPT current \u2014 locked holder can delete lock state without SAV"
      ],
      "broken": "A holder can tfMPTUnauthorize a locked zero-balance MPToken, deleting issuer lock state, then re-authorize without lsfMPTLocked.",
      "expected": "Locked-token deletion checks should preserve issuer lock state.",
      "source_signal": "Source review of MPTokenAuthorize::preclaim plus upstream no-SAV lock/delete coverage.",
      "remediation": "Enforce locked MPToken deletion checks in the MPT authorization path.",
      "script": "repros/MPT-LOCK-UNAUTH-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "mpt-lock-unauth-001",
      "required_amendments": [
        "MPTokensV1"
      ],
      "required_disabled_amendments": [
        "SingleAssetVault"
      ]
    },
    {
      "id": "TRUSTLINE-POSITIVE-BALANCE-RESERVE-001",
      "title": "Positive IOU balance without receiver owner reserve after offer crossing, transfer-rate offer crossing, CheckCash, TokenEscrow finish, NFToken settlement, AMMWithdraw, or AMMClawback",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.1,
      "affected": "rippled 3.1.3 IOU trustline reserve and owner-count accounting",
      "exploit_type": "Reserve and owner-count bypass",
      "markers": [
        "TrustLine current \u2014 offer crossing creates positive balance without reserve",
        "TrustLine current \u2014 offer crossing leaves positive balance unowned with existing owner objects",
        "TrustLine current \u2014 offer crossing succeeds below missing owner reserve",
        "TrustLine current \u2014 offer crossing with transfer rate creates positive balance without reserve",
        "TrustLine current \u2014 CheckCash creates positive balance without reserve",
        "TrustLine current \u2014 CheckCash leaves positive balance unowned with existing owner objects",
        "TrustLine current \u2014 CheckCash succeeds below missing owner reserve",
        "TrustLine current \u2014 TokenEscrow creates positive balance without reserve",
        "TrustLine current \u2014 NFToken AcceptOffer creates positive balance without reserve",
        "TrustLine current \u2014 NFToken broker fee creates positive balance without reserve",
        "TrustLine current \u2014 AMMWithdraw creates positive balance without reserve",
        "TrustLine current \u2014 AMMClawback creates positive balance without reserve"
      ],
      "broken": "After a holder clears its trust limit and balance, old live settlement paths can move that holder back to a positive IOU balance while the trustline reserve flag remains unset. The packet reproduces this through offer crossing, offer crossing with an issuer transfer rate, CheckCash, TokenEscrow finish, NFToken seller proceeds, NFToken broker fees, AMMWithdraw one-asset withdrawal, and AMMClawback paired-asset return, including controls where the holder already owns two ticket objects and OwnerCount remains 2 instead of increasing for the positive trustline. Reserve-boundary controls for offer crossing and CheckCash also leave the holder below the reserve required for the missing third owner object while still accepting the positive IOU balance.",
      "expected": "A receiver whose trustline balance moves from zero or negative to positive should either receive the reserve flag and OwnerCount increment, or the transaction should fail for insufficient reserve. The rule should be enforced consistently even when the receiver already has owner objects and across offer crossing, transfer-rate offer crossing, CheckCash, TokenEscrow finish, NFToken settlement, AMMWithdraw, AMMClawback, and any other live settlement path that uses the shared IOU credit helper.",
      "source_signal": "Unmerged upstream branch origin/vvysokikh1/fix-positive-balance-trustline-pay-no-reserve, commit b4a45f1f0.",
      "remediation": "Charge the receiver owner reserve when IOU balance crosses from non-positive to positive, and fail if the receiver lacks reserve.",
      "script": "repros/TRUSTLINE-POSITIVE-BALANCE-RESERVE-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "trustline-positive-balance-reserve-001",
      "required_amendments": [],
      "required_baseline_surface": "IOU trustline reserve accounting in rippled 3.1.3"
    },
    {
      "id": "TRUSTLINE-DISALLOW-INCOMING-OFFER-001",
      "title": "OfferCreate bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 IOU OfferCreate trustline admission",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "TrustLine current \u2014 OfferCreate bypasses DisallowIncomingTrustline"
      ],
      "broken": "An issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but a taker without an existing trustline can still cross an offer and receive the issuer's IOU, creating the trustline through OfferCreate.",
      "expected": "OfferCreate should enforce the same incoming-trustline opt-out policy before allowing a taker without an existing trustline to receive the issuer's IOU.",
      "source_signal": "Unmerged upstream branch origin/copilot/apply-asfdisallowincomingtrustline, commits fa0cc0abc and ad840518f, adds a check gated by proposed fixDisallowIncomingV1_1.",
      "remediation": "Reject OfferCreate acceptance of an issuer IOU when the issuer has lsfDisallowIncomingTrustline and the taker has no existing trustline.",
      "script": "repros/TRUSTLINE-DISALLOW-INCOMING-OFFER-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "trustline-disallow-incoming-offer-001",
      "required_amendments": [
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "NFTOKEN-DISALLOW-INCOMING-ACCEPT-001",
      "title": "NFTokenAcceptOffer bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 NFTokenAcceptOffer IOU settlement",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "NFToken current \u2014 AcceptOffer bypasses DisallowIncomingTrustline"
      ],
      "broken": "An IOU issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but NFTokenAcceptOffer can still settle a sell offer in that issuer's IOU to a seller with no existing trustline, creating the incoming trustline through NFT settlement.",
      "expected": "NFTokenAcceptOffer should enforce the same incoming-trustline opt-out policy before allowing a seller without an existing trustline to receive the issuer's IOU.",
      "source_signal": "Source review of NFTokenAcceptOffer and nft::checkTrustlineAuthorized shows RequireAuth and deep-freeze checks but no lsfDisallowIncomingTrustline check; no matching fix was found in 3.2.0-b7 or origin/develop.",
      "remediation": "Reject NFToken IOU settlement to a receiver with no existing trustline when the IOU issuer has lsfDisallowIncomingTrustline set, unless a governed amendment defines different NFT-specific semantics.",
      "script": "repros/NFTOKEN-DISALLOW-INCOMING-ACCEPT-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "nftoken-disallow-incoming-accept-001",
      "required_amendments": [
        "NonFungibleTokensV1_1",
        "fixEnforceNFTokenTrustlineV2",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "NFTOKEN-BROKER-FEE-DISALLOW-INCOMING-TRUSTLINE-001",
      "title": "NFToken broker fee bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 NFTokenAcceptOffer brokered IOU settlement",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "NFToken current \u2014 broker fee bypasses DisallowIncomingTrustline"
      ],
      "broken": "An IOU issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but a brokered NFTokenAcceptOffer can pay the broker fee in that issuer's IOU to a broker with no existing trustline, creating the incoming trustline through NFTokenAcceptOffer::pay/accountSend.",
      "expected": "NFTokenAcceptOffer should enforce the same incoming-trustline opt-out before allowing an IOU broker fee to create a broker trustline for the issuer's IOU.",
      "source_signal": "Source review of NFTokenAcceptOffer shows broker-fee authorization/deep-freeze checks under fixEnforceNFTokenTrustlineV2, then payment through accountSend; no lsfDisallowIncomingTrustline check was found in NFTokenAcceptOffer, NFTokenUtils, or generic accountSend/issueIOU in 3.2.0-b7, origin/develop, or origin/copilot/apply-asfdisallowincomingtrustline.",
      "remediation": "Reject NFToken broker-fee IOU settlement to a broker with no existing trustline when the IOU issuer has lsfDisallowIncomingTrustline set, unless a governed amendment defines different broker-fee-specific semantics.",
      "script": "repros/NFTOKEN-BROKER-FEE-DISALLOW-INCOMING-TRUSTLINE-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "nftoken-broker-fee-disallow-incoming-trustline-001",
      "required_amendments": [
        "NonFungibleTokensV1_1",
        "fixEnforceNFTokenTrustlineV2",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "CHECKCASH-DISALLOW-INCOMING-TRUSTLINE-001",
      "title": "CheckCash bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 CheckCash IOU settlement and automatic trustline creation",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "CheckCash current \u2014 bypasses DisallowIncomingTrustline"
      ],
      "broken": "An IOU issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but CheckCash can still cash an IOU check to a receiver with no existing trustline, creating the incoming trustline through the CheckCash auto-trustline path.",
      "expected": "CheckCash should enforce the same incoming-trustline opt-out policy before auto-creating a receiver trustline for the issuer's IOU.",
      "source_signal": "Source review of CheckCash::preclaim/doApply shows RequireAuth and freeze checks plus automatic trustCreate under CheckCashMakesTrustLine, but no lsfDisallowIncomingTrustline check; no matching fix was found in 3.2.0-b7, origin/develop, or origin/copilot/apply-asfdisallowincomingtrustline.",
      "remediation": "Reject CheckCash IOU settlement to a receiver with no existing trustline when the IOU issuer has lsfDisallowIncomingTrustline set, unless a governed amendment defines different check-specific semantics.",
      "script": "repros/CHECKCASH-DISALLOW-INCOMING-TRUSTLINE-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "checkcash-disallow-incoming-trustline-001",
      "required_amendments": [
        "Checks",
        "CheckCashMakesTrustLine",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "TOKENESCROW-DISALLOW-INCOMING-FINISH-001",
      "title": "EscrowFinish bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 TokenEscrow IOU finish and automatic trustline creation",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "TokenEscrow current \u2014 Finish bypasses DisallowIncomingTrustline"
      ],
      "broken": "An IOU issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but EscrowFinish can still finish an IOU escrow to a destination with no existing trustline, creating the incoming trustline through the TokenEscrow finish path.",
      "expected": "EscrowFinish should enforce the same incoming-trustline opt-out policy before auto-creating a destination trustline for the issuer's IOU.",
      "source_signal": "Source review of EscrowFinish::preclaim and escrowUnlockApplyHelper<Issue> shows RequireAuth and deep-freeze checks plus automatic trustCreate for destination-finished token escrow, but no lsfDisallowIncomingTrustline check; no matching fix was found in 3.2.0-b7, origin/develop, or origin/copilot/apply-asfdisallowincomingtrustline.",
      "remediation": "Reject TokenEscrow IOU finish to a destination with no existing trustline when the IOU issuer has lsfDisallowIncomingTrustline set, unless a governed amendment defines different escrow-specific semantics.",
      "script": "repros/TOKENESCROW-DISALLOW-INCOMING-FINISH-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "tokenescrow-disallow-incoming-finish-001",
      "required_amendments": [
        "TokenEscrow",
        "fixTokenEscrowV1",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "AMMWITHDRAW-DISALLOW-INCOMING-TRUSTLINE-001",
      "title": "AMMWithdraw bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMMWithdraw IOU settlement and automatic trustline creation",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "AMM current \u2014 Withdraw bypasses DisallowIncomingTrustline"
      ],
      "broken": "An IOU issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but AMMWithdraw can still withdraw that issuer's IOU from a live AMM pool to a user with no existing issuer trustline, creating the incoming trustline through the AMM withdrawal accountSend path.",
      "expected": "AMMWithdraw should enforce the same incoming-trustline opt-out policy before allowing pool withdrawal to create a receiver trustline for the issuer's IOU.",
      "source_signal": "Source review of AMMWithdraw shows only reserve checking before accountSend returns pool IOU assets to the withdrawing account; no lsfDisallowIncomingTrustline check was found in AMMWithdraw or the generic accountSend/issueIOU path in 3.2.0-b7, origin/develop, or origin/copilot/apply-asfdisallowincomingtrustline.",
      "remediation": "Reject AMMWithdraw IOU withdrawal to a receiver with no existing trustline when the IOU issuer has lsfDisallowIncomingTrustline set, unless a governed amendment defines different AMM-specific semantics.",
      "script": "repros/AMMWITHDRAW-DISALLOW-INCOMING-TRUSTLINE-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "ammwithdraw-disallow-incoming-trustline-001",
      "required_amendments": [
        "AMM",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "AMMCREATE-DISALLOW-INCOMING-TRUSTLINE-001",
      "title": "AMMCreate bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMMCreate IOU pool creation and automatic AMM-account trustline creation",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "AMM current \u2014 Create bypasses DisallowIncomingTrustline"
      ],
      "broken": "An IOU issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but AMMCreate can still create a new AMM pool containing that issuer's IOU, creating the AMM account trustline to the issuer through the AMM creation accountSend path.",
      "expected": "AMMCreate should enforce the same incoming-trustline opt-out policy before creating an AMM account trustline for the issuer's IOU.",
      "source_signal": "Source review of AMMCreate shows it sends creator IOU assets into the AMM account and marks the resulting trustline as an AMM node, but neither AMMCreate nor generic accountSend / issueIOU checks lsfDisallowIncomingTrustline; no matching check was found in 3.2.0-b7, origin/develop, or origin/copilot/apply-asfdisallowincomingtrustline.",
      "remediation": "Reject AMMCreate involving an issuer IOU when the issuer has lsfDisallowIncomingTrustline set and the AMM account trustline does not already exist, unless a governed amendment defines different AMM-specific semantics.",
      "script": "repros/AMMCREATE-DISALLOW-INCOMING-TRUSTLINE-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "ammcreate-disallow-incoming-trustline-001",
      "required_amendments": [
        "AMM",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "AMMDEPOSIT-EMPTY-DISALLOW-INCOMING-TRUSTLINE-001",
      "title": "AMMDeposit empty-pool reinit bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMMDeposit tfTwoAssetIfEmpty IOU pool reinitialization and automatic AMM-account trustline creation",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "AMM current \u2014 Empty deposit bypasses DisallowIncomingTrustline"
      ],
      "broken": "An IOU issuer with asfDisallowIncomingTrustline set blocks direct TrustSet-created incoming trustlines, but AMMDeposit with tfTwoAssetIfEmpty can reinitialize an empty AMM pool and recreate the AMM account trustline to that issuer through the AMM deposit accountSend path.",
      "expected": "AMMDeposit empty-pool reinitialization should enforce the same incoming-trustline opt-out policy before recreating an AMM account trustline for the issuer's IOU.",
      "source_signal": "Source review of AMMDeposit shows tfTwoAssetIfEmpty deposits send IOU assets into the AMM account through accountSend, but neither AMMDeposit nor generic accountSend / issueIOU checks lsfDisallowIncomingTrustline; no matching check was found in 3.2.0-b7, origin/develop, or origin/copilot/apply-asfdisallowincomingtrustline.",
      "remediation": "Reject AMMDeposit empty-pool reinitialization involving an issuer IOU when the issuer has lsfDisallowIncomingTrustline set and the AMM account trustline does not already exist, unless a governed amendment defines different AMM-specific semantics.",
      "script": "repros/AMMDEPOSIT-EMPTY-DISALLOW-INCOMING-TRUSTLINE-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "ammdeposit-empty-disallow-incoming-trustline-001",
      "required_amendments": [
        "AMM",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "AMMCLAWBACK-DISALLOW-INCOMING-PAIRED-ASSET-001",
      "title": "AMMClawback paired-asset return bypasses issuer DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMMClawback paired IOU settlement and automatic trustline creation",
      "exploit_type": "Issuer policy bypass",
      "markers": [
        "AMM current \u2014 Clawback returns paired asset through DisallowIncomingTrustline"
      ],
      "broken": "Issuer B can set asfDisallowIncomingTrustline after a user has removed the issuer B trustline, but issuer A can still use AMMClawback against a two-issuer AMM and force-return issuer B's paired IOU to that user, recreating issuer B's incoming trustline through the AMMWithdraw/accountSend path.",
      "expected": "AMMClawback should enforce the paired-asset issuer's incoming-trustline opt-out before returning that issuer's IOU to a holder with no existing trustline, or fail/require the trustline to exist.",
      "source_signal": "Source review of AMMClawback::applyGuts shows it routes paired-asset return through AMMWithdraw::withdraw; AMMWithdraw/accountSend performs reserve checks but no lsfDisallowIncomingTrustline check was found in 3.2.0-b7, origin/develop, or origin/copilot/apply-asfdisallowincomingtrustline.",
      "remediation": "Reject AMMClawback paired-asset returns that would create a receiver trustline to an issuer with lsfDisallowIncomingTrustline set, unless a governed amendment defines different AMM-clawback semantics.",
      "script": "repros/AMMCLAWBACK-DISALLOW-INCOMING-PAIRED-ASSET-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "ammclawback-disallow-incoming-paired-asset-001",
      "required_amendments": [
        "AMM",
        "AMMClawback",
        "DisallowIncoming",
        "fixDisallowIncomingV1",
        "fixAMMClawbackRounding"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "AMMCLAWBACK-DEPOSITAUTH-PAIRED-ASSET-001",
      "title": "AMMClawback paired-asset return bypasses holder DepositAuth",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMMClawback paired IOU settlement and DepositAuth receive policy",
      "exploit_type": "Holder receive-policy bypass",
      "markers": [
        "AMM current \u2014 Clawback bypasses DepositAuth paired asset"
      ],
      "broken": "A holder can set asfDepositAuth and direct issuer Payment to that holder is rejected with tecNO_PERMISSION, but AMMClawback against a two-issuer AMM can still force-return the paired IOU to that holder, recreating the paired-asset trustline through the AMMWithdraw/accountSend path.",
      "expected": "AMMClawback should enforce the holder's DepositAuth receive policy before returning paired IOU assets to a holder with no existing trustline, or fail/require an explicit authorization path.",
      "source_signal": "Source review of AMMClawback::applyGuts shows it routes paired-asset return through AMMWithdraw::withdraw; no DepositAuth / verifyDepositPreauth check was found in AMMClawback.cpp, AMMWithdraw.cpp, or the paired-asset return path in 3.2.0-b7 or origin/develop.",
      "remediation": "Reject AMMClawback paired-asset returns that would deliver an IOU to a DepositAuth holder without authorization, unless a governed amendment defines different AMM-clawback semantics.",
      "script": "repros/AMMCLAWBACK-DEPOSITAUTH-PAIRED-ASSET-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "ammclawback-depositauth-paired-asset-001",
      "required_amendments": [
        "AMM",
        "AMMClawback",
        "DepositAuth",
        "fixAMMClawbackRounding"
      ]
    },
    {
      "id": "AMMBID-DEPOSITAUTH-REFUND-001",
      "title": "AMMBid auction refund bypasses holder DepositAuth",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMMBid LP-token auction refund and DepositAuth receive policy",
      "exploit_type": "Holder receive-policy bypass",
      "markers": [
        "AMM current \u2014 Bid refund bypasses DepositAuth"
      ],
      "broken": "A previous AMM auction-slot owner can set asfDepositAuth and direct LP-token Payment to that account is rejected with tecNO_PERMISSION, but a later AMMBid by another account can still refund LP tokens to the previous owner through AMMBid::applyBid/accountSend.",
      "expected": "AMMBid should enforce the previous owner's DepositAuth receive policy before refunding LP tokens to a non-signing account, or require an explicit authorization path.",
      "source_signal": "Source review of AMMBid::applyBid shows the previous-owner refund routes through accountSend without a DepositAuth / verifyDepositPreauth check; no matching check was found in 3.2.0-b7 or origin/develop.",
      "remediation": "Reject AMMBid auction refunds that would deliver LP tokens to a DepositAuth account without authorization, unless a governed amendment defines different AMM auction-refund semantics.",
      "script": "repros/AMMBID-DEPOSITAUTH-REFUND-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "ammbid-depositauth-refund-001",
      "required_amendments": [
        "AMM",
        "DepositAuth",
        "fixAMMv1_3"
      ]
    },
    {
      "id": "ESCROW-CANCEL-IOU-001",
      "title": "EscrowCancel deleted IOU trustline exception",
      "category": "Current transaction-path",
      "level": "High",
      "score": 8.1,
      "affected": "rippled 3.1.3 token escrow cancellation",
      "exploit_type": "Deterministic transaction exception",
      "markers": [
        "EscrowCancel current \u2014 deleted IOU trustline returns tefEXCEPTION"
      ],
      "broken": "Canceling an IOU escrow after the sender trustline was deleted returns tefEXCEPTION / OwnerCount template-field error.",
      "expected": "Escrow cancellation accounting should not depend on a deleted sender trustline.",
      "source_signal": "Later upstream commit ad3d172a1 / PR #6171.",
      "remediation": "Switch token escrow cancellation accounting to the account ledger entry rather than the deleted trustline.",
      "script": "repros/ESCROW-CANCEL-IOU-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "escrow-cancel-iou-001",
      "required_amendments": [
        "TokenEscrow",
        "fixTokenEscrowV1"
      ]
    },
    {
      "id": "AMM-STALE-AUTH-001",
      "title": "AMM stale AuthAccounts after empty reinit",
      "category": "Current transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMM empty-pool reinitialization",
      "exploit_type": "Stale authorization state",
      "markers": [
        "AMM current \u2014 stale AuthAccounts survive empty reinit"
      ],
      "broken": "Empty-pool reinitialization with tfTwoAssetIfEmpty leaves stale sfAuthAccounts from the prior auction slot.",
      "expected": "Reinitializing an empty AMM should clear stale auction authorization state.",
      "source_signal": "Later upstream commit e1fe35993 / PR #6996.",
      "remediation": "Clear AuthAccounts during empty-pool AMM reinitialization.",
      "script": "repros/AMM-STALE-AUTH-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "amm-stale-auth-001",
      "required_amendments": [
        "AMM",
        "fixAMMv1_3"
      ]
    },
    {
      "id": "MPT-NONCANONICAL-AMOUNT-001",
      "title": "Non-canonical MPT amount reaches ledger engine",
      "category": "Current transaction-path",
      "level": "High",
      "score": 7.6,
      "affected": "rippled 3.1.3 MPT amount validation",
      "exploit_type": "Malformed amount accepted into application path",
      "markers": [
        "MPT current \u2014 non-canonical amount reaches ledger engine"
      ],
      "broken": "A non-canonical MPT amount reaches transaction application and returns fee-burning tecPATH_PARTIAL instead of failing preflight as temBAD_AMOUNT.",
      "expected": "Non-canonical MPT amounts should fail before ledger application.",
      "source_signal": "Later upstream commit dcd2ff0b5 / PR #7117.",
      "remediation": "Reject non-canonical MPT amounts during preflight/preclaim before fee-burning application.",
      "script": "repros/MPT-NONCANONICAL-AMOUNT-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "mpt-noncanonical-amount-001",
      "required_amendments": [
        "MPTokensV1",
        "fixMPTDeliveredAmount"
      ]
    },
    {
      "id": "MPT-TRANSFER-RATE-OVERFLOW-001",
      "title": "MPT transfer-rate scaling overflow",
      "category": "Current helper/accounting",
      "level": "High",
      "score": 7.4,
      "affected": "rippled 3.1.3 MPT transfer-rate helper",
      "exploit_type": "Arithmetic overflow",
      "markers": [
        "MPT current \u2014 transfer-rate scaling overflows large integral amount"
      ],
      "broken": "Applying a 1.5 transfer rate to a large integral MPT amount throws overflow_error in the legacy scaled-mantissa path.",
      "expected": "Valid integral-token amounts should scale through bounded consensus arithmetic or fail cleanly before overflow.",
      "source_signal": "Later upstream commit 22fbf4d06.",
      "remediation": "Route MPT/V2 transfer-rate math through Number arithmetic.",
      "script": "repros/MPT-TRANSFER-RATE-OVERFLOW-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "mpt-transfer-rate-overflow-001",
      "required_amendments": [
        "MPTokensV1"
      ]
    },
    {
      "id": "PDEX-HYBRID-QUALITY-001",
      "title": "Permissioned-DEX hybrid-offer quality mismatch",
      "category": "Current transaction-path",
      "level": "High",
      "score": 7.7,
      "affected": "rippled 3.1.3 permissioned DEX hybrid offers",
      "exploit_type": "Order-book metadata corruption",
      "markers": [
        "Permissioned DEX current \u2014 hybrid offer open-book quality mismatch"
      ],
      "broken": "A partially crossed hybrid offer leaves its open-book directory key at one quality while sfExchangeRate records another.",
      "expected": "Open-book directory key quality and sfExchangeRate metadata must match after partial crossing.",
      "source_signal": "Later upstream commit 28cc20c81 / PR #7087.",
      "remediation": "Use the correct open-book placement rate and repair existing bad sfExchangeRate metadata.",
      "script": "repros/PDEX-HYBRID-QUALITY-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "pdex-hybrid-quality-001",
      "required_amendments": [
        "PermissionedDEX",
        "PermissionedDomains",
        "Credentials"
      ]
    },
    {
      "id": "PDEX-CANCEL-INVARIANT-001",
      "title": "Permissioned-DEX regular-offer cancel invariant failure",
      "category": "Current transaction-path",
      "level": "High",
      "score": 7.5,
      "affected": "rippled 3.1.3 permissioned DEX OfferCreate",
      "exploit_type": "Valid transaction invariant failure",
      "markers": [
        "Permissioned DEX current \u2014 cancel regular offer via domain offer invariant"
      ],
      "broken": "A valid domain OfferCreate that cancels the user regular offer fails with tecINVARIANT_FAILED because the invariant treats the deleted regular offer as forbidden mutation.",
      "expected": "The invariant should ignore regular offers deleted as part of a valid domain offer cancellation.",
      "source_signal": "Later upstream commit 8c0080020 / PR #7118.",
      "remediation": "Update the permissioned-DEX invariant to ignore deleted regular offers in this path.",
      "script": "repros/PDEX-CANCEL-INVARIANT-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "pdex-cancel-invariant-001",
      "required_amendments": [
        "PermissionedDEX",
        "PermissionedDomains",
        "Credentials"
      ]
    },
    {
      "id": "NFTOKEN-OFFER-ISSUER-SELF-FREEZE-001",
      "title": "NFTokenCreateOffer issuer-self GlobalFreeze blocks own-currency offer",
      "category": "Current baseline transaction-path",
      "level": "Medium",
      "score": 6.0,
      "affected": "rippled 3.1.3 NFToken offer-creation freeze enforcement",
      "exploit_type": "Issuer-exemption gap (protocol-spec violation, liveness)",
      "markers": [
        "NFToken current \u2014 issuer NFTokenCreateOffer blocked by own GlobalFreeze"
      ],
      "broken": "When an account is both the IOU issuer and an NFT offer creator and has lsfGlobalFreeze set on itself, NFTokenUtils.cpp:941 returns tecFROZEN even though the protocol generally exempts issuers from their own freeze when transacting in their own currency (cf. CashCheck.cpp:176 'An issuer can always accept their own currency'; DirectStep.cpp:906 'pure issue/redeem can't be frozen').",
      "expected": "The isFrozen call inside tokenOfferCreatePreclaim should skip the freeze check when acctID == amount.getIssuer(), mirroring the established issuer-exemption pattern.",
      "source_signal": "Static review of NFTokenUtils.cpp:tokenOfferCreatePreclaim at 46b241ace8b30d9c9775d60ffba7d24b21903896; PR #6113 addresses the same class of issuer-exemption for Payment.cpp / LoanBrokerCoverDeposit / VaultWithdraw but does not touch NFTokenUtils.",
      "remediation": "Add an issuer-exemption guard above the line-941 isFrozen call: skip the freeze check when acctID == amount.getIssuer(). Apply the same fix at the analogous line-927 site. Gate under a new fix amendment (e.g. fixNFTokenIssuerSelfFreeze) or fold into a broader fixIssuerSelfFreeze covering CashCheck-style sites.",
      "script": "repros/NFTOKEN-OFFER-ISSUER-SELF-FREEZE-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "nftoken-offer-issuer-self-freeze-001",
      "required_amendments": [
        "NonFungibleTokensV1_1",
        "NFTokenMintOffer"
      ]
    },
    {
      "id": "AMMBID-DISALLOW-INCOMING-REFUND-001",
      "title": "AMMBid auction refund bypasses recipient DisallowIncomingTrustline",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 AMMBid LP-token auction refund and DisallowIncoming receive policy",
      "exploit_type": "Holder receive-policy bypass",
      "markers": [
        "AMM current \u2014 Bid refund bypasses DisallowIncomingTrustline"
      ],
      "broken": "When a previous AMM auction-slot owner has closed their LP trustline and subsequently set asfDisallowIncomingTrustline, a later AMMBid by another account refunds LP tokens to that previous owner via accountSend, which routes through rippleCreditIOU -> trustCreate. trustCreate does not check lsfDisallowIncomingTrustline, so a new LP trustline is created on the unwilling recipient.",
      "expected": "AMMBid should reject refunds that would force creation of a new trustline on a destination that has DisallowIncomingTrustline set, or burn the would-be refund and emit a metadata note.",
      "source_signal": "Source review of AMMBid::doApply:351 at 46b241ace8b30d9c9775d60ffba7d24b21903896; trustCreate at View.cpp:1733-1782 has zero references to lsfDisallowIncomingTrustline; SetTrust.cpp:253 is the only enforcement site.",
      "remediation": "Either gate the accountSend refund through a wrapper that respects lsfDisallowIncomingTrustline, or pre-check the destination's existing trustline before refund. Fold into fixDisallowIncomingV1_1 amendment.",
      "script": "repros/AMMBID-DISALLOW-INCOMING-REFUND-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "ammbid-disallow-incoming-refund-001",
      "required_amendments": [
        "AMM",
        "DisallowIncoming",
        "fixDisallowIncomingV1"
      ],
      "required_disabled_amendments": [
        "fixDisallowIncomingV1_1"
      ]
    },
    {
      "id": "NFTOKEN-ACCEPT-DEPOSITAUTH-001",
      "title": "NFTokenAcceptOffer bypasses recipient lsfDepositAuth",
      "category": "Current baseline transaction-path",
      "level": "High",
      "score": 8.0,
      "affected": "rippled 3.1.3 NFTokenAcceptOffer IOU payment to seller and DepositAuth receive policy",
      "exploit_type": "Holder receive-policy bypass",
      "markers": [
        "NFToken current \u2014 AcceptOffer bypasses DepositAuth"
      ],
      "broken": "NFTokenAcceptOffer::pay at NFTokenAcceptOffer.cpp:414 delivers the buyer's IOU payment to the seller via accountSend with no verifyDepositPreauth call. A seller who set asfDepositAuth still receives the buyer's payment as a side-effect of NFT settlement.",
      "expected": "NFTokenAcceptOffer::pay should call verifyDepositPreauth(tx, view, from, to, sleTo, j) and return tecNO_PERMISSION when the destination has lsfDepositAuth set and the sender is not preauthorized.",
      "source_signal": "Source review of NFTokenAcceptOffer.cpp and NFTokenUtils.cpp at 46b241ace8b30d9c9775d60ffba7d24b21903896; exhaustive grep returns zero DepositAuth references; checkTrustlineAuthorized (RequireAuth) and checkTrustlineDeepFrozen exist but no verifyDepositPreauth.",
      "remediation": "Add verifyDepositPreauth check inside NFTokenAcceptOffer::pay before accountSend, or push the check down into accountSend/rippleCreditIOU itself. Gate under a fix amendment.",
      "script": "repros/NFTOKEN-ACCEPT-DEPOSITAUTH-001.sh",
      "proof_log": "runs/20260527-p0-hunt/live_mainnet_enabled_proof_extract_20260527_v23.log",
      "proof_log_sha256": "3ad276376bc6a04b7ddc335144d57d9297fedb9a09022af57095967efa939769",
      "source": "OpenP0Repro_test.cpp",
      "anchor": "nftoken-accept-depositauth-001",
      "required_amendments": [
        "NonFungibleTokensV1_1",
        "DepositAuth"
      ]
    }
  ],
  "live_scope": {
    "source": "direct XRPL public JSON-RPC feature and ledger_entry methods",
    "checked_utc": "2026-05-28T10:28:36Z",
    "receipt": "direct_xrpl_amendment_status_20260527.json",
    "inclusion_rule": "Packet includes only reproduced high/critical findings whose required XRPL mainnet amendment surfaces are enabled in the direct validated-ledger amendment receipt and whose disabled-surface dependencies are excluded.",
    "included_ids": [
      "MPT-LOCK-UNAUTH-001",
      "TRUSTLINE-POSITIVE-BALANCE-RESERVE-001",
      "TRUSTLINE-DISALLOW-INCOMING-OFFER-001",
      "NFTOKEN-DISALLOW-INCOMING-ACCEPT-001",
      "NFTOKEN-BROKER-FEE-DISALLOW-INCOMING-TRUSTLINE-001",
      "CHECKCASH-DISALLOW-INCOMING-TRUSTLINE-001",
      "TOKENESCROW-DISALLOW-INCOMING-FINISH-001",
      "AMMWITHDRAW-DISALLOW-INCOMING-TRUSTLINE-001",
      "AMMCREATE-DISALLOW-INCOMING-TRUSTLINE-001",
      "AMMDEPOSIT-EMPTY-DISALLOW-INCOMING-TRUSTLINE-001",
      "AMMCLAWBACK-DISALLOW-INCOMING-PAIRED-ASSET-001",
      "AMMCLAWBACK-DEPOSITAUTH-PAIRED-ASSET-001",
      "AMMBID-DEPOSITAUTH-REFUND-001",
      "ESCROW-CANCEL-IOU-001",
      "AMM-STALE-AUTH-001",
      "MPT-NONCANONICAL-AMOUNT-001",
      "MPT-TRANSFER-RATE-OVERFLOW-001",
      "PDEX-HYBRID-QUALITY-001",
      "PDEX-CANCEL-INVARIANT-001",
      "NFTOKEN-OFFER-ISSUER-SELF-FREEZE-001",
      "AMMBID-DISALLOW-INCOMING-REFUND-001",
      "NFTOKEN-ACCEPT-DEPOSITAUTH-001"
    ],
    "excluded_by_live_dependency": [
      {
        "id": "MPT-DOMAIN-AUTH-001",
        "reason": "The reproduced MPT DomainID path requires featureSingleAssetVault in MPTokenIssuanceCreate/Set::checkExtraFeatures; direct XRPL mainnet status shows SingleAssetVault disabled."
      }
    ]
  },
  "upstream_remediation_scope": {
    "source": "local XRPLF/rippled git ancestry after fetch --all --tags --prune",
    "checked_utc": "2026-05-27T18:41:13Z",
    "receipt": "upstream_remediation_status_20260527.json",
    "patched_in_3_2_0_b7_or_develop": [
      "ESCROW-CANCEL-IOU-001",
      "AMM-STALE-AUTH-001",
      "MPT-NONCANONICAL-AMOUNT-001",
      "PDEX-HYBRID-QUALITY-001",
      "PDEX-CANCEL-INVARIANT-001"
    ],
    "not_confirmed_fixed_in_3_2_0_b7_or_develop": [
      "MPT-TRANSFER-RATE-OVERFLOW-001",
      "MPT-LOCK-UNAUTH-001",
      "TRUSTLINE-POSITIVE-BALANCE-RESERVE-001",
      "TRUSTLINE-DISALLOW-INCOMING-OFFER-001",
      "NFTOKEN-DISALLOW-INCOMING-ACCEPT-001",
      "NFTOKEN-BROKER-FEE-DISALLOW-INCOMING-TRUSTLINE-001",
      "CHECKCASH-DISALLOW-INCOMING-TRUSTLINE-001",
      "TOKENESCROW-DISALLOW-INCOMING-FINISH-001",
      "AMMWITHDRAW-DISALLOW-INCOMING-TRUSTLINE-001",
      "AMMCREATE-DISALLOW-INCOMING-TRUSTLINE-001",
      "AMMDEPOSIT-EMPTY-DISALLOW-INCOMING-TRUSTLINE-001",
      "AMMCLAWBACK-DISALLOW-INCOMING-PAIRED-ASSET-001",
      "AMMCLAWBACK-DEPOSITAUTH-PAIRED-ASSET-001",
      "AMMBID-DEPOSITAUTH-REFUND-001",
      "NFTOKEN-OFFER-ISSUER-SELF-FREEZE-001",
      "AMMBID-DISALLOW-INCOMING-REFUND-001",
      "NFTOKEN-ACCEPT-DEPOSITAUTH-001"
    ]
  }
}
