Stop cleanup() and removeInactiveDevices from touching other tenants - #157
Open
Arsenii-Malov wants to merge 1 commit into
Open
Stop cleanup() and removeInactiveDevices from touching other tenants#157Arsenii-Malov wants to merge 1 commit into
Arsenii-Malov wants to merge 1 commit into
Conversation
All tenant servers share one MongoDB database and are separated only by the
`domain` field, but both of these routines are written for upstream's "one
server owns the whole database" assumption and act on everything they can see.
cleanup() runs unconditionally on every boot. It rewrote every tenant's user
and mesh documents, and ended with
deleteMany({ meshid: { $exists: true, $nin: meshlist } })
which deletes anything whose device group is not in the list it had just read.
That statement sat outside the `if (err == null && docs.length > 0)` guard, so a
transient read error left meshlist empty and `$nin: []` matched every document
with a meshid — one boot could wipe every tenant's nodes. Reproduced on a
two-domain database: a tenant A boot deleted a tenant B device.
The call is now gated out of OpenFrame mode and the delete is removed from the
MongoDB branch entirely, so it cannot fire if the image is run without the flag.
What cleanup() repairs (pre-1.0 field formats, legacy event/power/smbios rows in
the main collection) never existed in this database; routine housekeeping is done
by RemoveMeshDocuments(), the explicit Remove() calls on device deletion, and the
TTL indexes.
removeInactiveDevices() runs hourly. It collected domains out of the fleet-wide
device-group cache, so one tenant enabling expireDevs made all servers delete
that tenant's devices — using the local pod's connectivity state, which never
holds another tenant's agents, so a foreign pod could delete a device that was
online on its own. It is now limited to this pod's domain.
Adds a write guard as a second layer: Set/SetUser/Remove refuse documents whose
domain is not ours, with OPENFRAME_CROSS_TENANT_WRITES=allow as a rollback
switch. It is the only way to show "zero writes outside our domain" in
production, since these writes never pass through the gateway.
Tests run against a real two-domain database; see test/README.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WAcuUJKfVLEX4KpvpKd1pS
kirill-567
approved these changes
Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
All tenant servers share one MongoDB database and are separated only by the
domainfield, but both of these routines are written for upstream's "one server owns the whole database" assumption and act on everything they can see.cleanup() runs unconditionally on every boot. It rewrote every tenant's user and mesh documents, and ended with
which deletes anything whose device group is not in the list it had just read. That statement sat outside the
if (err == null && docs.length > 0)guard, so a transient read error left meshlist empty and$nin: []matched every document with a meshid — one boot could wipe every tenant's nodes. Reproduced on a two-domain database: a tenant A boot deleted a tenant B device.The call is now gated out of OpenFrame mode and the delete is removed from the MongoDB branch entirely, so it cannot fire if the image is run without the flag. What cleanup() repairs (pre-1.0 field formats, legacy event/power/smbios rows in the main collection) never existed in this database; routine housekeeping is done by RemoveMeshDocuments(), the explicit Remove() calls on device deletion, and the TTL indexes.
removeInactiveDevices() runs hourly. It collected domains out of the fleet-wide device-group cache, so one tenant enabling expireDevs made all servers delete that tenant's devices — using the local pod's connectivity state, which never holds another tenant's agents, so a foreign pod could delete a device that was online on its own. It is now limited to this pod's domain.
Adds a write guard as a second layer: Set/SetUser/Remove refuse documents whose domain is not ours, with OPENFRAME_CROSS_TENANT_WRITES=allow as a rollback switch. It is the only way to show "zero writes outside our domain" in production, since these writes never pass through the gateway.
Tests run against a real two-domain database; see test/README.md.
Claude-Session: https://claude.ai/code/session_01WAcuUJKfVLEX4KpvpKd1pS