fix: validate repository URI before source kind

This commit is contained in:
2026-09-13 00:45:43 +09:00
parent 7675f81999
commit f061a95b48
3 changed files with 58 additions and 16 deletions
+46 -14
View File
@@ -484,6 +484,18 @@ impl RuntimeGitMaterializer {
Ok(binding) Ok(binding)
} }
fn validate_plain_http_source(
request: &WorkingDirectoryRequest,
) -> Result<(), WorkingDirectoryDiagnostic> {
if url::Url::parse(&request.repository.source.uri).is_ok_and(|url| url.scheme() == "http") {
return Err(WorkingDirectoryDiagnostic::new(
"working_directory_repository_plain_http_unsupported",
"plain HTTP Repository sources are not executable; register an HTTPS or SSH source instead",
));
}
Ok(())
}
fn validate_request( fn validate_request(
request: &WorkingDirectoryRequest, request: &WorkingDirectoryRequest,
) -> Result<(), WorkingDirectoryDiagnostic> { ) -> Result<(), WorkingDirectoryDiagnostic> {
@@ -499,6 +511,7 @@ impl RuntimeGitMaterializer {
"the configured Repository provider is unsupported", "the configured Repository provider is unsupported",
)); ));
} }
Self::validate_plain_http_source(request)?;
if matches!( if matches!(
request.repository.source.kind, request.repository.source.kind,
workspace_api::RepositorySourceKind::Https | workspace_api::RepositorySourceKind::Ssh workspace_api::RepositorySourceKind::Https | workspace_api::RepositorySourceKind::Ssh
@@ -540,20 +553,10 @@ impl RuntimeGitMaterializer {
validate_ssh_materialization_access(ssh)?; validate_ssh_materialization_access(ssh)?;
} }
workspace_api::RepositorySourceKind::Invalid => { workspace_api::RepositorySourceKind::Invalid => {
let is_plain_http = url::Url::parse(&request.repository.source.uri) return Err(WorkingDirectoryDiagnostic::new(
.is_ok_and(|url| url.scheme() == "http"); "working_directory_repository_source_invalid",
let (code, message) = if is_plain_http { "configured Repository source is invalid and cannot be materialized",
( ));
"working_directory_repository_plain_http_unsupported",
"plain HTTP Repository sources are not executable; register an HTTPS or SSH source instead",
)
} else {
(
"working_directory_repository_source_invalid",
"configured Repository source is invalid and cannot be materialized",
)
};
return Err(WorkingDirectoryDiagnostic::new(code, message));
} }
} }
validate_selector(request.repository.selector.as_deref().unwrap_or("HEAD")) validate_selector(request.repository.selector.as_deref().unwrap_or("HEAD"))
@@ -627,6 +630,7 @@ impl RuntimeGitMaterializer {
request: &WorkingDirectoryRequest, request: &WorkingDirectoryRequest,
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> { ) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
validate_working_directory_id(&working_directory_id)?; validate_working_directory_id(&working_directory_id)?;
Self::validate_plain_http_source(request)?;
let request = let request =
self.request_with_authorized_repository_access(&working_directory_id, request)?; self.request_with_authorized_repository_access(&working_directory_id, request)?;
Self::validate_request(&request)?; Self::validate_request(&request)?;
@@ -3812,6 +3816,34 @@ mod tests {
"plain HTTP rejection must occur before invoking Git" "plain HTTP rejection must occur before invoking Git"
); );
for (offset, kind) in [
workspace_api::RepositorySourceKind::LocalPath,
workspace_api::RepositorySourceKind::File,
workspace_api::RepositorySourceKind::Https,
]
.into_iter()
.enumerate()
{
let mut mismatched_http = request(repo.path());
mismatched_http.repository.source = workspace_api::RepositorySource {
kind,
uri: "http://example.test/repo.git".to_string(),
};
let git_invocations_before = test_repository_git_invocation_count();
let error = materializer
.materialize(&worker_ref(4 + offset as u64), &mismatched_http)
.expect_err("plain HTTP URI must fail regardless of its declared source kind");
assert_eq!(
error.code,
"working_directory_repository_plain_http_unsupported"
);
assert_eq!(
test_repository_git_invocation_count(),
git_invocations_before,
"mismatched plain HTTP source must fail before invoking Git"
);
}
let mut ssh = request(repo.path()); let mut ssh = request(repo.path());
ssh.repository.source = workspace_api::RepositorySource { ssh.repository.source = workspace_api::RepositorySource {
kind: workspace_api::RepositorySourceKind::Ssh, kind: workspace_api::RepositorySourceKind::Ssh,
@@ -1980,6 +1980,17 @@ mod tests {
let error = validate_repository_access_source("remote", &source).unwrap_err(); let error = validate_repository_access_source("remote", &source).unwrap_err();
assert!(error.to_string().contains("unsupported plain HTTP")); assert!(error.to_string().contains("unsupported plain HTTP"));
assert!(error.to_string().contains("HTTPS or SSH")); assert!(error.to_string().contains("HTTPS or SSH"));
let mismatched = workspace_api::RepositorySource {
kind: workspace_api::RepositorySourceKind::Https,
uri: "http://git.example.test/team/project.git".to_string(),
};
let error = validate_repository_access_source("remote", &mismatched).unwrap_err();
assert!(
error
.to_string()
.contains("repository_source_plain_http_unsupported")
);
} }
#[test] #[test]
@@ -113,8 +113,7 @@ pub fn classify_legacy_repository_source(value: &str) -> RepositorySource {
} }
pub(crate) fn is_plain_http_repository_source(source: &RepositorySource) -> bool { pub(crate) fn is_plain_http_repository_source(source: &RepositorySource) -> bool {
source.kind == RepositorySourceKind::Invalid Url::parse(&source.uri).is_ok_and(|url| url.scheme() == "http")
&& Url::parse(&source.uri).is_ok_and(|url| url.scheme() == "http")
} }
pub fn repository_source_fingerprint(source: &RepositorySource) -> String { pub fn repository_source_fingerprint(source: &RepositorySource) -> String {