You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Inference route API keys stored in plain object store #21
Inference route API keys are stored in full in the same SQLite or Postgres object store as every other entity. The InferenceRoute protobuf, including spec.api_key, is encoded to bytes and written as the row payload. There is no dedicated secret store, no encryption of sensitive fields at rest, and no redaction when routes are listed or returned. A compromise of the database file, a backup, or any dump of the objects table exposes all inference API keys.
Source Code
Route create/update in crates/navigator-server/src/inference.rs persist the full InferenceRoute (including spec.api_key) via store.put_message.
The store abstraction in crates/navigator-server/src/persistence/mod.rs implements put_message (lines 126-137) by encoding the message with message.encode_to_vec() and calling put(object_type, id, name, payload). The Postgres and SQLite backends write this payload as a blob with no per-field encryption or secret handling.
When listing routes, inference.rs (lines 258-261) decodes each record.payload as InferenceRoute and appends it to the response with no redaction of spec.api_key.
Originally by @drew on 2026-02-19T08:59:02.208-08:00
Inference route API keys are stored as plaintext within protobuf-encoded blobs in the SQLite/Postgres object store with no field-level encryption. All API responses (create, update, list, get_sandbox_inference_bundle) return the full api_key without redaction, meaning any caller with gRPC access can read all stored inference provider keys.
Severity Assessment
Impact: High — Compromise of the database (file theft, backup exposure, SQL injection in a future code path) or unauthorized gRPC access exposes all third-party inference API keys (e.g., NVIDIA, OpenAI, Anthropic keys). These keys grant direct access to paid inference endpoints.
Exploitability: Medium — Requires either database-level access (file system access to SQLite DB, database credentials for Postgres, or access to backups) or authenticated gRPC access to the ListInferenceRoutes / GetSandboxInferenceBundle endpoints. No special privileges beyond basic API access are needed to call these endpoints.
crates/navigator-core/src/proto/navigator.inference.v1.rs — InferenceRouteSpec.api_key (line 14) is a plain string field in the protobuf schema
Attack Scenario
Attacker gains read access to the SQLite database file (e.g., via a path traversal vulnerability, container escape, backup exposure, or compromised host).
Attacker decodes any row in the objects table where object_type = 'inference_route' using the known protobuf schema.
All inference API keys are extracted in plaintext, granting the attacker direct access to upstream inference providers (NVIDIA NIM, OpenAI, Anthropic, etc.) — potentially incurring costs or exfiltrating data.
Alternative scenario: An attacker with any gRPC client access calls ListInferenceRoutes and receives all API keys in the response, no database access needed.
Remediation Plan
Encrypt at rest: Introduce field-level encryption for api_key before storage. Use an envelope encryption scheme (e.g., AES-256-GCM with a key derived from a server-managed secret or external KMS). Decrypt only when the key is needed for outbound inference requests.
Redact on read: Strip or mask api_key in all gRPC responses (InferenceRouteResponse, ListInferenceRoutesResponse). Only the GetSandboxInferenceBundle internal path (used by the router to make outbound calls) should receive the decrypted key, and ideally over an internal-only channel.
Restrict access: Add authorization checks on inference route management endpoints so that only admin-level callers can create/update routes, and no caller can read back raw keys.
Audit logging: Log access to inference route secrets for forensic traceability.
Additional Notes
The navigator-router crate already redacts api_key in its Debug impl for ResolvedRoute (config.rs:42), showing awareness of the sensitivity — but this protection does not extend to the persistence layer or gRPC API responses.
Provider credentials in grpc.rs have a similar pattern (stored in the same object store), which may warrant a separate but related review.
InferenceRouteSpec (which contained the api_key field) has been deleted. The persisted InferenceRoute now contains only a ClusterInferenceConfig with provider_name and model_id — no secrets. API keys live exclusively on the Provider records and are resolved at runtime when the server builds the inference bundle. The stored route object no longer contains any sensitive material.
Activity
johntmyers commentedon Mar 5, 2026
Security Review
Determination: Legitimate concern
Summary
Inference route API keys are stored as plaintext within protobuf-encoded blobs in the SQLite/Postgres object store with no field-level encryption. All API responses (create, update, list, get_sandbox_inference_bundle) return the full
api_keywithout redaction, meaning any caller with gRPC access can read all stored inference provider keys.Severity Assessment
ListInferenceRoutes/GetSandboxInferenceBundleendpoints. No special privileges beyond basic API access are needed to call these endpoints.crates/navigator-server/src/inference.rs—create_inference_route(line 100),update_inference_route(line 141),list_inference_routes(lines 163-189),resolve_sandbox_inference_bundle(lines 274-278),list_sandbox_routes(line 322)crates/navigator-server/src/persistence/mod.rs—put_message(lines 259-270) stores plaintext protobuf blobscrates/navigator-core/src/proto/navigator.inference.v1.rs—InferenceRouteSpec.api_key(line 14) is a plain string field in the protobuf schemaAttack Scenario
objectstable whereobject_type = 'inference_route'using the known protobuf schema.Alternative scenario: An attacker with any gRPC client access calls
ListInferenceRoutesand receives all API keys in the response, no database access needed.Remediation Plan
api_keybefore storage. Use an envelope encryption scheme (e.g., AES-256-GCM with a key derived from a server-managed secret or external KMS). Decrypt only when the key is needed for outbound inference requests.api_keyin all gRPC responses (InferenceRouteResponse,ListInferenceRoutesResponse). Only theGetSandboxInferenceBundleinternal path (used by the router to make outbound calls) should receive the decrypted key, and ideally over an internal-only channel.Additional Notes
navigator-routercrate already redactsapi_keyin itsDebugimpl forResolvedRoute(config.rs:42), showing awareness of the sensitivity — but this protection does not extend to the persistence layer or gRPC API responses.grpc.rshave a similar pattern (stored in the same object store), which may warrant a separate but related review.pimlock commentedon Mar 6, 2026
Resolved by #146.
InferenceRouteSpec(which contained theapi_keyfield) has been deleted. The persistedInferenceRoutenow contains only aClusterInferenceConfigwithprovider_nameandmodel_id— no secrets. API keys live exclusively on theProviderrecords and are resolved at runtime when the server builds the inference bundle. The stored route object no longer contains any sensitive material.fix: openshell naming migration and launchable start up issues (NVIDI…
Merge pull request NVIDIA#21 from LobsterTrap/merge/align-midstream-w…