Repository navigation
fix(integrations): install only what the operations agent loads - #607
Merged
Merged
Conversation
The agent loads installed integrations only from directories and manifests its own account owns and nobody else can write, and one it rejects stops it from authorizing any configuration change. The installer applied archive-declared modes and the umask, and accepted links and manifests the agent never reads, so a single install could break every trusted-configuration request. Installed files now get fixed modes (0755 directories, 0644 files) for every source kind and on rollback restore. custom_integrations is created or tightened in the state the agent trusts, and refused when another account owns it. A manifest.json that is a symbolic link, hard-linked, over 128 KiB or not UTF-8, a configuration schema the agent rejects, or an id longer than 64 characters is refused. Finally the staged tree goes through the agent's own per-integration check, now exported from core. Packaging refuses a manifest over 128 KiB.
Medformatik
force-pushed
the
fix/installer-custom-integrations-modes
branch
from
October 24, 2026 18:34
8cad0a9 to
88d3698
Compare
Integrations installed before the installer fixed its modes, for example under a umask of 002, kept group-writable directories and manifests, which the agent refuses to load. As #605 does for infra/docker, the configuration writer now removes group and other write from custom_integrations and every directory and regular file in it that its uid owns, when it takes the writer lock (before any render or request reads the integration schemas) and where it creates the directory. Entries are opened without following links and changed through the descriptor; on Linux the walk goes through the opened directories, so a swapped link is never followed. Only write bits are removed, and entries another uid owns are left for the agent to report. The agent now takes the writer before its first authority load at startup, so an upgraded agent starts on such a host.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Summary
The operations agent loads an installed integration only when:
custom_integrations/, the integration's directory and itsmanifest.jsonare owned by the agent's account and not writable by group or others;configSchemapasses the agent's schema check.If any installed integration fails these checks, the agent rejects its whole integration registry ("Trusted integration registry rejected"). Every trusted-configuration request and every
compose renderthen fails.The installer guaranteed none of this:
installIntegrationcreatedcustom_integrations/under the umask, so 0775 with umask 002;manifest.json, manifests up to 1 MiB, and schemas the agent rejects.The breakage is real: with main's installer, one archive declaring 0666/0664 modes, installed under umask 002, makes the next render fail with "Trusted integration registry rejected".
What the installer does now
custom_integrations/itself. The installer creates it at 0755, or removes group/other write from it when it already exists and the installing account owns it. It refuses with a clear message when another account owns it.manifest.jsonthat is a symlink, hardlinked in either direction, or larger than 128 KiB;configSchemathe agent does not accept;assertTrustedIntegrationDirectory. This is the loader's per-integration check, now exported from core so the installer and the loader share one definition.openmapx-ext packagerefuses a manifest larger than 128 KiB, since no OpenMapX agent would load it. Changeset:@openmapx/extension-clipatch.servicesnamespace added 233 KB to the CLI bundle.custom_integrationskeeps its owner and mode checks: the loader is unchanged apart from sharing its per-integration read.Integrations installed before this fix are not tightened
An earlier revision of this PR had the configuration writer remove group and other write from earlier installs, as #605 does for
infra/docker. I reverted it: removing write bits afterwards can't prove nobody else wrote the files, so it would turn "possibly written by someone else" into "trusted". Limiting it to the owner's private group isn't reliable either, because inside the agent container/etc/groupisn't the host's.Integrations installed before this fix with group-writable files therefore still stop the agent until they are installed again; the release note says so. The follow-up below removes the dependency on modes altogether.
Assessment: trusting integrations by digest, as #605 does for community services
This PR doesn't do it, because it isn't contained:
integrations install, which has no agent client;custom_integrationsat all.Follow-up: move integration installs into the agent, as for community repositories, then drop the owner and mode checks. The agent would record each manifest's SHA-256 in
infra/docker/state/ops-agent/, which app-api can't write.Residual risks
These are limits of the loader that the installer can't fully guard, because they depend on the set of integrations as a whole:
pnpm openmapx integrations installunder umask 002, still breaks the agent. The release note tells operators to install it again, which replaces its files.Validation
Unit tests in
packages/cli/__tests__/cli-integrations.test.ts, which drive the real installer. Each case ends by asking the agent's loader (loadTrustedIntegrationSchemas) what it loads:manifest.jsonhardlinked to another entry, and one with another entry hardlinked tomanifest.json, are both refused, and the previously installed integration still loads;manifest.json, from an archive and from a local source, is refused;examples) and a 65-character id are each refused;custom_integrations/the installer owns is tightened to 0755;Against main's installer, 8 of these 9 tests fail. The one that passes on both documents existing behaviour: a local source's hardlinked manifest is copied as its own file.
End-to-end in
scripts/ops-agent-checkout-trust.test.ts, on a checkout cloned under umask 002:manifest.json;region: "eu"against its schema).With main's installer, that render fails with "Trusted integration registry rejected". I moved the agent start and the request into helpers that the existing umask-002 test now uses too.
Suites: Vitest for core, integration-framework, cli, extension-cli,
apps/api/src/services, ops-agent and scripts: 579 files and 8,971 tests passed, 97 skipped.node:24.22.1-bookworm, usernode, umask 002), the installer, core services, integration-framework and agent authority suites passed: 81 files, 838 tests.Checks:
check-typesfor core, integration-framework, cli, extension-cli, api, ops-agent and data-manager;check-ops-authorityandcheck-dockerfile-sync;Not run: an image build or a deployment, and an install through the admin Store against a running stack.
Release and upgrade impact
Product impact: security
The pending fragment
release-notes/release-community-and-integration-trust.mdnow says what the installer guarantees. Its required action gains a recovery step: reinstall an integration installed earlier that breaks the agent.Changeset
.changeset/integration-manifest-limit.md:@openmapx/extension-clipatch. Packaging refuses a manifest larger than 128 KiB.