{"record":{"id":"e31e49ed439cd676","repo":"chenhg5/cc-connect","slug":"target-user-q-can-run-passwordless-sudo-isolatio","errorCode":null,"errorMessage":"target user %q can run passwordless sudo; isolation is meaningless. Remove NOPASSWD sudo for this user. Output: %s","messagePattern":"target user %q can run passwordless sudo; isolation is meaningless\\. Remove NOPASSWD sudo for this user\\. Output: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/runas.go","lineNumber":249,"sourceCode":"//\n// The expensive checks (work_dir access, isolation probe) live in the\n// preflight and audit packages and only run at startup / via `cc-connect\n// doctor user-isolation`.\nfunc VerifyRunAsUserCheap(ctx context.Context, runner SudoRunner, runAsUser string) error {\n\tif runAsUser == \"\" {\n\t\treturn errors.New(\"VerifyRunAsUserCheap: runAsUser is empty\")\n\t}\n\tif verifyCacheHit(runAsUser) {\n\t\treturn nil\n\t}\n\tif out, err := runner.Run(ctx, \"-n\", \"-iu\", runAsUser, \"--\", \"/usr/bin/true\"); err != nil {\n\t\tverifyCacheEvict(runAsUser)\n\t\treturn 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)))\n\t}\n\tout, err := runner.Run(ctx, \"-n\", \"-iu\", runAsUser, \"--\", \"sudo\", \"-n\", \"/usr/bin/true\")\n\tif err == nil {\n\t\tverifyCacheEvict(runAsUser)\n\t\treturn 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)))\n\t}\n\tverifyCacheStore(runAsUser)\n\treturn nil\n}\n\n// verifyCacheTTL is short by design. It absorbs a burst of messages\n// (one Slack user typing rapidly) while still re-verifying often enough\n// that a sudoers edit during a long idle gap is caught on the next spawn.\nconst verifyCacheTTL = 30 * time.Second\n\nvar (\n\tverifyCacheMu sync.Mutex\n\tverifyCache   = map[string]time.Time{}\n)\n\nfunc verifyCacheHit(user string) bool {\n\tverifyCacheMu.Lock()\n\tdefer verifyCacheMu.Unlock()","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/runas.go#L231-L267","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Remove NOPASSWD sudo privileges from the target user: delete or narrow the offending /etc/sudoers.d entry","Ensure the target user is not in the sudo/wheel group (gpasswd -d <target> sudo)","Scope remaining sudoers rules to the specific supervisor→target direction only, never ALL","Verify with: sudo -n -iu <target> -- sudo -n /usr/bin/true — this must now fail","Restart cc-connect so verification re-runs and the cache refreshes"],"exampleFix":"// before: /etc/sudoers.d/agent\nagentuser ALL=(ALL) NOPASSWD: ALL\n// after: no sudo for the target; only supervisor may act as agentuser\nsupervisor ALL=(agentuser) NOPASSWD: ALL","handlingStrategy":"validation","validationCode":"func targetCanEscalate(target string) bool {\n\treturn exec.Command(\"sudo\", \"-n\", \"-iu\", target, \"--\", \"sudo\", \"-n\", \"/usr/bin/true\").Run() == nil\n}\n// must be false before enabling run-as isolation","typeGuard":null,"tryCatchPattern":"sess, err := newClaudeSession(ctx, cfg)\nif err != nil {\n\tif strings.Contains(err.Error(), \"can run passwordless sudo\") {\n\t\tlog.Fatalf(\"Security: %v — strip NOPASSWD sudo from the target user and retry\", err)\n\t}\n\treturn err\n}","preventionTips":["Never grant NOPASSWD ALL to service/agent users; scope sudoers to supervisor→target only","Exclude agent users from sudo/wheel groups in provisioning","Add a deployment check that the target user cannot sudo (this probe) and fail the deploy otherwise","Audit /etc/sudoers.d/ periodically for overly broad rules"],"tags":["sudo","security","isolation","privilege-escalation"],"backgroundTag":"insufficient-permissions","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}