Skip to content

feat: wire CLI to real implementations#4

Merged
muneebs merged 5 commits intomainfrom
pr4/wire-cli-to-real-implementations
Apr 16, 2026
Merged

feat: wire CLI to real implementations#4
muneebs merged 5 commits intomainfrom
pr4/wire-cli-to-real-implementations

Conversation

@muneebs
Copy link
Copy Markdown
Owner

@muneebs muneebs commented Apr 16, 2026

Summary

  • Wire all 15 CLI commands (create, start, stop, delete, status, list, run, reset, shell, mount, unmount, config, network lock, network unlock, network policy) to their real implementations through the Dependencies struct, replacing stub handlers
  • Add Run and Reset to api.SandboxManager interface — eliminates LSP/DIP violations from type assertions on concrete *sandbox.Manager
  • Add api.Provisioner, api.ShellProvider, and api.ProfileRegistry interfaces (ISP/DIP) — CLI never imports concrete types in handlers
  • Fix LimaController to use sandboxName parameter instead of hardcoded vmName; add per-sandbox policies map and RemovePolicy for proper multi-sandbox support
  • Add Apply() method to mount.JSONStore to satisfy api.MountManager
  • Wire production limactlRunExec/limactlOutputExec to exec.CommandContext
  • Fix duplicate LimaController instances — single shared instance for Manager and Dependencies.Network
  • Remove TASKS.md and REVIEW.md from git tracking

Key design decisions

  • P1/P5 compliance: All Dependencies fields are interfaces — zero concrete type imports in CLI handlers, no if concreteType checks
  • ISP: Provisioner and ShellProvider are separate small interfaces rather than lumping all VM operations into one
  • DIP: ProfileRegistry interface decouples sandbox.Manager and CLI from *profile.Registry
  • Resource cleanup: RemovePolicy(sandboxName) called in Destroy workflow prevents memory leaks in the network controller's policies map

AI Use Disclosure

AI-assisted. This PR was developed with AI assistance (opencode / GLM-5.1). AI was used for code generation, refactoring suggestions, and test writing. Every line was reviewed, understood, and owned by the human committer. Security-sensitive code (network policy enforcement, mount handling, credential copying, shell command execution) was reviewed line-by-line per P6.

Test plan

  • go test ./... — all existing tests pass
  • go vet ./... — clean
  • go build ./... — clean
  • Added TestRemovePolicy covering multi-sandbox cleanup isolation
  • Improved TestCheckResourcesForSpec with excessive resource spec and len(issues) > 0 assertion
  • Compile-time interface checks for Provisioner and ShellProvider
  • Systematic review against all 16 past security issues — no regressions

Summary by CodeRabbit

Release Notes

  • New Features

    • Fully implemented sandbox management commands: setup, creation, execution, and lifecycle operations now functional
    • Added run command to execute commands inside sandboxes
    • Added config command to display resolved configuration
    • Added profile command to list available security profiles
    • List command now supports JSON output format
  • Improvements

    • Shell command now opens interactive sandbox sessions
    • Network policies are automatically cleaned up when sandboxes are destroyed

muneebs added 3 commits April 16, 2026 22:32
Replace all stub CLI commands with real wiring through a Dependencies
struct that holds only interface fields (Dependency Inversion). Add
Run and Reset to SandboxManager interface, Provisioner and ShellProvider
interfaces (ISP). Fix LimaController to use sandboxName parameter
instead of hardcoded vmName, with per-sandbox policy tracking. Wire
production limactl runners to exec.CommandContext. Add JSONStore.Apply,
LimaProvider.Shell, compile-time interface checks, and fix
TestCheckResourcesForSpec.
- Add api.ProfileRegistry interface, replace *profile.Registry in
  Dependencies and Manager with the interface (DIP)
- Add RemovePolicy to api.NetworkController interface, call from
  Destroy workflow to prevent memory leak in policies map
- Add TestRemovePolicy covering multi-sandbox cleanup isolation
- Improve TestCheckResourcesForSpec: use excessive spec, assert
  len(issues) > 0
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Warning

