To avoid syncing generated or sensitive data into a vm, a new sync-exclude flag
is added, to be used in conjunction with --sync. This allows to specify rsync excludes
which are then not synced.
Additionally a .limasyncignore file can be created, which also considered and
passed to rsync as the exclude-from flag.
Signed-off-by: Mathias Petermann <mathias.petermann@gmail.com>
Signed-off-by: Ansuman Sahoo <anshumansahoo500@gmail.com>
Each reverse-sshfs mount ran its sshfs session on the instance's ssh
ControlMaster, so the mount stopped working whenever the master
exited, for example on `limactl shell --reconnect`, and stayed broken
until the instance restarted. Windows hosts already gave each mount
its own ssh connection; do the same on every host.
Signed-off-by: Jan Dubois <jan.dubois@suse.com>
The default user-mode network maps the gateway IP to the host's 127.0.0.1,
so the guest can reach every host service via `host.lima.internal`,
including ones bound only to the loopback. That is useful for dev VMs but
exposes local databases and dev servers to a guest that runs untrusted
code, such as a CI runner.
Add `networkOpts.hostLoopback` to lima.yaml (default true, unchanged),
in a new `networkOpts` group for network options. When false, the
in-process gvisor-tap-vsock gateway maps the gateway IP and
0.0.0.0 to 0.0.0.1 instead of 127.0.0.1. The host has no route to 0.0.0.1,
so connections to `host.lima.internal` are refused at once, rather than
going to the host's LAN, and UDP to 0.0.0.0 cannot reach the host's
loopback either. DNS, outbound access and SSH are unaffected.
Only `vmType: vz` supports it. `qemu`, `krunkit`, `wsl2` and `hcs` reject
`false` rather than ignore it (krunkit's default host resolver sends DNS
through `host.lima.internal`, so it needs separate work), and so does a
`user-v2` network, which is a daemon shared by instances. The refusals
are in each driver's Validate, not in validateConfig, which Configure also
runs on deletion.
A proxy on the host's loopback, whether it comes from the host's network
settings, `env`, or `propagateProxyEnv`, is rewritten to the gateway
address, which `networkOpts.hostLoopback: false` makes unreachable. Lima
warns when that happens, and the docs say so.
A BATS test, run in the vz CI job, checks end to end that a host
loopback service is refused at once with `false` and reachable after
switching to `true`, that DNS keeps working, and that a `user-v2`
network is rejected.
Refs #5574
Assisted-by: Claude Code
Signed-off-by: Rana Md Ali Ahsan <rana@codesamplez.com>
Add a `replace` tool for targeted edits, so agents no longer have to
resend a whole file with `write_file` to change a few lines.
It fails unless `old_string` occurs exactly `expected_replacements`
times (default 1), never creates a file, and refuses files and results
over the 32 MiB read limit. `replace` and `write_file` share a lock,
as the MCP server handles calls concurrently.
Like Gemini CLI's replace, it writes the file in place. If that write
fails, it tries to put the original content back.
Fixes#5539
Assisted-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Nandor Kracser <nandor@riptides.io>
An `additionalDisks` entry whose name did not fit in the filesystem label
was reformatted on every boot, silently destroying its contents.
`mkfs.ext4` silently truncates the label to 16 bytes, so a name longer
than 11 characters produced a label that could never match the
`/dev/disk/by-label/lima-${DISK_NAME}` node the first-time-setup guard
tested for. The guard was true on every boot and `mkfs` ran again. The
disk still mounted, because the mount uses the device path, so nothing
appeared to be wrong. (`mkfs.xfs` instead rejects a label over 12 bytes,
so an xfs disk with a name over 7 characters was never formatted at all.)
Identify the disk by the presence of a filesystem on its partition
instead: the guard tests whether `blkid -o value -s TYPE` prints anything.
It tests the output rather than the exit status, because util-linux blkid
exits 0 for a bare GPT partition-table entry that has no filesystem yet,
and BusyBox blkid (Alpine) ignores -o and -s and always exits 0. This
removes the length limit and, because a disk formatted by an older Lima
version also carries a filesystem, such disks are recognised rather than
reformatted on upgrade. A partition left without a filesystem by an
interrupted boot or a failed mkfs is formatted on the next boot. The
guard fails closed: if blkid is missing or reports an error, the disk is
left alone, so a disk whose state cannot be determined is never
reformatted.
The labels are kept as a convenience and clamped to what each filesystem
accepts, so that `mkfs.xfs` no longer aborts the boot. A GPT partition
label, which holds 36 characters, now carries the name as well. For an
fsType not listed here, the limit (and even whether mkfs.$FORMAT_FSTYPE
accepts a label at all) is unknown, so no label is set rather than guess
and risk aborting boot. The label is passed without a bash array, since
on Alpine /bin/bash is BusyBox ash, which has none.
Over-long names are reported at info level rather than as an error: the
disk works correctly now that identification does not depend on the
label. The notice is limited to Linux guests and disks Lima formats, the
only case in which a label is written. The `additionalDisks` comment in
the default template now points at the partition label, which keeps
names of up to 31 characters whole.
The disk-mount test regression check used a plain substring match, which
the new long-named disk's mount point ("/mnt/lima-data-with-a-long-name")
also satisfies, silently defeating the original disk's assertion. Anchor
the pattern to end-of-line.
Fixes#5407
Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Baptiste Collard <9626829+bcollard@users.noreply.github.com>
The documentation and the k3s template pass the control plane as a
URL ("https://<ADDRESS>:6443"), but the k8s template put the url
param into apiServerEndpoint unchanged, and kubeadm expects a bare
host:port there, so the documented command did not work.
Strip the https:// prefix in the template, so both forms work, and
make the template comment and the BATS test use the URL form.
Fixes#5552
Signed-off-by: Sarthak <sarthakshreshtha345@gmail.com>
WSL 3.0.1 mounts /proc/sys/fs/binfmt_misc read-only, so systemd-binfmt
cannot flush its rules and exits 1. That leaves the guest "degraded" and
fails the systemd check even though the instance is fully functional.
The behaviour is host-side and reproduces with any rootfs, so add the
unit to the set of failures lima does not control, scoped to vmType wsl2.
Fixes#5551
Signed-off-by: unsuman <anshumansahoo500@gmail.com>
Attach explicitly allowed host disks through a dedicated root-owned helper,
passing validated file descriptors to unprivileged VZ over an authenticated
Unix socket. Scope sudoers grants to the invoking user and exact device paths,
reject unsafe helper paths and socket symlinks, authenticate both peers, and
validate the received device identity.
Retain descriptors per VM while preserving process-lifetime network wrappers
used by krunkit. Lock whole host disks across instances sharing LIMA_HOME,
including raw/block aliases and partitions. Reject duplicate configuration
entries and external VZ drivers at runtime.
Compose network and block-device sudoers fragments through pkg/sudoers, match
complete generated lines, and warn when regeneration removes existing grants.
Keep integration-test grants in temporary fragments and revoke them before
detaching the test RAM disk. Add security, lifecycle, and real-disk regression
coverage and document installation requirements and remaining grant limits.
Signed-off-by: Nick Sweeting <git@sweeting.me>
On Windows, `limactl copy` converted host paths to the Cygwin/MSYS form
no matter which tool would consume them, and it passed ControlMaster
options unconditionally. Native OpenSSH cannot use that path form and
has no multiplexing, so scp failed with "getsockname failed".
The two backends need different path forms. scp takes whatever its own
toolchain uses, while rsync on Windows is always Cygwin-based and reads
a drive letter as a hostspec. So every path now takes the form of the
binary that reads it, operands and ssh options alike, because scp hands
its options to the ssh beside itself while rsync hands them to the ssh
it names.
Both backends now skip multiplexing on Windows, so they stop building
a control socket path they never use.
Signed-off-by: Jan Dubois <jan.dubois@suse.com>
Apple changed the IPSW path layout for macOS 27.0; the URL no longer
contains the `fullrestores` path component:
https://updates.cdn-apple.com/2026FallFCS/<uuid>/UniversalMac_27.0_26A428_Restore.ipsw
The regex in `hack/update-template-macos.sh` did not match this layout, and
`macos_url_spec_from_location` just returns non-zero on a mismatch, so the
script would silently skip such a template rather than fail. Relax the path
part of the regex to accept any depth.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
For a recursive copy whose destination is an existing directory, the rsync
backend copied the *contents* of the source directory, while the scp backend
copied the directory itself, like `cp -r` and `scp -r` do. Since `--backend=auto`
prefers rsync and only falls back to scp when the guest has no rsync, the
outcome of the very same command depended on the guest image.
The rsync backend appended a trailing slash to every path of a recursive copy,
and a trailing slash on the source is what tells rsync to copy the contents.
Derive the form to pass to rsync from the destination instead, so that a
trailing slash spelled by the user is no longer significant:
- the destination is an existing directory: `SRC DST`, copying SRC into DST;
- the destination does not exist yet: `SRC/ DST`, making DST a copy of SRC.
A destination that can neither be confirmed nor ruled out to be a directory
fails the copy, instead of being assumed to be missing, which would put the
files in the wrong place again.
This also fixes two related defects of the previous code: `copy -r` of a
non-directory into a destination that does not exist made rsync fail on the
appended slash, and multiple sources were merged into the destination instead
of being copied by name.
Add the missing case to copy.bats (both backends, both directions), which only
covered a plain `SRC DST` copy into a destination that does not exist yet, plus
unit tests for the path rewriting, which needs no VM.
`limactl shell --sync` does want the form that copies the contents, so let it
spell its own paths with a trailing slash and stop setting Options.Recursive,
which for rsync only added a `-r` that `-a` already implies.
Fixes#5500
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Ubuntu 25.04 added an apparmor profile for fusermount3 that only permits
fuse mounts under home, /mnt, /tmp and /media. A Lima mount point mirrors
the host path, so it is /Users/USER from a macOS host and /c/Users/USER
from a Windows one. Reverse-sshfs mounts fail there with
"fusermount3: mount failed: Permission denied". The override added in
#4968 covered @{HOME} alone, which only ever helped a Linux host.
Derive the rules from the cidata mount points instead, and rewrite the
file on every boot. Writing it only when it is missing, as #4968 did,
would keep the home-only rule on instances that already have it, and
would miss later changes to an instance's mounts. The cost is that a
rule added to the file by hand is lost on the next boot.
The Windows QEMU job has skipped its mount-home check since the job was
added. The failure the skip cites predates the default template's move
to Ubuntu 25.04, but the skip hid this bug too. Re-enable it.
Signed-off-by: Jan Dubois <jan.dubois@suse.com>
gomodjail v2 moves the focus to static analysis: `gomodjail analyze`
verifies that the modules annotated `gomodjail:confined` in go.mod cannot
reach a denied capability (filesystem, network, process execution, raw
syscalls, OS state modification, or cgo).
Add gomodjail to ./hack/tools, add a `gomodjail` lint target, and wire it
into `make lint` and the "Lints" job. Unlike the legacy dynamic mode, this
gate is blocking.
The verdicts are platform-dependent, so the gate is pinned to linux/amd64
and linux/arm64. These are the only platforms that can be analyzed from any
host: analyzing darwin needs a macOS host, as the "vz" driver requires cgo.
The linux verdicts are a superset of the darwin ones anyway. Windows is
deliberately left out, as it would unconfine 18 more modules (go-yaml,
containerd/log, go-runewidth, ...) and weaken the gate for the other
platforms.
`gomodjail fix` downgraded 36 modules to `gomodjail:unconfined` to make the
gate pass. 65 modules remain confined: 37 clean, 28 warning-only.
`make gomodjail-fix` is added for redoing this on dependency bumps, so that
the decision stays visible and reviewable in go.mod.
The macOS job that exercises the legacy dynamic mode (`gomodjail run`) is
kept as an experiment, with its checkout pinned to v2.0.1 instead of HEAD.
Assisted-by: Claude Code (Opus 5)
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Add hack/bats/tests/port-forwarding.bats, covering the same portForwards
rule matrix as hack/test-port-forwarding.pl, with one @test per
forward/ignore scenario, and run it in CI on every host platform:
- Linux: the existing "bats" job
- macOS: the "qemu" job (QEMU driver) and the "vz" job (vz driver)
- Windows: the "windows" job (WSL2 driver) and the "windows-qemu" job
Instead of grepping hostagent log strings and sleeping for a fixed time
(issue 4388), the test records `limactl watch --json` events and waits
for the "forwarding"/"not-forwarding" event of each guest listener
before connecting.
The test honors LIMACTL_CREATE_ARGS (e.g. --vm-type), allows overriding
the base template via LIMA_BATS_PORT_FORWARDING_BASE_TEMPLATE (the WSL2
job uses template:experimental/wsl2), and installs socat in the guest
with whatever package manager the guest image provides.
Notable differences from the Perl script:
- Uses socat on both the host and the guest (the nc/socat choice is
dropped; CI was already running the socat mode).
- The tests for the `guestPortRange: [305, 309]` rule now actually use
ports 305-309; the Perl script tested ports 325-329 by mistake, which
are not covered by any rule.
- Test names are ASCII-only ("->" instead of a Unicode arrow): the MSYS2
bash mangles multibyte characters inconsistently between bats' test
registration and dispatch, making every such test fail with
"bats: unknown test name".
- Guest paths passed to `limactl shell` are embedded in an `sh -c`
command string instead of being passed as arguments, because MSYS2
path conversion would rewrite a bare /tmp/... argument to
C:/msys64/tmp/... before limactl.exe sees it.
hack/test-port-forwarding.pl is removed. The only piece of it that
test-templates.sh still needs is the `guestPort: 8888` rule for the
"CONTAINER_ENGINE run" test, which is now injected with `limactl yq`.
The injected rule replaces any portForwards rules from the template, as
the Perl script did: all instances share the host port namespace, so
e.g. test-misc.yaml's own rules would collide with a concurrently
running instance created from the same template. The modified template
is round-tripped through `limactl tmpl copy --embed` so that it still
passes the template-embedding check (`limactl yq` indents sequences
differently than the embed encoder).
The perl/nc/socat host requirements and the guest netcat/socat installs
are gone; the external host IP is now looked up with getent instead of
perl.
The test passed 56/56 (16 skipped as designed on a Linux host) against
the QEMU driver without KVM.
Fix#4389
Assisted-by: Claude Fable 5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>