Skip to content

Commit 5459b98

Browse files
committed
Add simple artifact scanner for tests only
1 parent 0c8bfea commit 5459b98

File tree

2 files changed

+59
-155
lines changed

2 files changed

+59
-155
lines changed

src/artifact-scanner.test.ts

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from "path";
44

55
import test from "ava";
66

7-
import { scanArtifactsForTokens } from "../.github/workflows/artifact-scanner/artifact-scanner";
7+
import { scanArtifactsForTokens } from "./artifact-scanner";
88
import { getRunnerLogger } from "./logging";
99

1010
test("scanArtifactsForTokens detects GitHub tokens in files", async (t) => {
@@ -19,12 +19,15 @@ test("scanArtifactsForTokens detects GitHub tokens in files", async (t) => {
1919
"This is a test file with token ghp_1234567890123456789012345678901234AB",
2020
);
2121

22-
const result = await scanArtifactsForTokens([testFile], logger);
22+
const error = await t.throwsAsync(
23+
async () => await scanArtifactsForTokens([testFile], logger),
24+
);
2325

24-
t.is(result.scannedFiles, 1);
25-
t.is(result.findings.length, 1);
26-
t.is(result.findings[0].tokenType, "Personal Access Token");
27-
t.is(result.findings[0].filePath, "test.txt");
26+
t.regex(
27+
error?.message || "",
28+
/Found 1 potential GitHub token.*Personal Access Token/,
29+
);
30+
t.regex(error?.message || "", /test\.txt/);
2831
} finally {
2932
// Clean up
3033
fs.rmSync(tempDir, { recursive: true, force: true });
@@ -43,70 +46,11 @@ test("scanArtifactsForTokens handles files without tokens", async (t) => {
4346
"This is a test file without any sensitive data",
4447
);
4548

46-
const result = await scanArtifactsForTokens([testFile], logger);
47-
48-
t.is(result.scannedFiles, 1);
49-
t.is(result.findings.length, 0);
50-
} finally {
51-
// Clean up
52-
fs.rmSync(tempDir, { recursive: true, force: true });
53-
}
54-
});
55-
56-
test("scanArtifactsForTokens skips binary files", async (t) => {
57-
const logger = getRunnerLogger(true);
58-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scanner-test-"));
59-
60-
try {
61-
// Create a binary file (we'll just use a simple zip for this test)
62-
const zipFile = path.join(tempDir, "test.zip");
63-
fs.writeFileSync(zipFile, Buffer.from([0x50, 0x4b, 0x03, 0x04])); // ZIP header
64-
65-
const result = await scanArtifactsForTokens([zipFile], logger);
66-
67-
// The zip file itself should be counted but not scanned for tokens
68-
t.is(result.findings.length, 0);
49+
await t.notThrowsAsync(
50+
async () => await scanArtifactsForTokens([testFile], logger),
51+
);
6952
} finally {
7053
// Clean up
7154
fs.rmSync(tempDir, { recursive: true, force: true });
7255
}
7356
});
74-
75-
test("scanArtifactsForTokens detects tokens in debug artifacts zip", async (t) => {
76-
const logger = getRunnerLogger(true);
77-
const testZipPath = path.join(
78-
__dirname,
79-
"..",
80-
"..",
81-
"..",
82-
"src",
83-
"testdata",
84-
"debug-artifacts-with-fake-token.zip",
85-
);
86-
87-
const result = await scanArtifactsForTokens([testZipPath], logger);
88-
89-
t.true(result.scannedFiles > 0, "Should have scanned files");
90-
t.true(
91-
result.findings.length > 0,
92-
"Should have found tokens in the test zip",
93-
);
94-
95-
// Check that the token types are tracked
96-
const serverToServerFindings = result.findings.filter(
97-
(f) => f.tokenType === "Server-to-Server Token",
98-
);
99-
t.is(
100-
serverToServerFindings.length,
101-
1,
102-
"Should have found exactly 1 Server-to-Server Token",
103-
);
104-
105-
// Check that the path includes the nested structure
106-
const expectedPath =
107-
"debug-artifacts-with-fake-token.zip/debug-artifacts-with-test-token/my-db-java-partial.zip/my-db-java-partial/trap/java/invocations/kotlin.9017231652989744319.trap";
108-
t.true(
109-
result.findings.some((f) => f.filePath === expectedPath),
110-
`Expected to find token at ${expectedPath}, but found: ${result.findings.map((f) => f.filePath).join(", ")}`,
111-
);
112-
});

src/artifact-scanner.ts

Lines changed: 47 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as fs from "fs";
22
import * as os from "os";
33
import * as path from "path";
44

5-
import * as core from "@actions/core";
65
import * as exec from "@actions/exec";
76

87
import { Logger } from "./logging";
@@ -64,31 +63,6 @@ function scanFileForTokens(
6463
): TokenFinding[] {
6564
const findings: TokenFinding[] = [];
6665
try {
67-
// Skip binary files that are unlikely to contain tokens
68-
const ext = path.extname(filePath).toLowerCase();
69-
const binaryExtensions = [
70-
".zip",
71-
".tar",
72-
".gz",
73-
".bz2",
74-
".xz",
75-
".db",
76-
".sqlite",
77-
".bin",
78-
".exe",
79-
".dll",
80-
".so",
81-
".dylib",
82-
".jpg",
83-
".jpeg",
84-
".png",
85-
".gif",
86-
".pdf",
87-
];
88-
if (binaryExtensions.includes(ext)) {
89-
return [];
90-
}
91-
9266
const content = fs.readFileSync(filePath, "utf8");
9367

9468
for (const { name, pattern } of GITHUB_TOKEN_PATTERNS) {
@@ -130,13 +104,9 @@ async function scanZipFile(
130104
): Promise<ScanResult> {
131105
const MAX_DEPTH = 10; // Prevent infinite recursion
132106
if (depth > MAX_DEPTH) {
133-
logger.warning(
107+
throw new Error(
134108
`Maximum zip extraction depth (${MAX_DEPTH}) reached for ${zipPath}`,
135109
);
136-
return {
137-
scannedFiles: 0,
138-
findings: [],
139-
};
140110
}
141111

142112
const result: ScanResult = {
@@ -237,38 +207,32 @@ async function scanDirectory(
237207
findings: [],
238208
};
239209

240-
try {
241-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
210+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
242211

243-
for (const entry of entries) {
244-
const fullPath = path.join(dirPath, entry.name);
245-
const relativePath = path.join(baseRelativePath, entry.name);
212+
for (const entry of entries) {
213+
const fullPath = path.join(dirPath, entry.name);
214+
const relativePath = path.join(baseRelativePath, entry.name);
246215

247-
if (entry.isDirectory()) {
248-
const subResult = await scanDirectory(
249-
fullPath,
250-
relativePath,
251-
logger,
252-
depth,
253-
);
254-
result.scannedFiles += subResult.scannedFiles;
255-
result.findings.push(...subResult.findings);
256-
} else if (entry.isFile()) {
257-
const fileResult = await scanFile(
258-
fullPath,
259-
relativePath,
260-
path.dirname(fullPath),
261-
logger,
262-
depth,
263-
);
264-
result.scannedFiles += fileResult.scannedFiles;
265-
result.findings.push(...fileResult.findings);
266-
}
216+
if (entry.isDirectory()) {
217+
const subResult = await scanDirectory(
218+
fullPath,
219+
relativePath,
220+
logger,
221+
depth,
222+
);
223+
result.scannedFiles += subResult.scannedFiles;
224+
result.findings.push(...subResult.findings);
225+
} else if (entry.isFile()) {
226+
const fileResult = await scanFile(
227+
fullPath,
228+
relativePath,
229+
path.dirname(fullPath),
230+
logger,
231+
depth,
232+
);
233+
result.scannedFiles += fileResult.scannedFiles;
234+
result.findings.push(...fileResult.findings);
267235
}
268-
} catch (e) {
269-
logger.warning(
270-
`Error scanning directory ${dirPath}: ${getErrorMessage(e)}`,
271-
);
272236
}
273237

274238
return result;
@@ -285,8 +249,10 @@ async function scanDirectory(
285249
export async function scanArtifactsForTokens(
286250
filesToScan: string[],
287251
logger: Logger,
288-
): Promise<ScanResult> {
289-
logger.info("Starting security scan for GitHub tokens in debug artifacts...");
252+
): Promise<void> {
253+
logger.info(
254+
"Starting best-effort check for potential GitHub tokens in debug artifacts (for testing purposes only)...",
255+
);
290256

291257
const result: ScanResult = {
292258
scannedFiles: 0,
@@ -298,26 +264,22 @@ export async function scanArtifactsForTokens(
298264

299265
try {
300266
for (const filePath of filesToScan) {
301-
try {
302-
const stats = fs.statSync(filePath);
303-
const fileName = path.basename(filePath);
304-
305-
if (stats.isDirectory()) {
306-
const dirResult = await scanDirectory(filePath, fileName, logger);
307-
result.scannedFiles += dirResult.scannedFiles;
308-
result.findings.push(...dirResult.findings);
309-
} else if (stats.isFile()) {
310-
const fileResult = await scanFile(
311-
filePath,
312-
fileName,
313-
tempScanDir,
314-
logger,
315-
);
316-
result.scannedFiles += fileResult.scannedFiles;
317-
result.findings.push(...fileResult.findings);
318-
}
319-
} catch (e) {
320-
logger.warning(`Error scanning ${filePath}: ${getErrorMessage(e)}`);
267+
const stats = fs.statSync(filePath);
268+
const fileName = path.basename(filePath);
269+
270+
if (stats.isDirectory()) {
271+
const dirResult = await scanDirectory(filePath, fileName, logger);
272+
result.scannedFiles += dirResult.scannedFiles;
273+
result.findings.push(...dirResult.findings);
274+
} else if (stats.isFile()) {
275+
const fileResult = await scanFile(
276+
filePath,
277+
fileName,
278+
tempScanDir,
279+
logger,
280+
);
281+
result.scannedFiles += fileResult.scannedFiles;
282+
result.findings.push(...fileResult.findings);
321283
}
322284
}
323285

@@ -341,12 +303,12 @@ export async function scanArtifactsForTokens(
341303
? `${baseSummary} (${tokenTypesSummary})`
342304
: baseSummary;
343305

344-
logger.info(`Security scan complete: ${summaryWithTypes}`);
306+
logger.info(`Artifact check complete: ${summaryWithTypes}`);
345307

346308
if (result.findings.length > 0) {
347309
const fileList = Array.from(filesWithTokens).join(", ");
348-
core.warning(
349-
`Found ${result.findings.length} potential GitHub token(s) (${tokenTypesSummary}) in debug artifacts at: ${fileList}. This may indicate a security issue. Please review the artifacts before sharing.`,
310+
throw new Error(
311+
`Found ${result.findings.length} potential GitHub token(s) (${tokenTypesSummary}) in debug artifacts at: ${fileList}. This is a best-effort check for testing purposes only.`,
350312
);
351313
}
352314
} finally {
@@ -359,6 +321,4 @@ export async function scanArtifactsForTokens(
359321
);
360322
}
361323
}
362-
363-
return result;
364324
}

0 commit comments

Comments
 (0)