Tencent/WeKnora · warning

invalid path: %w

Error message

invalid path: %w

What it means

localFileService.SaveFile wraps SafePathUnderBase failures with "invalid path: %w" when the constructed storage directory (baseDir/tenantID/knowledgeID) escapes the configured base directory. SafePathUnderBase canonicalizes both paths and refuses any path not strictly under baseDir, blocking path-traversal uploads. knowledgeID is the main attacker-controlled component.

Source

Thrown at internal/application/service/file/local.go:62

		externalURL: strings.TrimRight(externalURL, "/"),
	}
}

// SaveFile stores an uploaded file to the local file system
// The file is stored in a directory structure: baseDir/tenantID/knowledgeID/filename
// Returns the full file path or an error if saving fails
func (s *localFileService) SaveFile(ctx context.Context,
	file *multipart.FileHeader, tenantID uint64, knowledgeID string,
) (string, error) {
	logger.Info(ctx, "Starting to save file locally")
	logger.Infof(ctx, "File information: name=%s, size=%d, tenant ID=%d, knowledge ID=%s",
		file.Filename, file.Size, tenantID, knowledgeID)

	// Create storage directory with tenant and knowledge ID
	dir := filepath.Join(s.baseDir, fmt.Sprintf("%d", tenantID), knowledgeID)
	if _, err := secutils.SafePathUnderBase(s.baseDir, dir); err != nil {
		logger.Errorf(ctx, "Path traversal denied for SaveFile dir: %v", err)
		return "", fmt.Errorf("invalid path: %w", err)
	}
	logger.Infof(ctx, "Creating directory: %s", dir)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		logger.Errorf(ctx, "Failed to create directory: %v", err)
		return "", fmt.Errorf("failed to create directory: %w", err)
	}

	// Generate unique filename using timestamp
	ext := filepath.Ext(file.Filename)
	filename := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext)
	filePath := filepath.Join(dir, filename)
	logger.Infof(ctx, "Generated file path: %s", filePath)

	// Open source file for reading
	logger.Info(ctx, "Opening source file")
	src, err := file.Open()
	if err != nil {
		logger.Errorf(ctx, "Failed to open source file: %v", err)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Sanitize knowledgeID before SaveFile: restrict it to a safe charset (UUID/alphanumeric/dash) and reject any value containing "/", "\\", or "..".
  2. Configure baseDir as a fully-resolved absolute path without symlinks (filepath.EvalSymlinks) so validation and the real layout agree.
  3. Log the offending knowledgeID and return HTTP 400 — treat as malicious input rather than retrying.
  4. If IDs must contain separators, map them to a flat safe form (e.g. hash or url-encode) before building the directory.

Example fix

// before
svc.SaveFile(ctx, fh, 1, r.URL.Query().Get("knowledgeId")) // "../../evil"
// after
id := r.URL.Query().Get("knowledgeId")
if !regexp.MustCompile(`^[A-Za-z0-9-]+$`).MatchString(id) {
    http.Error(w, "invalid knowledge id", 400); return
}
svc.SaveFile(ctx, fh, 1, id)
Defensive patterns

Strategy: validation

Validate before calling

var safeID = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`)
func safeKnowledgeID(id string) bool { return safeID.MatchString(id) }

Try / catch

if _, err := svc.SaveFile(ctx, fh, tenantID, knowledgeID); err != nil {
	if strings.Contains(err.Error(), "invalid path") {
		http.Error(w, "invalid knowledge id", http.StatusBadRequest)
		return
	}
}

Prevention

When it happens

Trigger: SaveFile called with a knowledgeID containing "../", absolute-path segments, or a baseDir that canonicalizes outside itself (e.g. baseDir containing symlinks or relative segments) so that filepath.Join produces a path escaping the base.

Common situations: Client-supplied knowledge ID passed unsanitized from an HTTP handler; knowledge IDs with slashes or dot-dot from a legacy importer; baseDir configured with trailing relative components or symlinked subpaths that resolve outside the base.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/a2ea20f4a865766f. Report an issue: GitHub.