Skip to content

Rename prohibitAllSharing to containsRestrictedData - #234

Draft
Maximo-Guk wants to merge 8 commits into
mainfrom
restricted-data-rename
Draft

Rename prohibitAllSharing to containsRestrictedData#234
Maximo-Guk wants to merge 8 commits into
mainfrom
restricted-data-rename

Conversation

@Maximo-Guk

@Maximo-Guk Maximo-Guk commented Aug 17, 2026

Copy link
Copy Markdown
Member

Old model: prohibitAllSharing blocked all sharing the moment sensitive data was observed. No one other than the owner could access a latched workspace.

New model: containsRestrictedData allows sharing but requires per-collaborator observer verification against each gatekeeper that read sensitive data. The workspace still goes into a restricted mode (no actions against gatekeepers, no web fetches), but collaborators can open it if they pass verification.

WIP

@github-actions github-actions Bot added workshop/frontend Changes to the Workshop frontend kernel Changes to the Workshop kernel gatekeeper Changes to a gatekeeper integration workshop/shared Changes to shared Workshop APIs labels Aug 17, 2026
@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch from c04d5e3 to aa71300 Compare August 17, 2026 03:45
@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch from cf3449d to f633a1a Compare August 17, 2026 13:17
@Maximo-Guk
Maximo-Guk force-pushed the maximo/pr-previews branch 6 times, most recently from 5f1ff10 to e4e661c Compare August 17, 2026 22:55
Base automatically changed from maximo/pr-previews to main August 17, 2026 22:59
@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch from f633a1a to bc6f07a Compare August 18, 2026 14:34
@github-actions

Copy link
Copy Markdown

Preview: pr234-restricted-data-rename

https://pr234-restricted-data-rename-router.cloudflare-os-previews.workers.dev

Dashboard · deleted when this PR closes

@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch 2 times, most recently from 413e955 to b2c3e58 Compare August 18, 2026 16:38
@Maximo-Guk
Maximo-Guk marked this pull request as ready for review August 18, 2026 16:55
@Maximo-Guk
Maximo-Guk marked this pull request as draft August 18, 2026 16:55
devin-ai-integration[bot]

This comment was marked as resolved.

@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch 2 times, most recently from cd22d16 to b7c9372 Compare August 19, 2026 16:04
devin-ai-integration[bot]

This comment was marked as resolved.

@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch from 41ae6a0 to 740374a Compare August 19, 2026 19:52

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment thread packages/workshop-backend/src/overseer.ts Outdated

const isOwner = !metadata.owner
const sharingProhibited = metadata.sharingProhibited === true
const containsRestrictedData = metadata.containsRestrictedData === true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Kernel and UI changes are shipped in the same commits, against the repository's review rule

The change edits the kernel and the browser UI together in single commits (packages/workshop-frontend/src/ShareModal.tsx:374 alongside packages/workshop-backend/src/overseer.ts), so the kernel cannot be reviewed apart from the UI as the repository requires.
Impact: Reviewers cannot review the security-critical kernel diff independently, which the project's review policy mandates.

Rule and commit layout

REVIEW.md ("High-scrutiny areas"): "A large kernel change should be split by concern into separate PRs, and at minimum grouped into commits that let workshop-backend/workshop-shared be reviewed apart from UI. Flag PRs that bundle both."

Commits 6b778a1 and 1991473 each touch packages/workshop-backend/src/overseer.ts, packages/workshop-shared/src/api.ts, packages/workshop-shared/src/gatekeeper.ts and packages/workshop-frontend/src/ShareModal.tsx in one commit.

Prompt for agents
Per REVIEW.md, kernel changes (packages/workshop-backend, packages/workshop-shared) must be reviewable apart from UI changes. Restructure the history so the ShareModal.tsx (workshop-frontend) edits live in their own commit(s) separate from the overseer.ts/api.ts/gatekeeper.ts changes, or split the frontend change into its own PR.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +2996 to +3011
vendorId !== null && this.#inScopeGatekeepers("use").some(gk => gk.id === gatekeeperId);

