diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f5f1e..d0ff384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 1.2.0 - 2024-08-08 + +- Support verbose scan result + ## 1.1.1 - 2024-04-10 - Update README.md diff --git a/README.md b/README.md index 21e3b7d..b0d1d7c 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Create a new instance of the `AmaasGrpcClient` class. **_Return_** An AmaasGrpcClient instance -#### `scanFile(name: string, tags?: string[], pml: boolean = false, feedback: boolean = false): Promise` +#### `scanFile(name: string, tags?: string[], pml: boolean = false, feedback: boolean = false): Promise` Scan a file for malware and retrieves response data from the API. @@ -170,11 +170,12 @@ Scan a file for malware and retrieves response data from the API. | tags | The list of tags which can be used to tag the scan. Max size of tags list is 8. Max size of each tag is 63. | | | pml | This flag is to enable Trend's predictive machine learning detection. | false | | feedback | This flag is to enable Trend Micro Smart Protection Network's Smart Feedback. | false | +| verbose | This flag is to enable verbose format for returning scan result. | false | **_Return_** A Promise that resolves to the API response data. -#### `scanBuffer(fileName: string, buff: Buffer, tags?: string[], pml: boolean = false, feedback: boolean = false): Promise` +#### `scanBuffer(fileName: string, buff: Buffer, tags?: string[], pml: boolean = false, feedback: boolean = false): Promise` Scan a buffer for malware and retrieves response data from the API. @@ -187,6 +188,7 @@ Scan a buffer for malware and retrieves response data from the API. | tags | The list of tags which can be used to tag the scan. Max size of tags list is 8. Max size of each tag is 63. | | | pml | This flag is to enable Trend's predictive machine learning detection. | false | | feedback | This flag is to enable Trend Micro Smart Protection Network's Smart Feedback. | false | +| verbose | This flag is to enable verbose format for returning scan result. | false | **_Return_** A Promise that resolves to the API response data. @@ -231,16 +233,18 @@ void ### `AmaasScanResultObject` -The AmaasScanResultObject interface defines the structure of the response data that is retrieved from our API. +The AmaasScanResultObject interface defines the structure of the response data that is retrieved from our API in regular format (i.e. verbose flag is off). The following are the fields in the interface. ```typescript interface AmaasScanResultObject { + scannerVersion: string; // Scanner version + schemaVersion: string; // Scan result schema version + scanResult: number; // Number of malwares found. A value of 0 means no malware was found scanTimestamp: string; // Timestamp of the scan in ISO 8601 format - version: string; // Scan result schema version fileName: string; // Name of the file scanned scanId: string; // ID of the scan - scanResult: number; // Number of malwares found. A value of 0 means no malware was found + foundMalwares: [ // A list of malware names and the filenames found by AMaaS { @@ -248,9 +252,110 @@ interface AmaasScanResultObject { malwareName: string; // Malware name } ]; + foundErrors?: [ + name: string; // Name of the error + description: string // Description of the error + ] + "fileSHA1": string; + "fileSHA256": string +} +``` + +### `AmaasScanResultVerbose` + +The AmaasScanResultVerbose interface defines the structure of the response data that is retrieved from our API in verbose format. +The following are the fields in the interface. + +```typescript + +interface AmaasScanResultVerbose { + scanType: string + objectType: string + timestamp: { + start: string + end: string + } + schemaVersion: string + scannerVersion: string + fileName: string + rsSize: number + scanId: string + accountId: string + result: { + atse: { + elapsedTime: number + fileType: number + fileSubType: number + version: { + engine: string + lptvpn: number + ssaptn: number + tmblack: number + tmwhite: number + macvpn: number + } + malwareCount: number + malware: Array< + { + name: string + fileName: string + type: string + fileType: number + fileTypeName: string + fileSubType: number + fileSubTypeName: string + } + > | null + error: Array< + { + code: number + message: string + } + > | null + fileTypeName: string + fileSubTypeName: string + } + trendx?: { + elapsedTime: number + fileType: number + fileSubType: number + version: { + engine: string + tmblack: number + tmwhite: number + trendx: number + } + malwareCount: number + malware: Array< + { + name: string + fileName: string + type: string + fileType: number + fileTypeName: string + fileSubType: number + fileSubTypeName: string + } + > | null + error: Array< + { + code: number + message: string + } + > | null + fileTypeName: string + fileSubTypeName: string + } + } + tags?: [ string ] + fileSHA1: string + fileSHA256: string + appName: string } + ``` + ### `LogLevel` ```typescript diff --git a/VERSION b/VERSION index 524cb55..26aaba0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.1 +1.2.0 diff --git a/__tests__/amaasSDK.test.ts b/__tests__/amaasSDK.test.ts index 5f6c175..b2cd1db 100644 --- a/__tests__/amaasSDK.test.ts +++ b/__tests__/amaasSDK.test.ts @@ -11,6 +11,7 @@ import { randomUUID } from 'crypto' import { AmaasGrpcClient } from '../src/lib/amaasGrpcClient' import { AmaasScanResultObject } from '../src/lib/amaasScanResultObject' +import { AmaasScanResultVerbose } from '../src/lib/amaasScanResultVerbose' import { AmaasCredentials } from '../src/lib/amaasCredentials' import { Logger, LogLevel } from '../src/lib/logger' import * as scanPb from '../src/lib/protos/scan_pb' @@ -188,7 +189,7 @@ describe('AmaasGrpcClient scanFile function testing', () => { it('should successfully scan file sequentially', async () => { const amaasGrpcClient = new AmaasGrpcClient(amaasHostName, authKey, grpcConnectionTimeout, enableTLS) const filesArray = filesToScan.slice(1) - const results: AmaasScanResultObject[] = [] + const results: (AmaasScanResultObject | AmaasScanResultVerbose)[] = [] const lastResult = await filesArray.reduce( async (promised, current) => { await promised.then(result => { @@ -220,14 +221,30 @@ describe('AmaasGrpcClient scanFile function testing', () => { amaasGrpcClient.close() }) - it('should scan file with tags successfully without TrendX and feedback', async () => { + it('should scan file with tags successfully without TrendX and feedback and in non verbose format', async () => { const amaasGrpcClient = new AmaasGrpcClient(amaasHostName, authKey, grpcConnectionTimeout, enableTLS) const tags = ['tag1', 'tag2', 'tag3'] const pml = false const feedback = false - await amaasGrpcClient.scanFile(filesToScan[0], tags, pml, feedback) + const verbose = false + await amaasGrpcClient.scanFile(filesToScan[0], tags, pml, feedback, verbose) .then(result => { - expect(result.scanResult).not.toEqual(-1) + const regularResult = result as AmaasScanResultObject + expect(regularResult.scanResult).not.toEqual(-1) + }) + amaasGrpcClient.close() + }) + + it('should scan file with tags successfully without TrendX and feedback and in verbose format', async () => { + const amaasGrpcClient = new AmaasGrpcClient(amaasHostName, authKey, grpcConnectionTimeout, enableTLS) + const tags = ['tag1', 'tag2', 'tag3'] + const pml = false + const feedback = false + const verbose = true + await amaasGrpcClient.scanFile(filesToScan[0], tags, pml, feedback, verbose) + .then(result => { + const verboseResult = result as AmaasScanResultVerbose + expect(verboseResult.result).not.toEqual(null) }) amaasGrpcClient.close() }) @@ -237,9 +254,10 @@ describe('AmaasGrpcClient scanFile function testing', () => { const tags = ['tag1', 'tag2', 'tag3'] const pml = true const feedback = true - await amaasGrpcClient.scanFile(filesToScan[0], tags, pml, feedback) + const verbose = false + await amaasGrpcClient.scanFile(filesToScan[0], tags, pml, feedback, verbose) .then(result => { - expect(result.scanResult).not.toEqual(-1) + expect((result as AmaasScanResultObject).scanResult).not.toEqual(-1) }) amaasGrpcClient.close() }) @@ -256,7 +274,7 @@ describe('AmaasGrpcClient scanBuffer function testing', () => { it('should successfully scan buffer sequentially', async () => { const amaasGrpcClient = new AmaasGrpcClient(amaasHostName, authKey, grpcConnectionTimeout, enableTLS) const fileArray = filesToScan.slice(1) - const results: AmaasScanResultObject[] = [] + const results: (AmaasScanResultObject | AmaasScanResultVerbose)[] = [] const lastResult = await fileArray.reduce( async (promised, current) => { await promised.then(result => { @@ -297,7 +315,7 @@ describe('AmaasGrpcClient scanBuffer function testing', () => { const tags = ['tag1', 'tag2', 'tag3'] await amaasGrpcClient.scanBuffer(filesToScan[0], buff, tags) .then(result => { - expect(result.scanResult).not.toEqual(-1) + expect((result as AmaasScanResultObject).scanResult).not.toEqual(-1) }) amaasGrpcClient.close() }) @@ -308,9 +326,10 @@ describe('AmaasGrpcClient scanBuffer function testing', () => { const tags = ['tag1', 'tag2', 'tag3'] const pml = true const feedback = true - await amaasGrpcClient.scanBuffer(filesToScan[0], buff, tags, pml, feedback) + const verbose = false + await amaasGrpcClient.scanBuffer(filesToScan[0], buff, tags, pml, feedback, verbose) .then(result => { - expect(result.scanResult).not.toEqual(-1) + expect((result as AmaasScanResultObject).scanResult).not.toEqual(-1) }) amaasGrpcClient.close() }) @@ -370,7 +389,7 @@ describe('error testing', () => { const tags = ['tag1', 'tag2', 'tag4'] await amaasGrpcClient.scanFile(filesToScan[0], tags) .then(result => { - expect(result.scanResult).toEqual(-1) + expect((result as AmaasScanResultObject).scanResult).toEqual(-1) }) amaasGrpcClient.close() }) diff --git a/examples/cli/src/fileScan.ts b/examples/cli/src/fileScan.ts index e33f9e1..7fb9dca 100644 --- a/examples/cli/src/fileScan.ts +++ b/examples/cli/src/fileScan.ts @@ -21,9 +21,10 @@ const useKey = false * @param fileName - Name of the file to scan * @param tags - List of string to tag a scan * @param pml - Enable predictive machine learning detection - * @param feedback: Enable Trend Micro Smart Protection Network's Smart Feedback + * @param feedback - Enable Trend Micro Smart Protection Network's Smart Feedback + * @param verbose - Flag to indicate whether return result in verbose format */ -const runFileScan = async (fileName: string, tags: string[], pml: boolean, feedback: boolean): Promise => { +const runFileScan = async (fileName: string, tags: string[], pml: boolean, feedback: boolean, verbose: boolean): Promise => { console.log(`\nScanning '${fileName}'`) const amaasGrpcClient = useKey ? new AmaasGrpcClient(amaasHostName, key) @@ -32,7 +33,7 @@ const runFileScan = async (fileName: string, tags: string[], pml: boolean, feedb loggerConfig(amaasGrpcClient) await amaasGrpcClient - .scanFile(fileName, tags, pml, feedback) + .scanFile(fileName, tags, pml, feedback, verbose) .then(result => { console.log(`${JSON.stringify(result)}`) }) @@ -49,5 +50,6 @@ void (async () => { const tags = ['example', 'test'] const predictive_machine_learning = true const smart_feedback = true - await runFileScan('node_modules/@grpc/grpc-js/src/admin.ts', tags, predictive_machine_learning, smart_feedback) + const verbose = true + await runFileScan('node_modules/@grpc/grpc-js/src/admin.ts', tags, predictive_machine_learning, smart_feedback, verbose) })() diff --git a/package.json b/package.json index f86878a..568872f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "file-security-sdk", - "version": "1.1.1", + "version": "1.2.0", "description": "Vision One File Security API library in TypeScript", "main": "index.js", "engines": { diff --git a/protos/scan.proto b/protos/scan.proto index c052249..3537f2f 100644 --- a/protos/scan.proto +++ b/protos/scan.proto @@ -29,6 +29,7 @@ message C2S { repeated string tags = 9; bool bulk = 10; bool spn_feedback = 11; + bool verbose = 12; } enum Command { diff --git a/src/lib/amaasGrpcClient.ts b/src/lib/amaasGrpcClient.ts index b29d3b9..ec4713b 100644 --- a/src/lib/amaasGrpcClient.ts +++ b/src/lib/amaasGrpcClient.ts @@ -4,6 +4,7 @@ import { status, credentials, Metadata, ServiceError } from '@grpc/grpc-js' import { ScanClient } from './protos/scan_grpc_pb' import { ScanRun } from './scanRun' import { AmaasScanResultObject } from './amaasScanResultObject' +import { AmaasScanResultVerbose } from './amaasScanResultVerbose' import { AmaasCredentials } from './amaasCredentials' import { CallMetadataGenerator } from '@grpc/grpc-js/build/src/call-credentials' import {getFQDN, validateTags } from './utils' @@ -152,8 +153,11 @@ export class AmaasGrpcClient { * * @param name - Filename * @param tags - Tags to be added to the scan request + * @param pml - Flag to enable predictive machine learning detection. + * @param feedback - Flag to use Trend Micro Smart Protection Network's Smart Feedback. + * @param verbose - Flag to enable verbose mode in returning scan result */ - public async scanFile (name: string, tags?: string[], pml: boolean = false, feedback: boolean = false): Promise { + public async scanFile (name: string, tags?: string[], pml: boolean = false, feedback: boolean = false, verbose: boolean = false): Promise { let size: number try { @@ -165,7 +169,7 @@ export class AmaasGrpcClient { const scanRun = this.initScanRun(tags) return await scanRun - .scanFile(name, size, pml, feedback) + .scanFile(name, size, pml, feedback, verbose) .then(result => result) .catch(err => { throw this.processError(err) @@ -178,11 +182,14 @@ export class AmaasGrpcClient { * @param fileName - Filename * @param buff - Buffer to scan * @param tags - Tags to be added to the scan request + * @param pml - Flag to enable predictive machine learning detection. + * @param feedback - Flag to use Trend Micro Smart Protection Network's Smart Feedback. + * @param verbose - Flag to enable verbose mode in returning scan result */ - public async scanBuffer (fileName: string, buff: Buffer, tags?: string[], pml: boolean = false, feedback: boolean = false): Promise { + public async scanBuffer (fileName: string, buff: Buffer, tags?: string[], pml: boolean = false, feedback: boolean = false, verbose: boolean = false): Promise { const scanRun = this.initScanRun(tags) return await scanRun - .scanBuffer(fileName, buff, pml, feedback) + .scanBuffer(fileName, buff, pml, feedback, verbose) .then(result => result) .catch(err => { throw this.processError(err) diff --git a/src/lib/amaasScanResultObject.ts b/src/lib/amaasScanResultObject.ts index 39c3969..2077eb0 100644 --- a/src/lib/amaasScanResultObject.ts +++ b/src/lib/amaasScanResultObject.ts @@ -1,13 +1,22 @@ export interface AmaasScanResultObject { + schemaVersion: string + scannerVersion: string + scanResult: number + scanId: string scanTimestamp: string - version: string fileName: string - scanId: string - scanResult: number - foundMalwares: [ + foundMalwares?: [ { fileName: string malwareName: string } ] + foundErrors?: [ + { + name: string + description: string + } + ] + fileSHA1: string + fileSHA256: string } diff --git a/src/lib/amaasScanResultVerbose.ts b/src/lib/amaasScanResultVerbose.ts new file mode 100644 index 0000000..0445d40 --- /dev/null +++ b/src/lib/amaasScanResultVerbose.ts @@ -0,0 +1,92 @@ +export interface AmaasScanResultVerbose { + scanType: string + objectType: string + timestamp: { + start: string + end: string + } + schemaVersion: string + scannerVersion: string + fileName: string + rsSize: number + scanId: string + accountId: string + result: { + atse: { + elapsedTime: number + fileType: number + fileSubType: number + version: { + engine: string + lptvpn: number + ssaptn: number + tmblack: number + tmwhite: number + macvpn: number + } + malwareCount: number + malware: Array< + { + name: string + fileName: string + type: string + fileType: number + fileTypeName: string + fileSubType: number + fileSubTypeName: string + } + > | null + error: Array< + { + code: number + message: string + } + > | null + fileTypeName: string + fileSubTypeName: string + } + trendx?: { + elapsedTime: number + fileType: number + fileSubType: number + version: { + engine: string + tmblack: number + tmwhite: number + trendx: number + } + malwareCount: number + malware: Array< + { + name: string + fileName: string + type: string + fileType: number + fileTypeName: string + fileSubType: number + fileSubTypeName: string + } + > | null + error: Array< + { + code: number + message: string + } + > | null + fileTypeName: string + fileSubTypeName: string + } + tap?: { + error: Array< + { + code: number + message: string + } + > | null + } + } + tags?: [ string ] + fileSHA1: string + fileSHA256: string + appName: string +} diff --git a/src/lib/scanRun.ts b/src/lib/scanRun.ts index 36ea457..9850937 100644 --- a/src/lib/scanRun.ts +++ b/src/lib/scanRun.ts @@ -4,6 +4,7 @@ import { openSync, readSync, closeSync } from 'fs' import * as scanPb from './protos/scan_pb' import { ScanClient } from './protos/scan_grpc_pb' import { AmaasScanResultObject } from './amaasScanResultObject' +import { AmaasScanResultVerbose } from './amaasScanResultVerbose' import { Logger } from './logger' import { ClientDuplexStream, Deadline } from '@grpc/grpc-js' import { getBufferHashes, getHashes } from './utils' @@ -15,7 +16,7 @@ export class ScanRun { private readonly scanClient: ScanClient private readonly deadline: number private readonly logger: Logger - private finalResult: AmaasScanResultObject + private finalResult: object private readonly tags: string[] private readonly bulk: boolean @@ -23,17 +24,17 @@ export class ScanRun { this.scanClient = scanClient this.deadline = timeout this.logger = logger - this.finalResult = Object.create(null) as AmaasScanResultObject + this.finalResult = Object.create(null) this.tags = tags ?? [] this.bulk = false } - private async streamRun (fileName: string, fileSize: number, hashes: string[], pml: boolean, feedback: boolean, buff?: Buffer): Promise { - return await new Promise((resolve, reject) => { + private async streamRun (fileName: string, fileSize: number, hashes: string[], pml: boolean, feedback: boolean, verbose: boolean, buff?: Buffer): Promise { + return await new Promise((resolve, reject) => { const _deadline: Deadline = new Date().getTime() + this.deadline * 1000 const stream = this.scanClient.run({ deadline: _deadline }) stream.on('data', (response: scanPb.S2C) => { - this.handleStreamData(response, fileName, stream, buff) + this.handleStreamData(response, fileName, verbose, stream, buff) }) stream.on('error', (err: Error) => { const error = ['Metadata key ""'].includes(String(err)) @@ -42,7 +43,7 @@ export class ScanRun { reject(error) }) stream.on('end', () => { - resolve(this.finalResult) + resolve((verbose) ? this.finalResult as AmaasScanResultVerbose : this.finalResult as AmaasScanResultObject) }) // INIT stage @@ -56,6 +57,7 @@ export class ScanRun { } initRequest.setTrendx(pml) initRequest.setSpnFeedback(feedback) + initRequest.setVerbose(verbose) this.logger.debug(`sha1: ${hashes[1]}`) this.logger.debug(`sha256: ${hashes[0]}`) initRequest.setFileSha1(`${sha1Prefix}${hashes[1]}`) @@ -68,6 +70,7 @@ export class ScanRun { private handleStreamData ( response: scanPb.S2C, fileName: string, + verbose: boolean, stream: ClientDuplexStream, buff?: Buffer ): void { @@ -131,10 +134,11 @@ export class ScanRun { name: string, size: number, pml: boolean, - feedback: boolean - ): Promise { + feedback: boolean, + verbose: boolean + ): Promise { const hashes = await getHashes(name, ['sha256', 'sha1'], 'hex') - return await this.streamRun(name, size, hashes, pml, feedback) + return await this.streamRun(name, size, hashes, pml, feedback, verbose) .then(result => { return result }) @@ -145,11 +149,12 @@ export class ScanRun { name: string, buff: Buffer, pml: boolean, - feedback: boolean - ): Promise { + feedback: boolean, + verbose: boolean + ): Promise { const size = Buffer.byteLength(buff) const hashes = await getBufferHashes(buff, ['sha256', 'sha1'], 'hex') - return await this.streamRun(name, size, hashes, pml, feedback, buff) + return await this.streamRun(name, size, hashes, pml, feedback, verbose, buff) .then(result => result) .catch(err => { throw err }) }