executor-dao
ExecutorDAO
The foundational modular DAO framework for Stacks — proposals as smart contracts, extensions as plugins, single-address ownership via sending context. Widely forked across the ecosystem.
| Contracts |
executor-dao.clar (core) + dao-traits-v4 (trait definitions)On-chain traits: SP2ZNGJ85ENDY6QRHQ5P2D4FXKGZWCKTB2T0Z55KS.dao-traits-v4
|
| Author | Marvin Janssen |
| Source | GitHub commit 4676718 (2022-07-11) · Traits verified on-chain via Hiro API |
| Clarity Version | Pre-Clarity 4 (Clarity 1 — no as-contract? asset allowances) |
| Lines of Code | 68 (core) + 86 (traits) = 154 total |
| Audit Date | 2026-02-25 |
| Confidence | 🟢 HIGH — compact codebase fully reviewed; all findings verified against source; no ambiguous external dependencies in core |
Overview
ExecutorDAO is the most influential DAO framework on Stacks, forked and deployed by ALEX, Charisma, Megapont, aibtcdev, and dozens of other projects. The design is elegantly minimal: a single core contract that executes proposals (which are themselves smart contracts) and manages a registry of extensions. Extensions are plugin contracts that add governance tokens, voting, treasuries, etc.
The core contract is only 68 lines — a testament to Clarity's expressiveness. The three key design principles are:
- Proposals are smart contracts — any Clarity code can be a proposal, making the DAO arbitrarily programmable
- The core executes, extensions give form — the core has no features; everything is added via extension contracts
- Ownership via sending context —
as-contractmakes the DAO thetx-sender, so external ownable contracts can be governed by the DAO
The traits contract (dao-traits-v4) defines 10 traits covering SIP-010 tokens, proposals, extensions, governance tokens, NFTs, liquid FTs, CDK/EDK (land game mechanics), and fee-sharing.
Documented Limitations
- README states "This is an early release. More information, design specifics, and unit tests coming soon." (still the case as of 2026)
- The framework intentionally has no built-in governance token, voting, or treasury — these are left to extensions
- The
constructfunction is a one-time bootstrap that permanently transfers executive control to the contract itself
Findings
H-01: Blanket as-contract Grants Unlimited Asset Authority to Proposals and Extensions
Location: execute (line 52), construct (line 60), request-extension-callback (line 67)
Description: The core contract uses as-contract in three places to execute proposals, bootstrap, and handle extension callbacks. In pre-Clarity 4, as-contract grants blanket authority over all assets held by the DAO contract — STX, fungible tokens, NFTs, everything. Any proposal or extension that runs under as-contract can move any asset the DAO holds, with no language-level restriction on which assets it can touch.
;; Proposal gets blanket asset authority via as-contract
(define-public (execute (proposal <proposal-trait>) (sender principal))
(begin
(try! (is-self-or-extension))
(asserts! (map-insert executed-proposals (contract-of proposal) block-height) err-already-executed)
(print {event: "execute", proposal: proposal})
(as-contract (contract-call? proposal execute sender)) ;; <-- blanket authority
)
)
;; Extension callback also gets blanket authority
(define-public (request-extension-callback (extension <extension-trait>) (memo (buff 34)))
(let ((sender tx-sender))
(asserts! (is-extension contract-caller) err-invalid-extension)
(asserts! (is-eq contract-caller (contract-of extension)) err-invalid-extension)
(as-contract (contract-call? extension callback sender memo)) ;; <-- blanket authority
)
)
Impact: A malicious or compromised proposal/extension can drain the entire DAO treasury in a single transaction. Since proposals are arbitrary smart contracts, a governance attack (e.g., flash-loan voting in a fork that uses token-weighted voting) could pass a proposal that steals all funds. The risk is amplified by the fact that most forks hold significant treasury assets.
Recommendation: Migrate to Clarity 4 and replace as-contract with as-contract? using explicit asset allowances. For example, a treasury management proposal should use (as-contract? (with-stx (with-ft .governance-token) ...)) to limit which assets the proposal can move. This provides defense-in-depth even if governance is compromised — a voting proposal can't touch treasury funds if it only has with-ft .governance-token allowance.
M-01: No Emergency Shutdown or Pause Mechanism
Location: Entire contract architecture
Description: Once bootstrapped, the only way to modify the DAO is through proposals executed by extensions. If a critical vulnerability is discovered in an extension, there is no way to pause the DAO, disable all extensions atomically, or prevent malicious proposals from being executed while a fix is prepared. The only recourse is to rush a counter-proposal through whatever voting mechanism the fork has implemented.
Impact: In an active exploit scenario, the time between detection and a fix proposal being voted on and executed could allow an attacker to drain the DAO. Many DAOs have multi-day voting periods, during which the exploit would be unstoppable.
Recommendation: Add an emergency guardian pattern: a designated principal (multisig or timelock) that can pause all proposal execution and extension calls. The guardian should be removable by governance to prevent centralization. Example: (define-data-var paused bool false) checked at the top of execute and set-extension.
M-02: sender Parameter in execute Is Caller-Controlled and Unverified
Location: execute function (line 49)
Description: The execute function takes a sender parameter that is passed to the proposal's execute function. This parameter is provided by the calling extension and is not validated against tx-sender or contract-caller. A malicious or buggy extension could pass an arbitrary principal as the sender, potentially impersonating another user to a proposal.
;; sender is caller-supplied, not verified
(define-public (execute (proposal <proposal-trait>) (sender principal))
(begin
(try! (is-self-or-extension))
(asserts! (map-insert executed-proposals (contract-of proposal) block-height) err-already-executed)
(print {event: "execute", proposal: proposal})
(as-contract (contract-call? proposal execute sender)) ;; proposal trusts sender
)
)
Impact: If a proposal uses the sender parameter for authorization decisions (e.g., "only admin X can trigger this proposal"), a compromised extension could spoof that identity. The severity depends on how downstream proposals use the sender value — in many forks, it's used purely for logging, making this low-impact. But in forks where proposals check sender identity, this is exploitable.
Recommendation: Either validate that sender equals tx-sender (or contract-caller) inside the core, or document clearly that proposals must NOT trust the sender parameter for authorization. The construct function correctly captures tx-sender in a local binding — execute should do the same.
L-01: construct Has No Replay Protection Across Forks
Location: construct function (line 56)
Description: The construct function checks that tx-sender equals the executive variable (set to the deployer at deploy time). After execution, it sets executive to the contract's own principal, preventing re-invocation. However, if the same deployer key is used across multiple chain forks or testnet deployments, the bootstrap proposal could be front-run or replayed if the deployer's key is compromised before construct is called.
(define-public (construct (proposal <proposal-trait>))
(let ((sender tx-sender))
(asserts! (is-eq sender (var-get executive)) err-unauthorised)
(var-set executive (as-contract tx-sender)) ;; one-time lock
(as-contract (execute proposal sender))
)
)
Impact: Low — requires compromising the deployer key in the window between deployment and construct call. In practice, deployers typically call construct in the same block or shortly after deployment.
Recommendation: Call construct in the same transaction as deployment when possible. Document that the deployer key should be rotated or secured after bootstrap.
I-01: Pre-Clarity 4 — Migrate to as-contract? for Language-Level Asset Safety
Location: Entire contract
Description: This contract was written for Clarity 1 (epoch 2.05). Clarity 4 (epoch 3.3, activated at Bitcoin block 923,222) introduced as-contract? with explicit asset allowances. This is the single most impactful improvement available to ExecutorDAO forks. Instead of granting blanket authority, each as-contract? call explicitly declares which assets (STX, specific FTs, specific NFTs) the enclosed code can move. This directly mitigates H-01.
Recommendation: All new forks should use Clarity 4. The core execute function should use as-contract? with the minimum required asset allowances per proposal type. Consider a proposal categorization system where treasury proposals get with-stx/with-ft while governance proposals get no asset allowances.
I-02: Traits Contract Bundles Unrelated Concerns
Location: dao-traits-v4
Description: The traits contract defines 10 traits in a single file, mixing core DAO concerns (proposal-trait, extension-trait, governance-token-trait) with domain-specific traits (cdk-trait, edk-trait for land games, liquid-ft-trait, share-fee-to-trait). This creates an unnecessary coupling: any contract that needs the proposal trait must deploy a contract that also contains game mechanics traits.
Impact: No security impact. This is a code organization concern that increases deployment costs and reduces modularity.
Recommendation: Split traits into separate contracts: core DAO traits (proposal, extension, governance-token), token traits (sip010, nft, liquid-ft, ft-plus), and domain-specific traits (cdk, edk, share-fee-to).
I-03: governance-token-trait Uses Non-Standard dmg- Prefixed Functions
Location: dao-traits-v4 — governance-token-trait
Description: The governance token trait defines functions with a dmg- prefix (e.g., dmg-get-balance, dmg-transfer, dmg-mint). "DMG" appears to be a specific token name (possibly "Dungeon Master's Gold" from the Megapont ecosystem). This naming couples the generic DAO framework to a specific project's naming convention, making it confusing for forks that use different governance token names.
Impact: No security impact. Forks must implement functions named dmg-* even if their token has nothing to do with DMG.
Recommendation: Use generic names: gov-get-balance, gov-transfer, gov-mint, gov-burn, gov-lock, gov-unlock, gov-get-locked, gov-has-percentage-balance.
Architecture Analysis
Strengths
- Minimal core: 68 lines with a clear single responsibility — no feature bloat to audit
- Elegant authorization:
is-self-or-extensionusestx-sender == as-contract tx-senderfor self-calls andcontract-callerfor extension checks — correct and concise - Replay protection:
map-insertonexecuted-proposalsensures proposals can only execute once — idempotency by design - One-time bootstrap:
constructpermanently transfers executive control, preventing re-initialization - Extension callback pattern:
request-extension-callbackvalidates both that the caller is an enabled extension AND that it matches the extension being called — prevents spoofing - Clean event emission: All state changes emit structured print events for off-chain indexing
Trust Assumptions
- Proposals executed by the DAO are assumed to be vetted through whatever voting mechanism the fork implements — the core has no opinion on how proposals are approved
- Extensions are trusted with full DAO authority once enabled — a single malicious extension can compromise the entire DAO
- The deployer is trusted to call
constructwith a legitimate bootstrap proposal before the key is compromised - Downstream forks must implement their own access control for who can call
execute— the core only checks that the caller is self or an extension
Fork Guidance
ExecutorDAO's security is only as strong as its extensions. Forks should:
- Use Clarity 4 with
as-contract?and explicit asset allowances (mitigates H-01) - Implement timelock delays on proposal execution to allow community review
- Add an emergency pause mechanism (mitigates M-01)
- Validate the
senderparameter or document that proposals must not trust it (mitigates M-02) - Use post-conditions on all treasury transactions as a defense-in-depth layer
Priority Score
| Metric | Score | Weight | Weighted |
|---|---|---|---|
| Financial Risk | 2 — manages extensions that control assets | 3 | 6 |
| Deployment Likelihood | 3 — deployed on mainnet, widely forked | 2 | 6 |
| Code Complexity | 2 — 154 lines, multi-contract framework | 2 | 4 |
| User Exposure | 3 — most forked DAO framework on Stacks | 1.5 | 4.5 |
| Novelty | 3 — foundational pattern, first audit of original | 1.5 | 4.5 |
| Total (pre-penalty) | 2.5 / 3.0 | ||
| Clarity version penalty (pre-Clarity 4) | −0.5 | ||
| Final Score | 2.0 / 3.0 ✅ AUDIT | ||