Skip to content

File Search worker finds no work when the database user has neither superuser nor BYPASSRLS #1418

Closed

Description

@helpcodeai

Problem

The file_* tables use FORCE ROW LEVEL SECURITY; tenant queries go through FileSearchRepository.withTenant (app.current_org, optionally SET LOCAL ROLE amcp_files). The worker's cross-workspace maintenance queries do not set app.current_org:

They rely on the application user bypassing RLS: superuser, which is the case on AnythingMCP Cloud and in the default Docker Compose setup.

On a self-hosted install whose application user is neither superuser nor BYPASSRLS (some managed PostgreSQL offerings), those queries see no rows. Syncs then never start, and expired share files are not swept. Nothing errors.

Proposal

  • A new migration extends the org_isolation policies with an explicit maintenance switch, for example OR current_setting('app.files_maintenance', true) = 'on'.
  • The worker's cross-workspace queries run inside a transaction with set_config('app.files_maintenance', 'on', true).
  • Add an integration test as a non-superuser role, like the one used for Add File Search and File Share (#1379) #1395's CREATEROLE check.

Found while hardening File Search on 2026-11-01. Relates to #1379, #1380, #1383.

Activity

  1. aj-codespy commented on Nov 6, 2026

    @aj-codespy
    Contributor

    Hi, I'd like to take this one (I did the SonarQube adapter in #1317 and the satellites docs in #1276). I'd make the worker's cross-workspace maintenance queries work without superuser or BYPASSRLS, and add a test that runs the worker as a non-superuser role. Could I have it?

  2. helpcodeai commented on Nov 6, 2026

    @helpcodeai
    ContributorAuthor

    Thanks for offering, @aj-codespy, and for the SonarQube adapter and the satellites docs.

    We'll keep this one in house, though. The fix changes the row-level security policies that keep workspaces apart, and the worker's scheduler lives under ee/, which is under our commercial license and not open to outside contributions. Both are things we want to change and test ourselves.

    If you'd like another one, these adapter requests are free and fit your SonarQube experience:

    Pick one and comment there, and I'll assign it to you.

  3. helpcodeai commented on Nov 6, 2026

    @helpcodeai
    ContributorAuthor

    Plan update after auditing every File Search query that runs outside withTenant.

    No maintenance switch in the RLS policies. An app.files_maintenance setting in org_isolation could be set by any code on the application connection, and a single bug would then open every workspace at once, document contents included. Instead, maintenance runs one workspace at a time inside withTenant, the same way tenant queries do, and the policies stay unchanged.

    Affected code (worse than the issue lists):

    Where What fails without superuser or BYPASSRLS
    scheduler claim, renew, release and stop no sync starts; leases are never renewed or released
    scheduler pickScans() setup scans never run
    scheduler sweep (drafts, scan samples, licence-pause cleanup) cleanups never run
    closeInterruptedRuns() runs stay "running"
    FileShareService.sweep() expired share files are never deleted
    share byToken() and openSigned() public share links answer "not found"
    FileSearchEngine.scopeFor() Enterprise folder rules are invisible, so deny rules are ignored (fails open)

    Plan

    1. Workspace list. SELECT DISTINCT organization_id FROM connectors WHERE type = 'FILE_SEARCH'. connectors has no RLS.

    2. Scheduler claim.

      • A candidate pass per workspace inside withTenant.
      • A global ordering by next_sync_at.
      • Per-workspace FOR UPDATE SKIP LOCKED claims.

      This keeps today's rules: most overdue first, never two of one workspace, several workers safe.

    3. Leases. Renew, release and stop use the claimed organizationId. Scans, sweeps and closeInterruptedRuns() also run per workspace.

    4. Folder rules. scopeFor() reads them inside withTenant and fails closed.

    5. Share links. One additive SELECT-only policy on file_shares, matching token_hash = current_setting('app.share_token_hash', true). It only reveals the row to someone who already holds its secret token. The signed download carries the workspace id inside its HMAC.

    6. Startup logging. The worker logs which RLS mode it runs in, and warns when File Search connectors exist but it sees no source.

    Tests. A new integration spec runs in CI as a NOSUPERUSER NOBYPASSRLS role. It covers:

    • claim, scans, sweeps and interrupted runs;
    • share links;
    • folder rules;
    • cross-workspace isolation, including a wrong token.

    A second run as superuser shows unchanged behaviour.

    Cloud and self-hosted. No visible change on Cloud. On managed PostgreSQL, File Search works and folder rules apply again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions