fix(gosec): tighten file modes and annotate false positives

Write job state, configs and imported models with mode 0600, clean
the paths that are read from disk, and mark the route tables, the
header name and the retry jitter as reviewed false positives.

Assisted-by: Claude Code:claude-sonnet-5-5 gosec
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto committed 2026-10-20 20:54:43 +00:00
1 parent 1604a67c1f
commit ed6e677640
9 files changed
+17 -13

No files matched your search

+1
View File
@@ -623,6 +623,7 @@ func (a *Application) waitForHealthyWorker() {
}
}
// Add 0-1s jitter to prevent thundering-herd on the node registry
// #nosec G404 -- jitter only spreads retries, it is not security sensitive.
jitter := time.Duration(rand.Int64N(int64(time.Second)))
select {
case <-a.applicationConfig.Context.Done():
+1 -1
View File
@@ -730,7 +730,7 @@ func initializeWatchdog(application *Application, options *config.ApplicationCon
// loadOrGenerateHMACSecret loads an HMAC secret from the given file path,
// or generates a random 32-byte secret and persists it if the file doesn't exist.
func loadOrGenerateHMACSecret(path string) (string, error) {
data, err := os.ReadFile(path)
data, err := os.ReadFile(filepath.Clean(path))
if err == nil {
secret := string(data)
if len(secret) >= 32 {
+1 -1
View File
@@ -379,7 +379,7 @@ func (r *RunCMD) Run(ctx *cliContext.Context) (result error) {
config.WithMLXTunnelCallback(func(tunnels []string) {
hostfile := filepath.Join(os.TempDir(), "localai_mlx_hostfile.json")
data, _ := json.Marshal(tunnels)
os.WriteFile(hostfile, data, 0644)
os.WriteFile(hostfile, data, 0600)
os.Setenv("MLX_DISTRIBUTED_HOSTFILE", hostfile)
xlog.Debug("setting MLX_DISTRIBUTED_HOSTFILE", "value", hostfile, "tunnels", tunnels)
}),
+1 -1
View File
@@ -253,7 +253,7 @@ func DownloadExportedModelEndpoint(ftService *finetune.FineTuneService) echo.Han
return nil
}
f, err := os.Open(path)
f, err := os.Open(filepath.Clean(path))
if err != nil {
return err
}
+2
View File
@@ -409,6 +409,7 @@ func RegisterLocalAIRoutes(router *echo.Echo,
"load_status": "/api/models/:id/load-status",
"load_cancel": "/api/models/:id/load-cancel",
},
// #nosec G101 -- route paths, not credentials.
"ai_functions": map[string]string{
"tts": "/tts",
"tts_voices": "/v1/audio/voices",
@@ -429,6 +430,7 @@ func RegisterLocalAIRoutes(router *echo.Echo,
"prompts": "/v1/mcp/prompts/:model",
"resources": "/v1/mcp/resources/:model",
},
// #nosec G101 -- route paths, not credentials.
"p2p": map[string]string{
"nodes": "/api/p2p",
"token": "/api/p2p/token",
+1 -1
View File
@@ -31,7 +31,7 @@ func NewFileStore(path string) (*FileStore, error) {
if path == "" {
return s, nil
}
b, err := os.ReadFile(path)
b, err := os.ReadFile(filepath.Clean(path))
if errors.Is(err, os.ErrNotExist) {
return s, nil
}
+4 -4
View File
@@ -115,7 +115,7 @@ func (s *FineTuneService) saveJobState(job *schema.FineTuneJob) {
}
statePath := filepath.Join(dir, "state.json")
if err := os.WriteFile(statePath, data, 0640); err != nil {
if err := os.WriteFile(statePath, data, 0600); err != nil {
xlog.Error("Failed to write job state", "job_id", job.ID, "error", err)
}
}
@@ -137,7 +137,7 @@ func (s *FineTuneService) loadJobsFromDisk(_ context.Context) ([]*schema.FineTun
continue
}
statePath := filepath.Join(baseDir, entry.Name(), "state.json")
data, err := os.ReadFile(statePath)
data, err := os.ReadFile(filepath.Clean(statePath))
if err != nil {
continue
}
@@ -660,7 +660,7 @@ func (s *FineTuneService) ExportModel(ctx context.Context, userID, jobID string,
s.setExportFailed(job, fmt.Sprintf("failed to marshal config: %v", err))
return
}
if err := os.WriteFile(configPath, yamlData, 0644); err != nil {
if err := os.WriteFile(configPath, yamlData, 0600); err != nil {
s.setExportFailed(job, fmt.Sprintf("failed to write config file: %v", err))
return
}
@@ -765,7 +765,7 @@ func (s *FineTuneService) UploadDataset(filename string, data []byte) (string, e
return "", fmt.Errorf("invalid filename")
}
filePath := filepath.Join(uploadDir, uuid.New().String()[:8]+"-"+base)
if err := os.WriteFile(filePath, data, 0640); err != nil {
if err := os.WriteFile(filePath, data, 0600); err != nil {
return "", fmt.Errorf("failed to write dataset: %w", err)
}
+5 -5
View File
@@ -138,7 +138,7 @@ func (s *QuantizationService) saveJobState(job *schema.QuantizationJob) {
}
statePath := filepath.Join(dir, "state.json")
if err := os.WriteFile(statePath, data, 0640); err != nil {
if err := os.WriteFile(statePath, data, 0600); err != nil {
xlog.Error("Failed to write quantization job state", "job_id", job.ID, "error", err)
}
}
@@ -160,7 +160,7 @@ func (s *QuantizationService) loadJobsFromDisk(_ context.Context) ([]*schema.Qua
continue
}
statePath := filepath.Join(baseDir, entry.Name(), "state.json")
data, err := os.ReadFile(statePath)
data, err := os.ReadFile(filepath.Clean(statePath))
if err != nil {
continue
}
@@ -714,12 +714,12 @@ func (s *QuantizationService) ImportModel(ctx context.Context, userID, jobID str
srcFile := job.OutputFile
dstFile := filepath.Join(outputPath, filepath.Base(srcFile))
srcData, err := os.ReadFile(srcFile)
srcData, err := os.ReadFile(filepath.Clean(srcFile))
if err != nil {
s.setImportFailed(job, fmt.Sprintf("failed to read output file: %v", err))
return
}
if err := os.WriteFile(dstFile, srcData, 0644); err != nil {
if err := os.WriteFile(dstFile, srcData, 0600); err != nil {
s.setImportFailed(job, fmt.Sprintf("failed to write model file: %v", err))
return
}
@@ -752,7 +752,7 @@ func (s *QuantizationService) ImportModel(ctx context.Context, userID, jobID str
s.setImportFailed(job, fmt.Sprintf("failed to marshal config: %v", err))
return
}
if err := os.WriteFile(configPath, yamlData, 0644); err != nil {
if err := os.WriteFile(configPath, yamlData, 0600); err != nil {
s.setImportFailed(job, fmt.Sprintf("failed to write config file: %v", err))
return
}
+1
View File
@@ -48,6 +48,7 @@ const (
)
// HostCredentialHeader carries the host credential of the incarnation.
// #nosec G101 -- this is a header name, not a credential value.
const HostCredentialHeader = "X-Addon-Host-Credential"
// hostOpName is the brief's shape ^[a-z]+\.[a-z.]+$ without an empty word: