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.CubeSandboxTTL

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Set the command's work dir to a path under /workspace.
  2. If the work must happen in the skills image root, run in skills-install mode (allowSkillsRoot) so that root is permitted.
  3. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/ac5590584d73787f. Report an issue: GitHub.