Skip to content

fix: resolve 11 CodeQL path-injection alerts in Go CLI#411

Closed
Aureliolo wants to merge 2 commits intomainfrom
fix/codeql-path-injection
Closed

fix: resolve 11 CodeQL path-injection alerts in Go CLI#411
Aureliolo wants to merge 2 commits intomainfrom
fix/codeql-path-injection

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

Resolves all 11 open CodeQL go/path-injection alerts. These flag filesystem operations using paths derived from environment variables or config files without explicit sanitization at the point of use.

  • Add config.SecurePath() helper — validates path is absolute + applies filepath.Clean() (satisfies CodeQL taint tracking)
  • Apply SecurePath at all filesystem call sites: EnsureDir, Load, Save, and all cmd files (start, stop, logs, doctor, status, uninstall, init)
  • Add safeStateDir() cmd helper to avoid repetition

Also includes: post-merge-cleanup skill update (use individual git branch -D instead of piped xargs)

Test plan

  • go vet ./... passes
  • go build ./... passes
  • go test ./... — all tests pass
  • All 11 CodeQL alert locations covered

CodeQL alerts addressed

# File Line
42 cli/internal/config/paths.go 45
43-44 cli/internal/config/state.go 45, 83
35 cli/cmd/logs.go 49
36-37 cli/cmd/init.go 195, 231
38 cli/cmd/start.go 38
39 cli/cmd/stop.go 34
45 cli/cmd/doctor.go 44
46 cli/cmd/status.go 44
47 cli/cmd/uninstall.go 115

Add config.SecurePath() helper that validates paths are absolute and
applies filepath.Clean() — satisfies CodeQL's go/path-injection taint
tracking. Apply at all filesystem operation call sites:

- config: EnsureDir, Load, Save
- cmd: start, stop, logs, doctor, status, uninstall, init

Also update post-merge-cleanup skill to avoid piped xargs commands
that trigger permission prompts.
Copilot AI review requested due to automatic review settings March 14, 2026 18:59
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 14, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved path validation and sanitization across CLI commands, making operations more reliable and error-safe.
    • Commands now operate within a validated safe directory to avoid path-related failures.
  • Documentation

    • Expanded branch cleanup guidance with safer, clearer step-by-step deletion instructions and a new confirmation step.
  • Tests

    • Added tests covering secure path validation and normalization.

Walkthrough

Adds path sanitization via a new config.SecurePath and applies it across CLI commands and state handling, replacing direct uses of state.DataDir with validated absolute paths and updating init/uninstall/diagnostics/doctor flows and related tests and docs.

Changes

Cohort / File(s) Summary
Path Security Core
cli/internal/config/paths.go, cli/internal/config/state.go, cli/internal/config/paths_test.go
Introduce SecurePath(path string) (string, error) to normalize/require absolute paths; integrate into state Load/Save and EnsureDir; add unit tests for SecurePath behavior.
Safe State Helper
cli/cmd/root.go
Add safeStateDir(state config.State) (string, error) wrapper around config.SecurePath for reuse by commands.
Commands — use safeStateDir
cli/cmd/doctor.go, cli/cmd/logs.go, cli/cmd/start.go, cli/cmd/status.go, cli/cmd/stop.go, cli/cmd/update.go
Replace direct state.DataDir usage with sanitized safeDir from safeStateDir, update compose.yml checks, composeRun/exec calls, and error messages to reference the validated path.
Commands — direct SecurePath integration
cli/cmd/init.go, cli/cmd/uninstall.go
Resolve and validate target directories with SecurePath in init and uninstall flows; propagate errors and ensure directory creation/removal uses validated paths.
Diagnostics
cli/internal/diagnostics/collect.go
Validate state.DataDir with SecurePath and use the validated path for disk, container, and log collection; include path errors in diagnostics report.
Docs / Misc
.claude/skills/post-merge-cleanup/SKILL.md, go.mod
Update branch-cleanup instructions (avoid xargs, prefer listing/deleting gone branches; add git status check). go.mod touched by command changes.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary purpose of the changeset: resolving CodeQL path-injection alerts through sanitization.
Description check ✅ Passed The description clearly explains the changes made, their purpose, and test coverage. It directly relates to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/codeql-path-injection
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/codeql-path-injection
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security posture of the CLI by addressing potential path injection vulnerabilities. It introduces a centralized mechanism for path sanitization and ensures that all filesystem operations involving user-controlled or environment-derived paths are properly validated and cleaned, thereby preventing malicious path manipulation.

Highlights

  • Security Fixes: Resolved 11 CodeQL go/path-injection alerts by implementing explicit path sanitization for filesystem operations.
  • New Path Security Helper: Introduced a new config.SecurePath() helper function that validates paths are absolute and applies filepath.Clean() to prevent path injection vulnerabilities.
  • Widespread Application of SecurePath: Applied the SecurePath helper across various filesystem call sites, including config.EnsureDir, config.Load, config.Save, and within multiple CLI command files (start, stop, logs, doctor, status, uninstall, init).
  • Command Helper for State Directory: Added a safeStateDir() helper function in cli/cmd/root.go to centralize the secure retrieval of the state data directory, reducing repetition.
  • Documentation Update: Updated the post-merge-cleanup skill documentation to clarify the safe method for deleting local Git branches, advising against piping to xargs.
Changelog
  • .claude/skills/post-merge-cleanup/SKILL.md
    • Updated instructions for deleting local Git branches, advising against xargs for git branch -D.
  • cli/cmd/doctor.go
    • Updated diagnostic file saving to use safeStateDir for secure path handling.
  • cli/cmd/init.go
    • Integrated config.SecurePath for data directory creation and compose.yml path, and cleaned path in fileExists.
  • cli/cmd/logs.go
    • Modified compose.yml path handling and composeRun calls to use safeStateDir.
  • cli/cmd/root.go
    • Introduced safeStateDir helper for secure state directory retrieval.
  • cli/cmd/start.go
    • Modified compose.yml path handling and composeRun calls to use safeStateDir.
  • cli/cmd/status.go
    • Modified compose.yml path handling to use safeStateDir.
  • cli/cmd/stop.go
    • Modified compose.yml path handling and composeRun calls to use safeStateDir.
  • cli/cmd/uninstall.go
    • Integrated config.SecurePath for data directory removal.
  • cli/internal/config/paths.go
    • Added SecurePath function for path validation and cleaning.
    • Updated EnsureDir to utilize the new SecurePath function.
  • cli/internal/config/state.go
    • Modified Load and Save functions to use SecurePath for state file operations.
Activity
  • The author has confirmed that go vet ./..., go build ./..., and go test ./... all pass.
  • The author has verified that all 11 CodeQL alert locations identified in the PR description are covered by these changes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses 11 CodeQL go/path-injection alerts by introducing a config.SecurePath helper for path sanitization and applying it across the CLI commands. The changes are well-targeted and improve the security of file system operations. I've identified a couple of places where the new helpers are used in a way that leads to redundant path validation. My review includes suggestions to streamline these calls for better performance and code clarity. The documentation update for the post-merge cleanup script is also a good improvement.

cli/cmd/init.go Outdated
Comment on lines 188 to 190
if err := config.EnsureDir(safeDir); err != nil {
return fmt.Errorf("creating data directory: %w", err)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a small redundancy here. You get a safeDir by calling config.SecurePath and then pass it to config.EnsureDir, which itself calls config.SecurePath on its input. This means the path is being validated and cleaned twice.

To avoid this, you could call os.MkdirAll directly with the safeDir. This would keep the logic of 'secure once, use many times' while removing the redundant check.

Suggested change
if err := config.EnsureDir(safeDir); err != nil {
return fmt.Errorf("creating data directory: %w", err)
}
if err := os.MkdirAll(safeDir, 0o700); err != nil {
return fmt.Errorf("creating data directory: %w", err)
}

Comment on lines 84 to 86
if err := EnsureDir(safeDir); err != nil {
return err
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a redundant call to SecurePath here. EnsureDir already validates its input path, so calling it with an already-secured path means the validation happens twice.

You can call os.MkdirAll directly with safeDir to avoid this.

Suggested change
if err := EnsureDir(safeDir); err != nil {
return err
}
if err := os.MkdirAll(safeDir, 0o700); err != nil {
return err
}

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR resolves all 11 CodeQL go/path-injection alerts by introducing a central SecurePath helper that validates absolute paths and applies filepath.Clean, then applying it consistently across every filesystem call site in the CLI. The approach is sound and the coverage is thorough — including update.go which wasn't even in the alert list.

Key changes:

  • config.SecurePath — new validation helper; EnsureDir, Load, and Save all delegate to it
  • safeStateDir — thin cmd-layer wrapper used by start, stop, logs, doctor, status, update
  • DataDir() fallback updated from "." (relative, would fail SecurePath) to CWD or "/"
  • diagnostics.Collect and writeInitFiles apply SecurePath at call sites
  • Tests added for all SecurePath edge cases (relative, empty, .. traversal, cleaning)

Issues to consider:

  • In config/state.go Load, the "file found" branch validates s.DataDir with a manual filepath.Clean + filepath.IsAbs check rather than SecurePath, leaving duplicated validation logic that diverges from the "file not found" branch.
  • In diagnostics/collect.go, a path-validation failure triggers an early return that also skips non-path-dependent diagnostics (health check, redacted config) — the opposite of what's useful when debugging a misconfigured install.
  • uninstall.go uses config.SecurePath(state.DataDir) directly in two helper functions rather than the safeStateDir wrapper introduced by this PR, creating a minor inconsistency.
  • writeInitFiles in init.go calls SecurePath then immediately calls config.Save which calls SecurePath again — redundant but harmless.

Confidence Score: 4/5

  • Safe to merge — the CodeQL alerts are genuinely resolved and no regressions are introduced, though two minor logic inconsistencies are worth a follow-up.
  • The core SecurePath implementation is correct, well-tested, and applied consistently. The one logic concern (early return in diagnostics/collect.go silently dropping non-path diagnostics) is a degradation in UX for an edge case rather than a correctness or security bug. The state.go inconsistency is a maintenance issue rather than a defect. No functional regressions found.
  • cli/internal/config/state.go (duplicate validation logic in Load) and cli/internal/diagnostics/collect.go (early return drops health/config diagnostics on path error).

Important Files Changed

Filename Overview
cli/internal/config/paths.go Introduces SecurePath helper that validates absolute paths and applies filepath.Clean — the core of this PR. Also updates DataDir fallback from "." (relative) to CWD or "/" so the new check doesn't break on UserHomeDir failure. EnsureDir now delegates to SecurePath. Logic is correct and well-documented.
cli/internal/config/paths_test.go Good test coverage added for SecurePath: absolute, relative, empty, cleaning with .., and relative traversal cases. Cross-platform via absTestPath helper. No gaps for the new functionality.
cli/internal/config/state.go CodeQL alerts for Load and Save are fixed. However, when the config file exists, s.DataDir from JSON is still validated with a manual filepath.Clean + filepath.IsAbs check rather than SecurePath, creating an inconsistency with the "file not found" branch and duplicating validation logic.
cli/internal/diagnostics/collect.go Adds SecurePath validation at the top of Collect, but an early return on path error skips non-path-dependent diagnostics (health endpoint, config redaction) that could be valuable precisely when the data directory is misconfigured.
cli/cmd/uninstall.go Both stopAndRemoveVolumes and confirmAndRemoveData call config.SecurePath(state.DataDir) directly rather than using the safeStateDir helper introduced by this PR, creating a minor inconsistency with other cmd files.
cli/cmd/init.go CodeQL alerts resolved. writeInitFiles calls SecurePath then config.Save which calls SecurePath again — redundant but harmless. fileExists now rejects relative paths via SecurePath; all call sites pass absolute paths so behavior is preserved.

Comments Outside Diff (4)

  1. cli/internal/config/state.go, line 63-70 (link)

    Inconsistent validation path for loaded DataDir

    When the config file exists, s.DataDir (read from JSON) is validated using a manual filepath.Clean + filepath.IsAbs check rather than SecurePath. The "file not found" branch was updated to use SecurePath, but this branch was not. While the two approaches are functionally equivalent, it creates a subtle inconsistency: callers that subsequently call safeStateDir(state) are providing a "second-pass" validation that is actually needed here since Load never ran this path's value through SecurePath.

    Replacing the inline check with SecurePath would remove the duplicated logic and make the contract consistent regardless of whether the file existed:

    // Canonicalize and validate DataDir.
    if s.DataDir != "" {
        safeLoaded, err := SecurePath(s.DataDir)
        if err != nil {
            return State{}, fmt.Errorf("data_dir: %w", err)
        }
        s.DataDir = safeLoaded
    }

    This would also allow removing the filepath import from state.go since it would no longer be needed there.

  2. cli/internal/diagnostics/collect.go, line 49-53 (link)

    Early return skips non-path-dependent diagnostics

    If SecurePath fails (e.g. a malformed DataDir in the config file), the function returns immediately — before collecting the health endpoint check (line 75), the redacted config (line 95), and the errors that don't depend on safeDir at all. Before this PR, those sections would always run.

    Because diagnostics are specifically useful when something has gone wrong, an invalid DataDir is exactly the situation where the health status and redacted config would be most valuable to a user filing a bug report.

    Consider only gating the Docker and disk sections on safeDir:

    safeDir, pathErr := config.SecurePath(state.DataDir)
    if pathErr != nil {
        r.Errors = append(r.Errors, fmt.Sprintf("path: %v", pathErr))
        // safeDir is empty string; skip Docker/disk sections below
    }

    Then guard the Docker block and diskInfo call with if pathErr == nil { ... } rather than returning early. The health-endpoint, config-redaction, and error-collection sections can run unconditionally.

  3. cli/cmd/uninstall.go, line 64-66 (link)

    Inconsistent use of safeStateDir vs. direct config.SecurePath

    This PR introduces safeStateDir(state) in root.go as a shared helper to avoid repeating config.SecurePath(state.DataDir) across cmd files. Both stopAndRemoveVolumes (line 64) and confirmAndRemoveData (line 113) call config.SecurePath(state.DataDir) directly rather than using the new helper, which is inconsistent with every other cmd file in the PR.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  4. cli/cmd/init.go, line 183-191 (link)

    Redundant double SecurePath call via config.Save

    writeInitFiles calls config.SecurePath(state.DataDir), assigns state.DataDir = safeDir, and then calls config.Save(state) — which itself calls SecurePath(s.DataDir) again (see state.go lines 76-78). Since state.DataDir has already been validated and cleaned, the second call is a no-op but adds noise.

    This is minor, but it means writeInitFiles could simplify to calling config.EnsureDir(safeDir) (which also wraps SecurePath) and then config.Save without the pre-validation step — or, alternatively, document that the pre-validation is intentional for clarity.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: cli/internal/config/state.go
Line: 63-70

Comment:
**Inconsistent validation path for loaded `DataDir`**

When the config file exists, `s.DataDir` (read from JSON) is validated using a manual `filepath.Clean` + `filepath.IsAbs` check rather than `SecurePath`. The "file not found" branch was updated to use `SecurePath`, but this branch was not. While the two approaches are functionally equivalent, it creates a subtle inconsistency: callers that subsequently call `safeStateDir(state)` are providing a "second-pass" validation that is actually needed here since `Load` never ran this path's value through `SecurePath`.

Replacing the inline check with `SecurePath` would remove the duplicated logic and make the contract consistent regardless of whether the file existed:

```go
// Canonicalize and validate DataDir.
if s.DataDir != "" {
    safeLoaded, err := SecurePath(s.DataDir)
    if err != nil {
        return State{}, fmt.Errorf("data_dir: %w", err)
    }
    s.DataDir = safeLoaded
}
```

This would also allow removing the `filepath` import from `state.go` since it would no longer be needed there.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: cli/internal/diagnostics/collect.go
Line: 49-53

Comment:
**Early return skips non-path-dependent diagnostics**

If `SecurePath` fails (e.g. a malformed `DataDir` in the config file), the function returns immediately — before collecting the health endpoint check (line 75), the redacted config (line 95), and the errors that don't depend on `safeDir` at all. Before this PR, those sections would always run.

Because diagnostics are specifically useful when something has gone wrong, an invalid `DataDir` is exactly the situation where the health status and redacted config would be most valuable to a user filing a bug report.

Consider only gating the Docker and disk sections on `safeDir`:

```go
safeDir, pathErr := config.SecurePath(state.DataDir)
if pathErr != nil {
    r.Errors = append(r.Errors, fmt.Sprintf("path: %v", pathErr))
    // safeDir is empty string; skip Docker/disk sections below
}
```

Then guard the Docker block and `diskInfo` call with `if pathErr == nil { ... }` rather than returning early. The health-endpoint, config-redaction, and error-collection sections can run unconditionally.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: cli/cmd/uninstall.go
Line: 64-66

Comment:
**Inconsistent use of `safeStateDir` vs. direct `config.SecurePath`**

This PR introduces `safeStateDir(state)` in `root.go` as a shared helper to avoid repeating `config.SecurePath(state.DataDir)` across cmd files. Both `stopAndRemoveVolumes` (line 64) and `confirmAndRemoveData` (line 113) call `config.SecurePath(state.DataDir)` directly rather than using the new helper, which is inconsistent with every other cmd file in the PR.

```suggestion
	safeDir, err := safeStateDir(state)
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: cli/cmd/init.go
Line: 183-191

Comment:
**Redundant double `SecurePath` call via `config.Save`**

`writeInitFiles` calls `config.SecurePath(state.DataDir)`, assigns `state.DataDir = safeDir`, and then calls `config.Save(state)` — which itself calls `SecurePath(s.DataDir)` again (see `state.go` lines 76-78). Since `state.DataDir` has already been validated and cleaned, the second call is a no-op but adds noise.

This is minor, but it means `writeInitFiles` could simplify to calling `config.EnsureDir(safeDir)` (which also wraps `SecurePath`) and then `config.Save` without the pre-validation step — or, alternatively, document that the pre-validation is intentional for clarity.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: eee573b

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cli/cmd/status.go (1)

74-75: ⚠️ Potential issue | 🟠 Major

Inconsistent path handling: printContainerStates and printResourceUsage still use unsanitized state.DataDir.

While runStatus correctly validates the path via safeStateDir, the helper functions printContainerStates (line 75) and printResourceUsage (line 86) pass state.DataDir directly to docker.ComposeExecOutput. This inconsistency undermines the path sanitization effort for these code paths.

Consider passing safeDir to these functions instead of state.

🔧 Proposed fix
-	printContainerStates(ctx, out, info, state)
-	printResourceUsage(ctx, out, info, state)
+	printContainerStates(ctx, out, info, safeDir)
+	printResourceUsage(ctx, out, info, safeDir)
 	printHealthStatus(ctx, out, state)

Then update the function signatures:

-func printContainerStates(ctx context.Context, out io.Writer, info docker.Info, state config.State) {
-	psOut, err := docker.ComposeExecOutput(ctx, info, state.DataDir, "ps", "--format", "json")
+func printContainerStates(ctx context.Context, out io.Writer, info docker.Info, dataDir string) {
+	psOut, err := docker.ComposeExecOutput(ctx, info, dataDir, "ps", "--format", "json")
-func printResourceUsage(ctx context.Context, out io.Writer, info docker.Info, state config.State) {
+func printResourceUsage(ctx context.Context, out io.Writer, info docker.Info, dataDir string) {
 	// Get container IDs for this compose project only.
-	psOut, err := docker.ComposeExecOutput(ctx, info, state.DataDir, "ps", "-q")
+	psOut, err := docker.ComposeExecOutput(ctx, info, dataDir, "ps", "-q")

Also applies to: 84-86

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/status.go` around lines 74 - 75, The helpers printContainerStates and
printResourceUsage currently call docker.ComposeExecOutput with unsanitized
state.DataDir; update runStatus to pass the validated safeDir (result of
safeStateDir) into these helpers and change their signatures
(printContainerStates, printResourceUsage) to accept a dataDir string (or
safeDir) instead of the whole config.State, then use that dataDir when calling
docker.ComposeExecOutput; ensure references to docker.ComposeExecOutput,
printContainerStates, printResourceUsage, runStatus, and safeStateDir are
updated accordingly.
cli/internal/config/state.go (1)

51-59: 🛠️ Refactor suggestion | 🟠 Major

Duplicated path validation logic — reuse SecurePath instead.

Lines 54-58 manually replicate the validation that SecurePath performs. Since dataDir was already validated via SecurePath at line 44 (returning safeDir), you should use safeDir here instead of re-validating dataDir.

♻️ Proposed fix
 	if err != nil {
 		if errors.Is(err, os.ErrNotExist) {
 			defaults := DefaultState()
-			// Normalize the dataDir the same way we validate loaded paths.
-			clean := filepath.Clean(dataDir)
-			if !filepath.IsAbs(clean) {
-				return State{}, fmt.Errorf("data_dir must be an absolute path, got %q", dataDir)
-			}
-			defaults.DataDir = clean
+			defaults.DataDir = safeDir
 			return defaults, nil
 		}
 		return State{}, err
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/internal/config/state.go` around lines 51 - 59, Replace the duplicated
manual validation with the already validated safeDir from SecurePath: when
creating defaults (DefaultState()) use safeDir instead of re-cleaning/validating
dataDir and assign defaults.DataDir = safeDir; remove the filepath.Clean/IsAbs
check block so the code relies on the prior SecurePath validation (reference
SecurePath, safeDir, DefaultState, and defaults.DataDir).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/skills/post-merge-cleanup/SKILL.md:
- Around line 17-26: Add blank lines before and after each fenced code block in
the SKILL.md snippet so markdownlint MD031 is satisfied: ensure there is an
empty line before the ```bash block that contains "git branch -vv | grep '\[.*:
gone\]'" and an empty line after that closing fence, and likewise add empty
lines around the second ```bash block containing "git branch -D <branch-name>"
so both fenced blocks have a blank line separating them from the surrounding
prose.

In `@cli/cmd/init.go`:
- Around line 235-236: The fileExists helper currently passes unvalidated input
(after filepath.Clean) to os.Stat causing a path-injection finding; update
fileExists to run the same SecurePath validation used elsewhere before calling
os.Stat (e.g., call SecurePath on the cleaned path and return/handle any error
from SecurePath), then use the validated/secured path in the os.Stat call so
only approved paths reach os.Stat.

In `@cli/internal/config/paths.go`:
- Around line 44-53: SecurePath currently enforces absolute, cleaned paths but
does not restrict which absolute locations are allowed, which can trigger CodeQL
warnings; update the SecurePath function by adding a concise comment above it
that documents the security assumption: that paths originate from
user-controlled CLI flags/config (e.g., --data-dir) and are therefore trusted by
design, and note that intentional lack of filesystem containment is acceptable
for this CLI tool to avoid false positives from static analysis (mention CodeQL
go/path-injection). Keep the comment short and specific to SecurePath so future
maintainers understand the trust boundary and why no further containment checks
are performed.

---

Outside diff comments:
In `@cli/cmd/status.go`:
- Around line 74-75: The helpers printContainerStates and printResourceUsage
currently call docker.ComposeExecOutput with unsanitized state.DataDir; update
runStatus to pass the validated safeDir (result of safeStateDir) into these
helpers and change their signatures (printContainerStates, printResourceUsage)
to accept a dataDir string (or safeDir) instead of the whole config.State, then
use that dataDir when calling docker.ComposeExecOutput; ensure references to
docker.ComposeExecOutput, printContainerStates, printResourceUsage, runStatus,
and safeStateDir are updated accordingly.

In `@cli/internal/config/state.go`:
- Around line 51-59: Replace the duplicated manual validation with the already
validated safeDir from SecurePath: when creating defaults (DefaultState()) use
safeDir instead of re-cleaning/validating dataDir and assign defaults.DataDir =
safeDir; remove the filepath.Clean/IsAbs check block so the code relies on the
prior SecurePath validation (reference SecurePath, safeDir, DefaultState, and
defaults.DataDir).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bcee3b6b-7374-472a-a9a7-f983e2646fe6

📥 Commits

Reviewing files that changed from the base of the PR and between 515d816 and 4937a3c.

📒 Files selected for processing (11)
  • .claude/skills/post-merge-cleanup/SKILL.md
  • cli/cmd/doctor.go
  • cli/cmd/init.go
  • cli/cmd/logs.go
  • cli/cmd/root.go
  • cli/cmd/start.go
  • cli/cmd/status.go
  • cli/cmd/stop.go
  • cli/cmd/uninstall.go
  • cli/internal/config/paths.go
  • cli/internal/config/state.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Agent
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: CLI Go code: Go 1.26+, dependencies in cli/go.mod. Commands include: init, start, stop, status, logs, doctor, update, uninstall, version. Build with GoReleaser for cross-compile (linux/darwin/windows × amd64/arm64).
Go CLI: Go 1.26+, dependencies in cli/go.mod. Use Cobra for commands, charmbracelet/huh for CLI UI. Build with GoReleaser.

Files:

  • cli/cmd/uninstall.go
  • cli/cmd/root.go
  • cli/internal/config/paths.go
  • cli/cmd/stop.go
  • cli/cmd/status.go
  • cli/cmd/start.go
  • cli/cmd/doctor.go
  • cli/cmd/init.go
  • cli/internal/config/state.go
  • cli/cmd/logs.go
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T18:26:05.851Z
Learning: Go CLI build commands: `cd cli && go build -o synthorg ./main.go`, `cd cli && go test ./...`, `cd cli && go vet ./...`, `cd cli && golangci-lint run`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T18:26:05.851Z
Learning: Applies to .github/workflows/cli.yml : CLI workflow (`.github/workflows/cli.yml`): Go lint (golangci-lint + go vet) + test (-race -coverprofile) + build (cross-compile matrix: linux/darwin/windows × amd64/arm64) + vulnerability check (govulncheck) on `cli/**` changes. GoReleaser release on `v*` tags (attaches assets to existing Release Please release). Post-release pins checksums in install scripts and appends to GitHub Release notes.
📚 Learning: 2026-03-14T18:26:05.851Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T18:26:05.851Z
Learning: Applies to cli/**/*.go : CLI Go code: Go 1.26+, dependencies in `cli/go.mod`. Commands include: init, start, stop, status, logs, doctor, update, uninstall, version. Build with GoReleaser for cross-compile (linux/darwin/windows × amd64/arm64).

Applied to files:

  • cli/cmd/start.go
🧬 Code graph analysis (4)
cli/cmd/uninstall.go (1)
cli/internal/config/paths.go (2)
  • SecurePath (47-53)
  • DataDir (18-24)
cli/cmd/root.go (2)
cli/internal/config/state.go (1)
  • State (14-23)
cli/internal/config/paths.go (2)
  • SecurePath (47-53)
  • DataDir (18-24)
cli/cmd/init.go (1)
cli/internal/config/paths.go (3)
  • SecurePath (47-53)
  • DataDir (18-24)
  • EnsureDir (57-63)
cli/internal/config/state.go (1)
cli/internal/config/paths.go (3)
  • SecurePath (47-53)
  • DataDir (18-24)
  • EnsureDir (57-63)
🪛 GitHub Check: CodeQL
cli/internal/config/paths.go

[failure] 62-62: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/stop.go

[failure] 34-34: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/status.go

[failure] 44-44: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/start.go

[failure] 38-38: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/doctor.go

[failure] 44-44: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/init.go

[failure] 236-236: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/internal/config/state.go

[failure] 49-49: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.


[failure] 91-91: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/logs.go

[failure] 49-49: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

🪛 markdownlint-cli2 (0.21.0)
.claude/skills/post-merge-cleanup/SKILL.md

[warning] 19-19: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 21-21: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 23-23: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 25-25: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🔇 Additional comments (12)
cli/internal/config/paths.go (1)

55-62: CodeQL alert on line 62 is expected but may be a false positive in this context.

The static analysis tool flags os.MkdirAll(safe, 0o700) because the path ultimately derives from user input. The mitigation applied (ensuring absolute paths) is appropriate for a CLI tool's trust model. The //nolint:gosec comment used elsewhere could be applied here if needed to suppress warnings, but the code is functionally correct.

cli/internal/config/state.go (1)

44-49: CodeQL alerts on lines 49 and 91 are false positives given SecurePath validation.

The //nolint:gosec comments appropriately document that the paths are validated by SecurePath. CodeQL's warnings stem from taint tracking that doesn't recognize the validation as sufficient sanitization. The implementation is correct for this CLI's trust model.

Also applies to: 80-91

cli/cmd/root.go (1)

44-48: LGTM!

Clean helper function that centralizes the SecurePath call for command handlers. Good documentation referencing the CodeQL alert it addresses.

cli/cmd/status.go (1)

43-47: LGTM for the path sanitization changes in runStatus.

The use of safeStateDir correctly validates the path before filesystem operations.

cli/cmd/doctor.go (1)

42-48: LGTM!

The path sanitization is correctly applied. The safeDir is used consistently for the diagnostic file save path. The CodeQL alert is expected but represents a false positive given the SecurePath validation.

cli/cmd/stop.go (1)

33-48: LGTM!

The path sanitization is correctly and consistently applied throughout the runStop function. All filesystem and compose operations use the validated safeDir.

cli/cmd/logs.go (2)

48-55: LGTM!

The path sanitization is correctly applied and used consistently for compose path operations.


62-75: Good input validation for --tail and service names.

The validation of --tail values and service name pattern matching (serviceNamePattern) provides good defense against command injection via compose arguments.

cli/cmd/uninstall.go (1)

109-112: Good hardening before destructive delete.

Using config.SecurePath before safety checks and os.RemoveAll correctly moves deletion to a validated/normalized path.

cli/cmd/start.go (2)

37-43: Safe directory resolution is correctly centralized.

The safeStateDir(state) gate before filepath.Join/os.Stat is the right mitigation pattern for these path operations.


59-65: Compose operations now consistently use the sanitized directory.

Switching both pull and up -d calls to safeDir closes the previous unsafe-path gap in execution context.

cli/cmd/init.go (1)

184-199: writeInitFiles now applies secure-path handling correctly.

Resolving safeDir via config.SecurePath and writing compose.yml under that directory is a solid fix.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to eliminate CodeQL go/path-injection alerts in the Go CLI by introducing a central config.SecurePath() validator and applying it at filesystem path usage sites across config/state persistence and command implementations.

Changes:

  • Add config.SecurePath() (absolute-path validation + filepath.Clean) and integrate it into EnsureDir.
  • Apply sanitized data directory usage in config load/save and multiple CLI commands via safeStateDir().
  • Update post-merge cleanup skill documentation to avoid piped xargs branch deletion.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
cli/internal/config/paths.go Introduces SecurePath() and routes EnsureDir() through it to enforce cleaned absolute paths.
cli/internal/config/state.go Validates/cleans state paths on load/save before filesystem reads/writes.
cli/cmd/root.go Adds safeStateDir() helper to centralize per-command path validation.
cli/cmd/start.go Uses validated state dir for compose file checks and compose execution.
cli/cmd/stop.go Uses validated state dir for compose file checks and compose execution.
cli/cmd/logs.go Uses validated state dir for compose file checks and compose execution.
cli/cmd/status.go Uses validated state dir for compose file existence check.
cli/cmd/doctor.go Uses validated state dir for saving the diagnostics file.
cli/cmd/init.go Uses validated state dir when creating directories and writing init artifacts.
cli/cmd/uninstall.go Validates state dir before destructive RemoveAll.
.claude/skills/post-merge-cleanup/SKILL.md Updates workflow guidance for deleting gone local branches without piped xargs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 79 to +91
func Save(s State) error {
if err := EnsureDir(s.DataDir); err != nil {
safeDir, err := SecurePath(s.DataDir)
if err != nil {
return err
}
if err := EnsureDir(safeDir); err != nil {
return err
}
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
return os.WriteFile(StatePath(s.DataDir), data, 0o600)
return os.WriteFile(StatePath(safeDir), data, 0o600) //nolint:gosec // path validated by SecurePath
if err := config.EnsureDir(safeDir); err != nil {
return fmt.Errorf("creating data directory: %w", err)
}

safeDir, err := safeStateDir(state)
if err != nil {
return err
}
Comment on lines +44 to +52
// SecurePath validates that a path is absolute and returns a cleaned version.
// This satisfies static analysis (CodeQL go/path-injection) by ensuring
// environment-variable-derived paths are sanitized before filesystem use.
func SecurePath(path string) (string, error) {
clean := filepath.Clean(path)
if !filepath.IsAbs(clean) {
return "", fmt.Errorf("path must be absolute, got %q", path)
}
return clean, nil
Comment on lines +44 to +62
// SecurePath validates that a path is absolute and returns a cleaned version.
// This satisfies static analysis (CodeQL go/path-injection) by ensuring
// environment-variable-derived paths are sanitized before filesystem use.
func SecurePath(path string) (string, error) {
clean := filepath.Clean(path)
if !filepath.IsAbs(clean) {
return "", fmt.Errorf("path must be absolute, got %q", path)
}
return clean, nil
}

// EnsureDir creates the directory (and parents) if it does not exist.
// The path must be absolute.
func EnsureDir(path string) error {
return os.MkdirAll(path, 0o700)
safe, err := SecurePath(path)
if err != nil {
return err
}
return os.MkdirAll(safe, 0o700)
Fixes from go-reviewer, security-reviewer, CodeRabbit, Copilot, Gemini:

- Fix 4 missed files: update.go, uninstall.go (stopAndRemoveVolumes),
  status.go (printContainerStates/printResourceUsage), diagnostics/collect.go
- Remove dead redundant validation in state.go Load (use safeDir directly)
- Use SecurePath in fileExists instead of bare filepath.Clean
- Remove double-SecurePath in Save/writeInitFiles (use os.MkdirAll directly)
- Normalize state.DataDir before marshaling in Save and writeInitFiles
- Add trust boundary doc comment to SecurePath
- Fix DataDir() fallback from "." to os.Getwd() (absolute path)
- Add 5 unit tests for SecurePath (absolute, relative, empty, clean, traversal)
- Fix SKILL.md markdown lint (blank lines around fenced code blocks)
return err
}
state.DataDir = safeDir // normalize before persisting
if err := os.MkdirAll(safeDir, 0o700); err != nil {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.

Copilot Autofix

AI 21 days ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cli/cmd/uninstall.go (1)

113-127: 🧹 Nitpick | 🔵 Trivial

Defense-in-depth validation before directory removal.

The combination of SecurePath validation (line 113) plus the safety checks (line 119) provides robust protection against removing critical directories. The CodeQL alert on line 115 is a false positive since the path is validated.

Note: This function calls config.SecurePath directly rather than using safeStateDir(state) like other commands. While functionally equivalent, using safeStateDir would be more consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/uninstall.go` around lines 113 - 127, The code uses config.SecurePath
to validate the state.DataDir before removal but deviates from other commands by
not using safeStateDir(state); update the uninstall command to obtain the
directory via safeStateDir(state) (or call safeStateDir and use its returned
dir/error) instead of calling config.SecurePath directly, keep the existing
safety checks on dir (empty, "/", home, Windows root) and the os.RemoveAll(dir)
removal logic, and ensure error handling and the final fmt.Fprintf call remain
intact so behavior and defense-in-depth validation are preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@cli/cmd/uninstall.go`:
- Around line 113-127: The code uses config.SecurePath to validate the
state.DataDir before removal but deviates from other commands by not using
safeStateDir(state); update the uninstall command to obtain the directory via
safeStateDir(state) (or call safeStateDir and use its returned dir/error)
instead of calling config.SecurePath directly, keep the existing safety checks
on dir (empty, "/", home, Windows root) and the os.RemoveAll(dir) removal logic,
and ensure error handling and the final fmt.Fprintf call remain intact so
behavior and defense-in-depth validation are preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e40d2093-64ab-40f4-9ec4-6fe4b2d4e29a

📥 Commits

Reviewing files that changed from the base of the PR and between 4937a3c and eee573b.

📒 Files selected for processing (9)
  • .claude/skills/post-merge-cleanup/SKILL.md
  • cli/cmd/init.go
  • cli/cmd/status.go
  • cli/cmd/uninstall.go
  • cli/cmd/update.go
  • cli/internal/config/paths.go
  • cli/internal/config/paths_test.go
  • cli/internal/config/state.go
  • cli/internal/diagnostics/collect.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Greptile Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: CLI Go code: Go 1.26+, dependencies in cli/go.mod. Commands include: init, start, stop, status, logs, doctor, update, uninstall, version. Build with GoReleaser for cross-compile (linux/darwin/windows × amd64/arm64).
Go CLI: Go 1.26+, dependencies in cli/go.mod. Use Cobra for commands, charmbracelet/huh for CLI UI. Build with GoReleaser.

Files:

  • cli/cmd/init.go
  • cli/cmd/uninstall.go
  • cli/cmd/status.go
  • cli/internal/config/paths.go
  • cli/internal/config/state.go
  • cli/internal/diagnostics/collect.go
  • cli/internal/config/paths_test.go
  • cli/cmd/update.go
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T18:26:05.851Z
Learning: Go CLI build commands: `cd cli && go build -o synthorg ./main.go`, `cd cli && go test ./...`, `cd cli && go vet ./...`, `cd cli && golangci-lint run`
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T18:26:05.851Z
Learning: Applies to .github/workflows/cli.yml : CLI workflow (`.github/workflows/cli.yml`): Go lint (golangci-lint + go vet) + test (-race -coverprofile) + build (cross-compile matrix: linux/darwin/windows × amd64/arm64) + vulnerability check (govulncheck) on `cli/**` changes. GoReleaser release on `v*` tags (attaches assets to existing Release Please release). Post-release pins checksums in install scripts and appends to GitHub Release notes.
📚 Learning: 2026-03-14T18:26:05.851Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T18:26:05.851Z
Learning: Applies to .pre-commit-config.yaml : Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting)

Applied to files:

  • .claude/skills/post-merge-cleanup/SKILL.md
📚 Learning: 2026-03-14T18:26:05.851Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T18:26:05.851Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all. No deferring, no 'out of scope' skipping.

Applied to files:

  • .claude/skills/post-merge-cleanup/SKILL.md
🧬 Code graph analysis (5)
cli/cmd/init.go (1)
cli/internal/config/paths.go (2)
  • SecurePath (56-62)
  • DataDir (18-29)
cli/cmd/status.go (1)
cli/internal/docker/client.go (2)
  • Info (22-29)
  • ComposeExecOutput (100-107)
cli/internal/config/state.go (1)
cli/internal/config/paths.go (2)
  • SecurePath (56-62)
  • DataDir (18-29)
cli/internal/diagnostics/collect.go (2)
cli/internal/config/paths.go (2)
  • SecurePath (56-62)
  • DataDir (18-29)
cli/internal/docker/client.go (1)
  • ComposeExecOutput (100-107)
cli/internal/config/paths_test.go (1)
cli/internal/config/paths.go (1)
  • SecurePath (56-62)
🪛 GitHub Check: CodeQL
cli/cmd/init.go

[failure] 189-189: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.


[failure] 240-240: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/uninstall.go

[failure] 115-115: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/cmd/status.go

[failure] 44-44: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

cli/internal/config/state.go

[failure] 49-49: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.


[failure] 81-81: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.


[failure] 88-88: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

🔇 Additional comments (16)
.claude/skills/post-merge-cleanup/SKILL.md (1)

17-35: LGTM! Markdown formatting fixed and content improved.

The blank lines around fenced code blocks have been correctly added, resolving the MD031 markdownlint warnings from the previous review. The two-step approach (check first with grep, then delete individually) is a good defensive practice, and the explicit warning about avoiding piped xargs aligns with the PR's stated objective. The addition of Step 5 for final workspace validation is a sensible best practice.

cli/internal/config/paths.go (3)

49-62: Well-documented trust boundary for SecurePath.

The security note clearly documents the CLI's trust model — users control their own installation directories. This addresses the CodeQL alerts appropriately since enforcing filesystem containment would be inappropriate for a user-facing CLI tool.


20-27: Reasonable fallback chain for DataDir.

The fallback from os.UserHomeDir()os.Getwd()"/" ensures DataDir always returns an absolute path, which aligns with SecurePath's requirements. The comment clearly explains the rationale.


64-71: EnsureDir correctly validates paths before filesystem operations.

The integration of SecurePath ensures that only absolute, cleaned paths reach os.MkdirAll. The CodeQL alert on line 71 is a false positive — the path is validated by SecurePath immediately prior.

cli/internal/config/paths_test.go (1)

69-118: Good test coverage for SecurePath edge cases.

The tests appropriately cover:

  • Valid absolute paths (normalization)
  • Relative path rejection
  • Empty path rejection
  • Path cleaning with .. components
  • Relative traversal rejection

The absTestPath helper ensures cross-platform compatibility.

cli/internal/config/state.go (2)

43-70: Load correctly applies SecurePath validation at entry.

The validation flow is sound:

  1. Input dataDir is validated via SecurePath
  2. If file doesn't exist, defaults use the validated safeDir
  3. If file exists, the persisted DataDir is re-validated (lines 64-69)

The re-validation at lines 64-69 is defense-in-depth against malicious config files containing relative paths — good practice.


73-88: Save correctly normalizes and validates before persisting.

The flow ensures:

  1. DataDir is validated and cleaned via SecurePath
  2. The canonical path is stored back into s.DataDir
  3. Directory creation and file write use the validated path

The //nolint:gosec comments are appropriate since the paths are validated by SecurePath. The CodeQL alerts on lines 81 and 88 are false positives.

cli/cmd/update.go (2)

96-99: Proper integration of safeStateDir for path validation.

The safeDir is obtained early and error handling is correct. This validated path is then consistently used for all compose operations.


108-140: Consistent use of validated safeDir throughout compose operations.

All composeRun and ComposeExecOutput calls properly use the validated safeDir instead of the raw state.DataDir.

cli/cmd/uninstall.go (1)

64-67: stopAndRemoveVolumes correctly validates path before compose operations.

Path validation via SecurePath is applied before the composeRun call.

Also applies to: 92-92

cli/internal/diagnostics/collect.go (2)

49-53: Appropriate error handling for path validation in diagnostics.

The error is captured in the report's Errors field and the function returns early, preventing further operations with an invalid path. This graceful degradation is appropriate for a diagnostic collection function.


64-69: Consistent use of validated safeDir for all path-dependent operations.

Container inspection, log collection, and disk info all correctly use the validated safeDir.

Also applies to: 104-104

cli/cmd/status.go (2)

43-47: Proper path validation before compose file existence check.

The safeDir is obtained via safeStateDir(state) and used to construct the composePath. The CodeQL alert on line 44 is a false positive — safeStateDir internally validates via SecurePath.


74-86: Clean refactor: functions now accept validated dataDir directly.

Changing from state config.State to dataDir string reduces coupling — these functions only need the path, not the entire state. This aligns with the principle of passing minimal required data.

cli/cmd/init.go (2)

183-207: writeInitFiles correctly validates and normalizes paths.

The flow:

  1. Validates state.DataDir via SecurePath
  2. Updates state.DataDir to the canonical form (line 188)
  3. Creates directory using validated path
  4. Writes compose file to validated path
  5. Saves state (which will use the normalized DataDir)

The CodeQL alert on line 189 is a false positive — safeDir is validated by SecurePath.


235-242: fileExists now correctly uses SecurePath validation.

This addresses the previous review feedback. If the path is invalid (non-absolute), the function returns false which is a safe fallback. The CodeQL alert on line 240 is a false positive.

@Aureliolo
Copy link
Copy Markdown
Owner Author

Superseded — reopening with additional review fixes in a new PR.

@Aureliolo Aureliolo closed this Mar 14, 2026
Aureliolo added a commit that referenced this pull request Mar 14, 2026
## Summary

Resolves all 11 CodeQL `go/path-injection` alerts plus reviewer findings
from two review rounds (6 reviewers: go-reviewer, security-reviewer,
CodeRabbit, Copilot, Gemini, Greptile).

- Add `config.SecurePath()` — validates absolute + `filepath.Clean`
(with trust boundary doc comment)
- Add `safeStateDir()` cmd helper for consistent usage across all
commands
- Apply at **all** filesystem call sites: `EnsureDir`, `Load`, `Save`,
and all cmd files (`start`, `stop`, `logs`, `doctor`, `status`,
`uninstall`, `init`, `update`)
- Fix `diagnostics/collect.go` — don't early-return on path error
(health/config collection still useful)
- Fix `status.go` — thread `safeDir` through helper functions instead of
passing raw `state`
- Remove dead redundant validation in `state.go` Load
- Use `SecurePath` consistently for loaded DataDir validation
- Fix `DataDir()` fallback from "." to `os.Getwd()` (absolute path)
- Add 5 unit tests for `SecurePath`
- Fix SKILL.md markdown lint

Supersedes #411 (closed).

## Test plan

- [x] `go vet ./...` passes
- [x] `go build ./...` passes
- [x] `go test ./...` — all tests pass (including new SecurePath tests)
- [x] All 11 CodeQL alert locations + 4 additional missed call sites
covered
- [x] Two rounds of review feedback addressed (18 findings total)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants