Skip to content

Commit ce1ffb8

Browse files
authored
Merge pull request #282 from codecov/add-checksum-verification
Add checksum verification of bash script
2 parents 9b0b9bb + 864620a commit ce1ffb8

File tree

5 files changed

+317
-39
lines changed

5 files changed

+317
-39
lines changed

action.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ inputs:
5959
name:
6060
description: 'User defined upload name. Visible in Codecov UI'
6161
required: false
62+
network_filter:
63+
description: 'Used to restrict the set of git/hg files that can be matched with filenames in the coverage report. This is useful for monorepos or other setups where a full filepath may not be specified in the coverage report, and that shortened filepath may appear multiple times in a directory structure (e.g. __init__.py)'
64+
required: false
6265
override_branch:
6366
description: 'Specify the branch name'
6467
required: false
@@ -74,9 +77,6 @@ inputs:
7477
override_tag:
7578
description: 'Specify the git tag'
7679
required: false
77-
network_filter:
78-
description: 'Used to restrict the set of git/hg files that can be matched with filenames in the coverage report. This is useful for monorepos or other setups where a full filepath may not be specified in the coverage report, and that shortened filepath may appear multiple times in a directory structure (e.g. __init__.py)'
79-
required: false
8080
path_to_write_report:
8181
description: 'Write upload file to path before uploading'
8282
required: false

dist/index.js

Lines changed: 206 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13152,62 +13152,119 @@ module.exports = {"$id":"log.json#","$schema":"http://json-schema.org/draft-06/s
1315213152

1315313153
"use strict";
1315413154

13155+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13156+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
13157+
return new (P || (P = Promise))(function (resolve, reject) {
13158+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
13159+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13160+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
13161+
step((generator = generator.apply(thisArg, _arguments || [])).next());
13162+
});
13163+
};
13164+
var __generator = (this && this.__generator) || function (thisArg, body) {
13165+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13166+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13167+
function verb(n) { return function (v) { return step([n, v]); }; }
13168+
function step(op) {
13169+
if (f) throw new TypeError("Generator is already executing.");
13170+
while (_) try {
13171+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
13172+
if (y = 0, t) op = [op[0] & 2, t.value];
13173+
switch (op[0]) {
13174+
case 0: case 1: t = op; break;
13175+
case 4: _.label++; return { value: op[1], done: false };
13176+
case 5: _.label++; y = op[1]; op = [0]; continue;
13177+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
13178+
default:
13179+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
13180+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
13181+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
13182+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
13183+
if (t[2]) _.ops.pop();
13184+
_.trys.pop(); continue;
13185+
}
13186+
op = body.call(thisArg, _);
13187+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
13188+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
13189+
}
13190+
};
1315513191
exports.__esModule = true;
1315613192
var core = __webpack_require__(470);
1315713193
var exec = __webpack_require__(986);
1315813194
var fs = __webpack_require__(747);
1315913195
var request = __webpack_require__(335);
1316013196
var buildExec_1 = __webpack_require__(983);
13197+
var validate_1 = __webpack_require__(743);
1316113198
var failCi;
1316213199
try {
1316313200
request({
1316413201
json: false,
1316513202
maxAttempts: 10,
1316613203
timeout: 3000,
1316713204
url: 'https://codecov.io/bash',
13168-
}, function (error, response, body) {
13169-
var _a = buildExec_1["default"](), execArgs = _a.execArgs, options = _a.options, filepath = _a.filepath, failCi = _a.failCi;
13170-
try {
13171-
if (error && failCi) {
13172-
throw error;
13173-
}
13174-
else if (error) {
13175-
core.warning("Codecov warning: " + error.message);
13176-
}
13177-
fs.writeFile(filepath, body, function (err) {
13178-
if (err && failCi) {
13179-
throw err;
13180-
}
13181-
else if (err) {
13182-
core.warning("Codecov warning: " + err.message);
13183-
}
13184-
exec.exec('bash', execArgs, options)["catch"](function (err) {
13185-
if (failCi) {
13186-
core.setFailed("Codecov failed with the following error: " + err.message);
13205+
}, function (error, response, body) { return __awaiter(void 0, void 0, void 0, function () {
13206+
var _a, execArgs, options, filepath, failCi, isValid, failure, error_1;
13207+
return __generator(this, function (_b) {
13208+
switch (_b.label) {
13209+
case 0:
13210+
_a = buildExec_1["default"](), execArgs = _a.execArgs, options = _a.options, filepath = _a.filepath, failCi = _a.failCi;
13211+
_b.label = 1;
13212+
case 1:
13213+
_b.trys.push([1, 3, , 4]);
13214+
return [4 /*yield*/, validate_1["default"](body)];
13215+
case 2:
13216+
isValid = _b.sent();
13217+
if (!isValid) {
13218+
failure = 'Codecov failure: ' +
13219+
'Bash script checksums do not match published values. ' +
13220+
'Please contact [email protected] immediately.';
13221+
core.setFailed(failure);
13222+
throw new Error(failure);
1318713223
}
13188-
else {
13189-
core.warning("Codecov warning: " + err.message);
13224+
if (error && failCi) {
13225+
throw error;
1319013226
}
13191-
})
13192-
.then(function () {
13193-
unlinkFile();
13194-
});
13195-
var unlinkFile = function () {
13196-
fs.unlink(filepath, function (err) {
13227+
else if (error) {
13228+
core.warning("Codecov warning: " + error.message);
13229+
}
13230+
fs.writeFile(filepath, body, function (err) {
1319713231
if (err && failCi) {
1319813232
throw err;
1319913233
}
1320013234
else if (err) {
1320113235
core.warning("Codecov warning: " + err.message);
1320213236
}
13237+
exec.exec('bash', execArgs, options)["catch"](function (err) {
13238+
if (failCi) {
13239+
core.setFailed("Codecov failed with the following error: " + err.message);
13240+
}
13241+
else {
13242+
core.warning("Codecov warning: " + err.message);
13243+
}
13244+
})
13245+
.then(function () {
13246+
unlinkFile();
13247+
});
13248+
var unlinkFile = function () {
13249+
fs.unlink(filepath, function (err) {
13250+
if (err && failCi) {
13251+
throw err;
13252+
}
13253+
else if (err) {
13254+
core.warning("Codecov warning: " + err.message);
13255+
}
13256+
});
13257+
};
1320313258
});
13204-
};
13205-
});
13206-
}
13207-
catch (error) {
13208-
core.setFailed("Codecov failed with the following error: " + error.message);
13209-
}
13210-
});
13259+
return [3 /*break*/, 4];
13260+
case 3:
13261+
error_1 = _b.sent();
13262+
core.setFailed("Codecov failed with the following error: " + error_1.message);
13263+
return [3 /*break*/, 4];
13264+
case 4: return [2 /*return*/];
13265+
}
13266+
});
13267+
}); });
1321113268
}
1321213269
catch (error) {
1321313270
if (failCi) {
@@ -49116,7 +49173,121 @@ module.exports = function (data, opts) {
4911649173

4911749174

4911849175
/***/ }),
49119-
/* 743 */,
49176+
/* 743 */
49177+
/***/ (function(__unusedmodule, exports, __webpack_require__) {
49178+
49179+
"use strict";
49180+
49181+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
49182+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
49183+
return new (P || (P = Promise))(function (resolve, reject) {
49184+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
49185+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
49186+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
49187+
step((generator = generator.apply(thisArg, _arguments || [])).next());
49188+
});
49189+
};
49190+
var __generator = (this && this.__generator) || function (thisArg, body) {
49191+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
49192+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
49193+
function verb(n) { return function (v) { return step([n, v]); }; }
49194+
function step(op) {
49195+
if (f) throw new TypeError("Generator is already executing.");
49196+
while (_) try {
49197+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
49198+
if (y = 0, t) op = [op[0] & 2, t.value];
49199+
switch (op[0]) {
49200+
case 0: case 1: t = op; break;
49201+
case 4: _.label++; return { value: op[1], done: false };
49202+
case 5: _.label++; y = op[1]; op = [0]; continue;
49203+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
49204+
default:
49205+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
49206+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
49207+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
49208+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
49209+
if (t[2]) _.ops.pop();
49210+
_.trys.pop(); continue;
49211+
}
49212+
op = body.call(thisArg, _);
49213+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
49214+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
49215+
}
49216+
};
49217+
exports.__esModule = true;
49218+
exports.retrieveChecksum = void 0;
49219+
var crypto = __webpack_require__(417);
49220+
var core = __webpack_require__(470);
49221+
var request = __webpack_require__(335);
49222+
var validateUploader = function (body) { return __awaiter(void 0, void 0, void 0, function () {
49223+
var version, _i, _a, i, publicChecksum, uploaderChecksum;
49224+
return __generator(this, function (_b) {
49225+
switch (_b.label) {
49226+
case 0:
49227+
version = getVersion(body);
49228+
if (version === null) {
49229+
core.warning('Codecov could not identify the bash uploader version.');
49230+
return [2 /*return*/, false];
49231+
}
49232+
_i = 0, _a = [1, 256, 512];
49233+
_b.label = 1;
49234+
case 1:
49235+
if (!(_i < _a.length)) return [3 /*break*/, 4];
49236+
i = _a[_i];
49237+
return [4 /*yield*/, exports.retrieveChecksum(version, i)];
49238+
case 2:
49239+
publicChecksum = _b.sent();
49240+
uploaderChecksum = calculateChecksum(body, i);
49241+
if (uploaderChecksum !== publicChecksum.trim()) {
49242+
core.warning("Codecov " + version + " checksums for SHA" + i + " failed to match.\n" +
49243+
("Public checksum: " + publicChecksum) +
49244+
("Uploader checksum: " + uploaderChecksum));
49245+
return [2 /*return*/, false];
49246+
}
49247+
_b.label = 3;
49248+
case 3:
49249+
_i++;
49250+
return [3 /*break*/, 1];
49251+
case 4: return [2 /*return*/, true];
49252+
}
49253+
});
49254+
}); };
49255+
var retrieveChecksum = function (version, encryption) { return __awaiter(void 0, void 0, void 0, function () {
49256+
var url, response;
49257+
return __generator(this, function (_a) {
49258+
switch (_a.label) {
49259+
case 0:
49260+
url = "https://raw.githubusercontent.com/codecov/codecov-bash/" + version + "/SHA" + encryption + "SUM";
49261+
return [4 /*yield*/, request({
49262+
maxAttempts: 10,
49263+
timeout: 3000,
49264+
url: url,
49265+
})];
49266+
case 1:
49267+
response = _a.sent();
49268+
if (response.statusCode != 200) {
49269+
core.warning("Codecov could not retrieve checksum SHA" + encryption + " at " + url);
49270+
return [2 /*return*/, ''];
49271+
}
49272+
return [2 /*return*/, response.body];
49273+
}
49274+
});
49275+
}); };
49276+
exports.retrieveChecksum = retrieveChecksum;
49277+
var calculateChecksum = function (body, i) {
49278+
var shasum = crypto.createHash("sha" + i);
49279+
shasum.update(body);
49280+
return shasum.digest('hex') + " codecov";
49281+
};
49282+
var getVersion = function (body) {
49283+
var regex = /VERSION="(.*)+"/g;
49284+
var match = regex.exec(body);
49285+
return match ? match[1] : null;
49286+
};
49287+
exports["default"] = validateUploader;
49288+
49289+
49290+
/***/ }),
4912049291
/* 744 */
4912149292
/***/ (function(module) {
4912249293

src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const fs = require('fs');
55
const request = require('requestretry');
66

77
import buildExec from './buildExec';
8+
import validateUploader from './validate';
89

910
let failCi;
1011
try {
@@ -13,10 +14,19 @@ try {
1314
maxAttempts: 10,
1415
timeout: 3000,
1516
url: 'https://codecov.io/bash',
16-
}, (error, response, body) => {
17+
}, async (error, response, body) => {
1718
const {execArgs, options, filepath, failCi} = buildExec();
1819

1920
try {
21+
const isValid = await validateUploader(body);
22+
if (!isValid) {
23+
const failure = 'Codecov failure: ' +
24+
'Bash script checksums do not match published values. ' +
25+
'Please contact [email protected] immediately.';
26+
core.setFailed(failure);
27+
throw new Error(failure);
28+
}
29+
2030
if (error && failCi) {
2131
throw error;
2232
} else if (error) {

src/validate.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import validateUploader, {retrieveChecksum} from './validate';
2+
3+
const request = require('requestretry');
4+
5+
const bashScript = (async () => {
6+
try {
7+
const script = await request({
8+
json: false,
9+
maxAttempts: 10,
10+
timeout: 3000,
11+
url: 'https://codecov.io/bash',
12+
});
13+
return script.body;
14+
} catch (err) {
15+
throw err;
16+
}
17+
});
18+
19+
test('valid checksums', async () => {
20+
const valid = await validateUploader(await bashScript());
21+
expect(valid).toBeTruthy();
22+
});
23+
24+
test('invalid checksums', async () => {
25+
const script = await bashScript();
26+
const valid = await validateUploader(script.substring(0, script.length - 1));
27+
expect(valid).toBeFalsy();
28+
});
29+
30+
test('invalid script version', async () => {
31+
const script = await bashScript();
32+
const valid = await validateUploader(script.substring(0, 20));
33+
expect(valid).toBeFalsy();
34+
});
35+
36+
test('invalid public checksum file', async () => {
37+
const checksum = await retrieveChecksum('foo', 'bar');
38+
expect(checksum).toBeFalsy();
39+
});

0 commit comments

Comments
 (0)