Skip to content

cloudinary.config() is global state — per-call account configuration causes race conditions in multi-tenant servers #445

Description

@ojhilt

Problem

When a Python application needs to serve multiple Cloudinary accounts — for example a SaaS platform where each customer has their own Cloudinary credentials — the recommended approach is to call cloudinary.config() before each API call to switch accounts:

# Handler for User A's request
cloudinary.config(cloud_name="user_a_cloud", api_key="key_a", api_secret="secret_a")
result = cloudinary.uploader.upload(file)

This is unsafe on any shared server (threads, async frameworks, Django, Flask, etc.).

cloudinary.config() mutates a single module-level singleton (_config). If two requests arrive concurrently:

  1. Request A calls cloudinary.config(cloud_name="cloud_a", api_key="key_a", ...)
  2. Request B calls cloudinary.config(cloud_name="cloud_b", api_key="key_b", ...)
  3. Request A's upload now executes against cloud_b's credentials

This silently uploads assets to the wrong account, leaks data between tenants, or raises authentication errors that are hard to reproduce and diagnose.

Root cause

All SDK API functions (cloudinary.uploader.upload, cloudinary.api.*, cloudinary.provisioning.*) resolve credentials and routing by reading from the global cloudinary._config object at call time. There is no way to override this for a single call without temporarily mutating global state.

Expected behaviour

It should be possible to pass account configuration directly to any API call so that it applies only to that call, with no effect on the global config and no risk of cross-request contamination:

result = cloudinary.uploader.upload(
    file,
    cloudinary_config={
        "cloud_name": "tenant_cloud",
        "api_key":    "tenant_key",
        "api_secret": "tenant_secret",
    },
)

Who is affected

Any application that:

  • Manages multiple Cloudinary accounts from a single Python process
  • Uses a multi-threaded or async web framework (Django, Flask, FastAPI, Gunicorn workers, Celery, etc.)
  • Is building a multi-tenant SaaS product on top of Cloudinary

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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