* fix: sharing answers "who can reach this" from the grants, not the index Two ways a share listing could name access that was no longer there. A recipient's listing paired the flat permission read with the list of permissions it asked for by array index. That read drops misses and dedupes its keys, so the two are not positional: one entry's live grant vouched for another entry that had none, and listShared() kept publishing a withdrawn item's name, size and signed thumbnail URL to someone who could no longer open it. Read the permission off the value instead. getShares() had the same gap from the owner's side, with no liveness check at all — a grant withdrawn through /auth/revoke-user-user or an ACL mode change left the index row behind, and the owner was told someone could reach a file they could not. Checked against the grants now, one batched read per distinct holder. Pending invites are not subject to it: they have no grant yet, which is the point of them. Also covers the access-token actor, which reaches the same reach bound as an app through a different arm of the ACL check. No behavior change there — it was correct and untested. * test: pin that a revoke crosses regions A revoke reaches a peer region as a replicated SQL delete plus three invalidation events, one per cache the region owns: the u2u row cache, the flat view and the scan generation. The delete alone changes nothing there, and each cache has a different consequence if its event is lost — 20s for the generation, 5 minutes for the row cache, and forever for the flat view, whose grant-path entries carry no expiry. Nothing covered the whole path end to end. The store tests prove each event is emitted and applied; this proves the result, which is that the recipient stops being able to read. It asserts the read still succeeds after the SQL delete alone, so the test also records why replication is not sufficient on its own. * test: pin that a leaked uuid buys no access A masked share path hides which folder an item sits in; it was never the thing deciding who may open it. Nothing checked that at the route level, so the guarantee rested on unit tests of the resolver alone. Reads one shared file through its masked path, then tries the sibling four ways: the shared uuid with the sibling's name, the sibling's own uuid, a `..` back out of the root, and the owner's real path. Worth knowing about this one: it's mutation-checked. Removing the head !== root.name guard in sharePathMask.ts fails it with reachable: /testuser/55dd54c0…/share-http-1df8933e.txt. I verified that specifically because two tests I wrote earlier in this chunk passed with their guards broken — both were vacuous, and I deleted them rather than commit false assurance. * docs: say what listShared's total actually counts `total` counts the shares recorded for you; items are filtered after the page is read, so a withdrawn grant leaves the count higher than anything paging will yield. The page description already explained the short-page behaviour, but the field read as an exact count and the example printed it as one. The test pins the gap it describes: two shares, one withdrawn outside the index, one item listed and a total of two. * fix: keep an undelivered broadcast event instead of dropping it The outbound queue was cleared before the send, so a peer that timed out took its events with it. Most were survivable — a lost cache invalidation heals when the entry's TTL lapses. A revoke's flat-perm invalidation is not: grant-path entries carry no expiry, so a peer went on serving a withdrawn grant until something else wrote that key. Failed sends now go back on the queue, which the existing flush timer retries. Anything queued since wins over the retry, and the queue is bounded at 10,000 with the oldest dropped first, so a peer that stays down cannot grow it without limit. Each retry is signed at send time, so it is not rejected against the replay window. * fix: stop plus-addressing from deciding who a share reaches
The Open-Source Internet Computer!
« LIVE DEMO »
Puter.com
·
App Store
·
Developers
·
Discord
·
Reddit
·
X
Puter
Puter is an advanced, open-source, self-hostable internet computer designed to be feature-rich, fast, and highly extensible.
For Users
Puter's goal is to provide you with every app and feature you need to work, create, and play under one roof. From a simple Notepad and Voice Recorder to Spreadsheet and Camera, Puter wants to be the all-in-one solution for your digital life.
For Developers
Puter provides everything you need to build and publish web apps and games. From AI to Cloud Storage and Database to Serverless Workers, Puter has you covered. Puter also helps you get users! Once you build your app, you can publish it on our App Store to reach and monetize users.
Getting Started
💻 Local Development
git clone https://github.com/HeyPuter/puter
cd puter
npm install
npm start
→ This should launch Puter at http://puter.localhost:4100
🚀 Self-Hosting
Linux/macOS
curl -fsSL https://puter.com/selfhost | sh
Windows
irm https://puter.com/selfhost?os=windows | iex
→ For more details, see Self-Hosting Puter.
☁️ Puter.com
Puter is available as a hosted service at puter.com.
Support
Connect with the maintainers and community through these channels:
- Bug report or feature request? Please open an issue.
- Discord: discord.com/invite/PQcx7Teh8u
- X (Twitter): x.com/HeyPuter
- Reddit: reddit.com/r/puter/
- Mastodon: mastodon.social/@puter
- Security issues or abuse reports? security@puter.com
- Email maintainers at hi@puter.com
We are always happy to help you with any questions you may have. Don't hesitate to ask!
License
This repository, including all its contents, sub-projects, modules, and components, is licensed under AGPL-3.0 unless explicitly stated otherwise. Third-party libraries included in this repository may be subject to their own licenses.
Translations
- Arabic / العربية
- Armenian / Հայերեն
- Bengali / বাংলা
- Chinese / 中文
- Danish / Dansk
- English
- Farsi / فارسی
- Finnish / Suomi
- French / Français
- German / Deutsch
- Hebrew/ עברית
- Hindi / हिंदी
- Hungarian / Magyar
- Indonesian / Bahasa Indonesia
- Italian / Italiano
- Japanese / 日本語
- Korean / 한국어
- Malay / Bahasa Malaysia
- Malayalam / മലയാളം
- Polish / Polski
- Portuguese / Português
- Punjabi / ਪੰਜਾਬੀ
- Romanian / Română
- Russian / Русский
- Spanish / Español
- Swedish / Svenska
- Tamil / தமிழ்
- Telugu / తెలుగు
- Thai / ไทย
- Turkish / Türkçe
- Ukrainian / Українська
- Urdu / اردو
- Vietnamese / Tiếng Việt

