GitHub
10/17/2026, 6:33 AMfile_paths. Today the only option is to change the configuration for every node in the environment.
What changes
Admins of a node's environment can now set a configuration override for that node only. The override is a small JSON layer merged on top of the environment's configuration when the node fetches it. The node keeps receiving later environment changes; removing the override returns it to the plain environment configuration.
Node page (SPA): new Configuration tab
• Views: a read-only editor with a switch between Applied (default, what the host actually gets), Environment, Node override and Diff (environment vs applied, key order ignored).
• Edit override: locked by default. Unlocking requires a confirmation dialog with an "I understand this changes osquery on this host and I know what I'm doing" checkbox.
• Expiry: optional, none by default. Expired overrides stop applying and are shown as expired until removed.
• Remove override: behind its own confirmation.
• Indicators: while an override exists, a banner on the node page says who set it, when, and when it expires or expired, and the tab shows a dot.
• Visibility: the tab is shown only to admins of the node's environment.
osctrl-api
• GET / PUT / DELETE /api/v1/nodes/{env}/node/{uuid}/config
• require AdminLevel on the node's environment, with denials audited via `denyEnv`;
• look the node up by UUID only, scoped to the environment;
• limit request bodies to 256 KiB;
• refuse an expiry that isn't in the future;
• audit every set and removal with the override before and after.
• Deleting a node, through the API or CLI database mode, also deletes its override.
• OpenAPI is regenerated.
osctrl-tls
• ConfigHandler merges the node's active override into the environment configuration. An override is active when it exists, was saved for the node's current environment and hasn't expired.
• The override is validated again at serve time. Any validation or merge error is logged, and the environment configuration is served unchanged.
pkg/nodeconfig (new)
• Model: node_config_overrides, one row per node UUID.
• Validation:
• The override must be a JSON object, and the known sections must be objects.
• null is refused anywhere.
• Options that keep the host connected to osctrl are refused:
• prefixes config_, logger_tls_, distributed_, enroll_, `tls_`;
• exact names: the carver endpoints, *_plugin, disable_distributed, disable_enrollment, disable_reenrollment, host_identifier, specified_identifier, proxy_hostname, read_max.
• Deep merge: objects merge key by key, other values replace, and new top-level keys such as file_paths are added.
• Cache (Redis):
• osctrl-api writes the node's full override state after every save or removal.
• osctrl-tls only fills a missing entry with SET NX, so it never overwrites a newer write.
• If Redis fails, the override is read from SQL.
Security and operational impact
• Authorization is server-side on all three methods, with CSRF protection on PUT and DELETE. Denials and changes are audited. Access to another environment's node returns 404.
• The connection to osctrl can't be broken by an override. Options controlling config, logger, distributed and enroll communication, TLS, and identity are refused on save and again at serve time.
• osctrl-tls's response is unchanged for nodes without an override, and the existing node/environment ownership check is untouched. With an override, osctrl-tls serves re-serialized JSON (sorted keys).
• No extra database query per config request for nodes without an override; their absence is cached for 1 hour. API changes reach every osctrl-tls replica by the node's next config fetch.
• Schema: a new table, node_config_overrides, created by AutoMigrate in osctrl-api, osctrl-tls and CLI database-mode deletes. No changes to existing tables.
• Limitations:
• Options aren't fully reverted. osquery applies options only for keys present in a config, so after removal or expiry, an option the environment doesn't set keeps its overridden value until osquery restarts. The dialogs and docs say so. Schedules, packs and file_paths do revert.
• CLI deletes have no Redis. If a node deleted from the CLI re-enrolls into the same environment within 1 hour, osctrl-tls may still serve its cached override.
• Cache write failures on save or remove are logged, not returned. A node may get its previous configuration for up to 1 hour.
• Flags overrides aren't included. They need osctrld to identify the node, which it doesn't today. Separately, file integrity monitoring on one host still needs the environment's flags to enable events.
Validation
• `go test ./... -count=1`: 56 packages pass
• `go vet ./...`; gofmt -l pkg cmd (empty); `golangci-lint run ./...`: 0 issues
• `make openapi-check`: clean
• `cd frontend && npm test`: 65 files, 505 tests pass; `npm run check`: clean
• TestRedisStoreLive run against a local Redis (`REDIS_URL=redis://localhost:6379/0`): pass
• New tests, each seen failing first:
• `pkg/nodeconfig`: validation and the protected options (each name pinned literally); merge semantics including null and empty environments; the cache protocol (absence cached, refill never overwrites a newer write, SQL fallback when Redis fails); expiry; environment mismatch.
• `cmd/tls/handlers`: merged config served; other nodes unaffected; expired and invalid stored overrides fall back; a broken environment config is served unchanged.
• `cmd/api/handlers`:
• permissions, with every denial audited;
• cross-environment access returns 404;
• a decoy node reporting the target's UUID as its hostname is never targeted;
• protected and null overrides refused;
• past expiry and oversized bodies refused;
• audit lines carry before and after;
• deleting a node removes its override.
• `cmd/cli`: both database-mode delete paths remove the override.
• Frontend: read-only by default; unlock requires the checkbox; save sends the override and expiry; server errors shown inline; cancel and remove; banner and dot; no request or tab for non-admins; Diff ignores key order.
• Playwright end-to-end tests not run; navigation changes are limited to the node page.
jmpsec/osctrlGitHub
10/17/2026, 6:42 AM