NotASithLord / NotASithLord/peerd

dweb: installed dwapp code is mutable with no re-verification — add an explicit fork state + grant rotation

Open
#316 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
410
Forks
45
Avg merge
11d 5h
Merged PRs (30d)
1

Description

Problem

A dwapp's dweb.version_id is a provenance claim, not an integrity binding. Three concrete consequences, all verified in source:

  1. Writes don't check provenance. app_write_file / app_update / edit_file route to appClient.writeFile / update (background/app-client.js), which resolve an appId and write straight into OPFS peerd-apps/<appId>/ with no check of record.dweb, record.source, or the manifest hash. The agent (or the user via the app tab's Edit toggle) can silently rewrite code installed from a peer.
  2. Nothing re-verifies at run time. engine-tabs/app-tab/app-tab.js composes whatever is in OPFS; there is no comparison against dweb.version_id.
  3. Grants survive local mutation. The bridge grant key is derived from the stored dweb.hash (peerd-distributed/apps/bridge.js, appKey), which editing doesn't update — so a locally modified dwapp keeps the room grants the user approved for the original publisher's bytes.

Additionally, the update path (background/routes/dweb.js app-update, deliberately replace-not-merge) will silently clobber local modifications with no conflict detection.

Design intent: fork state, not immutability

Local mutability is the product thesis — "your agent modifies your software" — so the fix is not Sandstorm/Cloudflare-OS-style immutable packages. It's making the fork honest:

  • Fork transition. On first write to an app whose record carries dweb with a non-null hash: mark the record forked (e.g. dweb.forked: true), preserving lineage (base publisher, dwapp_id, version_id) for display as "modified from <publisher>". Registry change lands in peerd-engine/app-registry.js (note: dweb is currently excluded from applyPatch by design — the fork flag needs a sanctioned mutation path, not a loosening of that rule).
  • Grant rotation on fork. The bridge grant key becomes fork-scoped (e.g. recompute from current content, or fork:<appId>), so the next join re-consents under the new bytes. This closes the "approved bytes ≠ running bytes" gap without blocking the edit itself.
  • UI surface. "Locally modified" badge in the app tab; shown in the library/catalog if relevant.
  • Update conflict handling. dweb/base/update-app on a forked app must not silently overwrite — require an explicit confirm ("discard local changes and update to the publisher's vN?") or decline. Old-version seed data is already retained (offscreen/dweb-base.js keeps prior bundles as "the substrate for a future revert/changelog") — this is the first consumer.
  • Reshare already does the right thing — republishing a modified app goes out under your publisher DID and slug (dwapp_id = H(publisher‖slug), peerd-distributed/apps/meta.js), so attribution can't be spoofed. Optional follow-up: carry base-lineage in the reshared card.
  • Optional follow-up: verify-on-launch. For non-forked dweb apps, recompute the bundle hash at app-tab load and compare with version_id — catches tampering via any code path that didn't take the sanctioned fork transition. Needs a perf look (bundles are ≤50MB; hash lazily/cache by OPFS mtime).

Tests

  • Bun: registry fork-transition semantics; grant-key derivation before/after fork.
  • In-browser/e2e: editing a dweb-installed app forces re-consent on the next room join; update-on-forked-app prompts instead of clobbering.

Prior art

Cloudflare OS keeps app code as an immutable content-addressed snapshot (Blueprints) and puts all mutability in per-instance data; Sandstorm likewise (spk packages vs. grain storage). peerd deliberately inverts this — which is exactly why the fork boundary has to be explicit rather than implicit.

Related: #235 (dwapp actors epic — actors driving dwapps raises the stakes on knowing which bytes are running).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with peerd-engine/app-registry.js and background/app-client.js to trace sanctioned writes, then inspect peerd-distributed/apps/bridge.js and background/routes/dweb.js for grant derivation and update behavior. Check engine-tabs/app-tab/app-tab.js and the listed Bun and browser/e2e tests. Done means fork state and lineage are recorded, forked apps require new consent and cannot be silently overwritten, and the modified state is surfaced in the UI.

Written by the indexing model from the issue text.

Assessment

Tech stack
bun, javascript
Domain
frontend, security, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.