Tencent/WeKnora · error
sandbox: work dir %q is outside allowed roots (%s)
Error message
sandbox: work dir %q is outside allowed roots (%s)
What it means
cleanSessionWorkDir keeps shell_exec inside directories the sandbox is willing to let the agent work in. By default only /workspace is allowed; with the skills-install mode the skills image root is added. It throws this when the requested working directory is not under one of those roots.
Source
Thrown at internal/sandbox/session_manager.go:1066
// allowSkillsRoot widens it to the skills image root for install/maintenance
// sessions, so the installer agent can set work_dir to the skill directory and
// run ordinary relative commands instead of composing long absolute paths. It
// is a widening, not a removal: everything outside these two roots is still
// refused.
func cleanSessionWorkDir(workDir string, allowSkillsRoot bool) (string, error) {
clean := path.Clean(strings.TrimSpace(workDir))
if clean == SessionWorkspaceRoot || strings.HasPrefix(clean, SessionWorkspaceRoot+"/") {
return clean, nil
}
if allowSkillsRoot &&
(clean == SkillsImageRoot || strings.HasPrefix(clean, SkillsImageRoot+"/")) {
return clean, nil
}
allowed := SessionWorkspaceRoot
if allowSkillsRoot {
allowed = SessionWorkspaceRoot + ", " + SkillsImageRoot
}
return "", fmt.Errorf(
"sandbox: work dir %q is outside allowed roots (%s)",
workDir, allowed,
)
}
// buildSessionCreateRequest projects Config into a provider-neutral remote
// create request. The metadata block is populated per-session by the
// lifecycle coordinator; env vars propagate as-is.
//
// The provider parameter (derived from RemoteSandboxClient.Provider()) is the
// authoritative source of identity — it selects the correct Config fields so
// Cube and E2B never read each other's templates or TTLs.
func buildSessionCreateRequest(provider RemoteProvider, cfg *Config) (RemoteCreateRequest, error) {
envVars := withWorkspaceEnvDefaults(cloneMetadata(cfg.EnvVars))
switch provider {
case SandboxTypeCube:
ttl := cfg.CubeSandboxTTLView on GitHub (pinned to 988cbb0330)
Solutions
- Set the command's work dir to a path under /workspace.
- If the work must happen in the skills image root, run in skills-install mode (allowSkillsRoot) so that root is permitted.
- Create the needed directory under /workspace and cd there in the command instead.
Example fix
// before opts.WorkDir = "/opt/tools" // after opts.WorkDir = "/workspace/tools"
Defensive patterns
Strategy: validation
Validate before calling
func allowedWorkDir(dir string, allowSkills bool) bool {
c := filepath.Clean(dir)
ok := strings.HasPrefix(c, "/workspace/")
if allowSkills {
ok = ok || strings.HasPrefix(c, skillsImageRoot+"/")
}
return ok
} Type guard
func inRoot(dir, root string) bool {
c := filepath.Clean(dir)
return strings.HasPrefix(c, root+"/")
} Try / catch
err := exec.ExecShellCommandWithOptions(ctx, s, opts)
if err != nil && strings.Contains(err.Error(), "outside allowed roots") {
opts.WorkDir = "/workspace"
err = exec.ExecShellCommandWithOptions(ctx, s, opts)
} Prevention
- Default WorkDir to "/workspace" and require justification for anything else.
- Clean all work-dir values with filepath.Clean before use.
- Only enable the skills root in install-mode sessions.
When it happens
Trigger: ExecShellCommandWithOptions invoked with WorkDir set to anything outside /workspace (and, when allowSkillsRoot is set, outside the skills image root) — e.g. "/", "/home/user", "/opt/tool". Also reachable via ValidatedSessionOutputDir and the two cleanSessionWorkDir tests.
Common situations: Trying to cd into an installed tool's directory outside the workspace; reusing host-style working directories in remote sessions; assuming skills directories are executable workspaces in ordinary sessions.
Related errors
- sandbox: workspace write path %q is outside %s
- sandbox: config is missing required fields
- cube remote client config is required
- cube api: create sandbox: empty sandboxID
- sandbox: docker backend is disabled; enable it in System Set
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/ac5590584d73787f.
Report an issue: GitHub.