We actively support the following versions with security updates:
| Version | Supported | Notes |
|---|---|---|
| 6.x | ✅ | Current release (Go rewrite), fully supported |
| 5.x | ❌ | Legacy TypeScript version, no longer maintained |
| < 5.0 | ❌ | No longer supported |
The v6.0.0 rewrite from TypeScript to Go dramatically reduced the attack surface:
- 2 runtime dependencies:
github.com/spf13/cobraandgithub.com/spf13/pflag - Single static binary: ~8MB, no interpreter or runtime required
- No npm production dependencies: The npm package is a thin wrapper that downloads and runs the platform-specific Go binary
- No external code execution: The CLI does not evaluate user-provided code, load plugins, or shell out to external programs
- Cross-compiled binaries: Built from source for darwin/amd64, darwin/arm64, linux/amd64, linux/arm64, and windows/amd64
Compared to v5.x (573 npm dependencies), v6.x has a near-zero supply chain risk profile.
- Path:
~/.config/notion-cli/config.json - Permissions:
0o600(owner read/write only) - Contents: Notion API token and user preferences
- Atomic writes: Configuration is written to a temporary file and atomically renamed to prevent corruption
- Path:
~/.notion-cli/databases.json - Permissions:
0o600(owner read/write only) - Directory permissions:
0o700(owner access only) - Contents: Cached database metadata (IDs, titles, aliases) -- no page content or sensitive data
- Atomic writes: Cache is written to a temporary file and atomically renamed to prevent corruption
- Token masking: All CLI output masks tokens by default, displaying only the prefix and last 3 characters (e.g.,
secret_***...***abc) - Opt-in reveal: The
--show-secretflag is required to display the full token value - Environment variable:
NOTION_TOKENis the primary token source; it takes precedence over the config file - No token logging: Tokens are never written to log files or included in error reports
Please do not report security vulnerabilities through public GitHub issues.
-
Email: Send details to
jake@coastalprograms.com -
Include:
- Type of vulnerability
- Full paths of source files related to the vulnerability
- Location of affected code (tag/branch/commit)
- Step-by-step instructions to reproduce
- Proof-of-concept or exploit code (if possible)
- Impact of the vulnerability
- Suggested fix (if you have one)
-
Response Time:
- Initial response: Within 48 hours
- Status update: Within 7 days
- Resolution timeline: Depends on severity
- Acknowledgment: We will confirm receipt of your report within 48 hours
- Investigation: We will investigate and validate the vulnerability
- Updates: You will receive updates on our progress every 7 days
- Resolution: We will work on a fix and coordinate release timing
- Credit: You will be credited in release notes (unless you prefer anonymity)
- We follow coordinated disclosure
- Please allow us reasonable time to address the issue before public disclosure
- We aim to release security fixes within 90 days of initial report
- Critical vulnerabilities may be expedited
NEVER commit your Notion token to version control.
# BAD - Don't do this
export NOTION_TOKEN=secret_abc123...
git add .env
git commit -m "Add config"
# GOOD - Use environment variables
export NOTION_TOKEN=secret_abc123... # In shell session only
# Or add to ~/.bashrc, ~/.zshrc (never commit)
# GOOD - Use the built-in config command
notion-cli config set-token
# Stores token in ~/.config/notion-cli/config.json with 0o600 permissionsBest Practices:
- Use environment variables for tokens, never hardcode them
- Add
.envto.gitignoreif using env files - Rotate tokens regularly (every 90 days recommended)
- Use minimal permissions -- only grant integration access to needed databases
- Revoke unused tokens at https://www.notion.so/my-integrations
Follow the principle of least privilege:
- Create an integration at https://www.notion.so/my-integrations
- Share ONLY the databases you need to access
- Review permissions regularly
- Do not share your entire workspace if you only need a few databases
After installing via npm, verify the binary is the expected one:
# Check version and build info
notion-cli --version
# Verify the binary path
which notion-cli
# Run health check
notion-cli doctorWhen using in CI/CD pipelines:
# GOOD - Use encrypted secrets
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
# BAD - Never expose tokens in logs
- run: echo "Token: $NOTION_TOKEN" # DON'T DO THISBest Practices:
- Store tokens in encrypted CI/CD secrets
- Never print tokens in logs
- Use read-only tokens when possible
- Audit CI/CD logs for accidental token exposure
- Rotate tokens if exposed in logs
If you are contributing code:
- Never commit secrets -- Use environment variables
- Validate all inputs -- Sanitize user input via the resolver and error packages
- Use
context.Contextfor all API calls to support timeouts and cancellation - Follow least privilege -- Minimize API permissions in integration code
- Keep dependencies minimal -- The project intentionally uses only 2 Go dependencies
- Write security tests -- Test authentication, authorization, and input validation
- Use
internal/errors.NotionCLIError-- Never expose raw errors that could leak sensitive information
Security considerations for code reviewers:
- No hardcoded credentials or tokens
- Input validation for user-provided data (IDs, URLs, JSON)
- Error messages do not leak sensitive information
- File operations use restrictive permissions (0o600 for files, 0o700 for directories)
- No command injection vulnerabilities (no
os/execwith user input) - Token values are masked in all output paths
- Atomic file writes used for persistent data
The OAuth client secret used to sign authorization-code exchanges is embedded
in every released binary via -ldflags -X. If it leaks (committed to a repo,
pasted into a chat, etc.), every released binary becomes a liability until the
secret is rotated AND a new release is published.
⚠️ Rotation REQUIRES an immediate republish. Because the secret is baked into the binary at build time, regenerating it in the Notion dev portal instantly breaks every already-published binary: the OAuthauthorizestep keeps working (it only sendsclient_id), but the token exchange sendsclient_id:client_secretand Notion rejects the stale pair withinvalid_client. Users see the browser say "You're all set" and thenauth loginfails to store a token. Do not rotate the secret unless you can cut a new release in the same change. After publishing, tell users to upgrade:npm i -g @coastal-programs/notion-cli@latest.notion-cli doctorvalidates the embedded secret (the "OAuth Secret" check) and the release pipeline (publish.yml) fails the build if the embedded pair is rejected, so a botched rotation cannot silently ship a broken binary.
Rotate immediately if any of the following has happened:
- The secret was committed to a public or private repo (search history with
git log -p -S 'secret_'). - The secret was pasted into a chat, ticket, screenshot, or screen share.
- A maintainer who had access to the secret has left the team.
- More than 12 months have passed since the last rotation.
-
Generate a new secret in the Notion dev portal.
- Open https://www.notion.so/my-integrations
- Select the
notion-cliintegration - Under "OAuth Domain & URIs" → "Client secret", click "Regenerate"
- Copy the new secret (Notion shows it only once — store it now)
-
Update the maintainer dev dotfile at
~/.config/notion-cli-dev/.env:NOTION_OAUTH_CLIENT_ID=<unchanged client id> NOTION_OAUTH_SECRET=<new secret here>Permissions must be
0600:chmod 600 ~/.config/notion-cli-dev/.env -
Update the CI repository secret:
gh secret set NOTION_OAUTH_SECRET --repo Coastal-Programs/notion-cliPaste the new secret when prompted.
-
Publish a new release immediately so end users get a binary built with the new secret. This step is mandatory and time-critical: until it lands, every installed binary returns
invalid_clientonauth login.make releasewill refuse to build if the SHA-256 ofOAUTH_CLIENT_SECRETmatches the historically-leaked value (seeLEAKED_OAUTH_SECRET_SHA256in the Makefile). The check uses a hash instead of the literal value so the tripwire itself doesn't trip GitHub's secret-scanning push protection. Thepublish.ymlworkflow additionally performs a live token-endpoint probe of the embeddedclient_id/client_secretpair and fails the release oninvalid_client, so a mismatched or mis-pasted secret cannot ship. -
Tell users to upgrade once the release is live:
npm i -g @coastal-programs/notion-cli@latest, thennotion-cli auth login. A stale local install keeps embedding the old (now-revoked) secret and will keep failing withinvalid_clientuntil upgraded.notion-cli doctorsurfaces this via the "OAuth Secret" check. -
Revoke the old secret in the Notion dev portal once the new release is live and adopted. If the portal does not keep the old secret active during rollover, expect existing installs to break the moment you regenerate — which is exactly why step 4 must happen immediately.
As of 2026-05, the maintainer's local dev secret lives at:
~/.config/notion-cli-dev/.env
outside the repo tree, on purpose. The previous location, .env.local in
the repo root, is legacy and should be deleted once you have migrated:
shred -u .env.local # or `rm -P` on macOSThe Makefile reads from the new path automatically; CI is unaffected because it injects the same variables via repository secrets.
Example Timeline for Critical Vulnerability:
- Day 0: Vulnerability reported
- Day 1: Acknowledgment sent to reporter
- Day 2-7: Investigation and validation
- Day 8-14: Develop and test fix
- Day 15: Release security patch
- Day 16: Public disclosure (coordinated with reporter)
Example Timeline for Low/Moderate Vulnerability:
- Day 0: Vulnerability reported
- Day 1-2: Acknowledgment and initial assessment
- Day 3-30: Investigation and fix development
- Day 31-60: Testing and release preparation
- Day 61: Release with next scheduled version
- Day 62: Public disclosure
Security Issues: jake@coastalprograms.com
General Issues: https://github.com/Coastal-Programs/notion-cli/issues
Discussions: https://github.com/Coastal-Programs/notion-cli/discussions
Last Updated: 2026-03-01 Next Review: 2026-06-01