mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 09:08:54 -04:00
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:
9 files changed
+17
-13
No files matched your search
@@ -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():
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user