From a9bc03fb01b5d1ac41bf5592f384d22df5425aa4 Mon Sep 17 00:00:00 2001
From: Melodi <62381002+Melodi17@users.noreply.github.com>
Date: Sun, 6 Sep 2026 09:02:31 +1000
Subject: feat: vcs and secret management support
---
src/marlin.core/functional/SshKeyGitCredential.cs | 57 +++++++++++++++++++++++
1 file changed, 57 insertions(+)
create mode 100644 src/marlin.core/functional/SshKeyGitCredential.cs
(limited to 'src/marlin.core/functional/SshKeyGitCredential.cs')
diff --git a/src/marlin.core/functional/SshKeyGitCredential.cs b/src/marlin.core/functional/SshKeyGitCredential.cs
new file mode 100644
index 0000000..9183a66
--- /dev/null
+++ b/src/marlin.core/functional/SshKeyGitCredential.cs
@@ -0,0 +1,57 @@
+namespace marlin.core.functional;
+
+using System.Diagnostics;
+
+///
+/// SSH auth via a private key. The key is written to a per-invocation temp file
+/// (owner-read/write only) and deleted as soon as the git process that used it exits —
+/// ssh has no clean way to take key material via stdin/env, so a short-lived file is
+/// the least-bad option.
+///
+public sealed class SshKeyGitCredential : IGitCredential
+{
+ private readonly string _privateKeyPem;
+
+ public SshKeyGitCredential(string privateKeyPem)
+ {
+ this._privateKeyPem = privateKeyPem;
+ }
+
+ public async Task ApplyAsync(ProcessStartInfo psi)
+ {
+ string keyPath = Path.Combine(Path.GetTempPath(), $"marlin_{Guid.NewGuid():N}.key");
+ await File.WriteAllTextAsync(keyPath, this._privateKeyPem);
+
+ if (!OperatingSystem.IsWindows())
+ File.SetUnixFileMode(keyPath, UnixFileMode.UserRead | UnixFileMode.UserWrite);
+
+ psi.Environment["GIT_SSH_COMMAND"] =
+ $"ssh -i \"{keyPath}\" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new";
+
+ return new TempKeyFile(keyPath);
+ }
+
+ private sealed class TempKeyFile : IAsyncDisposable
+ {
+ private readonly string _path;
+
+ public TempKeyFile(string path)
+ {
+ this._path = path;
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ try
+ {
+ File.Delete(this._path);
+ }
+ catch (IOException)
+ {
+ // best-effort cleanup; a leftover temp file is not worth failing the caller over
+ }
+
+ return ValueTask.CompletedTask;
+ }
+ }
+}
--
cgit v1.3.1