Skip to content

Commit d02b1db

Browse files
authored
fix: validate configuration whitespace
Reject blank explicit credentials and base URLs, normalize supported string inputs, preserve CometAPI routing boundaries, and harden stable release validation.
1 parent 60a21dd commit d02b1db

10 files changed

Lines changed: 304 additions & 27 deletions

File tree

ARCHITECTURE.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,16 @@ Configuration follows one precedence rule:
4242

4343
`api_key` resolves from `COMETAPI_KEY` and has no default. `base_url` resolves
4444
from `COMETAPI_BASE_URL` and defaults to
45-
`https://api.cometapi.com/v1`. Complete credentials must never appear in
46-
CometAPI-generated exceptions or logs.
45+
`https://api.cometapi.com/v1`. Direct and environment string values are
46+
trimmed. An explicitly blank value is rejected without fallback; a blank
47+
environment key is missing, while a blank environment base URL selects the
48+
default. Callable keys and `httpx.URL` objects pass through unchanged. Complete
49+
credentials must never appear in CometAPI-generated exceptions or logs.
50+
51+
The inherited OpenAI `copy` and `with_options` helpers are outside the 0.1
52+
support contract. They must remain fail-closed for provider routing, workload
53+
identity, private credential controls, and injected keyword mappings rather
54+
than weakening the explicit CometAPI constructor boundary.
4755

4856
## Supported resource boundary
4957

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ automation.
88

99
### Fixed
1010

11+
- Reject explicitly blank API keys and base URLs without fallback, treat blank
12+
environment keys as missing, and use the default CometAPI URL for a blank
13+
`COMETAPI_BASE_URL`.
1114
- Add a fail-closed immutable-release recovery path with an environment-secret
1215
preflight before any live request.
1316
- Execute PyPI Trusted Publishing directly in the single top-level
@@ -17,6 +20,13 @@ automation.
1720
release chain after a successful selector, while continuing to reject
1821
cancellation, reruns, and every non-successful direct dependency.
1922

23+
### Changed
24+
25+
- Trim leading and trailing whitespace from direct and environment string API
26+
keys and base URLs. Applications that intentionally supplied surrounding
27+
whitespace must pass the intended credential or URL without that padding;
28+
callable keys and `httpx.URL` objects are unchanged.
29+
2030
### Documentation
2131

2232
- Record completed stable publication, provenance, digest, and clean-install

COMPATIBILITY.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,11 @@ remains upstream-supported.
6262

6363
| Behavior | Contract |
6464
| --- | --- |
65-
| API key | Explicit `api_key`, then `COMETAPI_KEY`; required |
66-
| Base URL | Explicit `base_url`, then `COMETAPI_BASE_URL`, then `https://api.cometapi.com/v1` |
65+
| API key | Trim explicit/environment strings; explicit blank is invalid, blank `COMETAPI_KEY` is missing; required |
66+
| Base URL | Trim explicit/environment strings; explicit blank is invalid, blank `COMETAPI_BASE_URL` uses `https://api.cometapi.com/v1` |
67+
| Callable key and `httpx.URL` | Preserve official deferred-key and URL-object behavior unchanged |
6768
| OpenAI options | Documented constructor options forwarded unchanged |
69+
| Inherited copy helpers | Unsupported and fail-closed for provider, workload-identity, and private-option injection |
6870
| Complete-key disclosure | Forbidden in CometAPI-generated errors and logs |
6971

7072
Applications can configure `openai.OpenAI` or `openai.AsyncOpenAI` directly

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ variable, then the default:
6161
| `api_key` | `COMETAPI_KEY` | Required |
6262
| `base_url` | `COMETAPI_BASE_URL` | `https://api.cometapi.com/v1` |
6363

64+
Leading and trailing whitespace is removed from string API keys and base URLs.
65+
An explicitly blank string is invalid and never falls back to the environment
66+
or default. A blank `COMETAPI_KEY` is treated as missing, while a blank
67+
`COMETAPI_BASE_URL` uses the default CometAPI URL. Callable API keys and
68+
`httpx.URL` values retain their official OpenAI behavior.
69+
6470
## Usage
6571

6672
### Chat Completions
@@ -138,7 +144,9 @@ The constructor exposes a named, typed option set: `api_key`, `admin_api_key`,
138144
that documented custom HTTP client path. OpenAI provider routing and workload
139145
identity are deliberately excluded because they replace CometAPI routing or
140146
authentication; private underscore-prefixed OpenAI controls and arbitrary
141-
keywords are rejected.
147+
keywords are rejected. Inherited `copy` and `with_options` helpers are not part
148+
of the supported 0.1 surface and cannot be used to introduce those excluded
149+
routing or authentication options.
142150

143151
## Direct OpenAI interoperability
144152

ROADMAP.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ claims remain limited to the evidence defined in this roadmap and
3333
| `0.3.0` CometAPI resources | Planned | First schema-backed typed CometAPI-specific resource. |
3434
| Media and task APIs | Planned | Coherent task lifecycle precedes individual media helpers. |
3535

36+
## `0.1.x` maintenance
37+
38+
Stable maintenance preserves the 0.1 public operation and constructor surface.
39+
String API keys and base URLs are trimmed at their direct or environment
40+
boundary. Explicit blank values fail without fallback; a blank environment key
41+
is missing, while a blank environment base URL selects the default CometAPI
42+
URL. Callable keys and `httpx.URL` values keep their official OpenAI semantics.
43+
Inherited `copy` and `with_options` helpers remain unsupported and fail-closed
44+
for provider routing, workload identity, and private-option injection.
45+
46+
No maintenance release activates 0.2 provider adapters or adds a new resource,
47+
CLI, translation, or Go surface without a separate maintainer request.
48+
3649
## Repository foundation
3750

3851
Deliverables:

scripts/check_workflows.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
RELEASE_PLEASE_LOCK_JSONPATH = "$.package[?(@.name.value == 'cometapi')].version"
2222
RELEASE_PLEASE_ACTION_SHA = "5c625bfb5d1ff62eadeeb3772007f7f66fdcf071"
2323
RELEASE_PLEASE_BRIDGE_VERSION = "0.1.0-alpha.1"
24-
RELEASE_PLEASE_STABLE_VERSION = "0.1.0"
24+
RELEASE_PLEASE_STABLE_VERSION_PATTERN = re.compile(r"0\.1\.(?:0|[1-9][0-9]*)")
2525
RELEASE_PLEASE_VERIFY_COMMAND = """\
2626
test -n "$EXPECTED_TAG"
2727
test -n "$EXPECTED_SHA"
@@ -953,7 +953,7 @@ def check_release_recovery_workflow(text: str) -> None:
953953

954954

955955
def check_release_please_config(text: str, manifest_text: str) -> None:
956-
"""Require either the reviewed bridge or its exact stable cleanup state."""
956+
"""Require either the reviewed bridge or a stable 0.1.x cleanup state."""
957957
try:
958958
value = cast(object, json.loads(text))
959959
except json.JSONDecodeError as error:
@@ -966,8 +966,14 @@ def check_release_please_config(text: str, manifest_text: str) -> None:
966966
manifest = _mapping(manifest_value, "Release Please manifest")
967967
_require_exact_keys(manifest, {"."}, "Release Please manifest")
968968
version = manifest["."]
969-
if version not in {RELEASE_PLEASE_BRIDGE_VERSION, RELEASE_PLEASE_STABLE_VERSION}:
970-
raise CheckError("Release Please manifest must be the reviewed bridge or stable version")
969+
stable_version = (
970+
isinstance(version, str)
971+
and RELEASE_PLEASE_STABLE_VERSION_PATTERN.fullmatch(version) is not None
972+
)
973+
if version != RELEASE_PLEASE_BRIDGE_VERSION and not stable_version:
974+
raise CheckError(
975+
"Release Please manifest must be the reviewed bridge or a stable 0.1.x version"
976+
)
971977

972978
common_keys = {
973979
"$schema",
@@ -996,7 +1002,7 @@ def check_release_please_config(text: str, manifest_text: str) -> None:
9961002
)
9971003
else:
9981004
_require_exact_keys(config, common_keys, "Release Please stable config")
999-
if version != RELEASE_PLEASE_STABLE_VERSION:
1005+
if not stable_version:
10001006
raise CheckError("Release Please may remove the one-time bridge only after stable")
10011007
if config["release-type"] != "python":
10021008
raise CheckError("Release Please must retain its Python release type")

src/cometapi/_config.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,43 @@
1212
_T = TypeVar("_T")
1313

1414

15+
def _strip_configuration_whitespace(value: str) -> str:
16+
start = 0
17+
end = len(value)
18+
while start < end and (value[start].isspace() or value[start] == "\ufeff"):
19+
start += 1
20+
while end > start and (value[end - 1].isspace() or value[end - 1] == "\ufeff"):
21+
end -= 1
22+
return value[start:end]
23+
24+
1525
def resolve_api_key(api_key: _T | None) -> _T | str:
1626
"""Resolve an explicit API key before consulting ``COMETAPI_KEY``."""
27+
if api_key is not None and not isinstance(api_key, str):
28+
return api_key
29+
1730
resolved = api_key if api_key is not None else os.environ.get("COMETAPI_KEY")
18-
if resolved is None or (isinstance(resolved, str) and not resolved):
31+
normalized = _strip_configuration_whitespace(resolved) if resolved is not None else ""
32+
if not normalized:
1933
raise OpenAIError(
2034
"The CometAPI API key must be provided with the api_key client option "
2135
"or the COMETAPI_KEY environment variable."
2236
)
23-
return resolved
37+
return normalized
2438

2539

2640
def resolve_base_url(base_url: _T | None) -> _T | str:
2741
"""Resolve an explicit base URL before environment and default values."""
2842
if base_url is not None:
43+
if isinstance(base_url, str):
44+
normalized = _strip_configuration_whitespace(base_url)
45+
if not normalized:
46+
raise OpenAIError("The CometAPI base_url client option must not be empty.")
47+
return normalized
2948
return base_url
30-
return os.environ.get("COMETAPI_BASE_URL") or DEFAULT_BASE_URL
49+
50+
environment_url = os.environ.get("COMETAPI_BASE_URL")
51+
normalized = (
52+
_strip_configuration_whitespace(environment_url) if environment_url is not None else ""
53+
)
54+
return normalized or DEFAULT_BASE_URL

0 commit comments

Comments
 (0)