Skip to content

hardening(p7): P0 sweep 2026-04-23 — sanitizer parity, ConfigureAwait, null-guards, ProviderName + 0.16.1#95

Merged
cyberprophet merged 2 commits intomainfrom
hardening/p0-sweep-2026-04-23
Apr 23, 2026
Merged

hardening(p7): P0 sweep 2026-04-23 — sanitizer parity, ConfigureAwait, null-guards, ProviderName + 0.16.1#95
cyberprophet merged 2 commits intomainfrom
hardening/p0-sweep-2026-04-23

Conversation

@cyberprophet
Copy link
Copy Markdown
Owner

Summary

Test plan

  • Gemini prompt sanitizer parity tests (ExtractProductInfo + AnalyzeReferenceLink — delimiter injection, angle-bracket escaping)
  • GenerateTitleAsync null/empty/whitespace throws ArgumentException on both providers (6 theory cases each)
  • ApiUsageEvent ProviderName propagation (groq, minimax, custom)
  • dotnet build -c Release clean with CA2007 enforcing (0 errors)
  • dotnet test green: 708 total (658 pre-existing + 50 new)

Closes #92, closes #93. Absorbs P7-04 + P7-07 from #94 (remaining sub-items tracked separately).

Post-merge: publish ShareInvest.Agency.0.16.1.nupkg to nuget.org, then bump Models.csproj in P5 to <PackageReference Include="ShareInvest.Agency" Version="0.16.1" /> and redeploy P5.

🤖 Generated with Claude Code

…, null-guards, ProviderName; bump to 0.16.1

Fix #92: GeminiProvider.ExtractProductInfoAsync and AnalyzeReferenceLinkAsync now
wrap all user-controlled text (d.Id, d.Text, url, html, context fields) with
PromptSanitizer.EscapeIdentifierForPrompt / EscapeForPrompt, matching the existing
GptService path (S-12 prompt-injection defence parity).

Fix #93: ConfigureAwait(false) applied to every await expression across all 12
library source files (GptService.*, GeminiProvider, WebTools, FallbackSearchProvider).
CA2007 promoted to error via .editorconfig + AnalysisLevel=latest-Recommended in
Agency.csproj to prevent regression.

P7-04 (#94): ArgumentException.ThrowIfNullOrWhiteSpace(conversationText) added at
the top of GenerateTitleAsync on both GptService and GeminiProvider, matching the
existing guard on systemPrompt.

P7-07 (#94): ApiUsageEvent call sites in GptService.ReferenceLink (line 87) and
GptService.StudioMint (line 120) changed from hardcoded "openai" to this.ProviderName
so custom-endpoint providers (Groq, MiniMax, etc.) report correctly.

Version bump: 0.16.0 → 0.16.1.

Tests: 708 green (658 pre-existing + 50 new from this branch).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cyberprophet
Copy link
Copy Markdown
Owner Author

@codex review

@cyberchacha @MintSurf — friendly ping, this PR has been awaiting review for ~30 min. Summary:

708 tests pass, dotnet build -c Release 0 errors.

Copy link
Copy Markdown

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

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: f5458d1a8c

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


var documentBlock = string.Join("\n\n", documents.Select(d =>
$"--- DOCUMENT: {d.Id} ---\n{d.Text}\n--- END ---"));
$"--- DOCUMENT: {PromptSanitizer.EscapeIdentifierForPrompt(d.Id)} ---\n{PromptSanitizer.EscapeForPrompt(d.Text)}\n--- END ---"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep Gemini document IDs canonical for provenance matching

Escaping d.Id here can change canonical IDs (for example & to &amp;), but TryParseProductInfo later compares returned SourceDocuments to the original raw IDs via knownIds.Contains(...) without HTML-decoding. In calls where document IDs are URLs or other strings containing escapable characters, Gemini can legitimately echo the escaped ID it was shown, causing the match to fail and dropping provenance for valid extraction results. Please preserve round-trippable IDs (or decode before validation) so source attribution is not lost.

Useful? React with 👍 / 👎.

@cyberprophet
Copy link
Copy Markdown
Owner Author

Thanks @chatgpt-codex-connector. Declining the change to keep OpenAI ↔ Gemini parity:

The exact same PromptSanitizer.EscapeIdentifierForPrompt(doc.Id) call has been the OpenAI path's behavior for GptService.ProductInfo.cs:84 since Intent 043 (Agency 0.14.0, 2026-04-17). This PR brings the Gemini path into line with it, not past it.

On the round-trip concern: PromptSanitizer.EscapeIdentifierForPrompt only escapes <, >, &, ". In this codebase, doc.Id flows in from PickFile.Id (P5) which is a Guid — punctuation-free by construction, so the escape is a no-op in practice. The helper's XML docstring explicitly acknowledges this ("All other characters — including the document id's original casing and punctuation — pass through unchanged so knownIds.Contains(id) still succeeds on the parsed output").

If IDs ever did contain HTML-special characters, the fix belongs in PromptSanitizer itself (either decode on round-trip, or switch to a URL-safe delimiter) — which would benefit both providers and is out of scope for this PR. Divergence between the two provider paths would be strictly worse: either (a) Gemini opens a prompt-injection hole that OpenAI closed, or (b) the two providers accept different ID shapes, making provider switching silently brittle.

@chatgpt-codex-connector
Copy link
Copy Markdown

Summary

  • I reviewed the trigger and did not apply any code changes. The requested “decline” is consistent with current implementation: Gemini now uses PromptSanitizer.EscapeIdentifierForPrompt(d.Id) in ExtractProductInfoAsync, matching the OpenAI product-info path’s long-standing behavior. agency/Google/GeminiProvider.csL151-L166 agency/OpenAI/GptService.ProductInfo.csL79-L85
  • The sanitizer behavior also matches the rationale in code comments/docs: only <, >, &, and " are escaped, while other characters are preserved for ID matching semantics (knownIds.Contains(id)). agency/OpenAI/PromptSanitizer.csL65-L86
  • Per repo startup protocol, I checked branch/status and (correctly) skipped git pull origin main because the working branch is work, not main. AGENTS.mdL11-L14
  • I could not run the required P0-issue query because gh is not installed in this environment. AGENTS.mdL24-L27

No commit was created and no follow-up PR was opened, because there were no file changes to commit.

Checks

  • git -C /workspace/mint status --short --branch
  • sed -n '1,220p' /workspace/mint/README.md
  • sed -n '1,260p' /workspace/mint/CHANGELOG.md
  • ⚠️ gh issue list --repo cyberprophet/mint --state open --search '[P0]' --json number,title,body --limit 10 (failed: gh: command not found)
  • sed -n '130,230p' /workspace/mint/agency/Google/GeminiProvider.cs
  • sed -n '1,220p' /workspace/mint/agency/OpenAI/PromptSanitizer.cs
  • nl -ba /workspace/mint/agency/OpenAI/GptService.ProductInfo.cs | sed -n '60,110p'
  • git -C /workspace/mint status --short

View task →

Copy link
Copy Markdown
Collaborator

@MintSurf MintSurf left a comment

Choose a reason for hiding this comment

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

Review Summary

  • Outcome: comment
  • Summary: The hardening changes look directionally correct, but the PR bumps the NuGet version to 0.16.1 without the matching CHANGELOG entry that this repo treats as the authoritative consumer-facing release record. I also could not run the optional project checks here because dotnet is not installed in this environment, and there is no *.sln/Node/Flutter manifest matching the requested check gates.

Comment thread agency/Agency.csproj
<!-- NuGet Package -->
<PackageId>ShareInvest.Agency</PackageId>
<Version>0.16.0</Version>
<Version>0.16.1</Version>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P2 Badge Missing 0.16.1 changelog entry

This PR bumps the package version to 0.16.1, but CHANGELOG.md still tops out at 0.16.0. Per the repo instructions, every release needs a changelog entry and the latest entry is the authoritative source for NuGet consumers. Shipping the version bump without that release note leaves consumers and downstream repos without an accurate record of what 0.16.1 contains.

Useful? React with 👍 / 👎.

cyberchacha
cyberchacha previously approved these changes Apr 23, 2026
Copy link
Copy Markdown
Collaborator

@cyberchacha cyberchacha left a comment

Choose a reason for hiding this comment

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

Review Summary

  • Outcome: approve
  • Summary: Clean, well-scoped hardening PR. All four findings (sanitizer parity, ConfigureAwait, null-guards, ProviderName) are correctly implemented. Build passes with 0 errors and CA2007 enforcement active. All 708 tests green (including 50 new). Mechanical changes are consistent and complete across all call sites.

Comment thread agency/Agency.csproj

<!-- Analysis: CA2007 (ConfigureAwait) as error to prevent regression -->
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P3 Badge TreatWarningsAsErrors=false — verify intent

AnalysisLevel=latest-Recommended enables a broad set of analyzers. With TreatWarningsAsErrors=false, only CA2007 (via .editorconfig) is promoted to error — the remaining ~120 warnings (CA1848, CA1861, CA1305, etc.) stay as warnings. If that's intentional, this is fine. If the goal is to gradually tighten, consider adding a tracking issue to promote additional rules over time rather than leaving them as non-blocking.

Useful? React with 👍 / 👎.


var documentBlock = string.Join("\n\n", documents.Select(d =>
$"--- DOCUMENT: {d.Id} ---\n{d.Text}\n--- END ---"));
$"--- DOCUMENT: {PromptSanitizer.EscapeIdentifierForPrompt(d.Id)} ---\n{PromptSanitizer.EscapeForPrompt(d.Text)}\n--- END ---"));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P3 Badge Good: sanitizer parity now matches GptService path

Using EscapeIdentifierForPrompt for d.Id and EscapeForPrompt for d.Text correctly mirrors the defense-in-depth pattern from the OpenAI path. The <user_input> wrapping inside --- DOCUMENT --- delimiters is a clean layered approach.

Useful? React with 👍 / 👎.

Per repo convention every NuGet version bump must land with a matching
CHANGELOG section. Added a Keep-a-Changelog entry for 0.16.1 covering:

- Security: GeminiProvider PromptSanitizer parity (#92)
- Changed: ConfigureAwait(false) mechanical rollout + CA2007 error-level (#93)
- Fixed: GenerateTitleAsync null-guard (#94 P7-04) + ApiUsageEvent.ProviderName (#94 P7-07)
- Notes: P5 Models.csproj bump to 0.16.1 + redeploy required

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cyberprophet
Copy link
Copy Markdown
Owner Author

Addressed MintSurf P2 on f5458d1 in follow-up commit e59bf1a:

P2 — missing CHANGELOG 0.16.1 entry: added a Keep-a-Changelog section for 0.16.1 covering Security (#92 GeminiProvider sanitizer parity), Changed (#93 ConfigureAwait + CA2007 error), Fixed (#94 P7-04 null-guard + P7-07 ProviderName), and a Notes row flagging the required Models.csproj bump + P5 redeploy after nuget.org publish.

Also declined the Codex P2 (Gemini ID escape parity) inline above — divergence from the OpenAI path would strictly worsen prompt-injection defense; fix belongs in PromptSanitizer itself if anywhere, out of scope for this PR.

@MintSurf — could I get your approval once CHANGELOG looks good? main branch requires 2 approvals and I currently have 1 (cyberchacha APPROVED).

@cyberprophet cyberprophet merged commit 098e56e into main Apr 23, 2026
@cyberprophet cyberprophet deleted the hardening/p0-sweep-2026-04-23 branch April 23, 2026 23:20
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.

[P0] ConfigureAwait(false) absent across the public library [P0] GeminiProvider inserts user-supplied text into prompts without PromptSanitizer

3 participants