Data safety¶
Nothing leaves the NetBox process through this plugin unless it appears in this page. Logs, audit and metrics are each built from an allowlist of names and attribute keys: an instrumentation library adding a new attribute later does not add a new way for that signal's data to leave, since only listed names are ever read or exported. Traces work differently: an instrumentor's other standard attributes (route, method, client address, user agent, and so on; see Traces, spans) are exported as recorded, and a SpanProcessor ahead of the exporter instead redacts a fixed set of known-sensitive attributes and patterns (headers, URL queries, bind parameters that are never captured in the first place, exception text) before each span reaches the exporter; see Traces in detail below.
Audit records, the one signal that can include the full field values of a changed object, are off by default (audit.include_data = False) and, when turned on, are filtered before export, not after. Exporter header values (the credential a Collector or backend needs) never appear in a log line, in the DEBUG-level dump of the resolved configuration, or in an exception message the plugin logs about a failed setup. No SQL bind parameter, no HTTP header value and no URL query string, inbound or outbound, is exported in a span from a TracerProvider the plugin builds itself (see Traces in detail for how each is kept out, and the one case, a TracerProvider configured outside the plugin, where this does not hold). Metrics carry only the instrument names and attribute keys listed in Metrics; everything else an instrumentation library records is dropped before it reaches an exporter. Every exported span keeps only the exception type in its status description and in any exception event; the message and stack trace are not exported, though both remain available in log records, which are not scrubbed for the same content (see Traces below).
By signal¶
| Signal | Exported | Never exported |
|---|---|---|
| Logs | Formatted message body; severity; code.file.path, code.function.name, code.line.number, logger.name, thread.name; exception.type, exception.message, exception.stacktrace when the record carries exception info; the active span's trace and span id, or, when no span is current, the trace id, span id and trace flags of the request span that RequestSpanMiddleware stored on the record's extra={"request": ...} object (see Logs, record fields; that object is read only for these ids) |
Any extra= field itself (the request object above included), including one whose key shadows an allowlisted name; records from netbox_opentelemetry_plugin, opentelemetry, urllib3 and grpc (the feedback-loop filter) |
| Audit | Change metadata only by default: id, action, object type and id, object representation, request id; message and related object only when set; the change's user_name as enduser.id, only when non-empty |
Field data (prechange_data, postchange_data) unless audit.include_data is on, and even then only after recursive filtering by audit.exclude_fields (case-insensitive substring match on key names, at every nesting level) |
| Traces | Route-templated span names, status; the instrumentors' own standard HTTP/DB semantic-convention attributes, exported as recorded rather than from an allowlist, including at least client.address, user_agent.original, url.path, http.route, server.address, http.request.method and db.statement with no bind values (see Traces, spans for the full list); netbox.request_id and enduser.id on the request span; each span's trace context, including its trace_state (the inbound tracestate, continued as received, when the request carried one); the trace context (traceparent, and tracestate when present, or the headers of the format OTEL_PROPAGATORS selects) injected on an instrumented outbound call; only the W3C trace context (traceparent, and tracestate when present) copied into an RQ job's meta |
No SQL bind parameter (never captured by the instrumentor in the first place); no HTTP header value on a span from the plugin's own TracerProvider (redacted before export, whatever an instrumentor's own configuration added); no URL query string, inbound or outbound, on such a span (url.query becomes REDACTED, url.full/http.url/http.target are truncated at ?REDACTED); a status description or exception event's free text has any path?query replaced with path?REDACTED; every exported span's status description is cut to the text before its first :, and exception.message/exception.stacktrace are dropped from every exception event, keeping only exception.type, exception.escaped and other attributes; baggage is never copied into job.meta (only the W3C trace context is); no baggage, inbound or outbound |
| Metrics | Only the instrument names and attribute keys listed in the metrics table; process.* and cpython.gc.* keep their own instrumentor-set attributes, since those describe process state, not requests |
Any instrument or attribute not in the allowlist, for example the Django instrumentor's http.server.active_requests, or server.port/url.path on the two HTTP histograms |
| Configuration | Nothing over OTLP | Exporter header values, in any log message, at any level, including the resolved configuration dump the plugin writes once, locally, to its own logger at DEBUG (never exported itself, see Logs, feedback loop); credentials embedded in a URL (scheme://user:pass@host or a bare token scheme://token@host) in a warning or that local dump |
Traces in detail¶
- Capturing header values is not something the plugin ever turns on: an instrumentor can be told, through its own environment variables (read by the instrumentation library itself, not by this plugin), to add
http.request.header.*/http.response.header.*attributes to a span. Whatever an operator's own instrumentor configuration adds this way, the plugin'sRedactingSpanProcessorremoves every such attribute before the span reaches the exporter, on anyTracerProviderthe plugin builds itself. This is redaction after the fact, not an allowlist: see the note on denylist-versus-allowlist below. It does not apply to aTracerProviderconfigured outside the plugin and reused; see What a provider configured outside the plugin changes below. - psycopg spans record the statement text but never bind parameters (
capture_parameters=False), and no SQL comment is appended (enable_commenter=False); this one is an allowlist-style omission at the source, not something redacted after the fact. - Redaction runs in a
SpanProcessorahead of the exporting one, at span end, and applies to every exported span regardless of kind:http.request.header.*/http.response.header.*attributes are removed outright;url.querybecomesREDACTED;url.full,http.urlandhttp.targetkeep everything up to and including the?, with the query itself replaced. This is a denylist, not an allowlist, unlike logs, audit and metrics: everything else an instrumentor attaches to a span is exported as is, which is why the instrumentors' other standard attributes (client.address,user_agent.original,url.path,http.route, and so on; see Traces, spans) reach the exporter unfiltered. A span that fails to redact is dropped rather than exported as is, since exporting an unredacted span is worse than exporting nothing. - This covers an error surfacing on any span kind, not only the CLIENT spans an instrumentor sets URL attributes on directly: a PostgreSQL error (for example a unique constraint violation's
DETAIL, which can carry the offending values) can surface unhandled on the request's SERVER span or a job's CONSUMER span just as easily as on the psycopg query span itself, and the same scrubbing applies there too. - The full, unredacted exception text is still available in log records (
exception.message,exception.stacktraceon theAllowlistLoggingHandler's output, see Logs), and a log record'sexception.messageis not scrubbed for URL query strings. This is a deliberate scope boundary in the code, not an oversight: span redaction and log record content are two different code paths (otel.redact_spanversusotel.AllowlistLoggingHandler), and only the former scrubs query strings. - Baggage is dropped at extraction and never injected: the plugin wraps the global propagator that Django's and requests' instrumentors use, with traces on or metrics only, so a client's
baggageheader is never attached to the request context or forwarded on an outbound call.OTEL_PROPAGATORSstill selects the trace-context format. See Traces, baggage. - An inbound
tracestateheader is kept as received, as W3C Trace Context requires: it is carried on every span of the request, exported in each span'strace_state, and injected on the request's outbound calls, so a client can place arbitrary vendor entries (up to 32 members) there. Strip the header at a proxy in front of NetBox if that matters for your deployment. - An inbound
traceparentheader is honoured with the defaultparentbased_traceidratiosampler, as standard OTel behaviour: a client can force sampling and choose the trace id for its own request. Use a non-parentbased sampler, or strip the header at a proxy, if an operator's deployment must not let a client dictate this.
Configuration masking¶
exporter.headersvalues are never logged:ExporterConfig.redacted()replaces every header value with***before the resolved configuration is logged atDEBUG, and the same masked form is what appears if a header value would otherwise show up in an exception message the plugin logs about a failed setup (installing a module, or rebuilding one after a fork; a failure while actually sending an export is logged by the OTel SDK's own exporter code, on its ownopentelemetry.*logger, not through this path).- An endpoint URL's credentials (
https://user:pass@host/...) are masked tohttps://***@host/...in the DEBUG-level configuration dump and in warning and exception text, by stripping the URL's own userinfo component before logging (conf._redact_userinfo). - On top of that, warning and exception messages the plugin builds around a caught setup or fork failure (
bootstrap._describe, used for every warning logged from inside such anexceptblock) run two additional regular expressions over the (already length-bounded) text: one matching aword:word@shape and one matching a bare//word@shape, either of which gets masked to***@, whatever produced the text. This is deliberately broader than "is this actually a valid URL userinfo": it favours false positives, masking some text that only happens to look like credentials, over ever leaving real credentials expressed in that shape unmasked in a warning.
TLS verification¶
By default every HTTPS or TLS gRPC export verifies the Collector's certificate. With exporter.insecure_skip_verify = True (HTTP only), it does not: anyone able to intercept the connection between NetBox and the Collector can read or alter everything exported, header values included, and the plugin cannot tell. The plugin logs one warning per process and endpoint when this is on. urllib3 also emits its own InsecureRequestWarning through Python's warnings module; the plugin leaves that alone, since filtering it would be process-wide and would also hide it for NetBox's own outbound requests. Use exporter.certificate with the Collector's CA file instead whenever that file is available.
What a provider configured outside the plugin changes¶
When a TracerProvider, MeterProvider or LoggerProvider is already set globally before NetBox starts (for example under opentelemetry-instrument) and the plugin reuses it instead of building its own (see How it works), several of the guarantees above stop being the plugin's to enforce:
- Span redaction (headers, URL queries, exception text) and the parentless-CLIENT-span filter live in the sampler and span processor the plugin builds itself; an externally configured
TracerProviderdoes not get them, so what such a provider exports is governed entirely by whatever configured it. - The metrics allowlist is a set of Views on the plugin's own
MeterProvider; an externally configured one is not restricted by it. - The larger log record queue the plugin sizes for audit bursts (see Audit records, sizing) is set only on a
LoggerProviderthe plugin builds; an externally configured one keeps whatever queue size it already had.
Header masking, endpoint credential masking and the metrics allowlist described above apply only to exporters and providers the plugin itself builds; they say nothing about what an operator's own OTel SDK configuration does.
See Failure behaviour for what happens when a module cannot be built at all, and Limitations for the fuller list of edge cases across every signal.