Skip to content

🔥 feat: Add support for RootDir and RootFs#4035

Open
gaby wants to merge 10 commits intomainfrom
add-path-normalization-and-validation-in-savefile
Open

🔥 feat: Add support for RootDir and RootFs#4035
gaby wants to merge 10 commits intomainfrom
add-path-normalization-and-validation-in-savefile

Conversation

@gaby
Copy link
Member

@gaby gaby commented Jan 26, 2026

Motivation

  • Harden multipart upload handling by performing path normalization and writable/symlink probes at startup to avoid runtime directory creation and unexpected panics.
  • Replace ad-hoc, unexported error values with exported errors to provide consistent API-level error reporting for invalid upload paths and root-escape attempts.
  • Prefer github.com/gofiber/utils/v2 trimming helpers for reliable, high-performance string trimming in hot code paths.

Description

  • Add Config.RootDir and Config.RootFs and initialize per-app upload state in configureUploads() (uploadRootDir, uploadRootEval, uploadRootPath, uploadRootFSPrefix, uploadRootFSWriter) to probe/create roots at startup and verify writability.
  • Introduce validateUploadPath, isAbsUploadPath, containsDotDot, storageRootPrefix, cleanUploadRootPrefix, ensureUploadPathWithinRoot, ensureNoSymlinkFS, probeUploadDirWritable, and probeUploadFSWritable in upload.go to centralize path normalization, validation, and probes; use utils.TrimLeft instead of strings.TrimPrefix where appropriate.
  • Add exported errors ErrInvalidUploadPath and ErrUploadPathEscapesRoot to error.go and update all upload-related code to return those errors.
  • Harden DefaultCtx.SaveFile and DefaultCtx.SaveFileToStorage to validate and normalize the provided path, resolve it against the configured root (OS dir or RootFs prefix), reject traversal/absolute attempts, and write either to the RootFs via its OpenFile or to the pre-resolved RootDir path; add saveMultipartFileToFS helper for FS writer paths.
  • Add tests covering allowed and rejected paths and storage behavior (Test_Ctx_SaveFile, Test_Ctx_SaveFile_RootDirTraversal, Test_Ctx_SaveFileToStorage, Test_Ctx_SaveFileToStorage_RootDirTraversal), and update docs (docs/api/ctx.md, docs/api/fiber.md) to document relative-path requirement and root resolution behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

Walkthrough

Adds configurable upload root support (RootDir, RootFs, RootPerms), comprehensive upload path validation and containment/symlink protections, RootFs-aware save logic, new upload-related errors, extensive tests, and updated docs and logging for upload handling.

Changes

Cohort / File(s) Summary
Config & app init
app.go, docs/api/fiber.md, docs/whats_new.md
Add Config.RootDir, Config.RootPerms, Config.RootFs; initialize default RootPerms and call configureUploads() during New(); document new fields and defaults.
Ctx save logic & errors
ctx.go, error.go, docs/api/ctx.md
Enhance SaveFile / SaveFileToStorage to validate/normalize paths, enforce relative-only uploads, prevent root escape (symlink + traversal), apply root prefixing, and surface ErrInvalidUploadPath / ErrUploadPathEscapesRoot.
Upload subsystem
upload.go
New upload handling module: validateUploadPath, path utilities, symlink detection, root containment checks, RootFs vs on-disk RootDir wiring, writability probes, and helper saveMultipartFileToFS.
Tests — core & integration
upload_test.go, ctx_test.go, app_test.go
Large test additions: in-memory FS stubs, root FS/file mocks, many tests for path validation, symlink escape, root containment, storage key prefixing, and storage error propagation.
Test logging change
listen_test.go
Swap standard log usage to Fiber's log package in tests for output capture.

Sequence Diagram

