The Problem
You get an error notification filed as an issue. You ask Claude Code to investigate the codebase, and it turns out you need real data to narrow down the root cause. You write a SQL query, run it manually, and paste the results back to Claude Code.
But if the query results contain personal information — emails, phone numbers, names — you can't just hand them over as-is. Every time, you either rewrite the SQL to exclude sensitive columns or manually redact PII from the output before pasting. Do this dozens of times a day and you'll inevitably forget once. And once is all it takes.
Why Existing Tools Don't Solve This
I looked for a MySQL client with output masking. None of the popular ones had it.
| Tool | Masking? | Notes |
|---|---|---|
| mycli | No | Focused on syntax highlighting and autocomplete |
| DataGrip | No | Can hide columns, but doesn't mask output |
| TablePlus / DBeaver | No | Same story across GUI clients |
| MySQL Enterprise | Server-side only |
mask_inner() etc. — requires Enterprise Edition |
MySQL Enterprise Edition has server-side masking functions, but they're paid and require DB-level configuration. No client-side tool offered output masking with AI workflows in mind.
So I Built mysh
If it doesn't exist, build it. I wrote mysh, a MySQL connection manager that auto-masks query output, in Go. Built it with Claude Code in two days.

Production connections are masked automatically. Development connections show raw data.
How It Works: Environment × Output Target
mysh determines whether to mask based on two factors: the connection's environment and the output destination (terminal vs. pipe).
| env | Terminal (human) | Pipe/capture (AI) |
|---|---|---|
| production | Auto-mask | Auto-mask |
| staging | Raw | Auto-mask |
| development | Raw | Raw |
Production connections are always masked regardless of output target. If you need raw data, use the --raw flag — but it requires interactive confirmation:
$ mysh run prod-db -e "SELECT * FROM users" --raw
⚠ Raw output requested for production connection "prod-db".
Masking will be disabled. Continue? [y/N]:
This confirmation only works on a TTY (terminal). AI tools and scripts run in non-TTY mode, so they physically cannot respond to the prompt — making it impossible for them to bypass masking.
Configuring Masked Columns
When adding a connection, you specify which columns to mask. Both exact matches and wildcards are supported:
Columns to mask (comma-separated, wildcards OK) [email,phone,*password*,*secret*,*token*,*address*]:
For production and staging environments, sensible defaults are suggested — just press Enter to cover common PII columns. Setting *pass* masks any column containing "pass" in its name.
Pro tip: show your schema to Claude Code and ask it to pick which columns should be masked. It catches things you might miss.
Masking Examples
| Type | Original | Masked |
|---|---|---|
| alice@example.com | a***@example.com | |
| Phone | 090-1234-5678 | 0*** |
| Name | Alice | A*** |
Usage
Adding a Connection
Walk through the setup interactively, or pre-fill fields with CLI flags:
# Fully interactive
mysh add
# Pre-fill with flags (password is always entered interactively)
mysh add --name prod --env prod --db-host 127.0.0.1 --db-user app --db-name myapp \
--ssh-host bastion.example.com --ssh-user deploy
A connection test runs after setup. If it fails, you can fix the specific field on the spot.
Running Queries
If you only have one connection, the name can be omitted:
mysh run -e "SELECT COUNT(*) FROM users" # Inline SQL
mysh run query.sql # SQL file
mysh tables # List tables
SSH Tunneling
Connections through a bastion host work with a single command:
mysh tunnel production # Start tunnel
mysh run production -e "SHOW PROCESSLIST" # Auto-reuses tunnel
mysh tunnel stop production # Stop
Security Design
Beyond masking, mysh is designed to minimize the risk of credential leaks:
- Encrypted passwords — AES-256-GCM encryption with Argon2id key derivation, resistant to GPU brute-force attacks
- Keychain integration — On macOS, the master password is stored in Keychain so you don't type it every time
-
File permissions — Config files are created with
0600, preventing access by other users - No password CLI flags — Passwords cannot be passed as CLI arguments, preventing leaks via shell history or process lists
Technical Notes
Why Go?
Single-binary distribution and easy cross-compilation make Go ideal for CLI tools. The golang.org/x/term package provides TTY detection, which made implementing the environment-aware masking behavior straightforward.
TTY Detection for Masking
term.IsTerminal() checks whether stdout is a terminal or a pipe. Combined with the environment setting, this determines masking behavior. Production always masks regardless; the --raw override additionally checks os.Stdin for TTY to block non-interactive bypass.
func (c *Connection) ShouldMask(isTTY bool) bool {
if c.Env == "production" {
return true
}
if c.Env == "development" {
return false
}
return !isTTY // staging: mask only when piped
}
Usage Tips
- Connect with a READ-only user — Combined with masking, this also prevents accidental data modification
- Use mysh for development too — Masking isn't applied in dev, so it doesn't interfere with testing. Using mysh everywhere means you don't switch tools when investigating production
-
Let AI pick masked columns — Run
SHOW COLUMNS FROM table_nameand ask Claude Code to identify PII columns. It's thorough
Limitations
-
Column-name-based masking — Irregular column names (
col1,data, etc.) require manual configuration - MySQL only — No PostgreSQL or other database support
- Masking format is fixed: first character preserved, rest replaced with
***. No customization - Large result sets (tens of thousands of rows) incur overhead from the masking pass
Try It
Install with Homebrew in 30 seconds:
brew tap atani/tap && brew install mysh
mysh add # Interactively add a connection
If you find it useful, a star on GitHub would mean a lot.
GitHub: https://github.com/atani/mysh

Top comments (0)