Skip to content

Per-account no-send guard is not enforced under --dry-run #915

Description

@veteranbv

Summary

The Gmail no-send safety system has three layers: the --gmail-no-send flag, the global gmail_no_send config key, and per-account guards (gog config no-send set <email>). The first two are enforced pre-Run() in enforceGmailNoSend and block even under --dry-run. The per-account guard is only checked post-auth inside each send command's Run() — which --dry-run exits before reaching. Result: --dry-run reports would gmail.send for an account whose sends are hard-blocked.

Real (non-dry-run) sends are still blocked by checkAccountNoSend before any API call, so no mail can actually leak. The failure is the safety signal, not the send itself. But that signal is exactly what agent operators use to verify their guardrails: probing with --dry-run (the safe way to test) tells you the guard is broken when it isn't — or, worse, an agent told to verify its constraints concludes sending is permitted. I hit this today: a --dry-run probe of a per-account guard sailed through, and I concluded the guard didn't work and switched to the global key instead.

Steps to reproduce

On v0.34.0, with an isolated config:

export GOG_CONFIG_DIR="$(mktemp -d)"
cat > "$GOG_CONFIG_DIR/config.json" <<'EOF'
{
  "no_send_accounts": {
    "[email protected]": true
  }
}
EOF
gog gmail send --account [email protected] --to [email protected] --subject S --body B --dry-run

Expected

Gmail sending is blocked for [email protected] (config no-send) — matching the behavior of the other two layers, which both block under --dry-run:

$ gog gmail send ... --gmail-no-send --dry-run
Gmail sending is blocked by --gmail-no-send
$ gog config set gmail_no_send true && gog gmail send ... --dry-run
Gmail sending is blocked by config gmail_no_send

Actual

Dry run: would gmail.send
{
  "attachments": [],
  ...

Same for all five send paths (gmail send, gmail forward, gmail reply/reply-all, gmail autoreply, gmail drafts send).

Root cause

dryRunExit runs before requireGmailSendService in every send command, and requireGmailSendService is the only place checkAccountNoSend fires. In internal/cmd/gmail_send.go on current main: dryRunExit at L120, requireGmailSendService at L142. enforceGmailNoSend (internal/cmd/gmail_no_send.go) checks the flag and the global key pre-Run() but never the per-account map.

The ordering gap traces back to the original per-account design in #454 (mine) — the landed implementation preserved it faithfully.

Proposed fix

Enforce the per-account guard in enforceGmailNoSend alongside its two siblings. The account is resolvable there without auth via requireAccount(flags) (flag → GOG_ACCOUNT → configured default, with alias resolution from config). When no account resolves (e.g. dry-run with no configuration), skip — the command owns that failure mode, and the existing post-auth checkAccountNoSend still covers real sends as defense-in-depth.

PR with fix + tests incoming.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions