Skip to content

feat(messages): add dryRunCompose (read-only compose preflight, stacked on #125)#151

Open
JordanRO2 wants to merge 3 commits into
TKasperczyk:mainfrom
JordanRO2:feat/dry-run-compose
Open

feat(messages): add dryRunCompose (read-only compose preflight, stacked on #125)#151
JordanRO2 wants to merge 3 commits into
TKasperczyk:mainfrom
JordanRO2:feat/dry-run-compose

Conversation

@JordanRO2

Copy link
Copy Markdown
Contributor

Stacked on #125 — it reuses the deny-list / recipient-count helpers from that PR (isSensitiveFilePath, countRecipients, MAX_FILE_PATH_ATTACHMENT_BYTES). Please review/merge #125 first; its commits drop out of this diff once it lands.

What

dryRunCompose(to, subject, body, cc, bcc, isHtml, from, attachments) — validate compose parameters without any side effect: it does not send, save a draft, open a compose window, write temp files, or copy/decode attachment bytes. A pure read-only preflight for agents that want to know "would this send cleanly?" before committing.

Returns a structured report:

  • wouldSucceed (roll-up), blockers[] (identity/resolution failures)
  • resolvedIdentity (resolved from, or the default-identity preview)
  • subjectAfterSanitization, bodyLength (content is not echoed), recipients: {to, cc, bcc} counts
  • per-attachment verdict { kind, name, status: ok|blocked|missing|error, reason, size } — file paths are checked against the sensitive-path deny-list and the 50 MB cap via a stat-only nsIFile (never opened/read/normalized); inline entries' decoded size is estimated arithmetically and checked against the 25 MB cap (base64 is never decoded)
  • skipReviewBlocked pref snapshot (reported only)

No side effects (verified)

Deliberately does not call the filePathsToAttachDescs path (which writes temp files / builds file URIs). The attachment loop only stats paths and arithmetic-estimates inline sizes. Reviewed adversarially specifically to disprove any send/save/window/temp-file/network effect.

Tests

test/validation.test.cjs covers the schema (incl. the content-alias on inline attachments, consistent with the other compose tools); ALL_TOOLS updated. Full suite green, eslint . 0 errors.

- blockSkipReview now defaults to true; sendMail/replyToMessage/
  forwardMessage and createEvent/createTask require review unless the
  user explicitly opts into silent sends. createEvent/createTask are
  now gated by the same pref the mail compose tools already honored.
- Reject file-path attachments that target credential stores,
  system directories, browser/mail profiles, and key/cert files
  across POSIX and Windows; cap file-path attachments at 50MB to
  match the saved-attachment ceiling.
- Strict allow-list for filter conditions and actions: replace
  ACTION_MAP[x] ?? parseInt(x) with hasOwnProperty checks so callers
  cannot reach unmapped nsMsgFilterAction values by passing raw ints.
- Extend validateToolArgs to enforce enum, oneOf, nested object
  properties, required, and additionalProperties:false. Schema
  keywords on existing tool definitions (notably bodyFormat and the
  attachments oneOf) are now actually checked.
- Harden writeConnectionInfo: after the symlink check, force tmp
  dir mode back to 0o700 if any group/world bits are set; refuse to
  write when the perms cannot be tightened. No-op on platforms
  without POSIX modes.

Adds 17 unit tests covering the deny-list (credential paths, system
dirs, browser/mail profiles, benign-path negatives, case and slash
normalization) and the recursive validator additions (enum, oneOf
branches with path-indexed errors, integer type, nested
additionalProperties).
…e attach; reject null array items

Codex review follow-ups on TKasperczyk#125:
- Attachment schema accepted only {name, base64} while the runtime reads
  entry.base64 || entry.content, so a previously-valid {name, content}
  inline attachment failed validation before dispatch. Add content as a
  base64 alias (required relaxed to [name]); base64 still wins at runtime.
- isSensitiveFilePath checked the raw string only, so a benign path
  symlinked to a credential store slipped through. Normalize the nsIFile
  (resolves symlinks + . / ..) and re-run the deny-list before reading.
- validateAgainstSchema returned early on null, so attachments:[null]
  skipped the item schema. Reject null/undefined array items explicitly.
- 6 regression tests (content alias, base64 without contentType, both set,
  missing name, unknown property, null item).
Validate compose parameters WITHOUT sending, saving, opening a compose
window, writing temp files, or copying attachment bytes -- a pure read-only
preflight for agents. Resolves the from identity (or previews the default),
sanitizes the subject header, counts to/cc/bcc recipients, reports body
length (content is not echoed), and gives a per-attachment verdict
(ok/blocked/missing/error with a reason): file paths are checked against the
sensitive-path deny-list and the 50MB cap via a stat-only nsIFile (never
opened/read/normalized), and inline entries' decoded size is estimated
arithmetically and checked against the 25MB cap -- base64 is never decoded.
Also reports the skipReview pref snapshot. wouldSucceed is the roll-up.

The attachment schema accepts content as a base64 alias (required ['name']),
matching the other compose tools so a runtime-accepted shape is not rejected
by validation.

Tests + ALL_TOOLS updated; full suite green, lint 0 errors.
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