From fa6391c62d9fcfb90887055a3add9d708c1ff344 Mon Sep 17 00:00:00 2001 From: Calum Harrison Date: Thu, 30 Jul 2026 10:59:14 +0100 Subject: [PATCH] feat: resolve Gradle distribution URLs on a warm cache Component metadata reads artifact provenance as Gradle resolves dependencies. On a warm cache Gradle skips that work, so nothing observes the downloads and distribution:url comes back empty. --gradle-refresh-dependencies is the opt-in that forces a refresh so those reads fire. - Thread the flag through the legacy CLI. cli-extension-dep-graph already forwards --gradle-refresh-dependencies down the legacy resolution path, but the legacy CLI dropped it. Recognise it off its dashed key and pass it as gradleRefreshDependencies, mirroring include-component-metadata. The spread is conditional so the default plugin-options shape is unchanged for scans that never set it. Kept off SupportedUserReachableFacingCliArgs so it stays internal. - Bump cli-extension-dep-graph to v2.8.1, the first release whose legacy resolution forwards the flag rather than dropping it. - Bump cli-extension-sbom, which registers the hidden flag on sbom create so it survives the config clone into that resolution path. - Bump snyk-gradle-plugin to 7.1.0, which reads gradleRefreshDependencies and appends --refresh-dependencies. - Cover the threading in all three states: forwarded when true, forwarded when explicitly false, and absent from the options entirely when unset. The plugin only appends --refresh-dependencies inside its includeComponentMetadata branch, so the flag needs --include-component-metadata alongside it to have any effect. Co-authored-by: Chris Heppell Co-Authored-By: Claude Opus 5 (1M context) --- cliv2-private/go.mod | 4 +- cliv2-private/go.sum | 8 +-- cliv2/go.mod | 4 +- cliv2/go.sum | 8 +-- package-lock.json | 10 +-- package.json | 2 +- src/lib/plugins/get-single-plugin-result.ts | 8 +++ src/lib/types.ts | 5 ++ .../plugins/get-single-plugin-result.spec.ts | 64 +++++++++++++++++++ 9 files changed, 95 insertions(+), 18 deletions(-) diff --git a/cliv2-private/go.mod b/cliv2-private/go.mod index 100f921dd1..b60b346400 100644 --- a/cliv2-private/go.mod +++ b/cliv2-private/go.mod @@ -210,11 +210,11 @@ require ( github.com/skeema/knownhosts v1.3.2 // indirect github.com/snyk/cli-extension-agent-scan v0.0.0-20260715092951-4f8fa1b9886c // indirect github.com/snyk/cli-extension-ai-bom v0.0.0-20260721084000-da8dc26c77f2 // indirect - github.com/snyk/cli-extension-dep-graph/v2 v2.7.2 // indirect + github.com/snyk/cli-extension-dep-graph/v2 v2.8.1 // indirect github.com/snyk/cli-extension-iac v0.0.0-20260515092252-505c498f1077 // indirect github.com/snyk/cli-extension-iac-rules v0.0.0-20260515141409-fb475901bb8e // indirect github.com/snyk/cli-extension-os-flows v0.0.0-20260722114313-168a09671091 // indirect - github.com/snyk/cli-extension-sbom v0.0.0-20260722102401-3c3af28e7b93 // indirect + github.com/snyk/cli-extension-sbom v0.0.0-20260728161808-90fff659c450 // indirect github.com/snyk/cli-extension-secrets v0.0.0-20260619070019-764c5c87eda3 // indirect github.com/snyk/code-client-go v1.27.5 // indirect github.com/snyk/container-cli v0.0.0-20260213211631-cd2b2cf8f3ea // indirect diff --git a/cliv2-private/go.sum b/cliv2-private/go.sum index 3543199863..f29288fd57 100644 --- a/cliv2-private/go.sum +++ b/cliv2-private/go.sum @@ -573,16 +573,16 @@ github.com/snyk/cli-extension-ai-bom v0.0.0-20260721084000-da8dc26c77f2 h1:fYTWY github.com/snyk/cli-extension-ai-bom v0.0.0-20260721084000-da8dc26c77f2/go.mod h1:axE+HO8U9CIcTjQ7k7BOLgdyZfNGqMn5QvZ0aIkUY4U= github.com/snyk/cli-extension-cos v0.0.0-20260730085209-d326e44b9140 h1:7yYsL4egJlFruDMgsng/nXTe5MFx8Ciys0+ADH61yOw= github.com/snyk/cli-extension-cos v0.0.0-20260730085209-d326e44b9140/go.mod h1:YpcOdcMBJOzwtla/OOoBOArx27Bc2v42Vcwd/FeGl9M= -github.com/snyk/cli-extension-dep-graph/v2 v2.7.2 h1:+D/r8zDdREbrZs1EZ4DmVzEf7oMHA2rRozhuaVFyg8U= -github.com/snyk/cli-extension-dep-graph/v2 v2.7.2/go.mod h1:I0GXbV6Rk8T0AEC9EugE4AgYE5SbDn0m0bGgAjZqy5U= +github.com/snyk/cli-extension-dep-graph/v2 v2.8.1 h1:ZlLN8KbMZiRN7QsiLzovFLsoIEVytC77+OESr+6wDhI= +github.com/snyk/cli-extension-dep-graph/v2 v2.8.1/go.mod h1:I0GXbV6Rk8T0AEC9EugE4AgYE5SbDn0m0bGgAjZqy5U= github.com/snyk/cli-extension-iac v0.0.0-20260515092252-505c498f1077 h1:lteivlSoQL0zYyFcGQjEPZcQgDkzM5PcgIHlIgzRuhg= github.com/snyk/cli-extension-iac v0.0.0-20260515092252-505c498f1077/go.mod h1:wRpQrWCjzWTPA5WK1xaHjWWI5zfY0qKe12PfKb+lcMk= github.com/snyk/cli-extension-iac-rules v0.0.0-20260515141409-fb475901bb8e h1:HE22F5/ivTJ4TlTUnjasZqfkDKVyfmYjf36RaIZqrs4= github.com/snyk/cli-extension-iac-rules v0.0.0-20260515141409-fb475901bb8e/go.mod h1:YN+M0+nNZ5VoQN2SxSvZSoTxs/PkdLgfNfUWvsKVL7o= github.com/snyk/cli-extension-os-flows v0.0.0-20260722114313-168a09671091 h1:MDR3Tj4tFVNPonOtL+AzX0osWdOkNq0lEpQQehvjGak= github.com/snyk/cli-extension-os-flows v0.0.0-20260722114313-168a09671091/go.mod h1:qNOr9aDWYUa3Tl74k+04hOCEgKcgpBLVcvs26L4ooN0= -github.com/snyk/cli-extension-sbom v0.0.0-20260722102401-3c3af28e7b93 h1:iCHSAW2OkvObczF5zNmEwnGeavQri4/rO1w1efaAfT4= -github.com/snyk/cli-extension-sbom v0.0.0-20260722102401-3c3af28e7b93/go.mod h1:YUDazoukFqA0OpYuACIoqVEsfPCAFXo9XotUk9RjmUY= +github.com/snyk/cli-extension-sbom v0.0.0-20260728161808-90fff659c450 h1:OZamCjj08Ss87/ELqWj2vI0uB14aotWtneLlPIHxq8o= +github.com/snyk/cli-extension-sbom v0.0.0-20260728161808-90fff659c450/go.mod h1:YUDazoukFqA0OpYuACIoqVEsfPCAFXo9XotUk9RjmUY= github.com/snyk/cli-extension-secrets v0.0.0-20260619070019-764c5c87eda3 h1:wBYQm4YP65FTZ4rZ0iunhsIUYBindUEnfRWRi+Rugag= github.com/snyk/cli-extension-secrets v0.0.0-20260619070019-764c5c87eda3/go.mod h1:D/Bk0EH8np/d6c1tG7wazpHI6iU9m8KzxytFcIQkEvQ= github.com/snyk/code-client-go v1.27.5 h1:fog+j64VKoj5TH/sGehJ5AXe52q7LpOJtKEhwQrcXx4= diff --git a/cliv2/go.mod b/cliv2/go.mod index a2b068cc94..472360aa7e 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -13,11 +13,11 @@ require ( github.com/rs/zerolog v1.34.0 github.com/snyk/cli-extension-agent-scan v0.0.0-20260715092951-4f8fa1b9886c github.com/snyk/cli-extension-ai-bom v0.0.0-20260721084000-da8dc26c77f2 - github.com/snyk/cli-extension-dep-graph/v2 v2.7.2 + github.com/snyk/cli-extension-dep-graph/v2 v2.8.1 github.com/snyk/cli-extension-iac v0.0.0-20260515092252-505c498f1077 github.com/snyk/cli-extension-iac-rules v0.0.0-20260515141409-fb475901bb8e github.com/snyk/cli-extension-os-flows v0.0.0-20260722114313-168a09671091 - github.com/snyk/cli-extension-sbom v0.0.0-20260722102401-3c3af28e7b93 + github.com/snyk/cli-extension-sbom v0.0.0-20260728161808-90fff659c450 github.com/snyk/cli-extension-secrets v0.0.0-20260619070019-764c5c87eda3 github.com/snyk/code-client-go v1.27.0 github.com/snyk/container-cli v0.0.0-20260213211631-cd2b2cf8f3ea diff --git a/cliv2/go.sum b/cliv2/go.sum index 57b1c33126..158507bf51 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -527,16 +527,16 @@ github.com/snyk/cli-extension-agent-scan v0.0.0-20260715092951-4f8fa1b9886c h1:o github.com/snyk/cli-extension-agent-scan v0.0.0-20260715092951-4f8fa1b9886c/go.mod h1:g1qxNvAl2Qb0V4UJivop+hCqehHVqzB0taEjwTm6VlU= github.com/snyk/cli-extension-ai-bom v0.0.0-20260721084000-da8dc26c77f2 h1:fYTWYiCqw+NqeETMP/0TdVKjCHrHRng8M6+nxvrmluo= github.com/snyk/cli-extension-ai-bom v0.0.0-20260721084000-da8dc26c77f2/go.mod h1:axE+HO8U9CIcTjQ7k7BOLgdyZfNGqMn5QvZ0aIkUY4U= -github.com/snyk/cli-extension-dep-graph/v2 v2.7.2 h1:+D/r8zDdREbrZs1EZ4DmVzEf7oMHA2rRozhuaVFyg8U= -github.com/snyk/cli-extension-dep-graph/v2 v2.7.2/go.mod h1:I0GXbV6Rk8T0AEC9EugE4AgYE5SbDn0m0bGgAjZqy5U= +github.com/snyk/cli-extension-dep-graph/v2 v2.8.1 h1:ZlLN8KbMZiRN7QsiLzovFLsoIEVytC77+OESr+6wDhI= +github.com/snyk/cli-extension-dep-graph/v2 v2.8.1/go.mod h1:I0GXbV6Rk8T0AEC9EugE4AgYE5SbDn0m0bGgAjZqy5U= github.com/snyk/cli-extension-iac v0.0.0-20260515092252-505c498f1077 h1:lteivlSoQL0zYyFcGQjEPZcQgDkzM5PcgIHlIgzRuhg= github.com/snyk/cli-extension-iac v0.0.0-20260515092252-505c498f1077/go.mod h1:wRpQrWCjzWTPA5WK1xaHjWWI5zfY0qKe12PfKb+lcMk= github.com/snyk/cli-extension-iac-rules v0.0.0-20260515141409-fb475901bb8e h1:HE22F5/ivTJ4TlTUnjasZqfkDKVyfmYjf36RaIZqrs4= github.com/snyk/cli-extension-iac-rules v0.0.0-20260515141409-fb475901bb8e/go.mod h1:YN+M0+nNZ5VoQN2SxSvZSoTxs/PkdLgfNfUWvsKVL7o= github.com/snyk/cli-extension-os-flows v0.0.0-20260722114313-168a09671091 h1:MDR3Tj4tFVNPonOtL+AzX0osWdOkNq0lEpQQehvjGak= github.com/snyk/cli-extension-os-flows v0.0.0-20260722114313-168a09671091/go.mod h1:qNOr9aDWYUa3Tl74k+04hOCEgKcgpBLVcvs26L4ooN0= -github.com/snyk/cli-extension-sbom v0.0.0-20260722102401-3c3af28e7b93 h1:iCHSAW2OkvObczF5zNmEwnGeavQri4/rO1w1efaAfT4= -github.com/snyk/cli-extension-sbom v0.0.0-20260722102401-3c3af28e7b93/go.mod h1:YUDazoukFqA0OpYuACIoqVEsfPCAFXo9XotUk9RjmUY= +github.com/snyk/cli-extension-sbom v0.0.0-20260728161808-90fff659c450 h1:OZamCjj08Ss87/ELqWj2vI0uB14aotWtneLlPIHxq8o= +github.com/snyk/cli-extension-sbom v0.0.0-20260728161808-90fff659c450/go.mod h1:YUDazoukFqA0OpYuACIoqVEsfPCAFXo9XotUk9RjmUY= github.com/snyk/cli-extension-secrets v0.0.0-20260619070019-764c5c87eda3 h1:wBYQm4YP65FTZ4rZ0iunhsIUYBindUEnfRWRi+Rugag= github.com/snyk/cli-extension-secrets v0.0.0-20260619070019-764c5c87eda3/go.mod h1:D/Bk0EH8np/d6c1tG7wazpHI6iU9m8KzxytFcIQkEvQ= github.com/snyk/code-client-go v1.27.0 h1:FOX4JzgHssm5fei4ALyrBAIL9eJGnG52yTkqWcM1Qew= diff --git a/package-lock.json b/package-lock.json index 44296af18d..10eb71a679 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "snyk-cpp-plugin": "^2.24.3", "snyk-docker-plugin": "^9.16.0", "snyk-go-plugin": "2.2.1", - "snyk-gradle-plugin": "7.0.0", + "snyk-gradle-plugin": "7.1.0", "snyk-module": "3.1.0", "snyk-mvn-plugin": "4.9.2", "snyk-nodejs-lockfile-parser": "2.10.0", @@ -19922,9 +19922,9 @@ "integrity": "sha512-N5ixXjzTy4QDQH0Q9YFjqIWd6zH6936Djpl2m9QNFmDv5Fum8q8BjkpAcHNMzOFE0IwQrFhJWex3AN6kS0OSwg==" }, "node_modules/snyk-gradle-plugin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-7.0.0.tgz", - "integrity": "sha512-wVuEWgHk+4HDgawLEhjz+zZw8Lvo7qo7O1D3TZ/xLHx//HDFYxuFtT11CVJ06Wc9Z6ioKfjwqoYkkbAmJko4/w==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-7.1.0.tgz", + "integrity": "sha512-l68wt/tdNk+Vka9zJZFgFDH3XkDrWUXsWnbP7u5BXcLWNtFSsM5SNdkyenhgiSCjI/T1xHdpArroch2NZRrBIA==", "license": "Apache-2.0", "dependencies": { "@common.js/yocto-queue": "^1.1.1", @@ -19935,7 +19935,7 @@ "p-map": "^4.0.0", "packageurl-js": "^1.2.1", "shescape": "2.1.11", - "tmp": "^0.2.5", + "tmp": "^0.2.7", "tslib": "^2.8.1" }, "engines": { diff --git a/package.json b/package.json index 10db4351c8..205ae23b11 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "snyk-cpp-plugin": "^2.24.3", "snyk-docker-plugin": "^9.16.0", "snyk-go-plugin": "2.2.1", - "snyk-gradle-plugin": "7.0.0", + "snyk-gradle-plugin": "7.1.0", "snyk-module": "3.1.0", "snyk-mvn-plugin": "4.9.2", "snyk-nodejs-lockfile-parser": "2.10.0", diff --git a/src/lib/plugins/get-single-plugin-result.ts b/src/lib/plugins/get-single-plugin-result.ts index 50a5c6885b..09e45c6c50 100644 --- a/src/lib/plugins/get-single-plugin-result.ts +++ b/src/lib/plugins/get-single-plugin-result.ts @@ -44,6 +44,14 @@ export async function getSinglePluginResult( ...(options['include-component-metadata'] !== undefined && { includeComponentMetadata: options['include-component-metadata'], }), + // Forwarded alongside include-component-metadata (see + // cli-extension-dep-graph's legacy resolution path). The Gradle plugin + // reads it to force --refresh-dependencies so distribution:url can be + // resolved; ignored by other plugins. Only added when set to keep the + // default plugin-options shape unchanged. + ...(options['gradle-refresh-dependencies'] !== undefined && { + gradleRefreshDependencies: options['gradle-refresh-dependencies'], + }), }, snykHttpClient, ); diff --git a/src/lib/types.ts b/src/lib/types.ts index ef80d15d17..65b759d621 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -60,6 +60,11 @@ export interface Options { // get-single-plugin-result.ts). Intentionally not part of // SupportedUserReachableFacingCliArgs so it stays off the documented surface. 'include-component-metadata'?: boolean; + // Internal/undocumented flag forwarded by cli-extension-dep-graph alongside + // include-component-metadata; makes the Gradle plugin force + // --refresh-dependencies so distribution:url provenance can be resolved. Kept + // off SupportedUserReachableFacingCliArgs for the same reason as above. + 'gradle-refresh-dependencies'?: boolean; includeProvenance?: boolean; fingerprintAlgorithm?: string; 'project-name'?: string; diff --git a/test/jest/unit/lib/plugins/get-single-plugin-result.spec.ts b/test/jest/unit/lib/plugins/get-single-plugin-result.spec.ts index 99e4a933a5..44178fe027 100644 --- a/test/jest/unit/lib/plugins/get-single-plugin-result.spec.ts +++ b/test/jest/unit/lib/plugins/get-single-plugin-result.spec.ts @@ -248,6 +248,70 @@ describe('getSinglePluginResult', () => { }); }); + describe('gradle-refresh-dependencies', () => { + it('should forward gradleRefreshDependencies to the plugin when set', async () => { + const options: Options & TestOptions = { + path: '/test', + packageManager: 'gradle', + showVulnPaths: 'some', + 'gradle-refresh-dependencies': true, + }; + + await getSinglePluginResult('/test', options); + + expect(mockModuleInfo.inspect).toHaveBeenCalledWith( + '/test', + undefined, + expect.objectContaining({ + gradleRefreshDependencies: true, + }), + snykHttpClient, + ); + }); + + it('should forward gradleRefreshDependencies=false when explicitly disabled', async () => { + const options: Options & TestOptions = { + path: '/test', + packageManager: 'gradle', + showVulnPaths: 'some', + 'gradle-refresh-dependencies': false, + }; + + await getSinglePluginResult('/test', options); + + expect(mockModuleInfo.inspect).toHaveBeenCalledWith( + '/test', + undefined, + expect.objectContaining({ + gradleRefreshDependencies: false, + }), + snykHttpClient, + ); + }); + + // The flag is gateway-driven and absent for almost every scan, so an unset + // flag must leave the plugin-options shape untouched rather than passing + // gradleRefreshDependencies: undefined. + it('should omit gradleRefreshDependencies entirely when unset', async () => { + const options: Options & TestOptions = { + path: '/test', + packageManager: 'gradle', + showVulnPaths: 'some', + }; + + await getSinglePluginResult('/test', options); + + expect(mockModuleInfo.inspect).toHaveBeenCalledWith( + '/test', + undefined, + expect.not.objectContaining({ + gradleRefreshDependencies: expect.anything(), + }), + snykHttpClient, + ); + }); + }); + describe('MonitorOptions', () => { it('should work with MonitorOptions for gomodules', async () => { const options: Options & MonitorOptions = {