feat(workspaces): implement secure GitHub PAT injection via memory-only mounts

This commit is contained in:
mkorwel
2026-03-19 09:20:52 -07:00
parent a7dd0ac571
commit 1ccd86cfd7
4 changed files with 103 additions and 4 deletions
+31 -1
View File
@@ -49,7 +49,7 @@ describe('SSHService', () => {
'--tunnel-through-iap',
'--ssh-flag=-A',
],
expect.objectContaining({ stdio: 'inherit' })
{ stdio: 'inherit' }
);
});
@@ -67,4 +67,34 @@ describe('SSHService', () => {
await expect(promise).rejects.toThrow('gcloud ssh exited with code 1');
});
it('should construct correct gcloud command for pushSecret', async () => {
const mockChild = new EventEmitter() as any;
vi.mocked(spawn).mockReturnValue(mockChild);
const secretValue = 'secret-val';
const promise = service.pushSecret(
{ instanceName: 'test-inst', zone: 'z1', project: 'p1' },
'.gh_token',
secretValue
);
setTimeout(() => mockChild.emit('exit', 0), 10);
await promise;
expect(spawn).toHaveBeenCalledWith(
'gcloud',
[
'compute',
'ssh',
'test-inst',
'--zone=z1',
'--project=p1',
'--tunnel-through-iap',
'--command',
`cat << 'EOF' > /dev/shm/.gh_token\n${secretValue}\nEOF\nchmod 600 /dev/shm/.gh_token`,
]
);
});
});
+37
View File
@@ -66,4 +66,41 @@ export class SSHService {
});
});
}
/**
* Pushes a secret string to a temporary, memory-only file on the remote VM.
* Uses gcloud compute ssh with a heredoc to write to /dev/shm.
*/
async pushSecret(options: SSHOptions, secretName: string, secretValue: string): Promise<void> {
const { instanceName, zone, project } = options;
const remotePath = `/dev/shm/${secretName}`;
// Command to write secret securely without it appearing in process list
const remoteCommand = `cat << 'EOF' > ${remotePath}
${secretValue}
EOF
chmod 600 ${remotePath}`;
const args = [
'compute',
'ssh',
instanceName,
`--zone=${zone}`,
`--project=${project}`,
'--tunnel-through-iap',
'--command',
remoteCommand,
];
debugLogger.log(`[SSHService] Pushing secret ${secretName} to ${instanceName}...`);
return new Promise((resolve, reject) => {
const child = spawn('gcloud', args);
child.on('exit', (code) => {
if (code === 0) resolve();
else reject(new Error(`Failed to push secret ${secretName}, exit code ${code}`));
});
child.on('error', (err) => reject(err));
});
}
}