Repository navigation
feat(sdk-coin-xlm): report full setOptions fields and unmodelled ops in explain - #10095
prajwalu142 wants to merge 1 commit into
Conversation
|
|
|
@claude review this pr for regression, backwards compatibility and edge cases |
|
Claude finished @prajwalu142's task in 30s —— View job Review: regression, backwards compatibility, edge casesI read the diff in Regression
Backwards compatibility
Edge cases
TestsThe PR covers each new field, the unset case, Summary: safe and additive. The only behavior change is the new |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟣
modules/sdk-coin-xlm/src/xlm.ts— pre-existing, security-relevant: Reviewers still cannot see two of the three trustline flags a setTrustLineFlags op changes, so a full deauthorization can show as "not touched". The authorizeTrustline entry at xlm.ts:1107-1112 copies onlyflags?.authorizedand dropsauthorizedToMaintainLiabilitiesandclawbackEnabled. Fix: report every flag the op sets or clears (true, false or undefined each), and update the new comment, which claims undefinedauthorizedmeans no revoke. Clearing maintain-liabilities on a trustline in that state deauthorizes it whileauthorizedstays undefined. [also at: modules/sdk-coin-xlm/src/xlm.ts:151 - Pre-existing, security-relevant: an issuer reviewing an authorizeTrustline explanation still cannot see a setTrustLineFlags op that clears clawbackEnabled or changes authorizedToMaintainLiabilities.]Why this was flagged
Trigger: a txBase64 holding stellar.Operation.setTrustLineFlags({ trustor, asset, flags: { authorizedToMaintainLiabilities: false } }) is passed to explainTransaction. When the trustline is in the authorized-to-maintain-liabilities state, that op fully deauthorizes it. The explain loop at xlm.ts:1107-1112 sets only
authorized: setTrustLineFlagsOp.flags?.authorized. The entry therefore showsauthorizedundefined, and the new interface comment at xlm.ts:150-151 tells consumers that undefined "must not be read as a revoke". A second case: flags { authorized: false, authorizedToMaintainLiabilities: true } shows asauthorized: false, a full revoke, when the real effect is a downgrade to maintain-liabilities. ClearingclawbackEnabledis likewise never shown. The base branch has the same omission. verifyAuthorizeTrustlineOperations at xlm.ts:1328-1333 also checks onlyauthorized, so verification does not catch these flags either.Verification: The explain loop at modules/sdk-coin-xlm/src/xlm.ts:1107-1112 pushes only
type,coin,trustorandauthorized: setTrustLineFlagsOp.flags?.authorized. The other two flags are never copied, and the TransactionOperation interface (xlm.ts:147-152) has no field for them. The base has the same mapping (base xlm.ts:1089-1094, sameauthorized: setTrustLineFlagsOp.flags?.authorized).
| coin: this.getChain(), | ||
| setFlags: setOptionsOp.setFlags, | ||
| clearFlags: setOptionsOp.clearFlags, | ||
| masterWeight: setOptionsOp.masterWeight, | ||
| lowThreshold: setOptionsOp.lowThreshold, | ||
| medThreshold: setOptionsOp.medThreshold, | ||
| highThreshold: setOptionsOp.highThreshold, | ||
| homeDomain: setOptionsOp.homeDomain, | ||
| inflationDest: setOptionsOp.inflationDest, | ||
| }); |
There was a problem hiding this comment.
🟣 pre-existing, not blocking: pre-existing, security-relevant: A reviewer still sees no sign of a setOptions that adds a signer, which is the most dangerous account change. The accountConfig entry at xlm.ts:1093-1104 omits setOptionsOp.signer. A signer-only op shows up as accountConfig with every field undefined, which looks like a no-op. Fix: report signer (key and weight, with weight 0 meaning removal) on accountConfig entries. The PR says this omission is intentional, but the stated goal is that no signed account change is hidden.
A small fix can ride a push you are already making; otherwise a short reply is enough.
Why this was flagged
Trigger: a txBase64 holding stellar.Operation.setOptions({ signer: { ed25519PublicKey: attacker, weight: 3 } }) is passed to explainTransaction. The setOptions branch at xlm.ts:1091-1104 copies setFlags, clearFlags, masterWeight, the three thresholds, homeDomain and inflationDest, but never signer. The result is { type: 'accountConfig', coin } with every other field undefined. verifyAccountConfigOperations (xlm.ts:1269-1281) lists 'signer' among the fields that matter, citing signer:{attacker,weight:3}, but that check only runs on the verify path, not for explain consumers such as offline vault review. The base behaves the same way. Still, this PR extends the setOptions output so that a reviewer can see every account change, and it leaves out the field that hands over control of the account. The PR description calls the omission intentional but gives no mitigation for explain readers.
Verification: Trigger: a setOptions op that sets only signer is passed to explainTransaction. The setOptions branch at modules/sdk-coin-xlm/src/xlm.ts:1091-1104 never reads setOptionsOp.signer. So a signer-only op comes out as { type: 'accountConfig', coin }, which looks like a no-op. The 'signer' guard in verifyAccountConfigOperations only runs in verifyTransaction. The base had the same omission.
There was a problem hiding this comment.
Addressed in 416b1f5. accountConfig entries now carry signer as { type, key, weight } (ed25519 public key, sha256 hash, pre-auth tx and signed payload signers). Weight 0 is kept and means removal, so a signer-only setOptions no longer reads as an empty entry. Tests cover add, removal, hex-encoded hash and pre-auth signers, and the unset case.
| coin: this.getChain(), | ||
| setFlags: setOptionsOp.setFlags, | ||
| clearFlags: setOptionsOp.clearFlags, | ||
| masterWeight: setOptionsOp.masterWeight, | ||
| lowThreshold: setOptionsOp.lowThreshold, | ||
| medThreshold: setOptionsOp.medThreshold, | ||
| highThreshold: setOptionsOp.highThreshold, | ||
| homeDomain: setOptionsOp.homeDomain, | ||
| inflationDest: setOptionsOp.inflationDest, | ||
| }); | ||
| } else if (op.type === 'setTrustLineFlags') { | ||
| const setTrustLineFlagsOp = op as stellar.Operation.SetTrustLineFlags; |
There was a problem hiding this comment.
🟣 pre-existing, not blocking: pre-existing, security-relevant: Reviewers cannot tell when any operation runs against a different account, because no explained operation reports its op-level source. The explain loop at xlm.ts:1047-1124 ignores op.source for setOptions, setTrustLineFlags, clawback, changeTrust, payment and createAccount. A setOptions with source set to another wallet looks the same as one for the tx source. Fix: add op.source (undefined when unset) to every operations entry and output the loop builds, including the new unsupported entries. The verify path already rejects source on setOptions.
A small fix can ride a push you are already making; otherwise a short reply is enough.
Why this was flagged
Trigger: a transaction whose setOptions or clawback op carries source: otherAccount, for example stellar.Operation.setOptions({ source: victim, masterWeight: 0 }), is passed to explainTransaction. Each branch of the forEach at xlm.ts:1047-1125 builds its entry only from op-specific fields, and none reads op.source. The explanation therefore shows masterWeight: 0 as if it applied to the transaction's own account. verifyAccountConfigOperations (xlm.ts:1263-1281) names 'source' as dangerous, saying an op redirected to another account runs under that account's thresholds. That check only runs on the verify path and only for setOptions. The base omits source in the same way. This PR's stated aim is that a reviewer sees everything a transaction does, and it adds fields to these same entries without this one.
Verification: The trigger is a txBase64 whose op carries an op-level source for another account, passed to explainTransaction. No branch of the loop in modules/sdk-coin-xlm/src/xlm.ts (lines 1047-1126) reads op.source. The base explain loop also never reported source. The source check in verifyAccountConfigOperations (1269-1277) covers only setOptions on the verify path, not explain.
There was a problem hiding this comment.
Addressed in 416b1f5. Every operations entry (including unsupported) and every outputs entry now carries source, set only when the operation names its own source account and undefined otherwise. Tests cover an op-level source on each operation kind and its absence.
…in explain
Problem/goal:
explainTransaction for XLM reported only setFlags, clearFlags and
lowThreshold for a setOptions operation and silently omitted every
other operation type it does not model. A reviewer reading the
explanation, for example in offline vault console, could therefore
sign a transaction that adds a signer, sets thresholds, a master
weight, a home domain or an inflation destination, that runs an
operation against another account, or that carries an extra
operation, without seeing it. authorizeTrustline entries also
reported only the authorized flag, so a change through
authorizedToMaintainLiabilities or clawbackEnabled was invisible, and
an undefined authorized was not distinguishable from a revoke.
Implementation details:
- accountConfig operations now also carry masterWeight, medThreshold,
highThreshold, homeDomain, inflationDest and signer. Each is
undefined when the operation does not set it, and a masterWeight or
signer weight of 0 is kept (weight 0 means signer removal). A signer
is reported as { type, key, weight } for ed25519 public key, sha256
hash, pre-auth transaction and signed payload signers.
- authorizeTrustline operations now carry authorized,
authorizedToMaintainLiabilities and clawbackEnabled. Each is true
when the operation sets the flag, false when it clears it and
undefined when it does not touch it.
- clawback operations now carry assetCode and assetIssuer so consumers
do not parse the coin name to label the amount.
- Every operations entry and every output now carries source, set only
when the operation names its own source account, so an operation that
runs against another account is visible.
- An operation type that explain does not model now yields an entry of
type 'unsupported' with the stellar operationType, instead of being
dropped. Payment and createAccount remain reported through outputs
and changeTrust through operations.
- All additions are optional fields, so existing consumers keep
working; consumers that assume every operations entry is one of the
known types will now also see 'unsupported' entries.
Verification:
Added unit tests in modules/sdk-coin-xlm/test/unit/xlm.ts covering each
new setOptions field, unset fields, signer variants and removal, the
three trustline flags, the clawback asset fields, operation source on
every operation kind and its absence, the unsupported entry (including
that payment and changeTrust do not produce one) and operation order
for a mixed transaction. The explain tests pass. The full module suite
shows the same 7 failures with and without the change, caused by a
stale locally built sdk-core, and tsc reports the same errors as
before. Prettier and eslint are clean.
Ticket: CSHLD-2079
2133114 to
416b1f5
Compare
|
Addressed in 416b1f5:
|
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
Description
explainTransactionfor XLM reported onlysetFlags,clearFlagsandlowThresholdfor asetOptionsoperation, and silently left out any operation type it does not model. A reviewer reading the explanation (for example in offline vault console) could sign a transaction that adds a signer, sets thresholds, a master weight, a home domain or an inflation destination, that runs an operation against another account, or that carries an extra operation, without seeing it.Changes in
modules/sdk-coin-xlm:accountConfigoperations also carrymasterWeight,medThreshold,highThreshold,homeDomain,inflationDestandsigner. Each isundefinedwhen the operation does not set it. AmasterWeightor signerweightof0is kept (weight0means signer removal). A signer is reported as{ type, key, weight }for ed25519 public key, sha256 hash, pre-auth transaction and signed payload signers.authorizeTrustlineoperations carryauthorized,authorizedToMaintainLiabilitiesandclawbackEnabled. Each istruewhen the operation sets the flag,falsewhen it clears it andundefinedwhen it does not touch it.clawbackoperations carryassetCodeandassetIssuer, so consumers do not parse the coin name to label the amount.operationsentry and everyoutputsentry carriessource, set only when the operation names its own source account, so an operation that runs against another account is visible.unsupportedwith the stellaroperationType, instead of being dropped. Payments and account creation are still reported throughoutputs, andchangeTrustthroughoperations.All additions are optional fields. A consumer that assumes every
operationsentry is one of the known types will now also seeunsupportedentries, and strict deep-equality on explain output will see the new optional keys.Issue Number
CSHLD-2079
Type of change
How Has This Been Tested?
Unit tests added in
modules/sdk-coin-xlm/test/unit/xlm.tsfor each newsetOptionsfield, unset fields, signer variants and removal, the three trustline flags, the clawback asset fields, operationsourceon every operation kind and its absence, theunsupportedentry (including that payment andchangeTrustdo not produce one), and operation order for a mixed transaction. The explain tests pass. The rest of the module suite andtscgive the same results with and without the change; prettier and eslint are clean.Checklist: