Most import features have an "update existing rows" option. The user uploads a CSV, the job matches each row on id, external_id or email, and runs an update when it finds a match. The permission check usually happens once, at the start: can this user import into this workspace? After that the job writes to whatever row it matched.
So a sales rep who can edit only their own accounts exports them, adds a few lines with IDs copied from a shared link or guessed in sequence, and imports the file. The job finds those accounts, which belong to another team, and overwrites the owner and the deal value. If the match key is email and the lookup isn't scoped to the tenant, the same file can reach contacts in a different customer's workspace.
Run the same edit check your single-record update endpoint uses on every matched row, scoped to the caller's tenant, before the write. Report rows that fail as "not found" in the error file so the import doesn't confirm which IDs exist. Imports usually run in a background job, so pass the importing user's identity into the job and check against that user. The job's service account can write anything.
To test it, take a record your limited user can't edit, put its ID in a CSV with a changed name, and import as that user. If the name changes, the import is skipping the check.
If you want the update endpoint and the import job to call one shared policy, Permit.io is one option for that kind of per-record check (full disclosure, I work with Permit.io).
Top comments (0)