Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d7d5d4c
Add similarity service to publish extension and namespace workflow (#…
janbro Dec 21, 2025
5a21a38
Add Secret Scanning to Publish Workflow (#1510)
janbro Jan 6, 2026
0756366
fix unit tests due to mocked secret scanning service
netomi Jan 7, 2026
523c6d4
Refactor validation services (#1531)
janbro Jan 7, 2026
495aeb9
remove fix for SecretScanningService after isEnabled has been externa…
netomi Jan 7, 2026
c55193d
Add extension scanning worfklow and admin dashboard (#1537)
janbro Jan 12, 2026
c1ccc62
fix admin scans api requests (#1540)
alejandro-n-rivera Jan 12, 2026
5957592
Fix scan api tests (#1544)
janbro Jan 14, 2026
169409a
fix admin Extension Scans light theme UI (#1543)
alejandro-n-rivera Jan 14, 2026
6d0940b
Add Long-Running Scan Infrastructure for Async External Scanners (#1565)
janbro Jan 27, 2026
c481aee
formatting to trigger rebuild
netomi Jan 28, 2026
44c3abb
minor fixes wrt formatting and obvious fixes
netomi Jan 28, 2026
689d677
minor cleanups
netomi Jan 28, 2026
89a2d75
Fix ambiguity in secret check service task executor. Removed unneeded…
janbro Jan 29, 2026
c94513d
minor fixes
netomi Jan 29, 2026
547d977
Fix/security improvements (#1574)
janbro Jan 30, 2026
5d5335c
fix unit tests
netomi Jan 30, 2026
1f29c50
Parallelize block list scanning. Add mime type check for secret (#1575)
janbro Feb 1, 2026
42f1709
Add required configuration for publish checks (#1584)
janbro Feb 4, 2026
6dd608e
fix download url for quarantined extensions
netomi Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ buildscript {
classpath "com.adarshr.test-logger:com.adarshr.test-logger.gradle.plugin:4.0.0"
}
}

plugins {
id 'java'
id 'scala'
Expand All @@ -18,6 +19,7 @@ plugins {
id 'io.gatling.gradle' version '3.14.9'
id 'maven-publish'
}

apply plugin: 'org.hibernate.orm'
apply plugin: 'com.adarshr.test-logger'

Expand All @@ -43,7 +45,9 @@ def versions = [
jaxb_impl: '2.3.8',
gatling: '3.14.9',
loki4j: '1.4.2',
jedis: '6.2.0'
jedis: '6.2.0',
re2j: '1.7',
jsonpath: '2.9.0'
]
ext['junit-jupiter.version'] = versions.junit
java {
Expand Down Expand Up @@ -131,11 +135,15 @@ dependencies {
implementation "com.fasterxml.jackson.module:jackson-module-jaxb-annotations:${versions.jackson}"
implementation "com.fasterxml.woodstox:woodstox-core:${versions.woodstox}"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${versions.jackson}"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${versions.jackson}"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-toml:${versions.jackson}"
implementation "javax.xml.bind:jaxb-api:${versions.jaxb_api}"
implementation "com.sun.xml.bind:jaxb-impl:${versions.jaxb_impl}"
implementation "org.apache.commons:commons-lang3:${versions.commons_lang3}"
implementation "org.apache.httpcomponents.client5:httpclient5"
implementation "org.apache.tika:tika-core:${versions.tika}"
implementation "com.google.re2j:re2j:${versions.re2j}"
implementation "com.jayway.jsonpath:json-path:${versions.jsonpath}"
implementation "com.github.loki4j:loki-logback-appender:${versions.loki4j}"
implementation "io.micrometer:micrometer-tracing"
implementation "io.micrometer:micrometer-tracing-bridge-otel"
Expand Down
39 changes: 39 additions & 0 deletions server/src/dev/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,42 @@ ovsx:
revoked-access-tokens:
subject: 'Open VSX Access Tokens Revoked'
template: 'revoked-access-tokens.html'
scanning:
enabled: true
# Shared archive limits for all scanning checks (secret detection, blocklist, etc.)
max-archive-size-bytes: 1073741824 # 1 GB total archive limit
max-single-file-bytes: 268435456 # 256 MB per-file limit
max-entry-count: 100000 # Max ZIP entries to process
blocklist-check:
enabled: true
enforced: false
required: false
similarity:
enabled: true
enforced: false
required: false
similarity-threshold: 0.2
skip-if-publisher-verified: false
only-protect-verified-names: false
allow-similarity-to-own-names: true
only-check-new-extensions: true
secret-detection:
enabled: true
enforced: false
required: false
rules-path: 'classpath:scanning/secret-detection-custom-rules.yaml'
suppression-markers: 'secret-detector:ignore,gitleaks:allow,nosecret,@suppress-secret'
gitleaks:
auto-fetch: true
force-refresh: true
output-path: '/tmp/secret-detection-rules-gitleaks.yaml'
scheduled-refresh: true
refresh-cron: '0 0 3 * * *' # Daily at 3 AM
skip-rule-ids: 'generic-api-key' # Rule IDs that produce too many false positives
max-findings: 200
minified-line-threshold: 10000
long-line-no-space-threshold: 1000
regex-context-chars: 100
debug-preview-chars: 10
timeout-seconds: 10
timeout-check-interval: 100
12 changes: 11 additions & 1 deletion server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ public String getVersion() {
return vsixManifest.path(MANIFEST_METADATA).path(MANIFEST_IDENTITY).path("Version").asText();
}

private String getTargetPlatform() {
public String getTargetPlatform() {
loadVsixManifest();
var targetPlatform = vsixManifest.path(MANIFEST_METADATA).path(MANIFEST_IDENTITY).path("TargetPlatform").asText();
if (targetPlatform.isEmpty()) {
targetPlatform = TargetPlatform.NAME_UNIVERSAL;
Expand All @@ -271,6 +272,15 @@ private String getTargetPlatform() {
return targetPlatform;
}

public String getDisplayName() {
loadVsixManifest();
var displayName = vsixManifest.path(MANIFEST_METADATA).path("DisplayName").asText();
if (StringUtils.isBlank(displayName)) {
return getExtensionName();
}
return displayName;
}

private List<String> getTags() {
var tags = vsixManifest.path(MANIFEST_METADATA).path("Tags").asText();
return asStringList(tags, ",").stream()
Expand Down
59 changes: 52 additions & 7 deletions server/src/main/java/org/eclipse/openvsx/ExtensionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.eclipse.openvsx.json.TargetPlatformVersionJson;
import org.eclipse.openvsx.publish.PublishExtensionVersionHandler;
import org.eclipse.openvsx.repositories.RepositoryService;
import org.eclipse.openvsx.scanning.ExtensionScanPersistenceService;
import org.eclipse.openvsx.scanning.ExtensionScanService;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.util.ErrorResultException;
import org.eclipse.openvsx.util.NamingUtil;
Expand All @@ -46,7 +48,6 @@

@Component
public class ExtensionService {

private static final int MAX_CONTENT_SIZE = 512 * 1024 * 1024;

private final EntityManager entityManager;
Expand All @@ -55,6 +56,8 @@ public class ExtensionService {
private final CacheService cache;
private final PublishExtensionVersionHandler publishHandler;
private final JobRequestScheduler scheduler;
private final ExtensionScanService scanService;
private final ExtensionScanPersistenceService scanPersistenceService;

@Value("${ovsx.publishing.require-license:false}")
boolean requireLicense;
Expand All @@ -68,14 +71,18 @@ public ExtensionService(
SearchUtilService search,
CacheService cache,
PublishExtensionVersionHandler publishHandler,
JobRequestScheduler scheduler
JobRequestScheduler scheduler,
ExtensionScanService scanService,
ExtensionScanPersistenceService scanPersistenceService
) {
this.entityManager = entityManager;
this.repositories = repositories;
this.search = search;
this.cache = cache;
this.publishHandler = publishHandler;
this.scheduler = scheduler;
this.scanService = scanService;
this.scanPersistenceService = scanPersistenceService;
}

@Transactional
Expand All @@ -86,12 +93,46 @@ public ExtensionVersion mirrorVersion(TempFile extensionFile, String signatureNa
}

public ExtensionVersion publishVersion(InputStream content, PersonalAccessToken token) throws ErrorResultException {
if (scanService.isEnabled()) {
return publishVersionWithScan(content, token);
} else {
var extensionFile = createExtensionFile(content);
doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);
publishHandler.publishAsync(extensionFile, this);
var download = extensionFile.getResource();
publishHandler.schedulePublicIdJob(download);
return download.getExtension();
}
}

private ExtensionVersion publishVersionWithScan(InputStream content, PersonalAccessToken token) throws ErrorResultException {
var extensionFile = createExtensionFile(content);
doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);
publishHandler.publishAsync(extensionFile, this);
var download = extensionFile.getResource();
publishHandler.schedulePublicIdJob(download);
return download.getExtension();
ExtensionScan scan = null;

try (var processor = new ExtensionProcessor(extensionFile)) {
scan = scanService.initializeScan(processor, token.getUser());

scanService.runValidation(scan, extensionFile, token.getUser());

doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true);

// Publish async handles requesting the longrunning scans
publishHandler.publishAsync(extensionFile, this, scan);
var download = extensionFile.getResource();
publishHandler.schedulePublicIdJob(extensionFile.getResource());
return download.getExtension();
} catch (ErrorResultException e) {
// ErrorResultException is thrown by doPublish when the extension is not valid, so we can remove the scan
if (scan != null && !scan.isCompleted()) {
scanService.removeScan(scan);
}
throw e;
} catch (Exception e) {
if (scan != null && !scan.isCompleted()) {
scanService.markScanAsErrored(scan, "Unexpected error: " + e.getMessage());
}
throw e;
}
}

private void doPublish(TempFile extensionFile, String binaryName, PersonalAccessToken token, LocalDateTime timestamp, boolean checkDependencies) {
Expand Down Expand Up @@ -252,6 +293,10 @@ protected ResultJson deleteExtension(ExtensionVersion extVersion) {
}

private void removeExtensionVersion(ExtensionVersion extVersion) {
// Clean up any pending scan jobs for this extension version
// to prevent "file not found" errors after deletion
scanPersistenceService.deleteScansForExtensionVersion(extVersion.getId());

repositories.findFiles(extVersion).map(RemoveFileJobRequest::new).forEach(scheduler::enqueue);
repositories.deleteFiles(extVersion);
entityManager.remove(extVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import org.eclipse.openvsx.search.ISearchService;
import org.eclipse.openvsx.search.SearchResult;
import org.eclipse.openvsx.search.SearchUtilService;
import org.eclipse.openvsx.search.SimilarityCheckService;
import org.eclipse.openvsx.storage.StorageUtilService;
import javax.annotation.Nullable;
import org.eclipse.openvsx.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -63,6 +65,7 @@ public class LocalRegistryService implements IExtensionRegistry {
private final EclipseService eclipse;
private final CacheService cache;
private final ExtensionVersionIntegrityService integrityService;
private final SimilarityCheckService similarityCheckService;

public LocalRegistryService(
EntityManager entityManager,
Expand All @@ -75,7 +78,8 @@ public LocalRegistryService(
StorageUtilService storageUtil,
EclipseService eclipse,
CacheService cache,
ExtensionVersionIntegrityService integrityService
ExtensionVersionIntegrityService integrityService,
@Nullable SimilarityCheckService similarityCheckService
) {
this.entityManager = entityManager;
this.repositories = repositories;
Expand All @@ -88,6 +92,7 @@ public LocalRegistryService(
this.eclipse = eclipse;
this.cache = cache;
this.integrityService = integrityService;
this.similarityCheckService = similarityCheckService;
}

@Value("${ovsx.webui.url:}")
Expand Down Expand Up @@ -595,6 +600,21 @@ public ResultJson createNamespace(NamespaceJson json, UserData user) {
throw new ErrorResultException("Namespace already exists: " + namespaceName);
}

// Check if the proposed namespace name is too similar to existing ones (if enabled)
if (similarityCheckService != null && similarityCheckService.isEnabled()) {
var similarNamespaces = similarityCheckService.findSimilarNamespacesForCreation(json.getName(), user);
if (!similarNamespaces.isEmpty()) {
var similarNames = similarNamespaces.stream()
.map(Namespace::getName)
.collect(Collectors.joining(", "));
throw new ErrorResultException(
"Namespace name '" + json.getName() + "' is too similar to existing namespace(s): " + similarNames + ". " +
"Please choose a more distinct name to avoid confusion. " +
"Refer to the publishing guidelines: https://github.com/EclipseFdn/open-vsx.org/wiki/Publishing-Extensions"
);
}
}

// Create the requested namespace
var namespace = new Namespace();
namespace.setName(json.getName());
Expand Down
Loading