Conversation
… if missing in config file
…, add GetWorkersThreads to task loop limit in db
| ) | ||
| if debug { | ||
| log.Println("DB ConnectDB - dataSourceName", dataSourceName) | ||
| log.Println("DB ConnectDB - DSN:", dsn) |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix the problem, the password must not appear in logs. The safest general approach is to either (a) avoid logging the DSN entirely or (b) log a redacted version that excludes or masks the password. We can preserve existing debug functionality (seeing what host, port, database, user are being used) while ensuring the password is never logged.
The single best fix here is to change the debug logging to log a DSN-like string that omits or masks the password. For example, we can build a separate safeDSN string that replaces the password portion with a placeholder like "***" or excludes it entirely, and log that instead of dsn. This maintains functionality (developers can still see connection parameters when debugging) without leaking credentials.
Concretely, in manager/database/DB.go around line 76–87, we will:
- Keep the existing
dsnconstruction unchanged (it’s needed forsql.Open). - Introduce a new
safeDSNvariable for logging, constructed with the samefmt.Sprintfbut replacingpasswordwith a constant such as"***". - Change the
log.Printlncall to logsafeDSNinstead ofdsn.
No new imports or helper methods are needed; fmt is already imported and sufficient.
| @@ -83,7 +83,15 @@ | ||
| database, | ||
| ) | ||
| if debug { | ||
| log.Println("DB ConnectDB - DSN:", dsn) | ||
| safeDSN := fmt.Sprintf( | ||
| "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&clientFoundRows=true", | ||
| username, | ||
| "***", | ||
| host, | ||
| port, | ||
| database, | ||
| ) | ||
| log.Println("DB ConnectDB - DSN:", safeDSN) | ||
| } | ||
|
|
||
| db, err := sql.Open("mysql", dsn) |
| Auth: []ssh.AuthMethod{ | ||
| auth, | ||
| }, | ||
| HostKeyCallback: ssh.InsecureIgnoreHostKey(), |
Check failure
Code scanning / CodeQL
Use of insecure HostKeyCallback implementation High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, the problem is fixed by replacing ssh.InsecureIgnoreHostKey() with a callback that validates the server’s host key against an allow‑list. The simplest and safest pattern in Go is to load one or more trusted public host keys from files (or another configuration source), parse them with ssh.ParseAuthorizedKey or ssh.ParsePublicKey, and then either (a) use ssh.FixedHostKey if there is exactly one expected key, or (b) implement a HostKeyCallback that compares the server’s presented key against the list of allowed keys and rejects unknown keys.
For this codebase, we should keep existing functionality (using the private key file and optional password) and only change how host keys are verified. Since we can’t assume additional fields on utils.ManagerSSHConfig, the least invasive change is to derive an expected host public key from a known file path. A common convention with OpenSSH is that for a private key path like /path/id_rsa, the corresponding public key is /path/id_rsa.pub. We can follow this convention locally within this file: construct the public‑key path by appending .pub to config.PrivateKeyPath, read and parse that as an authorized key, and use ssh.FixedHostKey to set HostKeyCallback. If reading/parsing the public key fails, we should log a fatal error, mirroring the existing fatal behavior on the private key.
Concretely, inside StartSSH in manager/sshTunnel/sshTunnel.go, we will:
- After successfully loading the private key (
auth), derive the public key path (config.PrivateKeyPath + ".pub"). - Read the public key file with
os.ReadFile. - Parse it with
ssh.ParseAuthorizedKeyto get anssh.PublicKey. - Set
HostKeyCallback: ssh.FixedHostKey(hostPublicKey)instead ofssh.InsecureIgnoreHostKey().
No new imports are required, asosandsshare already imported. This keeps authentication behavior the same while adding strong host key verification.
| @@ -74,13 +74,27 @@ | ||
| log.Fatal("Error loading file ", config.PrivateKeyPath, err) | ||
| } | ||
|
|
||
| // Load the expected host public key from a .pub file corresponding to the private key | ||
| hostPublicKeyPath := config.PrivateKeyPath + ".pub" | ||
| if !checkFileExists(hostPublicKeyPath) { | ||
| log.Fatal("Host public key file ", hostPublicKeyPath, " not found") | ||
| } | ||
| hostPubBytes, err := os.ReadFile(hostPublicKeyPath) | ||
| if err != nil { | ||
| log.Fatal("Error reading host public key file ", hostPublicKeyPath, err) | ||
| } | ||
| hostPubKey, _, _, _, err := ssh.ParseAuthorizedKey(hostPubBytes) | ||
| if err != nil { | ||
| log.Fatal("Error parsing host public key from ", hostPublicKeyPath, err) | ||
| } | ||
|
|
||
| // SSH connection configuration | ||
| sshConfig := &ssh.ClientConfig{ | ||
| User: config.SSHUsername, | ||
| Auth: []ssh.AuthMethod{ | ||
| auth, | ||
| }, | ||
| HostKeyCallback: ssh.InsecureIgnoreHostKey(), | ||
| HostKeyCallback: ssh.FixedHostKey(hostPubKey), | ||
| } | ||
|
|
||
| // If a password is provided, add it as an additional authentication method |
| return exec.Command("cmd", "/c", cmdStr) | ||
| } else if runtime.GOOS == "linux" { | ||
| // use --login to load bashrc | ||
| return exec.Command("bash", "--login", "-c", cmdStr) |
Check failure
Code scanning / CodeQL
Command built from user-controlled sources Critical
| var stdout, stderr bytes.Buffer | ||
|
|
||
| // Set the output and error streams to the buffers | ||
| return exec.Command(command, argumentsArray...), nil |
Check failure
Code scanning / CodeQL
Command built from user-controlled sources Critical
| // Ensure the directory exists | ||
| dir := getDirectory(path) | ||
| const dirPerm = 0600 // Use restricted permissions (0600) | ||
| if err := os.MkdirAll(dir, dirPerm); err != nil { |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, the fix is to ensure that any path derived (even indirectly) from websocket data is validated or constrained before being passed to filesystem APIs such as os.MkdirAll, os.WriteFile, and os.Remove. The usual approaches are: (1) enforce that the path is a single filename (no separators / ..), or (2) resolve it against a configured safe root directory and verify that the resulting absolute path is still under that root.
Here, RemoteFilePath can contain directories (you are calling filepath.Dir on it), so we should use the “safe root directory” approach. A minimal, non‑breaking change is:
- Introduce a helper inside
modules.goto map any providedRemoteFilePathinto a safe directory. A simple choice (consistent with the existing code) is to root all paths under the process’s current working directory:- Compute
safeBase, an absolute path (e.g.os.Getwd()once per call). - For each
file.RemoteFilePath, join it withsafeBaseand normalize viafilepath.Abs. - Verify that the resulting absolute path has
safeBaseas a prefix (taking care with separators). - If the check fails, return an error and skip processing/deletion for that file.
- Compute
- Use the validated absolute path both for directory creation and writing the file in
ProcessFiles, and for deletion inDeleteFiles. That way, even if an attacker passes"../../etc/passwd"or"/root/.ssh/authorized_keys", the normalized path will either be rejected or confined under the safe base.
Concretely, in worker/modules/modules.go:
- Add a small helper
getSafeBaseDir()that returnsos.Getwd()and caches/uses it per call to avoid recomputing it in every loop iteration. - Add a helper
sanitizePathUnsafe(baseDir, relativePath string) (string, error)which:joined := filepath.Join(baseDir, relativePath)abs, err := filepath.Abs(joined)- ensures
absstarts withbaseDir + string(os.PathSeparator)or equalsbaseDir - returns an error if not.
- In
ProcessFiles, before usingpath := file.RemoteFilePath, computesafePath, err := sanitizePathUnsafe(baseDir, path)and usesafePathforgetDirectory,MkdirAll,WriteFile, and in logs. - In
DeleteFiles, do the same: computesafePathfromfile.RemoteFilePath, deletesafePath, and log that path; if sanitization fails, return an error or at least skip that file.
No new imports are needed; all required functions (os.Getwd, filepath.Abs, filepath.Join) are already available via existing imports.
| } | ||
| // Write the decoded content to the specified path | ||
| const filePerm = 0600 | ||
| if err := os.WriteFile(path, decodedBytes, filePerm); err != nil { |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, to fix uncontrolled path usage you must ensure that any path derived from untrusted data is either (a) validated as a single safe filename (no separators, no ..), or (b) resolved relative to a trusted base directory and checked to remain within that directory after normalization. For this worker, it is more flexible and backward-compatible to keep supporting subdirectories but force all paths to live under a known safe root (e.g., a worker’s base working directory from config).
The best fix here is to change ProcessFiles so that it no longer uses file.RemoteFilePath directly. Instead:
- Decide a safe base directory (we must rely only on code we see;
utils.WorkerConfigis available but we don’t see its fields, so we’ll conservatively use the current working directory as a base and enforce that all paths stay within it). - For each
file.RemoteFilePath:- Reject empty paths.
- Clean it with
filepath.Clean. - Reject any absolute path (
filepath.IsAbs(cleaned)). - Build an absolute path under the safe base:
absPath := filepath.Join(baseDir, cleaned)and thenabsPath, err = filepath.Abs(absPath). - Ensure
absPathis still withinbaseDirby prefix check (after obtainingbaseDirAbsand appending a trailing separator).
- Use
absPathfor directory creation and writing, instead of the rawpath. - Keep logging using the original
pathor the resolvedabsPathas appropriate.
We will implement this entirely inside ProcessFiles in worker/modules/modules.go, adding local variables for baseDirAbs and absPath, and update lines 204–207 and 211–212 to use absPath instead of the unvalidated path. No new imports are required because filepath and strings are already imported.
| path := file.RemoteFilePath | ||
|
|
||
| // Attempt to delete the file | ||
| err := os.Remove(path) |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, to fix uncontrolled-path issues, derive a normalized absolute path from a trusted base directory plus the user-supplied path, then verify that the resulting path is still within that base directory. Reject or skip the operation if the check fails. Apply this consistently for all file operations (create, write, delete) that use user-controlled path data.
For this codebase, the minimal, non-breaking fix is:
-
Define a helper function in
worker/modules/modules.gothat:- Takes a
*utils.WorkerConfig(to get a base directory; if there is a suitable field in the real code, use it; since we cannot see it, we’ll assume a field likeWorkDirexists and fall back safely if empty). - Takes the untrusted relative path (
file.RemoteFilePath). - Joins it with the base directory, obtains an absolute path using
filepath.Abs, and checks that:- It’s under the base directory (by checking
HasPrefixagainst a cleaned absolute base, taking care to include a path separator).
- It’s under the base directory (by checking
- Returns the safe absolute path or an error.
- Takes a
-
In
ProcessFiles:- Replace direct use of
file.RemoteFilePathwith the validated safe path from the helper. - Use that safe path to compute the directory (
getDirectory) and to callos.MkdirAllandos.WriteFile. - Return a clear error if validation fails.
- Replace direct use of
-
In
DeleteFiles:- Change the signature to accept
config *utils.WorkerConfig(so we can reuse the same validation). - Replace direct use of
file.RemoteFilePathinos.Removewith the validated safe path from the helper. - Log or return an error when validation fails or deletion fails.
- Change the signature to accept
-
In
worker/process/processTask.go:- Update the call to
modules.DeleteFilesto passconfig.
- Update the call to
Concretely:
-
Add a new helper in
modules.go, e.g.:func getSafePath(config *utils.WorkerConfig, relPath string) (string, error) { baseDir := config.WorkDir // or another existing field; fallback to current dir if empty if baseDir == "" { baseDir = "." } baseAbs, err := filepath.Abs(baseDir) if err != nil { return "", fmt.Errorf("failed to resolve base directory: %w", err) } candidate := filepath.Join(baseAbs, relPath) candidateAbs, err := filepath.Abs(candidate) if err != nil { return "", fmt.Errorf("failed to resolve path %q: %w", relPath, err) } // Ensure candidateAbs is within baseAbs baseWithSep := baseAbs if !strings.HasSuffix(baseWithSep, string(os.PathSeparator)) { baseWithSep += string(os.PathSeparator) } if candidateAbs != baseAbs && !strings.HasPrefix(candidateAbs, baseWithSep) { return "", fmt.Errorf("path %q escapes base directory", relPath) } return candidateAbs, nil }
-
Modify
ProcessFiles:- After
path := file.RemoteFilePath, callsafePath, err := getSafePath(config, path)and usesafePathinstead ofpathingetDirectory,MkdirAll,WriteFile, and logging.
- After
-
Modify
DeleteFiles:- Change signature to
func DeleteFiles(task *globalstructs.Task, config *utils.WorkerConfig, verbose, debug bool) error. - For each file, call
safePath, err := getSafePath(config, path)and usesafePathinos.Removeand logging.
- Change signature to
-
Modify
TaskinprocessTask.go:- Change
modules.DeleteFiles(task, verbose, debug)tomodules.DeleteFiles(task, config, verbose, debug).
- Change
This keeps existing behavior for valid in-sandbox paths, while preventing arbitrary filesystem access and addressing the CodeQL alert.
| @@ -38,7 +38,7 @@ | ||
| } | ||
|
|
||
| if config.DeleteFiles { | ||
| err = modules.DeleteFiles(task, verbose, debug) | ||
| err = modules.DeleteFiles(task, config, verbose, debug) | ||
| if err != nil { | ||
| log.Println("Process Error DeleteFiles:", err) | ||
| } |
No description provided.