What nable touches,
and what it never can.
Every outbound call, every file it writes, every permission it asks for. Written to be checked rather than believed: the source is Apache-2.0, and the one command below prints the same list from the copy running on your machine.
$ nable scan --dry-run # every API call and IAM permission a scan makes, without running one
Outbound calls
The complete list. Everything else in the codebase pointing at a URL is a documentation link printed for you to click, not a request nable makes.
| Destination | When | What is sent |
|---|---|---|
| your cloud provider APIs | every scan / query | Read-only calls to your accounts using credentials already on the machine. AWS, Azure, GCP, and any SaaS or AI provider you connect. This is the work; nothing here reaches us. |
| pypi.org | version check | A GET for the latest published version number. No identifiers. Off with FINOPS_NO_UPDATE_CHECK=1 or air-gap mode. |
| us.i.posthog.com | only if you opt in | Anonymous usage events. Off by default. Requires NABLE_TELEMETRY=1. See the section below for the exact fields. |
| bench.nable.dev | only if you opt in | Anonymised peer benchmarks. Off by default. Requires NABLE_BENCHMARKING_ENABLED=true. Ratios only, never dollar amounts; account IDs hashed with a per-install salt. |
| api.anthropic.com | only if you set a key | The optional analyst chat. If you never set ANTHROPIC_API_KEY, nable never calls a model. Scans, detections and the overnight brief are fully deterministic and never call one regardless. |
| Slack / Teams / Jira / Linear | only if configured | Alerts and tickets you asked for, to the webhook or workspace you supplied. |
What it writes, and where
Everything is local. There is no nable-hosted database in the open-source product, so there is nowhere else for it to be.
One detail worth stating in the right order: the master key is read from the file first and the keyring second, not the other way round. On macOS a keychain read from an unsigned interpreter can pop a permission dialog, and uvx builds a fresh interpreter on every release, so a keychain-first vault prompted people on every upgrade. Reading a 0600 file is silent. Set FINOPS_VAULT_KEYCHAIN_ONLY=1 to invert it and take the prompts.
| Path | Contents | Protection |
|---|---|---|
| ~/.finops/ | SQLite database: cost snapshots, findings, budgets, the savings ledger. Override with FINOPS_DATA_DIR. | Directory 0700, owner only |
| ~/.finops/audit.log | Every tool call made against your account, locally, for your own review. | Owner only |
| ~/.finops/cache.db | Cached provider responses, so repeat questions do not re-bill metered APIs. | Inside the 0700 directory |
| ~/.finops/vault.db | The credentials you hand nable, encrypted. This is where they live: not in the keyring, not in a config file, not in an environment file anything else can read. | Fernet (AES-128-CBC + HMAC) |
| ~/.finops/vault.key | The master key that decrypts the vault, cached as a file so a read is silent. | File 0600 |
| OS keyring | A durable second copy of that master key, never the credentials themselves. macOS Keychain, Windows Credential Manager, libsecret. | OS-managed |
| ~/.config/finops/ | Install ID (a random UUID), and first-run markers. No credentials. | Files 0600 |
Telemetry, in full
Off by default. It used to be opt-out. That was the wrong default for a tool that runs on your machine against your cloud account, and it changed after someone asked the question plainly on r/selfhosted.
| Sent, if you opt in | Never sent |
|---|---|
| A random install UUID Event names (e.g. scan started) Which tools were invoked, by name Count of connected providers nable version, Python major.minor, install method, OS family Error class and exception type on failure |
Cost or billing figures Cloud account IDs or ARNs Resource names or IDs File paths Query content Credentials IP addresses (dropped server-side) |
Turn on: NABLE_TELEMETRY=1 · Guarantee off: NABLE_NO_TELEMETRY=1 (wins over everything, so if you set it once in a dotfile it stays off)
Least privilege, per provider
nable only ever reads. But a credential you hand it can be more powerful than the reading it does, and that is the part worth checking. Some providers offer a billing-only permission and nable asks for exactly that. Some offer nothing narrower than your whole account. A table that graded all of them "least privilege" would be wrong in the one place it matters, so each row says which.
$ nable connect --scopes # the same table, printed by the copy on your machine, before any credential is read
| Provider | Tightest credential | Grade | Notes |
|---|---|---|---|
| AWS | nable scan --dry-run --json prints the exact policy | billing only | Every action in it is a Describe, List or Get. Cost Explorer is not included unless you pass --spend, because AWS bills $0.01 per request. |
| Cloudflare | API token, Account > Billing > Read | billing only | One permission. The token cannot read DNS, edit zones or touch Workers. |
| Datadog | App key scoped to usage_read + billing_read | billing only | An unscoped app key inherits everything you can do in Datadog. Scoped to those two, it reads cost. |
| MongoDB Atlas | Organization Billing Viewer | billing only | Cannot read a cluster, a database, or a single document. |
| Twilio | Restricted API key, Usage read | billing only | Your Auth Token can send messages and buy numbers, so nable takes a Restricted API key instead when you set one. |
| Azure | Cost Management Reader | via a role | Built-in read-only role. Assign it at the billing or subscription scope, not at tenant root. |
| GCP | roles/billing.viewer + BigQuery Data Viewer | via a role | The second can be narrowed to the single billing-export dataset. |
| Snowflake | A role with IMPORTED PRIVILEGES ON DATABASE SNOWFLAKE | via a role | Not ACCOUNTADMIN. Reaches two metering views and nothing in your own databases. |
| New Relic | User key minted by a read-only user | via a role | New Relic has no billing-only key scope. The key inherits its user's role. |
| Langfuse | Project key pair | via a role | Scoped to the one project it was created in. |
| Databricks | Account admin | account-wide | The billable-usage download API is account-admin only. A narrower path exists through system.billing.usage and nable does not query it yet; that gap is recorded in the manifest, not hidden. |
| OpenAI | Admin key (sk-admin-…) | account-wide | The Costs API accepts nothing less, and an admin key can also manage keys and members. That is OpenAI's design, not a choice nable makes, and it is the broadest credential nable accepts. |
| Anthropic | Admin key plus organization ID | account-wide | Same shape: the cost endpoint is admin-gated. |
| Vercel | Access token, team-scoped | account-wide | No per-permission scopes exist. Narrow it to one team and set the shortest expiry you can live with. |
| OpenRouter, Modal, Together, Replicate, Cohere, Mistral |
API key | account-wide | None of them offer a billing-only scope today. For OpenRouter, a standard key still returns your credit balance if the provisioning key is more power than you want to hand over. |
What it can and cannot do
Can
- Read cost, usage and billing data
- Read resource metadata and CloudWatch metrics
- Write findings to your local database
- Draft a change as commands you run yourself
- Open a pull request, if you enable it
- Send an alert to a channel you configured
Cannot
- Delete, stop, resize or terminate anything
- Purchase a reservation or commitment
- Modify IAM, security groups or networking
- Apply a change to any cloud, ever
- Send your cost figures to us
- Accept a secret as a tool argument
The delete path is not a policy either. It was removed: an earlier version had a cleanup tool that could terminate resources, and a test now walks every file in the package and fails on any call that would change cloud infrastructure.
Verifying any of this
$ nable scan --dry-run # every call and permission, nothing executed $ nable scan --dry-run --json # the same, as a pasteable IAM policy $ nable connect --scopes # the tightest credential each provider can be given $ FINOPS_AIRGAP=1 nable scan # provider APIs only, nothing else outbound
Source: github.com/getnable/finopsmcp, Apache-2.0. Security contact and disclosure policy: /security. If something here does not match what the code does, that is a bug and we want the report.