-
Notifications
You must be signed in to change notification settings - Fork 547
Feature/mail read receipt #595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -55,6 +55,55 @@ func hintMarkAsRead(runtime *common.RuntimeContext, mailboxID, originalMessageID | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sanitizeForTerminal(mailboxID), sanitizeForTerminal(originalMessageID)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // hintReadReceiptRequest prints a stderr tip when a message that the caller | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // just read requested a read receipt (carries the READ_RECEIPT_REQUEST label). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The tip is emitted at CLI level so any caller — agents that read SKILL.md | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and those that don't — sees the prompt. Privacy is sensitive here: sending | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // a receipt tells the remote party "I have read your message", so the tip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // explicitly instructs the caller to ask the user before responding. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Flag values are wrapped in single quotes on the suggested command line so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // that values containing shell metacharacters (spaces, $, &, etc.) stay | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // intact when the user copies and pastes the line into a shell. The embedded | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // values are also sanitized to strip ANSI / control bytes, and any literal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // single quote is escaped as '\'' to keep the surrounding quoting balanced. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func hintReadReceiptRequest(runtime *common.RuntimeContext, mailboxID, messageID, fromEmail, subject string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Fprintf(runtime.IO().ErrOut, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "tip: sender requested a read receipt (READ_RECEIPT_REQUEST).\n"+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " - do NOT auto-send; ask the user first (from=%s, subject=%q)\n"+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " - if the user confirms, respond with:\n"+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " lark-cli mail +send-receipt --mailbox '%s' --message-id '%s' --yes\n", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sanitizeForTerminal(fromEmail), sanitizeForTerminal(subject), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shellQuoteForHint(mailboxID), shellQuoteForHint(messageID)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // shellQuoteForHint returns s sanitized for terminal output AND safe to embed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // inside single-quoted shell arguments: each single quote in the payload is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // rewritten as '\'' (close-quote, escaped quote, re-open-quote). Callers are | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // expected to wrap the result in outer single quotes, as hintReadReceiptRequest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // does in its format string. Use this only for user-copy-paste hints, not for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // building commands that the CLI itself executes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func shellQuoteForHint(s string) string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return strings.ReplaceAll(sanitizeForTerminal(s), "'", `'\''`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strip LF before embedding untrusted values in the receipt hint.
Proposed fix func hintReadReceiptRequest(runtime *common.RuntimeContext, mailboxID, messageID, fromEmail, subject string) {
fmt.Fprintf(runtime.IO().ErrOut,
"tip: sender requested a read receipt (READ_RECEIPT_REQUEST).\n"+
" - do NOT auto-send; ask the user first (from=%s, subject=%q)\n"+
" - if the user confirms, respond with:\n"+
" lark-cli mail +send-receipt --mailbox '%s' --message-id '%s' --yes\n",
- sanitizeForTerminal(fromEmail), sanitizeForTerminal(subject),
+ sanitizeSingleLineForHint(fromEmail), sanitizeSingleLineForHint(subject),
shellQuoteForHint(mailboxID), shellQuoteForHint(messageID))
}
+func sanitizeSingleLineForHint(s string) string {
+ return strings.ReplaceAll(sanitizeForTerminal(s), "\n", " ")
+}
+
// shellQuoteForHint returns s sanitized for terminal output AND safe to embed
// inside single-quoted shell arguments: each single quote in the payload is
// rewritten as '\'' (close-quote, escaped quote, re-open-quote). Callers are
// expected to wrap the result in outer single quotes, as hintReadReceiptRequest
// does in its format string. Use this only for user-copy-paste hints, not for
// building commands that the CLI itself executes.
func shellQuoteForHint(s string) string {
- return strings.ReplaceAll(sanitizeForTerminal(s), "'", `'\''`)
+ return strings.ReplaceAll(sanitizeSingleLineForHint(s), "'", `'\''`)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // requireSenderForRequestReceipt returns a validation error when --request- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // receipt is set but no sender address could be resolved. The Disposition- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Notification-To header can only be addressed to a known sender — silently | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // dropping the header when senderEmail is empty would mislead the caller into | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // believing a receipt was requested when it wasn't. Intended to be called | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // from a shortcut's Execute right after the sender address has been resolved. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func requireSenderForRequestReceipt(runtime *common.RuntimeContext, senderEmail string) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !runtime.Bool("request-receipt") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.TrimSpace(senderEmail) == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return output.ErrValidation( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "--request-receipt requires a resolvable sender address; specify --from explicitly") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // messageOutputSchema returns a JSON description of +message / +messages / +thread output fields. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Used by --print-output-schema to let callers discover field names without reading skill docs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func printMessageOutputSchema(runtime *common.RuntimeContext) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.