Skip to content

Commit 17c819c

Browse files
committed
feat: send result to discord webhook
1 parent bff483a commit 17c819c

File tree

6 files changed

+462
-54
lines changed

6 files changed

+462
-54
lines changed

cli.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ program.command('scan')
4040
.option('--db-output <dbOutput>', 'database folder path. ')
4141
.option('--remove-database', 'remove the CodeQL database after scanning.')
4242
.option('--create-db-only', 'only create CodeQL database, do not scan.')
43+
.option('--enable-file-logging', 'enable file logging.')
44+
.option('--discord-webhook <webhookUrl>', 'discord web hook to send the result to.')
4345
.option('--use-docker', 'use docker to isolated run CodeQL.')
4446
.option('-v, --verbose', 'verbose output')
4547
.action(scan);

index.js

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const logger = require('./logger');
1+
const { defaultLogger, bugLogger } = require('./logger');
22
const utils = require('./utils');
33
const fs = require('fs');
44
const config = require('./config');
@@ -9,34 +9,36 @@ module.exports = {
99
}
1010

1111
async function scanAction(sourceTarget, options) {
12-
if (options.verbose) { logger.setLevel('verbose') }
12+
if (options.verbose) { defaultLogger.setLevel('verbose') }
1313
if (options.useDocker) {
14-
await utils.isCommandExist('docker', logger);
15-
logger.error('Docker is not supported yet.');
14+
await utils.isCommandExist('docker', defaultLogger);
15+
defaultLogger.error('Docker is not supported yet.');
1616
return;
1717
}
18-
await utils.isCommandExist('codeql', logger);
18+
if (options.enableFileLogging) { defaultLogger.enableFileTransport() }
19+
if (options.discordWebhook) { bugLogger.enableDiscordTransport(options.discordWebhook) }
20+
await utils.isCommandExist('codeql', defaultLogger);
1921
// Create Database
2022
var createDbOptions = { ...options };
2123
createDbOptions.output = options.dbOutput;
2224
var isRemoteRepository = utils.isRemoteRepository(sourceTarget);
2325
if (isRemoteRepository) {
24-
logger.info(`Cloning remote repository ${sourceTarget}`)
25-
sourceFolderPath = await utils.cloneRemoteRepository(sourceTarget, logger);
26+
defaultLogger.info(`Cloning remote repository ${sourceTarget}`)
27+
sourceFolderPath = await utils.cloneRemoteRepository(sourceTarget, defaultLogger);
2628
} else sourceFolderPath = sourceTarget;
2729
sourceFolderPath = fs.realpathSync(sourceFolderPath);
28-
logger.info(`Creating CodeQL database for ${sourceFolderPath}...`)
29-
var { args: createDbArgs, databasePath } = await utils.setupCreateDatabaseCommandArgs(sourceFolderPath, createDbOptions, logger);
30-
logger.verbose(`Options:`);
30+
defaultLogger.info(`Creating CodeQL database for ${sourceFolderPath}...`)
31+
var { args: createDbArgs, databasePath } = await utils.setupCreateDatabaseCommandArgs(sourceFolderPath, createDbOptions, defaultLogger);
32+
defaultLogger.verbose(`Options:`);
3133
for (const key in options) {
3234
const element = options[key];
33-
logger.verbose(`[+] ${key}: ${element}`);
35+
defaultLogger.verbose(`[+] ${key}: ${element}`);
3436
}
35-
createDbExitCode = await utils.executeCommand('codeql', createDbArgs, 'Create CodeQL database', logger);
36-
logger.info(`CodeQL database created at ${databasePath}.`)
37+
createDbExitCode = await utils.executeCommand('codeql', createDbArgs, 'Create CodeQL database', defaultLogger);
38+
defaultLogger.info(`CodeQL database created at ${databasePath}.`)
3739
if (isRemoteRepository && options.removeRemoteRepository) {
38-
logger.info(`Removing remote repository ${sourceFolderPath}`)
39-
await utils.removeFolder(sourceFolderPath, logger);
40+
defaultLogger.info(`Removing remote repository ${sourceFolderPath}`)
41+
await utils.removeFolder(sourceFolderPath, defaultLogger);
4042
}
4143
if (options.createDbOnly) {
4244
return databasePath;
@@ -46,26 +48,39 @@ async function scanAction(sourceTarget, options) {
4648
if (!fs.existsSync(outputFolderPath)) {
4749
fs.mkdirSync(outputFolderPath);
4850
}
49-
const languages = await utils.getDatabaseLanguages(databasePath, logger);
51+
const languages = await utils.getDatabaseLanguages(databasePath, defaultLogger);
52+
if (!languages) {
53+
defaultLogger.error('Can not detect languages. Please specify the language using --language option');
54+
return;
55+
}
5056
for (const language of languages) {
5157
options.language = language;
5258
languageDatabasePath = path.resolve(`${databasePath}${path.sep}${language}`);
5359
options.output = path.resolve(outputFolderPath, `${language}-codeql-result.sarif`)
54-
logger.info(`Scanning ${language} code in ${databasePath}...`)
55-
var { args: scanArgs } = await utils.setupScanCommandArgs(languageDatabasePath, options, logger);
56-
await utils.executeCommand('codeql', scanArgs, 'Scan CodeQL database', logger);
60+
defaultLogger.info(`Scanning ${language} code in ${databasePath}...`)
61+
var { args: scanArgs } = await utils.setupScanCommandArgs(languageDatabasePath, options, defaultLogger);
62+
await utils.executeCommand('codeql', scanArgs, 'Scan CodeQL database', defaultLogger);
5763
}
58-
logger.info(`CodeQL scan results saved at ${outputFolderPath}.`)
64+
defaultLogger.info(`CodeQL scan results saved at ${outputFolderPath}.`)
5965
const resultFiles = fs.readdirSync(outputFolderPath);
66+
var alerts = [];
6067
for (const resultFile of resultFiles) {
61-
const alerts = await utils.parseSarif(path.resolve(outputFolderPath, resultFile), logger);
62-
for (const alert of alerts) {
63-
logger.error(`[${alert.id}][${alert.level}][precision:${alert.precision}][severity:${alert.severity}] ${alert.title}: ${alert.location}`);
64-
}
68+
alerts = alerts.concat(await utils.parseSarif(path.resolve(outputFolderPath, resultFile), defaultLogger));
69+
}
70+
for (const alert of alerts) {
71+
defaultLogger.log({
72+
level: utils.castBugLevelToLogLevel(alert.level),
73+
message: `[${alert.id}][${alert.level}][precision:${alert.precision}][severity:${alert.severity}][${alert.location}] ${alert.title}`
74+
});
75+
bugLogger.log({
76+
level: utils.castBugLevelToLogLevel(alert.level),
77+
message: path.basename(sourceFolderPath),
78+
meta: alert
79+
});
6580
}
6681
if (options.removeDatabase) {
67-
logger.info(`Removing database folder ${databasePath}`)
68-
await utils.removeFolder(databasePath, logger);
82+
defaultLogger.info(`Removing database folder ${databasePath}`)
83+
await utils.removeFolder(databasePath, defaultLogger);
6984
}
70-
return outputFolderPath;
85+
return alerts;
7186
}

logger.js

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,84 @@
11
const winston = require('winston');
2+
const { createLogger, format, transports } = winston;
3+
const Logger = winston.Logger;
4+
const { combine, timestamp, printf, colorize } = format;
5+
const DailyRotateFile = require('winston-daily-rotate-file');
6+
const { DiscordTransport } = require('winston-transport-discord');
7+
const { HiddenLogger } = require('winston/lib/winston/logger');
28

3-
// Default logger
4-
const logger = winston.createLogger({
5-
level: 'info',
6-
format: winston.format.json(),
7-
transports: [
8-
new winston.transports.Console({
9-
level: 'info',
10-
format: winston.format.combine(
11-
winston.format.colorize(),
12-
winston.format.simple()
13-
)
14-
})
15-
],
9+
/*
10+
* Custom logger
11+
*/
12+
const defaultLogFormat = printf(({ level, message, timestamp }) => {
13+
return `[${level}][${timestamp}]: ${message}`;
1614
});
1715

18-
logger.setLevel = function (logLevel) {
19-
this.clear()
20-
this.add(new winston.transports.Console({
21-
level: logLevel,
16+
const discordLogFormat = printf(({ level, message }) => {
17+
return `[${level}] ${message}`;
18+
});
19+
20+
const setLevel = function (logLevel) {
21+
this.level = logLevel;
22+
}
23+
const enableConsoleTransport = function () {
24+
this.add(new transports.Console({
2225
format: winston.format.combine(
23-
winston.format.colorize(),
24-
winston.format.simple()
26+
colorize({ all: true }),
27+
timestamp(),
28+
defaultLogFormat
2529
)
2630
}))
2731
}
32+
const enableFileTransport = function () {
33+
this.add(new DailyRotateFile({
34+
format: winston.format.combine(
35+
timestamp(),
36+
defaultLogFormat
37+
),
38+
filename: 'logs/application-%DATE%.log',
39+
datePattern: 'YYYY-MM-DD',
40+
zippedArchive: true,
41+
maxSize: '20m',
42+
maxFiles: '30d'
43+
}))
44+
}
45+
const enableDiscordTransport = function (webhookUrl) {
46+
this.add(new DiscordTransport({
47+
metadata: {
48+
host: 'CodeQL Agent'
49+
},
50+
discord: {
51+
webhook: {
52+
url: webhookUrl
53+
}
54+
},
55+
format: combine(discordLogFormat),
56+
level: 'info'
57+
}))
58+
}
59+
60+
/*
61+
* There are two logger instances
62+
* 1. Default logger: defaultLogger. This logger is used for logging to console and file
63+
* 2. Bug logger: bugLogger. This logger is used for logging bugs found.
64+
*/
65+
var defaultLogger = new createLogger({});
66+
var bugLogger = new createLogger({});
67+
68+
defaultLogger.setLevel = setLevel;
69+
defaultLogger.enableConsoleTransport = enableConsoleTransport;
70+
defaultLogger.enableFileTransport = enableFileTransport;
71+
defaultLogger.enableDiscordTransport = enableDiscordTransport;
72+
73+
bugLogger.setLevel = setLevel;
74+
bugLogger.enableConsoleTransport = enableConsoleTransport;
75+
bugLogger.enableFileTransport = enableFileTransport;
76+
bugLogger.enableDiscordTransport = enableDiscordTransport;
77+
78+
defaultLogger.setLevel('info')
79+
defaultLogger.enableConsoleTransport()
80+
81+
bugLogger.setLevel('info')
2882

29-
module.exports = logger;
83+
module.exports.defaultLogger = defaultLogger;
84+
module.exports.bugLogger = bugLogger;

0 commit comments

Comments
 (0)