the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add file path support and encryption for SSH keys

+96 -27
+94 -25
apps/cli/src/cmd/sshkeys.ts
··· 2 2 import getAccessToken from "../lib/getAccessToken"; 3 3 import { generateEd25519SSHKeyPair } from "../lib/sshKeys"; 4 4 import consola from "consola"; 5 + import fs from "node:fs/promises"; 6 + import encrypt from "../lib/sodium"; 7 + import { client } from "../client"; 8 + import type { Sandbox } from "../types/sandbox"; 9 + import { env } from "../lib/env"; 5 10 6 11 export async function getSshKey(sandbox: string) { 7 12 const token = await getAccessToken(); ··· 9 14 10 15 export async function putKeys( 11 16 sandbox: string, 12 - { generate }: { generate: boolean | undefined }, 17 + options: { generate?: boolean; publicKey?: string; privateKey?: string }, 13 18 ) { 14 19 const token = await getAccessToken(); 15 - if (generate) { 16 - const { privateKey, publicKey } = await generateEd25519SSHKeyPair(); 17 - consola.log("\nPrivate Key:\n"); 18 - consola.log(privateKey); 19 - consola.log("\nPublic Key:\n"); 20 - consola.log(publicKey, "\n"); 20 + let privateKey: string | undefined; 21 + let publicKey: string | undefined; 21 22 22 - consola.success("SSH keys generated successfully!"); 23 - return; 23 + if (options.generate) { 24 + const generated = await generateEd25519SSHKeyPair(""); 25 + privateKey = generated.privateKey; 26 + publicKey = generated.publicKey; 27 + } 28 + 29 + if (options.privateKey && !options.generate) { 30 + privateKey = await fs.readFile(options.privateKey, "utf8"); 31 + } 32 + 33 + if (options.publicKey && !options.generate) { 34 + publicKey = await fs.readFile(options.publicKey, "utf8"); 24 35 } 25 36 26 37 const validatePrivateKey = (value: string): string | true => { ··· 34 45 return true; 35 46 }; 36 47 37 - const privateKey = ( 38 - await editor({ 39 - message: "Enter your SSH private key (opens in $EDITOR):", 40 - postfix: ".pem", 41 - waitForUserInput: false, 42 - validate: validatePrivateKey, 43 - }) 44 - ).trim(); 48 + if (!privateKey) { 49 + privateKey = ( 50 + await editor({ 51 + message: "Enter your SSH private key (opens in $EDITOR):", 52 + postfix: ".pem", 53 + waitForUserInput: false, 54 + validate: validatePrivateKey, 55 + }) 56 + ).trim(); 57 + } 58 + 59 + if (!publicKey) { 60 + publicKey = ( 61 + await input({ 62 + message: "Enter your SSH public key:", 63 + validate: (value: string): string | true => 64 + value.trim().length > 0 ? true : "Public key cannot be empty.", 65 + }) 66 + ).trim(); 67 + } 68 + 69 + const { data } = await client.get<{ sandbox: Sandbox }>( 70 + "/xrpc/io.pocketenv.sandbox.getSandbox", 71 + { 72 + params: { 73 + id: sandbox, 74 + }, 75 + headers: { 76 + Authorization: `Bearer ${token}`, 77 + }, 78 + }, 79 + ); 80 + 81 + const encryptedPrivateKey = await encrypt(privateKey); 82 + 83 + const redacted = (() => { 84 + const header = "-----BEGIN OPENSSH PRIVATE KEY-----"; 85 + const footer = "-----END OPENSSH PRIVATE KEY-----"; 86 + const headerIndex = privateKey.indexOf(header); 87 + const footerIndex = privateKey.indexOf(footer); 88 + if (headerIndex === -1 || footerIndex === -1) 89 + return privateKey.replace(/\n/g, "\\n"); 90 + const body = privateKey.slice(headerIndex + header.length, footerIndex); 91 + const chars = body.split(""); 92 + const nonNewlineIndices = chars 93 + .map((c, i) => (c !== "\n" ? i : -1)) 94 + .filter((i) => i !== -1); 95 + const maskedBody = 96 + nonNewlineIndices.length > 15 97 + ? (() => { 98 + const middleIndices = nonNewlineIndices.slice(10, -5); 99 + middleIndices.forEach((i) => { 100 + chars[i] = "*"; 101 + }); 102 + return chars.join(""); 103 + })() 104 + : body; 105 + return `${header}${maskedBody}${footer}`.replace(/\n/g, "\\n"); 106 + })(); 45 107 46 - const publicKey = ( 47 - await input({ 48 - message: "Enter your SSH public key:", 49 - validate: (value: string): string | true => 50 - value.trim().length > 0 ? true : "Public key cannot be empty.", 51 - }) 52 - ).trim(); 108 + await client.post( 109 + "/xrpc/io.pocketenv.sandbox.putSshKeys", 110 + { 111 + id: data.sandbox.id, 112 + privateKey: encryptedPrivateKey, 113 + publicKey, 114 + redacted, 115 + }, 116 + { 117 + headers: { 118 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 119 + }, 120 + }, 121 + ); 53 122 54 123 consola.log("\nPrivate Key:\n"); 55 - consola.log(privateKey); 124 + consola.log(redacted); 56 125 consola.log("\nPublic Key:\n"); 57 126 consola.log(publicKey, "\n"); 58 127
+2 -2
apps/cli/src/index.ts
··· 155 155 sshkeys 156 156 .command("put") 157 157 .argument("<sandbox>", "the sandbox to put the SSH key in") 158 - .option("--private-key", "the path to the SSH private key") 159 - .option("--public-key", "the path to the SSH public key") 158 + .option("--private-key <path>", "the path to the SSH private key") 159 + .option("--public-key <path>", "the path to the SSH public key") 160 160 .option("--generate, -g", "generate a new SSH key pair") 161 161 .description("put an SSH key in the given sandbox") 162 162 .action(putKeys);