Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
df7c0ea
Add containers-canvas: a Copilot canvas for local containers
patverb Sep 18, 2026
d7bcfb9
containers-canvas: rename dist/ to bundle/ so installs keep the bundle
patverb Sep 21, 2026
7564d29
containers-canvas: code-split the webview to fit the 1 MB install limit
patverb Sep 21, 2026
831cebe
containers-canvas: ship a router skill so the canvas gets opened
patverb Sep 21, 2026
5b2e9d2
Add a plugin catalog so the canvas installs without the deprecation w…
patverb Sep 22, 2026
2493880
containers-canvas: fail the build on a manifest users cannot install …
patverb Sep 22, 2026
183557d
containers-canvas: detect a committed bundle that no longer matches s…
patverb Sep 22, 2026
ab99dce
containers-canvas: warn when a terminal is not isolated from the host
patverb Sep 22, 2026
e14aeb9
containers-canvas: add an ESLint config and fix what it found
patverb Sep 22, 2026
4cc4d31
containers-canvas: send nosniff and CORP on every panel response
patverb Sep 22, 2026
42c07d8
containers-canvas: write down the portability contract and the earned…
patverb Sep 22, 2026
7d78744
containers-canvas: bring the changelog up to date and drop a false li…
patverb Sep 22, 2026
a0c4535
containers-canvas: correct the react-icons licence to the upstream text
patverb Sep 22, 2026
db46426
containers-canvas: the notice is not a release blocker
patverb Sep 22, 2026
ebe7247
containers-canvas: verify the panel by driving it in a real browser
patverb Sep 23, 2026
0c06097
stop telling CLI users to reinstall a working plugin
patverb Sep 23, 2026
43116d0
say which clients show the panel, before install
patverb Sep 23, 2026
3f4d674
put the plugin manifest where the format says it goes
patverb Sep 23, 2026
dfad2f1
verify the bundle is current without requiring the same OS
patverb Sep 23, 2026
a7c44ae
name the directory that actually exists
patverb Sep 23, 2026
d2d793b
let a stopped container be removed from the panel
patverb Sep 23, 2026
a608d30
offer remove in every state, and say when it forces
patverb Sep 23, 2026
7b3c0dd
address review: argument injection, path traversal, leaked sessions
patverb Sep 24, 2026
1e4dd48
require an explicit acknowledgement to remove through the agent
patverb Sep 24, 2026
3536ad2
ask the commit whether the bundle is complete, not the worktree
patverb Sep 24, 2026
55ef8b4
describe what the entry point does, not what it used to
patverb Sep 24, 2026
e913f09
hash every file the build actually compiles
patverb Sep 24, 2026
07898f2
drop containerStats, and record the localization gap
patverb Sep 24, 2026
2ec4b09
fix the lifecycle, parsing and ordering bugs found in review
patverb Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ packages/compose-language-service/bin/docker-compose-langserver text eol=lf

# Mark some files as generated to prevent them from showing in the repo's language stats
NOTICE.html linguist-vendored=true

# containers-canvas commits its build output, because Copilot plugins are
# installed straight from a git ref with no build step. esbuild writes LF, so
# the bundle must not be line-ending converted: with autocrlf on, a checkout
# would rewrite it as CRLF and the next rebuild would report the whole bundle
# as modified. Marking it generated also keeps it out of language stats and
# collapses it in diffs.
extensions/containers-canvas/bundle/** -text linguist-generated=true

# Its NOTICE is generated too, and embeds third-party licence text verbatim --
# including whatever line endings those files use. Normalising it would both
# alter quoted licence text and make every rebuild look like a change.
extensions/containers-canvas/NOTICE.html -text linguist-generated=true
33 changes: 33 additions & 0 deletions .github/plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "vscode-containers",
"metadata": {
"description": "Copilot plugins published from the Container Tools repository.",
"version": "0.1.0"
},
"owner": {
"name": "Microsoft",
"url": "https://github.com/microsoft/vscode-containers"
},
"plugins": [
{
"name": "containers",
"description": "Browse and operate local Docker containers and images in a rich Copilot canvas: sortable lists, live log streaming, CPU and memory charts, a filesystem browser, an image layer size breakdown, Dockerfile provenance, and an interactive terminal. The panel is a GitHub Copilot app feature; on clients without canvases, such as Copilot CLI, the bundled skill answers the same questions with docker instead.",
"version": "1.0.0",
"author": {
"name": "Microsoft",
"url": "https://github.com/microsoft"
},
"repository": "https://github.com/microsoft/vscode-containers",
"homepage": "https://github.com/microsoft/vscode-containers/blob/main/extensions/containers-canvas/README.md",
"license": "MIT",
"keywords": [
"docker",
"containers",
"canvas",
"logs",
"devops"
],
"source": "./extensions/containers-canvas"
}
]
}
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,18 @@ esbuild.meta.json
/**/NOTICE.html
!/LICENSE.md
!/NOTICE.html

# containers-canvas ships as a Copilot plugin rather than a .vsix. Plugins are
# installed straight from a git ref with no build step, so its bundle has to be
# committed -- unlike every other workspace here, where dist/ is transient.
!extensions/containers-canvas/bundle/

# Same reason for its NOTICE. Every other package gets the root notice copied in
# by postinstall, which never runs for a plugin installed from a git ref. This
# one is generated from the build's metafiles and committed alongside the bundle
# it describes, so the licences travel with the code they cover.
!extensions/containers-canvas/NOTICE.html

# And its LICENSE.md, for the same reason: every source file's header and the
# README point at it, and without postinstall there is nothing to point at.
!extensions/containers-canvas/LICENSE.md
106 changes: 106 additions & 0 deletions extensions/containers-canvas/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Changelog

All notable changes to this plugin are documented here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] — 2026-09-22

First release. Packaged as an Agent Plugins 1.0 plugin that ships a Copilot
canvas extension under `com.github.copilot/extensions/`.

### Added

- **Containers and images list** — sortable, filterable, with lifecycle
controls (start, stop, restart, pause, unpause, remove). Removing asks for
confirmation in a dialog that names the container and shows the exact command
first; a running container is force-removed, and the confirmation says so
rather than leaving `-f` to be inferred. `kill` remains available to the agent
through the canvas actions rather than as a button.
- **Logs** — live `docker logs --follow` over a dedicated stream, with
filtering, adjustable tail, bottom-pinning and a "jump to live" control.
- **Stats** — CPU, memory and network sampled about once a second and drawn as
sparklines.
- **Files** — browse a container's filesystem and read files out of it using
`docker cp`, which works on stopped and distroless containers where `exec`
does not. A file can be copied to the workspace and opened in the Copilot
editor.
- **Terminal** — an interactive shell backed by a real PTY. Optional; requires
`@lydell/node-pty`, and reports itself unavailable when that is absent.
- **Commands** — run one-off commands as argv (never a shell string) and keep
an auditable record of argv, exit status, duration and output.
- **Layers** — an image's layers in build order with the size each added, and
the Dockerfile instruction that created it.
- **Dockerfile** — the recorded source repository and commit from SLSA
provenance or OCI labels, alongside a clearly separated reconstruction from
layer history.
- **Run image** — start a container from an image with validated ports,
environment, labels, mounts, network, restart policy and resource limits.
- **Pull and tag** images from the panel.
- **Live daemon tracking** — the panel follows `docker events`, so changes made
anywhere (another terminal, an IDE, a compose run) appear without a refresh.
A 10-second poll compares containers only and acts as a safety net for what a
stream cannot report: a dropped connection, a daemon restart, a machine
resuming from sleep. An idle machine produces no work.
- Agent actions for all of the above, so Copilot can drive the panel and the
user sees everything it does.
- A **router skill**, so container questions reliably open the panel rather than
producing pasted command output, and land on the view that answers them.
- A **plugin catalog** (`.github/plugin/marketplace.json`) so the plugin
installs by name without the deprecated direct-install path.

### Security

- Requests from web pages are refused. A foreign `Origin` or `Sec-Fetch-Site`
is rejected on every route, `/rpc` requires `application/json` so a
cross-origin request must be preflighted, and the terminal WebSocket is
checked at the upgrade. Local processes are not authenticated: one that can
reach the panel's port can generally reach the container runtime directly.
- Every response carries `X-Content-Type-Options: nosniff` and
`Cross-Origin-Resource-Policy: same-origin`, including error responses.
`nosniff` matters most on the routes that echo container output.
- `docker run` refuses to bind-mount drive roots, system directories or the
runtime socket. Paths containing a `..` segment are refused outright rather
than resolved, and the deny-list is matched against a separator-collapsed
form, so `/tmp/../etc` and `//etc` cannot name a blocked directory past a rule
anchored on `/etc`.
- The image passed to `docker run` is validated before it becomes an argument.
Docker parses options until its first positional, so an unvalidated image of
`--privileged` would have been read as a flag.
- Removing a container or image through the agent surface requires
`acknowledgeDestructive`. It is not a boundary against a caller that means it
— it is the same standard the privileged-exec acknowledgement sets, so that a
mistyped or guessed `op` cannot destroy something while every other verb in
the same list is reversible. The panel supplies it only after its confirmation
dialog.
- Files extracted from a container are written under a per-container folder, and
a target that would name its parent (`/..`) is refused rather than resolved.
- Running a command in a privileged container, or one mounting the runtime
socket, requires an explicit acknowledgement, because such a container is
effectively the host. `--cap-add ALL` counts as privileged for this purpose.
Opening an interactive terminal in one is allowed — the
person chose that container — but the panel says plainly that the shell is not
isolated from the machine.
- Bulk pruning is not exposed, to the agent or the panel.

### Known limitations

- Podman is detected when Docker is absent and the basics work, but it is not
supported: several command outputs are parsed differently, and image
provenance has no Podman equivalent.
- The panel itself is only exercised on Windows. The build and the unit tests
run on Linux in CI, but nothing there has a Docker daemon or a browser, so the
parts that talk to the runtime and the parts that render are verified on one
platform only. macOS is untested entirely.
- Compose containers managed by Docker Desktop report empty labels, so an
explanation handed to Copilot can say `Labels: {}`.
- Changes made outside the panel appear within a few seconds rather than
instantly, and a change the event stream misses entirely waits for the next
10-second poll.
- Every visible string is English. The repository's convention is
`vscode.l10n.t(...)`, which cannot be used here: a Copilot canvas runs outside
VS Code, and the build aliases the `vscode` specifier to a stub that throws.
Localizing the panel needs a webview-side catalogue and an extraction step,
and a decision about how those bundles reach a plugin that installs from a git
ref with no build step.
13 changes: 13 additions & 0 deletions extensions/containers-canvas/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Container Tools for Visual Studio Code

Copyright (c) Microsoft Corporation

All rights reserved.

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Loading
Loading