docs(notion-writer): document 1Password credential handling, deprecate .env

Plaintext .env storage of NOTION_API_KEY is unnecessary risk — Notion
integration tokens grant write access to every connected page/database
and have no expiry. Document the 1Password CLI fetch pattern as the
preferred path; .env becomes a discouraged fallback for environments
without `op`.

SKILL.md adds a Credential handling section covering:
- One-shot inline fetch via `op read 'op://Development/Notion - Claude Agent/api-key'`
- Subshell-scoped export pattern for repeated use without shell-history exposure
- 1Password field-name conventions (api-key for the token)
- Anti-patterns to avoid (echo, CLI args, git commits, chat paste)
- Token rotation procedure

notion_writer.py error message updated to point at the 1Password recipe
when NOTION_API_KEY is missing, instead of telling users to create a .env.

Tested end-to-end: pushed a 22 KB markdown report (16 tables, 120 nested
checkboxes) using the inline 1Password fetch. Bot identity verified, all
markdown blocks rendered as native Notion blocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 19:14:48 +09:00
parent 69526d345a
commit e1bd799672
2 changed files with 72 additions and 4 deletions

View File

@@ -12,8 +12,8 @@ Push markdown content to Notion pages or databases via Claude Code.
## Prerequisites
- Python virtual environment at `~/Project/our-claude-skills/custom-skills/02-notion-writer/code/scripts/venv`
- Notion API key configured in `.env` file
- Target pages/databases must be shared with the integration
- Notion integration token (preferred: stored in 1Password — see [Credential handling](#credential-handling) below)
- Target pages/databases must be shared with the integration in Notion (Database/Page → ⋯ → Connections → add integration)
## Quick Start
@@ -22,6 +22,70 @@ cd ~/Project/our-claude-skills/custom-skills/02-notion-writer/code/scripts
source venv/bin/activate
```
## Credential handling
**Do NOT store the Notion API key in a `.env` file.** A long-lived plaintext secret on disk is unnecessary risk — Notion integration tokens grant write access to every page/database the integration is connected to.
### Preferred: fetch from 1Password at runtime
Store the token once in 1Password (Item: `Notion - Claude Agent`, Vault: `Development`, Field: `api-key`), then fetch on each invocation. The token lives only in process memory — never on disk, never in shell history.
```bash
# One-shot push — token fetched + used + discarded in a single shell line
NOTION_API_KEY="$(op read 'op://Development/Notion - Claude Agent/api-key')" \
python notion_writer.py --test
```
For repeated use in the same session, scope the variable to a subshell so it leaves no trace:
```bash
(
export NOTION_API_KEY="$(op read 'op://Development/Notion - Claude Agent/api-key')"
python notion_writer.py --database "$DB_URL" --title "Foo" --file foo.md
python notion_writer.py --database "$DB_URL" --title "Bar" --file bar.md
)
# NOTION_API_KEY is gone from the parent shell
```
### Field name conventions in 1Password
When you create the Notion integration item, use these field names:
| Field | Value |
|---|---|
| `api-key` (CONCEALED) | The integration token (`ntn_...` for current API, `secret_...` for legacy) |
| `username` | Integration display name (e.g. "Claude Agent") |
| `hostname` | `https://api.notion.com` |
| `notesPlain` | Vault for which workspace + which databases this integration is connected to |
The script reads `NOTION_API_KEY` from the environment — it does not call `op` itself. This keeps the script free of `op`-specific dependencies and lets the same script work in CI/CD environments that inject the secret differently (GitHub Actions secrets, Vault, etc.).
### Fallback: `.env` file (discouraged)
If you must use a file (e.g. running on a host without 1Password CLI), place it at `~/Project/our-claude-skills/custom-skills/32-notion-writer/code/.env` with:
```
NOTION_API_KEY=ntn_xxxxxxxx_paste_your_token_here
```
Set the file to mode `600` (`chmod 600 .env`) and never commit it. Add `.env` to `.gitignore` if not already.
### What NEVER to do
- ❌ Echo the token to stdout: `echo "$NOTION_API_KEY"` (lands in terminal scrollback + shell history if the var was inline)
- ❌ Pass the token as a CLI argument: `--api-key "ntn_..."` (visible in `ps`, history, telemetry)
- ❌ Commit a `.env` file to git, even briefly (commit history is forever)
- ❌ Paste the token into chat / issues / PRs — assume any pasted secret is compromised within minutes
### Token rotation
If a token is exposed (or you reasonably suspect it might be):
1. Go to <https://www.notion.so/my-integrations> → open the integration → **Reset** the secret
2. Update the 1Password item's `api-key` field with the new token
3. Verify with `NOTION_API_KEY="$(op read ...)" python notion_writer.py --test`
4. The old token is invalidated immediately — any leftover scripts using it will fail loudly
## Commands
### Test Connection