In the world of decentralized systems, availability is just as critical as integrity. Recently, I was auditing a well-known Directed Acyclic Graph (DAG) platform that supports Autonomous Agents (AAs) — Turing-complete smart contracts that execute logic in response to incoming messages.
What I found was a way to stall the network's processing of these agents using a crafted recursive structure, leading to a denial of service for the execution layer. The story involves a unique "lock", an algorithmic complexity bottleneck, and bypassing modern KYC hurdles to get it fixed.
— -
The Discovery: Mutexes and Recursion
My attention was drawn to how the platform handles the sequential execution of agents. To ensure deterministic state changes, the node software utilizes a global mutex lock (let's call it 'aa_triggers') that serializes the execution of agent triggers.
The Hypothesis:
If I could force the system to hold this 'aa_triggers' lock for an extended period while processing a single malicious transaction, I could block all other AA transactions on the network, effectively halting the smart contract layer, though standard payments and consensus would remain unaffected.
The Exploit:
I analyzed how "Response Units" were generated and logged. The system uses a state object that gets expanded when an agent triggers another agent.
I crafted a Proof of Concept (PoC) that utilized exponential DAG expansion. By nesting triggers in a specific self-referential pattern, I caused the internal state representation to grow exponentially in complexity, even if the byte size was manageable.
When the node attempted to process this payload:
1. It acquired the 'aa_triggers' lock.
2. It attempted to log the state for debugging and calculate a hash for the response.
3. The specific bottleneck was in the payload hashing: message.payload_hash = objectHash.getBase64Hash(message.payload, true);
4. The 'object-hash' library and 'JSON.stringify' (used for debugging logs) attempted to traverse this deeply recursive structure without memoization.
5. Result: The CPU hung on the traversal, holding the lock indefinitely. This caused measured SLA violations and prevented legitimate AA transactions from being processed.
— -
The Reporting Hurdle vs. The Hacker Way
I prepared a detailed 'report.md' and a fully working JS PoC that demonstrated the execution stall. I went to Immunefi, where the project hosts their bounty program, ready to submit.
The Block:
"New submissions require ZKPassport identity verification." 🥲
I'm a privacy-focused researcher. I don't carry an ICAO-9303 compliant e-Passport, and I wasn't about to get one just to submit a bug.
The Pivot:😙
I didn't give up. I found the founder's handle and messaged them directly on Telegram.
> Me: Hi, I'm trying to responsibly disclose a critical DoS vulnerability… I'm unable to submit via Immunefi because of new KYC rules…
>
> Founder (Replying within hours): I didn't know about Immunefi's new rules, perhaps they are getting too much spam. If you can't register there, you can disclose the vuln here. We'll treat it like it was submitted through Immunefi.
I sent the report and PoC over Telegram.🥳
— -
The Fix: WeakMap & Memoization
The founder confirmed the issue quickly:
"Indeed, it takes a lot of time to process such triggers… I found a few places where the response unit was logged with JSON.stringify() and this logging took very long. The test now executes about 2 times faster."
However, the hashing was still slow. I proposed two architectural fixes to bound the traversal to O(N):
1. WeakSet: To track visited nodes and detect infinite loops or shared references during traversal. This is ideal for pure serialization where you just want to abort on cycles.
2. WeakMap: To memoize the hash results of nested objects. If a shared reference is encountered, the system simply returns the pre-calculated hash instead of re-traversing the sub-tree.
The team implemented the WeakMap solution for both the payload hashing and length calculations. This effectively neutralized the exponential growth vector.
> Founder: "I applied the WeakMap fix and it delivered a significant speedup… According to our bug bounty program, this DoS bug has Medium severity with $1000 reward."🥳
— -
How You Can Find Such Vulns
If you are auditing blockchain nodes or distributed systems, these are the patterns to hunt for:
1. Global Locks (Mutex): grep for mutex acquisitions (mutex.lock(), KeyedMutex, etc.). Any code that runs inside a lock is a DoS candidate.
2. Unbounded Traversal: Look for operations on user-controlled objects inside those locks. Functions like JSON.stringify, gob.Encode, or recursive hashers (object-hash) are dangerous if they don't handle deep nesting or shared references (DAGs) correctly.
3. Logging: Developers often verify logic but forget that log.debug(complexObject) runs even in production sometimes (or if the log level is high). I've found many DoS bugs simply because the logging function choked on a malicious input.
4. Graph Expansion: Can you create a transaction that references itself? If the system tries to flatten a graph into a tree for hashing/storage, you can often blow up the CPU or RAM.
Statistics
* Severity: Medium (AA execution stall / Network Degradation)
* Bounty: $1,000
* Fix Time: < 24 Hours
* Complexity: Low code change, High impact
Sometimes the hardest part isn't the exploit, it's finding a way to report it when the front door is KYC-locked. Don't be afraid to DM the founders.
Happy Hacking!
- Sumit Shah (hacksageX)