diff --git a/core/src/code_executors/unsafe_local_code_executor.ts b/core/src/code_executors/unsafe_local_code_executor.ts index 97061719a..1457d189e 100644 --- a/core/src/code_executors/unsafe_local_code_executor.ts +++ b/core/src/code_executors/unsafe_local_code_executor.ts @@ -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}`); diff --git a/core/test/code_executors/unsafe_local_code_executor_test.ts b/core/test/code_executors/unsafe_local_code_executor_test.ts index 82a0d2d01..7a38ed2c0 100644 --- a/core/test/code_executors/unsafe_local_code_executor_test.ts +++ b/core/test/code_executors/unsafe_local_code_executor_test.ts @@ -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 @@ -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,