Skip to content

An unprojected REST data query returns ORPHANED columns that no metadata declares (fields retired in an upgrade), outside any field-level rule, until os migrate apply --allow-destructive #21571

Description

@objectstack-fleet

Filing gate: ① product defect, exception class: possible data exposure. reach: public door POST /api/v1/data/crm_contact/query, measured on @objectstack/* 17.6.0 with the SQLite driver by a dev run of the repo:hotcrm seat (session_01ER8ntXZhYebyQ66aXWdjfT).

Who acts on it: objectstack triage routes it, likely to the data/REST lane and the security reviewers. ⛔ Not a claim; triage sets type and grade.

Measured

  1. hotcrm PR fix(spec): cross-validate permission object grants against declared objects #1997 retires five crm_contact fields (mailing_street, mailing_city, mailing_state, mailing_postal_code, mailing_country) in favour of one Field.address(). After booting the new metadata on a DB created by the old one, the five columns stay in the table. os migrate plan lists them as unmapped_column.
  2. POST /api/v1/data/crm_contact/query with no fields projection returns those five columns on every row, although no metadata declares them.
  3. The same query naming one of them in fields answers 400 INVALID_FIELD. So the platform knows they are not fields, and the unprojected path returns them anyway.
  4. They stay readable until an operator runs os migrate apply --allow-destructive.

Why it matters

  • Any caller with object read access sees data that no field-level rule can cover, because there is no field to attach a rule to.
  • Today's case is benign: the same address now also lives in the declared field. The class is not. A field retired because it held something sensitive stays readable through the unprojected door after its retirement.
  • The read is also undocumented: neither the query contract nor the retirement docs say an unprojected query returns undeclared columns.

Coupling to name before anyone fixes it

hotcrm's one-time conversion scripts/backfill-contact-mailing-address.ts reads the old values through exactly this path, because it is the only one that returns them. Closing the leak should come with a sanctioned way to read orphaned columns for a migration: an admin-only flag, or an os migrate export of unmapped columns. Otherwise every app that retires a field loses its data-conversion path. hotcrm will follow whatever the platform sanctions.

Duplicate check

Objectstack issues updated since 2026-07-01, state all: 5,268 issues over 100 REST pages. ⚠️ The walk stopped at the API's page ceiling, so completeness is declared, not proven. Title and body were grepped for orphan\w* column|unmapped_column|retired column…(return|read|query)|undeclared (column|field)…(return|query|read): 2 hits, #19845 (driver-turso drift detection) and #13688 (driver-sql orphan-shadow test timing). Neither is about the read path.

Dedupe words: orphaned column unprojected query undeclared field returned retired column REST read field-level security


Generated by Claude Code

Activity

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

Metadata

Metadata

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:enginepriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions