Skip to content

Conversation

@Airbyte-Support
Copy link

@Airbyte-Support Airbyte-Support commented Nov 27, 2025

Summary

Fixes a critical issue where JWT authentication fails when PEM-formatted private keys contain escaped newlines (\\n) instead of actual newline characters. This commonly occurs when keys are stored in configuration systems like Airbyte Cloud.

The Problem:

  • Builder connectors and declarative connectors using JwtAuthenticator with RS256 private keys fail with InvalidKeyError: Could not parse the provided public key
  • Root cause: Private keys stored in Cloud have literal \n characters instead of actual newlines
  • PyJWT's jwt.encode() cannot parse PEM keys in this format

The Solution:

  • Added normalization in JwtAuthenticator._get_secret_key() that detects PEM-style keys and converts escaped newlines to actual newlines
  • Guarded with PEM detection ("-----BEGIN" and "KEY-----") to avoid affecting HMAC secrets
  • Applied before both passphrase-protected and unencrypted key paths

Related:

Review & Testing Checklist for Human

Risk Level: 🟡 Yellow - Fix is straightforward but needs real-world validation

  • Verify PEM detection logic is robust: Check if the string matching ("-----BEGIN" and "KEY-----") could have false positives with non-PEM secrets
  • Test with actual Builder connector: Create or use an existing Builder connector with OAuth private key authentication in Airbyte Cloud to verify the fix works end-to-end
  • Consider edge cases: Are there other escape sequence scenarios beyond \\n (e.g., \r\n, double-escaping)?
  • Review unit test coverage: The test generates real RSA keys and verifies JWT signing works with escaped newlines - is this sufficient?

Test Plan

  1. Local testing (already done):

    • ✅ Verified fix works with user's actual dev key containing escaped newlines
    • ✅ Unit test passes with generated RSA keys
  2. Recommended Cloud testing:

    • Create a Builder connector with JWT auth using RS256 private key
    • Configure with a private key (Cloud will store it with escaped newlines)
    • Verify the connector's check operation succeeds
    • Compare behavior before/after CDK version bump

Notes

Summary by CodeRabbit

  • Bug Fixes

    • JWT authentication now correctly handles PEM-formatted private keys that include escaped newline sequences (e.g., "\n"), ensuring keys are parsed and used reliably.
  • Tests

    • Added a unit test verifying token creation and verification when PEM keys contain escaped newlines.

✏️ Tip: You can customize this high-level summary in your review settings.

Fixes an issue where JWT authentication fails when PEM-formatted private keys
contain escaped newlines (\n) instead of actual newline characters. This
commonly occurs when keys are stored in configuration systems like Airbyte Cloud.

The fix adds normalization logic in JwtAuthenticator._get_secret_key() that:
- Detects PEM-style keys (containing '-----BEGIN' and 'KEY-----')
- Converts escaped newlines to actual newlines before JWT signing
- Is guarded to only affect PEM keys, leaving other secret types unchanged

This resolves the same issue fixed for the Okta connector in airbytehq/airbyte#69831,
but at the CDK level so all declarative/Builder connectors using JWT authentication
with private keys benefit from the fix.

Includes a unit test that verifies JWT signing works with escaped newlines.

Co-Authored-By: syed.khadeer@airbyte.io <cloud-support@airbyte.io>
@devin-ai-integration
Copy link
Contributor

Original prompt from syed.khadeer@airbyte.io
@Devin Hi
Thread URL: https://airbytehq-team.slack.com/archives/D09QB1SEDDX/p1763952090971809

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions github-actions bot added the bug Something isn't working label Nov 27, 2025
@github-actions
Copy link

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

Testing This CDK Version

You can test this version of the CDK using the following:

# Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@devin/1764219353-fix-jwt-pem-escaped-newlines#egg=airbyte-python-cdk[dev]' --help

# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch devin/1764219353-fix-jwt-pem-escaped-newlines

Helpful Resources

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /autofix - Fixes most formatting and linting issues
  • /poetry-lock - Updates poetry.lock file
  • /test - Runs connector tests with the updated CDK
  • /prerelease - Triggers a prerelease publish with default arguments
  • /poe build - Regenerate git-committed build artifacts, such as the pydantic models which are generated from the manifest JSON schema in YAML.
  • /poe <command> - Runs any poe command in the CDK environment

📝 Edit this welcome message.

Co-Authored-By: syed.khadeer@airbyte.io <cloud-support@airbyte.io>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

Normalize escaped newline sequences in PEM-style secret keys used by the JWT authenticator before any passphrase or base64 fallback processing; add a unit test verifying signing with an escaped-newline PEM key.

Changes

Cohort / File(s) Change Summary
JWT authenticator PEM key normalization
airbyte_cdk/sources/declarative/auth/jwt.py
Added logic in _get_secret_key to detect PEM-like secrets (contain -----BEGIN and KEY-----) and replace literal \\n sequences with actual newlines before passphrase handling and base64 fallback.
JWT authentication tests
unit_tests/sources/declarative/auth/test_jwt.py
Added test_get_signed_token_with_escaped_newlines_in_pem_key which generates an RSA key, produces an escaped-newline PEM variant, constructs JwtAuthenticator (RS256), obtains a signed token, and decodes it to assert structure and payload claims.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as Caller
    participant Auth as JwtAuthenticator
    participant Crypto as KeyLoader / CryptoLib

    Client->>Auth: request_signed_token(secret, algorithm, payload)
    note right of Auth `#EFEFEF`: Secret preparation
    Auth->>Auth: inspect secret for "-----BEGIN" and "KEY-----"
    alt PEM-like secret
        Auth->>Auth: replace "\\n" → "\n"  %% escaped-newline normalization
    end
    Auth->>Auth: apply passphrase handling / base64 fallback
    Auth->>Crypto: load private key
    Crypto-->>Auth: private key object
    Auth->>Crypto: sign payload (algorithm)
    Crypto-->>Auth: signed JWT
    Auth-->>Client: return signed JWT
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Pay attention to airbyte_cdk/sources/declarative/auth/jwt.py::_get_secret_key for edge cases like keys with mixed actual and escaped newlines—should mixed forms be normalized fully or partially, wdyt?
  • Review the new unit test unit_tests/sources/declarative/auth/test_jwt.py for deterministic timing (iat/exp) and crypto backend compatibility across environments.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: handling PEM private keys with escaped newlines in the JWT authenticator, which directly aligns with the core fix implemented in the code.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/1764219353-fix-jwt-pem-escaped-newlines

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8648a53 and 68d58f4.

📒 Files selected for processing (1)
  • airbyte_cdk/sources/declarative/auth/jwt.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • airbyte_cdk/sources/declarative/auth/jwt.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Check: destination-motherduck
  • GitHub Check: Check: source-hardcoded-records
  • GitHub Check: Check: source-pokeapi
  • GitHub Check: Check: source-shopify
  • GitHub Check: Check: source-intercom
  • GitHub Check: Pytest (All, Python 3.11, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.12, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.10, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.13, Ubuntu)
  • GitHub Check: Manifest Server Docker Image Build
  • GitHub Check: SDM Docker Image Build
  • GitHub Check: Pytest (Fast)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
unit_tests/sources/declarative/auth/test_jwt.py (1)

396-436: Solid test coverage for the escaped newlines feature!

The test properly validates the end-to-end flow: generating a key, escaping newlines, signing, and verifying. The structure mirrors the existing passphrase test nicely.

One optional enhancement: you could add a more focused unit test that directly calls _get_secret_key() with an escaped PEM key and verifies the result is the properly loaded key object (similar to test_get_secret_key on lines 125-134). This would test the normalization logic more directly, wdyt? But the current integration-style test is definitely acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80b7668 and 8648a53.

📒 Files selected for processing (2)
  • airbyte_cdk/sources/declarative/auth/jwt.py (1 hunks)
  • unit_tests/sources/declarative/auth/test_jwt.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
unit_tests/sources/declarative/auth/test_jwt.py (1)
airbyte_cdk/sources/declarative/auth/jwt.py (1)
  • _get_signed_token (206-218)
🪛 GitHub Actions: Linters
airbyte_cdk/sources/declarative/auth/jwt.py

[error] 185-196: ruff format check failed. 1 file would be reformatted. Run 'poetry run ruff format .' to apply formatting changes.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Check: source-shopify
  • GitHub Check: Check: source-intercom
  • GitHub Check: Check: source-pokeapi
  • GitHub Check: Check: source-hardcoded-records
  • GitHub Check: Check: destination-motherduck
  • GitHub Check: Pytest (Fast)
  • GitHub Check: Manifest Server Docker Image Build
  • GitHub Check: SDM Docker Image Build
  • GitHub Check: Pytest (All, Python 3.12, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.13, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.10, Ubuntu)
  • GitHub Check: Pytest (All, Python 3.11, Ubuntu)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (python)

@github-actions
Copy link

PyTest Results (Fast)

3 814 tests  +1   3 802 ✅ +1   6m 24s ⏱️ -7s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 68d58f4. ± Comparison against base commit 80b7668.

@github-actions
Copy link

PyTest Results (Full)

3 817 tests  +1   3 805 ✅ +1   10m 52s ⏱️ -4s
    1 suites ±0      12 💤 ±0 
    1 files   ±0       0 ❌ ±0 

Results for commit 68d58f4. ± Comparison against base commit 80b7668.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants