BUG-HUNT: [PROVIDERS] Case-insensitive provider type validation accepts invalid formats #7040

Open
opened 2026-04-10 07:22:21 +00:00 by HAL9000 · 2 comments
Owner

Background and Context

The ProviderRegistry.get_provider_info() method in src/cleveragents/providers/registry.py silently converts any string input to lowercase before attempting to construct a ProviderType enum value. This means callers can pass "OPENAI", "OpenAI", or any other case variation and receive a valid result, even though the ProviderType enum contract only defines lowercase values (e.g., "openai", "anthropic", "google").

This violates the fail-fast principle mandated by CONTRIBUTING.md: "No silent failures — raise exceptions or explicit error types. Explicit over implicit." The API is more permissive than its contract, which causes user confusion and makes it impossible to enforce case-sensitive validation in the future without a breaking change.

Current Behavior (Bug)

Location: src/cleveragents/providers/registry.py, function get_provider_info(), lines 266–273

def get_provider_info(self, provider_type: ProviderType | str) -> ProviderInfo | None:
    if not isinstance(provider_type, ProviderType):
        try:
            provider_type = ProviderType(provider_type.lower())  # ← Silently converts case
        except ValueError:
            return None
    return self._providers.get(provider_type)

Evidence:

Input: "OPENAI"  → Expected: None (rejected), Actual: Accepted and processed ✗
Input: "OpenAI"  → Expected: None (rejected), Actual: Accepted and processed ✗
Input: "openai"  → Expected: Accepted,         Actual: Accepted              ✓

Expected Behavior

get_provider_info() must only accept:

  • A ProviderType enum instance, OR
  • An exact lowercase string matching a ProviderType enum value (e.g., "openai", "anthropic")

Any string that is not already lowercase (i.e., provider_type != provider_type.lower()) must be rejected by returning None immediately, without silent conversion.

Acceptance Criteria

  • get_provider_info("OPENAI") returns None
  • get_provider_info("OpenAI") returns None
  • get_provider_info("openai") returns the correct ProviderInfo (unchanged behaviour)
  • get_provider_info(ProviderType.OPENAI) returns the correct ProviderInfo (unchanged behaviour)
  • All existing BDD scenarios for ProviderRegistry continue to pass
  • New BDD scenarios cover the rejection of non-lowercase strings
  • Pyright strict-mode passes with no # type: ignore suppressions
  • Coverage ≥ 97%

Supporting Information

Suggested Fix (explicit case guard):

def get_provider_info(self, provider_type: ProviderType | str) -> ProviderInfo | None:
    if not isinstance(provider_type, ProviderType):
        if isinstance(provider_type, str) and provider_type != provider_type.lower():
            return None  # Reject non-lowercase strings
        try:
            provider_type = ProviderType(provider_type)
        except ValueError:
            return None
    return self._providers.get(provider_type)

Alternative Fix (strictest — remove .lower() entirely):

def get_provider_info(self, provider_type: ProviderType | str) -> ProviderInfo | None:
    if not isinstance(provider_type, ProviderType):
        try:
            provider_type = ProviderType(provider_type)  # No .lower()
        except ValueError:
            return None
    return self._providers.get(provider_type)

The alternative fix is preferred as it removes the implicit conversion entirely and lets the ProviderType enum enforce its own contract.

Backlog note: This issue was discovered during autonomous operation
on milestone v3.2.0. It does not block milestone completion and has been
placed in the backlog for human review and future milestone assignment.


Metadata

  • Branch: bugfix/providers-case-insensitive-validation
  • Commit Message: fix(providers): reject non-lowercase provider type strings in get_provider_info()
  • Milestone: (backlog — no milestone assigned)
  • Parent Epic: (see orphan note in comments — no matching provider Epic found)

Subtasks

  • Create TDD issue (TDD: [PROVIDERS] Case-insensitive provider type validation accepts invalid formats) with @tdd_expected_fail BDD scenario
  • Merge TDD PR on branch tdd/providers-case-insensitive-validation
  • Implement fix: remove .lower() from get_provider_info() and add explicit case guard
  • Remove @tdd_expected_fail tag from TDD scenario (leave @tdd_issue and @tdd_issue_<N> permanently)
  • Update docstring for get_provider_info() to document the case-sensitive contract
  • Verify all existing ProviderRegistry BDD scenarios still pass
  • Run nox — all stages green

Definition of Done

  • TDD issue created and its PR merged before this fix begins
  • get_provider_info("OPENAI") and get_provider_info("OpenAI") return None
  • get_provider_info("openai") and get_provider_info(ProviderType.OPENAI) return correct ProviderInfo
  • New BDD scenario(s) added covering non-lowercase rejection
  • @tdd_expected_fail tag removed from all @tdd_issue_<N> tests for this issue
  • Pyright strict-mode: zero errors, no # type: ignore
  • All nox stages pass
  • Coverage ≥ 97%
  • PR merged and this issue closed

Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: new-issue-creator

