Files
vscode/test
07864e41d5 agentHost: managed permission fixes for shell paths and read prompts (#333249)
* agentHost: let managed permissions see shell paths, and stop misstating why a read is gated

Two independent fixes to Copilot managed-permission handling in the agent host.

**Enable the runtime's shell-script safety classifier.**

Managed permission rules such as `Read(...)` and `Edit(...)` match on filesystem
paths. For a shell command the runtime only populates `possiblePaths` and
`hasWriteFileRedirection` when the session option `enableScriptSafety` is set;
otherwise `assess_safety` short-circuits and returns a degenerate assessment
with no paths, no redirection flag, and the whole script as a single non-read-only
command.

The agent host never set the option, so every shell command reached the
permission layer carrying no paths at all. Observed effect: with a managed
`deny: Edit(/.github/workflows/**)` in force, `echo x >> .github/workflows/f.yml`
surfaced as an ordinary shell prompt and modified the file. Even `tail -3 f`
was reported as non-read-only. The Copilot CLI opts in at session creation; the
agent host did not.

The SDK exposes the option on `SessionUpdateOptionsParams` rather than on
`SessionConfig`/`ResumeSessionConfig`, so it is applied from `_finalizeSession`
via `rpc.options.update`. That is the single point shared by the create, resume,
and resume-fallback paths, and it runs before the wrapper is handed to a caller,
so no turn can start without it. Failure is logged at error rather than thrown,
matching the surrounding post-launch option updates.

This restores the path data the permission layer needs. Note that matching
managed path rules against a shell command's `possiblePaths` is a separate
runtime-side concern and is not addressed here.

**Report the actual reason a read needs approval.**

`getPermissionDisplay` returned "Allow reading file outside of workspace?" for
every `read` request, but a read is gated for several reasons: the path is
outside the workspace, a managed or scoped rule matched it, managed rules are
active and an unmatched request defaults to ask, or the model asked to escape
the sandbox. Reads inside the workspace were therefore told they were outside
it. The branch also ignored `requestSandboxBypass`, which was already being
computed for reads, while the neighbouring `shell` and `write` branches vary
their titles.

The title is now chosen from the request: a sandbox-bypass read mirrors the
shell wording, a genuinely outside path keeps the original title, and anything
else uses a neutral "Allow reading file?". Containment uses
`extUriBiasedIgnorePathCase.isEqualOrParent` so platform case-insensitivity is
respected, and a relative path, unknown path, or unknown working directory
falls back to the neutral title rather than asserting a location the request
does not establish.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* agentHost: address review — multi-root read titles and fail-closed script safety

Two review findings on the previous commit.

**Account for every workspace root before claiming a read is outside it.**

The read confirmation title compared the path only against the primary process
root, so in a multi-root session a read under a peer root
(`ICopilotSessionLaunchBase.additionalDirectories`) still reported "outside of
workspace" — the same misleading diagnosis this change set exists to remove.
`getPermissionDisplay` now takes the session's additional directories and treats
a path as outside only when no root contains it.

**Fail closed when script safety cannot be enabled under managed rules.**

Logging and continuing left the session usable after the runtime failed or
refused to enable the classifier, which silently degrades enterprise
enforcement: managed `Read(...)`/`Edit(...)` rules cannot inspect shell redirect
targets, permitting exactly the bypass this change targets.

Launch now fails and the orphaned session is disconnected when managed
permission rules are in force. Without managed rules there is no policy to
escape, so a transient or compatibility failure is logged as a warning and the
session continues rather than making every user's sessions unlaunchable.
`_finalizeSession` disconnects `raw` before rethrowing, since nothing owns that
session until it is wrapped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* agentHost: keep the out-of-workspace read title reachable

The unauthorized-path gate is the case the original hardcoded title described,
and narrowing the title by path containment alone made it unreachable.

That gate runs ahead of the per-kind gates and fires only for paths outside the
allowed directories. It is not a distinct request kind: it reuses the access
kind, so it arrives as `kind: "read"`, but it carries `paths` (plural) rather
than the per-kind `path`. `getPermissionDisplay` reads `request.path`, which is
therefore undefined for these requests, so the containment check could not
succeed and every out-of-workspace read fell through to the neutral title —
losing the one diagnosis that was correct before this change set.

A request carrying `paths` is treated as out-of-workspace by construction, which
is a stronger signal than comparing against the workspace roots: it is the
runtime's own verdict that the path is outside the allowed directories, and it
covers directories the user approved earlier that are not workspace roots.

The SDK's `PermissionRequestRead` does not model `paths`, hence the structural
check rather than a typed discriminator.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* agentHost: make the script-safety launcher tests exercise the success path

`Array.push` returns a number, so a mocked `options.update` that returned it
gave `_applyScriptSafety` a `result.success` of `undefined`. Every launcher test
therefore took the SDK-rejection branch and passed without ever exercising a
successful enablement.

The mocks now return the SDK-shaped `{ success: true }` after recording the
options, and the enablement test runs with managed rules in force so anything
short of a successful enablement fails the launch instead of warning. Verified
by flipping the mock to `{ success: false }`, which fails that test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* agentHost: fix the SDK-sandbox smoke test for script-safety classification

Enabling the runtime's script-safety classifier changed which commands reach
the host's permission layer. `commands_requiring_approval` skips commands the
classifier reports as read-only, so the scenario's `echo` is now resolved by
the runtime without ever raising a permission request. The host's
"Auto-approving sandboxed shell command" branch therefore never runs, and the
SDK-sandbox smoke test failed waiting for that log entry on Linux and macOS.
(Windows skips both sandbox tests, which is why it stayed green.)

Before this PR the classifier short-circuited and reported the whole script as
a single non-read-only command, which forced the prompt the test was keyed to.
The prompt was redundant: the command still runs mxc-wrapped under the pushed
sandbox policy, so not asking is the better behavior, and the assertion is
simply stale rather than the code being wrong.

The test now keys on `Tool started: bash`, which proves the SDK's own shell
tool ran the command, and keeps the `Applied SDK sandboxConfig` and negative
`[ShellManager]` assertions. Polling on the tool run is also a stricter gate
for the negative assertion than the old pre-execution entry, since a competing
ShellManager run would have been logged by that point. The host auto-approve
branch still governs non-read-only sandboxed commands and remains asserted by
the custom-terminal-tool test above.

Verified by replaying all three assertions against the agenthost.log artifact
from the failing macOS run: both matches hold and the ShellManager pattern is
absent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* agentHost: fail closed on script safety for every session

`IAgentHostManagedSettingsService` only carries the legacy VS Code settings
bridge — its own documentation says so, and that new enterprise controls belong
directly in the SDK's managed-settings contract. Server and MDM policy is
discovered by the runtime itself under `enableManagedSettings`, so it never
appears in `permissions`.

Gating fail-closed on that signal therefore inverted the intent: a session
governed solely by GitHub or MDM policy looks unmanaged from the host's side, so
an `options.update` failure left exactly the policy-bearing sessions running
without the classifier that managed `Read(...)`/`Edit(...)` rules depend on to
see shell paths.

Querying the runtime's effective managed-settings result instead would cost a
proxy resolution and a separate SDK import on every launch, and would make a
security control depend on a second fallible lookup. The option is cheap,
present in the pinned SDK, and accepted by the runtime, so it is now treated as
required for every session and a failure fails the launch.

The launcher tests cover both a client-bridged session and one where the host
sees no managed rules at all, since that is what an enterprise session governed
only by server or MDM policy looks like from here. The shared resume-fallback
mock gained the `rpc.options.update` surface a real session always has; without
it the fixture failed on a missing `rpc` rather than on the behavior under test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* agentHost: cover the rejected-RPC path of the script-safety fail-closed guard

`session.options.update` reports `success: true` for any patch the runtime
accepts and signals real problems by failing the request, so a rejected RPC —
not a `success: false` body — is the path a genuine enablement failure takes.
Both existing fail-closed tests drove the `success: false` branch, leaving the
`catch` that logs the reason and aborts the launch uncovered.

Adds a test that rejects the update and asserts the launch fails closed, the
original error propagates, and the orphaned session is disconnected.

Also drops the managed rules from the success test. They no longer gate the
behavior, so passing a `deny` rule implied a dependency that is exactly the one
the fail-closed change removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Dmitriy Vasyura <dmitriv@microsoft.com>
2026-08-31 07:31:03 -07:00
..
2026-08-27 22:44:02 +00:00
…

VS Code Tests

Contents

This folder contains the various test runners for VS Code. Please refer to the documentation within for how to run them:

  • unit: our suite of unit tests (README)
  • integration: our suite of API tests (README)
  • smoke: our suite of automated UI tests (README)
  • sanity: release sanity tests (README)