CVE-2023-1065 (published 2023-02-28T19:15Z)

This vulnerability in the Snyk Kubernetes Monitor can result in irrelevant data being posted to a Snyk Organization, which could in turn obfuscate other, relevant, security issues. It does not expose the user of the integration to any direct security risk and no user data can be leaked. To exploit the vulnerability the attacker does not need to be authenticated to Snyk but does need to know the target's Integration ID (which may or may not be the same as the Organization ID, although this is an unpredictable UUID in either case).

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         

Fix information:
repo_url: https://github.com/snyk/kubernetes-monitor
commit_details: repo_url: https://github.com/snyk/kubernetes-monitor
author: Minsi Yang
author_date: 2023-01-26 11:50:39+00:00
msg: feat: call authenticated snyk API endpoints with token

BREAKING CHANGE: use a service account API token to perform
authenticated requests to snyk API.
repository: repo_url: https://github.com/snyk/kubernetes-monitor
repo_name: snyk/kubernetes-monitor
description: Use Snyk to find and fix vulnerabilities in your Kubernetes workloads
repo_language: TypeScript
owner: snyk
file_changes: filename: config.yml
change_type: ModificationType.MODIFY
diff: @@ -519,7 +519,8 @@ jobs:
 
                     set -e
                     INTEGRATION_ID=$(uuidgen)
-                    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=integrationId=${INTEGRATION_ID} --from-literal=dockercfg.json={}
+                    SERVICE_ACCOUNT_API_TOKEN=$(uuidgen)
+                    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=integrationId=${INTEGRATION_ID} --from-literal=serviceAccountApiToken=${SERVICE_ACCOUNT_API_TOKEN} --from-literal=dockercfg.json={}
                 name: Configure snyk-monitor namespace
             - run:
                 command: |

programming_language: Shell
filename: operator_upgrade_tests.yml
change_type: ModificationType.MODIFY
diff: @@ -120,7 +120,8 @@ steps:
 
         set -e
         INTEGRATION_ID=$(uuidgen)
-        kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=integrationId=${INTEGRATION_ID} --from-literal=dockercfg.json={}
+        SERVICE_ACCOUNT_API_TOKEN=$(uuidgen)
+        kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=integrationId=${INTEGRATION_ID} --from-literal=serviceAccountApiToken=${SERVICE_ACCOUNT_API_TOKEN} --from-literal=dockercfg.json={}
 
   - run:
       name: Install Operator

programming_language: Shell
filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -10,7 +10,7 @@ A containerized application that is deployed with Helm. Monitors the security of
 ## Prerequisites ##
 
 * 50 GiB of storage in the form of [emptyDir](https://kubernetes.io/docs/concepts/storage/volumes/#emptydir) or a [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/).
-* External internet access from the Kubernetes cluster to `kubernetes-upstream.snyk.io`.
+* External internet access from the Kubernetes cluster to `api.snyk.io`.
 * 1 CPU, 2 GiB RAM
 * 1 Kubernetes worker node of type `linux/amd64` - supported and tested only on the AMD64 CPU architecture
 

programming_language: Markdown
filename: config.default.json
change_type: ModificationType.MODIFY
diff: @@ -19,7 +19,8 @@
   "REQUEST_QUEUE_LENGTH": 2,
   "QUEUE_LENGTH_LOG_FREQUENCY_MINUTES": 1,
   "INTEGRATION_ID": "",
-  "DEFAULT_KUBERNETES_UPSTREAM_URL": "https://kubernetes-upstream.snyk.io",
+  "SERVICE_ACCOUNT_API_TOKEN": "",
+  "DEFAULT_KUBERNETES_UPSTREAM_URL": "https://api.snyk.io/v2/kubernetes-upstream",
   "MAX_RETRY_BACKOFF_DURATION_SECONDS": 300,
   "USE_KEEPALIVE": true
 }

programming_language: JavaScript
filename: deploy_to_dev.sh
change_type: ModificationType.MODIFY
diff: @@ -20,7 +20,7 @@ cp -r snyk-monitor/* $KUBERNETES_MONITOR_DEPLOYER_REPO/helm
 # Create helm values for different envs
 cat >$KUBERNETES_MONITOR_DEPLOYER_REPO/helm/values/multi-tenant-gcp-pre-production.yaml <<EOF
 clusterName: "Development cluster"
-integrationApi: https://kubernetes-upstream.dev.snyk.io
+integrationApi: "https://api.dev.snyk.io/v2/kubernetes-upstream"
 log_level: "DEBUG"
 skip_k8s_jobs: true
 

programming_language: Shell
filename: local-testing.sh
change_type: ModificationType.MODIFY
diff: @@ -15,10 +15,15 @@ if [ "$INTEGRATION_ID" == "" ]; then
     read INTEGRATION_ID
 fi
 
+if [ "$SERVICE_ACCOUNT_API_TOKEN" == "" ]; then
+    echo "Enter your service account API token: "
+    read SERVICE_ACCOUNT_API_TOKEN
+fi
+
 kind delete cluster
 kind create cluster
 kubectl create namespace snyk-monitor
-kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${INTEGRATION_ID}
+kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${INTEGRATION_ID} --from-literal=serviceAccountApiToken=${SERVICE_ACCOUNT_API_TOKEN}
 helm upgrade --install snyk-monitor ./snyk-monitor --namespace snyk-monitor --set clusterName="kind localhost" --set image.tag=${LATEST_TAG} --set integrationApi=http://${IP}:9000
 
 printf "\r\n\r\nYou can now check if the pod is running using:\r\n\tkubectl get pod -n snyk-monitor\r\n"

programming_language: Shell
filename: snyk-monitor-deployment.yaml
change_type: ModificationType.MODIFY
diff: @@ -43,6 +43,11 @@ spec:
               secretKeyRef:
                 name: snyk-monitor
                 key: integrationId
+          - name: SNYK_SERVICE_ACCOUNT_API_TOKEN
+            valueFrom:
+              secretKeyRef:
+                name: snyk-monitor
+                key: serviceAccountApiToken
           - name: SNYK_WATCH_NAMESPACE
             valueFrom:
               configMapKeyRef:

programming_language: CoffeeScript
filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -41,7 +41,7 @@ metadata:
   name: snyk-monitor
   namespace: snyk-monitor
 spec:
-  integrationApi: https://kubernetes-upstream.dev.snyk.io
+  integrationApi: https://api.dev.snyk.io/v2/kubernetes-upstream
   temporaryStorageSize: 20Gi
   pvc:
     enabled: true

programming_language: Markdown
filename: snyk-monitor.clusterserviceversion.yaml
change_type: ModificationType.MODIFY
diff: @@ -147,11 +147,11 @@ spec:
 
     ## Prerequisites
 
-    To start using the Operator and the Snyk controller that it manages, you will need to create a secret containing your Snyk integrationId and Docker config file:
+    To start using the Operator and the Snyk controller that it manages, you will need to create a secret containing your Snyk integrationId, serviceAccountApiToken and Docker config file:
 
     ```
     kubectl create namespace snyk-monitor
-    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${SNYK_INTEGRATION_ID}
+    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${SNYK_INTEGRATION_ID} --from-literal=serviceAccountApiToken=${SNYK_SERVICE_ACCOUNT_API_TOKEN}
     ```
 
     Refer to the [Snyk documentation](https://support.snyk.io/hc/en-us/articles/360003916138-Kubernetes-integration-overview) for more details.

programming_language: CoffeeScript
filename: snyk-monitor.clusterserviceversion.yaml
change_type: ModificationType.MODIFY
diff: @@ -129,11 +129,11 @@ spec:
 
     ## Prerequisites
 
-    To start using the Operator and the Snyk controller that it manages, you will need to create a secret containing your Snyk integrationId and Docker config file:
+    To start using the Operator and the Snyk controller that it manages, you will need to create a secret containing your Snyk integrationId, serviceAccountApiToken and Docker config file:
 
     ```
     kubectl create namespace snyk-monitor
-    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${SNYK_INTEGRATION_ID}
+    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${SNYK_INTEGRATION_ID} --from-literal=serviceAccountApiToken=${SNYK_SERVICE_ACCOUNT_API_TOKEN}
     ```
 
     Refer to the [Snyk documentation](https://support.snyk.io/hc/en-us/articles/360003916138-Kubernetes-integration-overview) for more details.

programming_language: HTML
filename: deployment.yaml
change_type: ModificationType.MODIFY
diff: @@ -95,6 +95,11 @@ spec:
               secretKeyRef:
                 name: {{ .Values.monitorSecrets }}
                 key: integrationId
+          - name: SNYK_SERVICE_ACCOUNT_API_TOKEN
+            valueFrom:
+              secretKeyRef:
+                name: snyk-monitor
+                key: serviceAccountApiToken
           - name: SNYK_WATCH_NAMESPACE
             value: {{ include "snyk-monitor.scope" . }}
           - name: SNYK_DEPLOYMENT_NAMESPACE

programming_language: Swift
filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -40,7 +40,7 @@ metadata:
   name: snyk-monitor
   namespace: snyk-monitor
 spec:
-  integrationApi: https://kubernetes-upstream.dev.snyk.io
+  integrationApi: https://api.dev.snyk.io/v2/kubernetes-upstream
   temporaryStorageSize: 20Gi
   pvc:
     enabled: true

programming_language: Markdown
filename: snyk-operator.v0.0.0.clusterserviceversion.yaml
change_type: ModificationType.MODIFY
diff: @@ -187,11 +187,11 @@ spec:
 
     ## Prerequisites
 
-    To start using the Operator and the Snyk controller that it manages, you will need to create a secret containing your Snyk integrationId and Docker config file:
+    To start using the Operator and the Snyk controller that it manages, you will need to create a secret containing your Snyk integrationId, serviceAccountApiToken and Docker config file:
 
     ```
     kubectl create namespace snyk-monitor
-    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${SNYK_INTEGRATION_ID}
+    kubectl create secret generic snyk-monitor -n snyk-monitor --from-literal=dockercfg.json={} --from-literal=integrationId=${SNYK_INTEGRATION_ID} --from-literal=serviceAccountApiToken=${SNYK_SERVICE_ACCOUNT_API_TOKEN}
     ```
 
     Refer to the [Snyk documentation](https://support.snyk.io/hc/en-us/articles/360003916138-Kubernetes-integration-overview) for more details.

programming_language: Markdown
filename: config.ts
change_type: ModificationType.MODIFY
diff: @@ -39,6 +39,7 @@ function getClusterName(): string {
 config.AGENT_ID = randomUUID();
 
 config.INTEGRATION_ID = config.INTEGRATION_ID.trim();
+config.SERVICE_ACCOUNT_API_TOKEN = config.SERVICE_ACCOUNT_API_TOKEN.trim();
 config.CLUSTER_NAME = getClusterName();
 config.IMAGE_STORAGE_ROOT = '/var/tmp';
 config.POLICIES_STORAGE_ROOT = '/tmp/policies';

programming_language: TypeScript
filename: types.ts
change_type: ModificationType.MODIFY
diff: @@ -19,6 +19,7 @@ export interface Config {
   REQUEST_QUEUE_LENGTH: number;
   QUEUE_LENGTH_LOG_FREQUENCY_MINUTES: number;
   INTEGRATION_ID: string;
+  SERVICE_ACCOUNT_API_TOKEN: string;
   DEFAULT_KUBERNETES_UPSTREAM_URL: string;
   MAX_RETRY_BACKOFF_DURATION_SECONDS: number;
 

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -21,6 +21,7 @@ import {
 import { getProxyAgent } from './proxy';
 
 import type { queueAsPromised } from 'fastq';
+import path from 'path';
 
 interface KubernetesUpstreamRequest {
   method: NeedleHttpVerbs;
@@ -32,11 +33,14 @@ interface KubernetesUpstreamRequest {
     | IDeleteWorkloadPayload
     | IClusterMetadataPayload
     | IRuntimeDataPayload;
+  options: NeedleOptions;
 }
 
 const upstreamUrl =
   config.INTEGRATION_API || config.DEFAULT_KUBERNETES_UPSTREAM_URL;
 
+const upstreamRequestVersion = '2023-02-10';
+
 let httpAgent = new HttpAgent({
   keepAlive: config.USE_KEEPALIVE,
 });
@@ -55,10 +59,29 @@ function getAgent(u: string): HttpAgent {
 const reqQueue: queueAsPromised<unknown> = fastq.promise(async function (
   req: KubernetesUpstreamRequest,
 ) {
-  return await retryRequest(req.method, req.url, req.payload);
+  return await retryRequest(req.method, req.url, req.payload, req.options);
 },
 config.REQUEST_QUEUE_LENGTH);
 
+const upstreamRequestOptions = {
+  headers: {
+    Authorization: `token ${config.SERVICE_ACCOUNT_API_TOKEN}`,
+  },
+};
+
+function constructUpstreamRequestUrl(
+  requestPath: string,
+  queryParams?: Record<string, string>,
+): string {
+  const requestUrl = new URL(upstreamUrl);
+  requestUrl.pathname = path.join(requestUrl.pathname, requestPath);
+  requestUrl.searchParams.set('version', upstreamRequestVersion);
+  for (const key in queryParams) {
+    requestUrl.searchParams.set(key, queryParams[key]);
+  }
+  return requestUrl.toString();
+}
+
 export async function sendDepGraph(
   ...payloads: IDependencyGraphPayload[]
 ): Promise<void> {
@@ -69,8 +92,9 @@ export async function sendDepGraph(
     try {
       const request: KubernetesUpstreamRequest = {
         method: 'post',
-        url: `${upstreamUrl}/api/v1/dependency-graph`,
+        url: constructUpstreamRequestUrl('/api/v1/dependency-graph'),
         payload,
+        options: upstreamRequestOptions,
       };
 
       const { response, attempt } = await reqQueue.push(request);
@@ -100,8 +124,9 @@ export async function sendScanResults(
     try {
       const request: KubernetesUpstreamRequest = {
         method: 'post',
-        url: `${upstreamUrl}/api/v1/scan-results`,
+        url: constructUpstreamRequestUrl('/api/v1/scan-results'),
         payload,
+        options: upstreamRequestOptions,
       };
 
       const { response, attempt } = await reqQueue.push(request);
@@ -136,8 +161,9 @@ export async function sendWorkloadMetadata(
 
     const request: KubernetesUpstreamRequest = {
       method: 'post',
-      url: `${upstreamUrl}/api/v1/workload`,
+      url: constructUpstreamRequestUrl('/api/v1/workload'),
       payload,
+      options: upstreamRequestOptions,
     };
 
     const { response, attempt } = await reqQueue.push(request);
@@ -172,8 +198,9 @@ export async function sendWorkloadEventsPolicy(
 
     const { response, attempt } = await retryRequest(
       'post',
-      `${upstreamUrl}/api/v1/policy`,
+      constructUpstreamRequestUrl('/api/v1/policy'),
       payload,
+      upstreamRequestOptions,
     );
     if (!isSuccessStatusCode(response.statusCode)) {
       throw new Error(`${response.statusCode} ${response.statusMessage}`);
@@ -207,11 +234,19 @@ export async function deleteWorkload(
   try {
     const { workloadLocator, agentId } = payload;
     const { userLocator, cluster, namespace, type, name } = workloadLocator;
-    const query = `userLocator=${userLocator}&cluster=${cluster}&namespace=${namespace}&type=${type}&name=${name}&agentId=${agentId}`;
+    const queryParams: Record<string, string> = {
+      userLocator,
+      cluster,
+      namespace,
+      type,
+      name,
+      agentId,
+    };
     const request: KubernetesUpstreamRequest = {
       method: 'delete',
-      url: `${upstreamUrl}/api/v1/workload?${query}`,
+      url: constructUpstreamRequestUrl('api/v1/workload', queryParams),
       payload,
+      options: upstreamRequestOptions,
     };
 
     const { response, attempt } = await reqQueue.push(request);
@@ -348,8 +383,9 @@ export async function sendClusterMetadata(): Promise<void> {
 
     const request: KubernetesUpstreamRequest = {
       method: 'post',
-      url: `${upstreamUrl}/api/v1/cluster`,
+      url: constructUpstreamRequestUrl('/api/v1/cluster'),
       payload,
+      options: upstreamRequestOptions,
     };
 
     const { response, attempt } = await reqQueue.push(request);
@@ -394,8 +430,9 @@ export async function sendRuntimeData(
 
     const request: KubernetesUpstreamRequest = {
       method: 'post',
-      url: `${upstreamUrl}/api/v1/runtime-results`,
+      url: constructUpstreamRequestUrl('/api/v1/runtime-results'),
       payload,
+      options: upstreamRequestOptions,
     };
 
     const { response, attempt } = await reqQueue.push(request);

programming_language: TypeScript
filename: config.spec.ts
change_type: ModificationType.MODIFY
diff: @@ -1,8 +1,10 @@
 describe('extractNamespaceName()', () => {
+  const apiToken = '46766a0a-ed0b-4e91-84c8-ea1c827f2a73';
   beforeEach(() => {
     jest.resetModules();
     process.env.SNYK_SYSDIG_ENDPOINT = 'https://api/v1/images/';
     process.env.SNYK_SYSDIG_TOKEN = '1432gtrhtrw32raf';
+    process.env.SNYK_SERVICE_ACCOUNT_API_TOKEN = apiToken;
   });
 
   afterEach(() => {
@@ -68,6 +70,7 @@ describe('extractNamespaceName()', () => {
     const { config } = require('../../src/common/config');
     expect(config.AGENT_ID).toEqual(expect.any(String));
     expect(config.INTEGRATION_ID).toEqual(expect.any(String));
+    expect(config.SERVICE_ACCOUNT_API_TOKEN).toEqual(apiToken);
     expect(config.CLUSTER_NAME).toEqual('Default cluster');
     expect(config.IMAGE_STORAGE_ROOT).toEqual('/var/tmp');
     expect(config.EXCLUDED_NAMESPACES).toBeNull();
@@ -76,7 +79,7 @@ describe('extractNamespaceName()', () => {
     expect(config.NO_PROXY).toBeUndefined();
     expect(config.USE_KEEPALIVE).toEqual(true);
     expect(config.SKIP_K8S_JOBS).toEqual(false);
-    expect(config.WORKERS_COUNT).toEqual(10);
+    expect(config.WORKERS_COUNT).toEqual(5);
     expect(config.SKOPEO_COMPRESSION_LEVEL).toEqual(6);
     expect(config.SYSDIG_ENDPOINT).toEqual('https://api/v1/images/');
     expect(config.SYSDIG_TOKEN).toEqual('1432gtrhtrw32raf');

programming_language: TypeScript
filename: custom-resource-k8s.yaml
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@ metadata:
   name: snyk-monitor
   namespace: marketplace
 spec:
-  integrationApi: https://kubernetes-upstream.dev.snyk.io
+  integrationApi: https://api.dev.snyk.io/v2/kubernetes-upstream
   temporaryStorageSize: 20Gi
   clusterName: ""
   pvc:

programming_language: CoffeeScript
filename: custom-resource.yaml
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@ metadata:
   name: snyk-monitor
   namespace: snyk-monitor
 spec:
-  integrationApi: https://kubernetes-upstream.dev.snyk.io
+  integrationApi: https://api.dev.snyk.io/v2/kubernetes-upstream
   temporaryStorageSize: 20Gi
   clusterName: ""
   pvc:

programming_language: CoffeeScript
filename: helm-with-proxy.ts
change_type: ModificationType.MODIFY
diff: @@ -37,7 +37,7 @@ async function deployKubernetesMonitor(
       `--set image.repository=${imageName} ` +
       `--set image.tag=${imageTag} ` +
       `--set image.pullPolicy=${imagePullPolicy} ` +
-      '--set integrationApi=https://kubernetes-upstream.dev.snyk.io ' +
+      '--set integrationApi=https://api.dev.snyk.io/v2/kubernetes-upstream ' +
       `--set clusterName=${deployOptions.clusterName} ` +
       '--set https_proxy=http://forwarding-proxy:8080',
   );

programming_language: TypeScript
filename: helm.ts
change_type: ModificationType.MODIFY
diff: @@ -30,7 +30,7 @@ async function deployKubernetesMonitor(
       `--set image.repository=${imageName} ` +
       `--set image.tag=${imageTag} ` +
       `--set image.pullPolicy=${imagePullPolicy} ` +
-      '--set integrationApi=https://kubernetes-upstream.dev.snyk.io ' +
+      '--set integrationApi=https://api.dev.snyk.io/v2/kubernetes-upstream ' +
       `--set clusterName=${deployOptions.clusterName} ` +
       '--set nodeSelector."kubernetes\\.io/os"=linux ' +
       '--set pvc.enabled=true ' +

programming_language: TypeScript
filename: yaml.ts
change_type: ModificationType.MODIFY
diff: @@ -47,7 +47,7 @@ function createTestYamlDeployment(
   const envVar = container.env.find(
     (env) => env.name === 'SNYK_INTEGRATION_API',
   );
-  envVar.value = 'https://kubernetes-upstream.dev.snyk.io';
+  envVar.value = 'https://api.dev.snyk.io/v2/kubernetes-upstream';
   delete envVar.valueFrom;
 
   if (clusterName) {

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,14 @@ function getClusterName(): string {
   return clusterName;
 }
 
+function getServiceAccountApiToken(): string {
+  const serviceAccountApiToken = randomUUID();
+  console.log(
+    `Generated new service account API token ${serviceAccountApiToken}`,
+  );
+  return serviceAccountApiToken;
+}
+
 function getEnvVariableOrDefault(
   envVarName: string,
   defaultValue: string,
@@ -84,6 +92,7 @@ async function createEnvironment(): Promise<void> {
 
 async function predeploy(
   integrationId: string,
+  serviceAccountApiToken: string,
   namespace: string,
 ): Promise<void> {
   try {
@@ -99,6 +108,7 @@ async function predeploy(
     await kubectl.createSecret(secretName, namespace, {
       'dockercfg.json': gcrDockercfg,
       integrationId,
+      serviceAccountApiToken,
     });
     await createRegistriesConfigMap(namespace);
     console.log(`Namespace ${namespace} and secret ${secretName} created`);
@@ -193,7 +203,8 @@ export async function deployMonitor(): Promise<{
     await createSecretForDockerHubAccess();
 
     const integrationId = getIntegrationId();
-    await predeploy(integrationId, namespace);
+    const serviceAccountApiToken = getServiceAccountApiToken();
+    await predeploy(integrationId, serviceAccountApiToken, namespace);
 
     // TODO: hack, rewrite this
     const imagePullPolicy =

programming_language: TypeScript
filename: scrape-data.spec.ts
change_type: ModificationType.MODIFY
diff: @@ -59,8 +59,8 @@ describe('dataScraper()', () => {
       .times(1)
       .reply(200, bodyNoToken);
 
-    nock('https://kubernetes-upstream.snyk.io')
-      .post('/api/v1/runtime-results')
+    nock('https://api.snyk.io')
+      .post('/v2/kubernetes-upstream/api/v1/runtime-results?version=2023-02-10')
       .times(1)
       .reply(200, (uri, requestBody: transmitterTypes.IRuntimeDataPayload) => {
         expect(requestBody).toEqual<transmitterTypes.IRuntimeDataPayload>({
@@ -80,7 +80,7 @@ describe('dataScraper()', () => {
           ],
         });
       })
-      .post('/api/v1/runtime-results')
+      .post('/v2/kubernetes-upstream/api/v1/runtime-results?version=2023-02-10')
       .times(1)
       .reply(200, (uri, requestBody: transmitterTypes.IRuntimeDataPayload) => {
         expect(requestBody).toEqual<transmitterTypes.IRuntimeDataPayload>({
@@ -102,5 +102,12 @@ describe('dataScraper()', () => {
       });
 
     await scrapeData();
+
+    try {
+      expect(nock.isDone()).toBeTruthy();
+    } catch (err) {
+      console.error(`nock pending mocks: ${nock.pendingMocks()}`);
+      throw err;
+    }
   });
 });

programming_language: TypeScript