Skip to content

chore(security): suppress two Semgrep filepath-clean false positives with inline reasons#99

Merged
aksOps merged 1 commit intomainfrom
fix/semgrep-filepath-clean-suppressions
May 4, 2026
Merged

chore(security): suppress two Semgrep filepath-clean false positives with inline reasons#99
aksOps merged 1 commit intomainfrom
fix/semgrep-filepath-clean-suppressions

Conversation

@aksOps
Copy link
Copy Markdown
Contributor

@aksOps aksOps commented May 4, 2026

Summary

Semgrep's go.lang.security.filepath-clean-misuse rule fires on every filepath.Clean / path.Clean call on user-controlled input regardless of surrounding defence. Two sites in this repo trip the rule but are safe by construction; this PR adds inline // nosemgrep: annotations with the reasoning captured next to the call rather than rewriting working code to placate a single-statement linter.

Findings handled

internal/api/notes_handlers.go:488 (tar import)

Clean here normalises hdr.Name; it is not the security boundary. The actual defence is three layers:

  1. Explicit reject of any name with a / prefix or .. substring (immediately after the Clean)
  2. filepath.Join(notesDir, ...) then filepath.Abs containment check (absDest must start with absBase + os.PathSeparator)
  3. Reader is bounded by MaxNoteBytes so a malicious entry can't cause a write blow-up either

All three must fail simultaneously for a traversal to land — Semgrep sees only the first statement.

internal/api/router.go:237 (SPA file server)

Clean here normalises a URL path for SPA-vs-asset classification. The handler reads only from assets fs.FS, which is fs.Sub(embed.FS, "dist") (see ui/embed.go). Two architectural guarantees apply:

  • io/fs rejects any path containing .. via fs.ValidPath before it reaches embed.FS
  • embed.FS only contains files baked in at compile time — there is no host-filesystem reachability regardless of what cleanPath ends up

Path traversal is not possible here.

Why suppress instead of "fix"

Semgrep's autofix in both cases is filepath.FromSlash(path.Clean(...)), which doesn't change the threat model — it just shuts the linter up. The recommended cyphar/filepath-securejoin would add a dependency and replace working multi-layer defence with a single library call that is no stronger here. The right answer is to keep the existing defence and document why the rule doesn't apply, which is what this PR does.

Test plan

  • go vet -tags sqlite_fts5 ./internal/api/... clean
  • go build -tags sqlite_fts5 ./internal/api/... clean
  • Pre-existing tar-import + SPA tests still pass (no behaviour change)
  • CI's Semgrep job goes green on this branch (the merge gate)

Compatibility

Drop-in. Pure documentation/comment changes — no behaviour change. Two pre-existing one-line comments at the same sites are folded into the new explanatory blocks; nothing else moves.

🤖 Generated with Claude Code

…with reasons

Semgrep's go.lang.security.filepath-clean-misuse rule fires on every
filepath.Clean / path.Clean call on user-controlled input regardless
of surrounding defence. Two sites in this repo trip the rule but are
safe by construction; rather than rewrite working code to placate a
single-statement linter, suppress the rule inline with the reasoning
captured next to the call.

internal/api/notes_handlers.go (tar import):
  Clean here normalises hdr.Name; the traversal defence is the
  explicit reject of "/" prefix + ".." substring on the next line,
  plus an absolute-path containment check after filepath.Join into
  notesDir. All three layers must fail for a traversal to land —
  Semgrep sees only the first.

internal/api/router.go (SPA file server):
  Clean normalises a URL path for SPA-vs-asset classification, not
  security. The handler reads only from `assets fs.FS`, which is
  fs.Sub(embed.FS, "dist"). io/fs rejects any path containing ".."
  via fs.ValidPath, and embed.FS holds only compile-time files —
  path traversal cannot reach the host filesystem regardless of
  what cleanPath becomes.

No behaviour change. Pre-existing one-line comments at the same
sites are folded into the new explanatory blocks.
@aksOps aksOps enabled auto-merge (squash) May 4, 2026 06:31
@aksOps aksOps merged commit 5754d74 into main May 4, 2026
17 checks passed
@aksOps aksOps deleted the fix/semgrep-filepath-clean-suppressions branch May 4, 2026 06:35
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.

1 participant