Skip to content

Commit a4dbb99

Browse files
committed
Add artifact property to indicate reason for blocking the package
1 parent 06ab474 commit a4dbb99

13 files changed

Lines changed: 130 additions & 4 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ target/
3131
Thumbs.db
3232
.directory
3333
.DS_Store
34+
35+
# Snyk Security Extension - AI Rules (auto-generated)
36+
.cursor/rules/snyk_rules.mdc

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ unzip -p distribution/target/artifactory-snyk-security-plugin-LOCAL-SNAPSHOT.zip
5959
unzip -p distribution/target/artifactory-snyk-security-plugin-LOCAL-SNAPSHOT.zip plugins/snykSecurityPlugin.groovy > distribution/docker/etc/artifactory/plugins/snykSecurityPlugin.groovy
6060
```
6161

62+
## Artifact property: block reason
63+
When a download is blocked (policy violation or Snyk API failure with `snyk.scanner.block-on-api-failure=true`), the plugin sets **`snyk.block.reason`** on the artifact to the same message returned to the client (truncated if very long). The property is removed when a new Snyk scan completes successfully (`storage.afterCreate`) or when a download is allowed after validation (each download attempt clears it before re-checking).
64+
6265
## Inspecting plugin logs
6366
In order to see the logs, set the log level for Snyk by inserting this line: `<logger name="io.snyk" level="debug"/>`
6467
into this file: `distribution/docker/etc/artifactory/logback.xml`.

core/src/main/groovy/io/snyk/plugins/artifactory/snykSecurityPlugin.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ snyk.api.organization=
7777
#snyk.scanner.lastModified.remoteOnly=true
7878

7979
# By default, if Snyk API fails while scanning an artifact for any reason, the download will be allowed.
80+
# When a download is blocked, artifact property "snyk.block.reason" records the message (see README).
8081
# Setting this property to "true" will block downloads when Snyk API fails.
8182
# Accepts: "true", "false"
8283
# Default: "false"

core/src/main/java/io/snyk/plugins/artifactory/SnykPlugin.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import io.snyk.plugins.artifactory.configuration.BaseUrlSanitiser;
55
import io.snyk.plugins.artifactory.configuration.UserAgent;
66
import io.snyk.plugins.artifactory.configuration.properties.ArtifactProperty;
7+
import io.snyk.plugins.artifactory.configuration.properties.BlockReasonProperty;
8+
import io.snyk.plugins.artifactory.configuration.properties.RepositoryArtifactProperties;
79
import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
810
import io.snyk.plugins.artifactory.exception.CannotScanException;
911
import io.snyk.plugins.artifactory.exception.SnykAPIFailureException;
@@ -30,6 +32,7 @@
3032
import java.util.Properties;
3133

3234
import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.*;
35+
import static io.snyk.plugins.artifactory.configuration.properties.ArtifactProperty.BLOCK_REASON;
3336
import static java.lang.String.format;
3437

3538
public class SnykPlugin {
@@ -39,6 +42,7 @@ public class SnykPlugin {
3942
private ConfigurationModule configurationModule;
4043
private AuditModule auditModule;
4144
private ScannerModule scannerModule;
45+
private Repositories repositories;
4246

4347
SnykPlugin() {
4448
}
@@ -65,6 +69,7 @@ public SnykPlugin(@Nonnull Repositories repositories, File pluginsDirectory) {
6569

6670
auditModule = new AuditModule();
6771
ScannerResolver scannerResolver = ScannerResolver.setup(configurationModule, snykClient);
72+
this.repositories = repositories;
6873
scannerModule = new ScannerModule(configurationModule, repositories, scannerResolver);
6974

7075
LOG.info("Plugin version: {}", pluginVersion);
@@ -131,6 +136,14 @@ public void handleBeforeDownloadEvent(RepoPath repoPath) {
131136
LOG.debug(message);
132137
if ("true".equals(blockOnApiFailure)) {
133138
LOG.debug("Blocking download. Plugin Property \"{}\" is \"true\". {}", blockOnApiFailurePropertyKey, repoPath);
139+
try {
140+
new RepositoryArtifactProperties(repoPath, repositories).set(
141+
BLOCK_REASON,
142+
BlockReasonProperty.truncateForStorage(message)
143+
);
144+
} catch (Exception ex) {
145+
LOG.warn("Could not set {} for {}: {}", BLOCK_REASON.propertyKey(), repoPath, ex.getMessage());
146+
}
134147
throw new CancelException(message, 500);
135148
}
136149
}

core/src/main/java/io/snyk/plugins/artifactory/configuration/properties/ArtifactProperties.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ public interface ArtifactProperties {
1111
void set(ArtifactProperty property, String value);
1212

1313
boolean has(ArtifactProperty property);
14+
15+
/** Removes the property from the artifact if present. */
16+
void remove(ArtifactProperty property);
1417
}

core/src/main/java/io/snyk/plugins/artifactory/configuration/properties/ArtifactProperty.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ public enum ArtifactProperty {
99
ISSUE_VULNERABILITIES_FORCE_DOWNLOAD_INFO("snyk.issue.vulnerabilities.forceDownload.info"),
1010
ISSUE_LICENSES("snyk.issue.licenses"),
1111
ISSUE_LICENSES_FORCE_DOWNLOAD("snyk.issue.licenses.forceDownload"),
12-
ISSUE_LICENSES_FORCE_DOWNLOAD_INFO("snyk.issue.licenses.forceDownload.info");
12+
ISSUE_LICENSES_FORCE_DOWNLOAD_INFO("snyk.issue.licenses.forceDownload.info"),
13+
/** Set when a download is blocked; cleared on successful scan or allowed download. */
14+
BLOCK_REASON("snyk.block.reason");
1315

1416
private final String propertyKey;
1517

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.snyk.plugins.artifactory.configuration.properties;
2+
3+
public final class BlockReasonProperty {
4+
5+
/** Max length for a single property value. */
6+
public static final int MAX_STORED_LENGTH = 2400;
7+
8+
private BlockReasonProperty() {
9+
}
10+
11+
public static String truncateForStorage(String message) {
12+
if (message == null || message.isEmpty()) {
13+
return "";
14+
}
15+
if (message.length() <= MAX_STORED_LENGTH) {
16+
return message;
17+
}
18+
return message.substring(0, MAX_STORED_LENGTH - 3) + "...";
19+
}
20+
}

core/src/main/java/io/snyk/plugins/artifactory/configuration/properties/RepositoryArtifactProperties.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,12 @@ public void set(ArtifactProperty property, String value) {
3535
public boolean has(ArtifactProperty property) {
3636
return repositories.hasProperty(repoPath, property.propertyKey());
3737
}
38+
39+
@Override
40+
public void remove(ArtifactProperty property) {
41+
if (!repositories.hasProperty(repoPath, property.propertyKey())) {
42+
return;
43+
}
44+
repositories.deleteProperty(repoPath, property.propertyKey());
45+
}
3846
}

core/src/main/java/io/snyk/plugins/artifactory/model/MonitoredArtifact.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public MonitoredArtifact write(ArtifactProperties properties) {
6363
setDefaultArtifactProperty(properties, ISSUE_LICENSES_FORCE_DOWNLOAD, "false");
6464
setDefaultArtifactProperty(properties, ISSUE_LICENSES_FORCE_DOWNLOAD_INFO, "");
6565

66+
properties.remove(BLOCK_REASON);
67+
6668
return this;
6769
}
6870

core/src/main/java/io/snyk/plugins/artifactory/scanner/ScannerModule.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
44
import io.snyk.plugins.artifactory.configuration.PluginConfiguration;
55
import io.snyk.plugins.artifactory.configuration.properties.ArtifactProperties;
6+
import io.snyk.plugins.artifactory.configuration.properties.BlockReasonProperty;
67
import io.snyk.plugins.artifactory.configuration.properties.RepositoryArtifactProperties;
78
import io.snyk.plugins.artifactory.ecosystem.EcosystemResolver;
89
import io.snyk.plugins.artifactory.ecosystem.RepositoryMetadataEcosystemResolver;
@@ -12,6 +13,7 @@
1213
import io.snyk.plugins.artifactory.model.ValidationSettings;
1314
import org.artifactory.fs.FileLayoutInfo;
1415
import org.artifactory.fs.ItemInfo;
16+
import org.artifactory.exception.CancelException;
1517
import org.artifactory.repo.RepoPath;
1618
import org.artifactory.repo.Repositories;
1719
import org.artifactory.repo.RepositoryConfiguration;
@@ -24,6 +26,7 @@
2426
import java.time.Instant;
2527
import java.util.Optional;
2628

29+
import static io.snyk.plugins.artifactory.configuration.properties.ArtifactProperty.BLOCK_REASON;
2730
import static java.util.Objects.requireNonNull;
2831

2932
public class ScannerModule {
@@ -64,7 +67,7 @@ public void filterAccess(@Nonnull RepoPath repoPath) {
6467

6568
resolveArtifact(repoPath)
6669
.ifPresentOrElse(
67-
this::filter,
70+
artifact -> filter(repoPath, artifact),
6871
() -> LOG.info("No vulnerability info found for {}", repoPath)
6972
);
7073
}
@@ -92,10 +95,26 @@ private MonitoredArtifact runTestWith(PackageScanner scanner, RepoPath repoPath)
9295
return toMonitoredArtifact(testResult, repoPath);
9396
}
9497

95-
private void filter(MonitoredArtifact artifact) {
98+
private void filter(RepoPath repoPath, MonitoredArtifact artifact) {
99+
ArtifactProperties props = properties(repoPath);
100+
try {
101+
props.remove(BLOCK_REASON);
102+
} catch (Exception e) {
103+
LOG.debug("Could not clear block reason for {}: {}", repoPath, e.getMessage());
104+
}
105+
96106
ValidationSettings validationSettings = ValidationSettings.from(configurationModule);
97107
PackageValidator validator = new PackageValidator(validationSettings);
98-
validator.validate(artifact);
108+
try {
109+
validator.validate(artifact);
110+
} catch (CancelException e) {
111+
try {
112+
props.set(BLOCK_REASON, BlockReasonProperty.truncateForStorage(e.getMessage()));
113+
} catch (Exception ex) {
114+
LOG.warn("Could not set {} for {}: {}", BLOCK_REASON.propertyKey(), repoPath, ex.getMessage());
115+
}
116+
throw e;
117+
}
99118
}
100119

101120
private @NotNull MonitoredArtifact toMonitoredArtifact(TestResult testResult, @NotNull RepoPath repoPath) {

0 commit comments

Comments
 (0)