fix(security): fail closed when an agent-conversation webhook secret is unset - #208
Open
massimoalbarello wants to merge 1 commit into
Open
fix(security): fail closed when an agent-conversation webhook secret is unset#208massimoalbarello wants to merge 1 commit into
massimoalbarello wants to merge 1 commit into
Conversation
…is unset The webhook secret check was skipped entirely when a connection carried no webhookSecret in its metadata, so a connection created outside the bootstrap script accepted unauthenticated writes into the knowledge pipeline. The secret is now required by MetadataSchema and always compared. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
The agent-conversations webhook verified its shared secret only when one happened to be configured:
If a connection's metadata carried no
webhookSecret, the guard short-circuited and every payload was accepted.MetadataSchemadeclared the field.optional(), so that state was well-formed rather than a parse error.Why it matters — this sync is reachable from Nango's public ingress:
nango/packages/server/lib/routes.public.ts:183registers/webhook/:environmentUuid/:providerConfigKeyonpublicAPIbehind only a rate limiter. Against a secret-less connection, anyone who knows the environment UUID and provider config key can push arbitraryAgentConversationrecords straight intobrain.records, which then feed the knowledge-distillation pipeline — so this is a knowledge-poisoning path, not just spurious rows.Scope of exposure — the deployed connections are not currently affected.
_scripts/lib/bootstrap-connections.ts:27reads the secret viarequiredValue(), which throws on a missing or emptyAGENT_SYNC_WEBHOOK_SECRET, so every bootstrapped connection has one. The gap opens only for connections created out-of-band (Nango UI or API). I'm treating "the deploy script happens to set it" as an accidental mitigation rather than a control, which is why this fixes the check itself.Fix —
webhookSecretbecomes required (.min(1)), soparseMetadatathrows before any record is written when it is absent, and the comparison is now unconditional.Verified by reverting the source change and re-running the new tests: the no-secret case saves records against the old code and rejects against the new one. The wrong-secret test passes either way and is there as a regression guard.
Note this is intentionally a breaking change for any existing secret-less connection — it will now error on webhook delivery until a secret is set, which is the fail-closed behaviour. Constant-time comparison was deliberately left out:
node:cryptois unused anywhere innango-integrationsand a remote timing attack on this value isn't practical, so pulling it in would have widened the diff for no real gain.