Updated: v0.29.0 · 6 providers + plugins · 4 notification channels + digest · 5 security headers · Rate limiting
| Data | How | Why |
|---|---|---|
| Language server process list | ps aux / CIM / Get-Process |
Detect running Antigravity instance |
| Language server command-line args | Process inspection | Extract CSRF token and port number |
Local HTTP endpoint (127.0.0.1) |
Single HTTP POST per snap | Fetch quota data via Connect RPC |
Codex auth file (~/.codex/auth.json) |
File read | OAuth token for Codex API polling |
Claude settings (~/.claude/settings.json) |
File read + optional patch | Statusline bridge for rate limits |
Claude session logs (~/.claude/projects/) |
File read | JSONL session parsing for token analytics |
| Cursor session token | File read from ~/.cursor-server/ |
HTTP API authentication |
| GitHub Copilot PAT | User-provided in Settings UI | GitHub billing API authentication |
| Dashboard API token | Generated config secret | Authenticate every /api/* and HTTP /mcp request |
| Plugin scripts | Subprocess execution from ~/.niyantra/plugins/ |
Execute operator-trusted local scripts only through the opt-in polling path; HTTP/manual execution is disabled |
- No programmatic account switching (see "Why Not Account Switching" below)
- Provider and notification secrets are stored in the OS credential manager by default; SQLite keeps only opaque references unless the operator explicitly enables the insecure plaintext fallback
- No telemetry, analytics, or phone-home
- Core Niyantra writes stay inside its own database directory; operator-trusted plugins can read/write anywhere their subprocess permissions allow
niyantra snap: 1 HTTP POST tohttps://127.0.0.1:{port}(self-signed TLS, local LS)niyantra serve: Binds HTTP server onlocalhost:9222(configurable)niyantra mcp: stdio only, no networkniyantra status: 0 network calls (reads from SQLite)
- Codex: HTTPS to
auth0.openai.com(OAuth token refresh) + OpenAI API - Cursor: HTTPS to
cursor.com/api/usage - GitHub Copilot: HTTPS to
api.github.com(billing endpoints)
- SMTP: TCP to configured SMTP server (plain/STARTTLS/TLS)
- Webhook: HTTPS POST to configured endpoint (Discord/Telegram/Slack/ntfy)
- WebPush: HTTPS POST to push service (Chrome FCM, Firefox autopush, etc.)
Note: All external network calls are opt-in and disabled by default. The dashboard works fully offline with only Antigravity LS detection.
The Antigravity language server uses a self-signed certificate. Niyantra connects with InsecureSkipVerify: true because:
- The connection is to
127.0.0.1(localhost only) - The CSRF token provides request authentication
- The alternative (no TLS) would be less secure
Config keys containing secrets are masked before API transmission. When returned via GET /api/config or PUT /api/config response, the actual values are replaced with "configured".
| Key | Purpose |
|---|---|
copilot_pat |
GitHub Personal Access Token |
cursor_session_token |
Cursor session authentication cookie |
smtp_pass |
SMTP authentication password |
smtp_user |
SMTP authentication username |
webhook_secret |
Webhook authentication secret (Telegram bot token, etc.) |
webpush_vapid_private |
VAPID P-256 private key |
dashboard_api_token |
Bearer token for dashboard API and HTTP MCP |
Plugin config keys matching these suffix patterns are automatically masked:
_api_key, _token, _secret, _password, _pat, _credential
For example, plugin_weather_api_key is masked automatically.
All HTTP responses include the following security headers:
| Header | Value | Purpose |
|---|---|---|
Content-Security-Policy |
default-src 'self'; script-src 'self'; ... |
Prevents XSS by restricting script sources |
X-Frame-Options |
DENY |
Prevents clickjacking via framing |
X-Content-Type-Options |
nosniff |
Prevents MIME type sniffing |
Referrer-Policy |
strict-origin-when-cross-origin |
Limits URL leakage in Referer header |
Permissions-Policy |
camera=(), microphone=(), geolocation=(), payment=() |
Disables unnecessary browser features |
Every /api/* and HTTP /mcp request requires Authorization: Bearer <dashboard_api_token>. niyantra serve creates the token with cryptographic randomness if missing and prints a tokenized first-open URL; the frontend stores the token in sessionStorage and strips it from the address bar. Use niyantra token show to print the current token and niyantra token rotate to invalidate old browser/API sessions. /healthz and static UI assets are intentionally unauthenticated.
Optional HTTP basic auth via --auth user:pass flag or NIYANTRA_AUTH environment variable can be layered on top for dashboard access. No cookie session is created. Basic auth is per-request and not confidential over plaintext HTTP.
Non-local Bind Gate: Niyantra binds to 127.0.0.1 by default. To bind a non-loopback address you must opt in with --allow-remote / NIYANTRA_ALLOW_REMOTE=true, enable --auth user:pass, and acknowledge TLS termination with --behind-https-proxy / NIYANTRA_BEHIND_HTTPS_PROXY=true. Basic auth is only an HTTP authentication scheme; it is not confidential over plaintext HTTP. Streamable HTTP MCP follows the same non-local bind policy.
Per-IP in-memory token bucket rate limiter protects all mutation endpoints from abuse:
| Tier | Endpoints | Limit | Window |
|---|---|---|---|
snap |
Provider snapshot routes such as POST /api/snap, POST /api/codex/snap, POST /api/cursor/snap, POST /api/copilot/snap |
10 requests | 1 minute |
mutate |
Dashboard writes such as config, subscriptions, backup create, notification tests, web push, alerts, pricing, account metadata/deletes, snapshot adjustment/deletes, and plugin config/run compatibility route | 30 requests | 1 minute |
import |
POST /api/import/json |
2 requests | 1 minute |
When exceeded: 429 Too Many Requests with Retry-After header. Zero external dependencies — uses sync.Mutex + background cleanup goroutine (stale buckets cleaned every 5 minutes).
PUT /api/config validates values against their declared schema types before persistence:
| Type | Validation |
|---|---|
bool |
Only "true" or "false" accepted |
int |
Must parse as integer; range-checked per key (poll_interval: 30-3600, retention_days: 30-3650) |
float |
Must parse as float; range-checked per key (notify_threshold: 5-50) |
string |
No validation (passthrough) |
Rejects malformed input with 400 Bad Request and a descriptive error message.
- All operational data is stored in a single SQLite file (default:
~/.niyantra/niyantra.db) - Provider and notification secrets are stored in the OS credential manager by default via
go-keyring - SQLite stores opaque secret references rather than plaintext credentials unless
--insecure-plaintext-secrets/NIYANTRA_INSECURE_PLAINTEXT_SECRETS=trueis explicitly enabled - Database backups created by
niyantra backuporPOST /api/backup/createredact sensitive config values from the copied SQLite file, includingdashboard_api_token, provider credentials, notification secrets, and plugin keys matching sensitive suffix patterns - WebPush VAPID keys auto-generated on first subscribe (P-256 ECDSA)
- Plugin API keys follow the same keychain-backed storage path as other secrets when their config key suffix matches the supported secret patterns
Plugins are operator-trusted local scripts, not a sandbox or marketplace boundary. Discovery rejects oversized manifests, manifest symlinks, and entry points that resolve outside the plugin directory. POST /api/plugins/{id}/run is retained only as a compatibility stub and returns 410 Gone; it never spawns a process. Plugin polling still requires explicit --enable-plugins / NIYANTRA_ENABLE_PLUGINS=true.
When cloud sync is enabled (opt-in):
| Concern | Mitigation |
|---|---|
| Data isolation | PocketBase Row-Level Security on every sync-eligible collection; the final collection set must be recalculated after schema v21 |
| Auth | OAuth 2.0 PKCE (no client_secret on user's machine) |
| Token storage | OS-native keychain via go-keyring (Win/Mac/Linux) |
| Secrets in sync | "Secrets Don't Sync" policy - future sync metadata must classify masked config keys as local-only before any cloud sync ships |
| Transport | TLS 1.3 via Caddy + Let's Encrypt (HTTPS everywhere) |
| MCP exposure | MCP reads local SQLite only — no direct cloud access from MCP |
| CORS | Eliminated via single-origin architecture |
| Admin access | PocketBase admin /_/ restricted by IP whitelist in Caddy |
| GDPR | "Delete Cloud Data" button must cascade every sync-eligible collection once the final cloud schema exists |
| At rest | Oracle Cloud boot volume encryption |
Every snapshot is tagged with:
capture_method:manualorautocapture_source:cli,ui, orserversource_id: which data source produced itcaptured_at: timestamp
This creates an audit trail — you can always verify how and when data entered the system.
If you discover a security issue, please email security@bhaskarjha.com rather than opening a public issue.
Niyantra deliberately avoids any feature that programmatically switches IDE accounts. This is a safety decision:
- Google's Trust & Safety systems use AI-driven anomaly detection that flags programmatic OAuth/RPC manipulation (e.g.,
registerGdmUser, automated token injection) as "botting" or "malicious behavior" - The punishment is account-wide: a flagged violation can result in suspension of the user's entire Google account (Gmail, Drive, Calendar, YouTube — not just the IDE service)
- Competitors that offer switching (e.g., AG Switchboard's one-click account switch) expose users to this risk by injecting OAuth credentials into the Language Server process
- Niyantra's principle: "Observe, never act" — we read quota data, we never write to or manipulate external services
This applies equally to:
- Proactive token refresh (automated credential management)
- IDE account auto-sync (detecting and reacting to external switches)
- Any feature requiring
registerGdmUseror similar RPC calls