@shipsafe/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/bin/shipsafe.d.ts +3 -0
- package/dist/bin/shipsafe.d.ts.map +1 -0
- package/dist/bin/shipsafe.js +33 -0
- package/dist/bin/shipsafe.js.map +1 -0
- package/dist/src/autofix/pr-generator.d.ts +48 -0
- package/dist/src/autofix/pr-generator.d.ts.map +1 -0
- package/dist/src/autofix/pr-generator.js +359 -0
- package/dist/src/autofix/pr-generator.js.map +1 -0
- package/dist/src/autofix/scaffolding.d.ts +26 -0
- package/dist/src/autofix/scaffolding.d.ts.map +1 -0
- package/dist/src/autofix/scaffolding.js +249 -0
- package/dist/src/autofix/scaffolding.js.map +1 -0
- package/dist/src/autofix/secret-fixer.d.ts +27 -0
- package/dist/src/autofix/secret-fixer.d.ts.map +1 -0
- package/dist/src/autofix/secret-fixer.js +138 -0
- package/dist/src/autofix/secret-fixer.js.map +1 -0
- package/dist/src/claude-md/manager.d.ts +17 -0
- package/dist/src/claude-md/manager.d.ts.map +1 -0
- package/dist/src/claude-md/manager.js +143 -0
- package/dist/src/claude-md/manager.js.map +1 -0
- package/dist/src/cli/activate.d.ts +4 -0
- package/dist/src/cli/activate.d.ts.map +1 -0
- package/dist/src/cli/activate.js +53 -0
- package/dist/src/cli/activate.js.map +1 -0
- package/dist/src/cli/config.d.ts +21 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +128 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/connect.d.ts +36 -0
- package/dist/src/cli/connect.d.ts.map +1 -0
- package/dist/src/cli/connect.js +107 -0
- package/dist/src/cli/connect.js.map +1 -0
- package/dist/src/cli/init.d.ts +12 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +45 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/license-check.d.ts +7 -0
- package/dist/src/cli/license-check.d.ts.map +1 -0
- package/dist/src/cli/license-check.js +69 -0
- package/dist/src/cli/license-check.js.map +1 -0
- package/dist/src/cli/license-gate.d.ts +9 -0
- package/dist/src/cli/license-gate.d.ts.map +1 -0
- package/dist/src/cli/license-gate.js +25 -0
- package/dist/src/cli/license-gate.js.map +1 -0
- package/dist/src/cli/scan.d.ts +9 -0
- package/dist/src/cli/scan.d.ts.map +1 -0
- package/dist/src/cli/scan.js +75 -0
- package/dist/src/cli/scan.js.map +1 -0
- package/dist/src/cli/setup.d.ts +27 -0
- package/dist/src/cli/setup.d.ts.map +1 -0
- package/dist/src/cli/setup.js +134 -0
- package/dist/src/cli/setup.js.map +1 -0
- package/dist/src/cli/status.d.ts +4 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +52 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/cli/upload-sourcemaps.d.ts +13 -0
- package/dist/src/cli/upload-sourcemaps.d.ts.map +1 -0
- package/dist/src/cli/upload-sourcemaps.js +157 -0
- package/dist/src/cli/upload-sourcemaps.js.map +1 -0
- package/dist/src/config/manager.d.ts +37 -0
- package/dist/src/config/manager.d.ts.map +1 -0
- package/dist/src/config/manager.js +131 -0
- package/dist/src/config/manager.js.map +1 -0
- package/dist/src/constants.d.ts +28 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +34 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/engines/graph/data-flow.d.ts +36 -0
- package/dist/src/engines/graph/data-flow.d.ts.map +1 -0
- package/dist/src/engines/graph/data-flow.js +189 -0
- package/dist/src/engines/graph/data-flow.js.map +1 -0
- package/dist/src/engines/graph/index.d.ts +20 -0
- package/dist/src/engines/graph/index.d.ts.map +1 -0
- package/dist/src/engines/graph/index.js +100 -0
- package/dist/src/engines/graph/index.js.map +1 -0
- package/dist/src/engines/graph/parser.d.ts +13 -0
- package/dist/src/engines/graph/parser.d.ts.map +1 -0
- package/dist/src/engines/graph/parser.js +620 -0
- package/dist/src/engines/graph/parser.js.map +1 -0
- package/dist/src/engines/graph/queries.d.ts +11 -0
- package/dist/src/engines/graph/queries.d.ts.map +1 -0
- package/dist/src/engines/graph/queries.js +196 -0
- package/dist/src/engines/graph/queries.js.map +1 -0
- package/dist/src/engines/graph/store.d.ts +35 -0
- package/dist/src/engines/graph/store.d.ts.map +1 -0
- package/dist/src/engines/graph/store.js +284 -0
- package/dist/src/engines/graph/store.js.map +1 -0
- package/dist/src/engines/pattern/gitleaks.d.ts +4 -0
- package/dist/src/engines/pattern/gitleaks.d.ts.map +1 -0
- package/dist/src/engines/pattern/gitleaks.js +78 -0
- package/dist/src/engines/pattern/gitleaks.js.map +1 -0
- package/dist/src/engines/pattern/index.d.ts +11 -0
- package/dist/src/engines/pattern/index.d.ts.map +1 -0
- package/dist/src/engines/pattern/index.js +111 -0
- package/dist/src/engines/pattern/index.js.map +1 -0
- package/dist/src/engines/pattern/semgrep.d.ts +4 -0
- package/dist/src/engines/pattern/semgrep.d.ts.map +1 -0
- package/dist/src/engines/pattern/semgrep.js +83 -0
- package/dist/src/engines/pattern/semgrep.js.map +1 -0
- package/dist/src/engines/pattern/trivy.d.ts +4 -0
- package/dist/src/engines/pattern/trivy.d.ts.map +1 -0
- package/dist/src/engines/pattern/trivy.js +90 -0
- package/dist/src/engines/pattern/trivy.js.map +1 -0
- package/dist/src/github/api.d.ts +19 -0
- package/dist/src/github/api.d.ts.map +1 -0
- package/dist/src/github/api.js +75 -0
- package/dist/src/github/api.js.map +1 -0
- package/dist/src/github/app-manifest.d.ts +28 -0
- package/dist/src/github/app-manifest.d.ts.map +1 -0
- package/dist/src/github/app-manifest.js +27 -0
- package/dist/src/github/app-manifest.js.map +1 -0
- package/dist/src/github/checks.d.ts +36 -0
- package/dist/src/github/checks.d.ts.map +1 -0
- package/dist/src/github/checks.js +90 -0
- package/dist/src/github/checks.js.map +1 -0
- package/dist/src/github/scanner.d.ts +20 -0
- package/dist/src/github/scanner.d.ts.map +1 -0
- package/dist/src/github/scanner.js +78 -0
- package/dist/src/github/scanner.js.map +1 -0
- package/dist/src/github/webhook.d.ts +39 -0
- package/dist/src/github/webhook.d.ts.map +1 -0
- package/dist/src/github/webhook.js +80 -0
- package/dist/src/github/webhook.js.map +1 -0
- package/dist/src/hooks/installer.d.ts +4 -0
- package/dist/src/hooks/installer.d.ts.map +1 -0
- package/dist/src/hooks/installer.js +146 -0
- package/dist/src/hooks/installer.js.map +1 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +96 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/check-package.d.ts +30 -0
- package/dist/src/mcp/tools/check-package.d.ts.map +1 -0
- package/dist/src/mcp/tools/check-package.js +196 -0
- package/dist/src/mcp/tools/check-package.js.map +1 -0
- package/dist/src/mcp/tools/fix.d.ts +41 -0
- package/dist/src/mcp/tools/fix.d.ts.map +1 -0
- package/dist/src/mcp/tools/fix.js +98 -0
- package/dist/src/mcp/tools/fix.js.map +1 -0
- package/dist/src/mcp/tools/graph-query.d.ts +7 -0
- package/dist/src/mcp/tools/graph-query.d.ts.map +1 -0
- package/dist/src/mcp/tools/graph-query.js +139 -0
- package/dist/src/mcp/tools/graph-query.js.map +1 -0
- package/dist/src/mcp/tools/production-errors.d.ts +23 -0
- package/dist/src/mcp/tools/production-errors.d.ts.map +1 -0
- package/dist/src/mcp/tools/production-errors.js +46 -0
- package/dist/src/mcp/tools/production-errors.js.map +1 -0
- package/dist/src/mcp/tools/scan.d.ts +7 -0
- package/dist/src/mcp/tools/scan.d.ts.map +1 -0
- package/dist/src/mcp/tools/scan.js +9 -0
- package/dist/src/mcp/tools/scan.js.map +1 -0
- package/dist/src/mcp/tools/status.d.ts +9 -0
- package/dist/src/mcp/tools/status.d.ts.map +1 -0
- package/dist/src/mcp/tools/status.js +18 -0
- package/dist/src/mcp/tools/status.js.map +1 -0
- package/dist/src/mcp/tools/verify-resolution.d.ts +12 -0
- package/dist/src/mcp/tools/verify-resolution.d.ts.map +1 -0
- package/dist/src/mcp/tools/verify-resolution.js +45 -0
- package/dist/src/mcp/tools/verify-resolution.js.map +1 -0
- package/dist/src/types.d.ts +136 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/engines/pattern/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAOzG,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,aAAa,CAW/D;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAQzE;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgB1E;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgFzF"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { SEVERITY_ORDER } from '../../constants.js';
|
|
3
|
+
import { checkSemgrepInstalled, runSemgrep } from './semgrep.js';
|
|
4
|
+
import { checkGitleaksInstalled, runGitleaks } from './gitleaks.js';
|
|
5
|
+
import { checkTrivyInstalled, runTrivy } from './trivy.js';
|
|
6
|
+
import { runGraphEngine, isGraphEngineAvailable } from '../graph/index.js';
|
|
7
|
+
export function computeScore(findings) {
|
|
8
|
+
if (findings.length === 0)
|
|
9
|
+
return 'A';
|
|
10
|
+
const severities = new Set(findings.map((f) => f.severity));
|
|
11
|
+
if (severities.has('critical'))
|
|
12
|
+
return 'F';
|
|
13
|
+
if (severities.has('high'))
|
|
14
|
+
return 'D';
|
|
15
|
+
if (severities.has('medium'))
|
|
16
|
+
return 'C';
|
|
17
|
+
// Only info and/or low remain
|
|
18
|
+
return 'B';
|
|
19
|
+
}
|
|
20
|
+
export async function getAvailableScanners() {
|
|
21
|
+
const [semgrep, gitleaks, trivy] = await Promise.all([
|
|
22
|
+
checkSemgrepInstalled(),
|
|
23
|
+
checkGitleaksInstalled(),
|
|
24
|
+
checkTrivyInstalled(),
|
|
25
|
+
]);
|
|
26
|
+
return { semgrep, gitleaks, trivy };
|
|
27
|
+
}
|
|
28
|
+
export async function getStagedFiles(projectDir) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
execFile('git', ['diff', '--cached', '--name-only'], { cwd: projectDir }, (error, stdout) => {
|
|
31
|
+
if (error) {
|
|
32
|
+
resolve([]);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const files = (typeof stdout === 'string' ? stdout : '')
|
|
36
|
+
.split('\n')
|
|
37
|
+
.map((line) => line.trim())
|
|
38
|
+
.filter((line) => line.length > 0);
|
|
39
|
+
resolve(files);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export async function runPatternEngine(options) {
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
const { targetPath, scope, stagedFiles: providedStagedFiles } = options;
|
|
46
|
+
// 1. Check which scanners are installed
|
|
47
|
+
const availability = await getAvailableScanners();
|
|
48
|
+
// 2. If scope is 'staged', get staged files
|
|
49
|
+
let stagedFiles = providedStagedFiles;
|
|
50
|
+
if (scope === 'staged' && !stagedFiles) {
|
|
51
|
+
stagedFiles = await getStagedFiles(targetPath);
|
|
52
|
+
}
|
|
53
|
+
// 3. If scope is 'staged' and no staged files, return clean result immediately
|
|
54
|
+
if (scope === 'staged' && (!stagedFiles || stagedFiles.length === 0)) {
|
|
55
|
+
return {
|
|
56
|
+
status: 'pass',
|
|
57
|
+
score: 'A',
|
|
58
|
+
findings: [],
|
|
59
|
+
scan_duration_ms: Date.now() - startTime,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// 4. Run all available scanners in parallel with Promise.allSettled()
|
|
63
|
+
const scannerPromises = [];
|
|
64
|
+
if (availability.semgrep) {
|
|
65
|
+
scannerPromises.push(runSemgrep(targetPath, stagedFiles));
|
|
66
|
+
}
|
|
67
|
+
if (availability.gitleaks) {
|
|
68
|
+
scannerPromises.push(runGitleaks(targetPath, stagedFiles));
|
|
69
|
+
}
|
|
70
|
+
if (availability.trivy) {
|
|
71
|
+
scannerPromises.push(runTrivy(targetPath));
|
|
72
|
+
}
|
|
73
|
+
const results = await Promise.allSettled(scannerPromises);
|
|
74
|
+
// 5. Merge all findings into single array
|
|
75
|
+
const findings = [];
|
|
76
|
+
for (const result of results) {
|
|
77
|
+
if (result.status === 'fulfilled') {
|
|
78
|
+
findings.push(...result.value);
|
|
79
|
+
}
|
|
80
|
+
// Rejected promises are silently skipped (scanner failure is non-fatal)
|
|
81
|
+
}
|
|
82
|
+
// 5b. Run graph engine (optional — failures are non-fatal)
|
|
83
|
+
if (isGraphEngineAvailable()) {
|
|
84
|
+
try {
|
|
85
|
+
const graphResult = await runGraphEngine({ targetPath, scope });
|
|
86
|
+
findings.push(...graphResult.findings);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Graph engine failure is non-fatal — pattern results are still returned
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 6. Sort findings by severity (critical first)
|
|
93
|
+
findings.sort((a, b) => {
|
|
94
|
+
const orderA = SEVERITY_ORDER[a.severity] ?? 999;
|
|
95
|
+
const orderB = SEVERITY_ORDER[b.severity] ?? 999;
|
|
96
|
+
return orderA - orderB;
|
|
97
|
+
});
|
|
98
|
+
// 7. Compute score
|
|
99
|
+
const score = computeScore(findings);
|
|
100
|
+
// 8. Determine status
|
|
101
|
+
const hasCriticalOrHigh = findings.some((f) => f.severity === 'critical' || f.severity === 'high');
|
|
102
|
+
const status = hasCriticalOrHigh ? 'fail' : 'pass';
|
|
103
|
+
// 9. Return ScanResult with timing info
|
|
104
|
+
return {
|
|
105
|
+
status,
|
|
106
|
+
score,
|
|
107
|
+
findings,
|
|
108
|
+
scan_duration_ms: Date.now() - startTime,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/engines/pattern/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep.d.ts","sourceRoot":"","sources":["../../../../src/engines/pattern/semgrep.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,gBAAgB,CAAC;AAsExD,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9D;AAED,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CAgCpB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
function execFilePromise(cmd, args) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
execFile(cmd, args, (error, stdout, stderr) => {
|
|
5
|
+
if (error) {
|
|
6
|
+
// Attach stdout/stderr to the error so callers can still read output
|
|
7
|
+
const enrichedError = error;
|
|
8
|
+
enrichedError.stdout = typeof stdout === 'string' ? stdout : '';
|
|
9
|
+
enrichedError.stderr = typeof stderr === 'string' ? stderr : '';
|
|
10
|
+
reject(enrichedError);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
resolve({
|
|
14
|
+
stdout: typeof stdout === 'string' ? stdout : '',
|
|
15
|
+
stderr: typeof stderr === 'string' ? stderr : '',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const SEVERITY_MAP = {
|
|
21
|
+
ERROR: 'critical',
|
|
22
|
+
WARNING: 'high',
|
|
23
|
+
INFO: 'medium',
|
|
24
|
+
};
|
|
25
|
+
function mapSeverity(semgrepSeverity) {
|
|
26
|
+
return SEVERITY_MAP[semgrepSeverity] ?? 'low';
|
|
27
|
+
}
|
|
28
|
+
function parseSemgrepOutput(jsonString) {
|
|
29
|
+
const output = JSON.parse(jsonString);
|
|
30
|
+
return output.results.map((result) => ({
|
|
31
|
+
id: `semgrep_${result.check_id}_${result.start.line}`,
|
|
32
|
+
engine: 'pattern',
|
|
33
|
+
severity: mapSeverity(result.extra.severity),
|
|
34
|
+
type: result.check_id,
|
|
35
|
+
file: result.path,
|
|
36
|
+
line: result.start.line,
|
|
37
|
+
description: result.extra.message,
|
|
38
|
+
fix_suggestion: result.extra.fix ?? '',
|
|
39
|
+
auto_fixable: result.extra.fix != null,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
export async function checkSemgrepInstalled() {
|
|
43
|
+
try {
|
|
44
|
+
await execFilePromise('which', ['semgrep']);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function runSemgrep(targetPath, stagedFiles) {
|
|
52
|
+
const installed = await checkSemgrepInstalled();
|
|
53
|
+
if (!installed) {
|
|
54
|
+
console.warn('ShipSafe: semgrep is not installed, skipping pattern scan');
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const args = ['scan', '--json', '--quiet'];
|
|
59
|
+
if (stagedFiles && stagedFiles.length > 0) {
|
|
60
|
+
args.push(...stagedFiles);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
args.push(targetPath);
|
|
64
|
+
}
|
|
65
|
+
const { stdout } = await execFilePromise('semgrep', args);
|
|
66
|
+
return parseSemgrepOutput(stdout);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// If semgrep exits non-zero but produced output, still try to parse it
|
|
70
|
+
const execError = error;
|
|
71
|
+
if (execError.stdout) {
|
|
72
|
+
try {
|
|
73
|
+
return parseSemgrepOutput(execError.stdout);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// JSON parse failed on the output — fall through to warn
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.warn('ShipSafe: semgrep scan failed', execError.message);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=semgrep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep.js","sourceRoot":"","sources":["../../../../src/engines/pattern/semgrep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trivy.d.ts","sourceRoot":"","sources":["../../../../src/engines/pattern/trivy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,gBAAgB,CAAC;AA2FxD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO5D;AAED,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CA0BrE"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
function execFilePromise(cmd, args) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
execFile(cmd, args, (error, stdout, stderr) => {
|
|
5
|
+
if (error) {
|
|
6
|
+
// Attach stdout/stderr to the error so callers can still read output
|
|
7
|
+
const enrichedError = error;
|
|
8
|
+
enrichedError.stdout = typeof stdout === 'string' ? stdout : '';
|
|
9
|
+
enrichedError.stderr = typeof stderr === 'string' ? stderr : '';
|
|
10
|
+
reject(enrichedError);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
resolve({
|
|
14
|
+
stdout: typeof stdout === 'string' ? stdout : '',
|
|
15
|
+
stderr: typeof stderr === 'string' ? stderr : '',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const SEVERITY_MAP = {
|
|
21
|
+
CRITICAL: 'critical',
|
|
22
|
+
HIGH: 'high',
|
|
23
|
+
MEDIUM: 'medium',
|
|
24
|
+
LOW: 'low',
|
|
25
|
+
};
|
|
26
|
+
function mapSeverity(trivySeverity) {
|
|
27
|
+
return SEVERITY_MAP[trivySeverity] ?? 'low';
|
|
28
|
+
}
|
|
29
|
+
function parseTrivyOutput(jsonString) {
|
|
30
|
+
const output = JSON.parse(jsonString);
|
|
31
|
+
const findings = [];
|
|
32
|
+
for (const result of output.Results) {
|
|
33
|
+
if (!result.Vulnerabilities) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
for (const vuln of result.Vulnerabilities) {
|
|
37
|
+
const hasFixedVersion = vuln.FixedVersion !== '' && vuln.FixedVersion != null;
|
|
38
|
+
findings.push({
|
|
39
|
+
id: `trivy_${vuln.VulnerabilityID}_${vuln.PkgName}`,
|
|
40
|
+
engine: 'pattern',
|
|
41
|
+
severity: mapSeverity(vuln.Severity),
|
|
42
|
+
type: 'dependency_vulnerability',
|
|
43
|
+
file: result.Target,
|
|
44
|
+
line: 0,
|
|
45
|
+
description: `${vuln.VulnerabilityID}: ${vuln.Title} (${vuln.PkgName}@${vuln.InstalledVersion})`,
|
|
46
|
+
fix_suggestion: hasFixedVersion
|
|
47
|
+
? `Upgrade ${vuln.PkgName} to ${vuln.FixedVersion}`
|
|
48
|
+
: 'No fix available yet',
|
|
49
|
+
auto_fixable: hasFixedVersion,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return findings;
|
|
54
|
+
}
|
|
55
|
+
export async function checkTrivyInstalled() {
|
|
56
|
+
try {
|
|
57
|
+
await execFilePromise('which', ['trivy']);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function runTrivy(targetPath) {
|
|
65
|
+
const installed = await checkTrivyInstalled();
|
|
66
|
+
if (!installed) {
|
|
67
|
+
console.warn('ShipSafe: trivy is not installed, skipping dependency vulnerability scan');
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const args = ['fs', '--format', 'json', '--quiet', targetPath];
|
|
72
|
+
const { stdout } = await execFilePromise('trivy', args);
|
|
73
|
+
return parseTrivyOutput(stdout);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// If trivy exits non-zero but produced output, still try to parse it
|
|
77
|
+
const execError = error;
|
|
78
|
+
if (execError.stdout) {
|
|
79
|
+
try {
|
|
80
|
+
return parseTrivyOutput(execError.stdout);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// JSON parse failed on the output — fall through to warn
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
console.warn('ShipSafe: trivy scan failed', execError.message);
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=trivy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trivy.js","sourceRoot":"","sources":["../../../../src/engines/pattern/trivy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a JWT for GitHub App authentication.
|
|
3
|
+
* Uses RS256 signing with the App's private key.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateJwt(appId: string, privateKey: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get an installation access token for GitHub API calls.
|
|
8
|
+
* Requires SHIPSAFE_GITHUB_APP_ID and SHIPSAFE_GITHUB_PRIVATE_KEY env vars.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getInstallationToken(installationId: number): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Make an authenticated GitHub API request.
|
|
13
|
+
*/
|
|
14
|
+
export declare function githubApi(path: string, options: {
|
|
15
|
+
method?: string;
|
|
16
|
+
body?: unknown;
|
|
17
|
+
token: string;
|
|
18
|
+
}): Promise<unknown>;
|
|
19
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/github/api.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAiBrE;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BlF;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,GACA,OAAO,CAAC,OAAO,CAAC,CAgClB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createSign } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a JWT for GitHub App authentication.
|
|
4
|
+
* Uses RS256 signing with the App's private key.
|
|
5
|
+
*/
|
|
6
|
+
export function generateJwt(appId, privateKey) {
|
|
7
|
+
const now = Math.floor(Date.now() / 1000);
|
|
8
|
+
const header = Buffer.from(JSON.stringify({ alg: 'RS256', typ: 'JWT' })).toString('base64url');
|
|
9
|
+
const payload = Buffer.from(JSON.stringify({
|
|
10
|
+
iat: now - 60, // issued 60s ago to account for clock drift
|
|
11
|
+
exp: now + 600, // expires in 10 minutes
|
|
12
|
+
iss: appId,
|
|
13
|
+
})).toString('base64url');
|
|
14
|
+
const unsigned = `${header}.${payload}`;
|
|
15
|
+
const signer = createSign('RSA-SHA256');
|
|
16
|
+
signer.update(unsigned);
|
|
17
|
+
const signature = signer.sign(privateKey, 'base64url');
|
|
18
|
+
return `${unsigned}.${signature}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get an installation access token for GitHub API calls.
|
|
22
|
+
* Requires SHIPSAFE_GITHUB_APP_ID and SHIPSAFE_GITHUB_PRIVATE_KEY env vars.
|
|
23
|
+
*/
|
|
24
|
+
export async function getInstallationToken(installationId) {
|
|
25
|
+
const appId = process.env.SHIPSAFE_GITHUB_APP_ID;
|
|
26
|
+
const privateKey = process.env.SHIPSAFE_GITHUB_PRIVATE_KEY;
|
|
27
|
+
if (!appId || !privateKey) {
|
|
28
|
+
throw new Error('Missing SHIPSAFE_GITHUB_APP_ID or SHIPSAFE_GITHUB_PRIVATE_KEY environment variables');
|
|
29
|
+
}
|
|
30
|
+
const jwt = generateJwt(appId, privateKey);
|
|
31
|
+
const response = await fetch(`https://api.github.com/app/installations/${installationId}/access_tokens`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${jwt}`,
|
|
35
|
+
Accept: 'application/vnd.github+json',
|
|
36
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const body = await response.text();
|
|
41
|
+
throw new Error(`Failed to get installation token: ${response.status} ${body}`);
|
|
42
|
+
}
|
|
43
|
+
const data = (await response.json());
|
|
44
|
+
return data.token;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Make an authenticated GitHub API request.
|
|
48
|
+
*/
|
|
49
|
+
export async function githubApi(path, options) {
|
|
50
|
+
const url = path.startsWith('https://') ? path : `https://api.github.com${path}`;
|
|
51
|
+
const headers = {
|
|
52
|
+
Authorization: `token ${options.token}`,
|
|
53
|
+
Accept: 'application/vnd.github+json',
|
|
54
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
55
|
+
};
|
|
56
|
+
const fetchOptions = {
|
|
57
|
+
method: options.method ?? 'GET',
|
|
58
|
+
headers,
|
|
59
|
+
};
|
|
60
|
+
if (options.body !== undefined) {
|
|
61
|
+
headers['Content-Type'] = 'application/json';
|
|
62
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
63
|
+
}
|
|
64
|
+
const response = await fetch(url, fetchOptions);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const body = await response.text();
|
|
67
|
+
throw new Error(`GitHub API error: ${response.status} ${body}`);
|
|
68
|
+
}
|
|
69
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
70
|
+
if (contentType.includes('application/json')) {
|
|
71
|
+
return response.json();
|
|
72
|
+
}
|
|
73
|
+
return response.text();
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/github/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,UAAkB;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/F,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CACzB,IAAI,CAAC,SAAS,CAAC;QACb,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,4CAA4C;QAC3D,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,wBAAwB;QACxC,GAAG,EAAE,KAAK;KACX,CAAC,CACH,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAExB,MAAM,QAAQ,GAAG,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC;IACxC,MAAM,MAAM,GAAG,UAAU,CAAC,YAAY,CAAC,CAAC;IACxC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxB,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAEvD,OAAO,GAAG,QAAQ,IAAI,SAAS,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,cAAsB;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IACjD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;IAE3D,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,qFAAqF,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAE3C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,4CAA4C,cAAc,gBAAgB,EAC1E;QACE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,GAAG,EAAE;YAC9B,MAAM,EAAE,6BAA6B;YACrC,sBAAsB,EAAE,YAAY;SACrC;KACF,CACF,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,qCAAqC,QAAQ,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAsB,CAAC;IAC1D,OAAO,IAAI,CAAC,KAAK,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAY,EACZ,OAIC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,yBAAyB,IAAI,EAAE,CAAC;IAEjF,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,SAAS,OAAO,CAAC,KAAK,EAAE;QACvC,MAAM,EAAE,6BAA6B;QACrC,sBAAsB,EAAE,YAAY;KACrC,CAAC;IAEF,MAAM,YAAY,GAAgB;QAChC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,OAAO;KACR,CAAC;IAEF,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC7C,YAAY,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAEhD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC/D,IAAI,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC7C,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub App manifest for ShipSafe registration.
|
|
3
|
+
*
|
|
4
|
+
* This manifest defines the permissions and events the ShipSafe GitHub App
|
|
5
|
+
* requires. The actual App registration is done manually on GitHub; this
|
|
6
|
+
* manifest serves as the canonical source of truth for the configuration.
|
|
7
|
+
*
|
|
8
|
+
* See: https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest
|
|
9
|
+
*/
|
|
10
|
+
export declare const APP_MANIFEST: {
|
|
11
|
+
name: string;
|
|
12
|
+
url: string;
|
|
13
|
+
hook_attributes: {
|
|
14
|
+
url: string;
|
|
15
|
+
};
|
|
16
|
+
redirect_url: string;
|
|
17
|
+
setup_url: string;
|
|
18
|
+
public: boolean;
|
|
19
|
+
default_permissions: {
|
|
20
|
+
checks: "write";
|
|
21
|
+
contents: "read";
|
|
22
|
+
pull_requests: "write";
|
|
23
|
+
statuses: "write";
|
|
24
|
+
};
|
|
25
|
+
default_events: readonly ["pull_request", "push"];
|
|
26
|
+
};
|
|
27
|
+
export type AppManifest = typeof APP_MANIFEST;
|
|
28
|
+
//# sourceMappingURL=app-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-manifest.d.ts","sourceRoot":"","sources":["../../../src/github/app-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;CAgBxB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,OAAO,YAAY,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub App manifest for ShipSafe registration.
|
|
3
|
+
*
|
|
4
|
+
* This manifest defines the permissions and events the ShipSafe GitHub App
|
|
5
|
+
* requires. The actual App registration is done manually on GitHub; this
|
|
6
|
+
* manifest serves as the canonical source of truth for the configuration.
|
|
7
|
+
*
|
|
8
|
+
* See: https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest
|
|
9
|
+
*/
|
|
10
|
+
export const APP_MANIFEST = {
|
|
11
|
+
name: 'ShipSafe',
|
|
12
|
+
url: 'https://shipsafe.org',
|
|
13
|
+
hook_attributes: {
|
|
14
|
+
url: '', // filled in during setup with the webhook endpoint URL
|
|
15
|
+
},
|
|
16
|
+
redirect_url: '',
|
|
17
|
+
setup_url: '',
|
|
18
|
+
public: true,
|
|
19
|
+
default_permissions: {
|
|
20
|
+
checks: 'write',
|
|
21
|
+
contents: 'read',
|
|
22
|
+
pull_requests: 'write',
|
|
23
|
+
statuses: 'write',
|
|
24
|
+
},
|
|
25
|
+
default_events: ['pull_request', 'push'],
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=app-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-manifest.js","sourceRoot":"","sources":["../../../src/github/app-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,UAAU;IAChB,GAAG,EAAE,sBAAsB;IAC3B,eAAe,EAAE;QACf,GAAG,EAAE,EAAE,EAAE,uDAAuD;KACjE;IACD,YAAY,EAAE,EAAE;IAChB,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,IAAI;IACZ,mBAAmB,EAAE;QACnB,MAAM,EAAE,OAAgB;QACxB,QAAQ,EAAE,MAAe;QACzB,aAAa,EAAE,OAAgB;QAC/B,QAAQ,EAAE,OAAgB;KAC3B;IACD,cAAc,EAAE,CAAC,cAAc,EAAE,MAAM,CAAU;CAClD,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Finding } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a check run in 'in_progress' state.
|
|
4
|
+
* Returns the check run ID.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createCheckRun(options: {
|
|
7
|
+
repoFullName: string;
|
|
8
|
+
headSha: string;
|
|
9
|
+
installationId: number;
|
|
10
|
+
}): Promise<number>;
|
|
11
|
+
/**
|
|
12
|
+
* Format findings as GitHub check run annotations.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatAnnotations(findings: Finding[]): Array<{
|
|
15
|
+
path: string;
|
|
16
|
+
start_line: number;
|
|
17
|
+
end_line: number;
|
|
18
|
+
annotation_level: 'failure' | 'warning' | 'notice';
|
|
19
|
+
message: string;
|
|
20
|
+
title: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Build a summary string for the check run output.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildSummary(findings: Finding[]): string;
|
|
26
|
+
/**
|
|
27
|
+
* Complete a check run with results.
|
|
28
|
+
*/
|
|
29
|
+
export declare function completeCheckRun(options: {
|
|
30
|
+
repoFullName: string;
|
|
31
|
+
checkRunId: number;
|
|
32
|
+
installationId: number;
|
|
33
|
+
conclusion: 'success' | 'failure' | 'neutral';
|
|
34
|
+
findings: Finding[];
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../../../src/github/checks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAelB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,EAAE,GAClB,KAAK,CAAC;IACP,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CASD;AAgBD;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAiBxD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9C,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getInstallationToken, githubApi } from './api.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a check run in 'in_progress' state.
|
|
4
|
+
* Returns the check run ID.
|
|
5
|
+
*/
|
|
6
|
+
export async function createCheckRun(options) {
|
|
7
|
+
const token = await getInstallationToken(options.installationId);
|
|
8
|
+
const result = (await githubApi(`/repos/${options.repoFullName}/check-runs`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
token,
|
|
11
|
+
body: {
|
|
12
|
+
name: 'ShipSafe Security Scan',
|
|
13
|
+
head_sha: options.headSha,
|
|
14
|
+
status: 'in_progress',
|
|
15
|
+
started_at: new Date().toISOString(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
return result.id;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Format findings as GitHub check run annotations.
|
|
22
|
+
*/
|
|
23
|
+
export function formatAnnotations(findings) {
|
|
24
|
+
return findings.map((finding) => ({
|
|
25
|
+
path: finding.file,
|
|
26
|
+
start_line: finding.line,
|
|
27
|
+
end_line: finding.line,
|
|
28
|
+
annotation_level: severityToAnnotationLevel(finding.severity),
|
|
29
|
+
message: `${finding.description}\n\nFix: ${finding.fix_suggestion}`,
|
|
30
|
+
title: `[${finding.severity.toUpperCase()}] ${finding.type}`,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
function severityToAnnotationLevel(severity) {
|
|
34
|
+
switch (severity) {
|
|
35
|
+
case 'critical':
|
|
36
|
+
case 'high':
|
|
37
|
+
return 'failure';
|
|
38
|
+
case 'medium':
|
|
39
|
+
return 'warning';
|
|
40
|
+
default:
|
|
41
|
+
return 'notice';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build a summary string for the check run output.
|
|
46
|
+
*/
|
|
47
|
+
export function buildSummary(findings) {
|
|
48
|
+
if (findings.length === 0) {
|
|
49
|
+
return 'ShipSafe found no security issues. Ship it!';
|
|
50
|
+
}
|
|
51
|
+
const critical = findings.filter((f) => f.severity === 'critical').length;
|
|
52
|
+
const high = findings.filter((f) => f.severity === 'high').length;
|
|
53
|
+
const medium = findings.filter((f) => f.severity === 'medium').length;
|
|
54
|
+
const low = findings.filter((f) => f.severity === 'low').length;
|
|
55
|
+
const parts = [];
|
|
56
|
+
if (critical > 0)
|
|
57
|
+
parts.push(`${critical} critical`);
|
|
58
|
+
if (high > 0)
|
|
59
|
+
parts.push(`${high} high`);
|
|
60
|
+
if (medium > 0)
|
|
61
|
+
parts.push(`${medium} medium`);
|
|
62
|
+
if (low > 0)
|
|
63
|
+
parts.push(`${low} low`);
|
|
64
|
+
return `ShipSafe found ${findings.length} issues (${parts.join(', ')})`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Complete a check run with results.
|
|
68
|
+
*/
|
|
69
|
+
export async function completeCheckRun(options) {
|
|
70
|
+
const token = await getInstallationToken(options.installationId);
|
|
71
|
+
const annotations = formatAnnotations(options.findings);
|
|
72
|
+
const summary = buildSummary(options.findings);
|
|
73
|
+
// GitHub API limits annotations to 50 per request
|
|
74
|
+
const annotationBatch = annotations.slice(0, 50);
|
|
75
|
+
await githubApi(`/repos/${options.repoFullName}/check-runs/${options.checkRunId}`, {
|
|
76
|
+
method: 'PATCH',
|
|
77
|
+
token,
|
|
78
|
+
body: {
|
|
79
|
+
status: 'completed',
|
|
80
|
+
conclusion: options.conclusion,
|
|
81
|
+
completed_at: new Date().toISOString(),
|
|
82
|
+
output: {
|
|
83
|
+
title: 'ShipSafe Security Scan',
|
|
84
|
+
summary,
|
|
85
|
+
annotations: annotationBatch,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../../src/github/checks.ts"],"names":[],"mappings":"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"}
|