for (let collaborator of collaborators) {
// A verifiable gatekeeper outside a "use" collaborator's scope is one the UI can't invoke
// and ensureObserver can't cover; only the unverifiable case above blocks regardless of
// role. An absent role means "build" (see CollaboratorInfo), which fails safe here.
if (vendorId && (collaborator.role ?? "build") === "use" && !inUseScope) continue;
let observer = vendorId ? this.storage.observers.get(collaborator.profile.id) : undefined;
if (!observer || !(gatekeeperId in observer.accountChoices)) {
throw new Error(
"This observation was blocked because it contains sensitive data, but this " +
`workspace is shared with ${collaborator.profile.name} (${collaborator.profile.id}), ` +
"who has not been verified to have access to that data. They must re-open the " +
"workspace (which verifies their access) or be removed from it before this data " +
"can be read.");
}

@devin-ai-integration devin-ai-integration Bot Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 A "use" collaborator can now open a workspace holding restricted data without being verified against the connection that read it

Removing the wholesale block in open() (packages/workshop-backend/src/overseer.ts:6751-6756) means a non-owner may now open a workspace that has observed containsRestrictedData, and verification is only run for gatekeepers in their role's scope. For a use collaborator, #inScopeGatekeepers("use") (packages/workshop-backend/src/overseer.ts:6091-6112) only includes connections bound by a non-pending gadget, so a connection that read restricted data through the agent's chat bindings only (never bound to a gadget) is never verified against them, and the observation-time coverage guard explicitly skips them for that connection (packages/workshop-backend/src/overseer.ts:3002). If the agent persists that restricted data into gadget code/storage, the use collaborator renders it through the gadget UI with no access check at all — previously impossible, since a latched workspace refused every non-owner open.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from ask-bonk Bot Aug 19, 2026
@cloudflare cloudflare deleted a comment from github-actions Bot Aug 19, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment thread packages/workshop-backend/src/overseer.ts Outdated
Comment on lines +3018 to +3024
throw new Error(
"This observation was blocked because it contains sensitive data, but this " +
`workspace is shared with ${collaborator.profile.name}, ` +
"who has not been verified to have access to that data. They must re-open the " +
"workspace (which verifies their access) or be removed from it before this data " +
"can be read.");
}

@devin-ai-integration devin-ai-integration Bot Aug 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Blocked sensitive read names a collaborator to sandboxed gadget and agent output

The coverage-guard error embeds collaborator.profile.name (packages/workshop-backend/src/overseer.ts:3020) and, as its own comment notes, this message is surfaced to sandboxed gadget code and agent output — an audience that cannot otherwise enumerate collaborators. The comment claims the profile id is omitted because it is "often an email address", but profile.name is the user-supplied display name and, for password signups, is set to the username/email (see createAccount(username, username, ...) usage), so the identifier the guard intends to withhold can still be exposed.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch from f9c4f0e to f53bdac Compare August 20, 2026 18:57
Maximo-Guk and others added 4 commits August 20, 2026 14:32
Rename prohibitAllSharing to containsRestrictedData: the flag now means
"this observation carries data whose sharing is governed by observer
verification" rather than a wholesale ban on sharing the workspace. The
legacy flag is still read on persisted records and converted on load.

Instead of refusing to share a flagged workspace outright, sharing is
admitted when every collaborator is verified as an observer of the
producing gatekeeper:

- open() verifies each collaborator against every gatekeeper in their
  role's verification scope (ensureObserver), and the coverage guard
  (#assertSensitiveObservationCoverage) blocks a sensitive observation
  naming any current collaborator not yet verified against its
  producer. Coverage is held to each collaborator's role scope, since a
  "use" collaborator can never be verified against a gatekeeper no
  gadget binds.
- Share-key redemption writes a *pending* edge that grants no interim
  authority: the redeeming open() verifies the recipient at the role
  the edge would grant, confirms only on success (capped at the
  verified role, denied if the connection/binding topology changed
  mid-verification, merged if a concurrent redemption landed first),
  and rolls the edge back on refusal or a null effective role.
- Removing the producing connection no longer lifts the restriction
  for existing collaborators, and assertNewSharingAllowed refuses new
  grants inside the grant write's synchronous block.
- The coverage-guard error reaches sandboxed gadget/agent output, so
  it names the collaborator but omits their profile id (the full email
  on OAuth/CF Access deployments).

The accepted residuals (formerly-bound and never-bound producers
outside "use" scope) are documented in docs/observers.md edge case 4
and docs/sharing.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e scope

New sensitive-observations and observer-role-scope suites, with the
harness, RPC client, and gatekeeper-test fixture support they need
(the fixture gains a configurable observer hook and sensitive reads).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update gatekeeper-google to the renamed flag, and revise the
gatekeeper-mcp README and write-gatekeeper skill guidance to describe
observer-verified sharing rather than a wholesale sharing prohibition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The modal no longer refuses to share a workspace with restricted data;
it explains that collaborators must be verified against the producing
connection at their next open. The share key is retained across
failures and reloads (sessionStorage tier) so a recipient who is
denied verification can retry, and is discarded at the first
successful open so a later removal isn't undone by an automatic
re-redemption.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Maximo-Guk
Maximo-Guk force-pushed the restricted-data-rename branch from f53bdac to a3f45c3 Compare August 20, 2026 19:51
Maximo-Guk and others added 4 commits August 20, 2026 16:16
ensureObserver re-runs each gatekeeper's addObserver on every open, but a
failure only denied the open: the collaborator's persisted observer record
(and the gatekeeper-side registration, being preConfigured) survived
untouched. #assertSensitiveObservationCoverage reads that record from other
turns, so a collaborator whose provider-side access was revoked kept
admitting the producer's restricted observations to their still-live older
session indefinitely.

Now fail() scrubs the failed gatekeeper from the persisted record
synchronously with the failure determination -- covering both failure sites
(verifier gone, addObserver threw) and cancel-after-reprompt -- and the
terminal catch best-effort de-registers the invalidated gatekeepers alongside
the newly-added ones (removeObserver is idempotent). The scrub is scoped to
the failed gatekeeper; a repaired pass re-persists full coverage at step 6.
A first-pass modal cancel never scrubs (no failure was determined).

Known tradeoff, by design: an operational failure (vendor outage, expired
credential) scrubs the same way -- the overseer cannot tell it from a settled
denial -- so that producer's restricted reads are blocked workspace-wide
until the affected collaborator re-opens successfully. Fail-closed.

The fixture gatekeeper gains an optional per-resource verify outcome
(resource-specific key, label-wide fallback) so the new integration test can
prove the scrub is per-producer rather than account-wide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Share-key redemption is two-phase: assertNewSharingAllowed runs synchronously
with the *pending* edge write in redeemShareKey, but the granting write is
confirmShareKeyRedemption, separated from it by the redeeming open()'s await
windows (ensureCapsules, ensureObserver). The topology fingerprint is taken
after those awaits, so a restricted-data producer removed in the window
between the redemption gate and the snapshot -- an unverifiable legacy
producer's remove() skips the share-link guard entirely -- appears in neither
fingerprint, and the confirm admitted a recipient nobody can verify for the
restricted data.

confirmShareKeyRedemption now takes the same optional assertGrantAllowed
callback the other grant-writing mutators use, invoked in the confirm's
synchronous block before the pending flag is cleared or the missing edge
re-added; an already-confirmed edge (an existing grant) skips it, matching
redeemShareKey. The overseer passes assertNewSharingAllowed, and a throw
lands in open()'s catch, which severs the pending edge.

No integration repro: the window (the ensureCapsules await) has no fixture
hook and needs an unverifiable legacy producer the fixture cannot mint. The
callback pattern keeps the check unit-testable in the sharing.test.ts
harness; existing redemption integration tests guard regressions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The accepted wart in confirmShareKeyRedemption (an owner removal racing a
mid-verification redemption is quietly re-granted by the confirm) was
documented, but not why it grants nothing an attacker could not already take:
a pending-only recipient is invisible to listCollaborators, so the removal UI
cannot target one mid-verification (a racing removal necessarily aimed at a
previously confirmed edge); the re-add carries no incremental authority,
since the recipient holds the live link and can re-redeem it manually; and
the durable exclusion is revoking the link, which computeEffectiveRoles
already inerts. Spell those three points out in the docblock and in
docs/sharing.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sessionStorage tier of share-key retention was keyed only by workspace id
and auto-resent on a fresh mount, while logout cleared only the auth token --
so user B opening the same workspace in A's tab silently redeemed A's
retained key under B's account.

Two complementary fixes, in a new retainedShareKeys.ts module (so useAuth
does not import the workspace hook):

- Entries are identity-stamped (v2 format, JSON {key, userId}) with the
  capturing session's whoami, resolved from the same stub the open is issued
  on. The retained-read path honors an entry only when the current session's
  identity matches; a definite mismatch sweeps it, a transport failure leaves
  it but does not attach the key. The common keyless open stays fully
  pipelined -- identity is only resolved when a fragment key is captured
  (async, gated against the success-discard racing it) or a stored entry
  exists (rare: only after a reload mid-retry). v1 and malformed entries read
  as absent.
- logout() sweeps the whole retention prefix (all format versions), before
  the CF Access navigate-away.

The in-memory ref tier needs no stamp: it is bounded by the editor's
lifetime (logout unmounts it; CF Access logout navigates away). Residual: a
reload before the identity stamp lands loses retention, recovered by
re-clicking the invite link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gatekeeper Changes to a gatekeeper integration kernel Changes to the Workshop kernel workshop/frontend Changes to the Workshop frontend workshop/shared Changes to shared Workshop APIs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant