Skip to content

VNX-GO-026: Missing file type validation on upload

Detects an uploaded multipart file saved to disk under the client's own file name (FileHeader.Filename) by a handler that never checks the file's extension or content type.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-GO-026
Severity High
SARIF level error
Kind sast
Languages Go
CWE CWE-434
CAPEC CAPEC-177
ATT&CK T1105
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags file-upload validation mime-type
Source internal/sast/rules/vnx-go-026.rego

Overview

This rule flags instances where file upload functionality is implemented using Go’s standard library multipart form handling but lacks apparent file type validation. Accepting file uploads without validating the file type can lead to malicious file upload vulnerabilities where attackers upload executable code, scripts, or other dangerous files.

This maps to CWE-434: Unrestricted Upload of File with Dangerous Type.

Why This Matters

File upload functionality is a common feature in web applications, but it presents significant security risks if not properly implemented. Without file type validation, attackers can upload:

  • Malicious scripts (.php, .py, .sh) that can be executed on the server
  • Executable files (.exe, .bin) that can compromise the server
  • HTML/JavaScript files that can lead to XSS when served to users
  • Files with dangerous extensions that can bypass client-side validation

These vulnerabilities can lead to remote code execution, data theft, server compromise, or use of the server as a malware distribution point.

What Gets Flagged

A handler that receives an uploaded file and saves it under the name the client sent, with no check anywhere in the handler of what kind of file it is.

// FLAGGED: the client picks the extension (shell.php, index.html)
file, handler, err := r.FormFile("upload")
dst, err := os.Create(filepath.Join("./public/uploads", handler.Filename))
io.Copy(dst, file)

// FLAGGED: gin
f, _ := c.FormFile("avatar")
c.SaveUploadedFile(f, "uploads/"+f.Filename)

Not flagged: handlers that check filepath.Ext, strings.HasSuffix, http.DetectContentType, the part’s Content-Type, image.DecodeConfig or call a helper such as validateUpload; files saved under a server-generated name; uploads parsed in memory; clients building a multipart request.

Remediation

  1. Validate MIME type: Use Go’s net/http.DetectContentType or mime package to verify the actual file content:

    // SAFE: Validate MIME type
    func uploadHandler(w http.ResponseWriter, r *http.Request) {
        if err := r.ParseMultipartForm(10 << 20); err != nil {
            http.Error(w, "Unable to parse form", http.StatusBadRequest)
            return
        }
        
        file, handler, err := r.FormFile("upload")
        if err != nil {
            http.Error(w, "Invalid file", http.StatusBadRequest)
            return
        }
        defer file.Close()
        
        // Read first 512 bytes to detect content type
        buffer := make([]byte, 512)
        _, err := file.Read(buffer)
        if err != nil {
            http.Error(w, "Unable to read file", http.StatusInternalServerError)
            return
        }
        // Reset file pointer for later use
        file.Seek(0, io.SeekStart)
        
        contentType := http.DetectContentType(buffer)
        allowedTypes := map[string]bool{
            "image/jpeg": true,
            "image/png":  true,
            "application/pdf": true,
        }
        
        if !allowedTypes[contentType] {
            http.Error(w, "Invalid file type", http.StatusBadRequest)
            return
        }
        
        // Now safe to save the file
        dst, err := os.Create(filepath.Join("./uploads", handler.Filename))
        // ... rest of upload logic
    }
  2. Validate file extension: Check that the file extension matches expected types:

    // SAFE: Validate file extension
    func isAllowedExtension(filename string) bool {
        ext := strings.ToLower(filepath.Ext(filename))
        allowed := map[string]bool{
            ".jpg": true, ".jpeg": true,
            ".png": true, ".gif": true,
            ".pdf": true, ".txt": true,
        }
        return allowed[ext]
    }
    
    // Then in upload handler:
    if !isAllowedExtension(handler.Filename) {
        http.Error(w, "Invalid file extension", http.StatusBadRequest)
        return
    }
  3. Combine multiple validation approaches: Use both MIME type and extension validation:

    // SAFE: Defense in depth - check both content and extension
    func validateFile(file multipart.File, filename string) error {
        // Check extension
        ext := strings.ToLower(filepath.Ext(filename))
        if !isAllowedExtension(ext) {
            return fmt.Errorf("invalid file extension: %s", ext)
        }
        
        // Check content type
        buffer := make([]byte, 512)
        if _, err := file.Read(buffer); err != nil {
            return fmt.Errorf("unable to read file: %v", err)
        }
        file.Seek(0, io.SeekStart) // Reset for later use
        
        contentType := http.DetectContentType(buffer)
        if !isAllowedMIMEType(contentType) {
            return fmt.Errorf("invalid file type: %s", contentType)
        }
        
        // Optional: Additional validation like magic bytes for specific types
        return nil
    }
  4. Implement file quarantine and scanning: For high-security applications:

    • Save uploaded files to a quarantine area outside web root
    • Scan with antivirus software
    • Validate file content matches expected format (e.g., use image libraries to verify real images)
    • Only move to public location after passing all validations

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-go-026
// nosec vnx-go-026

Or record an ignore rule: vulnetix ignore add --rule vnx-go-026 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-go-026.rego, cli/internal/sast/docs/vnx-go-026.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.