fix(api): validate organizationId in the agent job/contract CRUD tools (#375) #381
No reviewers
Labels
No labels
area:auth
area:ci
area:db
area:infra
area:native
area:pwa
area:service
epic
feature
foundation
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
james/carol!381
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "validate-job-org-in-crud-tools"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The
update_jobfollow-up from #375 — which turned out to also close a per-user-isolation hole.The hole
The agent
create_job/update_job(and contract) tools use the sharedzJobCreate/zJobUpdateschemas, which already acceptorganizationId(added in #375). The factory write tools passed it straight to the repo unvalidated — so an agent/MCP client could link a job to another user's organization, which then leaked that org's name to the attacker via the readLEFT JOIN. (My #379 PR note that "the generic tools don't expose organizationId" was wrong — they do, through the shared DTO. This corrects it.)The REST routes were never affected — they validate via
resolveJobOrganizationLink. Only the agent/MCP surface was.The fix
Validate org ownership in the
createRow/updateRowcallbacks via the sameresolveJobOrganizationLinkresolver the routes use: a cross-user/missing org id is a 404 (don't leak existence); on a valid link the org's live name is snapshotted intocompany. Applies to both jobs and contracts, create and update. The tool descriptions now document theorganizationIdoption.Bonus:
link_job_to_orgis now fully undoableThe undo system's generic inverse for a
safe_updateisupdate_<entityType>=update_job. Now thatupdate_jobcarries and validatesorganizationId, undoing a link (vialink_job_to_orgorupdate_job) properly clears it and restores the prior company — instead of the half-apply I flagged in #379.Verification
typecheck✓ ·lint✓ ·openapi:check✓ (no spec change — agent tools aren't in the spec; the DTOs already hadorganizationId) ·api-client check✓ · semgrep ✓ 0 findings.testagainst both engines (ephemeral Postgres): 1272 passed, 0 skipped — incl. new dual-engine tests:create_job/update_jobreject a cross-userorganizationId(no write, no leak), and a link made viaupdate_jobround-trips through undo (clears the link + restores the prior company).Part of #375.
🤖 Generated with Claude Code
📊 Test coverage
Patch coverage: no testable lines changed.
Overall (
app/,lib/,db/, excluding UI per ADR-0019):Soft thresholds per ADR-0019. Coverage is informational and does not block merge.