Skip to content

Comments

[PPSC-437] feat(output): add defense-in-depth secret masking#77

Merged
yiftach-armis merged 9 commits intomainfrom
feat/PPSC-437-secret-masking-output
Feb 19, 2026
Merged

[PPSC-437] feat(output): add defense-in-depth secret masking#77
yiftach-armis merged 9 commits intomainfrom
feat/PPSC-437-secret-masking-output

Conversation

@yiftach-armis
Copy link
Collaborator

@yiftach-armis yiftach-armis commented Feb 18, 2026

Related Issue

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Refactoring (no functional changes)

Problem

Secrets may leak through output formatters (human-readable and JSON) even if masked upstream. Defense-in-depth masking ensures secrets are redacted at the output layer before display or serialization.

Solution

  • Add maskSecretInMultiLineString() masking to human-readable output in renderFinding()
  • Implement maskFixForDisplay() to mask secrets in Fix patches and proposed fixes for human output
  • Add maskScanResultForOutput() for JSON formatter to mask secrets before serialization
  • Expand secret pattern detection from 12 to 34+ regex patterns for well-known API key prefixes (sk-, AIzaSy, SG., GitHub PATs, AWS keys, Bearer tokens, connection strings, dict literals, etc.)
  • All masking is idempotent—already-masked content remains preserved

Testing

Automated Tests

  • Unit tests added for all masking scenarios
  • Tests cover: simple secrets, multi-line snippets, already-masked content, all Fix fields (Patch, VulnerableCode, ProposedFixes, PatchFiles)
  • All tests passing locally
  • All linting checks passing

Manual Testing

Verified with test data including:

  • AWS credentials, passwords, API keys
  • Service-specific patterns (Stripe, OpenAI, SendGrid, GitHub, GitLab, Slack, MongoDB, PostgreSQL, etc.)
  • Bearer tokens and connection strings
  • Dict/JSON literals with quoted keys
  • Already-masked content is preserved

Reviewer Notes

  • Secret masking patterns use regex prefixes for high-confidence detection (reduces false positives)
  • Masking functions return new objects/copies; original data structures are never modified
  • Idempotent masking: applying mask multiple times produces same result
  • Output layer masking complements existing upstream masking—this is defense-in-depth

Checklist

  • Code follows project style guidelines
  • Pre-commit hooks pass (go fmt, go vet, golangci-lint)
  • Self-review performed
  • No new warnings generated

…n and JSON output

- Add maskSecretInMultiLineString masking to human-readable output before display
- Implement maskFixForDisplay to mask secrets in Fix patches and proposed fixes
- Add maskScanResultForOutput for JSON formatter to mask secrets before serialization
- Expand secret pattern detection with 22 regex patterns for well-known API prefixes
- Add Bearer token and dict literal detection for improved secret catching
- Comprehensive test coverage for all masking scenarios (existing content, multi-line, all fix fields)
- Defense-in-depth approach: mask at output layer even if upstream already masked
Copilot AI review requested due to automatic review settings February 18, 2026 12:07
@github-actions
Copy link

github-actions bot commented Feb 18, 2026

🛡️ Armis Security Scan Results

🟠 HIGH issues found

Severity Count
🟠 HIGH 4

Total: 4

View all 4 findings

🟠 HIGH (4)

CWE-522_armis-cli_38295677_internal/auth/auth.go_158_2_158_32 - Insecure Design (CWE-522

Location: internal/auth/auth.go:158

Insufficiently Protected Credentials): The GetRawToken method returns the JWT or legacy token exactly as stored, without adding any protection such as encryption or masking. Because this function is part of a CLI tool, an attacker who can invoke the command (or any code that calls this method) can obtain the raw credential directly. The token originates from the authentication service and is passed unchanged to the caller, with no sanitizers applied. This constitutes insufficiently protected credentials (CWE‑522), making the vulnerability exploitable through a simple call to the method, exposing the credential to any user or script that can trigger it.

Code snippet is redacted as it contains secrets.

CWEs: CWE-522: Insufficiently Protected Credentials

CWE-522_armis-cli_38295677_internal/auth/auth.go_148_3_148_28 - Insecure Design (CWE-522

Location: internal/auth/auth.go:148

Insufficiently Protected Credentials): The GetRawToken method returns the authentication token exactly as stored, without adding any protection such as encryption, masking, or access controls. The token originates from the configuration (for legacy Basic auth) or from the JWT credentials obtained during authentication, both of which are sensitive credential sources. By exposing the raw token through a public function, any code that can call this method—including scripts, other binaries, or users invoking the CLI—can retrieve the credential in clear text. This direct exposure matches the definition of CWE‑522 (Insufficiently Protected Credentials). No sanitization is performed before the token is returned, making the taint flow from source to sink reachable. Consequently, the vulnerability is a true positive.

Code snippet is redacted as it contains secrets.

CWEs: CWE-522: Insufficiently Protected Credentials

CWE-522_armis-cli_38295677_internal/auth/client.go_42_2_42_33 - Insecure Design (CWE-522

Location: internal/auth/client.go:42

Insufficiently Protected Credentials): The NewAuthClient function (line 42) allows HTTP URLs when the host is localhost. Consequently, the Authenticate method can send the provided clientSecret over an unencrypted HTTP connection to a localhost endpoint. This transmits credentials without encryption, matching CWE‑522. The clientSecret flows from the Authenticate arguments into the JSON request body and is then written to the network socket via http.Client.Do, reaching an insecure sink. No sanitizers intervene, making the taint reachable. Because the vulnerable endpoint is a network‑accessible HTTP service, the exposure level is high (≥6), leading to a high likelihood of exploitation.

CWEs: CWE-522: Insufficiently Protected Credentials

CWE-918_armis-cli_38295677_internal/httpclient/client.go_71_3_71_103 - Server-Side Request Forgery (CWE-918

Location: internal/httpclient/client.go:71

Server-Side Request Forgery (SSRF)): The function receives an http.Request from its caller and forwards it directly to c.httpClient.Do(req) without any validation or restriction of the request URL. If an attacker can influence the URL field of the request (e.g., by supplying it through user‑controlled input to the caller), the attacker can cause the client to issue arbitrary HTTP requests to internal or external services. No sanitization or whitelist checks are performed before the request is sent, so the tainted URL reaches the network request sink, making the SSRF vulnerability exploitable. The code resides in a client library, not an exposed HTTP endpoint, resulting in internal‑service exposure. This confirms a true positive for CWE‑918.

CWEs: CWE-918: Server-Side Request Forgery (SSRF)

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements defense-in-depth secret masking at the output layer by expanding secret detection patterns from 12 to 34+ regex patterns and applying masking to both JSON and human-readable output formatters before display or serialization. The goal is to prevent secret leakage through output even if upstream masking was bypassed or incomplete.

Changes:

  • Expanded secret detection regex patterns to cover 34+ well-known API key formats (AWS, GitHub, Stripe, OpenAI, SendGrid, connection strings, etc.)
  • Added MaskSecretInMultiLineString() to mask secrets in code snippets before human-readable rendering
  • Added maskFixForDisplay() to mask secrets in Fix patches and proposed fixes for human output
  • Added maskScanResultForOutput() to mask secrets before JSON serialization
  • Comprehensive test coverage for new secret patterns and output masking

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
internal/util/mask.go Added 22 new standalone secret prefix patterns (lines 18-39) and 3 new context-aware patterns (lines 53, 74, 79) for broader secret detection
internal/util/mask_test.go Added 407 lines of tests covering well-known prefixes, service-specific patterns, dict literals, Bearer tokens, token prefixes, and bare keyword patterns
internal/output/json.go Added maskScanResultForOutput() and maskFindingSecrets() to mask secrets in all code-containing fields before JSON encoding
internal/output/json_test.go Added 144 lines of tests for JSON masking including multi-line secrets, Fix fields, and nil/empty handling
internal/output/human.go Added maskFixForDisplay() and integrated secret masking into renderFinding() for human-readable output
internal/output/human_test.go Added tests for secret masking in human output including AWS keys, passwords, and masked content preservation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…fix CI failures

- Fix well-known prefix patterns to preserve surrounding quotes when masking
  by using submatches[1] replacement instead of masking entire match
- Fix Slack webhook test URL to use correct hooks.slack.com domain
- Add #nosec G101 comments to test fixtures (false positive warnings)
- Replace WriteString(fmt.Sprintf(...)) with fmt.Fprintf for efficiency
- Add regression test for quote preservation
…ing QF1012

- Add #nosec G101 to Slack webhook and MongoDB test cases
- Add #nosec G101 to quote preservation regression tests
- Fix remaining WriteString(fmt.Sprintf(...)) in formatProposedSnippet
Copilot AI review requested due to automatic review settings February 18, 2026 12:34
@github-actions
Copy link

github-actions bot commented Feb 18, 2026

Test Coverage Report

total: (statements) 80.2%

Coverage by function
github.com/ArmisSecurity/armis-cli/cmd/armis-cli/main.go:18:			main					0.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:67:			WithHTTPClient				100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:76:			WithAllowLocalURLs			100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:88:			NewClient				100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:138:			IsDebug					100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:155:			setAuthHeader				77.8%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:189:			StartIngest				73.6%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:286:			GetIngestStatus				82.6%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:327:			WaitForIngest				84.6%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:378:			FetchNormalizedResults			74.2%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:433:			FetchAllNormalizedResults		91.7%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:458:			GetScanResult				68.4%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:493:			WaitForScan				90.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:514:			formatBytes				100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:536:			FetchArtifactScanResults		75.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:591:			ValidatePresignedURL			100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:627:			DownloadFromPresignedURL		84.2%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:52:			NewAuthProvider				95.2%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:98:			GetAuthorizationHeader			100.0%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:118:			GetTenantID				85.7%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:133:			IsLegacy				100.0%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:146:			GetRawToken				85.7%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:163:			exchangeCredentials			83.3%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:193:			refreshIfNeeded				100.0%
github.com/ArmisSecurity/armis-cli/internal/auth/auth.go:222:			parseJWTClaims				93.3%
github.com/ArmisSecurity/armis-cli/internal/auth/client.go:31:			NewAuthClient				100.0%
github.com/ArmisSecurity/armis-cli/internal/auth/client.go:72:			Authenticate				71.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:54:			InitColors				73.3%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:82:			ColorsEnabled				100.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:88:			ColorsForced				100.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:92:			enableColors				100.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:99:			disableColors				100.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:114:			parseErrorMessage			92.9%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:145:			PrintError				100.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:158:			PrintErrorf				0.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:164:			PrintWarning				100.0%
github.com/ArmisSecurity/armis-cli/internal/cli/color.go:169:			PrintWarningf				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/auth.go:35:			init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/auth.go:41:			runAuth					93.8%
github.com/ArmisSecurity/armis-cli/internal/cmd/context.go:24:			NewSignalContext			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/context.go:33:			handleScanError				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/help.go:30:			SetupHelp				91.7%
github.com/ArmisSecurity/armis-cli/internal/cmd/help.go:58:			styledUsageTemplate			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/help.go:101:			defaultUsageTemplate			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/help.go:108:			initColorsForHelp			35.3%
github.com/ArmisSecurity/armis-cli/internal/cmd/help.go:149:			styleHelpOutput				83.3%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:119:			SetVersion				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:127:			Execute					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:131:			init					76.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:174:			PrintUpdateNotification			50.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:191:			getEnvOrDefault				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:198:			getEnvOrDefaultInt			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:208:			getAPIBaseURL				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:221:			getAuthProvider				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:232:			getPageLimit				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:239:			validatePageLimit			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:249:			validateFailOn				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:267:			getFailOn				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan.go:83:			init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan_image.go:145:		init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan_repo.go:135:		init					100.0%
github.com/ArmisSecurity/armis-cli/internal/httpclient/client.go:30:		NewClient				100.0%
github.com/ArmisSecurity/armis-cli/internal/httpclient/client.go:56:		Do					85.3%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:54:			wrapText				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:77:			wrapLine				91.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:115:		formatRecommendations			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:185:		wrapTextWithFirstLinePrefix		90.9%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:224:		write					66.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:255:		Write					89.5%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:285:		Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:290:		FormatWithOptions			84.4%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:360:		SyncColors				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:364:		sortFindingsBySeverity			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:375:		loadSnippetFromFile			69.4%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:487:		formatCodeSnippetWithFrame		91.1%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:580:		truncatePlainLine			0.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:592:		highlightColumns			93.5%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:637:		scanDuration				89.5%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:670:		pluralize				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:679:		renderBriefStatus			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:719:		renderSummaryDashboard			56.4%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:800:		renderFindings				88.9%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:829:		renderFinding				69.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:919:		renderGroupedFindings			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:943:		groupFindings				96.8%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1000:		severityRank				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1007:		isGitRepo				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1014:		getGitBlame				38.1%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1051:		parseGitBlame				95.2%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1087:		maskEmail				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1110:		getTopLevelDomain			75.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1122:		getHumanDisplayTitle			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1136:		wrapTitle				93.9%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1195:		maskFixForDisplay			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1230:		formatFixSection			0.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1295:		formatProposedSnippet			0.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1378:		limitHunkContext			64.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1454:		parseDiffHunk				91.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1476:		parseDiffLines				94.6%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1567:		findInlineChanges			73.5%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1638:		computeLCS				92.3%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1690:		buildTokenPositions			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1706:		tokenizeLine				92.9%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1734:		isWordChar				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1741:		formatDiffWithColorsStyled		77.1%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1815:		extractDiffFilename			80.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1837:		formatDiffHunkLine			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1857:		formatDiffContextLine			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1868:		formatDiffRemoveLine			86.4%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1909:		formatDiffAddLine			86.4%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1951:		applyInlineHighlights			81.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1993:		truncateDiffLine			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2000:		truncateDiffLineWithFlag		66.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2014:		adjustHighlightSpans			83.3%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2036:		groupDiffHunks				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2067:		collectRenderOps			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2110:		renderChangeBlock			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2169:		formatDiffHunkSeparator			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2184:		formatValidationSection			0.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:2241:		getExposureDescription			0.0%
github.com/ArmisSecurity/armis-cli/internal/output/icons.go:24:			GetConfidenceIcon			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:15:			Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:24:			FormatWithOptions			66.7%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:32:			formatWithDebug				0.0%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:58:			maskScanResultForOutput			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:78:			maskFindingSecrets			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:48:			Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:55:			FormatWithOptions			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:63:			formatWithSeverities			83.3%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:88:			isFailureSeverity			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:98:			convertToJUnitCasesWithSeverities	91.7%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:130:		countFailuresWithSeverities		100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:34:		GetFormatter				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:50:		ShouldFail				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:66:		ExitIfNeeded				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:159:		stripMarkdown				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:170:		Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:197:		buildRules				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:261:		convertToSarifResults			88.5%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:351:		buildMessageText			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:358:		severityToSarifLevel			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:377:		severityToSecurityScore			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:395:		generateHelpURI				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:422:		convertFixToSarif			90.5%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:539:		FormatWithOptions			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/styles.go:138:		DefaultStyles				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/styles.go:276:		NoColorStyles				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/styles.go:353:		GetStyles				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/styles.go:361:		SyncStylesWithColorMode			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/styles.go:386:		GetSeverityText				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/styles.go:414:		TerminalWidth				33.3%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:21:		GetLexer				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:32:		GetChromaStyle				80.0%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:45:		HighlightCode				81.2%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:79:		HighlightLine				75.0%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:88:		getTerminalFormatter			60.0%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:103:		HighlightLineWithBackground		87.5%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:126:		getBackgroundANSI			58.3%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:158:		rgbToANSI256				0.0%
github.com/ArmisSecurity/armis-cli/internal/output/syntax.go:171:		parseHexColor				76.9%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:33:		IsCI					100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:61:		isTerminalWriter			100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:69:		NewReader				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:84:		NewWriter				50.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:118:		NewSpinner				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:126:		NewSpinnerWithTimeout			100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:142:		NewSpinnerWithContext			100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:150:		SetWriter				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:159:		Start					89.8%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:268:		Stop					100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:303:		Update					100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:310:		GetElapsed				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:317:		formatDuration				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/finding_type.go:9:		DeriveFindingType			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:44:		NewScanner				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:58:		WithPollInterval			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:64:		WithSBOMVEXOptions			0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:70:		ScanImage				0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:103:		ScanTarball				77.1%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:194:		exportImage				0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:235:		isDockerAvailable			42.9%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:249:		getDockerCommand			75.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:258:		validateDockerCommand			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:265:		buildScanResult				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:292:		convertNormalizedFindings		85.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:416:		shouldFilterByExploitability		100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:435:		cleanDescription			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:454:		isEmptyFinding				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:469:		generateFindingTitle			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/validate.go:11:		validateImageName			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/mask.go:21:			MaskFixSecrets				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/files.go:26:		ParseFileList				87.5%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/files.go:41:		addFile					87.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/files.go:93:		Files					100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/files.go:98:		RepoRoot				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/files.go:103:		ValidateExistence			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/ignore.go:18:		LoadIgnorePatterns			75.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/ignore.go:52:		loadIgnoreFile				89.5%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/ignore.go:86:		Match					100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/ignore.go:98:		shouldSkipDir				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:43:		NewScanner				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:57:		WithPollInterval			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:63:		WithIncludeFiles			0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:69:		WithSBOMVEXOptions			0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:75:		Scan					70.9%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:240:		tarGzDirectory				71.8%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:323:		isPathContained				75.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:332:		tarGzFiles				78.6%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:419:		calculateFilesSize			0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:440:		calculateDirSize			81.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:480:		shouldSkip				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:511:		isTestFile				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:555:		buildScanResult				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:582:		convertNormalizedFindings		73.3%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:706:		shouldFilterByExploitability		100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:725:		cleanDescription			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:746:		generateFindingTitle			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:750:		isEmptyFinding				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/sbom_vex.go:38:		NewSBOMVEXDownloader			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/sbom_vex.go:50:		Download				85.2%
github.com/ArmisSecurity/armis-cli/internal/scan/sbom_vex.go:102:		downloadAndSave				77.8%
github.com/ArmisSecurity/armis-cli/internal/scan/status.go:16:			FormatScanStatus			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/status.go:35:			FormatElapsed				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/status.go:48:			MapSeverity				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/testhelpers/findings.go:9:	CreateNormalizedFinding			0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/testhelpers/findings.go:14:	CreateNormalizedFindingWithLabels	0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/testhelpers/findings.go:19:	CreateNormalizedFindingFull		0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/title.go:14:			GenerateFindingTitle			0.0%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:66:		NewChecker				100.0%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:81:		CheckInBackground			100.0%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:101:		check					85.7%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:144:		fetchLatestVersion			89.5%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:177:		getCacheFilePath			44.4%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:195:		readCache				84.6%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:218:		writeCache				76.9%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:241:		IsNewer					100.0%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:264:		parseVersion				100.0%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:287:		FormatNotification			100.0%
github.com/ArmisSecurity/armis-cli/internal/update/update.go:305:		getUpdateCommand			40.0%
github.com/ArmisSecurity/armis-cli/internal/util/format.go:7:			FormatCategory				100.0%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:109:			MaskSecretInLine			86.4%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:163:			maskValue				83.3%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:189:			MaskSecretInLines			100.0%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:203:			MaskSecretInMultiLineString		100.0%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:217:			MaskSecretsInStringMap			100.0%
github.com/ArmisSecurity/armis-cli/internal/util/path.go:13:			SanitizePath				90.9%
github.com/ArmisSecurity/armis-cli/internal/util/path.go:51:			SafeJoinPath				87.5%
github.com/ArmisSecurity/armis-cli/test/sample-repo/src/main.go:6:		main					0.0%
total:										(statements)				80.2%

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix idempotency comment wording (already-masked content remains masked)
- Add documentation about intentional pattern ordering for sk- patterns
- Add VulnerableCode and PatchFiles masking to maskFixForDisplay for
  consistency with JSON formatter (defense-in-depth)
- Use minimum lengths instead of exact for SendGrid/Google API patterns
- Fix sk- pattern to make second delimiter optional (sk-proj vs sk-proj-)
- Fix private key regex to use (?s) flag for multiline matching
- Add TestMaskSecretInLine_PrivateKeys for RSA/EC/DSA key coverage
- Add benchmark tests for performance measurement
- Add #nosec G101 to RSA private key test fixture
- Convert WriteString(fmt.Sprintf(...)) to fmt.Fprintf for efficiency
Copilot AI review requested due to automatic review settings February 18, 2026 12:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…ings

- Add maxLineLength (10KB) limit in MaskSecretInLine to prevent ReDoS
- Add maxTokenizeLength (10KB) limit in tokenizeLine to prevent unbounded memory
- Update DB connection string patterns to require credentials (user:pass@)
- Split service-specific tokens regex into 8 smaller patterns for performance
- Change strings.Replace to strings.ReplaceAll for complete secret masking
- Use specific char class for PEM private keys to prevent backtracking
- Use local copy in renderFinding to avoid struct mutation
- Add documentation comments for pattern limitations (quotes, false positives)
- Add test for maxLineLength ReDoS protection
- G117: ClientSecret field names in auth config structs (not secret values)
- G704: SSRF false positives for validated/constant URLs (auth, API, GitHub)
- G705: XSS false positives for stderr output (not HTML rendering)
- G115: Integer overflow for uintptr->int conversion (safe on all platforms)
- G204: Command injection false positives for validated image names
Copilot AI review requested due to automatic review settings February 18, 2026 13:30
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

- Extract shared maskFixForDisplay function in json.go to avoid
  duplicating masking logic across formatters
- Fix private key regex pattern to use specific char class [\n\r]
  instead of \s to limit potential backtracking
- Add worst-case benchmark for regex pattern performance testing
- Add tests for nested/escaped quote edge cases in secret masking
}

resp, err = c.httpClient.Do(req)
resp, err = c.httpClient.Do(req) //nolint:gosec // G704: URL is from API client, validated before use

Check failure

Code scanning / Armis Security Scanner

Server-Side Request Forgery (CWE-918: Server-Side Request Forgery (SSRF)) High

Server-Side Request Forgery (CWE-918: Server-Side Request Forgery (SSRF)): The function receives an *http.Request* from its caller and forwards it directly to c.httpClient.Do(req) without any validation or restriction of the request URL. If an attacker can influence the URL field of the request (e.g., by supplying it through user‑controlled input to the caller), the attacker can cause the client to issue arbitrary HTTP requests to internal or external services. No sanitization or whitelist checks are performed before the request is sent, so the tainted URL reaches the network request sink, making the SSRF vulnerability exploitable. The code resides in a client library, not an exposed HTTP endpoint, resulting in internal‑service exposure. This confirms a true positive for CWE‑918.
gosec v2.10's taint analysis flags CLI debug output as XSS risk (G705).
This is a false positive - stderr output in a CLI tool has no browser
context for XSS attacks. Add #nosec G705 comments with explanation.
Copilot AI review requested due to automatic review settings February 19, 2026 10:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 136 to +139
masked := maskValue(value)
return strings.Replace(match, value, masked, 1)
// Use ReplaceAll to mask all occurrences of the secret value within the match,
// though typically each match contains the value only once
return strings.ReplaceAll(match, value, masked)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the single capture group handling, using strings.ReplaceAll here could cause issues if the secret value contains substrings that repeat within the match. While less likely with the 3-capture-group patterns (which typically have distinct prefixes), it's still safer to use strings.Replace(match, value, masked, 1) to avoid edge cases where the secret value itself contains repeated patterns.

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +151
// Use ReplaceAll to mask all occurrences of the secret value within the match
return strings.ReplaceAll(match, value, masked)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for handling single capture groups (lines 142-151) uses strings.ReplaceAll to replace the captured value within the match. However, this could cause incorrect replacements if the captured value appears multiple times in the match string, including in the prefix or surrounding quotes. For example, if the secret value itself contains a substring that matches part of the quote or prefix pattern, multiple replacements could corrupt the output.

Consider using strings.Replace(match, value, masked, 1) instead of strings.ReplaceAll to replace only the first occurrence, or use a more targeted approach that replaces only the captured group position.

Suggested change
// Use ReplaceAll to mask all occurrences of the secret value within the match
return strings.ReplaceAll(match, value, masked)
// Replace only the first occurrence of the secret value within the match to avoid
// corrupting prefixes or surrounding structure if the value repeats.
return strings.Replace(match, value, masked, 1)

Copilot uses AI. Check for mistakes.
@yiftach-armis yiftach-armis merged commit 5b2706b into main Feb 19, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants