diff --git a/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap
index e1aead3be8d6fd0ca7f64a0e940e3561094f3b84..c779cc09bbdd7932542c6228bfa68e4e32cbc686 100644
--- a/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap
+++ b/lib/modules/datasource/cdnjs/__snapshots__/index.spec.ts.snap
@@ -6,7 +6,6 @@ exports[`modules/datasource/cdnjs/index getReleases filters releases by asset pr
   "registryUrl": "https://api.cdnjs.com/",
   "releases": [
     {
-      "newDigest": undefined,
       "version": "0.7.5",
     },
   ],
diff --git a/lib/modules/datasource/cdnjs/index.ts b/lib/modules/datasource/cdnjs/index.ts
index 5988d1b12d395bc8ced356bb0cdb5bc13349fdc9..95f4952b615760c2d63cd827b508a6e29b18d7ff 100644
--- a/lib/modules/datasource/cdnjs/index.ts
+++ b/lib/modules/datasource/cdnjs/index.ts
@@ -1,7 +1,30 @@
+import { ZodError, z } from 'zod';
+import { logger } from '../../../logger';
 import { ExternalHostError } from '../../../types/errors/external-host-error';
+import type { HttpError } from '../../../util/http';
+import { Result } from '../../../util/result';
 import { Datasource } from '../datasource';
-import type { GetReleasesConfig, ReleaseResult } from '../types';
-import type { CdnjsResponse } from './types';
+import { ReleasesConfig } from '../schema';
+import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
+
+const Homepage = z.string().optional().catch(undefined);
+
+const Repository = z
+  .object({
+    type: z.literal('git'),
+    url: z.string(),
+  })
+  .transform(({ url }) => url)
+  .optional()
+  .catch(undefined);
+
+const Assets = z.array(
+  z.object({
+    version: z.string(),
+    files: z.string().array(),
+    sri: z.record(z.string()).optional(),
+  })
+);
 
 export class CdnJsDatasource extends Datasource {
   static readonly id = 'cdnjs';
@@ -16,44 +39,68 @@ export class CdnJsDatasource extends Datasource {
 
   override readonly caching = true;
 
-  // this.handleErrors will always throw
-
-  async getReleases({
-    packageName,
-    registryUrl,
-  }: GetReleasesConfig): Promise<ReleaseResult | null> {
-    // Each library contains multiple assets, so we cache at the library level instead of per-asset
-    const library = packageName.split('/')[0];
-    // TODO: types (#7154)
-    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-    const url = `${registryUrl}libraries/${library}?fields=homepage,repository,assets`;
-    let result: ReleaseResult | null = null;
-    try {
-      const { assets, homepage, repository } = (
-        await this.http.getJson<CdnjsResponse>(url)
-      ).body;
-      if (!assets) {
-        return null;
-      }
-      const assetName = packageName.replace(`${library}/`, '');
-      const releases = assets
-        .filter(({ files }) => files.includes(assetName))
-        .map(({ version, sri }) => ({ version, newDigest: sri?.[assetName] }));
-
-      result = { releases };
-
-      if (homepage) {
-        result.homepage = homepage;
-      }
-      if (repository?.url) {
-        result.sourceUrl = repository.url;
-      }
-    } catch (err) {
-      if (err.statusCode !== 404) {
-        throw new ExternalHostError(err);
-      }
+  async getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> {
+    const result = Result.wrap(ReleasesConfig.safeParse(config))
+      .transform(({ packageName, registryUrl }) => {
+        const [library] = packageName.split('/');
+        const assetName = packageName.replace(`${library}/`, '');
+
+        const url = `${registryUrl}libraries/${library}?fields=homepage,repository,assets`;
+
+        const schema = z.object({
+          homepage: Homepage,
+          repository: Repository,
+          assets: Assets.transform((assets) =>
+            assets
+              .filter(({ files }) => files.includes(assetName))
+              .map(({ version, sri }) => {
+                const res: Release = { version };
+
+                const newDigest = sri?.[assetName];
+                if (newDigest) {
+                  res.newDigest = newDigest;
+                }
+
+                return res;
+              })
+          ),
+        });
+
+        return this.http.getJsonSafe(url, schema);
+      })
+      .transform(({ assets, homepage, repository }): ReleaseResult => {
+        const releases: Release[] = assets;
+
+        const res: ReleaseResult = { releases };
+
+        if (homepage) {
+          res.homepage = homepage;
+        }
+
+        if (repository) {
+          res.sourceUrl = repository;
+        }
+
+        return res;
+      });
+
+    const { val, err } = await result.unwrap();
+
+    if (err instanceof ZodError) {
+      logger.debug({ err }, 'cdnjs: validation error');
+      return null;
+    }
+
+    if (err) {
       this.handleGenericErrors(err);
     }
-    return result;
+
+    return val;
+  }
+
+  override handleHttpErrors(err: HttpError): void {
+    if (err.response?.statusCode !== 404) {
+      throw new ExternalHostError(err);
+    }
   }
 }
diff --git a/lib/modules/datasource/cdnjs/types.ts b/lib/modules/datasource/cdnjs/types.ts
deleted file mode 100644
index 97ff84563dd3717db973853aa1399355b76824ad..0000000000000000000000000000000000000000
--- a/lib/modules/datasource/cdnjs/types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-interface CdnjsAsset {
-  version: string;
-  files: string[];
-  sri?: Record<string, string>;
-}
-
-export interface CdnjsResponse {
-  homepage?: string;
-  repository?: {
-    type: 'git' | unknown;
-    url?: string;
-  };
-  assets?: CdnjsAsset[];
-}
diff --git a/lib/modules/datasource/schema.ts b/lib/modules/datasource/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3f31fbaa964ee7bc6defe37a6d051fce2c0aae28
--- /dev/null
+++ b/lib/modules/datasource/schema.ts
@@ -0,0 +1,6 @@
+import { z } from 'zod';
+
+export const ReleasesConfig = z.object({
+  packageName: z.string(),
+  registryUrl: z.string(),
+});