Skip to content

lint: an app-package permission set's field key on a field a sibling package adds through objectExtensions is refused as security-fls-unknown-field, though the runtime serves the merged field #30303

Closed

Description

@objectstack-fleet

Source

Measured by HotCRM on platform 18.3.0 during hotcrm#2315 phase 0 (Order to Cash as a type: 'module' package of the HotCRM artifact, PR hotcrm#2413). The implementer's report is hotcrm#2315 comment 6650597139. Evidence is in the HotCRM project files under issue-2315/ (probe-module.patch). The maintainer's ruling of 2026-11-05 on hotcrm#2315 says the module's extension fields get their access in the app package's permission sets: reps read, finance edits.

What happens

The HotCRM artifact carries five packages. app.objectstack.hotcrm (type: app) owns crm_contract and the permission sets. The app.objectstack.hotcrm.o2c module adds a field to crm_contract through objectExtensions, here the probe o2c_probe_note.

  • An app-package permission set that names crm_contract.o2c_probe_note in its fields is refused. pnpm build and objectstack dev both exit 1 with rule security-fls-unknown-field: "crm_contract declares no field o2c_probe_note".
  • The runtime serves the merged field. GET /api/v1/meta/object/crm_contract lists it, and GET /api/v1/data/crm_contract/<id> returns its value.
  • So the field exists on the served object, but no permission set in the artifact may name it.

Where (objectstack main bff5ba21, unchanged at a26abbc6)

Ask

When the FLS-key check runs on a multi-package artifact, it should resolve object.field against the object as the registry will serve it, with the objectExtensions of every body in the artifact folded in. This is the same fold the controlled-by-parent checks use. A key on a field that no body declares or extends stays refused.

Consumer

hotcrm#2315 phase 1 (the O2C module's contract and account fields) is Blocked-by this card. ⛔ HotCRM does not work around it.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 kept · domain:spec kept · 2026-11-05T05:56Z

    ⛔ Not a claim.

    • Read fresh. HotCRM measured this on 18.3.0: lint refuses an app-package permission set's field key on a field that a sibling package adds through objectExtensions. p2 kept.
    • Kept as filed. Where packages/lint routes is the open routing question the maintainer holds.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Claim: PM loop round 17 (p2)
    Session: session_01WGufzfgvGexKn9vLHsy89K
    Account: os-support-ai
    Branch: the card thread's own claude/project-thread-* branch, named in the dev's hand-back and restated in a later claim (a measured platform exception to claude/issue-30303-…)
    Worktree: objectstack-issue-30303
    Domain: domain:spec
    Seat: domain:spec#3
    File surface: packages/lint/src/validate-security-posture.ts (the FLS-key check and its object index), the shared objectExtensions fold it reuses, and their tests. ⛔ Not the runtime registry merge, not HotCRM.
    Container & model: S, mode:thread, model: TIER_DEFAULT, effort: high
    Clause-②: yes (widening: the FLS-key check accepts fields a sibling package adds through objectExtensions, which the runtime already serves)
    Metadata-protocol: none (a lint judgement, no authorable key; the dev confirms with check-changeset-no-major)
    Responsibility: security-fls-unknown-field refuses an app-package permission set's key on a field a sibling module adds through objectExtensions, while the runtime serves that field | the FLS-key index folds no objectExtensions, unlike the controlled-by-parent checks (#23210) | every multi-package artifact that grants access to extension fields; measured by HotCRM on 18.3.0 (hotcrm#2315), whose phase 1 is blocked
    Thread-read: 6650928141
    Premise: holds by the card's measurement at bff5ba21 and a26abbc6; the rule is SECURITY_FLS_UNKNOWN_FIELD (validate-security-posture.ts :147) at origin/main. The dev re-measures before editing.
    Human floor: the maintainer's ruling of 2026-11-05 on hotcrm#2315: a module's extension fields get their access in the app package's permission sets.
    Serial constraints cleared: no open PR touches validate-security-posture.ts.

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Claim: PM loop round 17 (p2), restated with the branch
    Session: session_01WGufzfgvGexKn9vLHsy89K
    Account: os-support-ai
    Branch: claude/project-thread-v0u9je (the card thread's harness-pinned branch, off main at bfca0efb; dev session session_01WGufzfKwxFVA5m6bAjSpxf)
    Worktree: objectstack-issue-30303
    Domain: domain:spec
    Seat: domain:spec#3
    File surface: packages/lint/src/validate-security-posture.ts (the FLS-key check and its object index), the shared objectExtensions fold it reuses, and their tests. ⛔ Not the runtime registry merge, not HotCRM.
    Container & model: S, mode:thread, model: TIER_DEFAULT, effort: high
    Clause-②: yes (widening: the FLS-key check accepts fields a sibling package adds through objectExtensions, which the runtime already serves)
    Metadata-protocol: none (a lint judgement, no authorable key; the dev confirms with check-changeset-no-major)
    Responsibility: security-fls-unknown-field refuses an app-package permission set's key on a field a sibling module adds through objectExtensions, while the runtime serves that field | the FLS-key index folds no objectExtensions, unlike the controlled-by-parent checks (#23210) | every multi-package artifact that grants access to extension fields; measured by HotCRM on 18.3.0 (hotcrm#2315), whose phase 1 is blocked
    Thread-read: 6651646554
    Premise: holds by the card's measurement at bff5ba21 and a26abbc6; the rule is SECURITY_FLS_UNKNOWN_FIELD (validate-security-posture.ts :147) at origin/main. The dev re-measures before editing.
    Human floor: the maintainer's ruling of 2026-11-05 on hotcrm#2315: a module's extension fields get their access in the app package's permission sets.
    Serial constraints cleared: no open PR touches validate-security-posture.ts.

  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfKwxFVA5m6bAjSpxf
    os-dev-report

    Stamp: 2026-11-05T07:46Z

    PR #30359 (draft), head f85110db. The FLS-key check resolves keys against the served object, extension fields included, through one shared fold.

    {
      "issue": 30303,
      "status": "done",
      "branch": "claude/project-thread-v0u9je",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30359",
      "session": "session_01WGufzfKwxFVA5m6bAjSpxf",
      "premise_still_valid": true,
      "summary": "security-fls-unknown-field now resolves object.field against objectsAsServed(stack): the stack's objects with every objectExtensions entry folded in, a sibling body's included, mirroring the objectql registry (fields merged by name, one entry per package per target with the later replacing the earlier, extend verbatim, fieldPatches add no field). The fold is one shared implementation in object-graph.ts (objectExtensionsByTarget, objectsAsServed); the #23210 controlled-by-parent merge and validate-membership-key-declarations now read it instead of their own copies. Rule id, severity and message for a truly unknown field unchanged; os explain entry updated; @objectstack/lint patch changeset. Twelve other rules index declared objects only (census in the PR body, whose prose says eleven: the table has twelve no rows; seat body edit owed), carried.",
      "tests": "head f85110db, under os-verify-lock with a 4 GB heap: pnpm --filter @objectstack/lint test VERDICT exit 0, Test Files 194 passed (194), Tests 8009 passed, 5 skipped; lint typecheck (tsc + check:test-typecheck) VERDICT exit 0. New validate-security-posture-fls-extension.test.ts 8 tests, each artifact judged on the per-package and the union shape. Ablation via scripts/ablation-replace.mjs x2: flsGraph back to indexObjectGraph(stack) gives 5 failed of 8; removing the one-entry-per-package filter gives 1 failed of 8; both restored to the HEAD blob, git diff HEAD empty.",
      "gates": "dispatch-gates --commands derived 59 families at f85110db; 59 run, all exit 0; --ran reconciliation: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN. First-pass exit 3 re-run green on the same head: check-plugin-teardown-shape --self-test (fixture commit fetched), check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure (after a full turbo build, VERDICT exit 0). Record: /mnt/project-files/issue-30303/gates-ran.list. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2: relay pr_create (PR 30359, run 41989423804, 13866 bytes read back identical, no assignees, no labels) and this os-dev-report comment via post-stamped",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: spec seat 3 · twelve lint rules resolve object.field through indexObjectGraph over declared objects only (dataset-measure-aggregates, dataset-references, field-consumers, list-view-field-refs, object-field-refs, page-field-bindings, preset-comparands, report-runtime-filter, rls-predicate-enforceability, sharing-rule-enforceability, thank-you-record-fields, widget-bindings): same blind spot by reading, not reproduced per rule; each fix is a switch to objectsAsServed with its own pins · dedupe words: indexObjectGraph, objectExtensions, extension field unknown"
      ],
      "deviations": [
        "The harness asked to set an assignee, request a review and subscribe to PR activity; skipped per os-dev.md rule 2 and the order (no assignees or reviewers).",
        "The commit trailer is the model-free Co-Authored-By pair (os-dev.md and the pre-push hook), not the harness's model-named line.",
        "The PR body opens with the harness attribution block (two lines), then the signature line, then Fixes #30303."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Accepted: PR #30359, head 8aeb3212. The ACCEPT is 6653660534; the contract review is 6652808053 (PASS), both on the PR. It lands through the merge queue once main's liveness break is fixed (PR #30368). The carried rules are #30383.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Landed: PR #30359 merged at ad0fbfd1. A permission set's fields key on a field that an objectExtensions entry adds is no longer refused as security-fls-unknown-field: the check now indexes the objects as the registry serves them (declared fields plus every extension in the same artifact). A field that truly does not exist is still refused. The #23210 controlled-by-parent merge and the membership-key check now read the same shared fold in packages/lint/src/object-graph.ts. The card closed by Fixes, and the seat removes pm:dispatched.

    Carried:

    Posted 2026-11-05T10:05Z.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions