Skip to content

Commit 95ce4a8

Browse files
committed
chore(sandbox): WS9 dead-code cleanup + WS5b egress canary
WS9 (cosmetic/contract cleanup): remove the deprecated Blaxel-era localBucket flag everywhere (it was carried but ignored in V2; 0 refs remain), delete the dead Blaxel normalization-helper cluster in project-sandbox-runtime.ts (~220 lines, all def-only / no live callers, plus the transitively-orphaned private helpers), and drop the client-side backup TTL that the Daytona DO ignores (disk-is-the-store). localBucket on DirectoryBackupHandle was a TS interface field, not a DB column — no migration. WS5b egress canary (apps/webhooks-worker/src/egress-canary.ts): a daily cron (03:30 UTC) that detects if Daytona starts enforcing Tier-2 egress limits. Zero-cost piggyback — it runs a curl probe inside a sandbox that is ALREADY started (already billing a user's run) and SKIPS entirely if none is running, never spinning up a dedicated sandbox. Alerts via the existing HMAC postInternalAlert only when both probe hosts fail; transient toolbox errors retry via the ops workflow step instead of false-alarming. Also includes in-tree Composio hardening (gateway/webhooks/agent-core): pin baseURL=backend.composio.dev, allowTracking:false, and request the documented max tool-list page so large toolkits aren't under-enumerated.
1 parent 4727665 commit 95ce4a8

13 files changed

Lines changed: 333 additions & 248 deletions

File tree

apps/agent-worker/src/durable-objects/project-sandbox-runtime.ts

Lines changed: 0 additions & 220 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ export interface BackupOptions {
44
dir: string;
55
excludes?: string[];
66
gitignore?: boolean;
7-
localBucket?: boolean;
87
name?: string;
98
ttl?: number;
109
}
1110

1211
export interface DirectoryBackup {
1312
dir: string;
1413
id: string;
15-
localBucket?: boolean;
1614
}
1715

1816
export interface ExecOptions {
@@ -175,22 +173,14 @@ export const ProjectUnexposePortInputSchema = z
175173
export const ProjectCreateBackupInputSchema = z
176174
.object({
177175
dir: WorkspacePathSchema.default("/workspace"),
178-
localBucket: z.boolean().optional(),
179176
name: z.string().min(1).max(200).optional(),
180-
ttl: z
181-
.number()
182-
.int()
183-
.positive()
184-
.max(90 * 24 * 60 * 60)
185-
.default(30 * 24 * 60 * 60),
186177
})
187178
.strict();
188179

189180
export const ProjectBackupHandleSchema = z
190181
.object({
191182
id: z.string().min(1),
192183
dir: WorkspacePathSchema,
193-
localBucket: z.boolean().optional(),
194184
})
195185
.strict();
196186

@@ -292,7 +282,6 @@ export interface NormalizedExposePortResult {
292282
export interface NormalizedBackupHandle {
293283
id: string;
294284
dir: string;
295-
localBucket?: boolean;
296285
}
297286

298287
function isSafeWorkspacePath(path: string): boolean {
@@ -320,219 +309,10 @@ function normalizeWorkspacePath(path: string): string {
320309
return `/${parts.join("/")}${path.endsWith("/") ? "/" : ""}`;
321310
}
322311

323-
interface SandboxExecutionResult {
324-
logs: {
325-
stdout: string[];
326-
stderr: string[];
327-
};
328-
error?: unknown;
329-
}
330-
331-
function isSandboxExecutionResult(value: unknown): value is SandboxExecutionResult {
332-
if (!value || typeof value !== "object") {
333-
return false;
334-
}
335-
const candidate = value as Record<string, unknown>;
336-
const logs = candidate["logs"];
337-
if (!logs || typeof logs !== "object") {
338-
return false;
339-
}
340-
const typedLogs = logs as Record<string, unknown>;
341-
return Array.isArray(typedLogs["stdout"]) && Array.isArray(typedLogs["stderr"]);
342-
}
343-
344-
function toPlainExecutionResult(value: unknown): unknown {
345-
if (
346-
value &&
347-
typeof value === "object" &&
348-
"toJSON" in value &&
349-
typeof (value as { toJSON?: unknown }).toJSON === "function"
350-
) {
351-
return (value as { toJSON(): unknown }).toJSON();
352-
}
353-
return value;
354-
}
355-
356-
function normalizeEncoding(value: unknown): "utf8" | "base64" {
357-
return value === "base64" ? "base64" : "utf8";
358-
}
359-
360312
function shellQuote(arg: string): string {
361313
return `'${arg.replaceAll("'", "'\\''")}'`;
362314
}
363315

364316
export function commandToShellString(command: string[]): string {
365317
return command.map(shellQuote).join(" ");
366318
}
367-
368-
export function createExecOptions(input: ProjectExecInput): ExecOptions {
369-
const options: ExecOptions = {};
370-
if (input.cwd) {
371-
options.cwd = input.cwd;
372-
}
373-
if (input.env) {
374-
options.env = input.env;
375-
}
376-
if (input.timeoutMs) {
377-
options.timeout = input.timeoutMs;
378-
}
379-
return options;
380-
}
381-
382-
export function createProcessOptions(input: ProjectStartProcessInput): ProcessOptions {
383-
const options: ProcessOptions = createExecOptions(input);
384-
options.autoCleanup = false;
385-
if (input.keepAliveTimeoutMs !== undefined) {
386-
options.keepAliveTimeoutMs = input.keepAliveTimeoutMs;
387-
}
388-
if (input.maxRestarts !== undefined) {
389-
options.maxRestarts = input.maxRestarts;
390-
}
391-
if (input.processId) {
392-
options.processId = input.processId;
393-
}
394-
if (input.restartOnFailure !== undefined) {
395-
options.restartOnFailure = input.restartOnFailure;
396-
}
397-
return options;
398-
}
399-
400-
export function createRunCodeOptions(input: ProjectRunCodeInput): RunCodeOptions {
401-
const options: RunCodeOptions = { language: input.language };
402-
if (input.env) {
403-
options.envVars = input.env;
404-
}
405-
return options;
406-
}
407-
408-
export function normalizeExecResult(value: unknown): NormalizedExecResult {
409-
const parsed = z
410-
.object({
411-
command: z.string(),
412-
duration: z.number().nonnegative().optional(),
413-
exitCode: z.number().int(),
414-
stderr: z.string(),
415-
stdout: z.string(),
416-
success: z.boolean(),
417-
})
418-
.passthrough()
419-
.parse(toPlainExecutionResult(value));
420-
return {
421-
command: parsed.command,
422-
stdout: parsed.stdout,
423-
stderr: parsed.stderr,
424-
success: parsed.success,
425-
exitCode: parsed.exitCode,
426-
...(parsed.duration === undefined ? {} : { durationMs: parsed.duration }),
427-
};
428-
}
429-
430-
export function normalizeReadFileResult(value: unknown): NormalizedReadFileResult {
431-
const parsed = z
432-
.object({
433-
content: z.string(),
434-
encoding: z.enum(["utf-8", "base64"]).optional(),
435-
path: z.string(),
436-
size: z.number().int().nonnegative().optional(),
437-
})
438-
.passthrough()
439-
.parse(value);
440-
return {
441-
path: parsed.path,
442-
content: parsed.content,
443-
encoding: normalizeEncoding(parsed.encoding),
444-
...(parsed.size === undefined ? {} : { size: parsed.size }),
445-
};
446-
}
447-
448-
export function normalizeWriteFileResult(value: unknown): NormalizedWriteFileResult {
449-
const parsed = z.object({ path: z.string(), success: z.boolean() }).passthrough().parse(value);
450-
return {
451-
path: parsed.path,
452-
success: parsed.success,
453-
};
454-
}
455-
456-
export function normalizeListFilesResult(value: unknown): NormalizedListFilesResult {
457-
const parsed = z
458-
.object({
459-
path: z.string(),
460-
files: z.array(
461-
z
462-
.object({
463-
absolutePath: z.string(),
464-
modifiedAt: z.string(),
465-
name: z.string(),
466-
relativePath: z.string(),
467-
size: z.number().int().nonnegative(),
468-
type: z.enum(["file", "directory", "symlink", "other"]),
469-
})
470-
.passthrough(),
471-
),
472-
})
473-
.passthrough()
474-
.parse(value);
475-
return {
476-
path: parsed.path,
477-
files: parsed.files.map((file) => ({
478-
name: file.name,
479-
path: file.absolutePath,
480-
relativePath: file.relativePath,
481-
type: file.type,
482-
size: file.size,
483-
modifiedAt: file.modifiedAt,
484-
})),
485-
};
486-
}
487-
488-
export function normalizeExposePortResult(value: unknown): NormalizedExposePortResult {
489-
const parsed = z
490-
.object({
491-
name: z.string().optional(),
492-
port: z.number().int().positive(),
493-
token: z.string().optional(),
494-
url: z.string().url(),
495-
})
496-
.passthrough()
497-
.parse(value);
498-
return {
499-
port: parsed.port,
500-
...(parsed.token === undefined ? {} : { token: parsed.token }),
501-
url: parsed.url,
502-
...(parsed.name === undefined ? {} : { name: parsed.name }),
503-
};
504-
}
505-
506-
export function normalizeDirectoryBackup(value: DirectoryBackup): NormalizedBackupHandle {
507-
const parsed = ProjectBackupHandleSchema.parse(value);
508-
return {
509-
id: parsed.id,
510-
dir: parsed.dir,
511-
...(parsed.localBucket === undefined ? {} : { localBucket: parsed.localBucket }),
512-
};
513-
}
514-
515-
export function createBackupOptions(input: ProjectCreateBackupInput): BackupOptions {
516-
const parsedInput = ProjectCreateBackupInputSchema.parse(input);
517-
return {
518-
dir: parsedInput.dir,
519-
ttl: parsedInput.ttl,
520-
gitignore: true,
521-
excludes: ["node_modules", ".git", ".next", ".turbo"],
522-
...(parsedInput.localBucket === undefined ? {} : { localBucket: parsedInput.localBucket }),
523-
...(parsedInput.name ? { name: parsedInput.name } : {}),
524-
};
525-
}
526-
527-
export function normalizeRunCodeResult(value: unknown): NormalizedRunCodeResult {
528-
const serialized = toPlainExecutionResult(value);
529-
if (!isSandboxExecutionResult(serialized)) {
530-
throw new Error("Unexpected sandbox execution result.");
531-
}
532-
return {
533-
stdout: serialized.logs.stdout.join(""),
534-
stderr: serialized.logs.stderr.join(""),
535-
success: !serialized.error,
536-
exitCode: serialized.error ? 1 : 0,
537-
};
538-
}

apps/gateway-worker/src/integrations.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ export async function listIntegrationSummaries(
9090
export async function connectIntegration(input: ConnectIntegrationInput): Promise<Response> {
9191
const apiKey = await readRequiredSecret(input.env.COMPOSIO_API_KEY, "COMPOSIO_API_KEY");
9292
const authConfigId = await readAuthConfigId(input.env, input.integration);
93-
const composio = new Composio({ apiKey });
93+
const composio = new Composio({
94+
allowTracking: false,
95+
apiKey,
96+
baseURL: "https://backend.composio.dev",
97+
});
9498
const callbackUrl = resolveCallbackUrl(input.request);
9599
const connection = await createConnectionLink({
96100
authConfigId,
@@ -119,7 +123,11 @@ export async function deleteIntegration(input: DeleteIntegrationInput): Promise<
119123
return;
120124
}
121125
const apiKey = await readRequiredSecret(input.env.COMPOSIO_API_KEY, "COMPOSIO_API_KEY");
122-
const composio = new Composio({ apiKey });
126+
const composio = new Composio({
127+
allowTracking: false,
128+
apiKey,
129+
baseURL: "https://backend.composio.dev",
130+
});
123131
await deleteConnectedAccount(composio, record.composioConnectionId);
124132
await deleteUserIntegration(input.db, {
125133
integration: input.integration,

apps/webhooks-worker/src/analytics-watchdog.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ async function queryAnalyticsEngine(input: {
161161
return parseSqlResponse(await response.json()).data;
162162
}
163163

164-
async function postInternalAlert(
164+
export async function postInternalAlert(
165165
env: AnalyticsWatchdogEnv,
166166
alert: InternalAlertPayload,
167167
): Promise<void> {

0 commit comments

Comments
 (0)