Rate limit exceeded

@muneebs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 31 minutes and 31 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 31 minutes and 31 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 700a63bb-a526-436a-a7a7-eecf2ee3c5ef

📥 Commits

Reviewing files that changed from the base of the PR and between cece5f9 and 6ce2ac6.

📒 Files selected for processing (16)
  • cmd/airlock/cli/cli.go
  • internal/api/network.go
  • internal/api/profile.go
  • internal/api/vm.go
  • internal/network/network.go
  • internal/network/network_test.go
  • internal/profile/profile.go
  • internal/profile/profile_test.go
  • internal/sandbox/create.go
  • internal/sandbox/delete.go
  • internal/sandbox/reset.go
  • internal/sandbox/sandbox.go
  • internal/sandbox/sandbox_test.go
  • internal/vm/lima/provider.go
  • internal/vm/lima/snapshot.go
  • internal/vm/lima/snapshot_test.go

Walkthrough

This pull request implements real command functionality in the CLI through dependency injection, refactors the network controller for per-sandbox policy management, and introduces new interfaces for provisioning and shell access. Thirteen files across API, CLI, network, sandbox, and VM layers are modified to support these operational behaviors.

Changes

Cohort / File(s) Summary
CLI Implementation
cmd/airlock/cli/cli.go
Replaced static CLI wiring with dependency assembly and injection via assembleDependencies(). Implemented real command behaviors (setup, sandbox, run, shell, list, remove, status, stop, reset, destroy, lock, unlock). Added new commands config and profile. Added sandbox naming/sanitization helpers and TTY detection. Updated all command factory signatures to accept *Dependencies struct containing Manager, Provider, Network, Provisioner, Sheller, Profiles, and Mounts interfaces.
API Interface Additions
internal/api/profile.go, internal/api/sandbox.go, internal/api/vm.go, internal/api/network.go
Added ProfileRegistry interface for profile lookup. Extended SandboxManager with Run and Reset methods. Added Provisioner (VM provisioning, snapshots) and ShellProvider (shell access) interfaces. Added RemovePolicy method to NetworkController.
Network Controller Refactoring
internal/network/network.go, internal/network/network_test.go
Refactored LimaController to track per-sandbox policies via map instead of single VM state. Removed vmName parameter from constructors; updated CurrentPolicy and added RemovePolicy to operate on sandbox scope. Replaced placeholder network execution with real limactl commands via exec.CommandContext. Updated test usage to match new signatures and added TestRemovePolicy.
Sandbox Manager Integration
internal/sandbox/sandbox.go, internal/sandbox/sandbox_test.go, internal/sandbox/delete.go
Updated Manager to depend on api.ProfileRegistry interface. Added network policy cleanup in Destroy method via RemovePolicy. Enhanced test fake network controller with RemovePolicy stub and updated resource validation test.
VM Provider Extensions
internal/vm/lima/provider.go, internal/vm/lima/snapshot.go
Added Shell method to LimaProvider with terminal passthrough. Added compile-time interface assertions verifying LimaProvider implements Provisioner and ShellProvider.
Mount Store Enhancement
internal/mount/mount.go
Added no-op Apply method to JSONStore for mount application interface compliance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #2: Adds and modifies Lima VM provider surface (provider/snapshot/shell/provision functionality), affecting the same provider and snapshot responsibilities expanded in this PR.
  • #1: Establishes Go scaffold and initial API/interface structure that this PR builds upon with concrete CLI wiring and implementation.
  • #3: Implements sandbox manager and Lima provider hardening that directly supports the interface contracts (SandboxManager.Run/Reset, NetworkController.RemovePolicy, Provisioner) introduced here.

Poem

🐰 Behold! Commands now live and breathe,
No more placeholders—let's achieve!
Networks track each sandbox dear,
Provisioners and shells debut here,
Dependency wires all align,
The CLI now shines so fine! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: wire CLI to real implementations' directly and clearly summarizes the main change: replacing stub CLI handlers with wiring to real implementations via dependency injection.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr4/wire-cli-to-real-implementations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/network/network.go (1)

83-96: ⚠️ Potential issue | 🟡 Minor

Shell-quoting is fragile and a latent injection surface.

printf '%%s' '%s' | sudo iptables-restore embeds ruleset inside a single-quoted shell string. Today buildOutputRules only interpolates booleans into hardcoded rules, so this is safe — but the moment api.NetworkPolicy gains any string-valued field (allowlisted domains, per-port rules, custom chains), a single ' in the input breaks out of the quoting and runs arbitrary commands as root inside the VM (note the sudo).

Two safer alternatives:

  1. Write the ruleset to a temp file via provider.CopyToVM and run sudo iptables-restore < /tmp/rules.
  2. Pipe via stdin: pass ruleset as c.Stdin to a sudo iptables-restore command — no shell interpolation at all.

Worth tightening now, or at least a // SECURITY: comment documenting the invariant that buildOutputRules must never emit user-controlled strings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/network/network.go` around lines 83 - 96, ApplyPolicy currently
shells the ruleset into a single-quoted printf which is a command-injection risk
if NetworkPolicy ever contains strings; change ApplyPolicy (and its use of
buildOutputRules/runCmd) to avoid shell interpolation by either: (A) writing the
ruleset to a temp file in the VM via provider.CopyToVM and invoking "sudo
iptables-restore < /tmp/RENAME" with runCmd, or (B) piping the ruleset to a
non-shell command by invoking "sudo iptables-restore" and supplying ruleset as
the command's stdin (modify runCmd to accept stdin if needed); update
lc.policies and locking behavior as before and add a SECURITY comment near
buildOutputRules/ApplyPolicy documenting the invariant if you choose not to
change behavior now.
🧹 Nitpick comments (8)
internal/api/vm.go (1)

23-33: Minor signature consistency nits on the new interfaces.

  • HasCleanSnapshot(name string) bool at Line 26 drops both ctx and error. For a filesystem os.Stat call that's defensible, but a mock/remote implementation can't signal transient errors. Consider (bool, error) and optionally ctx.
  • Shell(name string) error at Line 32 — see the detailed comment on internal/vm/lima/provider.go; adding ctx context.Context aligns with Provider and resolves the noctx lint there.

Not blocking, but easier to change now than once more implementations exist.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/vm.go` around lines 23 - 33, Update the Provisioner and
ShellProvider interfaces to be context- and error-consistent: change
HasCleanSnapshot to accept a context and return (bool, error) (i.e.,
HasCleanSnapshot(ctx context.Context, name string) (bool, error)) so
implementations can report transient failures, and change Shell to include
context (Shell(ctx context.Context, name string) error); then update all
implementations, tests and mocks of Provisioner.HasCleanSnapshot and
ShellProvider.Shell (e.g., provider implementations in
internal/vm/lima/provider.go and any mock types) to match the new signatures and
propagate/return errors accordingly.
internal/api/network.go (1)

18-18: Consider aligning RemovePolicy signature with the rest of NetworkController.

All other methods take ctx context.Context and return error. RemovePolicy(sandboxName string) breaks that pattern. If a future implementation needs to tear down iptables rules on the VM (not just drop an in-memory entry), the missing ctx/error return will force a breaking interface change. Consider:

-	RemovePolicy(sandboxName string)
+	RemovePolicy(ctx context.Context, sandboxName string) error

And handle the error in Destroy (at least log it) so cleanup failures aren't silently swallowed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/network.go` at line 18, Update the
NetworkController.RemovePolicy signature to follow the existing pattern by
adding a context parameter and returning an error (i.e., change
RemovePolicy(sandboxName string) to RemovePolicy(ctx context.Context,
sandboxName string) error), then update all implementations of NetworkController
and any callers (notably Destroy) to pass a ctx and propagate or handle the
returned error (at minimum log it) so cleanup failures aren’t silently ignored;
search for the RemovePolicy, NetworkController, and Destroy symbols to adjust
implementations and call sites accordingly.
internal/api/profile.go (1)

1-11: No import cycle exists; consider whether profile.Profile belongs in internal/api layer.

internal/api/profile.go safely imports internal/profile without creating a cycle. However, the current structure inverts typical Dependency Inversion Principle layering: internal/api (the abstraction/contract layer) depends on internal/profile (implementation details), when ideally implementations should depend on abstractions, not vice versa. Consider whether profile.Profile should be defined in internal/api as a pure data type to maintain correct dependency direction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/profile.go` around lines 1 - 11, The ProfileRegistry currently
depends on the concrete type profile.Profile from internal/profile which inverts
the dependency direction; to fix this, introduce a pure abstraction in the api
package (e.g. define an api.Profile interface or data struct) and update
ProfileRegistry to reference that api.Profile instead of profile.Profile, then
change the internal/profile package to implement/translate to that api.Profile
(implementing the interface or returning the api struct) so implementations
depend on the api abstraction (symbols to change: ProfileRegistry,
profile.Profile, and the internal/profile implementation).
cmd/airlock/cli/cli.go (3)

666-672: Silently skipping profiles on Get error hides registry bugs.

If deps.Profiles.Get(name) fails for a name returned by List(), the row is dropped without any indication. Since List is the source of names, a Get failure signals a bug in the registry — suppressing it turns a loud error into a subtly shorter table. At minimum, log to cmd.ErrOrStderr(); ideally, return the error.

♻️ Suggested change
 		for _, name := range names {
 			p, err := deps.Profiles.Get(name)
 			if err != nil {
-				continue
+				fmt.Fprintf(cmd.ErrOrStderr(), "warning: profile %q: %v\n", name, err)
+				continue
 			}
 			fmt.Fprintf(w, "%s\t%s\n", p.Name, p.Description)
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/airlock/cli/cli.go` around lines 666 - 672, The loop over names silently
skips errors from deps.Profiles.Get(name) which hides registry bugs; update the
loop in cli.go to surface failures instead of dropping rows by either returning
the error from the function or at minimum logging it to cmd.ErrOrStderr() with
context (include the profile name and error). Change the block around the call
to deps.Profiles.Get to check err and call cmd.ErrOrStderr().Write or
processLogger (or return the error) so any Get(name) failure is reported rather
than ignored.

698-703: Strip .git suffix when deriving sandbox name from a GitHub URL.

For https://github.com/user/repo.git, parts[1] is "repo.git" and the name is not passed through sanitizeName, so the sandbox ends up named repo.git (with a . that is allowed by sanitizeName). Users typing the URL straight from GitHub's clone box get a surprising name. Consider trimming .git and routing through sanitizeName for consistency with the local-path branch.

♻️ Suggested change
 	if strings.HasPrefix(source, "https://github.com/") {
 		parts := strings.SplitN(strings.TrimPrefix(source, "https://github.com/"), "/", 3)
 		if len(parts) >= 2 {
-			return parts[1]
+			return sanitizeName(strings.TrimSuffix(parts[1], ".git"))
 		}
 	}

Apply the same TrimSuffix to the gh: branch for symmetry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/airlock/cli/cli.go` around lines 698 - 703, The GitHub-URL branch
currently returns parts[1] which can end with ".git" and bypasses sanitizeName;
update the logic that handles strings.HasPrefix(source, "https://github.com/")
to TrimSuffix(parts[1], ".git") and then pass the resulting repo name through
sanitizeName before returning it; also mirror this TrimSuffix + sanitizeName
behavior in the existing "gh:" branch so both GitHub URL and gh: inputs produce
consistent, sanitized sandbox names.

111-117: Prefer golang.org/x/term.IsTerminal for TTY detection.

The ModeCharDevice heuristic works for the common case but is not idiomatic Go. golang.org/x/term.IsTerminal is the recommended approach by the Go team, handles edge cases like pseudo-terminals across platforms (including Windows), and is actively maintained. Not blocking.

import "golang.org/x/term"

func isTerminal(f *os.File) bool {
    return term.IsTerminal(int(f.Fd()))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/airlock/cli/cli.go` around lines 111 - 117, Replace the custom
ModeCharDevice heuristic in the isTerminal function with the upstream terminal
detection: import "golang.org/x/term" and change isTerminal(f *os.File) to
return term.IsTerminal(int(f.Fd())); update imports accordingly and remove the
old os.File.Stat() based logic so isTerminal uses term.IsTerminal across
platforms.
internal/sandbox/sandbox_test.go (1)

161-162: Consider tracking RemovePolicy calls to strengthen Destroy-path coverage.

The no-op satisfies the interface, but the PR introduces m.network.RemovePolicy(name) in Manager.Destroy specifically to prevent policy-map leaks. Without recording removals, TestDeleteSandbox (and similar) cannot actually verify that Destroy cleans up network state. A one-line removed []string field plus an append here would let you add assert len(network.removed) == 1 in the delete tests.

♻️ Suggested change
 type fakeNetworkController struct {
 	policies []api.NetworkPolicy
 	locked   []string
 	unlocked []string
+	removed  []string
 }
@@
-func (f *fakeNetworkController) RemovePolicy(sandboxName string) {
+func (f *fakeNetworkController) RemovePolicy(sandboxName string) {
+	f.removed = append(f.removed, sandboxName)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/sandbox/sandbox_test.go` around lines 161 - 162, Add tracking for
RemovePolicy calls on the fake network controller by adding a removed []string
field to fakeNetworkController and appending the sandboxName inside
RemovePolicy; update tests (e.g., TestDeleteSandbox) to assert on
network.removed contents/length. Modify the fakeNetworkController struct to
include removed []string and change the RemovePolicy(sandboxName string) method
to append sandboxName to removed so Destroy's call to
m.network.RemovePolicy(name) can be verified.
internal/api/sandbox.go (1)

40-42: Consider streaming output for Run in a future iteration.

Run returns the entire command output as a string, which buffers everything in memory. For long-running commands or large outputs (logs, builds), this will either delay output until completion or blow up memory. An io.Writer-based variant (or adding Stdout/Stderr to a request struct) would let the CLI stream output directly to the terminal. Not blocking for this PR since it matches the CLI's current synchronous fmt.Fprint(cmd.OutOrStdout(), output) pattern, but worth tracking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/sandbox.go` around lines 40 - 42, The Run method on the sandbox
interface buffers entire command output into a string which can OOM or delay
output; add a non-breaking streaming variant to support large/long-running
commands by extending the interface with a RunStream(ctx context.Context, name
string, command []string, stdout io.Writer, stderr io.Writer) error (keep the
existing Run for compatibility), update implementors to provide the new method
(or stub it to call Run and copy to writers for now), and add a TODO comment on
the Run signature to migrate callers to the streaming API later; reference the
Run method and the new RunStream method when making changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/airlock/cli/cli.go`:
- Line 434: The call to MarkFlagRequired("sandbox") returns an error that is
currently ignored; update the MarkFlagRequired invocation in cli.go to either
check and handle the returned error (e.g., err :=
cmd.MarkFlagRequired("sandbox") and handle/log/return if err != nil) or
explicitly discard it with a clear comment to satisfy linters (e.g., _ =
cmd.MarkFlagRequired("sandbox") // explicitly ignore error for required flag
that was just defined). Use the same function name MarkFlagRequired and the
"sandbox" flag reference when making the change.
- Around line 291-293: The message about ephemeral sandboxes is misleading
because the flag stored in SandboxInfo is never acted upon; update the logic so
ephemeral sandboxes are actually cleaned up: in newRunCmd and newShellCmd (and
any other long-running command that creates a sandbox via newSandboxCmd), check
sandboxInfo.Ephemeral and register a cleanup that calls
Manager.Destroy(sandboxID) on exit—use a defer for non-interactive flows and a
signal handler (SIGINT/SIGTERM) for interactive shells to ensure Manager.Destroy
is invoked before process exit; alternatively, if you intentionally want no
automatic cleanup, remove the “will be destroyed on exit” message in
newSandboxCmd so the flag is treated as informational only.

In `@internal/network/network_test.go`:
- Around line 263-264: In TestRemovePolicy, the calls to lc.Lock(...) and
lc.Unlock(...) are missing error checks; update the test to capture and assert
those returns (e.g., err := lc.Lock(ctx, "sandbox-a"); require.NoError(t, err)
and similarly for lc.Unlock) so failures fail the test and subsequent assertions
(like CurrentPolicy calls) aren't misleading; reference the lc.Lock and
lc.Unlock calls inside TestRemovePolicy and use the existing test
helper/assertion style used elsewhere in network_test.go (e.g., require.NoError
or t.Fatalf) to handle errors.

In `@internal/network/network.go`:
- Around line 165-189: Both limactlRunExec and limactlOutputExec are discarding
stderr which hides useful error messages; change them to capture stderr (e.g.,
use a bytes.Buffer for c.Stderr or use c.CombinedOutput()) and include that
captured stderr text in the returned error (wrap with fmt.Errorf and include
vmName and stderr), updating limactlRunExec to run the command, check for
non-nil err and return an error containing the stderr buffer, and updating
limactlOutputExec to return combined output or include stderr in the fmt.Errorf
when c.Output()/c.Run() fails so callers of limactlRunExec and limactlOutputExec
see the real limactl/command error details.

In `@internal/vm/lima/provider.go`:
- Around line 232-243: The Shell method must accept a context to satisfy noctx
and propagate cancellation; change the Shell signature from Shell(name string)
error to Shell(ctx context.Context, name string) error on LimaProvider and the
ShellProvider interface, validate the name with validateName(ctx?) as before
(keep calling validateName(name)), and replace exec.Command(...) with
exec.CommandContext(ctx, p.limactlPath, "shell", name) while still wiring
cmd.Stdin/os.Stdout/os.Stderr and returning cmd.Run(); update all call sites and
interface declarations accordingly to keep the API consistent with other
Provider methods.

---

Outside diff comments:
In `@internal/network/network.go`:
- Around line 83-96: ApplyPolicy currently shells the ruleset into a
single-quoted printf which is a command-injection risk if NetworkPolicy ever
contains strings; change ApplyPolicy (and its use of buildOutputRules/runCmd) to
avoid shell interpolation by either: (A) writing the ruleset to a temp file in
the VM via provider.CopyToVM and invoking "sudo iptables-restore < /tmp/RENAME"
with runCmd, or (B) piping the ruleset to a non-shell command by invoking "sudo
iptables-restore" and supplying ruleset as the command's stdin (modify runCmd to
accept stdin if needed); update lc.policies and locking behavior as before and
add a SECURITY comment near buildOutputRules/ApplyPolicy documenting the
invariant if you choose not to change behavior now.

---

Nitpick comments:
In `@cmd/airlock/cli/cli.go`:
- Around line 666-672: The loop over names silently skips errors from
deps.Profiles.Get(name) which hides registry bugs; update the loop in cli.go to
surface failures instead of dropping rows by either returning the error from the
function or at minimum logging it to cmd.ErrOrStderr() with context (include the
profile name and error). Change the block around the call to deps.Profiles.Get
to check err and call cmd.ErrOrStderr().Write or processLogger (or return the
error) so any Get(name) failure is reported rather than ignored.
- Around line 698-703: The GitHub-URL branch currently returns parts[1] which
can end with ".git" and bypasses sanitizeName; update the logic that handles
strings.HasPrefix(source, "https://github.com/") to TrimSuffix(parts[1], ".git")
and then pass the resulting repo name through sanitizeName before returning it;
also mirror this TrimSuffix + sanitizeName behavior in the existing "gh:" branch
so both GitHub URL and gh: inputs produce consistent, sanitized sandbox names.
- Around line 111-117: Replace the custom ModeCharDevice heuristic in the
isTerminal function with the upstream terminal detection: import
"golang.org/x/term" and change isTerminal(f *os.File) to return
term.IsTerminal(int(f.Fd())); update imports accordingly and remove the old
os.File.Stat() based logic so isTerminal uses term.IsTerminal across platforms.

In `@internal/api/network.go`:
- Line 18: Update the NetworkController.RemovePolicy signature to follow the
existing pattern by adding a context parameter and returning an error (i.e.,
change RemovePolicy(sandboxName string) to RemovePolicy(ctx context.Context,
sandboxName string) error), then update all implementations of NetworkController
and any callers (notably Destroy) to pass a ctx and propagate or handle the
returned error (at minimum log it) so cleanup failures aren’t silently ignored;
search for the RemovePolicy, NetworkController, and Destroy symbols to adjust
implementations and call sites accordingly.

In `@internal/api/profile.go`:
- Around line 1-11: The ProfileRegistry currently depends on the concrete type
profile.Profile from internal/profile which inverts the dependency direction; to
fix this, introduce a pure abstraction in the api package (e.g. define an
api.Profile interface or data struct) and update ProfileRegistry to reference
that api.Profile instead of profile.Profile, then change the internal/profile
package to implement/translate to that api.Profile (implementing the interface
or returning the api struct) so implementations depend on the api abstraction
(symbols to change: ProfileRegistry, profile.Profile, and the internal/profile
implementation).

In `@internal/api/sandbox.go`:
- Around line 40-42: The Run method on the sandbox interface buffers entire
command output into a string which can OOM or delay output; add a non-breaking
streaming variant to support large/long-running commands by extending the
interface with a RunStream(ctx context.Context, name string, command []string,
stdout io.Writer, stderr io.Writer) error (keep the existing Run for
compatibility), update implementors to provide the new method (or stub it to
call Run and copy to writers for now), and add a TODO comment on the Run
signature to migrate callers to the streaming API later; reference the Run
method and the new RunStream method when making changes.

In `@internal/api/vm.go`:
- Around line 23-33: Update the Provisioner and ShellProvider interfaces to be
context- and error-consistent: change HasCleanSnapshot to accept a context and
return (bool, error) (i.e., HasCleanSnapshot(ctx context.Context, name string)
(bool, error)) so implementations can report transient failures, and change
Shell to include context (Shell(ctx context.Context, name string) error); then
update all implementations, tests and mocks of Provisioner.HasCleanSnapshot and
ShellProvider.Shell (e.g., provider implementations in
internal/vm/lima/provider.go and any mock types) to match the new signatures and
propagate/return errors accordingly.

In `@internal/sandbox/sandbox_test.go`:
- Around line 161-162: Add tracking for RemovePolicy calls on the fake network
controller by adding a removed []string field to fakeNetworkController and
appending the sandboxName inside RemovePolicy; update tests (e.g.,
TestDeleteSandbox) to assert on network.removed contents/length. Modify the
fakeNetworkController struct to include removed []string and change the
RemovePolicy(sandboxName string) method to append sandboxName to removed so
Destroy's call to m.network.RemovePolicy(name) can be verified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3dd2e724-2132-4de1-bd24-af36ef4c4476

📥 Commits

Reviewing files that changed from the base of the PR and between d07bfba and cece5f9.

📒 Files selected for processing (13)
  • cmd/airlock/cli/cli.go
  • internal/api/network.go
  • internal/api/profile.go
  • internal/api/sandbox.go
  • internal/api/vm.go
  • internal/mount/mount.go
  • internal/network/network.go
  • internal/network/network_test.go
  • internal/sandbox/delete.go
  • internal/sandbox/sandbox.go
  • internal/sandbox/sandbox_test.go
  • internal/vm/lima/provider.go
  • internal/vm/lima/snapshot.go

Comment thread cmd/airlock/cli/cli.go Outdated
Comment thread cmd/airlock/cli/cli.go Outdated
Comment thread internal/network/network_test.go Outdated
Comment thread internal/network/network.go
Comment thread internal/vm/lima/provider.go
muneebs added 2 commits April 16, 2026 23:27
…tderr capture, Shell context, test error checks
…+error, heredoc injection fix, Profile DIP, deriveSandboxName sanitization, test coverage
@muneebs muneebs merged commit d13d438 into main Apr 16, 2026
1 check passed
@muneebs muneebs deleted the pr4/wire-cli-to-real-implementations branch April 16, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant