OpenZeppelin / OpenZeppelin/stellar-contracts
`weighted_threshold::install` does not validate that weighted addresses are rule signers, allowing an unreachable threshold to pass install-time validation
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 95
- Forks
- 68
- Avg merge
- 4d 42m
- Merged PRs (30d)
- 20
Description
Version: v0.7.2 · Package: packages/accounts
Summary
weighted_threshold::install validates threshold <= sum(signer_weights), but never checks that each Signer key in signer_weights belongs to the context_rule's signer set. Because enforce only counts the weights of signers that actually authenticate through the rule, weights assigned to non-rule signers inflate the install-time total while contributing nothing at enforcement. A threshold that passes install can therefore be permanently unreachable, silently bricking the context rule from construction.
Affected code (packages/accounts/src/policies/weighted_threshold.rs, v0.7.2)
install accepts the caller's signer_weights map and validates the threshold against the sum of all its values:
let total_weight = calculate_total_weight(e, ¶ms.signer_weights);
if params.threshold == 0 || params.threshold > total_weight {
panic_with_error!(e, WeightedThresholdError::InvalidThreshold);
}
calculate_total_weight sums every value in the map, with no reference to context_rule.signers:
fn calculate_total_weight(e: &Env, signer_weights: &Map<Signer, u32>) -> u32 {
let mut total_weight: u32 = 0;
for weight in signer_weights.values() { total_weight = total_weight.checked_add(weight)... }
total_weight
}
At enforcement, calculate_weight only credits weights for signers passed in authenticated_signers (constrained to the rule's signers by do_check_auth):
for signer in signers.iter() {
if let Some(weight) = signer_weights.get(signer.clone()) { total_weight = total_weight.checked_add(weight)... }
}
install receives the fully-populated context_rule (see smart_account/storage.rs::add_context_rule, which builds context_rule with signers before calling PolicyClient::install(¶m, &context_rule, ...)), so the signer set needed to validate is already in scope — it is simply not consulted.
Impact
- Configuration-time lockout: a rule can be created whose weighted-threshold policy can never be satisfied. Availability, not fund loss; it requires the account owner to misconfigure at setup, but the current
InvalidThresholdcheck gives false assurance that a passing install is reachable. - Reachable via the public constructor /
add_context_rulepath.
Reproduction (concept)
- Create a context rule with signers
{A, B}. - Install
weighted_thresholdwithsigner_weights = {A: 1, B: 1, X: 10}whereXis not a rule signer,threshold = 11. installpasses:sum = 12 >= 11.enforcewith any subset of{A, B}maxes at weight2 < 11→NotAllowed. The rule is permanently unusable.
Suggested fix
In install (and any setter that modifies signer_weights), reject entries whose Signer key is not present in context_rule.signers, or compute total_weight only over rule signers. The context_rule is already passed in, so the guard is local:
for signer in params.signer_weights.keys() {
if !context_rule.signers.contains(&signer) {
panic_with_error!(e, WeightedThresholdError::InvalidThreshold); // or a dedicated error
}
}
Notes
Reported as a hardening / configuration-safety gap, not a library exploit. Found while building RuleLens, an open-source configuration checker for deployed OZ Stellar smart accounts (https://github.com/bilhokista/rulelens). Happy to open a PR with the guard + a regression test if the maintainers agree the check belongs at the policy level.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/accounts/src/policies/weighted_threshold.rs and trace install alongside calculate_total_weight and calculate_weight. Read smart_account/storage.rs::add_context_rule to confirm how context_rule.signers reaches PolicyClient::install. Done means non-rule signers cannot make an unreachable threshold pass installation, with a regression test covering the A/B/X scenario.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- authorization, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100