Add Dockerfile and threat model for OSS Scanner

Anthropic's OSS Scanner builds an enrolled project's Dockerfile with
network access, scans the resulting image offline, and emails its
findings to the project. Keeping the Dockerfile and the threat model in
this repository lets us change them without a pull request to the
scanner's enrollment repository.

The image boots the default instance once during the build, while the
network is up, so the scanner can start it offline and probe the host
from a real guest. We expect the scanner's machine to provide nested
KVM; without it, QEMU emulates the CPU and the boot is slow.

The threat model summarizes Lima's CNCF self-assessment for the scanner.
It also treats everything in the guest, guest root included, as
untrusted, because the docs recommend Lima as a sandbox for AI agents.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
This commit is contained in:
Jan Dubois committed 2026-10-12 13:09:43 -07:00
1 parent 6c9920c57d
commit 1311c7fa5d
4 files changed
+172

No files matched your search

+3
View File
@@ -12,6 +12,9 @@ ls:
.github:
.yaml: snake_case
.oss-scanner:
.md: snake_case
cmd/limactl:
# valid names are `show-ssh.go`, `show-ssh_windows.go` or `show-ssh_test.go`
.go: lowercase
+35
View File
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: Copyright The Lima Authors
# SPDX-License-Identifier: Apache-2.0
# Build environment for Anthropic's OSS Scanner (https://github.com/anthropics/oss-scanner).
# The scanner builds this image online and then studies Lima inside it offline,
# so this file must fetch everything the build and the tests need.
FROM golang:1-trixie
ARG TARGETARCH
# limactl and its unit tests call ssh-keygen, qemu-img, rsync, visudo, bzip2, xz and zstd.
# QEMU, its UEFI firmware and its virtio-net ROM (ipxe-qemu) run the instance created below.
RUN case "${TARGETARCH}" in \
amd64) qemu="qemu-system-x86 ovmf" ;; \
arm64) qemu="qemu-system-arm qemu-efi-aarch64" ;; \
esac \
&& apt-get update && apt-get install -y --no-install-recommends \
bzip2 ipxe-qemu openssh-client qemu-utils rsync sudo xz-utils zstd ${qemu} \
&& rm -rf /var/lib/apt/lists/*
# limactl refuses to run as root, so give the scanner an unprivileged user to run it as.
RUN useradd --create-home lima
COPY . /src
WORKDIR /src
# DEBUG=1 keeps symbols and DWARF and disables optimization, for debuggers.
RUN make DEBUG=1 native && make install
# Keep the image even when tests fail, since the scanner can still use it.
RUN go test ./... || echo "WARNING: go test reported failures"
# Boot the default instance once while the network is up. The first boot downloads the image and
# the nerdctl archive and installs guest packages, so later boots work offline.
USER lima
RUN limactl start --tty=false --timeout=30m template:default && limactl stop default
+132
View File
@@ -0,0 +1,132 @@
# Lima threat model
Anthropic's OSS Scanner reads this file before it scans Lima. Lima's own statement of its security
model is the [CNCF self-assessment](https://github.com/cncf/tag-security/blob/main/community/assessments/projects/lima/self-assessment.md);
this file summarizes it and adds what a scanner needs.
## What Lima does and where untrusted input enters
Lima runs Linux virtual machines, and experimentally macOS, FreeBSD and Windows ones, for one user
on a macOS, Linux or Windows host, with automatic file sharing and port forwarding. `limactl`
creates and manages instances without root on the host. For each running instance, a host agent
(`limactl hostagent`) drives the VM through a driver (QEMU, Virtualization.framework, WSL2, HCS or
krunkit), forwards ports, sets up mounts and answers the guest's DNS queries. A guest agent
(`lima-guestagent`) inside the VM reports listening ports and other events to the host agent.
Attacker-controlled:
- **Everything in the guest, including root.** Users run untrusted code in their VMs, and the docs
recommend Lima for keeping AI agents away from host files. The guest user has passwordless sudo
by default, so assume the attacker is guest root. Guest data reaches the host through guest agent
events and port-forwarding streams (`pkg/guestagent/api`, `pkg/hostagent`, `pkg/portfwd`,
`pkg/portfwdserver`), DNS queries (`pkg/hostagent/dns`), the `user-v2` network stack
(`pkg/networks/usernet`, built on gvisor-tap-vsock), the host's sftp-server for `reverse-sshfs`
mounts, and files that `limactl copy` and `limactl shell --sync` bring back to the host.
- **Disk images and other downloads.** `pkg/downloader` fetches images and archives and checks
their digests when the template gives one. `pkg/imgutil` and `pkg/qemuimgutil` inspect and
convert images on the host. A crafted image must not make the host read or write other host
files.
- **Other local users.** An instance directory under `~/.lima` holds control sockets (`ha.sock`,
`ssh.sock`, `ga.sock`, `qmp.sock`), and `~/.lima/_config/user` is the private key that logs into
every guest. `limactl sudoers` prints a sudoers file for the user to install. It lets the group
named in `networks.yaml` run the network helpers as root (`socket_vmnet` on macOS,
`lima-privileged-net` on Linux), and on macOS it lets the current user run
`lima-privileged-block-device` for the devices listed. Whoever a grant covers must gain nothing
beyond the helper's job.
- **Unprivileged guest users, when `user.passwordlessSudo: false`.** Guest root is then a boundary
inside the guest.
Trusted: templates and `lima.yaml`, including `base` URLs, provisioning scripts, probes, mounts,
port-forward rules and `param` values; `networks.yaml` once it passes validation; the user's
command line, environment and `LIMA_HOME`; and the VM drivers themselves.
## Components that matter most / least
Most:
- Host agent code that parses or acts on guest data: events and port forwarding
(`pkg/hostagent/port*.go`, `pkg/portfwd`, `pkg/portfwdserver`), the DNS server and `user-v2`.
Port-forward rules decide which host addresses a guest port appears on, and the default is
127.0.0.1 only.
- The privileged helpers and their sudoers rules: `cmd/lima-privileged-net`,
`cmd/lima-privileged-block-device`, `pkg/blockdevice`, `pkg/networks` (`validate.go`,
`sudoers.go`, `reconcile`), `pkg/sudoers` and `cmd/limactl/sudoers*.go`.
`pkg/networks/networks.TEMPLATE.yaml` states their invariants. Only root may modify a helper or
its parent directories, the user must not be able to write `varRun` (helpers are killed through
sudo by the pid files in it), and no path segment may be a symlink.
- Image handling: `pkg/downloader`, `pkg/imgutil` and `pkg/qemuimgutil`, including the checks that
reject backing files, qcow2 external data files and VMDK extents, which point at other host files.
- Moving files between guest and host: mounts (`pkg/hostagent/mount.go`), `pkg/copytool`,
`limactl shell --sync`, and the MCP tools (`cmd/limactl-mcp`, `pkg/mcp`), which must act only
inside the guest.
Least: `hack/`, `website/`, test fixtures, and the image URLs in `templates/`. Code for macOS
hosts (vz, krunkit, block devices) and Windows hosts (WSL2, HCS) is in scope for review but cannot
run in this image; mark findings there as not executed.
## How to exercise it
- `/src` is the checkout, built with `make DEBUG=1 native` (symbols and DWARF kept). `make install`
put `limactl`, `lima`, the guest agent, the templates and the helpers under `/usr/local`. Go's
module and build caches are warm, so `go build ./...` and `go test ./...` work offline.
- `limactl` refuses to run as root. Run it as the unprivileged `lima` user, for example
`runuser -u lima -- limactl list`.
- We expect this machine to provide nested KVM. Lima uses KVM whenever `/dev/kvm` exists, and QEMU
fails when the `lima` user cannot open it, so run `chmod 666 /dev/kvm` first. Without
`/dev/kvm`, QEMU emulates the CPU and the boot takes several minutes.
- The `lima` user has an instance named `default`. It runs Ubuntu from `template:default` and
rootless containerd, and mounts `/home/lima` read-only over 9p. The build booted it once, so
`runuser -u lima -- limactl start default` starts it offline. `limactl shell default` gives a
guest shell, and `sudo` in it gives guest root.
- The download cache holds that Ubuntu image, so a new instance from `template:default` also
starts offline. Use one to try other settings, such as `user.passwordlessSudo: false` or the
`user-v2` network. A new instance's first boot cannot install `uidmap`, which rootless
containerd needs; `limactl clone default NAME` gives a copy that has it.
- Go tests can stand in for the guest, as the tests in `pkg/hostagent`, `pkg/portfwd` and
`pkg/networks/usernet` do. To attack `user-v2` with crafted packets, hand one end of a
`net.Pipe` to gvisor-tap-vsock's `virtualnetwork.AcceptQemu` and write Ethernet frames to the
other.
- The tests run as root here, so a test that expects a permission error may fail. That is not a
finding.
## How we rate severity
- Critical: code execution on the host, or reading or writing host files outside the configured
mounts, driven from inside the guest; root on the host for a user who is not already root,
through the helpers or their sudoers rules.
- High: a guest or another local user taking over an instance's host-side resources (the SSH key,
the sockets); a host port reachable beyond localhost that the configuration did not ask for; a
guest writing through a read-only mount; an unprivileged guest user gaining guest root when
passwordless sudo is off (GHSA-2j9v-p4xj-cjw2 was rated high).
- Medium: a guest crashing or hanging `limactl` or the host agent, or making it use unbounded memory
or disk.
- Low: a crafted disk image reading a host file (GHSA-f7qw-jj9c-rpq9 was rated low); hardening gaps
with no demonstrated exploit.
## Anything to leave alone
- Anything a template can do. Provisioning scripts run as root in the guest, mounts expose host
files and port-forward rules publish ports. The self-assessment lists tolerance of malicious
templates as a non-goal, and we apply that to templates fetched from a URL too.
- The guest reading host files under a configured mount. The default template mounts the user's
home directory read-only.
- The guest reaching host files outside a `reverse-sshfs` mount, an exception to the Critical
rating above. Lima serves the mount with OpenSSH's `sftp-server` or a builtin sftp server, and
neither confines the guest to the mount directory. The
[mount docs](https://lima-vm.io/docs/config/mount/#reverse-sshfs) list this caveat. Writing
through a read-only mount is still a finding, since Lima starts either server read-only for a
read-only mount.
- The guest reaching services on the host's loopback addresses at `host.lima.internal`
(192.168.5.2). User-mode networking does that by design.
- The guest user being root through the default passwordless sudo.
- Guest ports appearing on the host's localhost. That is port forwarding.
- Bugs in QEMU, Virtualization.framework, WSL2, Hyper-V, OpenSSH or the guest images, and outdated
images in templates.
- Anything that needs control of the user's `LIMA_HOME`, `PATH` or environment, or root on the
host.
## How reports should look
Name the attacker (a guest process, guest root, another local user, an image publisher), what they
control, the host OS and VM driver, and the path from input to impact. A Go test that fails before
the fix and passes after it is the best reproducer. Keep patches small.
@@ -35,6 +35,8 @@ Alternatively , you can set the [`upgradePackages`](https://github.com/lima-vm/l
See <https://github.com/cncf/tag-security/blob/main/community/assessments/projects/lima/self-assessment.md>.
See also <https://github.com/lima-vm/lima/blob/master/.oss-scanner/threat_model.md>, which states what we treat as attacker-controlled, what we leave out of scope, and how we rate severity.
## Reporting vulnerabilities
See <https://github.com/lima-vm/.github/blob/main/SECURITY.md>.