Repository: lima-vm/lima

Issue:
Tracking of code ownership and access control
As part of the incubation DD process for the Lima project in the CNCF, can you clarify how code ownership is controlled and if a mechanism similar to code owners is being applied?
https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners

If not would you consider adding such a mechanism?

Hints from issue comments:
@jandubois: > If not would you consider adding such a mechanism?

We discussed this before and decided that we would postpone this until we have more committers on the project. Right now only 4 maintainers have write access to the repo, so for practical reasons all 4 would be a codeowner of the full repo, which makes the whole process somewhat pointless.@rochaporto: That's fair. But given the goal of our due diligence is to guide projects towards maturity (graduation later) it seems like a good item to add now.

"Code and Doc ownership in Github and elsewhere matches documented governance roles" is a required item for the incubation due diligence. From the governance docs you have both "Committers" and "Reviewers" as maintainer roles, reflecting this in some form other than full write access to the repo seems like a good target.@jandubois: > it seems like a good item to add now

Yeah, we can of course add the CODEOWNERS file and give each committer full ownership for the repo for now, so we have the structure in place, even though it won't make a difference just yet.@AkihiroSuda: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#about-code-owners

> Code owners are automatically requested for review when someone opens a pull request that modifies code that they own. 

I fear this might inflate the amount of the GitHub notifications.

Is there any alternative that CNCF accepts?
@rochaporto: > https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#about-code-owners
> 
> > Code owners are automatically requested for review when someone opens a pull request that modifies code that they own.
> 
> I fear this might inflate the amount of the GitHub notifications.
> 
> Is there any alternative that CNCF accepts?

That's a good point. @mrbobbytables @castrojo do you have a recommendation?@AkihiroSuda: There is an upstream request to silence CODEOWNERS notifications, but apparently there is no progress:

- https://github.com/orgs/community/discussions/35673


Do we need to handle this issue before the promotion to CNCF Incubating? Or can we revisit this before the promotion to Graduated?@AkihiroSuda: I guess if we could make https://github.com/orgs/lima-vm/teams visible to non-members, it could be useful for tracking the code ownership and access control.
Doesn't seem possible either, though 😞 @rochaporto: > There is an upstream request to silence CODEOWNERS notifications, but apparently there is no progress:
> 
> * https://github.com/orgs/community/discussions/35673
> 
> Do we need to handle this issue before the promotion to CNCF Incubating? Or can we revisit this before the promotion to Graduated?

Hi @AkihiroSuda . Yes this is one of the required items for incubation:
https://github.com/cncf/toc/blob/main/operations/toc-templates/template-dd-pr-incubation.md#required-1

Right now this is the only missing item in the DD.@jandubois: I just looked at `containerd`, which is a graduated CNCF project, and I couldn't find a single `CODEOWNERS` file in the entire GitHub org:

https://github.com/search?type=code&q=path%3A**%2Fcodeowners+org%3Acontainerd

So how do they manage this?@rochaporto: > I just looked at `containerd`, which is a graduated CNCF project, and I couldn't find a single `CODEOWNERS` file in the entire GitHub org:
> 
> https://github.com/search?type=code&q=path%3A**%2Fcodeowners+org%3Acontainerd
> 
> So how do they manage this?

I can check/ask but it could be the criteria was not as clear at the time.@jandubois: I've checked a couple more projects, and having a `CODEOWNERS` file seems to be the exception and not the rule.

