feat(security): the tightest credential each provider can be given, stated per provider - #96
Merged
Merged
Conversation
…tated per provider
Asked on r/selfhosted: can the scope be reduced down to billing? For some
providers, exactly. For others the honest answer is no. The useful thing is
saying which is which, because a table that grades everything "least privilege"
is wrong in the one place it matters.
connector_scopes.py is that table, and every entry carries a grade:
scoped The provider has a billing-specific permission and nable uses exactly
it. A leaked credential reads billing and nothing else.
role No per-key scope, but the key can sit behind a limited role or user.
account No narrower option exists. The credential is as powerful as the
account it was minted from. Said plainly.
Twilio moves from account to scoped, and that one is code rather than
documentation. nable asked for the Account SID and Auth Token, a credential that
can send messages, buy numbers and spend money, to read a usage report. Twilio
supports Restricted API keys on the Usage API, so nable now takes
TWILIO_API_KEY / TWILIO_API_SECRET and prefers them when both are present. The
Auth Token still works, so nobody's setup breaks because a better option exists.
The account lookup is not covered by a Usage-only key and degrades to a default
label, which costs a display name and nothing else.
The ambient scan learned to see that credential. It matched on one fixed key set
per provider, so a machine holding only the safer Twilio credential would not
have been found: the scan would have quietly punished the better choice.
Datadog moves from account to scoped without code. An unscoped application key
inherits everything you can do in Datadog; usage_read and billing_read reduce it
to cost. The setup hint now says so, and every hint interpolates the manifest's
permission string rather than retyping it, so what a user is told and what the
trust page publishes cannot drift.
Databricks is graded account and records a known gap: the billable-usage
download API is account-admin only, while system.billing.usage needs three
grants. nable does not query it over SQL yet. A manifest that only records good
news is marketing, so the gap ships in the open and renders in the output.
`nable connect --scopes` prints the whole table before any credential is read,
worst grade first.
15 guards, 57 cases. They pin that every registered billing source has an entry,
that "scoped" is never spent on a hand-wave, that the broad ones stay labelled
broad, and that the setup hints still quote the manifest verbatim. Full suite
green at 2254.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nner
CI's secret scan flagged six lines in the new test file. They are fixtures
("tok", "sec", "auth-token-value"), not secrets, but the scanner is right to
stop at an assignment named TWILIO_AUTH_TOKEN, and the fix belongs at the line
rather than in the baseline: a pragma says "this is fake" where a reader will
see it, while a baseline entry hides it behind a hash nobody opens.
The local suite could never have caught this. detect-secrets runs in CI only,
so a green pytest was never evidence about it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous pass annotated four of the eight fixture lines and I checked it with a local run that exited 0 against a baseline CI does not have, so a green exit proved nothing. Verified this time the way CI verifies: the same detect-secrets-hook invocation, plus an assertion that the baseline file comes out unmodified, because the hook rewrites it and then fails on the dirty tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getnable
force-pushed
the
feat/least-privilege-scopes
branch
from
August 8, 2026 23:29
f923e95 to
b203199
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Asked on r/selfhosted: can the scope be reduced down to billing? For some providers, exactly. For others the honest answer is no. The useful thing is saying which is which, because a table that grades everything "least privilege" is wrong in the one place it matters.
connector_scopes.pyis that table. Every entry carries a grade:scopedroleaccountTwilio: account → scoped, and this one is code
nable asked for the Account SID and Auth Token, a credential that can send messages, buy numbers and spend money, in order to read a usage report. Twilio supports Restricted API keys on the Usage API, so nable now takes
TWILIO_API_KEY/TWILIO_API_SECRETand prefers them when both are present.The Auth Token still works, so nobody's setup breaks because a better option now exists. The account lookup is not covered by a Usage-only key and degrades to a default label, which costs a display name and nothing else.
The ambient scan learned to see that credential too. It matched on one fixed key set per provider, so a machine holding only the safer Twilio credential would not have been found: the scan would have quietly punished the better choice.
Datadog: account → scoped, no code needed
An unscoped application key inherits everything you can do in Datadog.
usage_readandbilling_readreduce it to cost. The setup hint now says so, and every hint interpolates the manifest's permission string rather than retyping it, so what a user is told and what the trust page publishes cannot drift.Databricks: still account, and the gap is recorded
The billable-usage download API is account-admin only.
system.billing.usagecan be read with three grants instead, and nable does not query it over SQL yet. That ships as agapfield, renders in the output, and is asserted by a test. A manifest that only records good news is marketing.The rest
Also graded honestly rather than talked up: OpenAI and Anthropic admin keys (the cost endpoints are admin-gated, and those keys can manage members), Vercel (team scope and an expiry, no per-permission scopes), New Relic (the key inherits its user's role), Snowflake (a dedicated role reaching two metering views), MongoDB Atlas and Cloudflare (billing-only by construction).
Surface
Prints the whole table before any credential is read, worst grade first, because bad news should not be buried under the good news.
Tests
15 guards, 57 cases: every registered billing source has an entry, no entry documents a source that does not exist,
scopedis never spent on a hand-wave like "no billing-only scope", the broad ones stay labelled broad, the Databricks gap survives, the setup hints still quote the manifest verbatim, and the Twilio credential swap does not move the account SID out of the URL path.Full suite green at 2254 passed, 2 skipped.
🤖 Generated with Claude Code