Published
- 18 min read
The SAP SDK Patch Needs a Behavior Test
Books by the author
Compare all 5As an Amazon Associate I earn from qualifying purchases. Buying through these links costs you nothing extra and helps pay for the blog.
A security fix landed in version 0.45.2 of SAP’s Cloud SDK for Python on 21 August 2026. The repaired code rejected a bad tenant name before it could alter an OAuth token address. Less than three weeks later, a refactor moved part of the token logic. Six subsequent releases lost that check on at least one path.
SAP publicly disclosed the underlying flaw on 13 October as CVE-2026-89111, with a CVSS score of 10.0. Its advisory says versions below 0.45.2 are vulnerable. Independent testing by The Hacker News found a less tidy history: versions 0.53.0 through 0.55.0 also failed SAP’s own new tests for the Destination service path, even though their version numbers sat above the listed repair (The Hacker News: SAP Discloses Critical CVSS 10 Flaw in Cloud SDK for Python).
That discrepancy changes the engineering lesson. “We upgraded past the fixed version” is a package-management receipt. It does not prove that the security behavior survived later changes. Teams using this library need a current safe release, a check of where tenant input comes from, a decision about rotating exposed client credentials, and one small regression test that follows the application into future upgrades.
The test is the durable part. A version can move forward while a security property moves backward.
What SAP disclosed, and what remains uncertain
SAP’s October 2026 Patch Day covered 16 new security notes and four GitHub security advisories. The Cloud SDK issue sits at the top of the vendor’s public table: CVE-2026-89111, critical, scored 10.0, affecting versions below 0.45.2 (SAP: Security Patch Day, October 2026). The affected package is sap-cloud-sdk, SAP’s Python library for working with services on its Business Technology Platform.
The scope is narrower than “every SAP Python application.” The vulnerable behavior matters in multi-tenant applications that pass a tenant subdomain into affected SDK calls. A tenant subdomain is the short name used to identify which customer or tenant an operation belongs to. The SDK uses it while constructing the address of the OAuth service that will issue an access token.
The problem appears when an attacker can control that value. Instead of accepting only a short DNS-style label such as acme-west, vulnerable code could accept text containing URL delimiters. The resulting token request could go to a server chosen by the attacker. Because the request carries the application’s OAuth client ID and client secret, the wrong destination could receive those credentials.
SAP’s GitHub advisory states the mechanism directly: control of tenant_subdomain can redirect OAuth token requests and expose client credentials. It recommends 0.45.2 and says applications that cannot update should compare each tenant value with a known allowlist before passing it into an SDK call. SAP did not report known exploitation in that advisory as of 14 October 2026.
The public record contains an important mismatch. The advisory names versions below 0.45.2. The Hacker News ran the vendor’s validation tests across releases and reported that 0.45.2 through 0.52.2 passed, 0.53.0 through 0.55.0 lacked the check on Destination calls, and 0.55.1 through 0.58.4 passed. Version 0.55.0 also lacked it on a Document Management Service path. SAP’s advisory did not classify those six later releases as affected when the article was published.
Treat that gap honestly. The independent test result is strong evidence about behavior, but the vendor’s formal affected-version range had not caught up as of this writing. Do not turn the discrepancy into a claim that every application on those releases leaked credentials. Exploitation still requires attacker influence over the tenant value, execution of an affected path, and a useful client identity. The safe operational choice is simpler: move to a release whose behavior has been tested, then verify your own call path.
How a tenant name became a credential destination
OAuth client credentials usually look like two configuration values: an identifier and a secret. An application sends them to an approved token endpoint and receives a short-lived access token. That token then authorizes calls to another service. The client secret is valuable because it proves the application’s identity to the token service.
In a multi-tenant platform, the token address may vary by tenant. The SDK begins with a configured address and substitutes the tenant subdomain into it. That is ordinary routing work. It becomes a security boundary because one small piece of application data now influences where a secret-bearing request travels.
A useful mental model is a courier carrying a sealed credential packet. The application writes the tenant name on a routing slip. The SDK turns that name into the destination address. If the field accepts only a registered tenant label, the courier stays on an approved route. If punctuation can turn the label into a different URL, the same courier may deliver the packet to an outsider.
This class of flaw is called server-side request forgery, usually shortened to SSRF. The server is induced to make a request that the developer did not intend. Some SSRF flaws are used to scan private networks or reach cloud metadata services. This one has a more direct consequence: the request itself carries OAuth client credentials to the destination.
The dangerous step is not OAuth. Client credentials are supposed to travel to a token endpoint. The mistake is allowing untrusted text to choose that endpoint without enforcing the grammar and identity of an approved tenant. Authentication can be perfectly implemented after the destination has already been chosen badly.
Version 0.45.2 added a narrow validation rule. Its release notes say the Agent Gateway, Destination, and Document Management Service paths began checking tenant subdomains against the DNS-label form. The accepted value is one label made from letters, digits, and hyphens, no longer than 63 characters, without a leading or trailing hyphen. Dots, slashes, spaces, low-line characters, and other URL-shaping punctuation cause an immediate ValueError.
That rule separates a tenant identifier from a destination. A value such as acme-west can select a registered tenant. A value containing a slash or query marker cannot quietly become part of a different token URL. The code fails before sending the client ID or secret anywhere.
Validation alone does not prove that a tenant belongs to your application. SAP also recommends an allowlist of registered tenant subdomains as a workaround. In a production system, both checks have value. A syntax rule blocks URL construction tricks. An allowlist blocks a well-formed but unknown label. The first asks, “Could this be a tenant name?” The second asks, “Is this one of ours?”
Why a higher version lost the security property
A later version number tells you that packaging moved forward. It says nothing by itself about every behavior preserved during the move. The history around 0.53.0 makes that distinction concrete.
According to The Hacker News, version 0.53.0 moved the Destination service onto shared token-request code. That shared path did not carry over the tenant-subdomain check. Releases 0.53.0 through 0.55.0 therefore sat above 0.45.2 while missing the repaired behavior for Destination calls. The check returned in 0.55.1 on 18 September.
The 0.55.1 release notes describe the repaired behavior. The shared XsuaaAuthProvider validates the tenant value before substitution and raises an error when it contains URL delimiters. The same release notes say the prior behavior could route credentials to an attacker-controlled endpoint. That is a clear contract: malformed tenant input must fail before network access.
This is a common shape of regression. A fix begins inside three concrete modules. A later refactor extracts common authentication code to reduce duplication. The functional happy path still works, because valid tenants receive tokens. Unless the security test follows the behavior into the new abstraction, the extracted code can omit the rejection path while normal integration tests stay green.
The refactor may even look cleaner. One token provider replaces several local implementations. Fewer lines handle the request. Reviewers see consolidation and familiar tests passing. The missing line is small because the security property lives in what the system refuses to do.
That last point matters. Happy-path tests prove that acme-west can obtain a token. They do not prove that evil.example/path is rejected before a request. A mock that checks only the returned token may never inspect the destination. A test environment with fake credentials may route the bad request harmlessly and still miss the boundary violation.
Security behavior needs a negative assertion. Given a tenant value that contains URL delimiters, the application must raise before opening a connection. Given a well-formed tenant that is not registered, the application must reject it before asking for a token. The test should also observe that the HTTP client made zero calls. An exception after the secret was sent is too late.
Many dependency policies stop short here. A scanner reads the manifest, compares the installed version with a vendor range, and reports green. That is useful inventory. It catches old packages. It cannot settle a case where a later refactor briefly reintroduced the behavior outside the vendor’s listed range.
The answer is not distrust of all version advisories. Teams still need them. The answer is to attach a behavior test to the few dependency fixes that protect a boundary important to your application. The version gets you onto the vendor’s repaired line. The test tells you whether your actual path still keeps the promise.
The credential’s authority decides the real impact
A CVSS score of 10.0 gets attention, but it does not describe your application’s blast radius. The stolen material would be an OAuth client identity. Its permissions determine what an attacker could do next.
One client may read a narrow set of destination records in a test account. Another may create documents, reach business data, or call administrative services across a production tenant. The flaw supplies a route to the credential. Your authorization design supplies the consequence.
Map the client before reaching for a generic incident checklist. Identify the secret used by each affected workload, the token service it should contact, the scopes or roles granted to the client, the resources reachable with those tokens, and the environments that share the same identity. A development service using a production client secret has turned a library flaw into a much larger problem than its deployment label suggests.
Short-lived access tokens help, but they do not settle exposure of the underlying client secret. An attacker holding valid client credentials may request fresh tokens until the secret is revoked, rotated, or otherwise restricted. Token lifetime limits the life of one token. Secret lifetime controls how long new tokens can be minted.
Network controls can reduce the route. If the application runtime may send token requests only to approved SAP authentication hosts, a tenant value that constructs an outsider’s address should hit a denied connection. That control sits below the library. It remains useful when application validation disappears during a refactor.
Do not assume every environment can enforce a simple hostname list. Proxies, regional endpoints, dynamic tenant hosts, and shared egress services complicate the rule. Build the list from observed legitimate traffic, then test it with the application owners. A boundary that blocks production authentication at 2 a.m. will be removed in a hurry and may never return.
Logging has a separate job. Record the destination host for token requests, the workload identity, the tenant label after safe normalization, the deployment version, and the outcome. Never log the client secret or full authorization material. The useful event says that workload X attempted a token request for tenant Y to host Z at a given time. It does not need the packet’s private contents.
Those records answer the incident question that a package manifest cannot: did an affected workload send a token request somewhere it should not have gone? DNS, proxy, firewall, and service-mesh logs may hold parts of that answer too. Their retention period decides how far back the review can reach.
What to check in a real application
Begin with deployment reality rather than the lock file in a developer’s branch. The file may name one release while an old container, worker, or serverless function still runs another. Collect the installed package version from each active artifact or runtime. Include scheduled jobs, rollback images, staging systems with production-like credentials, and dormant disaster-recovery deployments.
As of 13 October 2026, The Hacker News reported 0.58.4 as the newest release on the public Python Package Index and said it passed all 11 vendor tests. The package page records that published artifact. A later release may exist when you read this, so use the current vendor advisory and release history rather than freezing 0.58.4 into permanent policy.
Next, search for the data flow. Find every place the application supplies tenant_subdomain or an equivalent tenant identifier to the SDK. Trace the value backward. It may come from a signed platform claim, a hostname, a route parameter, a message, a database record, or a header forwarded by a proxy. Write down where its trust is established.
A value supplied by the platform can still cross an application boundary incorrectly. Proxies rewrite headers. Tests bypass middleware. Background jobs read records originally created through public APIs. An internal service may forward tenant text from a user-facing request. “It comes from our service” describes the previous hop, not the original authority.
Then inspect the affected features. The August release notes name Agent Gateway, Destination, and Document Management Service code paths. The later regression reported by The Hacker News affected Destination calls across 0.53.0 through 0.55.0 and a Document Management Service path in 0.55.0. Match those facts against the modules your application actually invokes.
Finally, review outbound evidence. Search for OAuth token requests to unexpected hosts from workloads that ran a version or path without the check. Include DNS queries, proxy destinations, denied egress events, and application exceptions. An absence of suspicious events means more when the logs cover the exposure window and record the relevant destination. A blank dashboard with two days of retention proves very little about August and September.
The review should produce a short, specific result. For example: “Service A ran 0.54.1 from 12 to 19 September, used the Destination module, accepted tenant names only from registry table B, and could reach arbitrary HTTPS hosts. Proxy logs retained from 15 September show no non-approved token destinations. Client secret C was rotated because the first three days were not observable.” That statement can be challenged and improved. “Patched, no impact” cannot.
The practical repair sequence
This incident needs more than a dependency bump, but it does not need theatre. Work from the path that can expose the credential to the controls that remain after the emergency.
-
Inventory the running releases and call paths. Collect the installed
sap-cloud-sdkversion from every active workload. Record whether it uses Agent Gateway, Destination, or Document Management Service with a tenant subdomain. Do not infer runtime state from one repository lock file. -
Move onto a currently safe release. Follow SAP’s current advisory and confirm the chosen release rejects malformed tenant labels on your module path. If you are on the 0.36 line, The Hacker News found that moving between its three releases did not add the check. Test the target before promoting it.
-
Add an application allowlist. Compare the tenant label with your registered tenant set before calling the SDK. Keep this check close to the boundary where the value becomes a token destination. A library patch should reinforce your trust decision rather than own it alone.
-
Prove that bad input causes no network request. Write a regression test with representative delimiter-bearing input and an unknown but well-formed tenant. Assert the expected exception and assert that the token HTTP client was never called. Run the test on every dependency update.
-
Review the earlier exposure window. Join package deployment dates with token-destination, DNS, proxy, firewall, and service-mesh records. State the retention gaps. Search for requests to hosts outside the approved authentication set without recording or exposing secret values.
-
Rotate according to evidence and authority. Replace a client secret when logs show an unexpected destination, when an affected path accepted attacker-controlled tenant input, or when missing telemetry prevents a defensible conclusion for a high-authority client. Revoke the old credential and verify that the new one has only the permissions the workload needs.
-
Constrain the route below the code. Where the platform permits it, restrict token-bearing workloads to approved authentication destinations. Alert on new token hosts. Test the control during deployment so an emergency bypass does not become the permanent configuration.
-
Keep the test with the application. Link it to the incident or advisory in a short comment, but make the assertion about behavior rather than a particular package version. The useful contract is “untrusted tenant text cannot choose the OAuth destination,” which survives the next refactor and the next release number.
This sequence separates three kinds of proof. The runtime receipt says which code is deployed. The behavior test says malformed input stops before the network. The exposure review says what happened while the weaker behavior was present. None can replace the other two.
A security test should survive the vendor’s refactor
A good regression test belongs at the narrowest layer that still sees the security property. Testing an internal helper by name can break whenever the vendor reorganizes its code. Testing only the full production service can be slow and difficult to diagnose. The useful middle layer calls your application’s tenant-to-token adapter with a controlled HTTP client.
The adapter should accept a tenant identifier, check it against your registered set, and call the SDK. In the test, replace the HTTP transport with a spy or mock that counts requests. Supply a tenant value containing a slash, question mark, or full host. The exact malicious string is less important than the assertion: the call fails, and the network count remains zero.
Add a second case with a syntactically valid but unregistered tenant. Library validation may accept other-company because it is a legal DNS label. Your application must still refuse it if that tenant has no relationship with the workload. This catches the gap between syntax and authorization.
Keep one normal case too. A registered tenant should reach the expected authentication host and only that host. Avoid asserting every internal method call, because those details make the test fragile during harmless upgrades. Assert the destination, the absence of secret-bearing calls on rejected input, and the public error your application promises.
Run this small suite against the resolved dependency set in continuous integration. If an automated updater opens a pull request, the security contract runs before merge. If a transitive change or refactor alters behavior, the pull request fails where a human can investigate it. That is cheaper than discovering the change through egress logs.
The test should not contain a real client secret or contact an external server. Use a fake credential and an in-memory or local transport. The goal is to prove request construction and rejection order, not to reproduce credential theft. A safe test can still prove that no request leaves the application boundary.
Package scanners remain useful beside it. They answer which known advisories match the installed graph. Software bills of materials help locate deployments. Lock files keep builds repeatable. The behavior test fills a different gap: a known security invariant can disappear even when the package is newer than the first fixed release.
This approach scales when applied selectively. You do not need to copy every vendor test into every application. Carry the tests for boundaries your system cannot afford to misunderstand: credential destinations, authorization decisions, signature checks, tenant separation, destructive operations, and external publication. A dozen sharp tests can protect more than a thousand assertions about library internals.
The version is evidence, not the verdict
CVE-2026-89111 is a useful warning because the mechanism is plain. Tenant text influenced a token address. The token request carried an application secret. SAP added validation in August, and public reporting in October found that a later shared-code path had missed the same check for six releases.
The right response starts with an upgrade. It continues by locating the input, measuring the credential’s authority, reviewing outbound evidence, and rotating where exposure or uncertainty justifies it. Then the team writes down the behavior it expects and makes the build prove it.
That final step turns a news item into an engineering control. The next package can change names, modules, and internals. Your requirement stays readable: an untrusted tenant value must never choose where client credentials are sent.
A fixed version closes today’s ticket. A behavior test keeps the fix alive.
If you want practical security and AI-engineering guidance without a daily flood, join the newsletter. It is one email per month.
Sources
- SAP: Security Patch Day, October 2026, accessed 2026-10-14
- SAP Cloud SDK for Python: GHSA-4mv3-jxxm-9cx2, OAuth2 SSRF vulnerability, accessed 2026-10-14
- The Hacker News: SAP Discloses Critical CVSS 10 Flaw in Cloud SDK for Python Allowing OAuth2 Credential Theft, accessed 2026-10-14
- SAP Cloud SDK for Python: Release 0.45.2, accessed 2026-10-14
- SAP Cloud SDK for Python: Release 0.55.1, accessed 2026-10-14
- Python Package Index: sap-cloud-sdk 0.58.4, accessed 2026-10-14