Skip to content

fix(drive): require upload access to probe a folder for filenames - #603

Open
ebrahimgamdiwala wants to merge 1 commit into
frappe:developfrom
ebrahimgamdiwala:fix/drive-upload-helper-permissions
Open

fix(drive): require upload access to probe a folder for filenames#603
ebrahimgamdiwala wants to merge 1 commit into
frappe:developfrom
ebrahimgamdiwala:fix/drive-upload-helper-permissions

Conversation

@ebrahimgamdiwala

@ebrahimgamdiwala ebrahimgamdiwala commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

does_entity_exist and get_new_title were whitelisted with no permission check. Both take a folder id straight from the caller, so any logged-in user could ask whether a given filename exists in any folder on the site:

/api/method/suite.drive.api.files.does_entity_exist?name=<name>&folder=<id>

get_new_title leaks strictly more - on an exact match it returns the name suffixed with a count of the matching siblings, so the reply also reveals how many similarly named files the folder holds.

Filenames are content. The realistic caller is not someone guessing ids, it is someone whose access was revoked: folder ids are learned while shared and outlive the grant, so an unshared user can keep polling a folder they can no longer open and keep getting answers.

Both now resolve the folder against upload.

Why upload and not read: these serve only the uploader naming a file it is about to write, and every principal the share dialog grants upload also holds read (ShareDialog.vue sets read: 1 on all three presets), so upload is the narrower gate of the two - it refuses everyone read would, plus view-only users who never call these. It also puts them in agreement with upload_file and can_create_in_folder, which resolve the same folder at the same level.

The guard cannot break an upload that would otherwise succeed: upload_file already applies the identical check, with the same default-folder resolution and the same message, moments later in the same flow.

suite/writer/api/docs.py calls get_new_title. It already checked the same level on the same folder two lines below; the check is moved above the call so Writer keeps its own error message and skips a lookup for a caller it is about to turn away. No behavioural change - the same callers are refused.

Adds 4 regression tests to the existing integration suite; the 2 covering the unshared and revoked callers were verified failing without this change.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

`does_entity_exist` and `get_new_title` were whitelisted with no permission
check. Both take a folder id straight from the caller, so any logged-in user
could ask whether a given filename exists in any folder on the site:

    /api/method/suite.drive.api.files.does_entity_exist?name=<name>&folder=<id>

`get_new_title` leaks strictly more - on an exact match it returns the name
suffixed with a count of the matching siblings, so the reply also reveals how
many similarly named files the folder holds.

Filenames are content. The realistic caller is not someone guessing ids, it
is someone whose access was revoked: folder ids are learned while shared and
outlive the grant, so an unshared user can keep polling a folder they can no
longer open and keep getting answers.

Both now resolve the folder against `upload`.

Why `upload` and not `read`: these serve only the uploader naming a file it
is about to write, and every principal the share dialog grants `upload` also
holds `read` (`ShareDialog.vue` sets `read: 1` on all three presets), so
`upload` is the narrower gate of the two - it refuses everyone `read` would,
plus view-only users who never call these. It also puts them in agreement
with `upload_file` and `can_create_in_folder`, which resolve the same folder
at the same level.

The guard cannot break an upload that would otherwise succeed: `upload_file`
already applies the identical check, with the same default-folder resolution
and the same message, moments later in the same flow.

`suite/writer/api/docs.py` calls `get_new_title`. It already checked the same
level on the same folder two lines below; the check is moved above the call
so Writer keeps its own error message and skips a lookup for a caller it is
about to turn away. No behavioural change - the same callers are refused.

Adds 4 regression tests to the existing integration suite; the 2 covering the
unshared and revoked callers were verified failing without this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The PR appears safe to merge, with only non-blocking request-validation hardening needed on the modified endpoints.

The authorization behavior is consistent with existing upload flows, but malformed API argument types can still reach permission and database helpers without controlled validation.

Files Needing Attention: suite/drive/api/files.py

Fix All in Claude Code Fix All in Codex

Reviews (1): Last reviewed commit: "fix(drive): require upload access to pro..." | Re-trigger Greptile

Comment thread suite/drive/api/files.py

@frappe.whitelist()
def does_entity_exist(name: str | None = None, folder: str | None = None):
"""Whether `folder` already holds a file called `name`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Validate whitelisted string inputs

These endpoints still pass caller-controlled name, folder, title, and parent_name values into permission and database helpers without runtime type validation, producing inconsistent framework errors for malformed requests.

Context Used: Guidelines for reviewing Frappe Framework applicat... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Frappe already applies runtime type validation to whitelisted arguments. frappe.whitelist() wraps the function in validate_argument_types, which validates every argument against its annotation with pydantic and raises FrappeTypeError before the function body runs.

Verified against these two endpoints:

folder={'read': 0}  → FrappeTypeError: Argument 'folder' in 'suite.drive.api.files.does_entity_exist' should be of type 'str | None' but got 'dict'
folder=['a', 'b']   → FrappeTypeError: ... but got 'list'
folder=12345        → FrappeTypeError: ... but got 'int'

All three are rejected before reaching user_has_permission or frappe.db.exists. The str | None annotations on name, folder, title and parent_name are the validation — adding manual isinstance checks would be dead code.

The one thing in this area that does differ: a well-formed id naming no record surfaces DoesNotExistError rather than PermissionError. That matches upload_file, which resolves the same folder at the same permission level a moment later in the same flow, so the two endpoints stay consistent with each other. Leaving it as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant