Skip to content

feat(aicore): transparent proxy routing and BTP Destination Service mode (Option 3) - #271

Draft
tiagoek wants to merge 5 commits into
feat/aicore-transparent-tlsfrom
feat/aicore-proxy-routing
Draft

feat(aicore): transparent proxy routing and BTP Destination Service mode (Option 3)#271
tiagoek wants to merge 5 commits into
feat/aicore-transparent-tlsfrom
feat/aicore-proxy-routing

Conversation

@tiagoek

@tiagoek tiagoek commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Option 3 from the AFSDK-4306 security alignment meeting (2026-08-14): "Hide the ugliness in the SDK" — agent code is identical in all environments; the deployer controls routing by choosing which env vars to inject.

Tracking: AFSDK-4414 / HASI2026203 (CVE 9.9)
Stacked on: #257 (feat/aicore-clear-client-secret) → #256 (feat/aicore-transparent-tls)

Neither proxy mode nor destination mode requires a LiteLLM upstream change. Both work with the current public litellm package.


What changed

set_aicore_config() now detects the routing mode from environment variables (priority: proxy > destination > direct):

Proxy mode — AICORE_PROXY_URL set

Routes all LiteLLM calls through an external LiteLLM proxy (e.g. ADR 0039):

  • Sets litellm.api_base and litellm.api_key (proxy master API key) globally
  • Model strings (e.g. sap/<model>) are passed verbatim — no prefix rewrite. LiteLLM routes sap/<model> through the configured api_base natively (aligned with ADR 0039)
  • No AI Core credentials written to the process environment
  • JWT never reaches the agent process — the proxy holds the OAuth client secret

Env var naming is intentionally AICORE_* (not LITELLM_*) — the SDK interface is vendor-agnostic and stays stable if the underlying proxy implementation changes.

# Deployer injects these; agent code unchanged
AICORE_PROXY_URL=https://litellm-proxy.cluster.svc
AICORE_PROXY_API_KEY=sk-master-xxx

Destination mode — AICORE_DESTINATION_NAME set

Loads AI Core credentials at startup from a named BTP Destination Service destination:

  • Uses the existing sap_cloud_sdk.destination client — no new dependencies
  • Deployer only needs to inject Destination Service binding credentials into the K8s Secret
  • AICORE_CLIENT_SECRET never needs to be in the K8s Secret — it lives in BTP Destination Service
  • Combined with _clear_client_secret() (PR feat(aicore): clear AICORE_CLIENT_SECRET after token acquisition (AFSDK-4291) #257), the secret is removed from env after the first successful LiteLLM call
  • On AuthenticationError, reload_aicore_credentials() re-fetches fresh credentials from the Destination Service
# Deployer injects these; agent code unchanged
AICORE_DESTINATION_NAME=aicore-instance
# + Destination Service service binding (clientid/clientsecret/url/uri)

End-to-end validated on Kyma managed runtime (AFSDK-4416 ✅).

Direct mode (unchanged)

Neither env var set → existing behaviour: load from mounted K8s secret or env vars. AICORE_TRANSPARENT_TLS still works within direct mode (pending LiteLLM upstream for full end-to-end; see PR #256).


Agent code — identical in all modes

# Same regardless of proxy, destination, or direct mode
set_aicore_config()
response = completion(model="sap/gpt-4o", messages=[{"role": "user", "content": "Hello"}])

Security coverage

Threat Proxy mode Destination mode Direct mode
CLIENT_SECRET in K8s Secret ✅ not needed ✅ not needed ❌ required
CLIENT_SECRET in process env ✅ never ✅ cleared after first call (#257) ✅ cleared after first call (#257)
JWT in agent process memory ✅ never (proxy holds it) ❌ agent receives JWT ❌ agent receives JWT
LiteLLM upstream change required ✅ no ✅ no ⚠️ yes (for transparent TLS)
Zero agent code changes

Tests

New unit tests:

  • TestSetAICoreConfigProxyMode (6 tests) — mode detection, litellm globals, precedence, filtering still applied
  • TestSetAICoreConfigDestinationMode (9 tests) — URL extraction, credentials, resource group, error cases

Full suite: 156 passed, 4 skipped (integration tests requiring real BTP).


Related

tiagoek added 3 commits August 4, 2026 16:01
Introduces two security improvements for AI Core credential handling:

1. Transparent TLS mode (AICORE_TRANSPARENT_TLS=true): when active,
   set_aicore_config() skips writing AICORE_CLIENT_SECRET to os.environ
   and removes any stale value. The infrastructure sidecar proxy adds
   the mTLS certificate transparently on the SDK's behalf — no secret
   material needed in the agent process. Addresses HASI2026203 /
   SEC-309 (credentials exposed as env vars with excessive scope).

2. Reactive credential reload on AuthenticationError: completion() and
   acompletion() now intercept litellm.AuthenticationError, re-read
   credentials from the mounted secret volume, and retry once. Covers
   client_secret rotation and mTLS certificate rotation (cert-manager
   updates the volume file; the next failed token refresh triggers the
   reload) without requiring a pod restart.

Relates-to: AFSDK-4306
… acquisition

After the first successful litellm.completion() call, AICORE_CLIENT_SECRET is
removed from os.environ. LiteLLM has captured the secret inside its token
creator closure at that point and no longer reads from the environment. Removing
it minimises the exposure window to child processes and container introspection
APIs (AFSDK-4291 / HASI2026203 SEC-309).

The flag is reset when credentials are reloaded (credential rotation flow) so
the secret is cleared again after the retry succeeds. No-op in transparent TLS
mode where the secret was never written.

Relates-to: AFSDK-4291
…ce mode

Implements Option 3 from the AFSDK-4306 security alignment meeting: SDK
absorbs all routing complexity so agent code is identical in all environments.
The deployer controls routing by choosing which env vars to inject.

Two new modes in set_aicore_config():

Proxy mode (AICORE_PROXY_URL set):
- Routes all LiteLLM calls through an external LiteLLM proxy
- Sets litellm.api_base / litellm.api_key globally
- Rewrites sap/<model> → litellm_proxy/<model> transparently in
  completion() and acompletion() wrappers (including on auth-error retry)
- No AI Core credentials written to the process environment
- JWT never reaches the agent process (proxy handles OAuth)

Destination mode (AICORE_DESTINATION_NAME set):
- Loads AI Core credentials at startup from a named BTP Destination Service
  destination via the existing sap_cloud_sdk.destination client
- Deployer only injects Destination Service binding — AI Core client_secret
  is never in the K8s Secret, only in BTP Destination Service
- Combined with _clear_client_secret() (PR #257), the secret is removed
  from env after the first successful LiteLLM call

Direct mode (neither set): existing behaviour unchanged, including
transparent TLS (AICORE_TRANSPARENT_TLS).

Adds 30 unit tests covering both new modes and all edge cases.

AFSDK-4306
… mode

Model strings (e.g. sap/<model>) are now passed verbatim to LiteLLM in all
routing modes. LiteLLM natively routes sap/<model> through the configured
litellm.api_base without a prefix rewrite. Removes _rewrite_model_for_proxy(),
_set_proxy_active(), and all associated proxy-aliasing tests (6 unit tests).

Aligns with ADR 0039 which explicitly documents the litellm_proxy/ prefix
approach as a rejected alternative.
The previous name was misleading — the SDK reads the LiteLLM proxy master
API key, not a virtual (per-user/per-team) key. AICORE_PROXY_API_KEY is
accurate for both master key and virtual key usage.

Aligned with Sam Garland (CAD) feedback on ADR 0039 review.
@tiagoek
tiagoek force-pushed the feat/aicore-clear-client-secret branch from 3e9ef43 to ed315de Compare August 26, 2026 17:37
@tiagoek
tiagoek changed the base branch from feat/aicore-clear-client-secret to feat/aicore-transparent-tls August 26, 2026 20:28
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