Skip to content

fix(adms): reject blank user_jwt in OBO mode (HASI2026203-276) - #370

Draft
tiagoek wants to merge 1 commit into
mainfrom
fix/hasi2026203-276-obo-blank-jwt
Draft

tiagoek wants to merge 1 commit into
mainfrom
fix/hasi2026203-276-obo-blank-jwt

Conversation

@tiagoek

@tiagoek tiagoek commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Security fix for HASI2026203-276 (Medium): blank or whitespace-only user_jwt values
previously caused a silent fallback from OBO (per-user) to service (application) credentials,
creating an authorization-escalation attack surface.

Automatic enforcement — no caller changes required. The validation is built directly into
the SDK entry points. Agents using sap_cloud_sdk.adms receive the protection for free.

Root cause

Both OBO decision points used Python truthy checks (if self._user_jwt / if _jwt).
Empty and whitespace-only strings are falsy, silently routing to get_token() (service
credentials) instead of exchange_token() (OBO).

Behavior contract (before → after)

Call Before After
create_client() / user_jwt=None service creds unchanged
create_client(user_jwt="valid") OBO exchange unchanged
create_client(user_jwt="") / " " ⚠️ silent service creds ValueError
AdmsHttp(user_jwt=None) service creds unchanged
AdmsHttp(user_jwt="") / " " ⚠️ silent service creds ValueError
with_user_jwt("valid") OBO exchange unchanged
with_user_jwt("") / " " / None ⚠️ silent service creds ValueError

Breaking change

Callers that previously passed "" or whitespace and unknowingly received service credentials
will now get ValueError. This is the intended security correction.

user_jwt=None / omitted (service mode) is unchanged at the constructor/factory level.

Blast radius: SDK-only. Cross-repo scan found zero importers of sap_cloud_sdk.adms
in any downstream agent/service repo. No external code changes required.

Changes

File Change
src/sap_cloud_sdk/adms/_http.py New _require_non_blank_jwt helper; fix 2 truthy-check bug sites; 2 constructor guards; 2 with_user_jwt guards
src/sap_cloud_sdk/adms/client.py 2 facade with_user_jwt guards; 2 factory user_jwt guards
src/sap_cloud_sdk/adms/user-guide.md Document new contract with security note
tests/adms/unit/test_http.py New TestAdmsHttpOboInvariant (14 tests)
tests/adms/unit/test_client.py 8 new facade + factory rejection tests

Verification

  • 30 new tests covering all 8 entry points (blank, whitespace, None, valid, service-mode regression)
  • 3569 unit tests pass — zero regressions
  • _http.py 94%, client.py 95% coverage
  • ruff + ty clean on all changed src/ files

Jira

HASI2026203-276

Blank or whitespace-only user_jwt values previously triggered a silent
fallback to service (application) credentials due to Python truthy checks
(`if self._user_jwt` / `if _jwt`). This is an authorization-escalation
finding: a per-user OBO client would silently downgrade to broad service
credentials without any error.

Fix: add a single enforcement helper `_require_non_blank_jwt` and apply
it at every public entry point — constructors (conditional on is not None),
`with_user_jwt` methods (unconditional), and factory functions (conditional).
Correct both truthy-check bug sites to use `is not None` comparisons.

Behavior contract (before → after):
- create_client()/user_jwt=None  → service creds (unchanged)
- create_client(user_jwt='valid') → OBO exchange (unchanged)
- create_client(user_jwt='')/'  ' → ValueError ← FIXED
- AdmsHttp(user_jwt=None)        → service creds (unchanged)
- AdmsHttp(user_jwt='')/'  '     → ValueError ← FIXED
- with_user_jwt('valid')         → OBO exchange (unchanged)
- with_user_jwt('')/'  '/None    → ValueError ← FIXED

BREAKING CHANGE: passing empty or whitespace user_jwt (which previously
silently used service credentials) now raises ValueError. user_jwt=None /
omitted (service mode) is unchanged. Cross-repo scan confirmed zero
external consumers of sap_cloud_sdk.adms.

Closes: HASI2026203-276

This branch has not been deployed

No deployments
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.

1 participant