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.

Validate owner when deleting secrets and variables

Ensure delete endpoints verify the authenticated user owns the
sandbox by joining users and sandboxes and checking the DID; return
404 if the secret or variable is not found. Accept both sandbox-
specific id and global secret/variable id when retrieving or
deleting. Update CLI to show IDs in lists and to delete by ID. Add
local build/install instructions to the CLI README.

+87 -31
+24 -1
apps/api/src/xrpc/io/pocketenv/secret/deleteSecret.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import { updateSandbox } from "atproto/sandbox"; 3 3 import type { Context } from "context"; 4 - import { eq, or } from "drizzle-orm"; 4 + import { and, eq, or } from "drizzle-orm"; 5 5 import type { Server } from "lexicon"; 6 6 import type { QueryParams } from "lexicon/types/io/pocketenv/secret/deleteSecret"; 7 7 import sandboxSecrets from "schema/sandbox-secrets"; 8 8 import sandboxes from "schema/sandboxes"; 9 9 import { consola } from "consola"; 10 10 import { createAgent } from "lib/agent"; 11 + import secrets from "schema/secrets"; 12 + import users from "schema/users"; 11 13 12 14 export default function (server: Server, ctx: Context) { 13 15 const deleteSecret = async (params: QueryParams, auth: HandlerAuth) => { 14 16 if (!auth.credentials) { 15 17 throw new XRPCError(401, "Unauthorized"); 18 + } 19 + 20 + const [existingSecret] = await ctx.db 21 + .select() 22 + .from(sandboxSecrets) 23 + .leftJoin(secrets, eq(sandboxSecrets.secretId, secrets.id)) 24 + .leftJoin(sandboxes, eq(sandboxSecrets.sandboxId, sandboxes.id)) 25 + .leftJoin(users, eq(sandboxSecrets.sandboxId, users.id)) 26 + .where( 27 + and( 28 + or( 29 + eq(sandboxSecrets.id, params.id), 30 + eq(sandboxSecrets.secretId, params.id), 31 + ), 32 + eq(users.did, auth.credentials.did), 33 + ), 34 + ) 35 + .execute(); 36 + 37 + if (!existingSecret) { 38 + throw new XRPCError(404, "Secret not found"); 16 39 } 17 40 18 41 const [secret] = await ctx.db
+5 -2
apps/api/src/xrpc/io/pocketenv/secret/getSecret.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import type { Context } from "context"; 3 - import { eq, and, count } from "drizzle-orm"; 3 + import { eq, and, count, or } from "drizzle-orm"; 4 4 import type { Server } from "lexicon"; 5 5 import type { 6 6 QueryParams, ··· 64 64 .where( 65 65 and( 66 66 eq(users.did, auth.credentials.did), 67 - eq(sandboxSecrets.secretId, params.id), 67 + or( 68 + eq(sandboxSecrets.id, params.id), 69 + eq(sandboxSecrets.secretId, params.id), 70 + ), 68 71 ), 69 72 ) 70 73 .execute()
+22 -1
apps/api/src/xrpc/io/pocketenv/variable/deleteVariable.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import { updateSandbox } from "atproto/sandbox"; 3 3 import type { Context } from "context"; 4 - import { eq, or } from "drizzle-orm"; 4 + import { and, eq, or } from "drizzle-orm"; 5 5 import type { Server } from "lexicon"; 6 6 import type { QueryParams } from "lexicon/types/io/pocketenv/variable/deleteVariable"; 7 7 import sandboxVariables from "schema/sandbox-variables"; 8 8 import sandboxes from "schema/sandboxes"; 9 9 import { consola } from "consola"; 10 10 import { createAgent } from "lib/agent"; 11 + import users from "schema/users"; 11 12 12 13 export default function (server: Server, ctx: Context) { 13 14 const deleteVariable = async (params: QueryParams, auth: HandlerAuth) => { 14 15 if (!auth.credentials) { 15 16 throw new XRPCError(401, "Unauthorized"); 17 + } 18 + 19 + const [existingVariable] = await ctx.db 20 + .select() 21 + .from(sandboxVariables) 22 + .leftJoin(sandboxes, eq(sandboxVariables.sandboxId, sandboxes.id)) 23 + .leftJoin(users, eq(sandboxes.userId, users.id)) 24 + .where( 25 + and( 26 + or( 27 + eq(sandboxVariables.id, params.id), 28 + eq(sandboxVariables.variableId, params.id), 29 + ), 30 + eq(users.did, auth.credentials.did), 31 + ), 32 + ) 33 + .execute(); 34 + 35 + if (!existingVariable) { 36 + throw new XRPCError(404, "Variable not found"); 16 37 } 17 38 18 39 const [variable] = await ctx.db
+1 -1
apps/api/src/xrpc/io/pocketenv/variable/getVariable.ts
··· 66 66 eq(users.did, auth.credentials.did), 67 67 or( 68 68 eq(sandboxVariables.variableId, params.id), 69 - eq(variables.name, params.id), 69 + eq(variables.id, params.id), 70 70 ), 71 71 ), 72 72 )
+7
apps/cli/README.md
··· 26 26 ## 🚚 Installation 27 27 28 28 ```sh 29 + # Build and install locally 30 + npm run build && npm install -g . 31 + ``` 32 + 33 + Or install globally 34 + 35 + ```sh 29 36 npm install -g @pocketenv/cli 30 37 ``` 31 38
+10 -18
apps/cli/src/cmd/env.ts
··· 42 42 ); 43 43 44 44 const table = new Table({ 45 - head: [chalk.cyan("NAME"), chalk.cyan("VALUE"), chalk.cyan("CREATED AT")], 45 + head: [ 46 + chalk.cyan("ID"), 47 + chalk.cyan("NAME"), 48 + chalk.cyan("VALUE"), 49 + chalk.cyan("CREATED AT"), 50 + ], 46 51 chars: { 47 52 top: "", 48 53 "top-mid": "", ··· 68 73 69 74 for (const variable of response.data.variables) { 70 75 table.push([ 76 + chalk.greenBright(variable.id), 71 77 chalk.greenBright(variable.name), 72 78 variable.value, 73 79 dayjs(variable.createdAt).fromNow(), ··· 106 112 consola.success("Variable updated successfully"); 107 113 } 108 114 109 - export async function deleteEnv(sandbox: string, key: string) { 115 + export async function deleteEnv(id: string) { 110 116 const token = await getAccessToken(); 111 - const { data } = await client.get<{ sandbox: Sandbox }>( 112 - "/xrpc/io.pocketenv.sandbox.getSandbox", 113 - { 114 - params: { 115 - id: sandbox, 116 - }, 117 - headers: { 118 - Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 119 - }, 120 - }, 121 - ); 122 117 123 - await client.post("/xrpc/io.pocketenv.variable.deleteVariable", { 124 - params: { 125 - sandboxId: data.sandbox.id, 126 - name: key, 127 - }, 118 + await client.post("/xrpc/io.pocketenv.variable.deleteVariable", undefined, { 119 + params: { id }, 128 120 headers: { 129 121 Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 130 122 },
+14 -2
apps/cli/src/cmd/secret.ts
··· 41 41 ); 42 42 43 43 const table = new Table({ 44 - head: [chalk.cyan("NAME"), chalk.cyan("CREATED AT")], 44 + head: [chalk.cyan("ID"), chalk.cyan("NAME"), chalk.cyan("CREATED AT")], 45 45 chars: { 46 46 top: "", 47 47 "top-mid": "", ··· 67 67 68 68 for (const secret of response.data.secrets) { 69 69 table.push([ 70 + chalk.greenBright(secret.id), 70 71 chalk.greenBright(secret.name), 71 72 dayjs(secret.createdAt).fromNow(), 72 73 ]); ··· 110 111 ); 111 112 } 112 113 113 - export async function deleteSecret(sandbox: string, key: string) { 114 + export async function deleteSecret(id: string) { 114 115 const token = await getAccessToken(); 116 + 117 + await client.post("/xrpc/io.pocketenv.secret.deleteSecret", undefined, { 118 + params: { 119 + id, 120 + }, 121 + headers: { 122 + Authorization: `Bearer ${env.POCKETENV_TOKEN || token}`, 123 + }, 124 + }); 125 + 126 + consola.success("Secret deleted successfully"); 115 127 }
+4 -6
apps/cli/src/index.ts
··· 120 120 secret 121 121 .command("delete") 122 122 .aliases(["rm", "remove"]) 123 - .argument("<sandbox>", "the sandbox to delete secrets from") 124 - .argument("<key>", "the key of the secret to delete") 125 - .description("delete a secret from the given sandbox") 123 + .argument("<secret_id>", "the ID of the secret to delete") 124 + .description("delete a secret") 126 125 .action(deleteSecret); 127 126 128 127 const env = program.command("env").description("manage environment variables"); ··· 145 144 env 146 145 .command("delete") 147 146 .aliases(["rm", "remove"]) 148 - .argument("<sandbox>", "the sandbox to delete environment variables from") 149 - .argument("<key>", "the key of the environment variable to delete") 150 - .description("delete an environment variable from the given sandbox") 147 + .argument("<variable_id>", "the ID of the environment variable to delete") 148 + .description("delete an environment variable") 151 149 .action(deleteEnv); 152 150 153 151 const sshkeys = program.command("sshkeys").description("manage SSH keys");