NDAgent has long supported an optional local policy — reject_dangerous_snippets — that lets the firewall itself refuse configuration pushes attempting to grant new privileges, add SSH keys, or enable remote command execution on service accounts, even from a fully authenticated session. Until now, it’s been off by default: something an operator could turn on, but not something every device shipped with. As of NDAgent v1.14.0, that changes: new installations ship with the policy on. The device starts closed, and opening it back up is a decision the device operator makes locally — not something the control plane can do on your behalf.
TL;DR
- NDAgent v1.14.0 makes
reject_dangerous_snippetsthe default for new installations — nothing to configure. - Existing installs are grandfathered: upgrading writes an explicit
false, so nothing changes silently. - Opt in early with one config line and a restart:
reject_dangerous_snippets=true. - Strengthens “the device has the final say” for configuration sync specifically — a compromised control plane or a misbehaving AI agent still can’t push a privilege grant via sync to a device that hasn’t opted out. It doesn’t touch the separate remote-session path; see the security page for that boundary.
What actually changes
This is a default change, not a removal of anything. The underlying mechanism — described in full on the Device-Side Protection page — is unchanged: a fixed, always-on rule already prevents any configuration sync from touching critical accounts (root, OPNsense administrators, the agent’s own service account, the read-only account), and reject_dangerous_snippets is the additional, opt-in layer on top of that. What’s new is which side of “opt-in” a fresh install lands on by default.
If you’re installing NDAgent for the first time on v1.14.0 or later, this protection is active immediately, with nothing to configure.
If you’re upgrading an existing device, nothing changes in behavior. If you never explicitly set reject_dangerous_snippets, the upgrade writes an explicit false into your local configuration — your current behavior is preserved exactly, just made explicit instead of implicit. We grandfathered this deliberately: some existing deployments have legitimate configuration that this policy would reject, and a silent, hard flip on upgrade would break working syncs without warning. Nobody’s sync should start failing because of an update they didn’t ask to change their security posture.
When you’re ready to move to the stricter default, it’s a one-line change to your agent’s local configuration, followed by a restart:
reject_dangerous_snippets=trueApply it whenever it fits your change process — there’s no forced migration window.
No action required for most fleets
If your change process doesn’t rely on privilege-granting snippets today, opting in early costs nothing. If you’re not sure, the grandfathering behavior means existing devices can stay exactly as they are while you apply the stricter default only where it makes sense.
What rejection actually looks like
If a configuration sync tries to grant a privilege, add an SSH key, or enable remote command execution on a service account while the policy is on, the sync doesn’t get silently trimmed — the task fails, with a message that names the specific policy that blocked it, what in the push triggered it, and how to adjust course if the change was intentional. You should never have to guess why something didn’t apply.
A rejected snippet fails the whole sync
If any part of a sync trips this policy, the entire sync task fails — not just the offending snippet. Review the failure message, adjust the snippet or your local configuration, and re-run.
This release also tightens the detection logic itself: the pattern-matching NDAgent uses to recognize a privilege-granting change was aligned with the equivalent logic already enforced server-side, closing a small gap where the two could disagree on an edge case. In practice this makes the local check at least as strict as the server-side one it’s meant to complement, not looser.
Why this matters
This directly strengthens a claim we make on our security page: the device has the final say — over what a configuration sync is allowed to contain. A compromised operator account, a compromised control plane, or an AI agent driving the MCP surface that gets prompt-injected into doing something it shouldn’t — none of them can push a privilege escalation via sync to a device running the new default, because the device itself won’t accept that sync. That’s the point of enforcing this on the device rather than trusting the thing sending the command: the guarantee holds even when something upstream of the device doesn’t.
It’s also a narrow guarantee, and worth being precise about the edge: this default says nothing about a remote session, which is a different one of the ten fixed operations and reaches a shell directly rather than through sync content. A compromised control plane, or any write-scoped credential, can still open one. See the security page for that boundary.
Why the device, not the platform, enforces this
Even valid operator credentials, or a compromised control plane, can’t flip this setting for you — it lives only in the device’s own local configuration. That’s the same principle behind Device-Side Protection generally: guarantees that hold on the device don’t depend on the platform staying honest.
To be candid about the scope: this is a default for new installations, not a retroactive change to every device already in the field. If you’re running an existing fleet and want the stricter behavior everywhere, you’ll need to set reject_dangerous_snippets=true deliberately, device by device or via your usual config-management process. We chose not to force that decision on your behalf — the same principle this whole feature is built around.
Join the discussion in our Community