Skip to content

Automation flow write routes (POST/PUT/DELETE /api/v1/automation) lack the manage_metadata gate — a plain tenant edits/deletes flows for every organization on a walled shared-database deployment #10145

Description

@baozhoutao

Summary

On the walled single-database hosted-SaaS shape (OS_TENANCY_POSTURE=isolated, HotCRM as composed artifact, OS_AI_STUDIO_AGENTS=ask), the automation-domain write routes are not gated by manage_metadata. A plain tenant org owner — holding organization_admin only, demonstrably without manage_metadata / studio.access — can create, modify and delete automation flows. Flow metadata is environment-scoped, not organization-scoped, so one tenant's write lands on the layer every organization runs on: a flow one tenant deletes vanishes for all tenants and the platform admin; a flow one tenant creates appears for all of them.

This satisfies both of the deployment's highest-severity criteria at once: metadata is mutable by a tenant, and the mutation crosses the tenant wall.

Measured over HTTP (fresh composed boot, framework 8798cd2, hotcrm 3940736)

Same northwind session (org owner, no manage_metadata) — the control that proves the account is unprivileged:

  • PUT /api/v1/meta/:type/:name, POST /api/v1/ai/tools/:tool/execute, POST /api/v1/packages/* → all 403.

Yet on the automation domain:

POST   /api/v1/automation  {name:'probe_flow_x',label:'Probe Flow X',type:'autolaunched',nodes:[],edges:[]}
  → 200 {success:true, data:{name:'probe_flow_x',…}}          # created
PUT    /api/v1/automation/probe_flow_x  {…}
  → 200 {success:true}                                        # modified
DELETE /api/v1/automation/lead_auto_assignment
  → 200 {success:true, data:{name:'lead_auto_assignment', deleted:true}}   # deleted a HotCRM-shipped flow

Cross-tenant blast radius, verified by reading back as three different principals:

flow northwind (actor) contoso (unrelated tenant) founder (platform admin)
lead_auto_assignment (deleted) 404 404 404
probe_flow_x (injected) 200 200 200

So one tenant deleted a flow out from under every other tenant, and injected a flow into the shared layer that every tenant now sees.

Root cause

The /api/v1/automation POST/PUT/DELETE handlers register without the manage_metadata authorization the parallel /api/v1/meta/* writes carry (route ledger: packages/runtime/src/route-ledger.ts:317,329,330; the /meta/* gate is manage_metadata per packages/rest/src/rest-server.ts). A flow is authored metadata registered at environment scope, so an ungated write is both a privilege escalation and a cross-tenant one on any multi-organization deployment.

Requested fix

Gate the automation-domain write routes with manage_metadata, identical to /api/v1/meta/*. On a walled posture, environment-scoped metadata writes must be refused for principals without manage_metadata — the same rule that already makes /meta/*, /packages/* and the AI authoring tools 403 for a tenant on this shape.

Found during a hosted-SaaS lockdown test pass. Same batch as #10131 and #10132 (walled-shape org-attribution / catalog-visibility defects).

Activity

  1. added theissue type on Aug 20, 2026
  2. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Triage: fix lands at the route-registration/gating layer (packages/runtime/src/route-ledger.ts + the rest-server gate wiring, per the card's own root cause) ⇒ domain:cli + security, type Bug (already set), pm:queue, target:v17 — release-blocker classes ① and ② both hit: a published-surface defect a customer on the walled shape hits today, and cross-tenant metadata mutability is the declared contract of that shape being violated (the parallel /meta/* writes already carry the gate).

    Priority note: measured cross-tenant metadata write+delete by an unprivileged tenant is the top of the current v17 board; the Priority: Urgent field is consistent with that. Dispatch note: gating previously-open write routes narrows the accepted caller set ⇒ expect Clause-②: yes at claim; refusal tests assert the ADR-0112 envelope, mirroring the /meta/* gate's shape. Same lockdown batch: cloud#1452 (UI half), cloud#1453 (Ask posture) — coordination stays with the cloud seat; this card is the backend fix and blocks nothing on them.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions