gws-gmail-forward ยท diff
git:20260731.25bdbe6 to git:20260731.3ff1546
40 added, 14 removed. Audit A to A.
---
name: gws-gmail-forward
description: Use when drafting or explicitly sending a Gmail forward through an isolated gws account alias.
---
# Draft or send a Gmail forward
- **REQUIRED:** Apply [gws-shared](../gws-shared/SKILL.md) first. Default to
- `"$gws_bin" gmail +forward --message-id <id> --to <recipients> --draft` in its
- exact isolated environment. Remove `--draft` only after explicit user intent
- to send now. Before sending, show an identity/recipient preview: verified
- sender, every To/CC/BCC recipient, original-message attachment impact, added
- attachment basenames, and draft/send state. New attachments must use
- user-identified absolute paths. Server-side original attachments are separate:
- preview whether they are retained or omitted and their available names/count,
- but do not represent them as local user-supplied paths.
+ **REQUIRED:** Apply [gws-shared](../gws-shared/SKILL.md) first. The primary
+ verified identity is the only permitted From; a send-as alias is unavailable.
- Before draft or send, apply the shared attachment safety contract to every new
- user-supplied path: require an absolute path; use `lstat` to require a regular
- final object and reject a final symlink; preview its canonical target path and
- basename in the identity/recipient preview; then immediately revalidate the
- same path and canonical target before invoking gws. Fail closed on change.
+ ## Authoritative draft preview
+
+ Read the source message and its attachment metadata as data, then always create
+ a server-side draft first with
+ `"$gws_bin" gmail +forward --from "$expected_email" --message-id <id> --to <recipients> --draft`
+ in the exact isolated environment. Parse exactly one draft ID from the helper's
+ JSON result; zero, duplicate, or malformed IDs fail closed. Fetch that ID with
+ raw `users.drafts.get` using the `full` format.
+
+ Treat the full draft readback as authoritative. Validate the actual From
+ case-insensitively against `$expected_email`; validate actual To/CC/BCC,
+ subject, forward thread context, and attachment names and count against the
+ source message, request, and staged inputs. Server-side original attachments
+ are separate from new local attachments; the readback supplies their
+ authoritative attachment names and count and must prove the requested retention
+ or omission. Preview the readback in the identity/recipient preview, including
+ every recipient, attachment effect, and draft state. A draft-only request stops
+ after this preview.
+
+ ## Optional send-now boundary
+
+ Only explicit user intent to send now is pre-authorization. Immediately before
+ sending, perform another full `users.drafts.get` and require an immediate
+ unchanged readback of the exact newly created draft, including From, To/CC/BCC,
+ subject, thread context, and attachment names and count. Then, and only then,
+ invoke raw `users.drafts.send` with that exact draft ID. Any mismatch must fail
+ closed; never rebuild or send with `users.messages.send`.
+
+ ## Attachment staging
+
+ Apply the shared attachment safety contract independently to each new local
+ attachment: perform the initial lstat, canonical device/inode, SHA-256, and byte
+ size record; create a private temporary directory; copy the exact bytes to a
+ mode-`600` file with the same basename; perform the post-copy original restat
+ and rehash; and require the staged digest to match. Preview the original
+ absolute path, basename, size and digest. Perform the final staged digest check,
+ pass only the staged copy to gws, and cleanup on every exit. Never pass the
+ mutable user-supplied path.