# Found

*Updates on our community progress*

By [Sensemaking - SpiritDAO's Weekly Newsletter](https://paragraph.com/@spiritdao) · 2026-09-21

---

**Contents:**
-------------

1.  A security issue we found and fixed
    
2.  Another bug: fixing to email privacy
    
3.  Onchain decisions; database memory
    
4.  Removing Members
    
5.  Signing in on a second device
    
6.  Proactivity
    
7.  Upcoming events
    
    * * *
    

### A security issue we found and fixed

We found a database permission issue that had been around longer than it should have and addressed it.

For 44 tables, an unsigned visitor could access data that should have required authentication. This included 115 chat messages, push notification subscriptions, member/pod information, and some event, governance, and treasury records. Two tables also allowed writes.

We found no evidence that anyone actually accessed or changed this data and given all available usage data do not believe it to be the case. Below we share exactly what was exposed, and that access is now closed.

The root cause was a database permission rule that looked like it was protecting each table but was effectively allowing anonymous access. We'd checked that the protections were turned on; we hadn't tested what they actually allowed.

We fixed this at the database level rather than patching individual tables. Anonymous requests are now rejected before they can reach any table rules. We then tested all 158 tables and views as an anonymous visitor: nothing can be read, written, or called without authentication. We also verified that members still have the access they should.

We caught one other issue while doing the sweep: event listings could expose other people's unpublished drafts. That's fixed too.

Not the most fun thing to find, but we're glad we found it, closed it properly, and can share the progress with you openly.

* * *

Another bug: fixing email privacy
---------------------------------

Until this week, any signed-in member could read any other member's email address, along with the phone number, street address, and date of birth they had entered on their profile. The app never displayed them, but our database rules allowed a member's browser to ask for them, and a browser can be made to ask.

Similar to the issue above, we found no evidence supporting the idea that anyone did.

Member-facing reads now go through a profile view that simply does not contain those columns. Your own profile still shows you your own details, and the server still has what it needs to send you a receipt or a digest. A check now runs on every build and fails it if new code reaches for a private column. Within about a week, once we are sure nothing else depended on the old behavior, the database itself will refuse to hand another member's private columns to anyone but that member.

One intentional exception: when you buy something in the marketplace, the seller can see your phone number and shipping address so they can send it. Your email is not shown to them. Contact stays in the app.

* * *

Onchain decisions; database memory
----------------------------------

After the repair run in the last letter, we stopped fixing individual screens and asked one question of the whole app: is there any place where a member could give themselves authority by writing to the database, rather than by holding the right hat on-chain?

We found the answer to be yes.

Some background for anyone new. Every role in SpiritDAO, whether member of a pod, leader of a pod, or seat on the Executive, is a **hat**: a token on Base issued through Hats Protocol that you either hold or do not. That is the source of truth. The database is meant to be a cache of it, so the app can draw a members list without asking the blockchain every time. The problem was that in a handful of places the app trusted the cache over the chain, and let the browser write to the cache.

What that meant concretely: a signed-in member could have sent the database a request changing their own row from member to leader, or added themselves to a pod they had never joined. Accountability signatures could have been inserted on someone else's behalf. Seven server endpoints accepted requests with no session at all and believed whatever wallet address the request claimed. Notifications were readable across accounts.

All of it is closed:

**Every authority decision starts on-chain.** Whether you lead a pod, manage a pod, or hold a role is now answered by reading your hat balances from Base. The database is consulted only as a fallback when the chain cannot be reached, and in that case it can only _deny_, never grant, anything the chain has not already confirmed.

**The browser no longer writes membership.** Approving an application, claiming a management hat, recording a new pod's leaders, removing a member: each now goes through the server, which checks the hat on-chain before touching a row.

**Signatures are recorded only when proven.** A pod agreement signature is written only after the server confirms it exists on-chain or verifies the signed message itself.

**Every endpoint needs a session.** The seven routes that accepted anonymous requests now refuse them, and only the recipient can read a notification.

**Agents fail closed.** If the agent registry cannot be reached, the app now assumes "agent" rather than "human." An agent cannot post until the chain confirms its registration.

Across the eight different places the app used to answer "is this person a pod leader?" there is now one resolver. They all agree, because they are all the same code.

We also removed dead treasury code: sixteen functions, two files, an API route signed with an admin key, and a page pointing at a retired contract. None had a caller and all would have been refused by the current contract. The only spend path left is the one the multisig guards.

* * *

**Removing Members**
--------------------

A leader can now remove someone from a pod, and the removal is real on-chain.

In the past the remove button tried to transfer the member's hat to the zero address, which Hats Protocol refuses, and our eligibility module had no way for a leader to declare someone ineligible. The button had shown a vague permissions error since launch.

Fixing it properly meant a new contract, now deployed. Our old eligibility module did one thing: refuse AI agents. The new one does three. It still refuses agents. It honors a revocation recorded by an admin of the hat, which is the removal the app never had. And it consults the criteria module for signed agreements, token holdings, and time windows, so a pod can make signing its agreement a real condition of membership rather than a checkbox.

![](https://storage.googleapis.com/papyrus_images/b1416596bfeb7958f6df0746bb69fa12569c6c0d6d40aaa3b347d3a34a1f47bd.png)

Revocation is not a mark against anyone. The contract reports a revoked member as ineligible but in good standing, so nothing punitive is written to the chain, and a leader who later re-approves the same person lifts the revocation and mints the hat in one action.

It is attached to the Temporal Naturalism and Treegens hats, where it changed nothing for any current member. We checked every active wearer before and after.

* * *

Signing in on a second device
-----------------------------

If you have ever signed in on your phone after setting up on a desktop, you met a prompt asking for a six-character code, followed by a baffling instruction to visit a page with _debug_ in its address to get one.

That page was a diagnostic tool. It was never meant for members, it was reachable by anyone who knew the address, and it had quietly become a required step in signing in.

There is now a **Linked Devices** section on your Identity page. Press "Get a code for another device" and you get a code, a copy button, and a countdown showing the ten minutes you have to use it. It clears itself when it lapses, so you are never reading a code that has already died.

![](https://storage.googleapis.com/papyrus_images/7e675a2716aa080577b3af396603cbd7a401901865503db8f759612b1293647f.png)

The diagnostic page is gone, along with two behind-the-scenes addresses that reported internal details to anyone who asked.

Also fixed: **linking an outside wallet works for the first time.** MetaMask and similar wallets could be connected since July, but the final confirmation step rejected every valid signature because of a timestamp formatting mismatch. It is now resolved.

* * *

**Proactivity**
---------------

Every problem in this letter and the last one has the same shape. Something was wrong for months and we (thankfully) found it through proactive audits rather than bad user experiences.

Part of our journey in building the next generation of community management infrastructure is proactively trying to break it. We already had scripts that try to break the app. One signs in as an ordinary member and attempts every way we know of to promote itself, read someone else's inbox, or forge a signature. Another checks that every decision the server makes about who leads a pod matches what the blockchain says. They worked. They ran when somebody remembered to run them, which is not a safety system.

They now run on every deploy, and the deploy is judged by the result. We added a third that re-runs the full anonymous sweep, every table and every function, probed with no session, on every release. If a future change reopens what we closed, the release fails and names the table.

We also made sure that test can actually fail. We deliberately exposed an empty throwaway table, confirmed the check caught it and named it, then removed the table. A test that has never failed is a test you are trusting on faith.

None of this is visible to you. It is the difference between finding the next one in two days and finding it in four months.

* * *

**Upcoming Events**
-------------------

We strongly recommend registering for events within the SpiritDAO app (Events section) at [**app.spiritdao.org**](http://app.spiritdao.org), it's how we gauge attendance and shape the flow of each session. Links to each event may be found in-app.

**Town Hall** — Mon, Sept. 21st · 8:00 PM EST - A standing community gathering for conversation, questions, and shared presence.

**First Contact** — Wed, Sept. 23rd · 8:00 PM EST - The official starting point for new members — a peer-to-peer orientation into how participation works.

**Feedback Loops** — Fri, Sept. 25th · 8:00 PM EST - A live working session where the community helps shape the app. Bring your bugs, your questions, and your ideas.

* * *

SpiritDAO is 100% donor-funded. There are no subscriptions, no paywalls, no venture capital. Every community on this platform uses it for free and that's by design. Our runway is small, and every donation directly extends how long we can keep building. If this work matters to you, even a small contribution goes further than you'd think.

→ [**Donate at spiritdao.org/donate**](https://spiritdao.org/donate)

SpiritDAO is a registered 501(c)(3). Your donation is tax-deductible. (EIN: 99-1308806)

### **Questions?**

The best way to get in touch is within our community: [**https://app.spiritdao.org**](https://app.spiritdao.org)  
Or write to Ron Rivers: [**ron@spiritdao.org**](mailto:ron@spiritdao.org)

[**Donate**](https://paragraph.com/@spiritdao/shown091426)

* * *

Thank you for reading!  
Please share this email with any friends you think might enjoy this newsletter. We are spreading the message one curious person at a time.

**Connect with SpiritDAO**

![](https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f9e0.png) [**SpiritDAO App**](https://app.spiritdao.org/)  
![](https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f310.png) [**SpiritDAO.org**](http://SpiritDAO.org)  
![](https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/25b6-fe0f.png) [**YouTube**](https://youtube.com/@spiritdao)  
![](https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f426.png) [**Twitter**](https://twitter.com/thespiritdao) / X  
![](https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f4f1.png)[**TikTok**](https://www.tiktok.com/@thespiritdao)  
![](https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f4dc.png) [**Docs**](https://spiritdao.org/docs)

**SpiritDAO is a registered 501(c)(3) EIN #99-1308806.**

---

*Originally published on [Sensemaking - SpiritDAO's Weekly Newsletter](https://paragraph.com/@spiritdao/found092126)*
