FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

Description

@os-warren

Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

What PR #7229 fixed, and where it stopped

objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

Measured

siteshapecheckField in file
packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

⚠️ The first three are the sharpest, and it is not an edge case

buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

Severity — precise rather than alarming

Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

The ordering constraint, carried forward

⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

Not measured

Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

      Description

      @os-warren

      Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

      Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

      What PR #7229 fixed, and where it stopped

      objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

      buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

      Measured

      siteshapecheckField in file
      packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
      packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
      packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
      packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
      packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

      ⚠️ The first three are the sharpest, and it is not an edge case

      buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

      So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

      This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

      Severity — precise rather than alarming

      Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

      FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

      It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

      The ordering constraint, carried forward

      ⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

      Not measured

      Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

      Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

          Description

          @os-warren

          Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

          Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

          What PR #7229 fixed, and where it stopped

          objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

          buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

          Measured

          siteshapecheckField in file
          packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
          packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
          packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
          packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
          packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

          ⚠️ The first three are the sharpest, and it is not an edge case

          buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

          So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

          This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

          Severity — precise rather than alarming

          Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

          FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

          It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

          The ordering constraint, carried forward

          ⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

          Not measured

          Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

          Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

              Description

              @os-warren

              Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

              Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

              What PR #7229 fixed, and where it stopped

              objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

              buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

              Measured

              siteshapecheckField in file
              packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
              packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
              packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
              packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
              packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

              ⚠️ The first three are the sharpest, and it is not an edge case

              buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

              So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

              This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

              Severity — precise rather than alarming

              Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

              FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

              It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

              The ordering constraint, carried forward

              ⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

              Not measured

              Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

              Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

                  Description

                  @os-warren

                  Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

                  Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

                  What PR #7229 fixed, and where it stopped

                  objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

                  buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

                  Measured

                  siteshapecheckField in file
                  packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
                  packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
                  packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
                  packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
                  packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

                  ⚠️ The first three are the sharpest, and it is not an edge case

                  buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

                  So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

                  This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

                  Severity — precise rather than alarming

                  Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

                  FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

                  It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

                  The ordering constraint, carried forward

                  ⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

                  Not measured

                  Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

                  Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

                      Description

                      @os-warren

                      Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

                      Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

                      What PR #7229 fixed, and where it stopped

                      objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

                      buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

                      Measured

                      siteshapecheckField in file
                      packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
                      packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
                      packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
                      packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
                      packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

                      ⚠️ The first three are the sharpest, and it is not an edge case

                      buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

                      So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

                      This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

                      Severity — precise rather than alarming

                      Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

                      FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

                      It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

                      The ordering constraint, carried forward

                      ⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

                      Not measured

                      Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

                      Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

                          Description

                          @os-warren

                          Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

                          Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

                          What PR #7229 fixed, and where it stopped

                          objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

                          buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

                          Measured

                          siteshapecheckField in file
                          packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
                          packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
                          packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
                          packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
                          packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

                          ⚠️ The first three are the sharpest, and it is not an edge case

                          buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

                          So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

                          This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

                          Severity — precise rather than alarming

                          Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

                          FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

                          It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

                          The ordering constraint, carried forward

                          ⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

                          Not measured

                          Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

                          Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              FLS-gate $expand at the remaining five build sites — calendar, gantt and record-detail expand EVERY declared relation with no column list at all #7230

                              Description

                              @os-warren

                              Filed by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM) on behalf of the #7215 lane, which measured all of this but could not file it: its duplicate-check search hit the API rate limit and repo-scoped REST answers 403 from that container, so there was no channel to check for duplicates and it correctly declined to file blind. Filed unassigned.

                              Duplicate check run by this seat before filing, with a control that fires: the query returned 3 on-topic issues including #7216 (the sibling $select gap) and none matching this. A non-empty on-topic hit set makes the absence a reading rather than a broken query.

                              What PR #7229 fixed, and where it stopped

                              objectui#7215 / PR #7229 FLS-gated $expand at the two projection sites in its scope — plugin-grid/src/ObjectGrid.tsx and plugin-list/src/ListView.tsx — by gating the output of buildExpandFields.

                              buildExpandFields is called from five more places, none of them gated, none in that card's scope, none touched by it.

                              Measured

                              siteshapecheckField in file
                              packages/plugin-calendar/src/ObjectCalendar.tsx:350buildExpandFields(objectSchema?.fields)no column list0
                              packages/plugin-gantt/src/ObjectGantt.tsx:674same, no column list0
                              packages/app-shell/src/views/RecordDetailView.tsx:402same, no column list0
                              packages/plugin-detail/src/DetailView.tsx:526passes a column list, ungated1, at :249 — the render half, not the projection
                              packages/plugin-dashboard/src/ObjectDataTable.tsx:680builds its own $expand whitelist via computeLookupExpand0

                              ⚠️ The first three are the sharpest, and it is not an edge case

                              buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object — denied ones included.

                              So calendar, gantt and record-detail do not merely fail to filter a column list; they have no column list, and therefore expand the maximum possible set by default. That is the ordinary configuration of those surfaces, not a corner of it.

                              This is also exactly why PR #7229 put its gate on the helper's output rather than its input, and that reasoning transfers unchanged: gating an input that is undefined is impossible, and gating an empty one widens the result. Whoever takes this card should read #7229's implementation first — the shape is already settled and pinned by tests there.

                              Severity — precise rather than alarming

                              Same grading as objectui#6898 and #7215: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The #7215 lane established the mechanism by reading it rather than assuming:

                              FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

                              It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

                              The ordering constraint, carried forward

                              ⚠️checkField answers false for an undeclared key, so a naive gate also drops derived and computed fields. Gating buildExpandFields's output avoids this structurally — the helper already returns a subset of the object's declared reference-bearing fields, so every name judged is declared and the trap is unreachable. Do not re-derive this; copy #7229's shape.

                              Not measured

                              Whether any of these five surfaces is reachable by a principal with a denied lookup in a shipped configuration. #7215 established that for its own two sites; these five were measured as code shape, not as a reproduction. The first task on this card is a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

                              Related: objectui#7215 / PR #7229 (the two sites already fixed; read its implementation first) · objectui#6898 (the original $select gate — note #7229 found it had a live bypass through the expand-root union) · objectui#7216 (the sibling gap: ListView's speculative view bindings reach $select known-field-checked but not FLS-checked).

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:queuepriority:p2

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions