Skip to content

Conversation

@yiftach-armis
Copy link
Collaborator

Description

Add explicit symlink detection and skipping in the tarball creation process to prevent security risks and extraction errors. Symlinks are now logged with a warning to stderr when encountered.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Test coverage improvement

Testing

  • Unit tests pass locally
  • New test case added for symlink handling
  • All existing tests pass

Checklist

  • My code follows the project's code style
  • I have performed a self-review of my code
  • I have added tests that prove my fix is effective
  • New and existing unit tests pass locally with my changes

Additional Notes

Symlinks are now skipped during tarball creation to avoid:

  • Security risks from symlinks pointing outside the repository
  • Issues with broken symlinks causing errors during extraction
  • Potential infinite loops from circular symlinks

@github-actions
Copy link

github-actions bot commented Jan 12, 2026

Test Coverage Report

total: (statements) 77.0%

Coverage by function
github.com/ArmisSecurity/armis-cli/cmd/armis-cli/main.go:16:			main					0.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:36:			WithHTTPClient				100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:44:			NewClient				91.7%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:88:			IsDebug					100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:93:			StartIngest				78.4%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:160:			GetIngestStatus				84.2%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:193:			WaitForIngest				0.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:235:			FetchNormalizedResults			78.6%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:282:			FetchAllNormalizedResults		91.7%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:307:			GetScanResult				66.7%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:335:			WaitForScan				0.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:356:			formatBytes				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:41:			SetVersion				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:49:			Execute					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:53:			init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:65:			getEnvOrDefault				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:72:			getEnvOrDefaultInt			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:82:			getAPIBaseURL				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:89:			getToken				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:96:			getTenantID				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:103:			getPageLimit				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:110:			validatePageLimit			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:120:			validateFailOn				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/root.go:134:			getFailOn				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan.go:21:			init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan_image.go:96:		init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan_repo.go:74:		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					87.5%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:31:			write					66.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:62:			Write					90.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:93:			Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:98:			FormatWithOptions			96.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:136:		getSeverityIcon				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:153:		getSeverityColor			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:182:		init					50.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:189:		disableColors				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:201:		sortFindingsBySeverity			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:230:		loadSnippetFromFile			75.5%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:312:		formatCodeSnippet			0.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:349:		highlightColumns			0.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:385:		detectLanguage				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:683:		scanDuration				26.3%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:715:		renderSummaryDashboard			61.2%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:804:		renderFindings				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:819:		renderFinding				62.5%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:876:		renderGroupedFindings			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:897:		groupFindings				96.6%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:952:		severityRank				75.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:966:		isGitRepo				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:973:		getGitBlame				0.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1009:		parseGitBlame				85.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1045:		maskEmail				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1068:		getTopLevelDomain			75.0%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:14:			Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:21:			FormatWithOptions			66.7%
github.com/ArmisSecurity/armis-cli/internal/output/json.go:29:			formatWithDebug				0.0%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:43:			Format					83.3%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:67:			convertToJUnitCases			91.7%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:99:			countFailures				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/junit.go:110:		FormatWithOptions			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:25:		GetFormatter				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:41:		ShouldFail				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:57:		ExitIfNeeded				0.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:64:			Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:87:			convertToSarifResults			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:124:		severityToSarifLevel			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:138:		FormatWithOptions			100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:25:		IsCI					100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:47:		NewReader				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:62:		NewWriter				50.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:96:		NewSpinner				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:104:		NewSpinnerWithTimeout			100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:120:		NewSpinnerWithContext			100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:128:		SetWriter				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:137:		Start					93.8%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:225:		Stop					100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:260:		UpdateMessage				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:267:		Update					100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:274:		GetElapsed				100.0%
github.com/ArmisSecurity/armis-cli/internal/progress/progress.go:281:		formatDuration				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:40:		NewScanner				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:54:		WithPollInterval			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:60:		ScanImage				0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:93:		ScanTarball				93.1%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:141:		exportImage				0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:174:		isDockerAvailable			42.9%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:188:		getDockerCommand			75.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:197:		validateDockerCommand			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:204:		buildScanResult				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:231:		convertNormalizedFindings		93.9%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:325:		shouldFilterByExploitability		100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:344:		cleanDescription			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:363:		isEmptyFinding				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:376:		mapSeverity				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:391:		formatElapsed				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/validate.go:11:		validateImageName			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:38:		NewScanner				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:52:		WithPollInterval			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:58:		Scan					84.6%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:145:		tarGzDirectory				71.4%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:224:		calculateDirSize			81.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:263:		shouldSkip				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:294:		isTestFile				88.9%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:337:		buildScanResult				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:364:		convertNormalizedFindings		90.2%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:462:		shouldFilterByExploitability		100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:481:		cleanDescription			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:500:		isEmptyFinding				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:513:		mapSeverity				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:528:		formatElapsed				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/util/mask.go:43:			MaskSecretInLine			81.2%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:79:			maskValue				60.0%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:94:			MaskSecretInLines			100.0%
github.com/ArmisSecurity/armis-cli/internal/util/path.go:12:			SanitizePath				90.9%
github.com/ArmisSecurity/armis-cli/internal/util/path.go:41:			SafeJoinPath				84.2%
github.com/ArmisSecurity/armis-cli/test/sample-repo/src/main.go:6:		main					0.0%
total:										(statements)				77.0%

Skip symlinks during tarball walk to avoid security risks (symlinks pointing outside repo) and issues with broken symlinks or loops. Log a warning to stderr for each skipped symlink.
@yiftach-armis yiftach-armis force-pushed the ci/check-checkout-update branch from ea6d163 to 03449a7 Compare January 12, 2026 13:37
Add comprehensive test coverage for symlink scenarios:
- Symlinks pointing to directories
- Broken symlinks pointing to non-existent targets

These tests ensure the symlink skip logic handles all edge cases correctly.
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 pull request adds explicit symlink detection and skipping during tarball creation to prevent security risks from symlinks pointing outside the repository, avoid errors from broken symlinks, and prevent potential infinite loops from circular symlinks. Symlinks are now logged with a warning to stderr when encountered.

Changes:

  • Added symlink detection check in tarGzDirectory to skip symlinks during tarball creation
  • Added warning message to stderr when symlinks are skipped
  • Added comprehensive test coverage for file symlinks, directory symlinks, and broken symlinks

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
internal/scan/repo/repo.go Added symlink detection logic to skip symlinks during tarball creation with warning message
internal/scan/repo/repo_test.go Added three comprehensive test cases covering file symlinks, directory symlinks, and broken symlinks

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

Comment on lines +184 to +189
// Skip symlinks to avoid security risks (symlinks pointing outside repo)
// and potential issues (broken symlinks, loops)
if info.Mode()&os.ModeSymlink != 0 {
fmt.Fprintf(os.Stderr, "Warning: skipping symlink %s\n", relPath)
return nil
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

The calculateDirSize function should also skip symlinks for consistency with tarGzDirectory. Currently, tarGzDirectory explicitly skips symlinks (lines 184-189), but calculateDirSize does not. While filepath.Walk doesn't follow symlinks by default, calculateDirSize may still count the symlink's metadata size, which could lead to a mismatch between the calculated size and the actual tarball size. Add the same symlink check after line 248 in calculateDirSize to ensure consistent behavior.

Copilot uses AI. Check for mistakes.
The calculateDirSize function now skips symlinks to match the behavior
of tarGzDirectory. This ensures the calculated size matches the actual
tarball size and maintains consistent symlink handling throughout the
codebase.

Addresses review comment from @copilot-pull-request-reviewer.
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