chenhg5/cc-connect · critical

target user %q can run passwordless sudo; isolation is meani

Error message

target user %q can run passwordless sudo; isolation is meaningless. Remove NOPASSWD sudo for this user. Output: %s

What it means

VerifyRunAsUserCheap (core/runas.go:249) refuses to start an isolated session when the target run-as user can itself run passwordless sudo ('sudo -n /usr/bin/true' succeeds as that user). If the sandboxed user can escalate to root, the isolation is theater, so startup fails closed with a remediation hint.

Source

Thrown at core/runas.go:249

//
// The expensive checks (work_dir access, isolation probe) live in the
// preflight and audit packages and only run at startup / via `cc-connect
// doctor user-isolation`.
func VerifyRunAsUserCheap(ctx context.Context, runner SudoRunner, runAsUser string) error {
	if runAsUser == "" {
		return errors.New("VerifyRunAsUserCheap: runAsUser is empty")
	}
	if verifyCacheHit(runAsUser) {
		return nil
	}
	if out, err := runner.Run(ctx, "-n", "-iu", runAsUser, "--", "/usr/bin/true"); err != nil {
		verifyCacheEvict(runAsUser)
		return fmt.Errorf("passwordless sudo to user %q failed (check that your sudoers rule is present and scoped to this user): %w: %s", runAsUser, err, strings.TrimSpace(string(out)))
	}
	out, err := runner.Run(ctx, "-n", "-iu", runAsUser, "--", "sudo", "-n", "/usr/bin/true")
	if err == nil {
		verifyCacheEvict(runAsUser)
		return fmt.Errorf("target user %q can run passwordless sudo; isolation is meaningless. Remove NOPASSWD sudo for this user. Output: %s", runAsUser, strings.TrimSpace(string(out)))
	}
	verifyCacheStore(runAsUser)
	return nil
}

// verifyCacheTTL is short by design. It absorbs a burst of messages
// (one Slack user typing rapidly) while still re-verifying often enough
// that a sudoers edit during a long idle gap is caught on the next spawn.
const verifyCacheTTL = 30 * time.Second

var (
	verifyCacheMu sync.Mutex
	verifyCache   = map[string]time.Time{}
)

func verifyCacheHit(user string) bool {
	verifyCacheMu.Lock()
	defer verifyCacheMu.Unlock()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Remove NOPASSWD sudo privileges from the target user: delete or narrow the offending /etc/sudoers.d entry
  2. Ensure the target user is not in the sudo/wheel group (gpasswd -d <target> sudo)
  3. Scope remaining sudoers rules to the specific supervisor→target direction only, never ALL
  4. Verify with: sudo -n -iu <target> -- sudo -n /usr/bin/true — this must now fail
  5. Restart cc-connect so verification re-runs and the cache refreshes

Example fix

// before: /etc/sudoers.d/agent
agentuser ALL=(ALL) NOPASSWD: ALL
// after: no sudo for the target; only supervisor may act as agentuser
supervisor ALL=(agentuser) NOPASSWD: ALL
Defensive patterns

Strategy: validation

Validate before calling

func targetCanEscalate(target string) bool {
	return exec.Command("sudo", "-n", "-iu", target, "--", "sudo", "-n", "/usr/bin/true").Run() == nil
}
// must be false before enabling run-as isolation

Try / catch

sess, err := newClaudeSession(ctx, cfg)
if err != nil {
	if strings.Contains(err.Error(), "can run passwordless sudo") {
		log.Fatalf("Security: %v — strip NOPASSWD sudo from the target user and retry", err)
	}
	return err
}

Prevention

When it happens

Trigger: Called from newClaudeSession when the second probe, run AS the target user, succeeds — meaning the sudoers setup granted the target user NOPASSWD sudo (e.g. an overly broad 'agentuser ALL=(ALL) NOPASSWD: ALL' rule, or membership in a sudo group with NOPASSWD).

Common situations: Admin lazily granted the agent user full sudo instead of scoping it; the target user is in the wheel/sudo group with NOPASSWD set globally; a base image or provisioning script added broad sudoers rules for service users.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/e31e49ed439cd676. Report an issue: GitHub.