mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 21:33:41 +02:00
* fix(daemon): run Codex unsandboxed on Windows to stop reject-by-policy (MUL-4957) Windows has no Landlock/Seatbelt-equivalent filesystem sandbox that the daemon configures, so the per-task `sandbox_mode = "workspace-write"` it wrote was unenforceable. Worse than having no sandbox, it pushed Codex into rejecting non-safe mutation commands "by policy": `multica issue create` fails with "was rejected by policy" because Codex can neither sandbox the command nor (under approval_policy = "never") escalate it to the daemon's auto-approver, so the request never reaches the approver. Mirror the existing macOS fallback and give Windows danger-full-access so those commands run. Also generalize the danger-full-access warn log so it no longer hardcodes "on macOS" and only surfaces the macOS-specific upgrade hint on macOS (new codexSandboxPolicy.Hint field). Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): correct Windows sandbox rationale and respect user windows.sandbox (MUL-4957) Addresses two review must-fixes on #5672: 1. Correct a false security fact. The comments and log Reason claimed Windows has no filesystem sandbox backend. Codex 0.144.5 does ship a native Windows sandbox (windows.sandbox = "unelevated"/"elevated"); it is experimental with open upstream reliability bugs, so the daemon defaults to danger-full-access as a deliberate compatibility choice. Enabling the native sandbox is tracked as separate follow-up work. 2. Stop silently downgrading users who opted into isolation. The fallback was unconditional. Add codexSandboxPolicyForConfig: on Windows an explicit windows.sandbox = unelevated|elevated keeps workspace-write so Codex enforces task isolation with the user's chosen backend; danger-full-access applies only when windows.sandbox is absent, disabled, or unparseable. This is also the branch point for a future native-sandbox rollout (flip the default; callers unchanged). Adds fixture tests locking the priority (user opt-in kept vs. unconfigured fallback) plus predicate coverage for codexSandboxPolicyForConfig. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): fail closed on undecidable Windows sandbox config, honor -c windows.sandbox (MUL-4957) Second review round on #5672. Two must-fixes. 1. Undecidable config no longer fails open. The old bool detector collapsed "unparseable / invalid value / failed copy" into "unconfigured" and then loosened to danger-full-access. Replaced with a tri-state (absent/native/undecidable): only exact-lowercase unelevated|elevated (the sole values Codex accepts — verified: any other value makes Codex refuse to load the config) counts as native; any other present value, unparseable TOML, a read error, or a missing per-task config when a shared ~/.codex/config.toml exists (i.e. the copy failed) is undecidable and fails closed to workspace-write — it never loosens — logged at error level. 2. windows.sandbox set via `-c`/`--config` custom args is now honored. Such args never land in config.toml, so config-only detection silently downgraded those users' isolation. The effective Codex args (daemon defaults + profile-fixed + per-agent custom_args) are threaded through PrepareParams/ReuseParams/CodexHomeOptions into the sandbox decision and scanned for a windows.sandbox override (inline, two-token, quoted, spaced; last-wins). Also drops issue-status-bound source comments (openai/codex#24098 has since closed). Adds unit coverage for config/args classification, the fold precedence (undecidable > native > absent), and the copy-failed fail-closed path. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): fail closed on config-sync errors and honor shell-quoted -c windows.sandbox (MUL-4957) Round-3 review must-fixes: 1. resolveWindowsSandboxState now takes the config.toml sync error and a tri-state shared-config presence instead of re-stat-ing inside. A failed sync (stale/absent per-task copy) or an un-stat-able shared source is undecidable and keeps workspace-write, closing the fail-open where a failed sync was read as "unconfigured". Splits IO from the decision so the paths are unit-testable without faulting the filesystem. 2. The Windows sandbox decision consumes agent.NormalizeCodexLaunchArgs (the shared helper buildCodexArgs now uses) so a shell-quoted -c windows.sandbox opt-in is normalized identically to launch, instead of being missed by a raw-token scan and silently downgraded. Co-authored-by: multica-agent <github@multica.ai> * fix(daemon): abort when the Codex sandbox block cannot be written (MUL-4957) Round-4 review must-fix: ensureCodexSandboxConfig failures were warn-and-continue, so a computed fail-closed workspace-write policy could stay only in memory while config.toml kept a stale danger-full-access from a prior run — the decision failed closed but the effective config failed open. prepareCodexHomeWithOpts now returns the error, which blocks startup on both paths: fresh Prepare fails the task, and Reuse leaves env.CodexHome unset, which configureCodexTaskShellEnvironment already refuses to start. Regression covers the full reuse scenario (stale danger-full-access + failed config sync + failed managed-block write); it fails with "got nil" without the fix. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: J <j@multica.ai>
50 lines
2.0 KiB
Go
50 lines
2.0 KiB
Go
package execenv
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/multica-ai/multica/server/pkg/agent"
|
|
)
|
|
|
|
// TestWindowsSandboxHonorsShellQuotedCustomArg is the MUL-4957 round-3 must-fix
|
|
// 2 integration test. A `-c windows.sandbox=...` opt-in supplied shell-quoted
|
|
// (as users commonly type custom_args) reaches Codex normalized by
|
|
// agent.NormalizeCodexLaunchArgs; the Windows sandbox decision must consume the
|
|
// SAME normalized args, not the raw tokens, or the two drift and the user's
|
|
// isolation opt-in is silently downgraded. This locks the raw-args →
|
|
// normalized → policy chain end to end across the two packages so they cannot
|
|
// diverge again.
|
|
func TestWindowsSandboxHonorsShellQuotedCustomArg(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
name string
|
|
raw []string
|
|
}{
|
|
{"both tokens single-quoted", []string{"'-c'", "'windows.sandbox=unelevated'"}},
|
|
{"value double-quoted", []string{"-c", `"windows.sandbox=elevated"`}},
|
|
{"inline flag single-quoted", []string{"'-c=windows.sandbox=unelevated'"}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// The raw tokens still carry shell quotes, so scanning them directly
|
|
// must NOT recognize the opt-in — this is the drift the bug was.
|
|
if got := windowsSandboxFromCustomArgs(tc.raw); got == windowsSandboxNative {
|
|
t.Fatalf("raw shell-quoted args unexpectedly recognized without normalization: %v", tc.raw)
|
|
}
|
|
// After the daemon's normalization the same args resolve to native,
|
|
// so the policy keeps workspace-write instead of loosening.
|
|
norm := agent.NormalizeCodexLaunchArgs(nil, tc.raw, nil, testLogger())
|
|
missing := filepath.Join(t.TempDir(), "config.toml")
|
|
state := resolveWindowsSandboxState(missing, nil, sharedConfigAbsent, norm, testLogger())
|
|
if state != windowsSandboxNative {
|
|
t.Fatalf("normalized %v -> state %v, want native", norm, state)
|
|
}
|
|
if p := codexSandboxPolicyForWindows(state); p.Mode != "workspace-write" {
|
|
t.Fatalf("policy mode = %q, want workspace-write (isolation preserved)", p.Mode)
|
|
}
|
|
})
|
|
}
|
|
}
|