Skip to content

Commit 07e5905

Browse files
committed
Add secure codecov publish script
1 parent 9cbfb3f commit 07e5905

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
os: linux
1717
script:
1818
- npm run test-unit
19-
- bash <(curl -s https://codecov.io/bash) -c -Z -f .coverage/coverage-final.json -F unit
19+
- npm run codecov -- -c -Z -f .coverage/coverage-final.json -F unit
2020

2121
# create a new release if $CREATE_RELEASE is set
2222
- stage: Create Release

CHANGELOG.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
unreleased:
22
chores:
3+
- Added secure codecov publish script
34
- Updated dependencies
45

56
3.0.1:

npm/publish-coverage.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env node
2+
// ---------------------------------------------------------------------------------------------------------------------
3+
// This script is intended to publish coverage reports to Codecov.
4+
//
5+
// It verifies the Codecov's bash uploader checksum before execution.
6+
// Usage: npm run codecov -- <bash uploader arguments>
7+
// Refer: https://about.codecov.io/security-update/
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
10+
const https = require('https'),
11+
crypto = require('crypto'),
12+
promisify = require('util').promisify,
13+
14+
// eslint-disable-next-line security/detect-child-process
15+
exec = promisify(require('child_process').exec),
16+
writeFile = promisify(require('fs').writeFile),
17+
18+
chalk = require('chalk'),
19+
20+
CODECOV_PATH = '.coverage/codecov.sh',
21+
BASH_UPLOADER_URL = 'https://codecov.io/bash',
22+
BASH_UPLOADER_BASE = 'https://raw.githubusercontent.com/codecov/codecov-bash';
23+
24+
function wget (url) {
25+
return new Promise((resolve, reject) => {
26+
const req = https.request(url, (res) => {
27+
if (res.statusCode !== 200) {
28+
return reject(new Error('non-200 response'));
29+
}
30+
31+
let data = '';
32+
33+
res.on('data', (chunk) => {
34+
data += chunk;
35+
});
36+
37+
res.on('end', () => {
38+
resolve(data);
39+
});
40+
});
41+
42+
req.on('error', (err) => {
43+
reject(err);
44+
});
45+
46+
req.end();
47+
});
48+
}
49+
50+
function getVersion (script) {
51+
const match = script.match(/VERSION="([0-9.]*)"/);
52+
53+
return match ? match[1] : null;
54+
}
55+
56+
async function getPublicChecksum (version, encryption) {
57+
const url = `${BASH_UPLOADER_BASE}/${version}/SHA${encryption}SUM`,
58+
checksumResponse = await wget(url);
59+
60+
// return codecov checksum only
61+
return checksumResponse.split('\n')[0];
62+
}
63+
64+
function calculateChecksum (script, encryption) {
65+
const shasum = crypto.createHash(`sha${encryption}`);
66+
67+
shasum.update(script);
68+
69+
return `${shasum.digest('hex')} codecov`;
70+
}
71+
72+
async function validateScript (script) {
73+
const version = getVersion(script);
74+
75+
if (!version) {
76+
throw new Error('Missing bash uploader version');
77+
}
78+
79+
for (const encryption of [1, 256, 512]) {
80+
// eslint-disable-next-line no-await-in-loop
81+
const publicChecksum = await getPublicChecksum(version, encryption),
82+
uploaderChecksum = calculateChecksum(script, encryption);
83+
84+
if (uploaderChecksum !== publicChecksum) {
85+
throw new Error(`SHA${encryption} checksum mismatch`);
86+
}
87+
}
88+
}
89+
90+
module.exports = async function () {
91+
// banner line
92+
console.info(chalk.yellow.bold('Publishing coverage reports...'));
93+
94+
const args = process.argv.slice(2),
95+
script = await wget(BASH_UPLOADER_URL);
96+
97+
await validateScript(script);
98+
await writeFile(CODECOV_PATH, script);
99+
100+
return exec(`bash ${CODECOV_PATH} ${args.join(' ')}`);
101+
};
102+
103+
// ensure we run this script exports if this is a direct stdin.tty run
104+
if (!module.parent) {
105+
module.exports()
106+
.then(({ stdout, stderr }) => {
107+
console.info(stdout);
108+
console.info(stderr);
109+
})
110+
.catch(({ message, stack, stdout, stderr }) => {
111+
console.error(stack || message);
112+
stdout && console.info(stdout);
113+
stderr && console.info(stderr);
114+
115+
process.exit(1);
116+
});
117+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
],
2323
"scripts": {
2424
"build-docs": "node npm/build-docs.js",
25+
"codecov": "node npm/publish-coverage.js",
2526
"release": "node npm/create-release.js",
2627
"test": "npm run test-lint && npm run test-unit",
2728
"test-benchmark": "node npm/test-benchmark.js",

0 commit comments

Comments
 (0)