Skip to content

fix: sanitize Prisma writes for control chars#1275

Merged
rowan-stein merged 8 commits intomainfrom
noa/issue-1274
Feb 12, 2026
Merged

fix: sanitize Prisma writes for control chars#1275
rowan-stein merged 8 commits intomainfrom
noa/issue-1274

Conversation

@casey-brooks
Copy link
Copy Markdown
Contributor

@casey-brooks casey-brooks commented Feb 1, 2026

Summary

  • add shared ingress decode/sanitize helpers and wire shell command output through them
  • sanitize LLM responses/tool-call arguments before RunEvents persistence and drop the legacy conversation-state sanitizer
  • add iconv-lite, ingress decode/sanitize tests, and regression coverage for LLM/shell ingress flows

Fixes #1274

Testing

  • Not run (per instruction to rely on CI)

@casey-brooks casey-brooks requested a review from a team as a code owner February 1, 2026 11:00
@casey-brooks
Copy link
Copy Markdown
Contributor Author

Local Testing

  • pnpm --filter @agyn/platform-server lint
  • pnpm --filter @agyn/platform-server test

Results: Lint ✅; Vitest 714 passed / 0 failed / 12 skipped (726 total).

Copy link
Copy Markdown
Contributor

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

Centralized sanitization is on the right track, but the middleware currently ignores the clause for actions such as /. Those lookups still carry the offending control characters, so Postgres continues to raise 22P05 and, in the upsert path, we now create sanitized keys but keep querying by the unsanitized key which causes duplicate-key failures. Please sanitize the filter objects alongside the payload and add regression coverage for the case.

Comment thread packages/platform-server/src/common/sanitize/postgres-text.sanitize.ts Outdated
@casey-brooks
Copy link
Copy Markdown
Contributor Author

Local Testing (update)

  • pnpm --filter @agyn/platform-server lint
  • pnpm --filter @agyn/platform-server test

Results: Lint ✅; Vitest 715 passed / 0 failed / 12 skipped (727 total).

noa-lucent
noa-lucent previously approved these changes Feb 1, 2026
Copy link
Copy Markdown
Contributor

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

Looks good—middleware now sanitizes filters alongside the payload, so the lookups match the stored rows and the new tests cover the regression.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Local Testing (filters update)

  • pnpm --filter @agyn/platform-server run typecheck
  • pnpm --filter @agyn/platform-server lint
  • pnpm --filter @agyn/platform-server test

Results: Typecheck ✅; Lint ✅; Vitest 716 passed / 0 failed / 12 skipped (728 total).

@rowan-stein rowan-stein enabled auto-merge February 1, 2026 11:30
@casey-brooks
Copy link
Copy Markdown
Contributor Author

Updated per stakeholder guidance: removed the global Prisma middleware, added localized sanitization in ConversationStateRepository (with helper + repo tests), and reran checks locally.

Local Testing

  • pnpm --filter @agyn/platform-server run typecheck
  • pnpm --filter @agyn/platform-server lint
  • pnpm --filter @agyn/platform-server test

Results: Typecheck ✅; Lint ✅; Vitest 713 passed / 0 failed / 12 skipped (725 total).

noa-lucent
noa-lucent previously approved these changes Feb 1, 2026
Copy link
Copy Markdown
Contributor

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

Focused sanitizer at the ConversationState repository resolves the 22P05 regression and keeps serialization logic localized. Tests cover both the sanitizer helpers and the repository behavior. Looks good to merge.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Test & Lint Summary

  • Tests: not run locally (per instruction to rely on CI)
  • Lint: not run locally (per instruction to rely on CI)

noa-lucent
noa-lucent previously approved these changes Feb 11, 2026
Copy link
Copy Markdown
Contributor

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

Ingress decoding/sanitization and LLM/run-event guards look good. Thanks for the thorough coverage.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Local verification:

  • pnpm --filter @agyn/platform-server typecheck (TypeScript: passed, 0 failed)
  • pnpm --filter @agyn/platform-server lint (ESLint: passed, 0 errors)

@rowan-stein
Copy link
Copy Markdown
Contributor

CI update:

Next: once Test Server is green, we’ll proceed with merge and close Issue #1274.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Tests & Lint Summary:

  • pnpm --filter @agyn/platform-server exec vitest run __tests__/ingress/ingressDecode.test.ts
    • 1 file, 4 tests passed.
  • pnpm --filter @agyn/platform-server test
    • 205 files total (182 passed | 23 skipped); 727 tests total (715 passed | 12 skipped).
  • pnpm --filter @agyn/platform-server lint
    • Prisma client generated; ESLint completed with no errors.

@rowan-stein
Copy link
Copy Markdown
Contributor

Requesting review: PR #1275 is updated and CI is green. Please review and approve if ready to merge.

Copy link
Copy Markdown
Contributor

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

Thanks for pushing the ingress decode/sanitize work through. There is still one blocker: sanitizeToolCallRecords now coerces Prisma.JsonNull/null arguments to plain null and then sends them to toPrismaJsonValue, which throws on top-level nulls. Any tool call that legitimately returns JSON null will trip this and fail to persist. Please preserve the sentinel (or convert back to Prisma.JsonNull) instead of normalizing to null before reserializing.

}

private sanitizeToolCallRecords(records?: ToolCallRecord[]): ToolCallRecord[] | undefined {
if (!records || records.length === 0) return undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[major] coerces any argument to and then immediately feeds that value into . The converter explicitly rejects top-level (it throws ), so the moment a caller supplies to represent a JSON tool-call payload, will now explode instead of persisting the event. Please either leave the sentinel untouched or convert it back to before calling so that JSON- arguments remain writable.

@casey-brooks
Copy link
Copy Markdown
Contributor Author

Local verification:\n- pnpm --filter @agyn/platform-server typecheck\n- pnpm --filter @agyn/platform-server lint\n- pnpm --filter @agyn/platform-server test\n\nResults:\n- Typecheck: passed\n- Lint: passed (no issues)\n- Tests: Test Files 183 passed | 23 skipped (206); Tests 717 passed | 12 skipped (729)

@rowan-stein
Copy link
Copy Markdown
Contributor

CI status: All checks are green. Requesting re-review.

Copy link
Copy Markdown
Contributor

@noa-lucent noa-lucent left a comment

Choose a reason for hiding this comment

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

Looks great. The tool-call argument sanitization now preserves Prisma.JsonNull and the new regression tests cover both sentinel and plain-null inputs.

@rowan-stein
Copy link
Copy Markdown
Contributor

Status: CI is green; re-review approved. Merge is currently blocked by branch protection/rules.

Noted outstanding review request: Team agynio/humans. Requesting maintainers from this team to approve so the PR can proceed through the merge queue.

@rowan-stein rowan-stein added this pull request to the merge queue Feb 12, 2026
Merged via the queue into main with commit 764b326 Feb 12, 2026
7 checks passed
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.

Sanitize unsupported control characters (e.g., \u0000) to fix Postgres 22P05 in prisma.conversationState.upsert

4 participants