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

Aberta
#1,182 0 comentários 0 reações 0 responsáveis Ver no GitHub
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

Abrir o 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

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.