Fix the four medium findings from the 2026-08-25 security audit #16

Merged
hannah-vernon merged 1 commit from fix/security-audit-medium into dev 2026-08-25 19:01:21 -05:00

What does this PR do?

Remediates the four Medium findings from the 2026-08-25 security audit (codebase evaluated against all 25 applicable prompts from ai-security-audit at commit 5885e32; prompts 07/15/28 N/A - no installer, PowerShell scripts, or CI/CD in this repo. No Critical or High findings).

  1. Policy allowlist sender extraction (verified finding). DefaultPolicyEngine previously scraped sender=/from= markers out of free-form classification text (category, reasons, recommended action) - text that derives from attacker-influenceable mail content, so a crafted message could steer the sender-allowlist guardrail. The sender is now read exclusively from the stored MailMessageEvent''s structured From addresses (engine gains an IEventStore dependency); the free-form extraction helpers are deleted. A regression test proves text markers can no longer trigger the allowlist. Note: the guardrail now applies to every mail-message classification rather than only categories containing "sender" - strictly more protective of allowlisted senders.
  2. IMAP body fetch cap. Body parts larger than 1 MiB (server-reported BODYSTRUCTURE octets) are no longer downloaded; previously a hostile oversized message was fully materialized before the normalizer''s 256 KiB truncation. The body carries an explicit [BODY NOT FETCHED BY VIEGARD ...] marker so classifiers see the absence honestly.
  3. MDaemon tailer symlink rejection. Files that are reparse points, or whose canonical path escapes the configured log directory, are skipped with a warning - the tailer can no longer be steered into ingesting arbitrary readable files.
  4. Pinned package source. Repo-level nuget.config with <clear />, nuget.org only, and packageSourceMapping - restores are reproducible for every contributor and cannot be redirected by ambient machine-level feeds. Verified with dotnet restore --force.

TODO.md records the audit outcome and the deferred recommendations (revisit at Phase 9 hardening): audit-record exception text, LISTEN/NOTIFY wakeup spam (least-privilege per-instance DB roles arrive with the D-0025 satellite), secret string zeroization, in-memory audit ledger doc note. A follow-up PR covers the Low/Info batch (bounded tail reads, UID-wrap guard, log sanitization, stats cast, Hosting patch).

How was this tested?

  • dotnet build - 0 errors, 0 warnings
  • dotnet test - 271 passed, 9 skipped (live-PostgreSQL integration tests; local WSL Docker unavailable)
  • Four new policy-engine tests: structured allowlisted sender denies action, allowlisted domain denies action, text-marker injection cannot trigger the allowlist (regression), missing mail event cannot match
  • dotnet restore --force against the pinned feed restores all projects

Checklist

  • I have read the Contributing Guide
  • Changes are focused - one logical change per PR
  • Documentation updated (README.md / DECISIONS.md / TODO.md / AGENT-README.md, if applicable)
  • New dependencies are MIT/Apache-2.0/BSD, security-vetted, and recorded in THIRD-PARTY-NOTICES.md (none added)
  • No secrets, credentials, or real infrastructure identifiers in the diff
  • No commented-out code or debug leftovers
## What does this PR do? Remediates the four **Medium** findings from the 2026-08-25 security audit (codebase evaluated against all 25 applicable prompts from [ai-security-audit](https://github.com/HannahVernon/ai-security-audit) at commit 5885e32; prompts 07/15/28 N/A - no installer, PowerShell scripts, or CI/CD in this repo. No Critical or High findings). 1. **Policy allowlist sender extraction (verified finding).** `DefaultPolicyEngine` previously scraped `sender=`/`from=` markers out of free-form classification text (category, reasons, recommended action) - text that derives from attacker-influenceable mail content, so a crafted message could steer the sender-allowlist guardrail. The sender is now read exclusively from the stored `MailMessageEvent`''s structured `From` addresses (engine gains an `IEventStore` dependency); the free-form extraction helpers are deleted. A regression test proves text markers can no longer trigger the allowlist. Note: the guardrail now applies to every mail-message classification rather than only categories containing "sender" - strictly more protective of allowlisted senders. 2. **IMAP body fetch cap.** Body parts larger than 1 MiB (server-reported BODYSTRUCTURE octets) are no longer downloaded; previously a hostile oversized message was fully materialized before the normalizer''s 256 KiB truncation. The body carries an explicit `[BODY NOT FETCHED BY VIEGARD ...]` marker so classifiers see the absence honestly. 3. **MDaemon tailer symlink rejection.** Files that are reparse points, or whose canonical path escapes the configured log directory, are skipped with a warning - the tailer can no longer be steered into ingesting arbitrary readable files. 4. **Pinned package source.** Repo-level `nuget.config` with `<clear />`, nuget.org only, and `packageSourceMapping` - restores are reproducible for every contributor and cannot be redirected by ambient machine-level feeds. Verified with `dotnet restore --force`. TODO.md records the audit outcome and the deferred recommendations (revisit at Phase 9 hardening): audit-record exception text, LISTEN/NOTIFY wakeup spam (least-privilege per-instance DB roles arrive with the D-0025 satellite), secret string zeroization, in-memory audit ledger doc note. A follow-up PR covers the Low/Info batch (bounded tail reads, UID-wrap guard, log sanitization, stats cast, Hosting patch). ## How was this tested? - [x] `dotnet build` - 0 errors, 0 warnings - [x] `dotnet test` - 271 passed, 9 skipped (live-PostgreSQL integration tests; local WSL Docker unavailable) - [x] Four new policy-engine tests: structured allowlisted sender denies action, allowlisted domain denies action, text-marker injection cannot trigger the allowlist (regression), missing mail event cannot match - [x] `dotnet restore --force` against the pinned feed restores all projects ## Checklist - [x] I have read the [Contributing Guide](../CONTRIBUTING.md) - [x] Changes are focused - one logical change per PR - [x] Documentation updated (README.md / DECISIONS.md / TODO.md / AGENT-README.md, if applicable) - [x] New dependencies are MIT/Apache-2.0/BSD, security-vetted, and recorded in THIRD-PARTY-NOTICES.md (none added) - [x] No secrets, credentials, or real infrastructure identifiers in the diff - [x] No commented-out code or debug leftovers
Remediates the Medium-severity findings from the ai-security-audit
prompt evaluation (25 prompts, commit 5885e32; report in PR).

- Policy allowlist (sec-01): the sender is now read exclusively from
  the stored MailMessageEvent's structured From addresses.  The old
  TryExtractSender parsed sender=/from= markers out of free-form
  classification text (category, reasons, recommended action), which
  derives from attacker-influenceable mail content; crafted mail could
  steer the allowlist guardrail.  DefaultPolicyEngine now takes
  IEventStore; free-form extraction helpers removed; regression test
  proves text markers cannot trigger the allowlist.
- IMAP body fetch cap (sec-02): body parts larger than 1 MiB
  (server-reported octets) are not downloaded; previously hostile
  oversized messages were fully materialized before the normalizer's
  256 KiB truncation.
- MDaemon tailer (sec-03): symlinks/reparse points are rejected and
  canonicalized paths must stay inside the configured log directory,
  so the tailer cannot be steered to ingest arbitrary readable files.
- nuget.config (sec-04): repo-level source pinning to nuget.org with
  packageSourceMapping; restores are reproducible and cannot be
  redirected by ambient machine-level feeds (dependency-confusion
  hardening).  Verified with dotnet restore --force.

TODO.md records the audit outcome and the deferred recommendations
(revisit at Phase 9): audit exception text, NOTIFY spam, secret
string zeroization, in-memory ledger doc note.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
hannah-vernon/viegard-sentinel!16
No description provided.