Skip to content

fix: sanitize plugin manifest name in plugin-manager add to prevent p…#671

Open
RanWurmbrand wants to merge 1 commit into
migtools:mainfrom
RanWurmbrand:fix/unsantize-plugin-name
Open

fix: sanitize plugin manifest name in plugin-manager add to prevent p…#671
RanWurmbrand wants to merge 1 commit into
migtools:mainfrom
RanWurmbrand:fix/unsantize-plugin-name

Conversation

@RanWurmbrand

@RanWurmbrand RanWurmbrand commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

closes: #670

Summary

The plugin-manager add command uses the name field from the plugin manifest as the binary filename without any sanitization. This allows a malicious plugin repository to:

  1. Override existing plugins — a manifest can set its name to an already-installed plugin (e.g. OpenShiftPlugin), silently replacing it with a trojanized version
  2. Write files anywhere on disk — a manifest name containing ../../ sequences escapes the plugins directory, writing an executable (0777 permissions) to arbitrary paths

Root Cause

In downloadBinary(), value.Name from the manifest YAML is passed directly to os.OpenFile as the filename with no validation.

Fix

Added a validateFileInput() function that runs before any download:

  • Rejects names containing /, \, or .. — blocks path traversal
  • Rejects names that match an already-installed plugin binary — blocks silent override

PoC

Full end-to-end proof of concept: https://github.com/RanWurmbrand/compromised-plugins

Test plan

  • crane plugin-manager add EvilPlugin with a manifest name containing ../../ is rejected with an error
  • crane plugin-manager add CoolPlugin with a manifest name matching an existing plugin (OpenShiftPlugin) is rejected with an error
  • Normal plugin installation (manifest name matches requested name, no traversal) still works

Summary by CodeRabbit

  • Bug Fixes
    • Added validation for plugin filenames to reject unsafe paths and prevent path traversal.
    • Prevented downloads from overwriting an existing plugin binary.
    • Invalid requests now fail before downloading or writing files.

…ath traversal and plugin override

Signed-off-by: Ran Wurmbrand <rwurmbra@redhat.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Plugin binary installation now validates manifest filenames for path traversal and existing targets before performing retrieval or filesystem writes.

Changes

Plugin installation validation

Layer / File(s) Summary
Validate plugin filename before download
cmd/plugin-manager/add/add.go
Adds filename validation for path separators, .., and existing install targets, then calls it at the start of downloadBinary.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: stillalearner

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: sanitizing plugin manifest names in plugin-manager add to prevent traversal and overwrite issues.
Linked Issues check ✅ Passed The new validation rejects path traversal names and existing binary conflicts before download, addressing both overwrite and arbitrary write risks in #670.
Out of Scope Changes check ✅ Passed The changes are narrowly scoped to input validation in plugin-manager add and do not introduce unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Test Coverage Report

Total: 49.1%

Per-package coverage

  • github.com/konveyor/crane — 0.0%
  • github.com/konveyor/crane/cmd/apply — 52.7%
  • github.com/konveyor/crane/cmd/convert — 0.0%
  • github.com/konveyor/crane/cmd/export — 92.9%
  • github.com/konveyor/crane/cmd/plugin-manager — 0.0%
  • github.com/konveyor/crane/cmd/plugin-manager/add — 0.0%
  • github.com/konveyor/crane/cmd/plugin-manager/list — 0.0%
  • github.com/konveyor/crane/cmd/plugin-manager/remove — 0.0%
  • github.com/konveyor/crane/cmd/skopeo-sync-gen — 0.0%
  • github.com/konveyor/crane/cmd/transfer-pvc — 18.7%
  • github.com/konveyor/crane/cmd/transform — 59.7%
  • github.com/konveyor/crane/cmd/transform/listplugins — 23.0%
  • github.com/konveyor/crane/cmd/transform/optionals — 0.0%
  • github.com/konveyor/crane/cmd/tunnel-api — 0.0%
  • github.com/konveyor/crane/cmd/validate — 69.2%
  • github.com/konveyor/crane/cmd/version — 20.0%
  • github.com/konveyor/crane/internal/apply — 33.8%
  • github.com/konveyor/crane/internal/buildinfo — 100.0%
  • github.com/konveyor/crane/internal/file — 79.0%
  • github.com/konveyor/crane/internal/flags — 0.0%
  • github.com/konveyor/crane/internal/kustomize — 85.7%
  • github.com/konveyor/crane/internal/plugin — 32.9%
  • github.com/konveyor/crane/internal/transform — 80.8%
  • github.com/konveyor/crane/internal/validate — 58.6%
Full function-level details
github.com/konveyor/crane/cmd/apply/apply.go:44:			Complete								100.0%
github.com/konveyor/crane/cmd/apply/apply.go:50:			Validate								87.5%
github.com/konveyor/crane/cmd/apply/apply.go:64:			Run									0.0%
github.com/konveyor/crane/cmd/apply/apply.go:68:			NewApplyCommand								0.0%
github.com/konveyor/crane/cmd/apply/apply.go:110:			getStageNames								100.0%
github.com/konveyor/crane/cmd/apply/apply.go:118:			addFlagsForOptions							0.0%
github.com/konveyor/crane/cmd/apply/apply.go:134:			run									81.4%
github.com/konveyor/crane/cmd/convert/convert.go:30:			NewConvertOptions							0.0%
github.com/konveyor/crane/cmd/convert/convert.go:63:			addFlagsForConvertOptions						0.0%
github.com/konveyor/crane/cmd/convert/convert.go:74:			Complete								0.0%
github.com/konveyor/crane/cmd/convert/convert.go:83:			Run									0.0%
github.com/konveyor/crane/cmd/convert/convert.go:87:			run									0.0%
github.com/konveyor/crane/cmd/convert/convert.go:112:			getClientFromContext							0.0%
github.com/konveyor/crane/cmd/convert/convert.go:134:			getRestConfigFromContext						0.0%
github.com/konveyor/crane/cmd/export/cluster.go:29:			NewClusterScopeHandler							100.0%
github.com/konveyor/crane/cmd/export/cluster.go:35:			isClusterScopedResource							100.0%
github.com/konveyor/crane/cmd/export/cluster.go:44:			filterRbacResources							100.0%
github.com/konveyor/crane/cmd/export/cluster.go:102:			NewClusterScopedRbacHandler						100.0%
github.com/konveyor/crane/cmd/export/cluster.go:113:			exportedSANamespaces							100.0%
github.com/konveyor/crane/cmd/export/cluster.go:126:			groupMatchesExportedSANamespaces					100.0%
github.com/konveyor/crane/cmd/export/cluster.go:143:			parseServiceAccountUserSubject						100.0%
github.com/konveyor/crane/cmd/export/cluster.go:154:			prepareForFiltering							100.0%
github.com/konveyor/crane/cmd/export/cluster.go:187:			filteredResourcesOfKind							100.0%
github.com/konveyor/crane/cmd/export/cluster.go:213:			accept									100.0%
github.com/konveyor/crane/cmd/export/cluster.go:223:			acceptClusterRoleBinding						100.0%
github.com/konveyor/crane/cmd/export/cluster.go:260:			acceptClusterRole							100.0%
github.com/konveyor/crane/cmd/export/cluster.go:284:			acceptSecurityContextConstraints					100.0%
github.com/konveyor/crane/cmd/export/cluster.go:336:			anyServiceAccountInNamespace						100.0%
github.com/konveyor/crane/cmd/export/crd.go:18:				normalizeGroupSet							85.7%
github.com/konveyor/crane/cmd/export/crd.go:30:				shouldSkipCRDGroup							100.0%
github.com/konveyor/crane/cmd/export/crd.go:51:				crdFailureAPIResourceName						100.0%
github.com/konveyor/crane/cmd/export/crd.go:57:				getOperatorManager							92.9%
github.com/konveyor/crane/cmd/export/crd.go:89:				collectRelatedCRDs							94.7%
github.com/konveyor/crane/cmd/export/discover.go:41:			hasClusterScopedManifests						100.0%
github.com/konveyor/crane/cmd/export/discover.go:59:			prepareClusterResourceDir						100.0%
github.com/konveyor/crane/cmd/export/discover.go:75:			prepareFailuresDir							100.0%
github.com/konveyor/crane/cmd/export/discover.go:87:			writeResources								85.7%
github.com/konveyor/crane/cmd/export/discover.go:135:			writeErrors								83.3%
github.com/konveyor/crane/cmd/export/discover.go:177:			getFilePath								95.2%
github.com/konveyor/crane/cmd/export/discover.go:215:			discoverPreferredResources						100.0%
github.com/konveyor/crane/cmd/export/discover.go:244:			resourceToExtract							100.0%
github.com/konveyor/crane/cmd/export/discover.go:318:			isAdmittedResource							100.0%
github.com/konveyor/crane/cmd/export/discover.go:327:			getObjects								96.3%
github.com/konveyor/crane/cmd/export/discover.go:378:			iterateItemsByGet							90.5%
github.com/konveyor/crane/cmd/export/discover.go:413:			iterateItemsInList							92.3%
github.com/konveyor/crane/cmd/export/export.go:54:			Complete								92.0%
github.com/konveyor/crane/cmd/export/export.go:101:			Validate								100.0%
github.com/konveyor/crane/cmd/export/export.go:142:			validateExportNamespace							87.5%
github.com/konveyor/crane/cmd/export/export.go:159:			allResourceListsForbidden						87.5%
github.com/konveyor/crane/cmd/export/export.go:177:			mergeImpersonationExtras						100.0%
github.com/konveyor/crane/cmd/export/export.go:192:			Run									0.0%
github.com/konveyor/crane/cmd/export/export.go:316:			NewExportCommand							48.1%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:44:		Complete								0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:49:		Validate								0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:92:		Run									0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:96:		NewAddCommand								0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:128:		addFlagsForOptions							0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:133:		run									0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:205:		validateFileInput							0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:217:		downloadBinary								0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:270:		binaryURIForPlatform							0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:46:		Complete								0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:51:		Validate								0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:56:		Run									0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:60:		NewListCommand								0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:90:		addFlagsForOptions							0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:98:		run									0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:167:		printInstalledInformation						0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:177:		groupInformationForPlugins						0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:196:		printInformation							0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:208:		printParamsInformation							0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:222:		getOptionalFields							0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:247:		printTable								0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:33:	Complete								0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:38:	Validate								0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:43:	Run									0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:47:	NewPluginManagerCommand							0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:78:	addFlagsForOptions							0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:86:	run									0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:33:	Complete								0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:38:	Validate								0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:43:	Run									0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:47:	NewRemoveCommand							0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:77:	run									0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:61:	Complete								0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:65:	Validate								0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:69:	NewSkopeoSyncGenCommand							0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:103:	shouldAddImageStream							0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:114:	Run									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:37:		NewRsyncLogStream							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:50:		Init									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:143:		writeProgressToFile							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:153:		Close									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:159:		Streams									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:195:		addDataSize								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:220:		String									100.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:224:		MarshalJSON								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:228:		AsString								97.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:276:		NewProgress								100.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:296:		Completed								100.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:301:		Status									100.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:326:		Merge									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:381:		newDataSize								72.7%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:404:		parseRsyncLogs								76.1%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:484:		waitForPodRunning							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:518:		getFinalPodStatus							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:93:		Validate								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:121:		Validate								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:137:		NewTransferPVCCommand							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:173:		addFlagsToTransferPVCCommand						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:193:		Complete								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:224:		Validate								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:250:		Run									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:254:		getClientFromContext							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:275:		getRestConfigFromContext						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:282:		run									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:500:		getValidatedResourceName						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:510:		getNodeNameForPVC							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:530:		getIDsForNamespace							58.3%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:593:		getSecurityContextFromWorkload						74.5%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:693:		podSpecReferencesPVC							100.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:702:		extractPodSecurityContext						90.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:752:		inspectPVCFileOwnership							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:856:		getRsyncClientPodSecurityContext					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:860:		getRsyncServerPodSecurityContext					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:864:		garbageCollect								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:901:		deleteResourcesIteratively						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:927:		deleteResourcesForGVK							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:948:		followClientLogs							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:977:		waitForEndpoint								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:989:		createEndpoint								0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1034:	getRouteHostName							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1051:	buildDestinationPVC							0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1072:	ApplyTo									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1092:	ApplyTo									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1104:	ApplyTo									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1120:	String									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1124:	Set									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1134:	Type									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1140:	parseSourceDestinationMapping						100.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1162:	String									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1166:	Set									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1175:	Type									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1179:	String									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1183:	Set									0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:1193:	Type									0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:33:	Complete								0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:38:	Validate								0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:43:	Run									0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:47:	NewListPluginsCommand							0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:80:	GetPluginNames								85.7%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:95:	getFilteredPlugins							75.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:104:	run									0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:31:	Complete								0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:36:	Validate								0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:41:	Run									0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:45:	NewOptionalsCommand							0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:78:	run									0.0%
github.com/konveyor/crane/cmd/transform/transform.go:56:		Complete								0.0%
github.com/konveyor/crane/cmd/transform/transform.go:62:		Validate								83.3%
github.com/konveyor/crane/cmd/transform/transform.go:81:		Run									0.0%
github.com/konveyor/crane/cmd/transform/transform.go:86:		getPluginCompletions							100.0%
github.com/konveyor/crane/cmd/transform/transform.go:111:		NewTransformCommand							0.0%
github.com/konveyor/crane/cmd/transform/transform.go:155:		addFlagsForOptions							0.0%
github.com/konveyor/crane/cmd/transform/transform.go:177:		run									11.8%
github.com/konveyor/crane/cmd/transform/transform.go:328:		optionalFlagsToLower							100.0%
github.com/konveyor/crane/cmd/transform/transform.go:338:		runStageWithCleanup							0.0%
github.com/konveyor/crane/cmd/transform/transform.go:354:		reconcileInstructionStages						81.0%
github.com/konveyor/crane/cmd/transform/transform.go:397:		ensurePreviousStagesRun							83.3%
github.com/konveyor/crane/cmd/transform/transform.go:414:		ensureStagesHaveOutput							92.9%
github.com/konveyor/crane/cmd/transform/transform.go:452:		createDefaultStagesForAllPlugins					94.7%
github.com/konveyor/crane/cmd/transform/transform.go:504:		validateStageNameToken							100.0%
github.com/konveyor/crane/cmd/transform/transform.go:519:		findStageByDirName							100.0%
github.com/konveyor/crane/cmd/transform/transform.go:531:		findStagesByName							100.0%
github.com/konveyor/crane/cmd/transform/transform.go:543:		createStageDirectory							77.8%
github.com/konveyor/crane/cmd/transform/transform.go:564:		createCustomStageWithExplicitName					100.0%
github.com/konveyor/crane/cmd/transform/transform.go:580:		createCustomStageWithAutoPriority					71.4%
github.com/konveyor/crane/cmd/transform/transform.go:602:		createPluginStage							0.0%
github.com/konveyor/crane/cmd/transform/transform.go:635:		resolveAndValidateStages						58.5%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:35:		NewTunnelAPIOptions							0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:65:		addFlagsForTunnelAPIOptions						0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:77:		Complete								0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:100:		Validate								0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:116:		Run									0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:120:		getClientFromContext							0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:129:		getRestConfigFromContext						0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:136:		run									0.0%
github.com/konveyor/crane/cmd/validate/validate.go:37:			Complete								90.0%
github.com/konveyor/crane/cmd/validate/validate.go:56:			determineClusterContext							91.7%
github.com/konveyor/crane/cmd/validate/validate.go:84:			Validate								100.0%
github.com/konveyor/crane/cmd/validate/validate.go:129:			Run									16.7%
github.com/konveyor/crane/cmd/validate/validate.go:235:			NewValidateCommand							47.6%
github.com/konveyor/crane/cmd/version/version.go:20:			Complete								0.0%
github.com/konveyor/crane/cmd/version/version.go:25:			Validate								0.0%
github.com/konveyor/crane/cmd/version/version.go:30:			Run									0.0%
github.com/konveyor/crane/cmd/version/version.go:34:			NewVersionCommand							0.0%
github.com/konveyor/crane/cmd/version/version.go:60:			run									100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:21:			CreateTempDir								100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:28:			ListFilesRecursively							91.7%
github.com/konveyor/crane/e2e-tests/utils/utils.go:51:			ListFilesRecursivelyAsList						80.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:75:			HasFilesRecursively							80.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:86:			ReadTestdataFile							88.9%
github.com/konveyor/crane/e2e-tests/utils/utils.go:105:			TestdataFilePath							0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:125:			GoldenManifestsDir							90.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:147:			GoldenManifestsDirForPlatform						0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:170:			CompareDirectoryFileSets						78.9%
github.com/konveyor/crane/e2e-tests/utils/utils.go:205:			CompareDirectoryYAMLSemantics						82.4%
github.com/konveyor/crane/e2e-tests/utils/utils.go:239:			CompareDirectoryYAMLSemanticsExport					100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:247:			CompareDirectoryYAMLSemanticsExportAllowOptionalOCPOutputDefaults	100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:251:			compareDirectoryYAMLSemanticsExport					92.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:330:			isOptionalOCPOutputIdentity						100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:357:			buildNormalizedExportIndex						81.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:397:			canonicalizeDocs							87.5%
github.com/konveyor/crane/e2e-tests/utils/utils.go:411:			extractResourceIdentity							79.5%
github.com/konveyor/crane/e2e-tests/utils/utils.go:482:			parseYAMLDocuments							100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:498:			canonicalOpenShiftDockercfgSecretName					80.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:514:			compareYAMLFileBytes							100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:541:			AssertNoKindsInOutput							0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:597:			AssertKindsInOutput							0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:649:			LooksLikeYAMLFile							100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:663:			normalizeUnstableFields							77.8%
github.com/konveyor/crane/e2e-tests/utils/utils.go:741:			stripPodTemplateHash							0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:749:			normalizePodServiceAccountVolumeNames					90.9%
github.com/konveyor/crane/e2e-tests/utils/utils.go:806:			normalizeServiceAccountDockercfgReferences				94.1%
github.com/konveyor/crane/e2e-tests/utils/utils.go:840:			normalizeWithPath							83.3%
github.com/konveyor/crane/e2e-tests/utils/utils.go:878:			shouldDropField								100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:934:			AssertWhiteoutResourceFilesExist					0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:974:			AssertWhiteoutResourceFileCount						0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:1006:		AssertWhiteoutCommentsInKustomization					0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:1061:		AssertKindsNotInActiveKustomizeResources				0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:1103:		CaptureAPISurfaceScriptPath						75.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:1122:		ToInt64									0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:1139:		ExtractCPUAverageUtilization						0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:1172:		AssertFilesExist							0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:1198:		RemapNamespaceInYAML							0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:31:		ApplySingleStage							0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:80:		ApplyMultiStage								0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:134:		runKustomizeBuild							0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:143:		filterClusterScopedResources						82.4%
github.com/konveyor/crane/internal/apply/kustomize.go:199:		splitMultiDocYAMLToFiles						86.7%
github.com/konveyor/crane/internal/buildinfo/buildinfo.go:25:		readKustomizeVersion							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:22:		ReadFiles								100.0%
github.com/konveyor/crane/internal/file/file_helper.go:32:		readFiles								84.0%
github.com/konveyor/crane/internal/file/file_helper.go:91:		GetWhiteOutFilePath							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:95:		GetTransformPath							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:99:		GetIgnoredPatchesPath							0.0%
github.com/konveyor/crane/internal/file/file_helper.go:103:		updateTransformDirPath							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:107:		updateIgnoredPatchesDirPath						0.0%
github.com/konveyor/crane/internal/file/file_helper.go:114:		updatePath								100.0%
github.com/konveyor/crane/internal/file/file_helper.go:121:		GetOutputFilePath							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:131:		GetStageDir								100.0%
github.com/konveyor/crane/internal/file/file_helper.go:137:		GetInputDir								100.0%
github.com/konveyor/crane/internal/file/file_helper.go:143:		GetPatchesDir								100.0%
github.com/konveyor/crane/internal/file/file_helper.go:149:		GetKustomizationPath							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:155:		GetMetadataPath								100.0%
github.com/konveyor/crane/internal/file/file_helper.go:161:		GetResourceTypeFilePath							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:167:		GetPatchFilePath							100.0%
github.com/konveyor/crane/internal/file/file_helper.go:174:		GetStageTransformDir							0.0%
github.com/konveyor/crane/internal/file/file_helper.go:180:		GetStageOutputDir							0.0%
github.com/konveyor/crane/internal/file/file_helper.go:187:		GetResourceFilename							75.0%
github.com/konveyor/crane/internal/file/ordering.go:82:			GetResourceOrder							100.0%
github.com/konveyor/crane/internal/file/ordering.go:92:			GetOrderedResourceFilename						100.0%
github.com/konveyor/crane/internal/flags/global_flags.go:14:		ApplyFlags								0.0%
github.com/konveyor/crane/internal/flags/global_flags.go:21:		GetLogger								0.0%
github.com/konveyor/crane/internal/flags/global_flags.go:29:		initConfig								0.0%
github.com/konveyor/crane/internal/flags/help_groups.go:12:		KubernetesClientInheritedFlagNames					0.0%
github.com/konveyor/crane/internal/flags/help_groups.go:40:		SetGroupedHelp								0.0%
github.com/konveyor/crane/internal/kustomize/args.go:20:		ParseAndValidateArgs							100.0%
github.com/konveyor/crane/internal/kustomize/args.go:92:		splitArgs								100.0%
github.com/konveyor/crane/internal/kustomize/runner.go:21:		Build									82.4%
github.com/konveyor/crane/internal/kustomize/runner.go:55:		buildOptions								100.0%
github.com/konveyor/crane/internal/kustomize/runner.go:105:		setEnvVars								46.2%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:21:		GetPlugins								0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:38:		getBinaryPlugins							0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:63:		IsExecAny								0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:67:		GetFilteredPlugins							0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:109:		isPluginInList								0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:24:	BuildManifestMap							0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:68:	GetYamlFromUrl								75.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:82:	YamlToManifest								72.7%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:104:	FilterPluginForOsArch							100.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:119:	GetDefaultSource							0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:128:	LocateBinaryInPluginDir							0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:140:	IsUrl									100.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:146:	getData									80.0%
github.com/konveyor/crane/internal/transform/instructions.go:31:	LoadInstructions							61.1%
github.com/konveyor/crane/internal/transform/instructions.go:63:	friendlyInstructionsDecodeError						87.5%
github.com/konveyor/crane/internal/transform/instructions.go:78:	ValidateInstructions							100.0%
github.com/konveyor/crane/internal/transform/instructions.go:112:	GenerateStageDirNames							100.0%
github.com/konveyor/crane/internal/transform/orchestrator.go:41:	RunMultiStage								75.6%
github.com/konveyor/crane/internal/transform/orchestrator.go:142:	executeStage								68.4%
github.com/konveyor/crane/internal/transform/orchestrator.go:189:	transformResources							68.4%
github.com/konveyor/crane/internal/transform/orchestrator.go:240:	formatResourceID							0.0%
github.com/konveyor/crane/internal/transform/orchestrator.go:247:	getPluginForStage							90.9%
github.com/konveyor/crane/internal/transform/orchestrator.go:274:	getAvailablePluginNames							0.0%
github.com/konveyor/crane/internal/transform/orchestrator.go:287:	applyStageTransforms							82.1%
github.com/konveyor/crane/internal/transform/orchestrator.go:354:	loadResourcesFromDirectory						85.7%
github.com/konveyor/crane/internal/transform/orchestrator.go:369:	writeResourcesToDirectory						73.1%
github.com/konveyor/crane/internal/transform/stages.go:22:		DiscoverStages								90.5%
github.com/konveyor/crane/internal/transform/stages.go:83:		FilterStages								100.0%
github.com/konveyor/crane/internal/transform/stages.go:112:		GetFirstStage								100.0%
github.com/konveyor/crane/internal/transform/stages.go:122:		GetLastStage								66.7%
github.com/konveyor/crane/internal/transform/stages.go:132:		GetPreviousStage							100.0%
github.com/konveyor/crane/internal/transform/stages.go:142:		GetNextStage								100.0%
github.com/konveyor/crane/internal/transform/stages.go:152:		ValidateStageName							100.0%
github.com/konveyor/crane/internal/transform/stages.go:161:		GenerateStageName							100.0%
github.com/konveyor/crane/internal/transform/test_helpers.go:8:		hasKustomizeCommand							100.0%
github.com/konveyor/crane/internal/transform/test_helpers.go:14:	contains								100.0%
github.com/konveyor/crane/internal/transform/test_helpers.go:23:	findInString								100.0%
github.com/konveyor/crane/internal/transform/writer.go:28:		NewKustomizeWriter							100.0%
github.com/konveyor/crane/internal/transform/writer.go:37:		WriteStage								68.4%
github.com/konveyor/crane/internal/transform/writer.go:218:		getResourceID								100.0%
github.com/konveyor/crane/internal/transform/writer.go:231:		filterValidRemoveOps							76.2%
github.com/konveyor/crane/internal/transform/writer.go:275:		pathExists								93.5%
github.com/konveyor/crane/internal/transform/writer.go:352:		generateKustomizationWithComments					93.8%
github.com/konveyor/crane/internal/transform/writer.go:383:		checkStageDirectory							23.1%
github.com/konveyor/crane/internal/validate/api_resources.go:24:	ParseAPIResourcesJSON							89.7%
github.com/konveyor/crane/internal/validate/matcher.go:25:		MatchResults								75.0%
github.com/konveyor/crane/internal/validate/matcher.go:36:		MatchResultsFromIndex							100.0%
github.com/konveyor/crane/internal/validate/matcher.go:76:		buildDiscoveryIndex							71.4%
github.com/konveyor/crane/internal/validate/matcher.go:110:		matchEntry								100.0%
github.com/konveyor/crane/internal/validate/matcher.go:139:		buildKindIndex								100.0%
github.com/konveyor/crane/internal/validate/matcher.go:151:		addSuggestion								91.7%
github.com/konveyor/crane/internal/validate/report.go:17:		FormatTable								91.3%
github.com/konveyor/crane/internal/validate/report.go:60:		FormatJSON								100.0%
github.com/konveyor/crane/internal/validate/report.go:67:		FormatYAML								0.0%
github.com/konveyor/crane/internal/validate/report.go:79:		WriteFailures								0.0%
github.com/konveyor/crane/internal/validate/report.go:115:		failureFileName								0.0%
github.com/konveyor/crane/internal/validate/report.go:129:		safeFilePart								0.0%
github.com/konveyor/crane/internal/validate/report.go:145:		parseAPIVersion								0.0%
github.com/konveyor/crane/internal/validate/scanner.go:34:		ScanManifests								76.5%
github.com/konveyor/crane/internal/validate/types.go:46:		HasIncompatible								100.0%
github.com/konveyor/crane/internal/validate/types.go:49:		IncompatibleResults							0.0%
github.com/konveyor/crane/main.go:22:					main									0.0%
total:									(statements)								49.1%

Posted by CI

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/plugin-manager/add/add.go`:
- Around line 217-221: Update the file creation logic in downloadBinary to
include os.O_EXCL alongside the existing creation flags, so opening the target
fails atomically if a file or symlink appears during download; also use
executable mode 0755 instead of 0777.
- Around line 205-215: The validateFileInput function must use os.Lstat to
inspect the destination without following symlinks, and treat any error other
than os.ErrNotExist as a validation failure. Preserve the existing
duplicate-file error for an existing path, while rejecting broken symlinks and
propagating unexpected filesystem errors before installation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 407cee60-227b-49d0-9410-e2eae0785a98

📥 Commits

Reviewing files that changed from the base of the PR and between 9c6e430 and 3cf69c9.

📒 Files selected for processing (1)
  • cmd/plugin-manager/add/add.go

Comment on lines +205 to +215
func validateFileInput(filepath, filename string) error {
if strings.Contains(filename, "/") || strings.Contains(filename, "\\") || strings.Contains(filename, "..") {
return fmt.Errorf("invalid plugin name %q: must not contain path separators or traversal sequences", filename)
}

existingPath := filepath + "/" + filename
if _, err := os.Stat(existingPath); err == nil {
return fmt.Errorf("a plugin named %q already exists at %s, please remove it first before installing", filename, existingPath)
}
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check current imports in add.go to determine if path/filepath and errors need to be added.
rg '^import ' -A 15 cmd/plugin-manager/add/add.go

Repository: migtools/crane

Length of output: 411


🏁 Script executed:

sed -n '180,280p' cmd/plugin-manager/add/add.go
python3 - <<'PY'
import os, tempfile, pathlib, stat

with tempfile.TemporaryDirectory() as d:
    p = pathlib.Path(d) / "broken"
    try:
        p.symlink_to(pathlib.Path(d) / "missing")
    except (OSError, NotImplementedError) as e:
        print("symlink_unavailable", type(e).__name__, e)
    else:
        try:
            os.stat(p)
        except OSError as e:
            print("stat", type(e).__name__, e.errno)
        try:
            os.lstat(p)
        except OSError as e:
            print("lstat", type(e).__name__, e.errno)
PY

Repository: migtools/crane

Length of output: 3309


🏁 Script executed:

python3 - <<'PY'
import os, tempfile, pathlib

with tempfile.TemporaryDirectory() as d:
    base = pathlib.Path(d)
    target = base / "outside.txt"
    link = base / "plugin"
    link.symlink_to(target)  # broken symlink

    print("before_exists", target.exists())
    try:
        with open(link, "w") as f:
            f.write("hello")
        print("open_succeeded", True)
    except OSError as e:
        print("open_succeeded", False, type(e).__name__, e.errno)

    print("after_exists", target.exists())
    if target.exists():
        print("target_contents", target.read_text())
PY

Repository: migtools/crane

Length of output: 232


Use os.Lstat and fail closed on unexpected errors in cmd/plugin-manager/add/add.go:205-215. os.Stat follows symlinks, so a broken symlink at the destination can slip past this guard and the later os.OpenFile(..., O_CREAT|O_TRUNC, 0777) will write through it. Treat any non-os.ErrNotExist error as a failure here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/plugin-manager/add/add.go` around lines 205 - 215, The validateFileInput
function must use os.Lstat to inspect the destination without following
symlinks, and treat any error other than os.ErrNotExist as a validation failure.
Preserve the existing duplicate-file error for an existing path, while rejecting
broken symlinks and propagating unexpected filesystem errors before
installation.

Source: Coding guidelines

Comment on lines 217 to +221
func downloadBinary(filepath string, filename string, url string, log *logrus.Logger) error {
if err := validateFileInput(filepath, filename); err != nil {
return err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use os.O_EXCL during file creation to prevent TOCTOU race conditions.

While validateFileInput provides a fast early check, a Time-of-Check to Time-of-Use (TOCTOU) race condition exists because the binary is downloaded over the network before the local file is actually created. If a file or symlink is created at the target path concurrently during the download, opening the file with syscall.O_TRUNC will overwrite it or follow the symlink to overwrite an arbitrary file.

To strictly enforce the protection against overwrites, use os.O_EXCL in os.OpenFile when creating the plugin binary (around line 249) to guarantee the operation fails atomically if the file already exists.

🔒 Proposed fix for file creation

Update the file creation logic (around line 249) to use os.O_EXCL and apply a safer default mode for executables (0755 instead of world-writable 0777):

-	pluginBinary, err := os.OpenFile(filepath+"/"+filename, syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0777)
+	pluginBinary, err := os.OpenFile(filepath+"/"+filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0755) // Update to use filepath.Join if parameter was renamed
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/plugin-manager/add/add.go` around lines 217 - 221, Update the file
creation logic in downloadBinary to include os.O_EXCL alongside the existing
creation flags, so opening the target fails atomically if a file or symlink
appears during download; also use executable mode 0755 instead of 0777.

@RanWurmbrand RanWurmbrand self-assigned this Jul 20, 2026
@RanWurmbrand RanWurmbrand moved this from Todo to In progress in Crane Development Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

[BUG] Unsanitized plugin name in plugin-manager add allows plugin override and arbitrary file write

1 participant