Skip to content

Add a folder-scoped Google Drive resource - #440

Open
ndisidore wants to merge 1 commit into
mainfrom
feat/gk-google-drive-folder
Open

Add a folder-scoped Google Drive resource#440
ndisidore wants to merge 1 commit into
mainfrom
feat/gk-google-drive-folder

Conversation

@ndisidore

@ndisidore ndisidore commented Sep 3, 2026

Copy link
Copy Markdown
Member

Adds a fourth Google Drive resource, scoped to one folder and everything currently
beneath it, read-only. It covers a folder in My Drive, including one someone else
shared with you, and an ordinary subfolder inside a shared drive; a shared drive's
own root stays with the existing Shared Drive resource. It requests the same three
read-only scopes as the account and exact-file resources rather than the wider
drive.readonly that Shared Drive needs.

Folder-derived Doc and Sheet sessions re-prove ancestry on every method call, not only
at open, so a file moved out of the subtree stops answering through a session that is
already open. Drive has no ancestry-plus-content transaction, so a move landing after
the final check still returns and the next read denies.

2026-09-03_18-59
Devin Review

@github-actions github-actions Bot added the gatekeeper Changes to a gatekeeper integration label Sep 3, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

@ndisidore
ndisidore force-pushed the feat/gk-google-drive-folder branch from 24a03b3 to 7256fd6 Compare September 3, 2026 22:56
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Preview: pr440-feat-gk-googl-62f8970b

https://pr440-feat-gk-googl-62f8970b-router.cloudflare-os-previews.workers.dev

Dashboard · deleted when this PR closes

@ask-bonk

ask-bonk Bot commented Sep 3, 2026

Copy link
Copy Markdown
  • High: drive-session.ts:470-472 returns unaudited empty pages after filtering. This leaks out-of-scope corpus page counts and enables search-term probing outside the bound folder.
  • Medium: drive-session.ts:419 revalidates only the root. A directParentId moved outside the subtree after cursor creation produces misleading empty results and can permanently latch observer admission closed. Revalidate it on every page.

github run

Confines the existing read-only GoogleDriveSession API to one selected
folder's current descendant subtree, for both My Drive folders and
ordinary subfolders inside a shared drive. A shared drive's own root
stays with the Shared Drive resource.

Drive v3 offers no folder corpus, no folder-scoped token, and no
recursive ancestor predicate -- `'<id>' in parents` means direct
children only -- so membership is proved in the gatekeeper.
drive-folder-scope.ts walks each candidate's parent chain upward through
freshly batched files.get metadata, level by level, admitting only
acyclic same-domain chains of live folders that reach the root within
Drive's 101-hop nesting limit, then re-reads every node on the surviving
paths immediately before disclosure. Nothing is cached across the
operation that proved it: a hierarchy change rotates no credential and
bumps no cache generation.

The grant identity is an internal `/_resource/folder/:folderId` path
rather than the natural browser URL, because `/drive/folders/:driveId`
is already the shared drive's permanent identity and its pattern leaves
the query component wildcard -- a query-qualified variant would match
both resources and make resource selection order-dependent. describe()
still reports the natural URL for the UI.

Folder-derived Doc and Sheet children now revalidate: DriveSessionCore
.nativeRead() re-proves ancestry and the exact native MIME type before
the provider is contacted, re-checks the proved chain before any
approval, and discards the fetched value if either fails. Drive has no
ancestry-plus-content transaction, so a move landing after that final
check still returns; the next read denies, and the method says so.

CursorPager's authorize() now takes `exhausted` and its page budget is
maxProviderPagesPerCall: reaching the bound authorizes the empty
nonterminal page and returns [] rather than throwing, so a folder cursor
can filter a whole page without claiming there are no results. Only an
exhausted cursor reaches the empty-search path, which now fences
observer admission through ObserverTracker.prepareWithheld() -- a
withheld read registers no tracked set, so addObserver would otherwise
verify a candidate against nothing. The Drive observer denial no longer
names a file ID.
@ndisidore
ndisidore force-pushed the feat/gk-google-drive-folder branch from 7256fd6 to 9ae99a3 Compare September 4, 2026 16:16

@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 1 new potential issue.

3 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

try {
return JSON.parse(text);
} catch {
throw new Error(`Google Drive ${context} was not valid JSON (${text.length} bytes)`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Malformed responses report incorrect sizes

For malformed non-ASCII JSON, text.length counts UTF-16 units instead of response bytes. The Drive error therefore reports the wrong body size.

Prompt for agents
The new parseDriveJson error labels text.length as a byte count, but JavaScript string length counts UTF-16 code units. Preserve the raw byte count while reading the response and batch part, or change the diagnostic to accurately describe a character/code-unit count. Account for both regular Drive responses and multipart batch bodies.
Devin Review

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

@ask-bonk

ask-bonk Bot commented Sep 4, 2026

Copy link
Copy Markdown
  • Medium: drive-folder-scope.ts:67 accepts metadata-only limited-access folders. Google returns empty child listings for these; require and verify capabilities.canListChildren.
  • Medium: drive-session.ts:528 exposes failed ancestry probes without authorization or observer fencing. A gadget can persist this membership bit for later collaborators; use the withheld-observation path before throwing.

github run

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant