Repository navigation
Conversation
`nbox serve --http --netbox-token-passthrough` (or `[serve].netbox_token_passthrough = true`) lets one process serve many users: each caller presents their own NetBox API token per request and nbox forwards it verbatim, so NetBox's object permissions and changelog apply to the real caller — no IdP and no `[serve.vault]` mapping. The token is read from `X-NetBox-Token`, or from `Authorization` when that header isn't already used by OIDC or `--http-token` (so a host that can only set a bearer works, without ever mistaking a JWT for a NetBox token). Each request builds its own client from the caller's credential. Isolation is a correctness requirement here, not an optimization, since NetBox returns different results to different users: - the read cache is partitioned per token fingerprint; - `nbox_cache_clear` drops only the caller's own partition; - `--allow-writes` runs writes under the caller's token. Fail closed: a request without a caller token is rejected with 401 and never downgraded to the server's profile token. Tokens are redacted in Debug, logs, and errors; the audit log carries `auth=netbox-token` plus a short SHA-256 fingerprint, which also keys the per-caller rate limit. Pass-through requires the HTTP transport — stdio has no per-request headers, so asking for it there is a usage error rather than a silent no-op — and it allows a routable bind, with TLS terminated in front. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ADR-0001 §7 documents MCP writes as running "in one of two explicit modes"; pass-through adds a third credential path, so the decision needs a record of the same rank. Follows the ADR-0002 precedent: what problem the mode solves (no per-user RBAC without an IdP and vault), the decision (caller NetBox API token forwarded verbatim, fail closed, cache partitioned per fingerprint), and the accepted trade-offs (TLS required in front, IP-keyed pre-auth rate limit, tokens in process memory). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
Problem
Every current serve mode uses a single NetBox credential. OIDC mode (Pattern 3) verifies the caller but still talks to NetBox as the service account — the docs call this "accountability, not per-user RBAC". Per-user writes exist (Pattern 2) but need an IdP plus a
[serve.vault]mapping. So a shared nbox server cannot reflect NetBox's own permissions per user.Motivation & discussion: #150
Change
Opt-in
--netbox-token-passthrough/[serve].netbox_token_passthrough: each caller sends their own NetBox API token per request (X-NetBox-Token, orAuthorizationwhen that header isn't already used by OIDC/--http-token), and nbox forwards it verbatim. NetBox's object permissions and changelog then apply to the real caller — no IdP, no vault.Security properties
nbox_cache_clearonly drops the caller's own partition (no cross-tenant cache-flush DoS).Authorizationis only read when it isn't already carrying a JWT/static bearer.Debug/logs/errors; the audit log recordsauth=netbox-tokenplus a short SHA-256 fingerprint, which also keys the post-auth per-caller rate limit.--http(stdio has no per-request headers, so that combination is a usage error rather than a silent no-op); a routable bind is allowed, with TLS terminated in front, same as OIDC mode.[serve.vault]is ignored in this mode, with a startup warning.--allow-writes, writes run under the caller's token through the same plan/apply two-step and audit as before.Notes for review
RequestContext::extensionshandoff thatwrite_caller_from_extensions()already uses for the OIDC identity; task-locals don't work here (the session manager runs its own worker task and the service factory has no request handle).*_tool(rmcp adapter) +*_impl(logic).#[cfg(test)]shims keep the old method names, so all existing test call sites are unchanged — deliberate, as a regression check on the refactor.docs/MCP.mdrather than silently inherited.Checks
cargo fmt --all -- --checkclean;cargo clippy --all-targets --all-features -D warningsclean;cargo buildclean;cargo test --all-features1404 passed / 0 failed (25 binaries). Rebased on currentmaster(rmcp 3.3).