Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Gmail: emit sanitized message headers and bodies once in `gmail get --json --sanitize-content`, while retaining the `message` envelope and `--results-only` unwrapping. (#986) — thanks @ronny-rentner.
- Sheets: add read-only Connected Sheets discovery, full data-source descriptions and execution status, plus bounded reads for anchored data-source tables (extracts) behind an explicitly opted-in BigQuery scope. (#938) — thanks @ryo-touch.

## v0.36.0 - 2026-08-13
Expand Down
4 changes: 3 additions & 1 deletion docs/gmail-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ gog gmail thread get <threadId> --sanitize-content --json
```

`--sanitize-content` strips unsafe/raw payload details while keeping useful
message text for automation.
message text for automation. Message JSON remains under the `message` key;
add `--results-only` to emit that sanitized message directly. Both shapes emit
the message headers and body once.

## Filters

Expand Down
9 changes: 1 addition & 8 deletions internal/cmd/gmail_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,7 @@ func (c *GmailGetCmd) Run(ctx context.Context, flags *RootFlags) error {
if outfmt.IsJSON(ctx) {
if c.SanitizeContent {
output := sanitizedGmailMessage(msg, format == gmailFormatFull, c.UseIndexedAttachmentIDs)
payload := map[string]any{
"message": output,
"headers": output.Headers,
}
if format == gmailFormatFull && output.Body != "" {
payload["body"] = output.Body
}
return outfmt.WriteJSON(ctx, stdoutWriter(ctx), payload)
return outfmt.WriteJSON(ctx, stdoutWriter(ctx), map[string]any{"message": output})
}
// Include a flattened headers map for easier querying
// (e.g., jq '.headers.to' instead of complex nested queries)
Expand Down
40 changes: 31 additions & 9 deletions internal/cmd/gmail_sanitize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,43 @@ func TestGmailGetCmd_SanitizeContent_JSONUsesSafeEnvelope(t *testing.T) {
if strings.Contains(result.stdout, "payload") || strings.Contains(result.stdout, "unsubscribe") {
t.Fatalf("sanitized JSON should not expose raw Gmail payload/unsubscribe: %s", result.stdout)
}
var envelope map[string]json.RawMessage
if err := json.Unmarshal([]byte(result.stdout), &envelope); err != nil {
t.Fatalf("decode JSON envelope: %v", err)
}
if len(envelope) != 1 || envelope["message"] == nil {
t.Fatalf("sanitized JSON should contain the message once, got: %s", result.stdout)
}

var parsed struct {
Body string `json:"body"`
Message struct {
ID string `json:"id"`
Headers map[string]string `json:"headers"`
} `json:"message"`
ID string `json:"id"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
}
if err := json.Unmarshal([]byte(result.stdout), &parsed); err != nil {
t.Fatalf("decode JSON: %v", err)
if err := json.Unmarshal(envelope["message"], &parsed); err != nil {
t.Fatalf("decode sanitized message: %v", err)
}
if parsed.Body != "Hello [url removed]" {
t.Fatalf("unexpected body: %q", parsed.Body)
}
if parsed.Message.Headers["subject"] != "Visit [url removed] now" {
t.Fatalf("unexpected sanitized subject: %#v", parsed.Message.Headers)
if parsed.Headers["subject"] != "Visit [url removed] now" {
t.Fatalf("unexpected sanitized subject: %#v", parsed.Headers)
}

result = executeWithGmailTestService(
t,
[]string{"--json", "--results-only", "--account", "a@b.com", "gmail", "get", "m1", "--sanitize-content"},
newGmailServiceFromServer(t, srv),
)
if result.err != nil {
t.Fatalf("Execute with --results-only: %v\nstderr=%q", result.err, result.stderr)
}
var direct gmailSanitizedMessageOutput
if err := json.Unmarshal([]byte(result.stdout), &direct); err != nil {
t.Fatalf("decode direct sanitized message: %v", err)
}
if direct.ID != parsed.ID || direct.Body != parsed.Body || direct.Headers["subject"] != parsed.Headers["subject"] {
t.Fatalf("--results-only did not unwrap the sanitized message: %s", result.stdout)
}
}

Expand Down