diff --git a/engine/agentfiles.go b/engine/agentfiles.go index 134dc1a..335f58c 100644 --- a/engine/agentfiles.go +++ b/engine/agentfiles.go @@ -72,7 +72,7 @@ func (afb *AgentFileBridge) Export(rules []AgentRule, fileType AgentFileType) er path := filepath.Join(afb.projectDir, string(fileType)) // Read existing content - existing, _ := os.ReadFile(path) + existing, _ := os.ReadFile(path) // #nosec G304 -- path is one of a fixed set of agent instruction filenames (CLAUDE.md, .cursorrules, etc.) joined under the bridge's configured projectDir existingStr := string(existing) // Build new content from rules @@ -119,10 +119,10 @@ func (afb *AgentFileBridge) Export(rules []AgentRule, fileType AgentFileType) er } // Write file - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil { return err } - return os.WriteFile(path, []byte(content), 0o644) + return os.WriteFile(path, []byte(content), 0o644) // #nosec G306 -- agent rule files (CLAUDE.md/AGENTS.md/etc.) must remain readable/editable by other tools and users } // Sync performs bidirectional sync between yaad and agent files. @@ -175,7 +175,7 @@ func (afb *AgentFileBridge) Watch() []string { // importFile reads a single agent file and extracts rules. func (afb *AgentFileBridge) importFile(path string, fileType AgentFileType) ([]AgentRule, error) { - f, err := os.Open(path) + f, err := os.Open(path) // #nosec G304 -- path is one of a fixed set of agent instruction filenames (CLAUDE.md, .cursorrules, etc.) joined under the bridge's configured projectDir if err != nil { return nil, err } diff --git a/engine/bloom.go b/engine/bloom.go index 9dec2f1..f274435 100644 --- a/engine/bloom.go +++ b/engine/bloom.go @@ -142,7 +142,7 @@ func (bf *BloomFilter) positions(term string) []uint64 { positions := make([]uint64, bf.numHash) for i := 0; i < bf.numHash; i++ { - positions[i] = (h1 + uint64(i)*h2) % bf.numBits + positions[i] = (h1 + uint64(i)*h2) % bf.numBits // #nosec G115 -- i is a bounded non-negative loop index; hash-mixing conversion } return positions } diff --git a/engine/engine_core.go b/engine/engine_core.go index 619660b..b405e3a 100644 --- a/engine/engine_core.go +++ b/engine/engine_core.go @@ -365,7 +365,7 @@ func (e *Engine) renderMemoryFile(ctx context.Context) error { fmt.Fprintf(&sb, "- **%s** [%s] (tier %d, conf %.1f)\n", label, n.Type, n.Tier, n.Confidence) } - return os.WriteFile(e.memoryFile, []byte(sb.String()), 0o644) + return os.WriteFile(e.memoryFile, []byte(sb.String()), 0o600) } // Close shuts down the engine and its background workers. diff --git a/engine/git_learn.go b/engine/git_learn.go index d409a90..b93a771 100644 --- a/engine/git_learn.go +++ b/engine/git_learn.go @@ -52,7 +52,7 @@ func (gl *GitLearner) LearnFromHistory(ctx context.Context, limit int, since tim args = append(args, fmt.Sprintf("--since=%s", since.Format("2006-01-02"))) } - out, err := exec.CommandContext(ctx, "git", args...).Output() + out, err := exec.CommandContext(ctx, "git", args...).Output() // #nosec G204 -- fixed "git" binary; args built internally from gl.dir/limit/since, not shell-interpreted if err != nil { return result, fmt.Errorf("git log failed: %w", err) } @@ -83,7 +83,7 @@ func (gl *GitLearner) LearnFromHistory(ctx context.Context, limit int, since tim // LearnFromBlame extracts file-level knowledge from git blame. // Discovers who owns what and when major changes happened. func (gl *GitLearner) LearnFromBlame(ctx context.Context, filePath string) error { - out, err := exec.CommandContext(ctx, "git", "-C", gl.dir, "log", "--oneline", "-1", "--diff-filter=A", "--", filePath).Output() + out, err := exec.CommandContext(ctx, "git", "-C", gl.dir, "log", "--oneline", "-1", "--diff-filter=A", "--", filePath).Output() // #nosec G204 -- fixed "git" binary; gl.dir/filePath are caller-supplied paths, not shell-interpreted, and "--" ends flag parsing if err != nil || len(out) == 0 { return nil } @@ -104,7 +104,7 @@ func (gl *GitLearner) Suggest(ctx context.Context) ([]MemorySuggestion, error) { var suggestions []MemorySuggestion // 1. Find repeated patterns in recent commits - out, err := exec.CommandContext(ctx, "git", "-C", gl.dir, "log", "--oneline", "-50").Output() + out, err := exec.CommandContext(ctx, "git", "-C", gl.dir, "log", "--oneline", "-50").Output() // #nosec G204 -- fixed "git" binary; gl.dir is the configured project directory, not shell-interpreted if err != nil { return nil, err } @@ -196,7 +196,7 @@ func (gl *GitLearner) remember(ctx context.Context, content, nodeType string) er } func (gl *GitLearner) frequentlyChangedFiles(ctx context.Context) []string { - out, err := exec.CommandContext(ctx, "git", "-C", gl.dir, "log", "--name-only", "--format=", "-30").Output() + out, err := exec.CommandContext(ctx, "git", "-C", gl.dir, "log", "--name-only", "--format=", "-30").Output() // #nosec G204 -- fixed "git" binary; gl.dir is the configured project directory, not shell-interpreted if err != nil { return nil } diff --git a/engine/hnsw.go b/engine/hnsw.go index 2f3ecd9..c318740 100644 --- a/engine/hnsw.go +++ b/engine/hnsw.go @@ -262,7 +262,7 @@ func (h *HNSW) Save(path string) error { // saveLocked writes the index without acquiring the lock. Caller must hold h.mu. func (h *HNSW) saveLocked(path string) (err error) { - f, err := os.Create(path) + f, err := os.Create(path) // #nosec G304 -- path is the index file location computed/configured by the caller (e.g. under the yaad data dir), not externally supplied if err != nil { return err } @@ -287,7 +287,7 @@ func (h *HNSW) saveLocked(path string) (err error) { // LoadHNSW restores an HNSW index from a gob-encoded file at path. func LoadHNSW(path string) (*HNSW, error) { - f, err := os.Open(path) + f, err := os.Open(path) // #nosec G304 -- path is the index file location computed/configured by the caller (e.g. under the yaad data dir), not externally supplied if err != nil { return nil, err } @@ -409,6 +409,7 @@ func (h *HNSW) Compact() { func (h *HNSW) randomLevel() int { level := 0 + // #nosec G404 -- non-cryptographic use (HNSW level sampling) for rand.Float64() < h.mL && level < 16 { level++ } diff --git a/engine/ingest.go b/engine/ingest.go index 3ac0d74..6769750 100644 --- a/engine/ingest.go +++ b/engine/ingest.go @@ -128,7 +128,7 @@ func (ing *Ingester) IngestMarkdown(ctx context.Context, content, source string) // IngestFile parses a markdown file from disk. func (ing *Ingester) IngestFile(ctx context.Context, path string) (*IngestResult, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 -- path is a project file path passed in by the caller (e.g. CLI/agent-driven ingestion), not raw user network input if err != nil { return nil, fmt.Errorf("read %s: %w", path, err) } @@ -137,7 +137,7 @@ func (ing *Ingester) IngestFile(ctx context.Context, path string) (*IngestResult // IngestClaudeMD imports from a CLAUDE.md file (conventions, rules, patterns). func (ing *Ingester) IngestClaudeMD(ctx context.Context, path string) (*IngestResult, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 -- path is a project file path passed in by the caller (e.g. CLI/agent-driven ingestion), not raw user network input if err != nil { return nil, err } @@ -157,7 +157,7 @@ func (ing *Ingester) IngestClaudeMD(ctx context.Context, path string) (*IngestRe // IngestCursorRules imports from a .cursorrules file. func (ing *Ingester) IngestCursorRules(ctx context.Context, path string) (*IngestResult, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 -- path is a project file path passed in by the caller (e.g. CLI/agent-driven ingestion), not raw user network input if err != nil { return nil, err } @@ -178,7 +178,7 @@ func (ing *Ingester) IngestCursorRules(ctx context.Context, path string) (*Inges // IngestCodeFile extracts specs from source code (functions, types, package purpose). func (ing *Ingester) IngestCodeFile(ctx context.Context, path string) (*IngestResult, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 -- path is a project file path passed in by the caller (e.g. CLI/agent-driven ingestion), not raw user network input if err != nil { return nil, err } @@ -250,6 +250,7 @@ func (ing *Ingester) DetectStack(ctx context.Context, projectDir string) (*Inges // Detect from package.json dependencies pkgJSON := filepath.Join(projectDir, "package.json") + // #nosec G304 -- pkgJSON is a fixed filename joined under the caller-supplied projectDir for stack detection if data, err := os.ReadFile(pkgJSON); err == nil { content := string(data) npmDetect := map[string]string{ @@ -270,6 +271,7 @@ func (ing *Ingester) DetectStack(ctx context.Context, projectDir string) (*Inges // Detect from go.mod dependencies goMod := filepath.Join(projectDir, "go.mod") + // #nosec G304 -- goMod is a fixed filename joined under the caller-supplied projectDir for stack detection if data, err := os.ReadFile(goMod); err == nil { content := string(data) goDetect := map[string]string{ diff --git a/engine/integrity.go b/engine/integrity.go index f18eb22..0bbaae5 100644 --- a/engine/integrity.go +++ b/engine/integrity.go @@ -30,7 +30,7 @@ func NewMemoryIntegrity(yaadDir string) (*MemoryIntegrity, error) { mi := &MemoryIntegrity{} keyPath := filepath.Join(yaadDir, "integrity.key") - rawKey, err := os.ReadFile(keyPath) + rawKey, err := os.ReadFile(keyPath) // #nosec G304 -- keyPath is joined from the caller-supplied yaadDir (the .yaad project data directory), a trusted internal path if err != nil { // Generate new key rawKey = make([]byte, 32) diff --git a/engine/lsh.go b/engine/lsh.go index d3646e8..f4c40a6 100644 --- a/engine/lsh.go +++ b/engine/lsh.go @@ -108,7 +108,7 @@ func (lsh *MinHashLSH) minHashSignature(terms map[string]bool) []uint64 { func (lsh *MinHashLSH) hashTerm(term string, perm int) uint64 { h := fnv.New64a() // Write permutation seed as 8 bytes - seed := uint64(perm) * 0x9e3779b97f4a7c15 + seed := uint64(perm) * 0x9e3779b97f4a7c15 // #nosec G115 -- perm is a bounded non-negative loop index; hash-mixing conversion var buf [8]byte buf[0] = byte(seed) buf[1] = byte(seed >> 8) @@ -118,8 +118,8 @@ func (lsh *MinHashLSH) hashTerm(term string, perm int) uint64 { buf[5] = byte(seed >> 40) buf[6] = byte(seed >> 48) buf[7] = byte(seed >> 56) - h.Write(buf[:]) - h.Write([]byte(term)) + _, _ = h.Write(buf[:]) // #nosec G104 -- hash.Hash.Write never returns an error + _, _ = h.Write([]byte(term)) // #nosec G104 -- hash.Hash.Write never returns an error return h.Sum64() } @@ -138,7 +138,7 @@ func (lsh *MinHashLSH) bandHash(sig []uint64, band int) uint64 { buf[5] = byte(v >> 40) buf[6] = byte(v >> 48) buf[7] = byte(v >> 56) - h.Write(buf[:]) + _, _ = h.Write(buf[:]) // #nosec G104 -- hash.Hash.Write never returns an error } return h.Sum64() } diff --git a/exportimport/export.go b/exportimport/export.go index e913bfb..bb36e74 100644 --- a/exportimport/export.go +++ b/exportimport/export.go @@ -112,7 +112,7 @@ func ExportObsidian(ctx context.Context, store storage.Storage, project, vaultDi if strings.Contains(cleaned, "..") { return 0, fmt.Errorf("vault_dir must not contain path traversal components") } - if err := os.MkdirAll(cleaned, 0o755); err != nil { + if err := os.MkdirAll(cleaned, 0o750); err != nil { return 0, fmt.Errorf("create vault dir: %w", err) } vaultDir = cleaned @@ -156,7 +156,7 @@ func ExportObsidian(ctx context.Context, store storage.Storage, project, vaultDi } fname := sanitizeFilename(obsidianTitle(n)) + ".md" - if err := os.WriteFile(filepath.Join(vaultDir, fname), []byte(sb.String()), 0o644); err == nil { + if err := os.WriteFile(filepath.Join(vaultDir, fname), []byte(sb.String()), 0o600); err == nil { written++ } } diff --git a/exportimport/langgraph.go b/exportimport/langgraph.go index f0075de..96cb965 100644 --- a/exportimport/langgraph.go +++ b/exportimport/langgraph.go @@ -72,7 +72,7 @@ func ImportLangGraph(ctx context.Context, store storage.Storage, jsonData []byte Type: "parent", CreatedAt: time.Now(), } - store.CreateEdge(ctx, edge) //nolint:errcheck // best-effort + _ = store.CreateEdge(ctx, edge) // best-effort; parent edge creation failure shouldn't abort the import } } diff --git a/git/watcher.go b/git/watcher.go index 381d07c..f59886c 100644 --- a/git/watcher.go +++ b/git/watcher.go @@ -76,7 +76,7 @@ func (w *Watcher) changedFiles(since time.Time) ([]string, error) { sinceStr := since.UTC().Format("2006-01-02T15:04:05") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - out, err := exec.CommandContext(ctx, "git", "-C", w.dir, "log", + out, err := exec.CommandContext(ctx, "git", "-C", w.dir, "log", // #nosec G204 -- fixed "git" binary; w.dir is the configured project directory, sinceStr is an internally formatted timestamp "--since="+sinceStr, "--name-only", "--pretty=format:").Output() if err != nil { return nil, fmt.Errorf("git log: %w", err) diff --git a/hooks/runner.go b/hooks/runner.go index d964fa8..b83e7a7 100644 --- a/hooks/runner.go +++ b/hooks/runner.go @@ -75,7 +75,7 @@ func (r *Runner) SessionStart(ctx context.Context, in *HookInput) error { // Write session ID to a temp file for other hooks to pick up sf := sessionFile(r.project) - if err := os.MkdirAll(filepath.Dir(sf), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(sf), 0o750); err != nil { fmt.Fprintf(os.Stderr, "yaad: warning: could not create session dir: %v\n", err) return fmt.Errorf("create session dir: %w", err) } diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 45c189f..921b96b 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -36,7 +36,7 @@ func WritePID(projectDir string) error { if err := os.MkdirAll(dir, 0o700); err != nil { return err } - return os.WriteFile(PIDFile(projectDir), []byte(strconv.Itoa(os.Getpid())), 0o644) + return os.WriteFile(PIDFile(projectDir), []byte(strconv.Itoa(os.Getpid())), 0o600) } // RemovePID removes the PID file. @@ -150,7 +150,7 @@ func EnsureRunning(projectDir, addr string) error { if err != nil { exe = "yaad" } - cmd := exec.CommandContext(context.Background(), exe, "serve", "--addr", addr) + cmd := exec.CommandContext(context.Background(), exe, "serve", "--addr", addr) // #nosec G204 -- exe is os.Executable() (own binary path) or a fixed fallback; addr is caller-controlled config cmd.Dir = projectDir cmd.Stdout = nil cmd.Stderr = nil diff --git a/internal/tls/tls.go b/internal/tls/tls.go index 43945c3..fe84e83 100644 --- a/internal/tls/tls.go +++ b/internal/tls/tls.go @@ -56,7 +56,7 @@ func TLSConfig(cfg Config, dataDir string) (*tls.Config, error) { // needsRegen reports whether the cert file is missing, unreadable/unparseable, // or within one day of expiry — any of which warrants regenerating it. func needsRegen(certFile string) bool { - pemBytes, err := os.ReadFile(certFile) + pemBytes, err := os.ReadFile(certFile) // #nosec G304 -- certFile is derived from configured dataDir or CertFile config option, not externally supplied if err != nil { return true // missing or unreadable } diff --git a/storage/codeindex.go b/storage/codeindex.go index d174ec9..5f2d966 100644 --- a/storage/codeindex.go +++ b/storage/codeindex.go @@ -414,6 +414,7 @@ func (s *Store) SearchCodeChunksHybrid(ctx context.Context, query string, queryV args = append(args, lang) } args = append(args, 50) + // #nosec G202 -- IN clause is placeholder tokens only; values parameterized q := `SELECT c.id, c.path, c.start_line, c.end_line, c.content, c.symbol, c.language, c.tokens, c.file_hash, c.schema_version, c.vector, rank FROM code_chunks_fts f