Skip to content

Commit b9f7617

Browse files
turianclaude
andauthored
Redact API token from JSON output envelope (#10)
The envelope() function included the raw api_token in every response's "args" field. Add _sanitize_args() to replace sensitive values with "**REDACTED**" before output. Expand AGENTS.md with project structure and security guidance. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 98a11d7 commit b9f7617

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,58 @@
1-
# Tooling Convention
1+
# AGENTS.md
2+
3+
## Tooling Convention
24

35
- Use `uv` directly for Python environment and package tasks; do **not** use `uv pip` or `uv venv`.
6+
7+
## Project Structure
8+
9+
```
10+
src/fastmail_cli/
11+
__init__.py # Exports main()
12+
__main__.py # python -m fastmail_cli
13+
cli.py # Entry point — promotes FASTMAIL_API_TOKEN env var, calls jmapc.main()
14+
jmapc.py # All CLI commands, argparse setup, JMAP client logic
15+
```
16+
17+
- **Entry point:** `fastmail-cli <command> [args]` (defined in pyproject.toml `[project.scripts]`)
18+
- **Single runtime dependency:** `jmapc>=0.2.23`
19+
20+
## Running
21+
22+
```bash
23+
uv run fastmail-cli help # list commands
24+
uv run fastmail-cli describe email.query # show command options
25+
uv run fastmail-cli session.get # test auth (needs JMAP_API_TOKEN)
26+
```
27+
28+
## Output Format
29+
30+
Every command returns a JSON envelope:
31+
32+
```json
33+
{
34+
"ok": true,
35+
"command": "email.query",
36+
"args": { "host": "...", "api_token": "**REDACTED**", ... },
37+
"meta": { "timestamp": "...", "account_id": "...", ... },
38+
"data": { ... }
39+
}
40+
```
41+
42+
On failure, `"data"` is replaced by `"error"`.
43+
44+
## Security
45+
46+
- **Never log or output API tokens.** The `envelope()` function redacts `api_token` from output automatically via `_sanitize_args()`.
47+
- Prefer environment variables (`JMAP_API_TOKEN`, `FASTMAIL_API_TOKEN`) over `--api-token` CLI flag — CLI args are visible in process listings.
48+
- If adding new sensitive fields to connection options, add the key name to `_REDACTED_KEYS` in `jmapc.py`.
49+
- The CLI is read-only by default. `email.draft` / `email.draft-reply` create drafts but never send. `pipeline.run` uses an allowlist of safe JMAP methods.
50+
51+
## Testing
52+
53+
No automated test suite exists yet. Verify changes manually:
54+
55+
```bash
56+
uv run fastmail-cli help
57+
uv run fastmail-cli session.get 2>&1 | python -m json.tool
58+
```

src/fastmail_cli/jmapc.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ def http_exit_code(status: int) -> int:
9191
return 3 if status in (401, 403) else 4
9292

9393

94+
_REDACTED_KEYS = frozenset({"api_token"})
95+
96+
97+
def _sanitize_args(args: Dict[str, Any]) -> Dict[str, Any]:
98+
"""Return a copy of *args* with sensitive values redacted."""
99+
sanitized = dict(args)
100+
for key in _REDACTED_KEYS:
101+
if key in sanitized and sanitized[key]:
102+
sanitized[key] = "**REDACTED**"
103+
return sanitized
104+
105+
94106
def envelope(
95107
ok: bool,
96108
command: str,
@@ -102,7 +114,7 @@ def envelope(
102114
out: Dict[str, Any] = {
103115
"ok": ok,
104116
"command": command,
105-
"args": args,
117+
"args": _sanitize_args(args),
106118
"meta": meta,
107119
}
108120
if ok:

0 commit comments

Comments
 (0)