Skip to content

OracleBackend silently drops wallet_password="", breaks auto-login wallets (Pydantic v2 SecretStr is falsy) #289

@fede-kamel

Description

@fede-kamel

Summary

locus.memory.backends.OracleBackend (and OracleSyncBackend) accepts a wallet_password argument that's converted to pydantic.SecretStr. The pool builder then uses a truthy guard to decide whether to forward it to oracledb.create_pool_async:

# locus/memory/backends/oracle.py — inside _get_pool's _build()
if self.config.wallet_password:
    params["wallet_password"] = self.config.wallet_password.get_secret_value()

In Pydantic v2, SecretStr("") is falsy (bool(SecretStr("")) == False). So an explicit empty-string wallet password — the documented python-oracledb idiom for auto-login wallets (cwallet.sso) — is silently dropped. The kwarg never reaches oracledb.create_pool_async, and the driver falls through to the encrypted ewallet.pem path, which on a TTY prompts for a PEM passphrase and on a non-TTY fails with:

OSError: [Errno 22] Invalid argument
  File "src/oracledb/impl/thin/transport.pyx", line 171, in oracledb.thin_impl.Transport.create_ssl_context

This shows up at connection acquisition time as:

oracledb.exceptions.OperationalError:
  DPY-6005: cannot connect to database
  [Errno 22] Invalid argument

Reproduction

import asyncio
from locus.memory.backends import OracleBackend

async def main():
    b = OracleBackend(
        dsn="mydb_high",
        user="MYAPP",
        password="<real password>",
        wallet_location="/path/to/auto-login/wallet",
        wallet_password="",          # auto-login (cwallet.sso)
        table_name="my_table",
    )
    pool = await b._get_pool()       # pool object created OK (lazy)
    async with pool.acquire() as c:  # ← OSError: [Errno 22]
        ...
asyncio.run(main())

A direct oracledb.create_pool_async(..., wallet_password="") with the same wallet and credentials connects and serves queries fine, confirming the regression is in Locus's guard, not in oracledb or in the wallet.

Diagnostic probe

Monkey-patching oracledb.create_pool_async to print the kwargs Locus actually sends:

>> oracledb.create_pool_async kwargs:
   config_dir       = '…/wallet'
   dsn              = 'mydb_high'
   wallet_location  = '…/wallet'
   user             = 'MYAPP'
   password         = '…'
   # ← no `wallet_password` key, despite the caller passing ""

Suggested fix

Change the guard from truthy to "explicitly None":

# locus/memory/backends/oracle.py
- if self.config.wallet_password:
+ if self.config.wallet_password is not None:
      params["wallet_password"] = self.config.wallet_password.get_secret_value()

This preserves "if the user didn't pass anything, don't add the kwarg" while honoring an explicit "" for auto-login wallets.

Same pattern should be audited anywhere else in Locus that stores SecretStr and gates with if self.foo: — Pydantic v2 changed SecretStr("") falsiness from previous behaviour, so older code that worked under v1 may quietly drop empty secrets.

Impact

  • Triggers when: the application uses an auto-login wallet (cwallet.sso) and follows python-oracledb's documented wallet_password="" idiom.
  • Doesn't trigger when: the wallet is encrypted and a real (non-empty) password is supplied — that's why production deployments that pull a real wallet password from a vault won't see this; only deployments that depend on auto-login do.

Environment

  • locus-sdk 0.2.0b24
  • python-oracledb 2.4+ (thin mode)
  • pydantic v2
  • Python 3.13
  • Oracle Autonomous Database 26ai (free-tier), wallet-based mTLS

Related

  • We file a sibling issue against modelcontextprotocol/python-sdk (#2727) for a different bug surfaced while debugging this stack — unrelated mechanism, separate fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions