Skip to content

Security

Content-free design

genai-otel-bridge never requests prompt text, completion text, message bodies, or any LLM inference content from the platform APIs it polls. It collects only operational signals: request counts, latencies, token counts, cost, error rates, and status codes.

This is enforced at three layers:

  1. Source strip — a default-deny allow-list in each source package drops every field not on the explicit list before any value enters the processing pipeline.
  2. Source.Guard content denylist — a defence-in-depth backstop at the composition root denies the AbsoluteNeverDenyKeys floor regardless of any opt-in: gen_ai.*, input.value, output.value, request, response, inputs, outputs, messages, metadata, portkeyHeaders.
  3. Conformance gate testsTestLogsExportContentLeakConformanceGate and TestLangsmithRunsContentLeakConformanceGate are release gate tests in internal/app. They assert the full wired pipeline produces zero content fields in the outbound OTLP payload and must remain green on every release.

See Content Governance for the full model.


SSRF egress guard

Outbound HTTP requests go through internal/httpx, a hardened client that enforces an egress guard at the dialer level:

Always blocked (even if AllowPrivate is set):

  • Cloud metadata endpoints: 169.254.169.254, 100.100.100.200, fd00:ec2::254
  • CGNAT range: 100.64.0.0/10
  • Link-local: 169.254.0.0/16, fe80::/10

Blocked unless AllowPrivate is true (dev/in-VPC use):

  • Loopback: 127.0.0.0/8, ::1
  • RFC-1918 private ranges

The guard runs at two points: the dialer Control hook (authoritative for direct dials) and a pre-dial hostname resolution check (checkDest) that resolves every IP before the transport. The guard fails closed on DNS resolution failure.

The Portkey logs_export loop adds a second layer for signed-URL downloads: the download URL is validated against settings.signed_url_allow_hosts with an exact host match before any fetch, independent of the dialer guard.


Cross-host redirect block

internal/httpx rejects redirects to a different hostname. Go strips Authorization and Cookie headers on cross-host redirects but forwards arbitrary custom headers — a source's vendor auth token (such as x-portkey-api-key) would otherwise leak to a different origin. The redirect check also re-runs the host allow-list and IP guard on every hop.


Secret handling

Credentials are never stored in git. The config file supports two secret reference forms:

auth:
  header: x-portkey-api-key
  value: ${PORTKEY_API_KEY}       # resolved from the environment
  # value: file:/run/secrets/key  # ...or resolved from a file path at load time

Plaintext credentials in the config file are accepted for dev use but should be avoided in production. Use Kubernetes Secrets with ExternalSecret / SecretStore for production deployments, or inject credentials via environment variables.

Credentials are never logged — the bridge redacts auth values in all log output and self-telemetry.


Kubernetes network policy

The Helm chart deploys a default-deny NetworkPolicy. Egress is whitelisted for:

  • DNS (CoreDNS pods in kube-system, by label selector — not a CIDR, which would break on some CNIs)
  • Kubernetes API server (apiServerCIDR — required for leader-election Lease and checkpoint ConfigMap)
  • OTLP endpoint (port 443, configurable CIDR)
  • Source APIs (port 443, configurable CIDR)

Ingress is default-deny except the health port (8080) from any source (required for kubelet probes). The pod cannot receive traffic from the product plane.

EKS

Network policy enforcement on EKS requires the VPC-CNI network-policy feature (or Calico) to be enabled.


TLS

All outbound HTTP connections require TLS 1.2 as a minimum. The internal/httpx client sets TLSMinVersion: tls.VersionTLS12. Connections to plaintext endpoints (HTTP, not HTTPS) require explicit opt-in via the emit.*.otlp.allow_insecure config flag, which should only be used for in-cluster collector deployments where the traffic does not leave the cluster.


RBAC (Kubernetes)

The pod's ServiceAccount is bound to a namespace-scoped Role (not a ClusterRole):

  • coordination.k8s.io/leases: get, create, update on the genai-otel-bridge-leader Lease only — the pod cannot touch any other Lease.
  • configmaps: get, create, update on the genai-otel-bridge-checkpoints ConfigMap only — the pod cannot read or modify its own genai-otel-bridge-config ConfigMap.
  • delete is not granted to the pod's ServiceAccount. The post-delete cleanup hook uses a separate ephemeral ServiceAccount that only exists during uninstall.

License

genai-otel-bridge is released under the GNU Affero General Public License v3.0 only (AGPL-3.0-only). Every source file carries the SPDX identifier:

// SPDX-License-Identifier: AGPL-3.0-only

Third-party dependencies retain their own upstream licenses. See LICENSING.md and the full license text.


Reporting a vulnerability

Do not open a public issue for security vulnerabilities. Report privately via GitHub's private vulnerability reporting (Security → Report a vulnerability on the repository).

Reports that demonstrate a way to make the service emit inference content, leak credentials, or bypass the egress or SSRF guards are particularly in scope.

See SECURITY.md for the full policy.


See also