Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions core/src/code_executors/unsafe_local_code_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,10 @@ async function createTempScriptFile(
language: CodeExecutionLanguage,
shellCommandPath?: string,
): Promise<{filePath: string; tempDir: string}> {
const tempDir = path.join(
os.tmpdir(),
'adk_js_unsafe_code_executor',
Date.now().toString() + '_' + Math.random().toString(36).slice(2),
// mkdtemp names the directory itself and creates it exclusively at 0o700.
const tempDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'adk_js_unsafe_code_executor_'),
);
await fs.mkdir(tempDir, {recursive: true});

const ext = getExtensionForLanguage(language, shellCommandPath) || '.js';
const filePath = path.join(tempDir, `script${ext}`);
Expand Down
37 changes: 37 additions & 0 deletions core/test/code_executors/unsafe_local_code_executor_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@google/adk';
import {EventEmitter} from 'node:events';
import * as os from 'node:os';
import * as path from 'node:path';
import {beforeEach, describe, expect, it, vi} from 'vitest';

// Only `spawn` is mocked; it defaults to the real implementation (see
Expand Down Expand Up @@ -90,6 +91,42 @@ describe('UnsafeLocalCodeExecutor', () => {
expect(result.stderr).toBe('');
});

// The script runs with the temporary directory as its cwd, so it can report
// the name and mode the executor actually created.
it('creates a private, unpredictable temporary directory', async () => {
const params: ExecuteCodeParams = {
invocationContext,
codeExecutionInput: {
code: [
'const fs = require("node:fs");',
'const dir = process.cwd();',
'const mode = (fs.statSync(dir).mode & 0o777).toString(8);',
'console.log(JSON.stringify({dir, mode}));',
].join('\n'),
language: CodeExecutionLanguage.JAVASCRIPT,
inputFiles: [],
},
};

const firstResult = await executor.executeCode(params);
const secondResult = await executor.executeCode(params);
expect(firstResult.stderr).toBe('');
expect(secondResult.stderr).toBe('');

const first = JSON.parse(firstResult.stdout);
const second = JSON.parse(secondResult.stdout);

// mkdtemp appends six random characters to the prefix it is given.
expect(path.basename(first.dir)).toMatch(
/^adk_js_unsafe_code_executor_.{6}$/,
);
expect(second.dir).not.toBe(first.dir);

if (os.platform() !== 'win32') {
expect(first.mode).toBe('700');
}
});

it('should capture stderr', async () => {
const params: ExecuteCodeParams = {
invocationContext,
Expand Down
Loading