Skip to content

[TT-14791] log error on missing vault path in api definition#7663

Merged
edsonmichaque merged 16 commits intomasterfrom
fix/innersource/TT-14791
Feb 2, 2026
Merged

[TT-14791] log error on missing vault path in api definition#7663
edsonmichaque merged 16 commits intomasterfrom
fix/innersource/TT-14791

Conversation

@chrisanderton
Copy link
Contributor

@chrisanderton chrisanderton commented Jan 12, 2026

Fix gateway panic when API definition contains vault:// reference and vault path does not exist

Description

When an API definition (Classic or OAS) contains a vault:// reference in any string field, the gateway attempts to load secrets from a hardcoded vault path (secret/data/tyk-apis) during API load. If this path does not exist in Vault, the gateway crashes with a nil pointer dereference panic.

Related Issue

https://tyktech.atlassian.net/browse/TT-14791

Motivation and Context

  • Add a nil check for the vault secret response in replaceVaultSecrets() before accessing secret.Data
  • Return a descriptive error message when the vault path does not exist
  • The existing error handling in replaceSecrets() will log this error and continue, preventing the panic

How This Has Been Tested

Test cases in jira

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring or add test (improvements in base code or adds test coverage to functionality)

Checklist

  • I ensured that the documentation is up to date
  • I explained why this PR updates go.mod in detail with reasoning why it's required
  • I would like a code coverage CI quality gate exception and have explained why

Ticket Details

TT-14791
Status In Code Review
Summary [Innersource] Gateway panics when API definition contains vault:// reference and vault path does not exist

Generated at: 2026-02-02 08:29:24

@probelabs
Copy link

probelabs bot commented Jan 12, 2026

This PR addresses a critical bug that causes the gateway to panic and crash if an API definition contains a vault:// reference to a secret path that does not exist in Vault. The fix prevents this panic by introducing nil checks to gracefully handle responses for non-existent secrets, logging a descriptive error, and allowing the gateway to continue its operation without interruption.

A key architectural improvement in this PR is the introduction of a SecretReader interface. This decouples the secret loading logic from the concrete Vault implementation, allowing for robust unit testing with mocks, as demonstrated by the comprehensive new tests added for various failure scenarios.

Files Changed Analysis

  • gateway/api_definition.go: Implements the core fix in the replaceVaultSecrets function. It now checks if the secret or its data is nil before access, preventing the nil pointer dereference. It also adopts the new SecretReader interface to fetch secrets.
  • storage/kv/vault.go: Defines the new SecretReader interface and adds a ReadSecret method to the Vault struct, satisfying the interface and abstracting the direct Vault client call.
  • gateway/api_definition_test.go: Adds a substantial new test suite (TestReplaceVaultSecrets) that uses a mock implementation of SecretReader to verify the correctness of the new error handling for various scenarios, such as a non-existent path, empty data, and Vault server errors.
  • storage/kv/store_test.go: Adds new unit tests for the Vault.ReadSecret method itself, using a mocked HTTP server to validate its behavior against different Vault API responses (success, not found, server error).

Architecture & Impact Assessment

  • What this PR accomplishes: It enhances the gateway's stability and resilience by preventing a crash caused by a common misconfiguration (referencing a non-existent secret), thus avoiding a potential denial-of-service.
  • Key technical changes introduced:
    1. Addition of nil checks in replaceVaultSecrets to handle missing Vault paths gracefully.
    2. Introduction of the SecretReader interface to abstract Vault read operations, improving testability through dependency injection.
  • Affected system components: The change is localized to the API definition loading process within the gateway. The impact is entirely positive, improving the system's robustness.
sequenceDiagram
    participant APILoader
    participant VaultClient as SecretReader
    participant Gateway

    APILoader->>VaultClient: ReadSecret("non/existent/path")

    alt Old Behavior (Panic)
        VaultClient-->>APILoader: Returns (nil, nil)
        APILoader->>APILoader: Accesses secret.Data (nil pointer dereference)
        APILoader--xGateway: Panic!
    end

    alt New Behavior (Graceful Error)
        VaultClient-->>APILoader: Returns (nil, nil)
        APILoader->>APILoader: Checks if secret is nil
        APILoader-->>Gateway: Returns descriptive error
        Note right of Gateway: Logs error and continues operation
    end
Loading

Scope Discovery & Context Expansion

  • The scope of this change is well-contained within the secret replacement logic for API definitions. The error returned by replaceVaultSecrets is handled by its caller, replaceSecrets, which logs the issue and allows the gateway to continue loading the API without the secret. This prevents a single misconfigured API from causing a service-wide outage.
  • The introduction of the SecretReader interface is a valuable architectural improvement. It enhances testability by allowing the Vault dependency to be mocked easily, setting a good precedent for decoupling other parts of the system from external services.
Metadata
  • Review Effort: 2 / 5
  • Primary Label: bug

Powered by Visor from Probelabs

Last updated: 2026-02-02T08:31:44.105Z | Triggered by: pr_updated | Commit: 6667ed0

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs
Copy link

probelabs bot commented Jan 12, 2026

✅ Security Check Passed

No security issues found – changes LGTM.

Architecture Issues (1)

Severity Location Issue
🟢 Info storage/kv/vault.go:13-15
The `SecretReader` interface is a good architectural improvement for decoupling, but it currently leaks an implementation detail by returning `*vaultapi.Secret`. This couples any consumer of the interface to the HashiCorp Vault client library, which could complicate swapping the secret backend for a different technology in the future.
💡 SuggestionFor a more complete abstraction, the interface could be defined to return a generic type like `map[string]interface{}` or a custom struct. The implementation in `vault.go` would then be responsible for extracting the relevant data from the `*vaultapi.Secret` object. This would fully encapsulate the Vault-specific logic within the `kv` package and make consumers like `APIDefinitionLoader` completely independent of the underlying secret store's client library.

Example of a more abstract interface:

type SecretDataReader interface {
    ReadSecretData(path string) (map[string]interface{}, error)
}

✅ Performance Check Passed

No performance issues found – changes LGTM.

✅ Quality Check Passed

No quality issues found – changes LGTM.


Powered by Visor from Probelabs

Last updated: 2026-02-02T08:31:47.169Z | Triggered by: pr_updated | Commit: 6667ed0

💡 TIP: You can chat with Visor using /visor ask <your question>

@github-actions
Copy link
Contributor

github-actions bot commented Jan 12, 2026

API Changes

--- prev.txt	2026-02-02 08:30:19.605032471 +0000
+++ current.txt	2026-02-02 08:30:08.902040415 +0000
@@ -13913,6 +13913,11 @@
 
 func (c *Consul) Store() *consulapi.KV
 
+type SecretReader interface {
+	ReadSecret(path string) (*vaultapi.Secret, error)
+}
+    SecretReader allows mocking Vault in tests without a real instance.
+
 type Store interface {
 	Get(key string) (string, error)
 	// Put writes a value to the KV store
@@ -13929,7 +13934,7 @@
 type Vault struct {
 	// Has unexported fields.
 }
-    Vault is an implementation of a KV store which uses Consul as it's backend
+    Vault is an implementation of a KV store which uses Vault as its backend
 
 func (v *Vault) Client() *vaultapi.Client
 
@@ -13937,6 +13942,8 @@
 
 func (v *Vault) Put(key string, value string) error
 
+func (v *Vault) ReadSecret(path string) (*vaultapi.Secret, error)
+
 # Package: ./storage/mock
 
 package mock // import "github.com/TykTechnologies/tyk/storage/mock"

@chrisanderton
Copy link
Contributor Author

Not easy to add test case for this new nil check without broader refactoring. This ticket is a targetted nil check bug fix, the change is low risk vs. the risk introduced by a refactor to add test coverage for 3 lines of code. Test coverage for vault should be a factor when kv interfaces are consolidated (a different piece of work to this bug fix).

Copy link
Collaborator

@pvormste pvormste left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it is a small change, we still need to provide coverage. No major refactoring is needed, look at this code snippet right before the change:

secret, err := a.Gw.vaultKVStore.(*kv.Vault).Client().Logical().Read(vaultSecretPath + prefixKeys)
if err != nil {
	return err
}

This needs to be changed, so that it can return a dummy implementation that can be used for tests.
One way could be to have an extended interface like

type VaultKVStore interface {
    kv.Store
    Client() *api.Client
}

and then add a function to GW that handles the interface/implemetation provision:

func (gw *Gateway) GetVaultKVStore() (vaultStore VaultKVStore, ok bool)

Use the dummy implemetation then to setup your tests

@chrisanderton
Copy link
Contributor Author

chrisanderton commented Jan 26, 2026

@pvormste how about just doing the read path?

In storage/kv/vault.go:

type SecretReader interface {
    ReadSecret(path string) (*vaultapi.Secret, error)
}

func (v *Vault) ReadSecret(path string) (*vaultapi.Secret, error) {
    return v.client.Logical().Read(path)
}

In gateway/api_definition.go:

vault, ok := a.Gw.vaultKVStore.(kv.SecretReader)
...
secret, err := vault.ReadSecret(vaultSecretPath + prefixKeys)

keeps the interface minimal vs. full client

@chrisanderton chrisanderton force-pushed the fix/innersource/TT-14791 branch from 79de8b4 to 81c32a4 Compare January 27, 2026 01:13
@chrisanderton
Copy link
Contributor Author

Path traversal check is outside the scope of this ticket, we've already ballooned in complexity vs the initial nil check only.

@chrisanderton
Copy link
Contributor Author

Note from/to visor - it initially suggested a path traversal risk, then disagreed with the todo. Comment has been removed.

The TODO comment suggests a path traversal vulnerability exists, but the implementation in replaceVaultSecrets does not seem to support this. The Vault path is hardcoded to secret/data/tyk-apis, and keys from the API definition are not used to construct this path. This comment is misleading and could cause confusion or misdirect future security efforts.

Copy link
Contributor

@edsonmichaque edsonmichaque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
@pvormste since you raised some questions, would you give an approval if you are happy?

Copy link
Collaborator

@pvormste pvormste left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this looks much better now, great job!
Please note that tests are absolutely required even for such small changes, otherwise we will not be able to catch possible regressions in the future.

@edsonmichaque edsonmichaque enabled auto-merge (squash) February 2, 2026 08:28
@edsonmichaque edsonmichaque merged commit 94ec558 into master Feb 2, 2026
52 of 55 checks passed
@edsonmichaque edsonmichaque deleted the fix/innersource/TT-14791 branch February 2, 2026 09:54
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 2, 2026

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
98.1% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@edsonmichaque
Copy link
Contributor

/release to release-5.8

probelabs bot pushed a commit that referenced this pull request Feb 2, 2026
Fix gateway panic when API definition contains vault:// reference and
vault path does not exist

## Description

When an API definition (Classic or OAS) contains a vault:// reference in
any string field, the gateway attempts to load secrets from a hardcoded
vault path (secret/data/tyk-apis) during API load. If this path does not
exist in Vault, the gateway crashes with a nil pointer dereference
panic.

## Related Issue

https://tyktech.atlassian.net/browse/TT-14791

## Motivation and Context

* Add a nil check for the vault secret response in replaceVaultSecrets()
before accessing secret.Data
* Return a descriptive error message when the vault path does not exist
* The existing error handling in replaceSecrets() will log this error
and continue, preventing the panic

## How This Has Been Tested

Test cases in jira

## Screenshots (if appropriate)

## Types of changes

<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)

## Checklist

<!-- Go over all the following points, and put an `x` in all the boxes
that apply -->
<!-- If there are no documentation updates required, mark the item as
checked. -->
<!-- Raise up any additional concerns not covered by the checklist. -->

- [ ] I ensured that the documentation is up to date
- [ ] I explained why this PR updates go.mod in detail with reasoning
why it's required
- [ ] I would like a code coverage CI quality gate exception and have
explained why

<!---TykTechnologies/jira-linter starts here-->

### Ticket Details

<details>
<summary>
<a href="https://tyktech.atlassian.net/browse/TT-14791" title="TT-14791"
target="_blank">TT-14791</a>
</summary>

|         |    |
|---------|----|
| Status  | In Code Review |
| Summary | [Innersource] Gateway panics when API definition contains
vault:// reference and vault path does not exist |

Generated at: 2026-02-02 07:50:30

</details>

<!---TykTechnologies/jira-linter ends here-->

---------

Co-authored-by: Edson Michaque <edson@michaque.com>
(cherry picked from commit 94ec558)
@probelabs
Copy link

probelabs bot commented Feb 2, 2026

✅ Cherry-pick successful. A PR was created: #7726

edsonmichaque added a commit that referenced this pull request Feb 2, 2026
… api definition (#7663) (#7726)

### **User description**
[TT-14791] log error on missing vault path in api definition (#7663)

Fix gateway panic when API definition contains vault:// reference and
vault path does not exist

## Description

When an API definition (Classic or OAS) contains a vault:// reference in
any string field, the gateway attempts to load secrets from a hardcoded
vault path (secret/data/tyk-apis) during API load. If this path does not
exist in Vault, the gateway crashes with a nil pointer dereference
panic.

## Related Issue

https://tyktech.atlassian.net/browse/TT-14791

## Motivation and Context

* Add a nil check for the vault secret response in replaceVaultSecrets()
before accessing secret.Data
* Return a descriptive error message when the vault path does not exist
* The existing error handling in replaceSecrets() will log this error
and continue, preventing the panic

## How This Has Been Tested

Test cases in jira

## Screenshots (if appropriate)

## Types of changes

<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)

## Checklist

<!-- Go over all the following points, and put an `x` in all the boxes
that apply -->
<!-- If there are no documentation updates required, mark the item as
checked. -->
<!-- Raise up any additional concerns not covered by the checklist. -->

- [ ] I ensured that the documentation is up to date
- [ ] I explained why this PR updates go.mod in detail with reasoning
why it's required
- [ ] I would like a code coverage CI quality gate exception and have
explained why
















<!---TykTechnologies/jira-linter starts here-->

### Ticket Details

<details>
<summary>
<a href="https://tyktech.atlassian.net/browse/TT-14791" title="TT-14791"
target="_blank">TT-14791</a>
</summary>

|         |    |
|---------|----|
| Status  | In Code Review |
| Summary | [Innersource] Gateway panics when API definition contains
vault:// reference and vault path does not exist |

Generated at: 2026-02-02 07:50:30

</details>

<!---TykTechnologies/jira-linter ends here-->

---------

Co-authored-by: Edson Michaque <edson@michaque.com>

[TT-14791]:
https://tyktech.atlassian.net/browse/TT-14791?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


___

### **PR Type**
Bug fix, Tests


___

### **Description**
- Prevent panic on missing Vault secrets

- Add SecretReader interface for Vault reads

- Return descriptive errors for nil data

- Add unit tests for Vault edge cases


___

### Diagram Walkthrough


```mermaid
flowchart LR
  A["API definition contains `vault://` reference"] 
  B["`APIDefinitionLoader.replaceVaultSecrets()`"] 
  C["`kv.SecretReader.ReadSecret()`"] 
  D["Nil/empty secret checks"] 
  E["Return descriptive error (no panic)"] 
  F["Tests: loader + Vault adapter"] 

  A -- "triggers secret resolution" --> B
  B -- "read from KV store interface" --> C
  C -- "returns secret / nil / error" --> D
  D -- "on missing path or data" --> E
  B -- "validated by" --> F
```



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>api_definition.go</strong><dd><code>Guard Vault secret
reads to avoid panic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

gateway/api_definition.go

<ul><li>Cast <code>vaultKVStore</code> to
<code>kv.SecretReader</code><br> <li> Log and error when interface
unsupported<br> <li> Handle <code>nil</code> secret and <code>nil</code>
<code>secret.Data</code><br> <li> Return clearer Vault-path failure
messages</ul>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/7726/files#diff-0cf80174bbafb36f6d4f4308ebbd971b2833b76a936bad568220aa1a4ba0ee8b">+15/-1</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>api_definition_test.go</strong><dd><code>Add
replaceVaultSecrets regression and edge tests</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/api_definition_test.go

<ul><li>Add mock Vault readers/stores for testing<br> <li> Test missing
<code>SecretReader</code> implementation case<br> <li> Test nil secret,
nil data, and read errors<br> <li> Test successful <code>vault://</code>
replacement behavior</ul>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/7726/files#diff-2394daab6fdc5f8dc234699c80c0548947ee3d68d2e33858258d73a8b5eb6f44">+166/-0</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>store_test.go</strong><dd><code>Add tests for Vault
ReadSecret behavior</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

storage/kv/store_test.go

<ul><li>Add HTTP-backed tests for <code>Vault.ReadSecret</code><br> <li>
Verify success path returns a populated secret<br> <li> Verify 404 maps
to <code>(nil, nil)</code> result<br> <li> Verify 500 maps to an error
result</ul>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/7726/files#diff-be6a5fa0dfe83bc6bdaa6cfa76f580e4e6891595b66a6b931d97fa1a2e39a06b">+79/-1</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>vault.go</strong><dd><code>Add SecretReader abstraction
for Vault secret reads</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

storage/kv/vault.go

<ul><li>Introduce <code>kv.SecretReader</code> interface for secret
reads<br> <li> Implement <code>ReadSecret</code> on <code>*Vault</code>
via Vault Logical API<br> <li> Enable mocking Vault reads without real
client</ul>


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/7726/files#diff-b49ef7901e3657f34a414ff71a5ad1c6665862c42497e59227fc128ea573c47d">+10/-1</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

</details>

___

Co-authored-by: Chris Anderton <chrisanderton@users.noreply.github.com>
Co-authored-by: Edson Michaque <edson@michaque.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants