Skip to content

Use os.Root for safe filesystem operations in SaveUploadedFile (Go 1.24+) and support returning 415 for wrong mime types #4488

@wathika-eng

Description

@wathika-eng

Feature Description

With the introduction of os.Root in Go 1.24, the standard library now provides a capability-based API for filesystem access that guarantees all operations remain confined to a specific directory tree.

The current implementation of SaveUploadedFile uses os.Create, os.MkdirAll and path-based operations, which are vulnerable to path traversal and symlink escape issues if dst is derived from user input.

Current implementation:

gin/context.go

Line 708 in 9914178

func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error {

Sample changes:

// SaveUploadedFile uploads the form file to dst within the provided root.
// The destination path is always constrained to the root directory.
func (c *Context) SaveUploadedFile(
	file *multipart.FileHeader,
	root *os.Root,
	dst string,
	perm ...fs.FileMode,
) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	mode := fs.FileMode(0o750)
	if len(perm) > 0 {
		mode = perm[0]
	}

	// Ensure parent directories exist inside the root
	if dir := filepath.Dir(dst); dir != "." {
		if err := root.MkdirAll(dir, mode); err != nil {
			return err
		}
	}

	out, err := root.OpenFile(
		dst,
		os.O_CREATE|os.O_WRONLY|os.O_TRUNC,
		mode,
	)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, src)
	return err
}

Also add 415 Unsupported Media Type → unexpected Content-Type in ShouldBindJSON so as to conform with HTTP standards: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/415

var ErrUnsupportedMediaType = errors.New("unsupported media type")

func (c *Context) ShouldBindJSON(obj any) error {
	//if c.ContentType() != MIMEJSON {
	//	return ErrUnsupportedMediaType
	//}
	return c.ShouldBindWith(obj, binding.JSON)
}

Although this unit test is not clear:

func TestContextBindWithJSON(t *testing.T) {

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/proposalGot an idea for a feature that Gin doesn't have currently? Submit your idea here!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions