diff --git a/src/lib/api-types.ts b/src/lib/api-types.ts index 3ae4ddc..af341d6 100644 --- a/src/lib/api-types.ts +++ b/src/lib/api-types.ts @@ -16,7 +16,17 @@ export type Validity = | "invalid" | "valid"; -export interface Incident { +type IncidentVaultProperties = { + secret_vaulted: false; +} | { + secret_vaulted: true; + vault_type: string; + vault_name: string; + vault_path: string; + vault_path_count: number; +} + +export type Incident = { type: string; occurrences: Occurrence[]; validity: Validity; @@ -24,8 +34,7 @@ export interface Incident { known_secret: boolean; incident_url: string; total_occurrences: number; - secret_vaulted: boolean; -} +} & IncidentVaultProperties export interface EntityWithIncidents { incidents: Incident[]; diff --git a/src/lib/ggshield-results-parser.ts b/src/lib/ggshield-results-parser.ts index 840b7fa..30aa53c 100644 --- a/src/lib/ggshield-results-parser.ts +++ b/src/lib/ggshield-results-parser.ts @@ -13,6 +13,7 @@ import { Occurrence, Validity, } from "./api-types"; +import { pluralize } from "../utils"; const validityDisplayName: Record = { unknown: "Unknown", @@ -45,6 +46,7 @@ function filterUriOccurrences(occurrences: Occurrence[]): Occurrence[] { * @param results ggshield scan results * @returns incidents diagnostics */ + export function parseGGShieldResults( results: GGShieldScanResults, ): Diagnostic[] { @@ -63,6 +65,22 @@ export function parseGGShieldResults( new Position(occurrence.line_start - 1, occurrence.index_start), new Position(occurrence.line_end - 1, occurrence.index_end), ); + + let vaultInfo = ""; + + if (incident.secret_vaulted) { + if (incident.vault_path_count !== null) { + vaultInfo += `Secret found in vault: YES (${incident.vault_path_count} ${pluralize(incident.vault_path_count, "location")}) +├─ Vault Type: ${incident.vault_type} +├─ Vault Name: ${incident.vault_name} +└─ Secret Path: ${incident.vault_path}`; + } else { + vaultInfo += "Secret found in vault: YES"; + } + } else { + vaultInfo += "Secret found in vault: NO"; + } + let diagnostic = new Diagnostic( range, `ggshield: ${occurrence.type} @@ -73,11 +91,11 @@ Known by GitGuardian dashboard: ${incident.known_secret ? "YES" : "NO"} Total occurrences: ${incident.total_occurrences} Incident URL: ${incident.incident_url || "N/A"} Secret SHA: ${incident.ignore_sha} -Secret in Secrets Manager: ${incident.secret_vaulted ? "YES" : "NO"}`, +${vaultInfo}`, DiagnosticSeverity.Warning, ); - diagnostic.source = "gitguardian"; + diagnostic.source = "\ngitguardian"; diagnostics.push(diagnostic); }, ); diff --git a/src/test/constants.ts b/src/test/constants.ts index dc7f8d0..6ce27f2 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -85,3 +85,48 @@ export const scanResultsWithUriIncident = `{ "total_occurrences": 1, "secrets_engine_version": "2.126.0" }`; + +export const scanResultsVaulted = `{ + "id":"test.py", + "type":"path_scan", + "entities_with_incidents":[ + { + "mode":"FILE", + "filename":"test.py", + "incidents":[ + { + "policy":"Secrets detection", + "occurrences":[ + { + "match":"DDACC73DdB04********************************************057c78317C39", + "type":"apikey", + "line_start":4, + "line_end":4, + "index_start":11, + "index_end":79, + "pre_line_start":4, + "pre_line_end":4 + } + ], + "type":"Generic High Entropy Secret", + "validity":"no_checker", + "ignore_sha":"38353eb1a2aac5b24f39ed67912234d4b4a2e23976d504a88b28137ed2b9185e", + "total_occurrences":2, + "incident_url":"", + "known_secret":false, + "secret_vaulted": true, + "vault_type": "AWS Secrets Manager", + "vault_name": "463175827647/us-west-2", + "vault_path": "arn:aws:secretsmanager:us-west-2:463175827647:secret:xav-test-svef2q:pwd", + "vault_path_count": 2 + + } + ], + "total_incidents":1, + "total_occurrences":1 + } + ], + "total_incidents":1, + "total_occurrences":1, + "secrets_engine_version":"2.96.0" + }`; \ No newline at end of file diff --git a/src/test/suite/results-parser.test.ts b/src/test/suite/results-parser.test.ts index b2d532c..960656b 100644 --- a/src/test/suite/results-parser.test.ts +++ b/src/test/suite/results-parser.test.ts @@ -4,6 +4,7 @@ import { parseGGShieldResults } from "../../lib/ggshield-results-parser"; import { DiagnosticSeverity } from "vscode"; import { scanResultsNoIncident, + scanResultsVaulted, scanResultsWithIncident, scanResultsWithUriIncident, } from "../constants"; @@ -17,7 +18,7 @@ suite("parseGGShieldResults", () => { const diagnostic = diagnostics[0]; assert.ok(diagnostic.message.includes("apikey")); assert.ok(diagnostic.message.includes("Generic High Entropy Secret")); - assert.ok(diagnostic.message.includes("Secret in Secrets Manager: NO")); + assert.ok(diagnostic.message.includes("Secret found in vault: NO")); assert.strictEqual(diagnostic.range.start.line, 3); assert.strictEqual(diagnostic.range.start.character, 11); assert.strictEqual(diagnostic.range.end.line, 3); @@ -25,6 +26,17 @@ suite("parseGGShieldResults", () => { assert.strictEqual(diagnostic.severity, DiagnosticSeverity.Warning); }); + test("Should parse vault information", () => { + const diagnostics = parseGGShieldResults( + JSON.parse(scanResultsVaulted), + ); + const diagnostic = diagnostics[0]; + assert.ok(diagnostic.message.includes("Secret found in vault: YES")); + assert.ok(diagnostic.message.includes("├─ Vault Type: AWS Secrets Manager")); + assert.ok(diagnostic.message.includes("├─ Vault Name: 463175827647/us-west-2")); + assert.ok(diagnostic.message.includes("└─ Secret Path: arn:aws:secretsmanager:us-west-2:463175827647:secret:xav-test-svef2q:pwd")); + }); + test("Should return an empty array if there are no incidents", () => { const diagnostics = parseGGShieldResults(JSON.parse(scanResultsNoIncident)); assert.strictEqual(diagnostics.length, 0); diff --git a/src/utils.ts b/src/utils.ts index 1f2978b..288fcb8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -29,3 +29,7 @@ export function getCurrentFile(): string { return ""; } } + +export function pluralize(num: number, word: string): string { + return `${[1, -1].includes(num) ? word : word + "s"}` +}