Skip to content

fix(security): distinguish unset vs empty cloud-accounts contextvar (… - #53

Merged
AhmadHammad21 merged 1 commit into
mainfrom
fix/credential-fail-closed
May 29, 2026
Merged

AhmadHammad21 merged 1 commit into
mainfrom
fix/credential-fail-closed

Conversation

@AhmadHammad21

Copy link
Copy Markdown
Owner

…fail-closed)

The product had a cross-tenant credential leak: a registered org that hadn't connected an AWS account in Settings -> Cloud Accounts could still investigate AWS because the agent silently fell back to the platform's own AWS credentials.

Root cause in the OSS resolver: current_cloud_accounts() collapsed None and {} into the same thing (return _current_accounts.get() or {}), so the resolver couldn't distinguish "OSS self-host, contextvar never set -> use ambient creds" (correct) from "product tenant, contextvar explicitly set to {} -> fail closed" (also needed). With this collapsed semantics, even if the product DID always call set_current_cloud_accounts([]), the resolver still returned _base_session().

Tri-state semantics on the per-task contextvar:

  • None -> OSS / self-host; resolve_session falls back to ambient host creds.
  • {} -> product tenant with no Cloud Account connected; resolve_session
    raises RuntimeError instead of leaking platform creds.
  • {p:a} -> per-provider resolution (existing behavior; unchanged).

Changes:

  • providers/aws/credentials.py:
    • current_cloud_accounts() returns Optional[dict] directly (no collapsing).
    • account_for_provider / get_current_cloud_account tolerate None.
    • resolve_session() branches on None vs {} vs has-aws-key.
  • tools/bash_tool.py: same distinction at the credential injection block. None -> ambient (run_env=None); {} -> block any cloud binary; else -> existing.
  • tests/test_tools/test_credentials.py: three new tests pinning the behavior, plus a small fix to test_bash_uses_both_clouds_when_both_connected so it matches on basename (shutil.which resolves tokens[0] to an absolute path).

…fail-closed)

The product had a cross-tenant credential leak: a registered org that hadn't
connected an AWS account in Settings -> Cloud Accounts could still investigate
AWS because the agent silently fell back to the platform's own AWS credentials.

Root cause in the OSS resolver: current_cloud_accounts() collapsed None and {}
into the same thing (`return _current_accounts.get() or {}`), so the resolver
couldn't distinguish "OSS self-host, contextvar never set -> use ambient creds"
(correct) from "product tenant, contextvar explicitly set to {} -> fail closed"
(also needed). With this collapsed semantics, even if the product DID always
call set_current_cloud_accounts([]), the resolver still returned _base_session().

Tri-state semantics on the per-task contextvar:
- None  -> OSS / self-host; resolve_session falls back to ambient host creds.
- {}    -> product tenant with no Cloud Account connected; resolve_session
          raises RuntimeError instead of leaking platform creds.
- {p:a} -> per-provider resolution (existing behavior; unchanged).

Changes:
- providers/aws/credentials.py:
  * current_cloud_accounts() returns Optional[dict] directly (no collapsing).
  * account_for_provider / get_current_cloud_account tolerate None.
  * resolve_session() branches on None vs {} vs has-aws-key.
- tools/bash_tool.py: same distinction at the credential injection block.
  None -> ambient (run_env=None); {} -> block any cloud binary; else -> existing.
- tests/test_tools/test_credentials.py: three new tests pinning the behavior,
  plus a small fix to test_bash_uses_both_clouds_when_both_connected so it
  matches on basename (shutil.which resolves tokens[0] to an absolute path).
@AhmadHammad21
AhmadHammad21 merged commit 9f40a8b into main May 29, 2026
2 checks passed
@AhmadHammad21
AhmadHammad21 deleted the fix/credential-fail-closed branch May 29, 2026 22:10
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