Skip to content

Commit 8b7bbd7

Browse files
Implement new feature Has executable binaries in project (#850)
* Implement new feature Has executable binaries in project Fixes #733
1 parent 7e9792b commit 8b7bbd7

File tree

14 files changed

+398
-16
lines changed

14 files changed

+398
-16
lines changed

src/main/java/com/sap/oss/phosphor/fosstars/data/DataProviderSelector.java

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.sap.oss.phosphor.fosstars.data.github.GitHubDataFetcher;
2828
import com.sap.oss.phosphor.fosstars.data.github.HasBugBountyProgram;
2929
import com.sap.oss.phosphor.fosstars.data.github.HasCompanySupport;
30+
import com.sap.oss.phosphor.fosstars.data.github.HasExecutableBinaries;
3031
import com.sap.oss.phosphor.fosstars.data.github.HasSecurityPolicy;
3132
import com.sap.oss.phosphor.fosstars.data.github.HasSecurityTeam;
3233
import com.sap.oss.phosphor.fosstars.data.github.InfoAboutVulnerabilities;
@@ -233,6 +234,7 @@ public DataProviderSelector(GitHubDataFetcher fetcher, NVD nvd) throws IOExcepti
233234
new NumberOfDependentProjectOnGitHub(fetcher),
234235
new VulnerabilitiesFromOwaspDependencyCheck(),
235236
new VulnerabilitiesFromNpmAudit(nvd),
237+
new HasExecutableBinaries(fetcher),
236238
PROJECT_USAGE_PROVIDER,
237239
FUNCTIONALITY_PROVIDER,
238240
HANDLING_UNTRUSTED_DATA_LIKELIHOOD_PROVIDER,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.sap.oss.phosphor.fosstars.data.github;
2+
3+
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
4+
5+
import com.google.common.base.Predicate;
6+
import com.sap.oss.phosphor.fosstars.model.Feature;
7+
import com.sap.oss.phosphor.fosstars.model.Value;
8+
import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject;
9+
import java.io.IOException;
10+
import java.nio.file.Path;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
import org.kohsuke.github.GitHub;
14+
import org.kohsuke.github.GitHubBuilder;
15+
16+
/**
17+
* The data provider tries to figure out if an open-source project has executable binaries (for
18+
* example, .class, .pyc, .exe)..
19+
*/
20+
public class HasExecutableBinaries extends CachedSingleFeatureGitHubDataProvider<Boolean> {
21+
22+
/**
23+
* List of file extensions deemed as executable binaries.
24+
*/
25+
static final List<String> FILE_EXTENSIONS = Arrays.asList(".crx", ".deb", ".dex", ".dey", ".elf",
26+
".o", ".so", ".iso", ".class", ".jar", ".bundle", ".dylib", ".lib", ".msi", ".acm", ".ax",
27+
".cpl", ".dll", ".drv", ".efi", ".exe", ".mui", ".ocx", ".scr", ".sys", ".tsp", ".pyc",
28+
".pyo", ".par", ".rpm", ".swf", ".torrent", ".cab", ".whl");
29+
30+
/**
31+
* Predicate to confirm if there is a file in open-source project with the executable binary
32+
* extension.
33+
*/
34+
private static final Predicate<Path> FILE_EXTENSIONS_PREDICATE = path -> isExecutableBinary(path);
35+
36+
/**
37+
* Initializes a data provider.
38+
*
39+
* @param fetcher An interface to GitHub.
40+
*/
41+
public HasExecutableBinaries(GitHubDataFetcher fetcher) {
42+
super(fetcher);
43+
}
44+
45+
@Override
46+
protected Feature<Boolean> supportedFeature() {
47+
return HAS_EXECUTABLE_BINARIES;
48+
}
49+
50+
@Override
51+
protected Value<Boolean> fetchValueFor(GitHubProject project) throws IOException {
52+
logger.info("Figuring out if the project has executable binaries...");
53+
54+
LocalRepository localRepository = loadLocalRepository(project);
55+
List<Path> paths = localRepository.files(FILE_EXTENSIONS_PREDICATE);
56+
return HAS_EXECUTABLE_BINARIES.value(!paths.isEmpty());
57+
}
58+
59+
/**
60+
* Fetch the locally cloned repository.
61+
*
62+
* @param project The GitHub project.
63+
* @return {@link LocalRepository} clone repository.
64+
* @throws IOException If something went wrong.
65+
*/
66+
LocalRepository loadLocalRepository(GitHubProject project) throws IOException {
67+
return GitHubDataFetcher.localRepositoryFor(project);
68+
}
69+
70+
/**
71+
* Check if the file represented by the path is a executable binary file.
72+
*
73+
* @param path The file path.
74+
* @return true if the executable binary file type is found, otherwise false.
75+
*/
76+
private static boolean isExecutableBinary(Path path) {
77+
return FILE_EXTENSIONS.stream().anyMatch(ext -> path.getFileName().toString().endsWith(ext));
78+
}
79+
}

src/main/java/com/sap/oss/phosphor/fosstars/model/feature/oss/OssFeatures.java

+6
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@ private OssFeatures() {
282282
public static final Feature<Boolean> SIGNS_ARTIFACTS
283283
= new BooleanFeature("If a project signs artifacts");
284284

285+
/**
286+
* Shows if an open-source project has executable binaries (for example, .class, .pyc, .exe).
287+
*/
288+
public static final Feature<Boolean> HAS_EXECUTABLE_BINARIES
289+
= new BooleanFeature("If a project has executable binaries");
290+
285291
/**
286292
* Shows if OWASP Dependency Check is used to scan a project. It is either used as a mandatory
287293
* step, optional step or not used at all.

src/main/java/com/sap/oss/phosphor/fosstars/model/score/oss/ProjectSecurityAwarenessScore.java

+25-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.FUZZED_IN_OSS_FUZZ;
44
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_BUG_BOUNTY_PROGRAM;
5+
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
56
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_POLICY;
67
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_TEAM;
78
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.OWASP_DEPENDENCY_CHECK_USAGE;
@@ -33,6 +34,9 @@
3334
* <p>The security awareness score is currently based on the following features.</p>
3435
* <ul>
3536
* <li>
37+
* {@link com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures#HAS_EXECUTABLE_BINARIES}
38+
* </li>
39+
* <li>
3640
* {@link com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures#HAS_SECURITY_POLICY}
3741
* </li>
3842
* <li>
@@ -126,6 +130,12 @@ public class ProjectSecurityAwarenessScore extends FeatureBasedScore {
126130
*/
127131
private static final double SECURITY_TOOL_POINTS = 1.0;
128132

133+
/**
134+
* A number of points which are subtracted from a score value if a project has executable
135+
* binaries.
136+
*/
137+
private static final double EXECUTABLE_BINARIES_POINTS = 2.0;
138+
129139
/**
130140
* A description of the score.
131141
*/
@@ -136,9 +146,11 @@ public class ProjectSecurityAwarenessScore extends FeatureBasedScore {
136146
+ "If the project uses verified signed commits, then the score adds %2.2f. "
137147
+ "If the project has a bug bounty program, then the score adds %2.2f. "
138148
+ "If the project signs its artifacts, then the score adds %2.2f. "
139-
+ "If the project uses a security tool or library, then the score adds %2.2f.",
149+
+ "If the project uses a security tool or library, then the score adds %2.2f. "
150+
+ "If the project has executable binaries, then the score subtracts %2.2f.",
140151
SECURITY_POLICY_POINTS, SECURITY_TEAM_POINTS, SIGNED_COMMITS_POINTS,
141-
BUG_BOUNTY_PROGRAM_POINTS, SIGNED_ARTIFACTS_POINTS, SECURITY_TOOL_POINTS);
152+
BUG_BOUNTY_PROGRAM_POINTS, SIGNED_ARTIFACTS_POINTS, SECURITY_TOOL_POINTS,
153+
EXECUTABLE_BINARIES_POINTS);
142154

143155
/**
144156
* Features that tell if a project uses specific security tools.
@@ -159,7 +171,7 @@ public class ProjectSecurityAwarenessScore extends FeatureBasedScore {
159171
super("How well open-source community is aware about security", DESCRIPTION,
160172
ArrayUtils.addAll(SECURITY_TOOLS_FEATURES,
161173
HAS_SECURITY_POLICY, HAS_SECURITY_TEAM, HAS_BUG_BOUNTY_PROGRAM,
162-
USES_SIGNED_COMMITS, SIGNS_ARTIFACTS));
174+
USES_SIGNED_COMMITS, SIGNS_ARTIFACTS, HAS_EXECUTABLE_BINARIES));
163175
}
164176

165177
@Override
@@ -169,14 +181,15 @@ public ScoreValue calculate(Value<?>... values) {
169181
Value<Boolean> signedCommits = find(USES_SIGNED_COMMITS, values);
170182
Value<Boolean> hasBugBountyProgram = find(HAS_BUG_BOUNTY_PROGRAM, values);
171183
Value<Boolean> signsArtifacts = find(SIGNS_ARTIFACTS, values);
184+
Value<Boolean> hasExecutableBinaries = find(HAS_EXECUTABLE_BINARIES, values);
172185

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

177190
List<Value<?>> usedValues = new ArrayList<>();
178-
usedValues.addAll(Arrays.asList(
179-
securityPolicy, securityTeam, signedCommits, hasBugBountyProgram, signsArtifacts));
191+
usedValues.addAll(Arrays.asList(securityPolicy, securityTeam, signedCommits,
192+
hasBugBountyProgram, signsArtifacts, hasExecutableBinaries));
180193
usedValues.addAll(securityToolsValues);
181194

182195
ScoreValue scoreValue = scoreValue(MIN, usedValues);
@@ -215,8 +228,13 @@ public ScoreValue calculate(Value<?>... values) {
215228
}
216229
});
217230

218-
long n = securityToolsValues.stream()
219-
.filter(ProjectSecurityAwarenessScore::usedSecurityTools)
231+
hasExecutableBinaries.processIfKnown(yes -> {
232+
if (yes) {
233+
scoreValue.decrease(EXECUTABLE_BINARIES_POINTS);
234+
}
235+
});
236+
237+
long n = securityToolsValues.stream().filter(ProjectSecurityAwarenessScore::usedSecurityTools)
220238
.count();
221239
scoreValue.increase(n * SECURITY_TOOL_POINTS);
222240

src/main/java/com/sap/oss/phosphor/fosstars/tool/format/CommonFormatter.java

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_ENOUGH_ADMINS_ON_GITHUB;
1111
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_ENOUGH_TEAMS_ON_GITHUB;
1212
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_ENOUGH_TEAM_MEMBERS_ON_GITHUB;
13+
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
1314
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_LICENSE;
1415
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_OPEN_PULL_REQUEST_FROM_DEPENDABOT;
1516
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_README;
@@ -223,6 +224,7 @@ private static void add(Class<? extends Feature<?>> clazz, String caption) {
223224
add(INTEGRITY_IMPACT, "What is potential integrity impact in case of a security problem?");
224225
add(AVAILABILITY_IMPACT,
225226
"What is potential availability impact in case of a security problem?");
227+
add(HAS_EXECUTABLE_BINARIES, "Does it have executable binaries?");
226228
}
227229

228230
/**

src/test/java/com/sap/oss/phosphor/fosstars/TestUtils.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.ARTIFACT_VERSION;
44
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.FUZZED_IN_OSS_FUZZ;
55
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_BUG_BOUNTY_PROGRAM;
6+
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
67
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_POLICY;
78
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_TEAM;
89
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.IS_APACHE;
@@ -212,7 +213,8 @@ public static Set<Value<?>> getDefaultValues() {
212213
OWASP_DEPENDENCY_CHECK_USAGE.value(MANDATORY),
213214
OWASP_DEPENDENCY_CHECK_FAIL_CVSS_THRESHOLD.value(7.0),
214215
PACKAGE_MANAGERS.value(PackageManagers.from(MAVEN)),
215-
SECURITY_REVIEWS.value(noReviews()));
216+
SECURITY_REVIEWS.value(noReviews()),
217+
HAS_EXECUTABLE_BINARIES.value(false));
216218
}
217219

218220
/**
@@ -286,7 +288,8 @@ public static Set<Value<?>> getBestValues() {
286288
OWASP_DEPENDENCY_CHECK_USAGE.value(MANDATORY),
287289
OWASP_DEPENDENCY_CHECK_FAIL_CVSS_THRESHOLD.value(4.0),
288290
PACKAGE_MANAGERS.value(PackageManagers.from(MAVEN)),
289-
SECURITY_REVIEWS.value(new SecurityReviews(new SecurityReview(new Date(), 0.0))));
291+
SECURITY_REVIEWS.value(new SecurityReviews(new SecurityReview(new Date(), 0.0))),
292+
HAS_EXECUTABLE_BINARIES.value(true));
290293
}
291294

292295
/**

src/test/java/com/sap/oss/phosphor/fosstars/data/github/BanditDataProviderTest.java

-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@
1111
import static org.mockito.Mockito.mock;
1212
import static org.mockito.Mockito.when;
1313

14-
import com.sap.oss.phosphor.fosstars.data.github.BanditDataProvider;
15-
import com.sap.oss.phosphor.fosstars.data.github.LocalRepository;
16-
import com.sap.oss.phosphor.fosstars.data.github.PackageManagementTest;
17-
import com.sap.oss.phosphor.fosstars.data.github.TestGitHubDataFetcherHolder;
1814
import com.sap.oss.phosphor.fosstars.model.Feature;
1915
import com.sap.oss.phosphor.fosstars.model.Value;
2016
import com.sap.oss.phosphor.fosstars.model.ValueSet;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.sap.oss.phosphor.fosstars.data.github;
2+
3+
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertTrue;
6+
import static org.mockito.Mockito.mock;
7+
import static org.mockito.Mockito.spy;
8+
import static org.mockito.Mockito.when;
9+
10+
import com.sap.oss.phosphor.fosstars.model.Value;
11+
import com.sap.oss.phosphor.fosstars.model.ValueSet;
12+
import com.sap.oss.phosphor.fosstars.model.subject.oss.GitHubProject;
13+
import com.sap.oss.phosphor.fosstars.model.value.ValueHashSet;
14+
import java.io.IOException;
15+
import java.io.UncheckedIOException;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.util.Optional;
19+
import org.apache.commons.io.FileUtils;
20+
import org.apache.commons.lang3.StringUtils;
21+
import org.eclipse.jgit.lib.Repository;
22+
import org.junit.AfterClass;
23+
import org.junit.BeforeClass;
24+
import org.junit.Test;
25+
26+
public class HasExecutableBinariesTest extends TestGitHubDataFetcherHolder {
27+
28+
private static Path BASE_DIR;
29+
30+
private static final GitHubProject PROJECT = new GitHubProject("org", "test");
31+
32+
private static LocalRepository LOCAL_REPOSITORY;
33+
34+
@BeforeClass
35+
public static void setup() {
36+
try {
37+
BASE_DIR = Files.createTempDirectory(HasExecutableBinariesTest.class.getName());
38+
Path git = BASE_DIR.resolve(".git");
39+
Files.createDirectory(git);
40+
Path submodule = BASE_DIR.resolve("submodule");
41+
Files.createDirectory(submodule);
42+
Path packageJson = submodule.resolve("pom.xml");
43+
Files.write(packageJson, StringUtils.repeat("x", 500).getBytes());
44+
45+
LocalRepositoryInfo localRepositoryInfo = mock(LocalRepositoryInfo.class);
46+
when(localRepositoryInfo.path()).thenReturn(BASE_DIR);
47+
Repository repository = mock(Repository.class);
48+
when(repository.getDirectory()).thenReturn(git.toFile());
49+
50+
LOCAL_REPOSITORY = new LocalRepository(localRepositoryInfo, repository);
51+
LOCAL_REPOSITORY = spy(LOCAL_REPOSITORY);
52+
when(LOCAL_REPOSITORY.info()).thenReturn(localRepositoryInfo);
53+
54+
TestGitHubDataFetcher.addForTesting(PROJECT, LOCAL_REPOSITORY);
55+
} catch (IOException e) {
56+
throw new UncheckedIOException(e);
57+
}
58+
}
59+
60+
@Test
61+
public void testExecutableIsPresent() throws IOException {
62+
Path exe = BASE_DIR.resolve("game.exe");
63+
try {
64+
Files.write(exe, StringUtils.repeat("x", 1000).getBytes());
65+
66+
HasExecutableBinaries provider = new HasExecutableBinaries(fetcher);
67+
provider = spy(provider);
68+
when(provider.loadLocalRepository(PROJECT)).thenReturn(LOCAL_REPOSITORY);
69+
70+
ValueSet values = new ValueHashSet();
71+
provider.update(PROJECT, values);
72+
73+
assertTrue(values.has(HAS_EXECUTABLE_BINARIES));
74+
75+
Optional<Value<Boolean>> something = values.of(HAS_EXECUTABLE_BINARIES);
76+
assertTrue(something.isPresent());
77+
78+
Value<Boolean> value = something.get();
79+
assertTrue(value.get());
80+
} finally {
81+
FileUtils.forceDeleteOnExit(exe.toFile());
82+
}
83+
}
84+
85+
@Test
86+
public void testExecutableIsNotPresent() throws IOException {
87+
Path javaFile = BASE_DIR.resolve("Test.java");
88+
try {
89+
Files.write(javaFile, StringUtils.repeat("x", 1000).getBytes());
90+
91+
HasExecutableBinaries provider = new HasExecutableBinaries(fetcher);
92+
provider = spy(provider);
93+
when(provider.loadLocalRepository(PROJECT)).thenReturn(LOCAL_REPOSITORY);
94+
95+
ValueSet values = new ValueHashSet();
96+
provider.update(PROJECT, values);
97+
98+
assertTrue(values.has(HAS_EXECUTABLE_BINARIES));
99+
100+
Optional<Value<Boolean>> something = values.of(HAS_EXECUTABLE_BINARIES);
101+
assertTrue(something.isPresent());
102+
103+
Value<Boolean> value = something.get();
104+
assertFalse(value.get());
105+
} finally {
106+
FileUtils.forceDeleteOnExit(javaFile.toFile());
107+
}
108+
109+
}
110+
111+
@AfterClass
112+
public static void shutdown() {
113+
try {
114+
FileUtils.forceDeleteOnExit(BASE_DIR.toFile());
115+
} catch (IOException e) {
116+
throw new UncheckedIOException(e);
117+
}
118+
}
119+
}

src/test/java/com/sap/oss/phosphor/fosstars/model/score/oss/OssSecurityScoreTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static com.sap.oss.phosphor.fosstars.TestUtils.DELTA;
44
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.FUZZED_IN_OSS_FUZZ;
55
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_BUG_BOUNTY_PROGRAM;
6+
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_EXECUTABLE_BINARIES;
67
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_POLICY;
78
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.HAS_SECURITY_TEAM;
89
import static com.sap.oss.phosphor.fosstars.model.feature.oss.OssFeatures.IS_APACHE;
@@ -131,7 +132,8 @@ public static Set<Value<?>> defaultValues() {
131132
OWASP_DEPENDENCY_CHECK_USAGE.value(MANDATORY),
132133
OWASP_DEPENDENCY_CHECK_FAIL_CVSS_THRESHOLD.value(7.0),
133134
PACKAGE_MANAGERS.value(PackageManagers.from(MAVEN)),
134-
SECURITY_REVIEWS.value(noReviews()));
135+
SECURITY_REVIEWS.value(noReviews()),
136+
HAS_EXECUTABLE_BINARIES.value(false));
135137
}
136138

137139
private static void checkUsedValues(ScoreValue scoreValue) {

0 commit comments

Comments
 (0)