Skip to content

Conversation

@yiftach-armis
Copy link
Collaborator

Description

Implement --include-files CLI flag to enable targeted file scanning for specific files instead of entire repositories. This allows PR workflows to scan only changed files, improving scan efficiency.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Test coverage improvement

Changes

  • Add --include-files flag accepting comma-separated file paths
  • Implement FileList struct with path validation and security checks
  • Add tarGzFiles() method to create tarballs with only specified files
  • Create pr-security-scan.yml workflow for PR delta scans using tj-actions/changed-files
  • Add include-files input to reusable-security-scan.yml workflow
  • Include 5 new unit tests validating path traversal prevention

Testing

  • Unit tests pass locally (all repo scan tests passing)
  • Integration test completed successfully (scanned with --include-files flag)
  • Build succeeds with no warnings

Checklist

  • Code follows project style
  • Self-review completed
  • Path traversal security validated with unit tests
  • New unit tests added and passing
  • No new warnings generated

@github-actions
Copy link

github-actions bot commented Jan 18, 2026

🛡️ Armis Security Scan Results

✅ No issues

Severity Count

Total: 0

View full results

No security issues found.

@github-actions
Copy link

github-actions bot commented Jan 18, 2026

Test Coverage Report

total: (statements) 74.7%

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:35:			WithHTTPClient				100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:48:			NewClient				100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:98:			IsDebug					100.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:103:			StartIngest				78.4%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:170:			GetIngestStatus				84.2%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:203:			WaitForIngest				0.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:245:			FetchNormalizedResults			78.6%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:292:			FetchAllNormalizedResults		91.7%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:317:			GetScanResult				66.7%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:345:			WaitForScan				0.0%
github.com/ArmisSecurity/armis-cli/internal/api/client.go:366:			formatBytes				100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/context.go:14:			NewSignalContext			100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/context.go:21:			handleScanError				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:22:			init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan_image.go:100:		init					100.0%
github.com/ArmisSecurity/armis-cli/internal/cmd/scan_repo.go:95:		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: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:1010:		parseGitBlame				85.7%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1046:		maskEmail				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/human.go:1069:		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:112:		FormatWithOptions			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:32:		GetFormatter				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:48:		ShouldFail				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/output.go:64:		ExitIfNeeded				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:96:			Format					100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:123:		buildRules				100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:152:		convertToSarifResults			82.4%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:212:		severityToSarifLevel			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:231:		severityToSecurityScore			100.0%
github.com/ArmisSecurity/armis-cli/internal/output/sarif.go:249:		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					89.6%
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/finding_type.go:9:		DeriveFindingType			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:41:		NewScanner				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:55:		WithPollInterval			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:61:		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		89.1%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:323:		shouldFilterByExploitability		100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:342:		cleanDescription			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:361:		isEmptyFinding				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:374:		mapSeverity				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/image/image.go:389:		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/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:40:		NewScanner				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:54:		WithPollInterval			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:60:		WithIncludeFiles			0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:66:		Scan					73.7%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:201:		tarGzDirectory				71.8%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:281:		isPathContained				0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:290:		tarGzFiles				0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:376:		calculateFilesSize			0.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:397:		calculateDirSize			81.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:436:		shouldSkip				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:467:		isTestFile				88.9%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:510:		buildScanResult				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:537:		convertNormalizedFindings		89.1%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:629:		shouldFilterByExploitability		100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:648:		cleanDescription			100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:667:		isEmptyFinding				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:680:		mapSeverity				100.0%
github.com/ArmisSecurity/armis-cli/internal/scan/repo/repo.go:695:		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:54:			MaskSecretInLine			81.2%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:93:			maskValue				83.3%
github.com/ArmisSecurity/armis-cli/internal/util/mask.go:119:			MaskSecretInLines			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)				74.7%

…lows

- Implement --include-files CLI flag to scan specific files instead of entire directory
- Add FileList struct with path validation and security checks (path traversal prevention)
- Add tarGzFiles method to create tarballs with only specified files
- Add include-files input to reusable-security-scan.yml workflow
- Create pr-security-scan.yml workflow for scanning only changed files in PRs
- Include comprehensive unit tests for file path handling and validation
- Enables efficient PR security scanning by targeting only modified files

Fixes #N/A
The test TestParseFileListAbsolutePathOutsideRepo was using Unix-style
paths (/etc/passwd) which are not recognized as absolute paths on Windows.
Updated to use real temp directories which work on all platforms.
@yiftach-armis yiftach-armis force-pushed the feature/scan-changed-files branch from 0f3911c to d25446e Compare January 18, 2026 10:48
- Change reusable workflow to use local action (`./) instead of @main
  so the PR can test its own changes
- Refactor action.yml to use bash arrays instead of string concatenation
  with eval, preventing potential command injection
Add build-from-source option to action.yml that builds the CLI from
source instead of downloading the released version. This allows PRs
that add new CLI flags (like --include-files) to be tested before
the feature is released.

The reusable workflow defaults to build-from-source=true since it's
used for self-testing in this repository.
Resolved conflict in action.yml by keeping security improvements
from main (env vars for command injection prevention) while adding
include-files support from feature branch.
…ents

- Add additional SARIF properties (type, codeSnippet, CVEs, CWEs, package, version, fixVersion)
- Enhance PR comment to show detailed findings grouped by severity with expandable sections
- Add resource leak prevention with pipe close in Scan function
- Simplify file handling in tarGzFiles function with deferred close
Using defer inside a loop doesn't close files until the function returns,
which can exhaust file descriptors when processing many files. This fix
closes each file immediately after use with proper error handling.
- Replace curl|bash pattern with direct binary download and checksum
  verification in action.yml. This addresses the insecure pipe-to-shell
  anti-pattern flagged by security scanners.

- Exclude test files (*_test.go) from PR security scans to avoid false
  positives from intentional path traversal test data.
- Add MaxFiles limit (1000) to prevent resource exhaustion from large file lists
- Add isPathContained helper for defense-in-depth path validation in tarGzFiles and calculateFilesSize
- Always apply MaskSecretInLine to CodeSnippet in SARIF output
- Document TOCTOU handling as acceptable (files disappearing are handled gracefully)
- Improve pipe close comment explaining why error is ignored
- Add tests for MaxFiles limit validation
- Add security documentation for path traversal protection in scan_repo.go
- Make tarFunc goroutine context-aware to prevent resource leaks
- Enhance TOCTOU security annotation in repo.go
- Add top-level permissions to pr-security-scan.yml and reusable-security-scan.yml

Addresses: CWE-22 (path traversal), CWE-772 (resource leak), CWE-362 (TOCTOU),
and CKV2_GHA_1 (workflow permissions)
When context is cancelled before tarFunc executes, the goroutine was
returning early without closing the pipe writer. This caused StartIngest
to hang forever waiting for data from the pipe (never receiving EOF).

The fix closes the pipe writer before returning early, which properly
signals EOF to StartIngest and allows the test to complete.

Fixes TestScan/respects_context_cancellation timeout on all platforms.
The error from pw.Close() is intentionally not checked because:
1. PipeWriter.Close() rarely returns meaningful errors
2. The purpose is to signal EOF to the reader, not to handle close errors
3. Any actual issues will surface through the main error flow
- CWE-22: Replace strings.HasPrefix with filepath.EvalSymlinks for secure
  path containment checking in files.go, preventing symlink-based escapes
- CWE-770: Cap SARIF results slice capacity to prevent resource exhaustion
  from large finding lists
@github-advanced-security
Copy link

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

Change build-from-source default from true to false to avoid
self-referential security scanning where untested code scans itself.
This ensures scans use a known-good released version of the CLI.
@yiftach-armis yiftach-armis merged commit cda36b8 into main Jan 18, 2026
8 of 9 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