sequenceDiagram
    participant Client
    participant SaveFileHandler as SaveFile Handler
    participant Validator as Path Validator
    participant RootStore as RootFs/RootDir
    participant Storage as Storage Backend

    Client->>SaveFileHandler: POST multipart with path
    SaveFileHandler->>Validator: validateUploadPath(path)
    alt invalid path
        Validator-->>SaveFileHandler: ErrInvalidUploadPath
        SaveFileHandler-->>Client: error response
    else valid path
        Validator-->>SaveFileHandler: normalized path
        SaveFileHandler->>RootStore: ensure path contained and writable
        alt escapes root or symlink detected
            RootStore-->>SaveFileHandler: ErrUploadPathEscapesRoot
            SaveFileHandler-->>Client: error response
        else contained
            SaveFileHandler->>RootStore: saveMultipartFileToFS / write to disk
            RootStore-->>Storage: provide storage key (prefixed)
            Storage-->>SaveFileHandler: success
            SaveFileHandler-->>Client: success response
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰 In my burrow RootDir snug and neat,
RootPerms guard every little feat.
Paths are checked, no ".." may slide,
Symlinks stopped from slipping outside.
Hooray — uploads hop home safe and sweet! 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title uses an emoji and vague phrasing ('feat: Add support') that doesn't clearly convey the main change of path normalization and validation for multipart uploads. Consider a more specific title like 'Add RootDir and RootFs configuration for hardened upload path handling' that avoids emojis and clearly indicates the primary purpose.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering motivation, detailed description of changes, and implementation details across multiple files.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-path-normalization-and-validation-in-savefile
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

@ReneWerner87 ReneWerner87 added this to v3 Jan 26, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Jan 26, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gaby, 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 improves the security and reliability of file uploads by introducing a comprehensive validation and normalization system for upload paths. It allows applications to define secure root directories or filesystems for uploads, preventing malicious path manipulations like directory traversal. The changes also standardize error reporting for invalid upload attempts and ensure that the upload environment is properly configured and writable at startup, leading to a more secure and predictable file handling mechanism.

Highlights

  • Enhanced Upload Security: Implemented robust path normalization and validation for multipart uploads to prevent path traversal and absolute path attacks, ensuring files are saved within designated boundaries.
  • Configurable Upload Roots: Introduced new configuration options, Config.RootDir and Config.RootFs, allowing developers to define a base directory or a filesystem interface for SaveFile and SaveFileToStorage operations.
  • Standardized Error Reporting: Replaced ad-hoc error values with exported errors (ErrInvalidUploadPath, ErrUploadPathEscapesRoot) for consistent and clear API-level error reporting related to upload path issues.
  • Startup Validation and Probes: Added logic to perform path normalization, writability checks, and symlink probes at application startup, preventing runtime panics and ensuring secure upload environments from the start.
  • Updated API and Documentation: Modified DefaultCtx.SaveFile and DefaultCtx.SaveFileToStorage to incorporate the new validation logic, and updated documentation to reflect the new path requirements and root resolution behavior.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

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.

@gaby gaby changed the title 🐛 bug: expose upload path validation errors 🔥 feat: Add support for RootDir and RootFs Jan 26, 2026
@gaby gaby removed the ☢️ Bug label Jan 26, 2026
Copy link
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 significantly enhances the security and robustness of file upload handling by introducing comprehensive path validation, root directory confinement, and symlink checks. The changes centralize upload logic into a new upload.go file, ensuring that all file saving operations (SaveFile and SaveFileToStorage) are hardened against path traversal and other malicious attempts. New configuration options (RootDir, RootFs) provide flexible control over upload destinations, and dedicated error types (ErrInvalidUploadPath, ErrUploadPathEscapesRoot) improve API-level error reporting. The accompanying tests thoroughly cover various scenarios, including valid and invalid paths, ensuring the new security measures function as intended. Overall, this is a well-executed and crucial security improvement.

Copy link
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 hardens multipart upload handling by introducing configurable upload roots (RootDir, RootFs), centralized upload path validation/normalization, and stronger traversal/symlink protections for SaveFile and SaveFileToStorage, with corresponding tests and docs.

Changes:

  • Added Config.RootDir/Config.RootFs and configureUploads() to pre-resolve, create, and probe upload roots (OS directory or fs.FS-backed) at app startup.
  • Introduced upload.go helpers (validateUploadPath, ensureUploadPathWithinRoot, ensureNoSymlinkFS, etc.) and wired them into DefaultCtx.SaveFile / SaveFileToStorage to enforce relative, non-escaping paths and symlink checks.
  • Exported new upload errors (ErrInvalidUploadPath, ErrUploadPathEscapesRoot), updated docs for config and context APIs, and extended tests to cover allowed paths and root traversal rejection.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
