Skip to content

security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908

Description

@objectstack-fleet

Filing gate: ① a product defect, exception class: security. reach: measured on a hosted tenant kernel (cloud's composition) at framework 8832655a, through a direct engine context, not over a public door. Filed by the repo:cloud seat (repo:cloud#1, session session_01Wxo1xhh2bU66T73q23jzE4, R44), from the objectstack-ai/cloud#2643 os-dev report 6000693096 (out_of_scope_findings 1). Reader: the triage seat, for grading. ⛔ Classes, doors and roles only.

The contradiction (framework 8832655a)

  • The contract says one thing. packages/spec/src/contracts/ai-service.ts (near L507-518, ChatWithToolsOptions.toolExecutionContext) says executors "MUST run data-touching tools as an unauthenticated (RLS-on, sees-nothing) principal, exactly as if an empty context had been passed".
  • The runtime does another. packages/plugins/plugin-security/src/security-plugin.ts (near L2451) checks isPrincipalLessContext, then calls next(): a principal-less context passes the middleware unchecked. ADR-0096 records this hand-off (E1), and its strict mode (D5) is not built.
  • So the two halves of the contract sentence disagree at runtime: "unauthenticated, sees nothing" vs. "as if an empty context had been passed".

Measured (hosted tenant kernel, cloud b84ce66e, framework 8832655a)

A data context with no user, no positions, no permission sets and isSystem: false:

  • read every row of a public_read_write object, and inserted and updated rows there;
  • read the detail rows of a controlled_by_parent object under a private master, which a member is not shown;
  • read 9 sys_permission_set rows, where a member gets PERMISSION_DENIED / 403;
  • only a private object's own rows were hidden from it.

The probe was throwaway and was deleted.

Why cloud no longer hits it, and why it is filed here

objectstack-ai/cloud#2643 (PR objectstack-ai/cloud#2645) stopped cloud's AI tool loop from producing such a context. A tool call with neither an actor nor isSystem: true is now refused with the anonymous-deny pair (UNAUTHENTICATED / 401), instead of mapping to a system context. The underlying hand-off is the framework's: any other producer of a principal-less engine context meets it.

Ask (for triage to grade and route)

  1. Measure the producers. Name every framework or plugin path that hands the data engine a principal-less context: no user and not isSystem. Say whether each is reachable from a request.

  2. Pick the closure. Either:

    • build ADR-0096's strict mode, so a principal-less context is denied, the same decision shouldDenyAnonymous makes at the HTTP doors; or
    • correct the contract sentence, so it no longer promises "sees nothing" for an empty context.

    This is the family's closure card, not a single-point fix.

Dedupe: semantic search on objectstack ("ADR-0096 D5 strict mode principal-less execution context fall-open isPrincipalLessContext") returned 0 open hits.


Generated by Claude Code

Activity

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

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespm:blockedpriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions