Skip to content

Decryption error handling and encryption service test cases update#2724

Merged
crivetimihai merged 1 commit intomainfrom
decryption-error-handling-and-encryption-service-test-cases-update
Feb 15, 2026
Merged

Decryption error handling and encryption service test cases update#2724
crivetimihai merged 1 commit intomainfrom
decryption-error-handling-and-encryption-service-test-cases-update

Conversation

@MohanLaksh
Copy link
Copy Markdown
Collaborator

Decryption Error Handling and Encryption Service Unit Tests

Summary

This PR enhances the encryption service with robust error handling, clear API contracts, and comprehensive test coverage. It addresses three critical issues: double encryption prevention, unclear decryption errors, and test organization.

Changes

1. Double Encryption Prevention

  • Added AlreadyEncryptedError exception raised when attempting to encrypt already-encrypted data
  • encrypt_secret() now validates input with is_encrypted() before encryption
  • Prevents accidental double-wrapping that would make secrets undecipherable by consumers

2. Clear Decryption Error Messages

  • Added NotEncryptedError exception with explicit message when decrypting plaintext
  • Replaced generic errors (invalid literal: line 1 column 1 (char 0)) with contextual messages:
    • "Input is not encrypted. Use decrypt_secret_or_plaintext() for idempotent behavior."
    • "Decryption failed (corrupted or wrong key): {details}"
  • Significantly improves debugging experience and reduces support burden

3. Strict vs Idempotent API Modes

Strict Mode (explicit validation):

  • encrypt_secret() - Raises AlreadyEncryptedError if input already encrypted
  • decrypt_secret() - Raises NotEncryptedError if input not encrypted
  • decrypt_secret_strict_async() - Async strict variant

Idempotent Mode (resilient):

  • decrypt_secret_or_plaintext() - Returns plaintext unchanged if not encrypted, None on error
  • decrypt_secret_async() - Backward compatible (idempotent)
  • decrypt_secret_or_plaintext_async() - Explicit idempotent async

4. Enhanced Format Detection

New v2 Format:

  • Encrypted bundles prefixed with "v2:{...json...}" for unambiguous detection
  • Contains explicit version, KDF type, parameters, salt, and encrypted token
  • All new encryptions use v2 format; legacy formats still supported

Robust Detection:

  • _is_valid_v2_bundle() - Validates v2: prefixed bundles
  • _is_valid_json_bundle() - Legacy JSON format support
  • _is_valid_fernet_format() - Legacy Fernet binary format
  • _looks_like_failed_encryption() - Heuristic for corrupted data

5. Comprehensive Documentation

  • 75-line module docstring with format specs, API usage, error handling table, migration strategy
  • Security warnings about is_encrypted() limitations
  • Clear examples for both strict and idempotent modes
  • Performance notes on Argon2id tuning

6. Test Suite Migration and Expansion

  • Moved: 264 lines of encryption tests from test_oauth_manager.py to new test_encryption_service.py
  • Added: 287 new lines of tests (551 total lines)
  • Coverage includes:
    • Double encryption prevention
    • Strict vs idempotent behavior
    • Real-world token formats (JWT, OAuth2, API keys)
    • Concurrent/async operations
    • Edge cases and error conditions

7. Consumer Updates

dcr_service.py:

  • Added null checks after decryption in update_client() and delete_client()
  • Update operation raises DcrError if decryption fails
  • Delete operation logs warning and continues (graceful degradation)

oauth_manager.py:

  • Replaced length heuristic (len(client_secret) > 50) with explicit is_encrypted() check
  • Applied to 4 methods: exchange_code_for_token(), refresh_token(), password_grant(), get_token_with_client_credentials()
  • Added null checks after decryption with fallback to encrypted version

Files Changed

mcpgateway/services/dcr_service.py                      | 5 additions
mcpgateway/services/encryption_service.py               | 551 lines (major refactor)
mcpgateway/services/oauth_manager.py                    | 36 changes
tests/unit/mcpgateway/services/test_encryption_service.py | 551 additions (new file)
tests/unit/mcpgateway/test_oauth_manager.py             | 264 deletions

Testing

  • ✅ All existing tests pass
  • ✅ 45 new test cases covering strict/idempotent modes, error handling, and edge cases
  • ✅ Real-world token format validation (JWT, OAuth2, API keys)
  • ✅ Concurrent/async operation testing
  • ✅ Backward compatibility verified

Migration Strategy

  1. Phase 1 (This PR): New encryptions use v2 format, decryptions accept both
  2. Phase 2 (Next sprint): Background job migrates legacy data to v2
  3. Phase 3 (When 95%+ migrated): Deprecate legacy format support
  4. Phase 4 (Next release): Remove legacy code

Security Improvements

  • Explicit format markers eliminate ambiguity
  • Strict validation with required key checks
  • Safe defaults: is_encrypted() returns False for ambiguous data
  • Clear security warnings in documentation
  • Null safety in all consumers

Backward Compatibility

  • ✅ Legacy format support maintained
  • ✅ Idempotent async methods preserve existing behavior
  • ✅ No breaking changes to public API
  • ✅ Existing encrypted data continues to work

Impact

Positive:

  • Prevents data corruption from double encryption
  • Reduces debugging time with clear error messages
  • Improves code organization with separated test files
  • Enhances security with explicit format detection

Risk Mitigation:

  • Comprehensive test coverage validates all edge cases
  • Null checks prevent runtime failures
  • Legacy format support ensures smooth transition

Closes #2405

@crivetimihai
Copy link
Copy Markdown
Member

Great work on the encryption subsystem improvements, @MohanLaksh! The v2 format prefix, double-encryption prevention, and the strict/idempotent API split are all well-designed. The test suite is comprehensive — good coverage of concurrent async, wrong-key scenarios, and real-world token formats.

A few items to address:

  1. Merge conflict — needs a rebase against main.
  2. _looks_like_failed_encryption() heuristic — strings that are 20-500 chars with no spaces and valid base64 encoding will be flagged. API keys like sk-proj-abc123... could match, causing decrypt_secret_or_plaintext() to return None instead of the plaintext. Consider removing this heuristic or tightening the criteria.
  3. DCR delete returns True on decryption failuredelete_client_registration() returns success without actually sending the delete request, potentially orphaning remote client registrations. Should this return False?
  4. v2: prefix + failed validationdecrypt_secret_or_plaintext() returns the corrupted string as-is when v2: prefix is present but JSON parsing fails. This should probably return None since it's almost certainly corrupted encrypted data.

@MohanLaksh MohanLaksh force-pushed the decryption-error-handling-and-encryption-service-test-cases-update branch from f1ca890 to d0ca2d2 Compare February 13, 2026 08:58
@MohanLaksh
Copy link
Copy Markdown
Collaborator Author

@crivetimihai ,

Can you please review and merge this PR.

I have rebased the PR against main and have addressed the following review feedback:

  1. Encryption Heuristics fix,
  2. V2 Prefix Handling and
  3. DCR Delete Return Value Fix

@crivetimihai crivetimihai force-pushed the decryption-error-handling-and-encryption-service-test-cases-update branch 2 times, most recently from 74678fa to 05334a3 Compare February 15, 2026 16:43
- Add AlreadyEncryptedError and NotEncryptedError (extend ValueError)
  for explicit validation in strict mode
- Introduce v2: format prefix for unambiguous encrypted data detection
- Add strict vs idempotent API modes (decrypt_secret vs
  decrypt_secret_or_plaintext) with backward-compatible async wrappers
- Replace length heuristic in oauth_manager with explicit is_encrypted()
- Add null checks after decryption in dcr_service update/delete
- Migrate encryption tests to dedicated test_encryption_service.py
- Add comprehensive test coverage for edge cases, concurrent operations,
  and real-world token formats (JWT, OAuth2, API keys)

Closes #2405

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai crivetimihai force-pushed the decryption-error-handling-and-encryption-service-test-cases-update branch from 05334a3 to 80993a7 Compare February 15, 2026 18:21
@crivetimihai crivetimihai merged commit ccafeb2 into main Feb 15, 2026
54 checks passed
@crivetimihai crivetimihai deleted the decryption-error-handling-and-encryption-service-test-cases-update branch February 15, 2026 19:10
suciu-daniel pushed a commit that referenced this pull request Feb 16, 2026
…2724)

- Add AlreadyEncryptedError and NotEncryptedError (extend ValueError)
  for explicit validation in strict mode
- Introduce v2: format prefix for unambiguous encrypted data detection
- Add strict vs idempotent API modes (decrypt_secret vs
  decrypt_secret_or_plaintext) with backward-compatible async wrappers
- Replace length heuristic in oauth_manager with explicit is_encrypted()
- Add null checks after decryption in dcr_service update/delete
- Migrate encryption tests to dedicated test_encryption_service.py
- Add comprehensive test coverage for edge cases, concurrent operations,
  and real-world token formats (JWT, OAuth2, API keys)

Closes #2405

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
vishu-bh pushed a commit that referenced this pull request Feb 18, 2026
…2724)

- Add AlreadyEncryptedError and NotEncryptedError (extend ValueError)
  for explicit validation in strict mode
- Introduce v2: format prefix for unambiguous encrypted data detection
- Add strict vs idempotent API modes (decrypt_secret vs
  decrypt_secret_or_plaintext) with backward-compatible async wrappers
- Replace length heuristic in oauth_manager with explicit is_encrypted()
- Add null checks after decryption in dcr_service update/delete
- Migrate encryption tests to dedicated test_encryption_service.py
- Add comprehensive test coverage for edge cases, concurrent operations,
  and real-world token formats (JWT, OAuth2, API keys)

Closes #2405

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
kcostell06 pushed a commit to kcostell06/mcp-context-forge that referenced this pull request Feb 24, 2026
…BM#2724)

- Add AlreadyEncryptedError and NotEncryptedError (extend ValueError)
  for explicit validation in strict mode
- Introduce v2: format prefix for unambiguous encrypted data detection
- Add strict vs idempotent API modes (decrypt_secret vs
  decrypt_secret_or_plaintext) with backward-compatible async wrappers
- Replace length heuristic in oauth_manager with explicit is_encrypted()
- Add null checks after decryption in dcr_service update/delete
- Migrate encryption tests to dedicated test_encryption_service.py
- Add comprehensive test coverage for edge cases, concurrent operations,
  and real-world token formats (JWT, OAuth2, API keys)

Closes IBM#2405

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TESTING][SECURITY]: Encryption and secrets manual test plan (Argon2, Fernet, key derivation)

2 participants