upload.go New upload configuration and helper functions for path validation, root resolution, symlink and writability checks backing the updated upload behavior.
error.go Adds exported error values for invalid upload paths and root-escape attempts to standardize error reporting.
app.go Extends Config with RootDir/RootFs, stores derived upload root state, and calls configureUploads() during app initialization.
ctx.go Reimplements SaveFile and SaveFileToStorage to normalize/validate paths, enforce RootDir/RootFs boundaries, check symlinks, and write via either fasthttp or a custom fs.FS writer.
ctx_test.go Updates SaveFile tests to use RootDir and adds traversal/absolute path rejection tests for both disk and storage uploads, plus a recordingStorage helper.
docs/api/fiber.md Documents the new Config.RootDir and Config.RootFs options and their role in upload path resolution.
docs/api/ctx.md Documents the new path constraints for SaveFile and SaveFileToStorage (relative-only, no .. or absolute prefixes, enforced against configured roots).

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 78.74016% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.77%. Comparing base (d6eb497) to head (0d76119).

Files with missing lines Patch % Lines
upload.go 82.74% 17 Missing and 17 partials ⚠️
ctx.go 62.96% 13 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4035      +/-   ##
==========================================
- Coverage   91.07%   90.77%   -0.31%     
==========================================
  Files         122      123       +1     
  Lines       11550    11801     +251     
