Skip to content

fix(security): confused-deputy deny + reentrancy guard + tree acyclicity + config-id split (F1/F2/G/I) - #1

Merged
highskore merged 1 commit into
masterfrom
security/audit-fixes-confused-deputy
Jun 10, 2026
Merged

fix(security): confused-deputy deny + reentrancy guard + tree acyclicity + config-id split (F1/F2/G/I)#1
highskore merged 1 commit into
masterfrom
security/audit-fixes-confused-deputy

Conversation

@highskore

Copy link
Copy Markdown
Owner

Ported from rhinestonewtf/daimon#186. D excluded (invalid finding); deadline/Execute digest untouched.

F1 — confused-deputy default-deny

EnforcementLib.enforceAction now default-denies a MANDATE call whose to is an installed ROOT validator (RootStorageLib) or one of THIS mandate's own policy sigils — the sharded-state analogue of the existing to == address(this) self-call guard. A new per-mandate reverse index mandateSigils (MandateStorageLib, appended per ERC-7201 append-only) is populated via _recordSigil for every action/outcome/signature sigil in _registerMandate and cleared in _clearMandateSets.

F2 — reentrancy guard

Daimon inherits solady ReentrancyGuardTransient; executeWithSig is now nonReentrant. A nested executeWithSig would clobber an outer execution's SpendSigil transient balance snapshot. The existing ERC-1608 deadline signature is preserved.

G — expression-tree acyclicity

OmniSigilTreeLib.validateExpressionTree requires every child index to be STRICTLY LESS than its parent's own index (new NodeChildIndexNotDescending error), after the existing out-of-bounds checks. This guarantees the node graph is a DAG and bounds evaluateNode recursion to nodeCount, closing an unbounded-recursion (OOG) brick.

I — config-id split

IdLib.toMandateConfigIdtoOutcomeConfigId (keeps the "daimon.outcome" tag); new toSignatureConfigId ("daimon.signature" tag) domain-separates the ERC-1271 tier from the outcome tier, so a single address serving both tiers can never collide. Callers updated (MandateEngine signature→signature / outcome→outcome, EnforcementLib.enforce1271→signature, pre/post→outcome, plus two test helpers).

Natspec-only clarifications

SpendSigil (rolling→tumbling window + ERC-777 send/operatorSend residual), NativeValueLimitSigil (per-call bound), RateLimitConfigLib (uint32 ~2106 wrap from the block.timestamp cast), TimeFrameSigil (_check fail-open default + check1271 time-only/compose), AttestationSigil (no cumulative cap), OmniSigilTreeLib.fill (usage.used reset on ROOT re-bind), IdLib (per-mandate cap non-aggregation).

Excluded

D (subMode/mandateId in the exec digest) — reverted upstream as an invalid finding; this repo's Execute + deadline digest design is left untouched.

Verification

forge build clean · forge test 450 passed, 0 failed.

🤖 Generated with Claude Code

…ity + config-id split (F1/F2/G/I)

Ported from rhinestonewtf/daimon#186.

F1 (confused-deputy deny): EnforcementLib.enforceAction now default-denies a
MANDATE call whose `to` is an installed ROOT validator (RootStorageLib) or one
of THIS mandate's own policy sigils. Adds a per-mandate `mandateSigils` reverse
index (MandateStorageLib, ERC-7201 append-only) populated via _recordSigil in
_registerMandate and cleared in _clearMandateSets.

F2 (reentrancy): Daimon inherits solady ReentrancyGuardTransient; executeWithSig
is now nonReentrant (existing deadline signature preserved).

G (tree acyclicity): OmniSigilTreeLib.validateExpressionTree requires each child
index to be strictly less than its parent's (new NodeChildIndexNotDescending
error), guaranteeing a DAG and bounding evaluateNode recursion.

I (config-id split): IdLib.toMandateConfigId renamed to toOutcomeConfigId; new
toSignatureConfigId ("daimon.signature") domain-separates the 1271 tier from the
outcome tier. Callers updated (MandateEngine, EnforcementLib, tests).

Plus natspec-only clarifications across SpendSigil (tumbling-window + ERC-777
residual), NativeValueLimitSigil (per-call), RateLimitConfigLib (uint32 ~2106
wrap), TimeFrameSigil (fail-open + time-only composition), AttestationSigil
(no cumulative cap), OmniSigilTreeLib.fill (usage reset on re-bind), IdLib
(per-mandate cap non-aggregation).

D (subMode/mandateId in exec digest) intentionally excluded: reverted upstream
as an invalid finding. This repo's ERC-1608 deadline + Execute digest design is
left untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@highskore
highskore merged commit fe61008 into master Jun 10, 2026
2 checks passed
@highskore
highskore deleted the security/audit-fixes-confused-deputy branch June 10, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant