Skip to content

fix(core): refuse caller-supplied part columns of multi-column fields - #1769

Merged
borisno2 merged 4 commits into
mainfrom
claude/wonderful-feynman-x36pkd
Oct 7, 2026
Merged

borisno2 merged 4 commits into
mainfrom
claude/wonderful-feynman-x36pkd

Conversation

@borisno2

@borisno2 borisno2 commented Oct 7, 2026

Copy link
Copy Markdown
Member

Summary

  • filterWritableFields now throws a ValidationError when caller input (inputData) names a raw part column of a multi-column field (avatar_url, doc_filename, …). The error names the column and the owning field, and points at the logical field / context.unsafe.
  • Applies to every caller, including sudo(), on create and update.
  • Part columns produced by the field's own splitColumns still pass, so logical-key writes (and their validation and authorizeStoredMetadata) are unchanged.
  • Removed the obsolete Write pipeline silently strips fields denied by field access instead of erroring (Keystone threw) #568 pass-through branch and its rationale comment.

Test plan

  • Unit tests: caller-supplied part column throws (sudo/non-sudo, create/update); field-produced columns pass
  • pnpm vitest run src/access src/hooks src/context in core
  • pnpm lint, pnpm format

Closes #1645

🤖 Generated with Claude Code

https://claude.ai/code/session_01TwFY9j4BfPu3Kamfcq5hCx


Generated by Claude Code

@changeset-bot

changeset-bot Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7c15cdf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@opensaas/stack-core Patch
@opensaas/stack-auth Patch
@opensaas/stack-cli Patch
@opensaas/stack-rag Patch
@opensaas/stack-storage Patch
@opensaas/stack-tiptap Patch
@opensaas/stack-ui Patch
@opensaas/stack-storage-s3 Patch
@opensaas/stack-storage-vercel Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
stack-docs Ready Ready Preview Oct 7, 2026 10:51am UTC

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T08:12:35.155092Z c58cbac PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

borisno2 commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

Code review: PR #1769 (refuse caller-supplied part columns of multi-column fields, fixes #1645)

Reviewed at high effort. No blocking correctness bugs outside the first item below, but several changes are requested.

Requested changes

  1. Refusal runs too late (packages/core/src/access/field-access.ts, ~L391). The check is in Phase 5, after list resolveInput, field resolveInput and validate hooks have already seen the raw part-column key. A list-level resolveInput that spreads inputData into resolvedData copies the key into hook output. Hook output is trusted (ADR-0074), so the part column could still persist. Move the refusal to the start of the write pipeline, or also check resolvedData. Note that args.inputData ?? {} skips the check entirely when inputData is undefined (for example on some update paths).

  2. Sudo is now refused too. The removed sudo test covered context.sudo().db.Profile.create({ data: { avatar_filename: ..., avatar_filesize: ... } }), which used to work and now throws a ValidationError. The error says to use context.unsafe, but that skips hooks and Field Visibility. The rag <field>Metadata column is also covered because getColumnNames returns it. Please confirm this is intended, check in-repo sudo callers (rag, storage, seeds), and call it out in the changeset.

  3. False positives on key ownership. The refusal treats any key found in splitColumnOwners as a part column. If a list has a plain text() field media_url alongside an image() field media whose columns include media_url, writing media_url gets a misleading "storage column of media" error. splitColumnOwners.set also silently overwrites an earlier owner when two fields claim one column. Skip keys present in fieldConfigs, and ideally fail generation on a duplicate column claim.

Cleanups

  1. splitColumnOwners still stores access, but nothing reads it. Use a Map<string,string> or a Set. The owningField and callerFields mapping is also dead for caller input now that part columns throw first, and the surrounding code reads as if the owning field's access still gates them. A later maintainer could "restore" that gate.

  2. Stale docblock in packages/core/src/hooks/index.ts (~L510). The splitMultiColumnFields comment still says filterWritableFields's undeclared-key reject cannot enforce this field's write access and that the gate is enforced "HERE". That no longer matches the code. Update or delete it (see the Comments section of CLAUDE.md).

Tests

  1. packages/core/src/access/field-access.test.ts (~L553) only calls the filter directly and asserts throw and pass-through. Please add pipeline-level tests for these cases:

    • a hook echoing a part column into resolvedData
    • an update with undefined inputData
    • a sudo write
    • a field that is both declared and listed in getColumnNames

    The deleted access-denied and sudo tests documented real behaviour. Replace them rather than dropping them.


Generated by Claude Code

borisno2 commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

CI test is red on pnpm check:adr-duplicates, which is not caused by this PR. main itself has two files numbered 0074 (0074-a-throwing-after-transaction-hook-is-reported-never-propagated.md, from #1768, and 0074-field-write-access-gates-caller-input-not-hook-output.md, from #1767). No fix exists yet. The patch is to renumber one of them to 0075 and update its references. I have not widened this PR to do it; the rest of the test job should be unaffected.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c58cbac9b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 484 to 485
if (splitColumnOwners.has(fieldName)) {
filtered[fieldName] = value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve access checks for hook-produced part columns

When a list-level resolveInput hook replaces a caller-supplied logical field with its physical columns (for example, removes media and returns media_url/media_size), callerFields still records media, while the keyed check above skips the logical-field access check. This unconditional pass-through then persists those columns without calling checkFieldAccess, so a non-sudo caller denied access to media can update it; the removed splitColumnOwner branch previously enforced access in this case. Keep the direct-raw-input refusal, but retain the owning-field access check for part columns that cannot be proven to have come from splitColumns.

AGENTS.md reference: AGENTS.md:L160-L168

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The access check for part columns is restored (a part column that isn't caller-supplied still checks the owning field's write access when the caller named that field), with a regression test for a hook swapping a denied logical key for its part columns. Direct caller-supplied part columns are still refused.


Generated by Claude Code

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🟢 Lines 94.52% (🎯 81%) 3798 / 4018
🟢 Statements 92.83% (🎯 76%) 4316 / 4649
🟢 Functions 96.08% (🎯 78%) 859 / 894
🟢 Branches 88.43% (🎯 71%) 2936 / 3320
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/core/src/access/field-access.ts 95.72% 97.19% 85.71% 96.26% 42, 72, 78, 321, 413
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 78.7% 244 / 310
🔵 Statements 78.43% 251 / 320
🔵 Functions 69.81% 74 / 106
🔵 Branches 67.51% 160 / 237
File CoverageNo changed files found.
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 82.18% 2044 / 2487
🔵 Statements 81.94% 2196 / 2680
🔵 Functions 87.28% 350 / 401
🔵 Branches 75.44% 1100 / 1458
File CoverageNo changed files found.
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 83.33% 405 / 486
🔵 Statements 82.19% 457 / 556
🔵 Functions 86.2% 100 / 116
🔵 Branches 78.28% 375 / 479
File CoverageNo changed files found.
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 96.97% 417 / 430
🔵 Statements 96.02% 459 / 478
🔵 Functions 98.36% 120 / 122
🔵 Branches 93.43% 427 / 457
File CoverageNo changed files found.
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 88.79% 634 / 714
🔵 Statements 88.19% 695 / 788
🔵 Functions 95.48% 127 / 133
🔵 Branches 85.03% 449 / 528
File CoverageNo changed files found.
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 47 / 47
🔵 Statements 100% 48 / 48
🔵 Functions 100% 10 / 10
🔵 Branches 96.87% 31 / 32
File CoverageNo changed files found.
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 74 / 74
🔵 Statements 100% 78 / 78
🔵 Functions 100% 16 / 16
🔵 Branches 96.55% 56 / 58
File CoverageNo changed files found.
Generated in workflow #2976 for commit 7c15cdf by the Vitest Coverage Report Action

@borisno2
borisno2 merged commit 32b9afa into main Oct 7, 2026
9 checks passed
@borisno2
borisno2 deleted the claude/wonderful-feynman-x36pkd branch October 7, 2026 11:22

This branch was successfully deployed

1 active deployment
Preview — 7c15cdf2 Deployed Oct 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-column storage fields: raw part columns bypass the logical field's validation and hooks

2 participants