==========================================
+ Hits        10519    10712     +193     
- Misses        651      685      +34     
- Partials      380      404      +24     
Flag Coverage Δ
unittests 90.77% <78.74%> (-0.31%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 0d76119 Previous: d6eb497 Ratio
Benchmark_Compress/Zstd (github.com/gofiber/fiber/v3/middleware/compress) - B/op 1 B/op 0 B/op +∞

This comment was automatically generated by workflow using github-action-benchmark.

@gaby
Copy link
Member Author

gaby commented Jan 27, 2026

/gemini review

Copy link
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 introduces robust security enhancements for file uploads by adding RootDir and RootFs configurations. The changes centralize path normalization, validation, and writability probes, effectively preventing path traversal and symlink attacks. The new upload.go file encapsulates this logic, and the comprehensive test suite demonstrates thorough coverage of various scenarios, including edge cases and error conditions. The documentation updates clearly explain the new features and security implications for users. Overall, this is a well-implemented and critical improvement for the framework's file upload capabilities.

@gaby gaby marked this pull request as ready for review January 27, 2026 15:28
@gaby gaby requested a review from a team as a code owner January 27, 2026 15:28
Copy link
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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

@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: 1

🤖 Fix all issues with AI agents
In `@docs/api/ctx.md`:
- Around line 1713-1714: The note about "Storage keys are prefixed by the
configured root when one is set" is misplaced under the SaveFile section; move
that sentence to the SaveFileToStorage section (and remove it from the current
paragraph at lines near 1713), and also update the duplicate mention around
lines 1748-1749 so the storage-key prefixing detail only appears in the
SaveFileToStorage doc; ensure the SaveFile paragraph keeps only disk/path
constraints (relative paths, no .., resolved against Config.RootDir/RootFs) and
that SaveFileToStorage explains the root prefixed storage key behavior.
🧹 Nitpick comments (2)
app_test.go (2)

199-204: Prefer require assertions for consistency.

The test file predominantly uses require from testify for assertions. For consistency, consider using require.Equal instead of manual comparisons with t.Fatalf.

♻️ Suggested refactor
-			if tfs.mkdirPath != "uploads" {
-				t.Fatalf("expected RootFs prefix %q, got %q", "uploads", tfs.mkdirPath)
-			}
-			if tfs.mkdirPerm != tt.wantPerm {
-				t.Fatalf("expected RootPerms %o, got %o", tt.wantPerm, tfs.mkdirPerm)
-			}
+			require.Equal(t, "uploads", tfs.mkdirPath, "RootFs prefix mismatch")
+			require.Equal(t, tt.wantPerm, tfs.mkdirPerm, "RootPerms mismatch")

214-224: Prefer require assertions for consistency.

Similar to the previous test, this test should use require assertions for consistency with the rest of the test file.

♻️ Suggested refactor
-	if err != nil {
-		t.Fatalf("expected no error, got %v", err)
-	}
-
-	if !strings.HasPrefix(normalized.osPath, ".") {
-		t.Fatalf("expected os path %q to preserve leading dot", normalized.osPath)
-	}
-	if normalized.slashPath != ".hidden/file.txt" {
-		t.Fatalf("expected slash path %q, got %q", ".hidden/file.txt", normalized.slashPath)
-	}
+	require.NoError(t, err)
+	require.True(t, strings.HasPrefix(normalized.osPath, "."), "expected os path %q to preserve leading dot", normalized.osPath)
+	require.Equal(t, ".hidden/file.txt", normalized.slashPath)

Copy link
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

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

@ReneWerner87 ReneWerner87 modified the milestones: v3.0.0, v3 Feb 1, 2026
Copy link
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

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

func (c fiber.Ctx) SaveFile(fh *multipart.FileHeader, path string) error
```

Paths must be relative and cannot contain `..` segments or absolute prefixes. When `Config.RootDir` or `Config.RootFs` is set, the path is resolved against that root and attempts to escape it are rejected. Storage keys are prefixed by the configured root when one is set.
}
if !hasPathPrefix(parentEval, rootEval) {
return ErrUploadPathEscapesRoot
}
Comment on lines +242 to +247
for _, entry := range entries {
if entry.Name() == name && entry.Type()&fs.ModeSymlink != 0 {
return ErrUploadPathEscapesRoot
}
}
}
Comment on lines +237 to +239
if errors.Is(err, fs.ErrNotExist) {
return nil
}
Comment on lines +103 to +118
cleanOS := filepath.Clean(path)

cleanSlash := pathpkg.Clean("/" + filepath.ToSlash(path))
cleanSlash = utils.TrimLeft(cleanSlash, '/')

if cleanOS == "." || cleanOS == "" || cleanSlash == "." || cleanSlash == "" {
return uploadPath{}, ErrInvalidUploadPath
}
if !fs.ValidPath(cleanSlash) {
return uploadPath{}, ErrInvalidUploadPath
}

return uploadPath{
osPath: cleanOS,
slashPath: cleanSlash,
}, nil
Copy link
Contributor

@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

🧹 Nitpick comments (2)
ctx.go (2)

552-558: RootFs and RootDir checks are not mutually exclusive here.

In SaveFile, the RootFs branch returns early, but here in SaveFileToStorage, both checks run independently. This appears intentional since storage is abstract and the validation still makes sense, but it's worth confirming this asymmetry is by design.

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

In `@ctx.go` around lines 552 - 558, The code currently runs both RootFs and
RootDir validations in SaveFileToStorage (calling ensureNoSymlinkFS for
c.app.config.RootFs and the existing RootDir check), which differs from SaveFile
where the RootFs branch returns early; decide and enforce one consistent
behavior: either keep both checks (intentionally validate against both storage
backends) or make them mutually exclusive like SaveFile. Update
SaveFileToStorage to match the chosen behavior by adjusting the conditional flow
around c.app.config.RootFs and c.app.config.RootDir, referencing
storageUploadPath(normalized.slashPath), ensureNoSymlinkFS, and ensureNoSymlink
so the function returns/continues consistently with SaveFile.

619-619: Hardcoded file permissions vs configurable RootPerms.

The file is created with hardcoded 0o644 permissions, while app.config.RootPerms (defaulting to 0o750) is used for directory creation. Consider whether file permissions should also be configurable, or document that RootPerms applies only to directories.

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

In `@ctx.go` at line 619, The code currently hardcodes 0o644 for file creation in
the fsys.OpenFile call (dst, err := fsys.OpenFile(path,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)) while directories use
app.config.RootPerms; make file permissions configurable instead of hardcoding:
add a new config field (e.g., app.config.FilePerms) with a sensible default
(0o644), or explicitly document that RootPerms only applies to directories and
leave as-is; then replace the hardcoded 0o644 with app.config.FilePerms (or use
app.config.RootPerms if you decide files should follow directory perms), and
update the config initialization, comments/docs, and any relevant tests to cover
the new behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ctx_test.go`:
- Around line 5374-5377: The test currently constructs expectedPath using the
production helpers storageUploadPath and storageRootPrefix (in the
SaveFileToStorage test), which can mask regressions; change the test to build
the expected storage key directly from the documented storage-key contract
(e.g., the literal path format the storage layer should produce for rootDir and
filename) instead of calling storageUploadPath/storageRootPrefix. Update the
assertion that compares storage.setKeys (used in the test harness) to use this
hard-coded expected string so the test independently verifies SaveFileToStorage
behavior.
- Around line 4801-4808: The tests currently perform traversal assertions inside
an HTTP handler by calling c.FormFile and c.SaveFile and using
require.NoError/require.Error there; move those assertions out of the handler so
they run in the test goroutine: call the handler-equivalent API directly (e.g.,
invoke ctx.SaveFile or the exported SaveFile helper) from the test and assert
with require.ErrorIs against the exported upload/traversal error constant
instead of a generic require.Error; if you must keep an in-handler check,
replace require.* calls inside the handler with testing.T methods
(t.Fatalf/t.Errorf) to avoid linter issues, but preferred fix is converting the
test to directly call SaveFile/FormFile equivalents and assert ErrorIs against
the exported upload error symbol.

In `@ctx.go`:
- Around line 631-637: The current error handling in the io.Copy failure path
discards the original copy error when dst.Close() also fails; update the block
that handles io.Copy(writer, file) in ctx.go to preserve both errors by
capturing the copy error (err) and the close error (closeErr) and returning a
combined error (e.g., using errors.Join(copyErr, closeErr) or wrapping them
together) instead of returning only closeErr; ensure references to writer, file,
dst.Close(), and io.Copy remain and that the returned error message includes
both failure contexts.

---

Nitpick comments:
In `@ctx.go`:
- Around line 552-558: The code currently runs both RootFs and RootDir
validations in SaveFileToStorage (calling ensureNoSymlinkFS for
c.app.config.RootFs and the existing RootDir check), which differs from SaveFile
where the RootFs branch returns early; decide and enforce one consistent
behavior: either keep both checks (intentionally validate against both storage
backends) or make them mutually exclusive like SaveFile. Update
SaveFileToStorage to match the chosen behavior by adjusting the conditional flow
around c.app.config.RootFs and c.app.config.RootDir, referencing
storageUploadPath(normalized.slashPath), ensureNoSymlinkFS, and ensureNoSymlink
so the function returns/continues consistently with SaveFile.
- Line 619: The code currently hardcodes 0o644 for file creation in the
fsys.OpenFile call (dst, err := fsys.OpenFile(path,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)) while directories use
app.config.RootPerms; make file permissions configurable instead of hardcoding:
add a new config field (e.g., app.config.FilePerms) with a sensible default
(0o644), or explicitly document that RootPerms only applies to directories and
leave as-is; then replace the hardcoded 0o644 with app.config.FilePerms (or use
app.config.RootPerms if you decide files should follow directory perms), and
update the config initialization, comments/docs, and any relevant tests to cover
the new behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cc3cdbe8-2a0c-49c7-8747-373f2b9ef35e

📥 Commits

Reviewing files that changed from the base of the PR and between 09cb0c8 and 0d76119.

📒 Files selected for processing (8)
  • app.go
  • app_test.go
  • ctx.go
  • ctx_test.go
  • docs/api/ctx.md
  • docs/api/fiber.md
  • docs/whats_new.md
  • error.go
✅ Files skipped from review due to trivial changes (1)
  • docs/api/ctx.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • error.go
  • docs/whats_new.md
  • docs/api/fiber.md
  • app_test.go

Comment on lines +4801 to +4808
app.Post("/test", func(c Ctx) error {
fh, err := c.FormFile("file")
require.NoError(t, err)

err = c.SaveFile(fh, targetPath)
require.Error(t, err)
return c.SendStatus(StatusOK)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move these traversal assertions out of the handler and lock the exported error down.

These new RootDir traversal tests only check require.Error(...), so they will still pass on the wrong failure mode, and they add require calls inside Fiber handlers. Converting them to direct ctx.SaveFile* tests would keep assertions in the test goroutine and let each case assert require.ErrorIs(...) against the intended exported upload error.

Based on learnings: In the Fiber codebase, the linter does not allow require assertions from within HTTP handlers (including net/http-style handlers). Use t.Fatalf, t.Errorf, or similar testing.T methods for error handling inside handler functions instead.

Also applies to: 5024-5031

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

In `@ctx_test.go` around lines 4801 - 4808, The tests currently perform traversal
assertions inside an HTTP handler by calling c.FormFile and c.SaveFile and using
require.NoError/require.Error there; move those assertions out of the handler so
they run in the test goroutine: call the handler-equivalent API directly (e.g.,
invoke ctx.SaveFile or the exported SaveFile helper) from the test and assert
with require.ErrorIs against the exported upload/traversal error constant
instead of a generic require.Error; if you must keep an in-handler check,
replace require.* calls inside the handler with testing.T methods
(t.Fatalf/t.Errorf) to avoid linter issues, but preferred fix is converting the
test to directly call SaveFile/FormFile equivalents and assert ErrorIs against
the exported upload error symbol.

Comment on lines +5374 to +5377
err := ctx.SaveFileToStorage(fileHeader, "rootdir.txt", storage)
require.NoError(t, err)
expectedPath := storageUploadPath(storageRootPrefix(rootDir), "rootdir.txt")
require.Equal(t, []string{expectedPath}, storage.setKeys)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep this expected storage key independent from the production helpers.

Line 5376 builds expectedPath with storageUploadPath(storageRootPrefix(rootDir), ...), so this test can still pass if the new RootDir key-normalization logic regresses in both places. Build the expected key from the documented storage-key contract instead of calling the helpers under test.

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

In `@ctx_test.go` around lines 5374 - 5377, The test currently constructs
expectedPath using the production helpers storageUploadPath and
storageRootPrefix (in the SaveFileToStorage test), which can mask regressions;
change the test to build the expected storage key directly from the documented
storage-key contract (e.g., the literal path format the storage layer should
produce for rootDir and filename) instead of calling
storageUploadPath/storageRootPrefix. Update the assertion that compares
storage.setKeys (used in the test harness) to use this hard-coded expected
string so the test independently verifies SaveFileToStorage behavior.

Comment on lines +631 to +637
if _, err = io.Copy(writer, file); err != nil {
closeErr := dst.Close()
if closeErr != nil {
return fmt.Errorf("failed to close upload destination: %w", closeErr)
}
return fmt.Errorf("failed to copy upload data: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Original copy error is lost when close also fails.

When io.Copy fails and dst.Close() also fails, only the close error is returned. The original copy error provides more diagnostic value. Consider using errors.Join or wrapping both.

Proposed fix to preserve both errors
 	if _, err = io.Copy(writer, file); err != nil {
 		closeErr := dst.Close()
 		if closeErr != nil {
-			return fmt.Errorf("failed to close upload destination: %w", closeErr)
+			return fmt.Errorf("failed to copy upload data: %w (also failed to close: %v)", err, closeErr)
 		}
 		return fmt.Errorf("failed to copy upload data: %w", err)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err = io.Copy(writer, file); err != nil {
closeErr := dst.Close()
if closeErr != nil {
return fmt.Errorf("failed to close upload destination: %w", closeErr)
}
return fmt.Errorf("failed to copy upload data: %w", err)
}
if _, err = io.Copy(writer, file); err != nil {
closeErr := dst.Close()
if closeErr != nil {
return fmt.Errorf("failed to copy upload data: %w (also failed to close: %v)", err, closeErr)
}
return fmt.Errorf("failed to copy upload data: %w", err)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ctx.go` around lines 631 - 637, The current error handling in the io.Copy
failure path discards the original copy error when dst.Close() also fails;
update the block that handles io.Copy(writer, file) in ctx.go to preserve both
errors by capturing the copy error (err) and the close error (closeErr) and
returning a combined error (e.g., using errors.Join(copyErr, closeErr) or
wrapping them together) instead of returning only closeErr; ensure references to
writer, file, dst.Close(), and io.Copy remain and that the returned error
message includes both failure contexts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants