Skip to content

Commit

Permalink
Implement new feature Has executable binaries in project (SAP#850)
Browse files Browse the repository at this point in the history
* Implement new feature Has executable binaries in project
Fixes SAP#733
  • Loading branch information
sourabhsparkala committed Aug 31, 2022
1 parent 1d36d4d commit 05c8d1c
Show file tree
Hide file tree
Showing 14 changed files with 398 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.sap.oss.phosphor.fosstars.data.github.GitHubDataFetcher;
import com.sap.oss.phosphor.fosstars.data.github.HasBugBountyProgram;
import com.sap.oss.phosphor.fosstars.data.github.HasCompanySupport;
import com.sap.oss.phosphor.fosstars.data.github.HasExecutableBinaries;
import com.sap.oss.phosphor.fosstars.data.github.HasSecurityPolicy;
import com.sap.oss.phosphor.fosstars.data.github.HasSecurityTeam;
import com.sap.oss.phosphor.fosstars.data.github.InfoAboutVulnerabilities;
Expand Down Expand Up @@ -235,6 +236,7 @@ public DataProviderSelector(GitHubDataFetcher fetcher, NVD nvd) throws IOExcepti
new NumberOfDependentProjectOnGitHub(fetcher),
new VulnerabilitiesFromOwaspDependencyCheck(),
new VulnerabilitiesFromNpmAudit(nvd),
new HasExecutableBinaries(fetcher),
PROJECT_USAGE_PROVIDER,
FUNCTIONALITY_PROVIDER,
HANDLING_UNTRUSTED_DATA_LIKELIHOOD_PROVIDER,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.sap.oss.phosphor.fosstars.data.github;

import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;

import com.google.common.base.Predicate;
import com.sap.oss.phosphor.fosstars.model.Feature;
import com.sap.oss.phosphor.fosstars.model.Value;
import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder;

/**
* The data provider tries to figure out if an open-source project has executable binaries (for
* example, .class, .pyc, .exe)..
*/
public class HasExecutableBinaries extends CachedSingleFeatureGitHubDataProvider<Boolean> {

/**
* List of file extensions deemed as executable binaries.
*/
static final List<String> FILE_EXTENSIONS = Arrays.asList(".crx", ".deb", ".dex", ".dey", ".elf",
".o", ".so", ".iso", ".class", ".jar", ".bundle", ".dylib", ".lib", ".msi", ".acm", ".ax",
".cpl", ".dll", ".drv", ".efi", ".exe", ".mui", ".ocx", ".scr", ".sys", ".tsp", ".pyc",
".pyo", ".par", ".rpm", ".swf", ".torrent", ".cab", ".whl");

/**
* Predicate to confirm if there is a file in open-source project with the executable binary
* extension.
*/
private static final Predicate<Path> FILE_EXTENSIONS_PREDICATE = path -> isExecutableBinary(path);

/**
* Initializes a data provider.
*
* @param fetcher An interface to GitHub.
*/
public HasExecutableBinaries(GitHubDataFetcher fetcher) {
super(fetcher);
}

@Override
protected Feature<Boolean> supportedFeature() {
return HAS_EXECUTABLE_BINARIES;
}

@Override
protected Value<Boolean> fetchValueFor(GitHubProject project) throws IOException {
logger.info("Figuring out if the project has executable binaries...");

LocalRepository localRepository = loadLocalRepository(project);
List<Path> paths = localRepository.files(FILE_EXTENSIONS_PREDICATE);
return HAS_EXECUTABLE_BINARIES.value(!paths.isEmpty());
}

/**
* Fetch the locally cloned repository.
*
* @param project The GitHub project.
* @return {@link LocalRepository} clone repository.
* @throws IOException If something went wrong.
*/
LocalRepository loadLocalRepository(GitHubProject project) throws IOException {
return GitHubDataFetcher.localRepositoryFor(project);
}

/**
* Check if the file represented by the path is a executable binary file.
*
* @param path The file path.
* @return true if the executable binary file type is found, otherwise false.
*/
private static boolean isExecutableBinary(Path path) {
return FILE_EXTENSIONS.stream().anyMatch(ext -> path.getFileName().toString().endsWith(ext));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ private OssFeatures() {
public static final Feature<Boolean> SIGNS_ARTIFACTS
= new BooleanFeature("If a project signs artifacts");

/**
* Shows if an open-source project has executable binaries (for example, .class, .pyc, .exe).
*/
public static final Feature<Boolean> HAS_EXECUTABLE_BINARIES
= new BooleanFeature("If a project has executable binaries");

/**
* Shows if OWASP Dependency Check is used to scan a project. It is either used as a mandatory
* step, optional step or not used at all.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.FUZZED_IN_OSS_FUZZ;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_BUG_BOUNTY_PROGRAM;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_POLICY;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_TEAM;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.OWASP_DEPENDENCY_CHECK_USAGE;
Expand Down Expand Up @@ -34,6 +35,9 @@
* <p>The security awareness score is currently based on the following features.</p>
* <ul>
* <li>
* {@link com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures#HAS_EXECUTABLE_BINARIES}
* </li>
* <li>
* {@link com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures#HAS_SECURITY_POLICY}
* </li>
* <li>
Expand Down Expand Up @@ -130,6 +134,12 @@ public class ProjectSecurityAwarenessScore extends FeatureBasedScore {
*/
private static final double SECURITY_TOOL_POINTS = 1.0;

/**
* A number of points which are subtracted from a score value if a project has executable
* binaries.
*/
private static final double EXECUTABLE_BINARIES_POINTS = 2.0;

/**
* A description of the score.
*/
Expand All @@ -140,9 +150,11 @@ public class ProjectSecurityAwarenessScore extends FeatureBasedScore {
+ "If the project uses verified signed commits, then the score adds %2.2f. "
+ "If the project has a bug bounty program, then the score adds %2.2f. "
+ "If the project signs its artifacts, then the score adds %2.2f. "
+ "If the project uses a security tool or library, then the score adds %2.2f.",
+ "If the project uses a security tool or library, then the score adds %2.2f. "
+ "If the project has executable binaries, then the score subtracts %2.2f.",
SECURITY_POLICY_POINTS, SECURITY_TEAM_POINTS, SIGNED_COMMITS_POINTS,
BUG_BOUNTY_PROGRAM_POINTS, SIGNED_ARTIFACTS_POINTS, SECURITY_TOOL_POINTS);
BUG_BOUNTY_PROGRAM_POINTS, SIGNED_ARTIFACTS_POINTS, SECURITY_TOOL_POINTS,
EXECUTABLE_BINARIES_POINTS);

/**
* Features that tell if a project uses specific security tools.
Expand All @@ -163,7 +175,7 @@ public class ProjectSecurityAwarenessScore extends FeatureBasedScore {
super("How well open-source community is aware about security", DESCRIPTION,
ArrayUtils.addAll(SECURITY_TOOLS_FEATURES,
HAS_SECURITY_POLICY, HAS_SECURITY_TEAM, HAS_BUG_BOUNTY_PROGRAM,
USES_SIGNED_COMMITS, SIGNS_ARTIFACTS));
USES_SIGNED_COMMITS, SIGNS_ARTIFACTS, HAS_EXECUTABLE_BINARIES));
}

@Override
Expand All @@ -173,14 +185,15 @@ public ScoreValue calculate(Value<?>... values) {
Value<Boolean> signedCommits = find(USES_SIGNED_COMMITS, values);
Value<Boolean> hasBugBountyProgram = find(HAS_BUG_BOUNTY_PROGRAM, values);
Value<Boolean> signsArtifacts = find(SIGNS_ARTIFACTS, values);
Value<Boolean> hasExecutableBinaries = find(HAS_EXECUTABLE_BINARIES, values);

List<Value<?>> securityToolsValues = new ArrayList<>();
Arrays.stream(SECURITY_TOOLS_FEATURES)
.forEach(feature -> securityToolsValues.add(find(feature, values)));

List<Value<?>> usedValues = new ArrayList<>();
usedValues.addAll(Arrays.asList(
securityPolicy, securityTeam, signedCommits, hasBugBountyProgram, signsArtifacts));
usedValues.addAll(Arrays.asList(securityPolicy, securityTeam, signedCommits,
hasBugBountyProgram, signsArtifacts, hasExecutableBinaries));
usedValues.addAll(securityToolsValues);

ScoreValue scoreValue = scoreValue(MIN, usedValues);
Expand Down Expand Up @@ -219,8 +232,13 @@ public ScoreValue calculate(Value<?>... values) {
}
});

long n = securityToolsValues.stream()
.filter(ProjectSecurityAwarenessScore::usedSecurityTools)
hasExecutableBinaries.processIfKnown(yes -> {
if (yes) {
scoreValue.decrease(EXECUTABLE_BINARIES_POINTS);
}
});

long n = securityToolsValues.stream().filter(ProjectSecurityAwarenessScore::usedSecurityTools)
.count();
scoreValue.increase(n * SECURITY_TOOL_POINTS);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_ENOUGH_ADMINS_ON_GITHUB;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_ENOUGH_TEAMS_ON_GITHUB;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_ENOUGH_TEAM_MEMBERS_ON_GITHUB;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_LICENSE;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_OPEN_PULL_REQUEST_FROM_DEPENDABOT;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_OPEN_PULL_REQUEST_FROM_SNYK;
Expand Down Expand Up @@ -230,6 +231,7 @@ private static void add(Class<? extends Feature<?>> clazz, String caption) {
add(INTEGRITY_IMPACT, "What is potential integrity impact in case of a security problem?");
add(AVAILABILITY_IMPACT,
"What is potential availability impact in case of a security problem?");
add(HAS_EXECUTABLE_BINARIES, "Does it have executable binaries?");
}

/**
Expand Down
7 changes: 5 additions & 2 deletions src/test/java/com/sap/oss/phosphor/fosstars/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.ARTIFACT_VERSION;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.FUZZED_IN_OSS_FUZZ;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_BUG_BOUNTY_PROGRAM;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_POLICY;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_TEAM;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.IS_APACHE;
Expand Down Expand Up @@ -214,7 +215,8 @@ public static Set<Value<?>> getDefaultValues() {
OWASP_DEPENDENCY_CHECK_USAGE.value(MANDATORY),
OWASP_DEPENDENCY_CHECK_FAIL_CVSS_THRESHOLD.value(7.0),
PACKAGE_MANAGERS.value(PackageManagers.from(MAVEN)),
SECURITY_REVIEWS.value(noReviews()));
SECURITY_REVIEWS.value(noReviews()),
HAS_EXECUTABLE_BINARIES.value(false));
}

/**
Expand Down Expand Up @@ -289,7 +291,8 @@ public static Set<Value<?>> getBestValues() {
OWASP_DEPENDENCY_CHECK_USAGE.value(MANDATORY),
OWASP_DEPENDENCY_CHECK_FAIL_CVSS_THRESHOLD.value(4.0),
PACKAGE_MANAGERS.value(PackageManagers.from(MAVEN)),
SECURITY_REVIEWS.value(new SecurityReviews(new SecurityReview(new Date(), 0.0))));
SECURITY_REVIEWS.value(new SecurityReviews(new SecurityReview(new Date(), 0.0))),
HAS_EXECUTABLE_BINARIES.value(true));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.sap.oss.phosphor.fosstars.data.github.BanditDataProvider;
import com.sap.oss.phosphor.fosstars.data.github.LocalRepository;
import com.sap.oss.phosphor.fosstars.data.github.PackageManagementTest;
import com.sap.oss.phosphor.fosstars.data.github.TestGitHubDataFetcherHolder;
import com.sap.oss.phosphor.fosstars.model.Feature;
import com.sap.oss.phosphor.fosstars.model.Value;
import com.sap.oss.phosphor.fosstars.model.ValueSet;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.sap.oss.phosphor.fosstars.data.github;

import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import com.sap.oss.phosphor.fosstars.model.Value;
import com.sap.oss.phosphor.fosstars.model.ValueSet;
import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject;
import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.lib.Repository;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class HasExecutableBinariesTest extends TestGitHubDataFetcherHolder {

private static Path BASE_DIR;

private static final GitHubProject PROJECT = new GitHubProject("org", "test");

private static LocalRepository LOCAL_REPOSITORY;

@BeforeClass
public static void setup() {
try {
BASE_DIR = Files.createTempDirectory(HasExecutableBinariesTest.class.getName());
Path git = BASE_DIR.resolve(".git");
Files.createDirectory(git);
Path submodule = BASE_DIR.resolve("submodule");
Files.createDirectory(submodule);
Path packageJson = submodule.resolve("pom.xml");
Files.write(packageJson, StringUtils.repeat("x", 500).getBytes());

LocalRepositoryInfo localRepositoryInfo = mock(LocalRepositoryInfo.class);
when(localRepositoryInfo.path()).thenReturn(BASE_DIR);
Repository repository = mock(Repository.class);
when(repository.getDirectory()).thenReturn(git.toFile());

LOCAL_REPOSITORY = new LocalRepository(localRepositoryInfo, repository);
LOCAL_REPOSITORY = spy(LOCAL_REPOSITORY);
when(LOCAL_REPOSITORY.info()).thenReturn(localRepositoryInfo);

TestGitHubDataFetcher.addForTesting(PROJECT, LOCAL_REPOSITORY);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Test
public void testExecutableIsPresent() throws IOException {
Path exe = BASE_DIR.resolve("game.exe");
try {
Files.write(exe, StringUtils.repeat("x", 1000).getBytes());

HasExecutableBinaries provider = new HasExecutableBinaries(fetcher);
provider = spy(provider);
when(provider.loadLocalRepository(PROJECT)).thenReturn(LOCAL_REPOSITORY);

ValueSet values = new ValueHashSet();
provider.update(PROJECT, values);

assertTrue(values.has(HAS_EXECUTABLE_BINARIES));

Optional<Value<Boolean>> something = values.of(HAS_EXECUTABLE_BINARIES);
assertTrue(something.isPresent());

Value<Boolean> value = something.get();
assertTrue(value.get());
} finally {
FileUtils.forceDeleteOnExit(exe.toFile());
}
}

@Test
public void testExecutableIsNotPresent() throws IOException {
Path javaFile = BASE_DIR.resolve("Test.java");
try {
Files.write(javaFile, StringUtils.repeat("x", 1000).getBytes());

HasExecutableBinaries provider = new HasExecutableBinaries(fetcher);
provider = spy(provider);
when(provider.loadLocalRepository(PROJECT)).thenReturn(LOCAL_REPOSITORY);

ValueSet values = new ValueHashSet();
provider.update(PROJECT, values);

assertTrue(values.has(HAS_EXECUTABLE_BINARIES));

Optional<Value<Boolean>> something = values.of(HAS_EXECUTABLE_BINARIES);
assertTrue(something.isPresent());

Value<Boolean> value = something.get();
assertFalse(value.get());
} finally {
FileUtils.forceDeleteOnExit(javaFile.toFile());
}

}

@AfterClass
public static void shutdown() {
try {
FileUtils.forceDeleteOnExit(BASE_DIR.toFile());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.sap.oss.phosphor.fosstars.TestUtils.DELTA;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.FUZZED_IN_OSS_FUZZ;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_BUG_BOUNTY_PROGRAM;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_POLICY;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_TEAM;
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.IS_APACHE;
Expand Down Expand Up @@ -133,7 +134,8 @@ public static Set<Value<?>> defaultValues() {
OWASP_DEPENDENCY_CHECK_USAGE.value(MANDATORY),
OWASP_DEPENDENCY_CHECK_FAIL_CVSS_THRESHOLD.value(7.0),
PACKAGE_MANAGERS.value(PackageManagers.from(MAVEN)),
SECURITY_REVIEWS.value(noReviews()));
SECURITY_REVIEWS.value(noReviews()),
HAS_EXECUTABLE_BINARIES.value(false));
}

private static void checkUsedValues(ScoreValue scoreValue) {
Expand Down
Loading

0 comments on commit 05c8d1c

Please sign in to comment.