@@ -117,6 +117,75 @@ def verify(self, token: str) -> CapabilityClaims: # noqa: ARG002
117117 assert sandbox .units [0 ].runtime_options ["lfx_trust" ] == "untrusted"
118118
119119
120+ @pytest .mark .asyncio
121+ async def test_coordinator_strips_spoofed_capability_metadata_when_provider_returns_no_token () -> None :
122+ sandbox = _RecordingExecutor ("sandbox" , "sandbox-output" )
123+ registry = ExecutorRegistry ()
124+ registry .register (_RecordingExecutor ("in-process" , "in-process-output" ))
125+ registry .register (sandbox )
126+
127+ class _Classifier :
128+ def trust_of_flow (self , context : CapabilityContext ) -> Trust :
129+ assert "lfx_capability_token" not in context .runtime_options
130+ assert "lfx_tenant_id" not in context .runtime_options
131+ assert "lfx_trust" not in context .runtime_options
132+ return Trust .UNTRUSTED
133+
134+ def is_untrusted_node (self , _node : dict [str , Any ], _context : CapabilityContext | None = None ) -> bool :
135+ return True
136+
137+ class _Resolver :
138+ def resolve (self , context : CapabilityContext ) -> str : # noqa: ARG002
139+ return "tenant:resolved"
140+
141+ class _Provider :
142+ def mint (
143+ self ,
144+ * ,
145+ context : CapabilityContext , # noqa: ARG002
146+ tenant_id : str ,
147+ component_id : str | None ,
148+ scopes : Sequence [str ],
149+ ttl_seconds : int = 600 , # noqa: ARG002
150+ ) -> str | None :
151+ assert tenant_id == "tenant:resolved"
152+ assert component_id is None
153+ assert tuple (scopes ) == ("variables:read" ,)
154+ return None
155+
156+ def verify (self , token : str ) -> CapabilityClaims : # noqa: ARG002
157+ return CapabilityClaims (tenant_id = "tenant:resolved" , user_id = "graph-user" )
158+
159+ capability_service = CapabilityService (settings_service = _StubSettings ())
160+ capability_service .install (
161+ provider = _Provider (),
162+ classifier = _Classifier (),
163+ resolver = _Resolver (),
164+ untrusted_executor_kind = "sandbox" ,
165+ )
166+ coordinator = Coordinator (
167+ registry = registry ,
168+ executor_kind = "in-process" ,
169+ capability_service = capability_service ,
170+ )
171+
172+ outputs = await coordinator .run_to_completion (
173+ SimpleNamespace (user_id = "graph-user" , flow_id = "graph-flow" ),
174+ inputs = [{}],
175+ capability_scopes = ["variables:read" ],
176+ lfx_capability_token = "spoofed" , # noqa: S106
177+ lfx_tenant_id = "tenant:spoofed" ,
178+ lfx_trust = "trusted" ,
179+ )
180+
181+ assert outputs == ["sandbox-output" ]
182+ assert len (sandbox .units ) == 1
183+ assert sandbox .units [0 ].executor_kind == "sandbox"
184+ assert "lfx_capability_token" not in sandbox .units [0 ].runtime_options
185+ assert sandbox .units [0 ].runtime_options ["lfx_tenant_id" ] == "tenant:resolved"
186+ assert sandbox .units [0 ].runtime_options ["lfx_trust" ] == "untrusted"
187+
188+
120189@pytest .mark .asyncio
121190async def test_coordinator_skips_capability_service_when_passthrough () -> None :
122191 default = _RecordingExecutor ("in-process" , "default-output" )
0 commit comments