Tencent/WeKnora · warning

rbac: resource not found

Error message

rbac: resource not found

What it means

ErrResourceNotFound is the sentinel returned by RBAC CreatorLookups when the targeted resource row does not exist or belongs to a different tenant. RequireOwnershipOrRole deliberately lets requests carrying this error proceed so the downstream handler can return its own 404 — a middleware 403 would mask genuine 'wrong URL' failures. Note the similarly-named datasource.ErrResourceNotFound ('resource not found in source system') used by connectors like Notion on HTTP 404; they are distinct sentinels.

Source

Thrown at internal/middleware/rbac.go:23

	"errors"
	"net/http"
	"sync"

	"github.com/Tencent/WeKnora/internal/config"
	"github.com/Tencent/WeKnora/internal/logger"
	"github.com/Tencent/WeKnora/internal/types"
	"github.com/gin-gonic/gin"
)

// ErrResourceNotFound is the sentinel a CreatorLookup returns when the
// :id on the request does not match any row the lookup can see (either
// the row is genuinely missing or its tenant doesn't match). When a
// lookup returns this error, RequireOwnershipOrRole intentionally lets
// the request proceed so the downstream handler can respond with its
// own 404 — middleware-level 403 would hide real "URL is wrong" failures
// behind a permissions error, which breaks client diagnostics and
// operator dashboards.
var ErrResourceNotFound = errors.New("rbac: resource not found")

// CreatorLookup resolves the creator user ID for the resource targeted
// by the current request, based on whatever is on the gin.Context (URL
// params, query, body). Implementations live next to the handlers they
// guard, e.g. handler.kbCreatorLookup(c) reads ":id" and returns
// KnowledgeBase.CreatorID.
//
// Return value contract:
//   - (creatorID, nil) where creatorID != ""  -> the resource has a
//     recorded owner; ownership match grants access.
//   - ("", nil)                                -> "tenant-owned": no
//     human creator was recorded (legacy row or built-in resource);
//     only callers whose role meets the bar may proceed.
//   - ("", ErrResourceNotFound)                -> the :id does not
//     resolve to any row visible to this caller's tenant. Middleware
//     proceeds to the handler so the handler can return 404 instead
//     of masking it as 403.
//   - ("", other error)                        -> transient or

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Verify the resource ID in the request URL is correct and still exists (query the DB or list endpoint).
  2. Confirm the request is made against the correct tenant/workspace — cross-tenant lookups produce the same sentinel.
  3. If deleted resources should 404 explicitly, ensure the downstream handler maps this case to a NotFoundError response (it will, since middleware lets it through).
  4. Use errors.Is(err, middleware.ErrResourceNotFound) to distinguish not-found from ErrOwnershipForbidden when handling.

Example fix

// before: treating any eval error as forbidden
if evalErr != nil {
    return errors.NewForbiddenError("no permission")
}
// after: distinguish not-found from forbidden
if goerrors.Is(evalErr, middleware.ErrResourceNotFound) {
    return errors.NewNotFoundError("knowledge base not found")
}
if goerrors.Is(evalErr, middleware.ErrOwnershipForbidden) {
    return errors.NewForbiddenError("No permission to operate on this knowledge base")
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before mutating, confirm the resource exists in your tenant
kb, err := client.GetKnowledgeBase(ctx, id)
if errors.Is(err, ErrNotFound) || kb == nil {
    return fmt.Errorf("knowledge base %s does not exist in this tenant", id)
}

Type guard

func isRBACNotFound(err error) bool {
    return errors.Is(err, middleware.ErrResourceNotFound)
}
// note: do not confuse with datasource.ErrResourceNotFound (connector 404s)

Try / catch

if err != nil {
    switch {
    case errors.Is(err, middleware.ErrResourceNotFound):
        // let the handler 404 — the URL/resource itself is wrong
        return errors.NewNotFoundError("not found")
    case errors.Is(err, middleware.ErrOwnershipForbidden):
        return errors.NewForbiddenError("no permission")
    }
    return err
}

Prevention

When it happens

Trigger: A CreatorLookup (KBCreatorLookup, KBCreatorLookupFromKbIDParam, AgentCreatorLookup) queries the DB for the resource named by the URL param and finds no row (nonexistent ID) or a row with a mismatched tenant ID. Also returned by doRequest on 404 paths.

Common situations: Client requests a deleted or never-existing knowledge base/agent ID; ID from another tenant; stale links cached after resource deletion; Notion connector fetching a page that was deleted in the source system (datasource variant).

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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