Skip to content

fix: sanitize user input in model prompts - #458

Open
thomasluizon wants to merge 7 commits into
mainfrom
fix/orb-93-sanitize-ai-input
Open

fix: sanitize user input in model prompts#458
thomasluizon wants to merge 7 commits into
mainfrom
fix/orb-93-sanitize-ai-input

Conversation

@thomasluizon

@thomasluizon thomasluizon commented Aug 7, 2026

Copy link
Copy Markdown
Owner

DEGRADED: same-vendor review

Linear: ORB-93

Summary

Sanitizes user controlled values before they enter the five model prompts named in the ticket. Notification fallback headings now use normalized and capped values, and proactive habit titles are quoted individually before joining so a title containing the separator stays one list item. Goal review sanitization preserves the complete aggregate instead of truncating later active goals.

Root cause

The services interpolated habit titles, descriptions, display names, tag names, and goal context directly even though PromptDataSanitizer already defines the repository boundary for model prompt data. The first goal review fix used SanitizeBlock's default 2,000 character cap on the completed aggregate, which could cut a record and omit every later goal.

Design choice

Goal context remains sanitized defensively inside AiGoalReviewService. The sanitizer receives the original context length as its maximum because its normalization only removes or replaces input characters and never expands the aggregate. This retains every goal record while preserving newline, whitespace, and control character neutralization. Sanitizing at construction would reverse the Application to Infrastructure dependency, and duplicating the sanitizer there would create a second prompt normalization policy.

The prompt capture tests inspect every string in the actual SDK request body and locate the prompt by a stable marker. They do not read an assumed SDK field or construct a successful external response fixture.

Impact

Quotes, newlines, control characters, separators, and overlong individual values can no longer break prompt boundaries in the named paths. Goal reviews no longer silently lose later records when their aggregate exceeds 2,000 characters. Ordinary notification headings remain unchanged.

Validation

  • focused goal context regression: passed
  • dotnet build Orbit.slnx: passed with 0 errors
  • Orbit.Infrastructure.Tests: passed, 2,077 tests
  • dotnet test: passed, 5,599 tests
  • diff scope: 8 files, 225 insertions, 15 deletions

@thomasluizon

Copy link
Copy Markdown
Owner Author

Approach for ORB-93:

  1. Update the five named services to sanitize every user controlled prompt value with the existing helper and established caps. Quoted titles use QuoteInline, single line display and fallback values use SanitizeInline, descriptions and goal context use SanitizeBlock, and proactive habit titles are quoted individually before joining.
  2. Keep goal context sanitization inside AiGoalReviewService because the caller is in Application and the sanitizer is owned by Infrastructure. Moving it into the caller would invert the dependency. Sanitizing both places would duplicate the transformation.
  3. Add red then green regression coverage at the completion client boundary. The two notification suites cover prompt escaping, fallback readability, separator handling, and truncation. One focused suite covers tag, habit setup, and goal review prompts so the change stays within the ticket's eight file cap.
  4. Run focused tests, compile, commit immediately, then run the solution build and full test suite before pushing the implementation.

Planned files are the five services named in the ticket, AiSlipAlertMessageServiceTests.cs, AiProactiveCheckinMessageServiceTests.cs, and one focused prompt sanitization test file. A non-interpolated prompt redesign and sanitizer changes remain out of scope.

@thomasluizon
thomasluizon marked this pull request as ready for review August 7, 2026 03:22

@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: ef51fcf2b8

ℹ️ 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 thread src/Orbit.Infrastructure/Services/AiGoalReviewService.cs Outdated
@thomasluizon

Copy link
Copy Markdown
Owner Author

CI follow-up: the first arch-map run treated capitalized payload words in the new tests as domain entity references. The test markers now avoid those false positives. A staged-tree regeneration confirmed both architecture.json and architecture.html are byte-for-byte unchanged before this push.

@thomasluizon

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: e427b499ea

ℹ️ 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".

@thomasluizon

Copy link
Copy Markdown
Owner Author

P1 follow-up approach:

  1. Remove the aggregate 2,000 character sanitizer from AiGoalReviewService so complete goal review context is passed through.
  2. Neutralize each user controlled goal title, unit, and linked habit title while BuildGoalsContext assembles records. Quoted inline fields cannot introduce record or prompt delimiters, and domain bounded values need no aggregate truncation.
  3. Replace the aggregate truncation expectation with a regression that builds enough goal records to exceed 2,000 characters, verifies every record including the last reaches the actual SDK request, and verifies injected line and quote delimiters are neutralized.

This changes the existing goal service, goal query builder, and existing prompt sanitization suite only. The branch remains within the eight file and 400 line limits.

@thomasluizon

Copy link
Copy Markdown
Owner Author

CI follow-up: the regression fixture initially used a capitalized payload marker that arch-map interpreted as a domain entity reference. The marker is now lowercase. Local regeneration left architecture.json and architecture.html byte-for-byte unchanged, and the Linux drift job passes at head 05f9d3f.

@thomasluizon

Copy link
Copy Markdown
Owner Author

DEGRADED: same-vendor review

Review verdict: 1 Blocking finding after round 1.

F1 High at src/Orbit.Infrastructure/Services/AiGoalReviewService.cs:23: aggregate post-composition sanitization preserves a standalone injected instruction and uses attacker-controlled length as its cap. Frozen for one fixer round.

Same-family bias is not eliminated and its magnitude is unmeasured.

@thomasluizon

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 3370228a39

ℹ️ 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".

@sonarqubecloud

sonarqubecloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

@thomasluizon

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 3370228a39

ℹ️ 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".

@thomasluizon

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 3370228a39

ℹ️ 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".

@thomasluizon

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 3370228a39

ℹ️ 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".

@thomasluizon

Copy link
Copy Markdown
Owner Author

DEGRADED: same-vendor review

Same-vendor bias is known to favor same-family output; its magnitude in this PR review is unmeasured.

Reviewed head: 3370228a392e4011f167a2bd2ce5040484bc2798a

Verdict: CLEAN

APPROVED BY PR-REVIEW: zero blocking findings

Findings: none.

Follow-ups: none.

Applicable dimensions reviewed with no findings: 1 Correctness, 2 Dead / stale code, 3 SOLID / clean architecture, 4 No-workaround / root cause, 5 Test quality, 6 Security, and 11 Backend hard rules.

N/A dimensions:

  • 7 Contract alignment and backward compatibility: no DTO, schema, endpoint, route, or mobile contract changed.
  • 8 Cross-platform parity: backend-only diff.
  • 9 i18n: no new user-facing copy or locale resources changed.
  • 10 Design: no app UI files changed.
  • 12 FEATURES.md gating: this is a backend security bugfix with no user-facing feature-surface change.

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.

1 participant