## Background and Context The `ProviderRegistry.get_provider_info()` method in `src/cleveragents/providers/registry.py` silently converts any string input to lowercase before attempting to construct a `ProviderType` enum value. This means callers can pass `"OPENAI"`, `"OpenAI"`, or any other case variation and receive a valid result, even though the `ProviderType` enum contract only defines lowercase values (e.g., `"openai"`, `"anthropic"`, `"google"`). This violates the fail-fast principle mandated by CONTRIBUTING.md: *"No silent failures — raise exceptions or explicit error types. Explicit over implicit."* The API is more permissive than its contract, which causes user confusion and makes it impossible to enforce case-sensitive validation in the future without a breaking change. ## Current Behavior (Bug) **Location:** `src/cleveragents/providers/registry.py`, function `get_provider_info()`, lines 266–273 ```python def get_provider_info(self, provider_type: ProviderType | str) -> ProviderInfo | None: if not isinstance(provider_type, ProviderType): try: provider_type = ProviderType(provider_type.lower()) # ← Silently converts case except ValueError: return None return self._providers.get(provider_type) ``` **Evidence:** ``` Input: "OPENAI" → Expected: None (rejected), Actual: Accepted and processed ✗ Input: "OpenAI" → Expected: None (rejected), Actual: Accepted and processed ✗ Input: "openai" → Expected: Accepted, Actual: Accepted ✓ ``` ## Expected Behavior `get_provider_info()` must only accept: - A `ProviderType` enum instance, OR - An exact lowercase string matching a `ProviderType` enum value (e.g., `"openai"`, `"anthropic"`) Any string that is not already lowercase (i.e., `provider_type != provider_type.lower()`) must be rejected by returning `None` immediately, without silent conversion. ## Acceptance Criteria - `get_provider_info("OPENAI")` returns `None` - `get_provider_info("OpenAI")` returns `None` - `get_provider_info("openai")` returns the correct `ProviderInfo` (unchanged behaviour) - `get_provider_info(ProviderType.OPENAI)` returns the correct `ProviderInfo` (unchanged behaviour) - All existing BDD scenarios for `ProviderRegistry` continue to pass - New BDD scenarios cover the rejection of non-lowercase strings - Pyright strict-mode passes with no `# type: ignore` suppressions - Coverage ≥ 97% ## Supporting Information **Suggested Fix (explicit case guard):** ```python def get_provider_info(self, provider_type: ProviderType | str) -> ProviderInfo | None: if not isinstance(provider_type, ProviderType): if isinstance(provider_type, str) and provider_type != provider_type.lower(): return None # Reject non-lowercase strings try: provider_type = ProviderType(provider_type) except ValueError: return None return self._providers.get(provider_type) ``` **Alternative Fix (strictest — remove `.lower()` entirely):** ```python def get_provider_info(self, provider_type: ProviderType | str) -> ProviderInfo | None: if not isinstance(provider_type, ProviderType): try: provider_type = ProviderType(provider_type) # No .lower() except ValueError: return None return self._providers.get(provider_type) ``` The alternative fix is preferred as it removes the implicit conversion entirely and lets the `ProviderType` enum enforce its own contract. > **Backlog note:** This issue was discovered during autonomous operation > on milestone v3.2.0. It does not block milestone completion and has been > placed in the backlog for human review and future milestone assignment. --- ## Metadata - **Branch**: `bugfix/providers-case-insensitive-validation` - **Commit Message**: `fix(providers): reject non-lowercase provider type strings in get_provider_info()` - **Milestone**: *(backlog — no milestone assigned)* - **Parent Epic**: *(see orphan note in comments — no matching provider Epic found)* ## Subtasks - [ ] Create TDD issue (`TDD: [PROVIDERS] Case-insensitive provider type validation accepts invalid formats`) with `@tdd_expected_fail` BDD scenario - [ ] Merge TDD PR on branch `tdd/providers-case-insensitive-validation` - [ ] Implement fix: remove `.lower()` from `get_provider_info()` and add explicit case guard - [ ] Remove `@tdd_expected_fail` tag from TDD scenario (leave `@tdd_issue` and `@tdd_issue_<N>` permanently) - [ ] Update docstring for `get_provider_info()` to document the case-sensitive contract - [ ] Verify all existing `ProviderRegistry` BDD scenarios still pass - [ ] Run `nox` — all stages green ## Definition of Done - [ ] TDD issue created and its PR merged before this fix begins - [ ] `get_provider_info("OPENAI")` and `get_provider_info("OpenAI")` return `None` - [ ] `get_provider_info("openai")` and `get_provider_info(ProviderType.OPENAI)` return correct `ProviderInfo` - [ ] New BDD scenario(s) added covering non-lowercase rejection - [ ] `@tdd_expected_fail` tag removed from all `@tdd_issue_<N>` tests for this issue - [ ] Pyright strict-mode: zero errors, no `# type: ignore` - [ ] All nox stages pass - [ ] Coverage ≥ 97% - [ ] PR merged and this issue closed --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: new-issue-creator
Author
Owner

⚠️ Orphan Issue — Parent Epic Required

This issue has no parent Epic linked. A search of all open Type/Epic issues found no existing Epic covering provider type validation or the ProviderRegistry API contract.

Action required (human triage): Please link this issue to an appropriate parent Epic using Forgejo's dependency system with the correct direction:

This issue (child) BLOCKS the parent Epic — add the parent Epic number under "blocks" on this issue.

If no suitable Epic exists, one should be created first (e.g., "Epic: Provider Registry API hardening and contract enforcement"), and this issue linked as a child.

Related provider bug for context: #7031 (also orphaned — same Epic would cover both).


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: new-issue-creator

## ⚠️ Orphan Issue — Parent Epic Required This issue has no parent Epic linked. A search of all open `Type/Epic` issues found no existing Epic covering provider type validation or the `ProviderRegistry` API contract. **Action required (human triage):** Please link this issue to an appropriate parent Epic using Forgejo's dependency system with the correct direction: > **This issue (child) BLOCKS the parent Epic** — add the parent Epic number under "blocks" on this issue. If no suitable Epic exists, one should be created first (e.g., *"Epic: Provider Registry API hardening and contract enforcement"*), and this issue linked as a child. Related provider bug for context: #7031 (also orphaned — same Epic would cover both). --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: new-issue-creator
Author
Owner

Verified — Bug: case-insensitive provider type validation accepts invalid formats. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Bug: case-insensitive provider type validation accepts invalid formats. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#7040
No description provided.