Even the recently (Feb 2025) incubated [Kubescape](https://github.com/kubescape/kubescape) has just a [MAINTAINERS.md](https://github.com/kubescape/project-governance/blob/main/MAINTAINERS.md) in a separate repo, and no owners defined at all, afaict. 

This is the same we have in our governance docs, with a link from `MAINTAINERS.md`.

Helm has an [OWNERS](https://github.com/helm/helm/blob/main/OWNERS) file, which lists different roles, but avoids the GitHub notification spam. Could we do the same thing as Helm?@jandubois: > Yes this is one of the required items for incubation: https://github.com/cncf/toc/blob/main/operations/toc-templates/template-dd-pr-incubation.md#required-1

The only rule that I see that references code ownership is this one:

- [ ] Code and Doc ownership in Github and elsewhere matches documented governance roles.

I would argue that we follow this rule, if you check our [governance docs](https://lima-vm.io/docs/community/governance/):

> Committers have full write accesses to repos under https://github.com/lima-vm. 

and

> Reviewers may moderate GitHub issues and pull requests [...], but they do not have any access to merge pull requests nor push commits.

This matches the GitHub permissions for Committers and Reviewers, which is all the rule is asking about.  For now all committers are owners for all the code and docs.

The @lima-vm/reviewers team members have the `Triage` role, and the @lima-vm/committers team members have the `Maintain` role, in GitHub speak.@rochaporto: > > Yes this is one of the required items for incubation: https://github.com/cncf/toc/blob/main/operations/toc-templates/template-dd-pr-incubation.md#required-1
> 
> The only rule that I see that references code ownership is this one:
> 
> * [ ]  Code and Doc ownership in Github and elsewhere matches documented governance roles.
> 
> I would argue that we follow this rule, if you check our [governance docs](https://lima-vm.io/docs/community/governance/):
> 
> > Committers have full write accesses to repos under https://github.com/lima-vm.
> 
> and
> 
> > Reviewers may moderate GitHub issues and pull requests [...], but they do not have any access to merge pull requests nor push commits.
> 
> This matches the GitHub permissions for Committers and Reviewers, which is all the rule is asking about. For now all committers are owners for all the code and docs.
> 
> The @lima-vm/reviewers team members have the `Triage` role, and the @lima-vm/committers team members have the `Maintain` role, in GitHub speak.

This issue was open for clarification of how access control is being done - CODEOWNERS was just an example. This comment clarifies how this is done, would it be possible to write it down (or point me to where it's already written if that's the case) so that we can point to it from the DD doc?@jandubois: > This issue was open for clarification of how access control is being done - CODEOWNERS was just an example.

Oh, thank you! That explains the misunderstanding.

> This comment clarifies how this is done, would it be possible to write it down (or point me to where it's already written if that's the case) so that we can point to it from the DD doc?

The team permissions are not documented (because they are redundant with the government docs).

I'm going to add an explanation of the teams to the docs, to explain how access control is enforced.@jandubois: @rochaporto https://lima-vm.io/docs/community/governance/ has been updated with the teams information. Please let me know if this is sufficient!@rochaporto: > [@rochaporto](https://github.com/rochaporto) https://lima-vm.io/docs/community/governance/ has been updated with the teams information. Please let me know if this is sufficient!

Excellent thanks.@jandubois: > Excellent thanks.

I assume this means this issue can be closed.

Solution patch:
diff --git a/cmd/limactl/restart.go b/cmd/limactl/restart.go
index a46bd09e..7416b84e 100644
--- a/cmd/limactl/restart.go
+++ b/cmd/limactl/restart.go
@@ -21,6 +21,7 @@ func newRestartCommand() *cobra.Command {
 	}
 
 	restartCmd.Flags().BoolP("force", "f", false, "Force stop and restart the instance")
+	restartCmd.Flags().Bool("progress", false, "Show provision script progress by tailing cloud-init logs")
 	return restartCmd
 }
 
@@ -40,12 +41,16 @@ func restartAction(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
+	progress, err := cmd.Flags().GetBool("progress")
+	if err != nil {
+		return err
+	}
 
 	if force {
-		return instance.RestartForcibly(ctx, inst)
+		return instance.RestartForcibly(ctx, inst, progress)
 	}
 
-	return instance.Restart(ctx, inst)
+	return instance.Restart(ctx, inst, progress)
 }
 
 func restartBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
diff --git a/cmd/limactl/shell.go b/cmd/limactl/shell.go
index f3aa208d..7fe79405 100644
--- a/cmd/limactl/shell.go
+++ b/cmd/limactl/shell.go
@@ -36,6 +36,11 @@ lima command is provided as an alias for limactl shell $LIMA_INSTANCE. $LIMA_INS
 By default, the first 'ssh' executable found in the host's PATH is used to connect to the Lima instance.
 A custom ssh alias can be used instead by setting the $` + sshutil.EnvShellSSH + ` environment variable.
 
+Environment Variables:
+  --preserve-env: Propagates host environment variables to the guest instance.
+                  Use LIMA_SHELLENV_ALLOW to specify which variables to allow.
+                  Use LIMA_SHELLENV_BLOCK to specify which variables to block (extends default blocklist with +).
+
 Hint: try --debug to show the detailed logs, if it seems hanging (mostly due to some SSH issue).
 `
 
diff --git a/cmd/nerdctl.lima b/cmd/nerdctl.lima
index b398e075..9a5205a6 100755
--- a/cmd/nerdctl.lima
+++ b/cmd/nerdctl.lima
@@ -1,3 +1,4 @@
 #!/bin/sh
 set -eu
+# Use --preserve-env to pass through environment variables from host machine into guest instance
 exec limactl shell --preserve-env default nerdctl "$@"
diff --git a/hack/test-preserve-env.sh b/hack/test-preserve-env.sh
new file mode 100755
index 00000000..20959107
--- /dev/null
+++ b/hack/test-preserve-env.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+
+# SPDX-FileCopyrightText: Copyright The Lima Authors
+# SPDX-License-Identifier: Apache-2.0
+
+set -eu -o pipefail
+
+scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=common.inc.sh
+source "${scriptdir}/common.inc.sh"
+
+if [ "$#" -ne 1 ]; then
+	ERROR "Usage: $0 NAME"
+	exit 1
+fi
+
+NAME="$1"
+
+INFO "Testing --preserve-env flag"
+
+# Environment variable propagation with --preserve-env
+INFO "=== Environment variable propagation with --preserve-env ==="
+test_var_name="LIMA_TEST_PRESERVE_ENV_VAR"
+test_var_value="test-value-${RANDOM}"
+
+got="$(LIMA_SHELLENV_ALLOW="LIMA_TEST_*" LIMA_TEST_PRESERVE_ENV_VAR="$test_var_value" limactl shell --preserve-env "$NAME" printenv "$test_var_name" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$test_var_name: expected=${test_var_value}, got=${got}"
+if [ "$got" != "$test_var_value" ]; then
+	ERROR "Environment variable was not propagated with --preserve-env"
+	exit 1
+fi
+
+# Clean up before next test
+unset LIMA_TEST_PRESERVE_ENV_VAR
+unset LIMA_SHELLENV_ALLOW
+
+# Environment variable NOT propagated without --preserve-env
+INFO "=== Environment variable NOT propagated without --preserve-env ==="
+got_without_flag="$(LIMA_TEST_PRESERVE_ENV_VAR="$test_var_value" limactl shell "$NAME" printenv "$test_var_name" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$test_var_name without --preserve-env: got=${got_without_flag}"
+if [ "$got_without_flag" != "NOT_FOUND" ]; then
+	ERROR "Environment variable was unexpectedly propagated without --preserve-env"
+	exit 1
+fi
+
+# Blocked environment variables should not be propagated even with --preserve-env
+INFO "=== Blocked environment variables are not propagated with --preserve-env ==="
+blocked_var_name="HOME"
+fake_home="/tmp/fake-home-${RANDOM}"
+got_blocked="$(HOME="$fake_home" limactl shell --preserve-env "$NAME" printenv "$blocked_var_name" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$blocked_var_name: host=${fake_home}, guest=${got_blocked}"
+if [ "$got_blocked" = "$fake_home" ]; then
+	ERROR "Blocked environment variable $blocked_var_name was propagated"
+	exit 1
+fi
+
+# LIMA_SHELLENV_BLOCK functionality
+INFO "=== LIMA_SHELLENV_BLOCK with custom pattern ==="
+custom_test_var="LIMA_TEST_CUSTOM_BLOCK"
+custom_test_value="should-be-blocked"
+got_custom_blocked="$(LIMA_SHELLENV_BLOCK="+LIMA_TEST_CUSTOM_*" LIMA_TEST_CUSTOM_BLOCK="$custom_test_value" limactl shell --preserve-env "$NAME" printenv "$custom_test_var" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$custom_test_var with LIMA_SHELLENV_BLOCK: got=${got_custom_blocked}"
+if [ "$got_custom_blocked" != "NOT_FOUND" ]; then
+	ERROR "Custom blocked environment variable was propagated"
+	exit 1
+fi
+
+# Clean up before next test
+unset LIMA_TEST_CUSTOM_BLOCK 2>/dev/null || true
+unset LIMA_SHELLENV_BLOCK 2>/dev/null || true
+
+# LIMA_SHELLENV_ALLOW functionality
+INFO "=== LIMA_SHELLENV_ALLOW with custom pattern ==="
+allow_test_var="LIMA_TEST_ALLOW_VAR"
+allow_test_value="should-be-allowed"
+got_allowed="$(LIMA_SHELLENV_ALLOW="LIMA_TEST_ALLOW_*" LIMA_TEST_ALLOW_VAR="$allow_test_value" limactl shell --preserve-env "$NAME" printenv "$allow_test_var" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$allow_test_var with LIMA_SHELLENV_ALLOW: got=${got_allowed}"
+if [ "$got_allowed" != "$allow_test_value" ]; then
+	ERROR "Allowed environment variable was not propagated"
+	exit 1
+fi
+
+# Non-allowed variables are blocked when LIMA_SHELLENV_ALLOW is set
+INFO "=== Non-allowed variables are blocked when LIMA_SHELLENV_ALLOW is set ==="
+other_test_var="LIMA_TEST_OTHER_VAR"
+other_test_value="should-be-blocked"
+got_other="$(LIMA_SHELLENV_ALLOW="LIMA_TEST_ALLOW_*" LIMA_TEST_OTHER_VAR="$other_test_value" limactl shell --preserve-env "$NAME" printenv "$other_test_var" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$other_test_var with LIMA_SHELLENV_ALLOW (should be blocked): got=${got_other}"
+if [ "$got_other" != "NOT_FOUND" ]; then
+	ERROR "Non-allowed environment variable was propagated when LIMA_SHELLENV_ALLOW was set"
+	exit 1
+fi
+
+INFO "All --preserve-env tests passed"
diff --git a/hack/test-templates.sh b/hack/test-templates.sh
index 58158a41..f01d249e 100755
--- a/hack/test-templates.sh
+++ b/hack/test-templates.sh
@@ -58,6 +58,7 @@ declare -A CHECKS=(
 	["provision-data"]=""
 	["param-env-variables"]=""
 	["set-user"]=""
+	["preserve-env"]="1"
 )
 
 case "$NAME" in
@@ -276,6 +277,10 @@ if [[ -n ${CHECKS["mount-home"]} ]]; then
 	"${scriptdir}"/test-mount-home.sh "$NAME"
 fi
 
+if [[ -n ${CHECKS["preserve-env"]} ]]; then
+	"${scriptdir}"/test-preserve-env.sh "$NAME"
+fi
+
 # Use GHCR to avoid hitting Docker Hub rate limit
 nginx_image="ghcr.io/stargz-containers/nginx:1.19-alpine-org"
 alpine_image="ghcr.io/containerd/alpine:3.14.0"
diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh
index 4a43ff3f..9fa517ae 100755
--- a/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh
+++ b/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh
@@ -27,7 +27,7 @@ if [ "$LIMA_CIDATA_ROSETTA_BINFMT" = "true" ]; then
 	[ ! -d "$(dirname "$binfmtd_conf")" ] || [ -f "$binfmtd_conf" ] || echo "$rosetta_binfmt" >"$binfmtd_conf"
 else
 	# unregister rosetta from binfmt_misc if it exists
-	[ ! -f "$binfmt_entry" ] || echo -1 | "$binfmt_entry"
+	[ ! -f "$binfmt_entry" ] || echo -1 >"$binfmt_entry"
 	# remove binfmt.d(5) configuration if it exists
 	[ ! -f "$binfmtd_conf" ] || rm "$binfmtd_conf"
 fi
diff --git a/pkg/driver/vz/boot/05-rosetta-volume.sh b/pkg/driver/vz/boot/05-rosetta-volume.sh
index 4a43ff3f..9fa517ae 100755
--- a/pkg/driver/vz/boot/05-rosetta-volume.sh
+++ b/pkg/driver/vz/boot/05-rosetta-volume.sh
@@ -27,7 +27,7 @@ if [ "$LIMA_CIDATA_ROSETTA_BINFMT" = "true" ]; then
 	[ ! -d "$(dirname "$binfmtd_conf")" ] || [ -f "$binfmtd_conf" ] || echo "$rosetta_binfmt" >"$binfmtd_conf"
 else
 	# unregister rosetta from binfmt_misc if it exists
-	[ ! -f "$binfmt_entry" ] || echo -1 | "$binfmt_entry"
+	[ ! -f "$binfmt_entry" ] || echo -1 >"$binfmt_entry"
 	# remove binfmt.d(5) configuration if it exists
 	[ ! -f "$binfmtd_conf" ] || rm "$binfmtd_conf"
 fi
diff --git a/pkg/instance/restart.go b/pkg/instance/restart.go
index 310bfd80..52db7a4d 100644
--- a/pkg/instance/restart.go
+++ b/pkg/instance/restart.go
@@ -14,10 +14,9 @@ import (
 
 const (
 	launchHostAgentForeground = false
-	showProgress              = false
 )
 
-func Restart(ctx context.Context, inst *limatype.Instance) error {
+func Restart(ctx context.Context, inst *limatype.Instance, showProgress bool) error {
 	if err := StopGracefully(ctx, inst, true); err != nil {
 		return err
 	}
@@ -33,7 +32,7 @@ func Restart(ctx context.Context, inst *limatype.Instance) error {
 	return nil
 }
 
-func RestartForcibly(ctx context.Context, inst *limatype.Instance) error {
+func RestartForcibly(ctx context.Context, inst *limatype.Instance, showProgress bool) error {
 	logrus.Info("Restarting the instance forcibly")
 	StopForcibly(inst)
 
diff --git a/website/content/en/docs/community/governance.md b/website/content/en/docs/community/governance.md
index 549d2963..bdcd2e8b 100644
--- a/website/content/en/docs/community/governance.md
+++ b/website/content/en/docs/community/governance.md
@@ -26,6 +26,11 @@ Maintainers consist of two roles:
   A Reviewer is considered as a candidate to become a Committer.
   Reviewers are not recognized as Maintainers in <https://github.com/cncf/foundation/blob/main/project-maintainers.csv>.
 
+Access control is enforced via GitHub team membership:
+* All committers are members of the `@lima-vm/committers` team and have the `Maintain` role on all repos.
+* All reviewers are members of the `@lima-vm/reviewers` team and have the `Triage` role on all repos.
+* The `@lima-vm/maintainers` team includes both committers and reviewers, but doesn't grant any extra access.
+
 See also the [Contributing](../contributing) page.
 
 ### Current maintainers
diff --git a/website/content/en/docs/config/environment-variables.md b/website/content/en/docs/config/environment-variables.md
index dcaa128c..064917e9 100644
--- a/website/content/en/docs/config/environment-variables.md
+++ b/website/content/en/docs/config/environment-variables.md
@@ -57,6 +57,45 @@ This page documents the environment variables used in Lima.
   lima
   ```
 
+### `LIMA_SHELLENV_ALLOW`
+
+- **Description**: Specifies a comma-separated list of environment variable patterns to allow when propagating environment variables to the Lima instance with `--preserve-env`. When set, **only** variables matching these patterns will be passed through, completely overriding the default block list behavior. This feature only applies to Lima v2.0.0 or later.
+- **Default**: unset (when using `--preserve-env`, all variables are propagated except those matching the block list patterns)
+- **Usage**:
+  ```sh
+  export LIMA_SHELLENV_ALLOW="FPATH,XAUTHORITY,CUSTOM_*"
+  limactl shell --preserve-env default
+  ```
+- **Behavior**:
+  - **Without `--preserve-env`**: No environment variables are propagated (regardless of this setting)
+  - **With `--preserve-env` and `LIMA_SHELLENV_ALLOW` unset**: All variables are propagated except those in the block list
+  - **With `--preserve-env` and `LIMA_SHELLENV_ALLOW` set**: Only variables matching the allow patterns are propagated (block list is ignored)
+- **Note**: Patterns support wildcards using `*` at the end (e.g., `CUSTOM_*` matches `CUSTOM_VAR`, `CUSTOM_PATH`, etc.).
+
+### `LIMA_SHELLENV_BLOCK`
+
+- **Description**: Specifies a comma-separated list of environment variable patterns to block when propagating environment variables to the Lima instance with `--preserve-env`. Can either replace the default block list or extend it by prefixing with `+`. This feature only applies to Lima v2.0.0 or later.
+- **Default**: A predefined list of system and shell-specific variables that should not be propagated:
+  - Shell variables: `BASH*`, `SHELL`, `SHLVL`, `ZSH*`, `ZDOTDIR`, `FPATH`
+  - System paths: `PATH`, `PWD`, `OLDPWD`, `TMPDIR`
+  - User/system info: `HOME`, `USER`, `LOGNAME`, `UID`, `GID`, `EUID`, `GROUP`, `HOSTNAME`
+  - Display/terminal: `DISPLAY`, `TERM`, `TERMINFO`, `XAUTHORITY`, `XDG_*`
+  - SSH/security: `SSH_*`
+  - Dynamic linker: `DYLD_*`, `LD_*`
+  - Internal variables: `_*` (variables starting with underscore)
+  
+  See [`GetDefaultBlockList()`](https://github.com/lima-vm/lima/blob/master/pkg/envutil/envutil.go#L133) for the complete list.
+- **Usage**:
+  ```sh
+  # Replace default block list entirely (not recommended)
+  export LIMA_SHELLENV_BLOCK="SECRET_*,PRIVATE_*"
+  
+  # Extend default block list (recommended)
+  export LIMA_SHELLENV_BLOCK="+SECRET_*,PRIVATE_*"
+  limactl shell --preserve-env default
+  ```
+- **Note**: Patterns support wildcards using `*` at the end (e.g., `SSH_*` matches `SSH_AUTH_SOCK`, `SSH_AGENT_PID`). Use the `+` prefix to add to the default block list rather than replacing it entirely. This variable only affects the `--preserve-env` flag behavior.
+
 ### `LIMACTL`
 
 - **Description**: Specifies the path to the `limactl` binary.



Test patch:
diff --git a/pkg/envutil/envutil_test.go b/pkg/envutil/envutil_test.go
index b6fb37ea..3530288f 100644
--- a/pkg/envutil/envutil_test.go
+++ b/pkg/envutil/envutil_test.go
@@ -207,7 +207,7 @@ func TestGetDefaultBlockList(t *testing.T) {
 
 	assert.DeepEqual(t, blocklist, defaultBlockList)
 
-	expectedItems := []string{"PATH", "HOME", "SSH_*"}
+	expectedItems := []string{"PATH", "HOME", "SSH_*", "USER"}
 	for _, item := range expectedItems {
 		found := slices.Contains(blocklist, item)
 		assert.Assert(t, found, "Expected builtin blocklist to contain %q", item)