Skip to content

fix(mail): restrict --output-dir to current working directory#376

Open
haidaodashushu wants to merge 7 commits intomainfrom
fix/mail-watch-output-dir-security
Open

fix(mail): restrict --output-dir to current working directory#376
haidaodashushu wants to merge 7 commits intomainfrom
fix/mail-watch-output-dir-security

Conversation

@haidaodashushu
Copy link
Copy Markdown
Collaborator

@haidaodashushu haidaodashushu commented Apr 9, 2026

Summary

  • Remove absolute path and ~ expansion support from mail +watch --output-dir
  • All paths now go through validate.SafeOutputPath, enforcing CWD containment
  • Prevents writing sender-controlled mail content to arbitrary system directories

Security issue

--output-dir previously accepted absolute paths (e.g. /etc, /tmp/evil) and home directory expansion (~/). Since mail content is sender-controlled, this allowed potential writes of attacker-influenced data to sensitive locations.

Test plan

  • Absolute path (/tmp/evil) → rejected
  • Home directory (~/evil) → rejected
  • Path traversal (./../../etc) → rejected
  • Nested traversal (./mail-output/../../../) → rejected
  • Valid relative path (./mail-output) → allowed
  • All existing mail package tests pass

Summary by CodeRabbit

  • Refactor
    • Mail Watch: tightened output-directory handling—paths beginning with ~ are now rejected with a hint to use a relative path (e.g., ./output). Non-empty paths are validated and created automatically. Symlinks are resolved and an explicit post-creation check ensures the output directory remains within the current working directory.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

MailWatch --output-dir handling now rejects values starting with ~; non-empty values are resolved via validate.SafeOutputPath, created with vfs.MkdirAll, then re-validated with validate.SafeOutputPath to re-resolve symlinks and re-check containment before accepting the path.

Changes

Cohort / File(s) Summary
Output Directory Path Handling
shortcuts/mail/mail_watch.go
Tightened --output-dir flow: reject ~-prefixed inputs; run validate.SafeOutputPath() on provided value; create directory with vfs.MkdirAll; re-run validate.SafeOutputPath() post-creation to re-resolve symlinks and re-verify containment in CWD; return error if revalidation fails.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped along each winding trail,
No tilde whispers left to veil.
I make the folder, check it twice,
Safe paths rewarded with a slice. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main security fix: restricting --output-dir to the current working directory, which is the core change in this PR.
Description check ✅ Passed The description covers all required template sections with complete information: Summary explains the motivation, Changes lists key modifications, Test Plan includes concrete test cases with checkmarks, and Security issue documents the rationale.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mail-watch-output-dir-security

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added domain/mail PR touches the mail domain size/M Single-domain feat or fix with limited business impact labels Apr 9, 2026
Previously, mail +watch --output-dir accepted absolute paths (e.g.
/etc, /tmp) and home directory paths (~/), allowing writes to arbitrary
locations. Since mail content is sender-controlled, this posed a risk
of writing attacker-influenced data to sensitive system directories.

Now all --output-dir values go through validate.SafeOutputPath which:
- Rejects absolute paths and ~ expansion
- Resolves .. and symlinks
- Enforces the result stays under CWD
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 9, 2026

Greptile Summary

This PR fixes a path traversal/arbitrary write vulnerability in mail +watch --output-dir by routing all input through validate.SafeOutputPath, which enforces CWD containment, rejects absolute paths, and resolves symlinks before directory creation. Tilde-prefixed paths are now rejected up-front with an explicit error rather than silently creating a literal ~/… directory under CWD.

The removal of the post-MkdirAll EvalSymlinks call is intentional and safe: SafeOutputPath already returns a fully-resolved absolute path, so outputDir is always set to the real filesystem path before any directory is created.

Confidence Score: 5/5

Safe to merge — cleanly eliminates the arbitrary-write vulnerability with no new issues introduced.

All remaining items are informational. The tilde rejection, absolute-path blocking, path-traversal prevention, and symlink-escape detection are all correctly implemented and well-tested. The removal of the post-MkdirAll EvalSymlinks is intentional and correct since SafeOutputPath already returns a fully-resolved path.

No files require special attention.

Important Files Changed

Filename Overview
shortcuts/mail/mail_watch.go Security fix: routes all --output-dir values through SafeOutputPath; explicitly rejects tilde-prefixed paths; removes the now-redundant post-MkdirAll EvalSymlinks step.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["--output-dir flag value"] --> B{outputDir != ""}
    B -- "empty" --> Z["skip path handling"]
    B -- "non-empty" --> C{starts with ~?}
    C -- "yes" --> D["❌ ErrValidation:\n'does not support ~ expansion'"]
    C -- "no" --> E["validate.SafeOutputPath(outputDir)"]
    E --> F{absolute path?}
    F -- "yes" --> G["❌ rejected by SafeOutputPath"]
    E --> H{path traversal\nor symlink escape?}
    H -- "yes" --> I["❌ rejected by SafeOutputPath"]
    E --> J["✅ returns resolved absolute\npath within CWD"]
    J --> K["outputDir = safePath"]
    K --> L["vfs.MkdirAll(outputDir, 0700)"]
    L --> M["proceed with mail watching,\nwrites stay inside CWD"]
Loading

Reviews (7): Last reviewed commit: "fix(mail): remove redundant post-mkdir S..." | Re-trigger Greptile

@haidaodashushu haidaodashushu force-pushed the fix/mail-watch-output-dir-security branch from 3e34929 to 9e5a99d Compare April 9, 2026 13:59
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@716dade4fe0ce5a74ff860fa6798cc1c52d1cbda

🧩 Skill update

npx skills add larksuite/cli#fix/mail-watch-output-dir-security -y -g

SafeOutputPath treats ~/x as a literal relative path, silently creating
a directory named "~" under CWD. Reject ~ prefixed paths with a clear
error message instead.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/mail/mail_watch.go`:
- Around line 199-206: The validation branch that rejects ~ and the call to
validate.SafeOutputPath currently return raw errors; wrap these user-input
validation errors with the package-level sentinel output.ErrValidation so
callers see them as validation failures. Specifically, in the block that returns
the "~ expansion" error and in the error path after calling
validate.SafeOutputPath(outputDir), replace the plain errors with wrapped errors
using output.ErrValidation (e.g., return fmt.Errorf("%w: <message or err>",
output.ErrValidation) or equivalent), preserving the original error text from
SafeOutputPath while tagging it with output.ErrValidation.
- Around line 198-199: The current check only rejects exactly "~" and "~/..."
but misses other tilde-prefixed forms; update the conditional in mail_watch.go
that inspects outputDir (the same place that later calls SafeOutputPath) to
reject any outputDir that begins with a tilde character (i.e., when the first
rune == '~' or strings.HasPrefix(outputDir, "~")), including forms with
backslashes and user expansions like "~user/..." or "~\output"; when detected,
return the same fmt.Errorf("--output-dir does not support ~ expansion; use a
relative path like ./output instead").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f724bfc9-a084-4e72-9e0c-6a9bca03b9bb

📥 Commits

Reviewing files that changed from the base of the PR and between 9e5a99d and 0b96b2d.

📒 Files selected for processing (1)
  • shortcuts/mail/mail_watch.go

- Broaden ~ check from "~ || ~/" to "~" prefix, covering ~user/path forms
- Use output.ErrValidation for consistent error type (exit code 2)
SafeOutputPath validates before MkdirAll, but an attacker could replace
the newly created directory with a symlink between mkdir and the first
write. Add EvalSymlinks after MkdirAll and re-verify the resolved path
is still under CWD.

Also broaden ~ rejection to all tilde-prefixed paths (~user/path) and
use output.ErrValidation for consistent error types.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
shortcuts/mail/mail_watch.go (1)

204-207: ⚠️ Potential issue | 🟠 Major

Wrap SafeOutputPath failures as validation errors.

At Line 206, returning raw err can lose validation error typing/exit semantics for bad user input. Keep this branch consistently tagged as output.ErrValidation.

Suggested fix
 			safePath, err := validate.SafeOutputPath(outputDir)
 			if err != nil {
-				return err
+				return output.ErrValidation("unsafe --output-dir: %s", err)
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_watch.go` around lines 204 - 207, The call to
validate.SafeOutputPath currently returns the raw err (safePath, err :=
validate.SafeOutputPath(outputDir) ... return err), which loses validation error
typing; change the error return to wrap the underlying error with
output.ErrValidation (e.g., return fmt.Errorf("%w: %v", output.ErrValidation,
err)) so callers can detect validation failures; update imports to include fmt
if needed and apply this wrapping wherever validate.SafeOutputPath's error is
returned in the mail_watch.go flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/mail/mail_watch.go`:
- Around line 216-227: The code currently calls filepath.EvalSymlinks and
os.Getwd (around the outputDir handling) which violates the repository's
forbidigo rules; remove the direct filepath.EvalSymlinks and os.Getwd calls and
instead, after creating the directory (the existing MkdirAll step), re-validate
the original raw output path using the repository helper validate.SafeOutputPath
(preserving the TOCTOU re-check). Concretely: stop resolving symlinks into
resolved/canonicalCwd and computing rel; after MkdirAll for outputDir, call
validate.SafeOutputPath(outputDir) (or the appropriate validate function used in
this repo) and return an output.ErrValidation on failure, referencing the same
output.ErrValidation message flow as before.

---

Duplicate comments:
In `@shortcuts/mail/mail_watch.go`:
- Around line 204-207: The call to validate.SafeOutputPath currently returns the
raw err (safePath, err := validate.SafeOutputPath(outputDir) ... return err),
which loses validation error typing; change the error return to wrap the
underlying error with output.ErrValidation (e.g., return fmt.Errorf("%w: %v",
output.ErrValidation, err)) so callers can detect validation failures; update
imports to include fmt if needed and apply this wrapping wherever
validate.SafeOutputPath's error is returned in the mail_watch.go flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fb459e77-60cc-4f0a-9515-867014ba3c2e

📥 Commits

Reviewing files that changed from the base of the PR and between 0b96b2d and 1264987.

📒 Files selected for processing (1)
  • shortcuts/mail/mail_watch.go

Replace direct os.Getwd and filepath.EvalSymlinks calls with a second
SafeOutputPath call after MkdirAll. This satisfies the forbidigo lint
rule (no direct os/filepath calls in shortcuts/) while maintaining the
same TOCTOU protection.
SafeOutputPath rejects absolute paths, but after the first call
outputDir was already resolved to an absolute path. Pass the original
relative path to the second SafeOutputPath call so it can properly
re-validate after MkdirAll.
The second SafeOutputPath call after MkdirAll provided no real TOCTOU
protection: mail +watch is long-running, so the directory could be
replaced at any point during the session, not just between mkdir and
the check. The first SafeOutputPath already validates and resolves
the path; one call is sufficient.
Copy link
Copy Markdown
Collaborator

@chanthuang chanthuang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. 核心安全修复完整:~ 显式拒绝 + 所有路径统一走 SafeOutputPath,代码比之前更简洁。合入时建议 squash commits。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/mail PR touches the mail domain size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants