Skip to content

fix(adms): reject blank user_jwt in OBO mode - #381

Draft
tiagoek wants to merge 1 commit into
mainfrom
fix/adms-obo-reject-blank-user-jwt
Draft

tiagoek wants to merge 1 commit into
mainfrom
fix/adms-obo-reject-blank-user-jwt

Conversation

@tiagoek

@tiagoek tiagoek commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Summary

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
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

Blank or whitespace-only user_jwt values triggered a silent fallback from
OBO (per-user) to service (application) credentials due to 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) — an authorization-escalation attack surface.

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
- AdmsHttp(user_jwt=None)        → service creds (unchanged)
- AdmsHttp(user_jwt='')/'  '     → ValueError
- with_user_jwt('valid')         → OBO exchange (unchanged)
- with_user_jwt('')/'  '/None    → ValueError

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.

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