n8n - 💡(How to fix) Fix Bug: Source control (git sync) overwrites credential port numbers, blocking per-environment values [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
n8n-io/n8n#29560Fetched 2026-05-01 05:52:32
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×3commented ×1mentioned ×1subscribed ×1

Root Cause

sanitizeCredentialData in packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts deliberately strips strings to '' but preserves numbers and booleans verbatim:

// packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts
export function sanitizeCredentialData(
  data: ICredentialDataDecryptedObject,
): ICredentialDataDecryptedObject {
  const result: ICredentialDataDecryptedObject = deepCopy(data);

  for (const [key, value] of Object.entries(data)) {
    if (value === null || key === 'oauthTokenData') {
      delete result[key];
    } else if (typeof value === 'object') {
      result[key] = sanitizeCredentialData(value as ICredentialDataDecryptedObject);
    } else if (typeof value === 'string') {
      result[key] = containsExpression(value) ? value : '';
    }

    // NOTE: number and boolean values are synchable for backward compatibility
    // Typically numbers represent PORT numbers or other numeric values that aren't sensitives
    // Boolean are usually represent non sensitive flags
    // This could be revisited in the future
  }

  return result;
}

The accompanying comment explicitly anticipates this: numbers ("typically PORT numbers") are intentionally pushed through, which is exactly what makes per-environment ports impossible. Introduced/last touched in #25530 ("fix: Enable credentials expressions push and pull").

Confirmed in a real sync repo: a Microsoft SQL stub serializes as

{
  "type": "microsoftSql",
  "data": { "server": "", "user": "", "password": "", "port": 1533, "requestTimeout": 15000000 }
}

— strings blanked, numbers preserved.

The Microsoft SQL credential definition (packages/nodes-base/credentials/MicrosoftSql.credentials.ts) declares port as type: 'number', which is why this hits SQL credentials in particular, but the issue is general — any numeric or boolean field on any credential will behave the same way.

Code Example

// packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts
export function sanitizeCredentialData(
  data: ICredentialDataDecryptedObject,
): ICredentialDataDecryptedObject {
  const result: ICredentialDataDecryptedObject = deepCopy(data);

  for (const [key, value] of Object.entries(data)) {
    if (value === null || key === 'oauthTokenData') {
      delete result[key];
    } else if (typeof value === 'object') {
      result[key] = sanitizeCredentialData(value as ICredentialDataDecryptedObject);
    } else if (typeof value === 'string') {
      result[key] = containsExpression(value) ? value : '';
    }

    // NOTE: number and boolean values are synchable for backward compatibility
    // Typically numbers represent PORT numbers or other numeric values that aren't sensitives
    // Boolean are usually represent non sensitive flags
    // This could be revisited in the future
  }

  return result;
}

---

{
  "type": "microsoftSql",
  "data": { "server": "", "user": "", "password": "", "port": 1533, "requestTimeout": 15000000 }
}
RAW_BUFFERClick to expand / collapse

Bug Description

When using Source Control (git sync) to sync credentials between environments, numeric and boolean credential fields (notably port, but also flags like allowUnauthorizedCerts and numbers like requestTimeout) are pushed and pulled as part of the credential stub. This makes it impossible to maintain different ports for the same credential type in different environments, and any subsequent push from the source instance silently overwrites the local value on the pulling instance.

Concrete example: Microsoft SQL credentials.

  • Instance A creates a Microsoft SQL credential with port 1533.
  • A pushes — stub committed with "port": 1533.
  • Instance B pulls — gets a credential with empty strings (server/user/password) but "port": 1533 already filled in from A.
  • Operator on B does initial setup on B: fills in server/user/password and sets B's own port 1433.
  • Some time later A pushes again (e.g. unrelated edit, or because port shows as "modified" in the dialog with credentials checked by default).
  • B pulls — B's port is silently overwritten back to 1533, because numbers are not sanitized during sync.

The same applies to any credential where a numeric or boolean field legitimately differs per environment (any DB port, custom HTTP port, timeouts, TLS toggles, etc.).

Likely related: #13966 — same root cause causes the credential not to be flagged as "initial setup required" on the pulling instance, because the synced stub already has non-empty port/allowUnauthorizedCerts values, so it doesn't look empty.

To Reproduce

  1. Connect two n8n instances (A and B) to the same source control repository.
  2. On A, create a Microsoft SQL credential with port 1533 and full server/user/password.
  3. Push from A.
  4. On B, pull. The credential appears with empty server/user/password but port: 1533 already set.
  5. On B, do the initial setup: fill server/user/password and change port to 1433.
  6. On A, edit the credential (or just trigger a push — the credential will appear as a modified candidate, checked by default).
  7. Push from A.
  8. On B, pull → B's port is silently changed back to 1533. B's connection breaks.

Expected behavior

Numeric and boolean connection fields that legitimately differ per environment should not be force-synced. At minimum:

  • Numbers (and booleans) should be sanitized the same way string values are: only the schema/key is exported, the value is preserved per environment on pull.
  • Or, credential field metadata should let node authors mark fields as "environment-local" so they are excluded from the synced stub (e.g. an opt-in noSync: true on INodeProperties).

Either way, after a sync the local value of port (and similar fields) on the pulling instance must not be silently overwritten. As a side effect this would also fix #13966, since with port/flags excluded the freshly-pulled stub would look empty and could properly trigger "initial setup required".

Root cause

sanitizeCredentialData in packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts deliberately strips strings to '' but preserves numbers and booleans verbatim:

// packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts
export function sanitizeCredentialData(
  data: ICredentialDataDecryptedObject,
): ICredentialDataDecryptedObject {
  const result: ICredentialDataDecryptedObject = deepCopy(data);

  for (const [key, value] of Object.entries(data)) {
    if (value === null || key === 'oauthTokenData') {
      delete result[key];
    } else if (typeof value === 'object') {
      result[key] = sanitizeCredentialData(value as ICredentialDataDecryptedObject);
    } else if (typeof value === 'string') {
      result[key] = containsExpression(value) ? value : '';
    }

    // NOTE: number and boolean values are synchable for backward compatibility
    // Typically numbers represent PORT numbers or other numeric values that aren't sensitives
    // Boolean are usually represent non sensitive flags
    // This could be revisited in the future
  }

  return result;
}

The accompanying comment explicitly anticipates this: numbers ("typically PORT numbers") are intentionally pushed through, which is exactly what makes per-environment ports impossible. Introduced/last touched in #25530 ("fix: Enable credentials expressions push and pull").

Confirmed in a real sync repo: a Microsoft SQL stub serializes as

{
  "type": "microsoftSql",
  "data": { "server": "", "user": "", "password": "", "port": 1533, "requestTimeout": 15000000 }
}

— strings blanked, numbers preserved.

The Microsoft SQL credential definition (packages/nodes-base/credentials/MicrosoftSql.credentials.ts) declares port as type: 'number', which is why this hits SQL credentials in particular, but the issue is general — any numeric or boolean field on any credential will behave the same way.

Suggested fix directions

  • Treat numbers and booleans the same as strings during sanitization (export schema only, preserve local value on pull). Simplest fix and matches the existing string behavior. Also fixes #13966 as a side effect.
  • Or, add an opt-in flag on INodeProperties (e.g. noSync: true / environmentLocal: true) so credential authors can mark fields that must not cross environments. port, connectTimeout, requestTimeout, tls, allowUnauthorizedCerts etc. on MicrosoftSql would then be marked accordingly.

The current backward-compatibility argument should be revisited — the comment in the code itself says "This could be revisited in the future," and the practical cost of the current behavior is that multi-environment setups with differing ports are unusable through git sync.

Debug Info

  • n8n version: 2.18.4 (also reproducible on earlier versions; behavior present since #25530)
  • Source control: Enterprise git sync feature
  • Affected file: `packages/cli/src/modules/source-control.ee/source-control-helper.ee.ts` (`sanitizeCredentialData`)
  • Affected credential (example): `packages/nodes-base/credentials/MicrosoftSql.credentials.ts`
  • Related: #13966

Environment

  • Operating System: Linux (self-hosted on GKE)
  • n8n Version: 2.18.4
  • Node.js Version: 24.x
  • Database: PostgreSQL
  • Execution mode: queue
  • Hosting: self hosted

extent analysis

TL;DR

The most likely fix is to modify the sanitizeCredentialData function to treat numbers and booleans the same as strings during sanitization, preserving local values on pull.

Guidance

  • Review the sanitizeCredentialData function in source-control-helper.ee.ts to understand how it handles numeric and boolean credential fields.
  • Consider modifying the function to sanitize numbers and booleans in the same way as strings, by setting them to a default value (e.g., empty string or null) during export, and preserving the local value on pull.
  • Alternatively, explore adding an opt-in flag (e.g., noSync: true or environmentLocal: true) to INodeProperties to allow credential authors to mark fields that should not be synced across environments.
  • Verify the fix by testing the git sync feature with a Microsoft SQL credential and confirming that the local port value is preserved after a pull.

Example

// Modified sanitizeCredentialData function
export function sanitizeCredentialData(
  data: ICredentialDataDecryptedObject,
): ICredentialDataDecryptedObject {
  const result: ICredentialDataDecryptedObject = deepCopy(data);

  for (const [key, value] of Object.entries(data)) {
    if (value === null || key === 'oauthTokenData') {
      delete result[key];
    } else if (typeof value === 'object') {
      result[key] = sanitizeCredentialData(value as ICredentialDataDecryptedObject);
    } else if (typeof value === 'string') {
      result[key] = containsExpression(value) ? value : '';
    } else if (typeof value === 'number' || typeof value === 'boolean') {
      // Sanitize numbers and booleans like strings
      result[key] = '';
    }
  }

  return result;
}

Notes

The suggested fix directions should be revisited to ensure backward compatibility and consider the practical implications of

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Numeric and boolean connection fields that legitimately differ per environment should not be force-synced. At minimum:

  • Numbers (and booleans) should be sanitized the same way string values are: only the schema/key is exported, the value is preserved per environment on pull.
  • Or, credential field metadata should let node authors mark fields as "environment-local" so they are excluded from the synced stub (e.g. an opt-in noSync: true on INodeProperties).

Either way, after a sync the local value of port (and similar fields) on the pulling instance must not be silently overwritten. As a side effect this would also fix #13966, since with port/flags excluded the freshly-pulled stub would look empty and could properly trigger "initial setup required".

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING