fix(security): validate account names to prevent Numscript injection (EN-1200)#106
fix(security): validate account names to prevent Numscript injection (EN-1200)#106flemzord wants to merge 3 commits into
Conversation
The credit/debit Numscript templates interpolate account names directly
into the script body (@{{ .source }}). WALLET subjects only checked that
the identifier was non-empty, and the balance-name regex was unanchored
(MatchString matches any substring), so a crafted identifier/balance/name
containing newlines or Numscript tokens could break out of the source
block and inject arbitrary statements executed with the service ledger
credentials.
- Anchor balanceNameRegex (^...$) so a name must be a single clean segment
- Validate WALLET subject identifier and balance via accounts.ValidateAddress
- Validate walletID before building debit/credit scripts
Adds regression tests for separator/whitespace/token names and for
injection via a WALLET source identifier and balance.
|
Warning Review limit reached
More reviews will be available in 58 minutes and 40 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
flemzord
left a comment
There was a problem hiding this comment.
Revue inline: j’ai relevé deux trous de validation restants dans le durcissement des segments de compte.
…nces Address review feedback: accounts.ValidateAddress accepts a full multi-segment address, so a WALLET subject with identifier/balance like "foo:bar" passed and resolved to a nested account outside the balance model. Validate wallet IDs and balance names against an anchored single-segment regex instead, keeping ValidateAddress only for ACCOUNT subjects. Documents the residual dash-stripping collision in Address.String() (kept as a separate, migration-bearing change rather than forbidding dashes, which would break legitimate dashed/UUID balance names).
🛑 Changes requested — multi-model reviewThe PR correctly anchors the balance name regex and adds injection validation for wallet subjects and wallet IDs, closing the primary Numscript injection vectors. However, two major issues require attention before merging. First, removing '-' from valid balance names goes beyond the stated anchoring fix: the dash character is not itself a Numscript injection vector, and the change silently breaks debit operations for any pre-existing wallet whose balances have dashed names (since names are re-validated against the new regex when read back from the ledger). This is a backwards-incompatible behavioral change that needs either a rollback to permitting dashes or a migration/compatibility plan. Second, WalletID validation for the Credit path is performed inside the manager method rather than inside Credit.Validate(), creating an inconsistency with the Debit path and a potential bypass for non-manager callers. Additionally, there are no handler-level tests exercising malicious walletIDs supplied via URL parameters, and the stated rationale for dash exclusion (Address.String() aliasing) lacks a confirming test or code reference. 🟠 [major] Removing '-' from balance names breaks backwards compatibility with existing data
The regex change from Suggestion: Either keep '-' in the allowed character set while still anchoring the regex (i.e. 🟠 [major] WalletID validation for Credit lives outside Validate(), making it bypassable
The WalletID validation in the Credit flow is performed directly inside Manager.Credit via accountSegmentRegexp rather than inside Credit.Validate(). This means any other caller of Credit.Validate() (e.g. tests or future handlers) will not have the WalletID checked. This is an inconsistency with how Debit.Validate() handles its WalletID, and represents a potential bypass vector if the manager method is not the sole entry point in the future. Suggestion: Move the 🟡 [minor] No handler-level tests for malicious URL wallet IDs
The new tests cover balance names and WALLET subject identifiers, but there are no handler tests that exercise a malicious walletID coming in via the URL path (e.g. Suggestion: Add credit and debit handler tests using URL walletIDs containing account separators or Numscript tokens (e.g. 🟡 [minor] Dash-exclusion rationale for balance names is unverified and should have a confirming test
Subject.Validate() enforces stricter balance-name rules (no '-') compared to wallet identifiers, with the justification being that Address.String() strips dashes and causes aliasing. However, there is no test or code reference that actually demonstrates this aliasing behavior. Without such evidence, the stricter rule appears arbitrary and the security rationale is unverifiable. Suggestion: Add a test demonstrating that Chart().GetBalanceAccount with a dashed name aliases to the undashed account, or add a code comment citing the specific ledger Address.String() behavior that causes the aliasing concern. ⚪ [nit] Mock transaction creator returns nil,nil which could mask nil-deref panics
In TestWalletsDebitRejectsInvalidBalanceMetadata, the WithCreateTransaction mock returns (nil, nil). If the validation check is ever accidentally removed and the code proceeds to use the nil transaction result, the test would panic rather than produce a clean assertion failure, potentially masking regressions. Suggestion: Return a minimal non-nil Reviewed in parallel by claude (anthropic/claude-opus-4-8) and gpt (openai/gpt-5.5), then consolidated. This comment is updated on each push. |
Problem (Critical + High — C1, H2)
The credit/debit Numscript templates interpolate account names directly into the script body (
@{{ .source }}). But:type:"WALLET"subjects only checkedIdentifier != ""(neither the content norBalancewas validated);balanceNameRegexwas unanchored ([0-9A-Za-z_-]+), soMatchStringaccepted any string containing at least one valid character (e.g.balance:injected,x\n@world).An
identifier/balance/balance-name containing a newline and Numscript tokens could close thesourceblock and inject arbitrary statements executed with the service's ledger credentials (e.g.send [USD *] (source = @world destination = @attacker:main)).Fix
balanceNameRegex→^[0-9A-Za-z_-]+$(a name must be a single clean segment).identifierandbalanceof WALLET subjects viaaccounts.ValidateAddress(the same guarantee the already-safe ACCOUNT branch relies on).walletIDbefore building debit/credit scripts.Possible follow-up defense-in-depth: bind sources as
account $sourceNvariables instead of interpolating them — left out here to avoid destabilizing the exact-Numscript assertions in the existing tests.Tests
TestBalancesCreate: names with an account separator and with whitespace + Numscript tokens → 400.TestWalletsCredit: injection via thebalanceand via theidentifierof a WALLET subject → 400.From the in-depth repository review.