anthropics / anthropics/anthropic-sdk-typescript
BetaLocalFilesystemMemoryTool: rename of the /memories root is not rejected, and unmapped filesystem errors (EINVAL, ENOTDIR) expose the host path to the model
- Linguagem predominante
- TypeScript
- Estrelas
- 2.1k
- Forks
- 403
- Merge médio
- 1d 21h
- PRs com merge (30d)
- 8
Descrição
**SDK version:** `@anthropic-ai/sdk` 0.124.0; the same code is in `src/tools/memory/node.ts` on `main` as of 2026-09-09.
**Runtime:** Node v24.9.0, macOS 15.
The memory tool documentation says the tool description tells Claude it cannot rename the `/memories` directory itself, and that a handler should reject a `rename` whose `old_path` is the memory root. `delete` has that guard (`Cannot delete the /memories directory itself`); `rename` does not. The call reaches `fs.rename`, fails with the operating system's error, and `BetaToolRunner` forwards that message to the model as the `tool_result`. The message contains the absolute path of the store on the host.
The same happens for any filesystem error the class doesn't map to a documented message. `validateNoSymlinkEscape` rethrows everything except `ENOENT`, so a path with a regular file as one of its components produces a raw `ENOTDIR` from `realpath`, and renaming a directory into itself produces a raw `EINVAL`.
### Repro
```ts
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { BetaLocalFilesystemMemoryTool } from '@anthropic-ai/sdk/tools/memory/node';
const base = await fs.mkdtemp(path.join(os.tmpdir(), 'memory-repro-'));
const memory = await BetaLocalFilesystemMemoryTool.init(base);
await memory.create({ command: 'create', path: '/memories/a/file.md', file_text: 'x' });
await memory.rename({ command: 'rename', old_path: '/memories', new_path: '/memories/archive' });
// Error: EINVAL: invalid argument, rename '/memory-repro-BHnSsF/memories' -> '/memory-repro-BHnSsF/memories/archive'
await memory.create({ command: 'create', path: '/memories/a/file.md/child.md', file_text: 'y' });
// Error: ENOTDIR: not a directory, realpath '/memory-repro-cS9ecm/memories/a/file.md/child.md'
await memory.rename({ command: 'rename', old_path: '/memories/a', new_path: '/memories/a/sub' });
// Error: EINVAL: invalid argument, rename '/memory-repro-cS9ecm/memories/a' -> '/memory-repro-cS9ecm/memories/a/sub'
```
`` stands in for the real absolute path, which is what the model receives.
None of these lets a path leave the store, since both `rename` arguments go through `validatePath`. The problems are a missing guard and an information leak of the host's directory layout into the conversation.
### Expected
- `rename` with `old_path` or `new_path` equal to `/memories` throws `Cannot rename the /memories directory itself`, mirroring `delete`.
- Filesystem errors that aren't already mapped to a documented message are rethrown naming the virtual `/memories/...` path, for example `A parent of /memories/a/file.md/child.md is a file, not a directory`, never the resolved disk path.
Guia de contribuição
Direção de pesquisa
Start in src/tools/memory/node.ts, comparing rename handling with the existing delete guard and tracing validateNoSymlinkEscape and filesystem-error handling. Add regression coverage for root renames and unmapped ENOTDIR/EINVAL cases, then verify that errors name virtual /memories paths without exposing the resolved host path.
Escrita pelo modelo de indexação a partir do texto da issue.
Avaliação
- Stack de tecnologia
- node.js, typescript
- Domínio
- security, tooling
- Tipo de issue
- Bug
- Dificuldade
- 3/5
- Tempo estimado
- 1-2 dias
- Status de atividade
- Ativa
- Clareza
- Claramente especificada
- Facilidade para iniciantes
- 76/100