{"record":{"id":"756bf578be1b2e58","repo":"chenhg5/cc-connect","slug":"project-q-passwordless-sudo-to-user-q-is-not-co","errorCode":null,"errorMessage":"project %q: passwordless sudo to user %q is not configured. Add a sudoers rule such as:\n  %s ALL=(%s) NOPASSWD: ALL\nthen restart cc-connect. Underlying error: %w","messagePattern":"project %q: passwordless sudo to user %q is not configured\\. Add a sudoers rule such as:\n  (.+?) ALL=\\((.+?)\\) NOPASSWD: ALL\nthen restart cc-connect\\. Underlying error: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/runas_check.go","lineNumber":96,"sourceCode":"//     help the operator find the offending rule.\n//  3. Target user can read AND write the work_dir root (fatal if not),\n//     plus a best-effort descendant walk producing warnings for paths\n//     the target user cannot access.\nfunc PreflightRunAsUser(ctx context.Context, cfg PreflightConfig) PreflightResult {\n\tresult := PreflightResult{Project: cfg.Project, RunAsUser: cfg.RunAsUser}\n\tif cfg.RunAsUser == \"\" {\n\t\tresult.Fatal = append(result.Fatal, errors.New(\"PreflightRunAsUser: RunAsUser is empty\"))\n\t\treturn result\n\t}\n\tif cfg.Runner == nil {\n\t\tcfg.Runner = ExecSudoRunner{}\n\t}\n\tif cfg.ScanConfig.MaxReport == 0 {\n\t\tcfg.ScanConfig = DefaultDescendantScanConfig\n\t}\n\n\tif _, err := cfg.Runner.Run(ctx, \"-n\", \"-iu\", cfg.RunAsUser, \"--\", \"/usr/bin/true\"); err != nil {\n\t\tresult.Fatal = append(result.Fatal, fmt.Errorf(\n\t\t\t\"project %q: passwordless sudo to user %q is not configured. Add a sudoers rule such as:\\n  %s ALL=(%s) NOPASSWD: ALL\\nthen restart cc-connect. Underlying error: %w\",\n\t\t\tcfg.Project, cfg.RunAsUser, currentUsernameOr(\"<supervisor>\"), cfg.RunAsUser, err))\n\t\treturn result // subsequent checks are pointless\n\t}\n\n\tif _, err := cfg.Runner.Run(ctx, \"-n\", \"-iu\", cfg.RunAsUser, \"--\", \"sudo\", \"-n\", \"/usr/bin/true\"); err == nil {\n\t\t// Escalation succeeded — collect sudo -l from the target's\n\t\t// context to help the operator find the offending rule.\n\t\tif out, listErr := cfg.Runner.Run(ctx, \"-n\", \"-iu\", cfg.RunAsUser, \"--\", \"sudo\", \"-n\", \"-l\"); listErr == nil {\n\t\t\tresult.SudoListOutput = strings.TrimSpace(string(out))\n\t\t}\n\t\tmsg := fmt.Sprintf(\n\t\t\t\"project %q: target user %q can run passwordless sudo. The run_as_user sandbox provides no isolation if the spawned agent can escalate non-interactively. Remove NOPASSWD sudo access for this user before starting cc-connect.\",\n\t\t\tcfg.Project, cfg.RunAsUser)\n\t\tif result.SudoListOutput != \"\" {\n\t\t\tmsg += \"\\n\\n`sudo -n -l` as \" + cfg.RunAsUser + \":\\n\" + indent(result.SudoListOutput, \"  \")\n\t\t}\n\t\tresult.Fatal = append(result.Fatal, errors.New(msg))","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/runas_check.go#L78-L114","documentation":"PreflightRunAsUser (core/runas_check.go:96) performs a doctor/startup check per project: it runs 'sudo -n -iu <user> -- /usr/bin/true' and, on failure, records a fatal result explaining that passwordless sudo to the run-as user is not configured, including a ready-to-paste sudoers line and the underlying sudo error. Subsequent checks are skipped since they'd all fail.","triggerScenarios":"Running the doctor (runDoctorOne) or an anonymous preflight when cfg.Runner.Run(ctx, '-n', '-iu', cfg.RunAsUser, '--', '/usr/bin/true') errors: no sudoers rule, rule requires a password, target user missing, or sudo policy blocks non-interactive use.","commonSituations":"New project added to config.toml with run_as_user but the matching sudoers entry was never created; sudoers rule created for the wrong supervisor username (e.g. root vs the daemon's service user); provisioning drifted after a VM rebuild.","solutions":["Add the exact sudoers rule shown in the error to /etc/sudoers.d/ (use visudo -f), substituting the real supervisor and target users","Reload/verify sudoers with visudo -c","Confirm the daemon actually runs as the supervisor user named in the rule (systemd User=)","Test manually: sudo -n -iu <run_as_user> -- /usr/bin/true","Re-run cc-connect doctor to confirm the fatal is cleared"],"exampleFix":"// before: error at startup\n// project \"myproj\": passwordless sudo to user \"agent1\" is not configured.\n// after: /etc/sudoers.d/cc-connect-myproj\nsupervisor ALL=(agent1) NOPASSWD: ALL","handlingStrategy":"validation","validationCode":"func preflightSudo(project, supervisor, target string) error {\n\tout, err := exec.Command(\"sudo\", \"-n\", \"-iu\", target, \"--\", \"/usr/bin/true\").CombinedOutput()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s: add '%s ALL=(%s) NOPASSWD: ALL' to /etc/sudoers.d/: %w: %s\", project, supervisor, target, err, out)\n\t}\n\treturn nil\n}\n// run in deploy smoke tests before cc-connect starts","typeGuard":null,"tryCatchPattern":"result := core.PreflightRunAsUser(ctx, cfg)\nfor _, f := range result.Fatal {\n\tif strings.Contains(f.Error(), \"passwordless sudo\") {\n\t\tfmt.Fprintf(os.Stderr, \"FATAL (fix sudoers, skipping further checks): %v\\n\", f)\n\t\tos.Exit(1)\n\t}\n}","preventionTips":["Ship the sudoers file per project via configuration management, keyed to the daemon's actual runtime user","Run PreflightRunAsUser (doctor) in CI or on deploy before enabling run-as projects","After OS upgrades or VM rebuilds, verify sudoers and target users still exist","Keep one sudoers file per project in /etc/sudoers.d/ so removals are clean"],"tags":["sudo","preflight","doctor","runas","configuration"],"backgroundTag":"permission-denied","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}