Tencent/WeKnora · error
storage path has no tenant segment
Error message
storage path has no tenant segment
What it means
ValidateStoragePathTenant found no tenant segment in the storage path: ParseTenantIDFromStoragePath returned 0. The path must embed the owning tenant's ID (e.g. /storage/<tenantID>/...) so presigned-URL validation can confirm the caller's tenant matches. A path without this segment cannot be attributed to a tenant and is rejected defensively.
Source
Thrown at internal/utils/presign.go:118
// Verify signature.
expected := signPayload(key, filePath, tenantID, expires)
return hmac.Equal([]byte(expected), []byte(sig))
}
// kbScopedExportsSegment is the only storage prefix served by the KB-scoped
// file proxy. Embedded wiki/chunk images land under exports/; raw knowledge
// uploads use {tenant}/{knowledgeID}/... and are served via
// /knowledge/{id}/download instead.
const kbScopedExportsSegment = "exports"
// ValidateStoragePathTenant ensures the tenant segment embedded in a provider://
// storage path matches the authenticated caller's tenant. Cross-tenant access
// for arbitrary tenant paths uses /api/v1/files/presigned with an HMAC bound to
// the resource owner; KB-scoped shared rendering uses ValidateKBScopedStoragePath.
func ValidateStoragePathTenant(filePath string, tenantID uint64) error {
pathTenant := ParseTenantIDFromStoragePath(filePath)
if pathTenant == 0 {
return fmt.Errorf("storage path has no tenant segment")
}
if pathTenant != tenantID {
return fmt.Errorf("storage path workspace mismatch")
}
return nil
}
// ValidateKBScopedStoragePath is used by GET /knowledge-bases/:id/files. It
// requires the path to belong to the KB owner tenant and to live under the
// exports/ namespace used for embedded images (SaveBytes / multimodal output).
// This prevents borrowers with shared-KB read access from using the proxy to
// fetch arbitrary owner-tenant objects such as raw knowledge uploads.
func ValidateKBScopedStoragePath(filePath string, tenantID uint64) error {
if err := ValidateStoragePathTenant(filePath, tenantID); err != nil {
return err
}
if !storagePathHasExportsScope(filePath, tenantID) {
return fmt.Errorf("storage path is outside KB-scoped exports namespace")View on GitHub (pinned to 988cbb0330)
Solutions
- Re-upload or copy the object into the tenant-scoped layout /storage/<tenantID>/... using the normal upload API
- Fix the code/fixture constructing the path so it includes the tenant ID segment (use the shared path-builder helper)
- Check ParseTenantIDFromStoragePath's expected format and confirm your path matches (delimiters, numeric segment position)
- For legacy data, run a one-time migration rewriting object keys to include the tenant segment
Example fix
// before
key := fmt.Sprintf("uploads/%s", filename)
// after
key := fmt.Sprintf("storage/%d/uploads/%s", tenantID, filename) Defensive patterns
Strategy: validation
Validate before calling
func pathHasTenantSegment(p string) bool {
return utils.ParseTenantIDFromStoragePath(p) != 0
}
// guard before requesting presigned access
if !pathHasTenantSegment(storageKey) {
return errors.New("storage key must be in /storage/<tenantID>/... layout")
} Type guard
func isTenantScopedPath(p string) bool {
parts := strings.SplitN(strings.TrimPrefix(p, "/"), "/", 3)
if len(parts) < 2 || parts[0] != "storage" { return false }
_, err := strconv.ParseUint(parts[1], 10, 64)
return err == nil
} Try / catch
err := utils.ValidateStoragePathTenant(key, session.TenantID)
if err != nil {
if strings.Contains(err.Error(), "no tenant segment") {
return fmt.Errorf("object %q is not tenant-scoped; re-upload or migrate it", key)
}
return err
} Prevention
- Always build storage keys with the shared path-builder that injects the tenant segment
- Add a startup/CI check that test fixtures use tenant-scoped paths
- Write a migration for any pre-multitenancy objects still under flat keys
When it happens
Trigger: Calling ValidateStoragePathTenant (directly or via ValidateKBScopedStoragePath) with a legacy path written before tenant-scoped layout was introduced, a malformed path, or a path built manually without the tenant component.
Common situations: Migrating pre-multitenancy objects stored under flat keys; objects uploaded by scripts bypassing the normal upload API; misconfigured storage base path stripping the tenant prefix; test fixtures using dummy paths like 'file.pdf'.
Related errors
- storage path workspace mismatch
- rbac: resource not found
- rbac: ownership or role insufficient
- wiki page %s returned knowledge base %s while resolving allo
- failed to generate KS3 presigned URL: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/705b9647d27cfa3c.
Report an issue: GitHub.