mirror of https://github.com/keeweb/keeweb.git
running VirusTotal checks on CI, see #1656
This commit is contained in:
parent
251b763ea9
commit
27d5fc4cba
|
@ -24,6 +24,14 @@ jobs:
|
||||||
run: npm test
|
run: npm test
|
||||||
- name: Grunt
|
- name: Grunt
|
||||||
run: grunt
|
run: grunt
|
||||||
|
- name: Write secrets
|
||||||
|
env:
|
||||||
|
VIRUS_TOTAL: ${{ secrets.VIRUS_TOTAL }}
|
||||||
|
run: |
|
||||||
|
mkdir keys
|
||||||
|
echo "$VIRUS_TOTAL" > keys/virus-total.json
|
||||||
|
- name: Check on VirusTotal
|
||||||
|
run: grunt virustotal
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -759,6 +759,15 @@ module.exports = function (grunt) {
|
||||||
headless: true
|
headless: true
|
||||||
},
|
},
|
||||||
default: 'test/runner.html'
|
default: 'test/runner.html'
|
||||||
|
},
|
||||||
|
virustotal: {
|
||||||
|
options: {
|
||||||
|
timeout: 10 * 60 * 1000,
|
||||||
|
get apiKey() {
|
||||||
|
return require('./keys/virus-total.json').apiKey;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
html: 'dist/index.html'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
module.exports = function (grunt) {
|
||||||
|
grunt.registerMultiTask('virustotal', 'Checks if a file has issues on VirusTotal', function () {
|
||||||
|
const done = this.async();
|
||||||
|
const opt = this.options();
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
this.files[0].src.map((file) =>
|
||||||
|
checkFile(opt, file).catch((err) => {
|
||||||
|
grunt.warn('VirusTotal check failed: ' + err);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).then(done);
|
||||||
|
|
||||||
|
async function checkFile(opt, file) {
|
||||||
|
grunt.log.writeln(`Uploading to VirusTotal: ${file}...`);
|
||||||
|
|
||||||
|
const timeStarted = Date.now();
|
||||||
|
|
||||||
|
const { apiKey, timeout = 60 * 1000 } = opt;
|
||||||
|
const interval = 5000;
|
||||||
|
|
||||||
|
const headers = { 'x-apikey': apiKey };
|
||||||
|
|
||||||
|
const fileData = fs.readFileSync(file);
|
||||||
|
const fileName = path.basename(file);
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('file', fileData, fileName);
|
||||||
|
const fileUploadResp = await fetch('https://www.virustotal.com/api/v3/files', {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: form
|
||||||
|
});
|
||||||
|
const fileUploadRespData = await fileUploadResp.json();
|
||||||
|
if (fileUploadRespData.error) {
|
||||||
|
const errStr = JSON.stringify(fileUploadRespData.error);
|
||||||
|
throw new Error(`File upload error: ${errStr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = fileUploadRespData.data.id;
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('File upload error: empty id');
|
||||||
|
}
|
||||||
|
|
||||||
|
grunt.log.writeln(`Uploaded ${file} to VirusTotal, id: ${id}`);
|
||||||
|
|
||||||
|
let elapsed;
|
||||||
|
do {
|
||||||
|
const checkResp = await fetch(`https://www.virustotal.com/api/v3/analyses/${id}`, {
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
const checkRespData = await checkResp.json();
|
||||||
|
if (checkRespData.error) {
|
||||||
|
const errStr = JSON.stringify(checkRespData.error);
|
||||||
|
throw new Error(`File check error: ${errStr}`);
|
||||||
|
}
|
||||||
|
const { attributes } = checkRespData.data;
|
||||||
|
if (attributes.status === 'completed') {
|
||||||
|
const { stats } = attributes;
|
||||||
|
if (stats.malicious > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`File ${file} reported as malicious ${stats.malicious} time(s)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (stats.suspicious > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`File ${file} reported as malicious ${stats.suspicious} time(s)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const statsStr = Object.entries(stats)
|
||||||
|
.map(([k, v]) => `${k}=${v}`)
|
||||||
|
.join(', ');
|
||||||
|
grunt.log.writeln(`VirusTotal check OK: ${file}, stats:`, statsStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed = Date.now() - timeStarted;
|
||||||
|
grunt.log.writeln(
|
||||||
|
`VirusTotal check status: ${attributes.status}, elapsed ${elapsed}ms`
|
||||||
|
);
|
||||||
|
|
||||||
|
await wait(interval);
|
||||||
|
} while (elapsed < timeout);
|
||||||
|
|
||||||
|
throw new Error(`Timed out after ${timeout}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wait(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -6829,12 +6829,12 @@
|
||||||
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
||||||
},
|
},
|
||||||
"form-data": {
|
"form-data": {
|
||||||
"version": "2.3.3",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
|
||||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.6",
|
"combined-stream": "^1.0.8",
|
||||||
"mime-types": "^2.1.12"
|
"mime-types": "^2.1.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14294,6 +14294,16 @@
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"form-data": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.6",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"qs": {
|
"qs": {
|
||||||
"version": "6.5.2",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
"eslint-plugin-promise": "4.2.1",
|
"eslint-plugin-promise": "4.2.1",
|
||||||
"eslint-plugin-standard": "4.1.0",
|
"eslint-plugin-standard": "4.1.0",
|
||||||
"exports-loader": "1.1.1",
|
"exports-loader": "1.1.1",
|
||||||
|
"form-data": "^3.0.0",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"grunt": "1.3.0",
|
"grunt": "1.3.0",
|
||||||
"grunt-chmod": "^1.1.1",
|
"grunt-chmod": "^1.1.1",
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
"mini-css-extract-plugin": "^1.3.1",
|
"mini-css-extract-plugin": "^1.3.1",
|
||||||
"mocha": "^8.2.1",
|
"mocha": "^8.2.1",
|
||||||
"morphdom": "^2.6.1",
|
"morphdom": "^2.6.1",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
"node-stream-zip": "1.12.0",
|
"node-stream-zip": "1.12.0",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
|
|
Loading…
Reference in New Issue