Skip to content

update_schema deletes Core/System-protected schema fields with no protection-level check (guard bypass) #2194

Closed

Description

@mstomar125

Summary

handle_update_schema's remove_fields removes any field purely by name (fields.retain(|f| !remove_names.contains(&f.name))) with no SchemaProtectionLevel check, so an update_schema call can permanently delete a Core- or System-protected field that the UI and the can_delete_field guard forbid removing.

Failure scenario

On any install the built-in task schema is seeded with a status field that is SchemaProtectionLevel::Core, required:true, indexed, enum (core_schemas.rs:60-101); can_delete_field("status") returns false by design (unit-tested at schema_node.rs:563). The Svelte UI never exposes it (task-schema-form.svelte:121 filters out core/system fields). But calling the agent tool update_schema (agent/src/local_agent/tools.rs:2956 -> handle_update_schema), the gRPC UpdateSchema handler (daemon/src/services/node_service.rs:1303), or the CLI schema update with {"schema_id":"task","remove_fields":["status"]} runs the retain at schema/mod.rs:816 with no protection check. validate_schema_node (behaviors/mod.rs:1415) only checks name-uniqueness and title_template tokens (task has title_template:None), so it passes, and update_node_unchecked persists the mutilated schema. Because seed_core_schemas_if_needed (node_service/mod.rs:1344-1366) only recreates a schema whose id is entirely absent, the deleted field is never restored on reboot. Result: the Core-protected required status field is permanently gone from the task type, breaking status-enum validation and the board/status UI that read the schema field definition, and violating the documented+tested invariant that only User fields are deletable. can_delete_field/can_modify_field (schema_node.rs:316,326) exist for exactly this but are never called on any non-test path.

Location

packages/core/src/schema/mod.rs:816

Severity

important


Found via a full-repo adversarial bug sweep, independently verified by two separate skeptical reviewers reading the actual current code before filing.

Activity

  1. self-assigned this
    on Aug 21, 2026
  2. mstomar125 commented on Aug 27, 2026

    @mstomar125
    ContributorAuthor

    Fixed in #2304: added a protection-level guard (checked against SchemaProtectionLevel via can_delete_field/can_modify_field) to handle_update_schema's Phase 0 and to the NodeService::rename_schema_field/update_schema_field_friendly_name primitives, so a Core/System-protected field can no longer be removed, renamed, or relabeled through update_schema. Also moved rename-destination grammar validation ahead of the migration/persist step, so a malformed destination name is rejected before anything is written rather than after. Regression tests cover the removal, rename, friendly-name-relabel, and grammar-before-persist scenarios described here. PR is open for review, not yet merged.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions