Security: blacklist guard fails open when factory query returns an error [SEC-I03] (F-02) #456
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#456
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
When the factory
BlacklistCheckquery returns any error, both pair and router blacklist guards silently allow the trade. Inpair/src/blacklist_guard.rsandrouter/src/blacklist_guard.rsthe error arm returnsOk(None)or equivalent, permitting the operation to proceed. The current behavior is intentionally documented as a pre-1.5.0 factory compatibility shim. However, if the factory address stored in a pair or router became stale after an emergency factory migration without updating stored addresses, all blacklist protections on every swap, limit placement, and LP operation would silently become no-ops.What Was Checked
smartcontracts/contracts/pair/src/blacklist_guard.rs: error arm on factoryBlacklistCheckquery returnsOk(None)with a note referencing pre-1.5.0 factory compatibility.smartcontracts/contracts/router/src/blacklist_guard.rslines 58-69 (assert_router_swap_not_blacklisted): same fail-open pattern on factory query error.Expected (per checklist)
Factory query errors during blacklist checks are either treated as blocking (default deny) or gated on a factory version check that confirms the silent pass only applies to the documented pre-1.5.0 compatibility case, not to any factory error indiscriminately.
Actual
All factory query errors silently pass the blacklist check. A factory address mismatch or factory unavailability would disable all blacklist enforcement with no observable signal to the operator or the transaction sender.
Suggested Fix
Replace the blanket fail-open error arm with a factory version check: query factory version first; if version >= 1.5.0 and the
BlacklistCheckquery fails, treat the error as blocking. Alternatively, treat all factory query errors as blocking and document the pre-1.5.0 compatibility case as a known-acceptable regression for deployments running old factories.Verification Checklist
Cc: @PlasticDigits
mentioned in issue #452
mentioned in issue #381
mentioned in merge request !982
Picked this up. Replaced the blanket
Err(_) => Ok(None)/Ok(())fail-open in both guards (pair + router) with a version-gated decision index_common::blacklist::blacklist_query_error_blocks, which reads the factory's cw2 version from raw storage (contract_info):ContractError::BlacklistGuardUnavailable, an observable signal instead of a silent pass)One honest residual I documented in the helper: an orphaned/typo'd factory address that resolves to no contract still fails open under that last branch. I went fail-open there on purpose so genuine pre-1.5.0 factories and the unit-test doubles keep working — and on Terra Classic a stored factory pointer can't really resolve to "no contract" (contracts aren't deletable, so a bad address is only a deploy-time typo). If you want the stricter posture, it's a one-line flip of the
Nonearm to block + a test update; your call.The version probe only runs on the error path, so the normal flow pays no extra gas.
Tests: 5 new unit tests on the decision fn (current/newer factory block; pre-1.5 + both unreadable cases fail open). Blacklist integration suite 10/0 unchanged (real factory exercises the happy path, never hits the error arm). Full workspace 460/0.
MR !982, branch
qa/456-blacklist-failopen-gate, commit7cc0afb5. Needs review/merge @PlasticDigits — leaving open for verification.mentioned in commit
4940a5fcaaWe are not yet deployed on mainnet so amy references to old 1.5 factories should be deleted, we are not deployed and do not need backwards compatibility.
mentioned in commit
f5085087abmentioned in merge request !993
mentioned in commit
11c4fb4788mentioned in commit
1ce2e1800bmentioned in merge request !994
mentioned in commit
e23df4a270mentioned in merge request !999
mentioned in commit
45d7dacb1eVerification — #456 (SEC-I03 F02)
Verified on
main@4f4f4de1(post-merge of !982, !999, and default-deny follow-upf5085087).Checklist
BlacklistCheckquery updated to block (no silent fail-open)pair/src/blacklist_guard.rsandrouter/src/blacklist_guard.rsboth mapErr(_)→ContractError::BlacklistGuardUnavailable(default deny). Pre-1.5.0 fail-open shim removed per maintainer comment.cargo test factory_blacklist_query_error_blocks_swap— swap rejected with"Blacklist guard unavailable"when pair factory pointer points at a CW20 withoutBlacklistCheck.docs/security-model.md§ Trading blacklist,docs/contracts-security-audit.mdB1 row,skills/AGENTS_BLACKLIST_DECISION.mdall document fail-closed guard and link #456.cargo test blacklist_tests— 9/9 pass (wallet/token/pair dimensions, router multihop, unban restore).make test-contracts— 456 tests, 0 failures.Code pointers (merged)
probe_factory_blacklistreturnsErr(BlacklistGuardUnavailable)on query failure.assert_router_swap_not_blacklistedsame onBlacklistCheckerror."Blacklist guard unavailable: factory blacklist check failed".Follow-ups (optional, non-blocking)
factory_blacklist_query_error_blocks_swap(router currently covered by code review + wallet-blacklist multihop tests, but not the factory-query-error path).Verdict: all acceptance criteria met — closing.
mentioned in issue #337
mentioned in merge request !1001
Closed issue so not reopening — just linking the follow-up. The router-side fail-closed arm this fix added (assert_router_swap_not_blacklisted, Err(_) => BlacklistGuardUnavailable) had no test; only the pair side did (factory_blacklist_query_error_blocks_swap).
Wrote one: MR !1001 (branch qa/456-router-blacklist-guard-test, commit
30ab853c) — a mock-querier test where the factory answers the Pair query but errors on BlacklistCheck, asserting the swap is rejected with BlacklistGuardUnavailable, plus healthy and blacklisted control cases. Test-only, no behavior change; router suite 3/3, fmt + clippy clean, and I confirmed the fail-closed test fails against a fail-open guard first (pre-fix proof).Review/merge when you get to it @PlasticDigits.
mentioned in commit
14e53a7915mentioned in issue #468
mentioned in merge request !1009