GitHub
11/07/2026, 4:59 PMosquery_nodes.environment, node_posture.environment), but neither rename nor delete updated them:
• Rename changed only tls_environments.name. The environment's nodes stayed under the old name.
• Delete removed the environment and left its nodes behind.
Either way, the old name ended up free while nodes still carried it. Anyone who could create an environment with that name, including an environment manager since #1144, became its env admin and inherited the orphaned fleet: node lists, posture, and query and carve targeting. Renames also silently emptied the renamed environment.
Change
• Rename moves the data. New EnvManager.UpdateFields(env, patch) applies the patch and, on a name change, moves osquery_nodes and node_posture rows to the new name, all in one transaction. PATCH /api/v1/environments/{env} uses it. The old shortcut: comment is removed.
• Delete is refused while nodes remain. EnvManager.Delete returns ErrEnvHasNodes. DELETE /api/v1/environments/{env} and the delete action on POST /api/v1/environments return 409. Soft-deleted nodes don't block. The CLI gets the same refusal through the shared package.
• Names carried by orphaned nodes can't be reused. Create and rename return ErrNameHasNodes (409) if any live node still carries the target name. This also covers nodes orphaned before this fix in databases that are already deployed.
Security / operational impact
• Closes an escalation path: recreating a renamed or deleted environment's name to inherit its nodes.
• Behavior change for operators: deleting an environment that still has enrolled nodes now fails with 409. Remove or move its nodes first.
• Existing orphans are not migrated. They stay orphaned, but nobody can adopt them by creating an environment with that name.
• Not covered: logging tables (osquery_status_data, osquery_result_data) keep the old name after a rename. They're only used for retention cleanup by name, so those rows won't be pruned by the renamed environment's per-environment retention. Not checked: whether TLS's Redis node cache holds the old environment name until it expires.
Validation
• New `pkg/environments/node_names_test.go`:
• delete refused with live nodes and allowed with only soft-deleted ones;
• create refused for an orphaned name;
• rename moves nodes and posture rows and leaves other environments untouched;
• a rename to an orphaned name is refused with nothing partially applied.
• New TestEnvNodesNeverOrphanedByRenameOrDelete covers the full chain through the handlers: 409 on both delete routes; the rename moves the node; recreating the old name gives an empty environment; 409 for a name with orphaned nodes.
• The new tests caught a real bug during development. GORM's Updates writes the new name back into the environment record, so without saving the old name first the migration would have moved zero nodes.
• `go test ./...`: pass
• `gofmt`: clean
• `make openapi-check`: clean (409 responses were already documented)
jmpsec/osctrlGitHub
11/07/2026, 5:30 PM