MCP, MCP server
io.github.camgitt/memoir
Portable, project-scoped memory and session handoffs for coding agents.
Compatibility: Not stated.
Grade C
One Medium finding. Read the report before installing.
Not a guarantee. A hygiene check of the source at the commit shown, not a security audit.
SECRETS_PATH_CREDENTIAL
Why this grade
- SECRETS_PATH_CREDENTIAL Medium
Mentions a credential store path (~/.ssh, id_rsa, .npmrc, .aws/credentials, credentials.json, .netrc).
scripts/check-clean-for-publish.mjs, line 32
'\npublish blocked: not logged in to npm (the token in ~/.npmrc has expired).\n' +
Any High, or two or more Medium findings, give D. One Medium gives C. Low findings or a security Note give B. Each code names a finding behind the letter.
Grades are a hygiene check of the source at the commit shown, on the date checked, not a security audit or a guarantee. The free directory grades are automated. Before release, a PlugVet engineer reviews every paid report and any grade we hold back, including all F grades, against the source code and the evidence we quote.
What was checked
Plain notes from the hygiene checks.
Permissions
Concern
can delete files.
PERMISSION_DELETE_FILES
test-mcp-helpers.mjs, line 36
await fs.rm(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
evals/retrieval-performance.mjs, line 49
await fs.rm(source, { recursive: true, force: true });
evals/retrieval-performance.mjs, line 50
await fs.rm(memoryRoot, { recursive: true, force: true });
Show more
evals/retrieval-performance.mjs, line 98
await fs.rm(scratch, { recursive: true, force: true });
scripts/test-packed.mjs, line 57
await fs.rm(path.join(configDir,'memories'),{recursive:true,force:true});
scripts/test-packed.mjs, line 58
await fs.rm(path.join(configDir,'session.json'),{force:true});
scripts/test-packed.mjs, line 108
await fs.rm(scratch,{recursive:true,force:true});
src/commands/consolidate.js, line 500
await fs.unlink(await safePath(adapter.source, file.path));
Install and update
Pass
No install-time script found, and dependencies are pinned or absent.
Network
Concern
can reach a host that is not written as a fixed address. can reach attacker.invalid. The description does not name that host. can reach registry.npmjs.org. The description does not name that host. can reach generativelanguage.googleapis.com. The description does not name that host.
NETWORK_NO_FIXED_HOST NETWORK_UNNAMED_HOST
test-work-recovery.mjs, line 87
const rejected = await fetch(viewer.origin + '/api/action', { method: 'POST', headers, body: JSON.stringify(input) });
test-work-recovery.mjs, line 89
const accepted = await fetch(viewer.origin + '/api/action', { method: 'POST', headers, body: JSON.stringify({ ...input, expected_recovery: result.recovery_id }) });
test-work-view.mjs, line 18
const send = (input, headers = {}) => fetch(view.origin+'/api/action', { method:'POS
Show more
test-work-view.mjs, line 24
const response = await fetch(view.origin);
test-work-view.mjs, line 31
for(const headers of [{},{Authorization:'Bearer incorrect'}]) assert.equal((await fetch(view.origin+'/api/state',{headers})).status,401);
test-work-view.mjs, line 32
const response=await fetch(view.origin+'/api/state',{headers:auth}); assert.equal(response.status,200); assert.equal((await response.json()).records[0].answer,'Local review');
test-work-view.mjs, line 37
const request = http.get(view.origin+'/api/state', {headers:{...auth,Host:'attacker.invalid'}}, response => {
test-work-view.mjs, line 43
assert.equal((await fetch(view.origin+'/api/state',{headers:{...auth,Origin:'https://attacker.invalid'}})).status,403);
Secrets
Concern
reads a credential or environment file.
SECRETS_ENV_READ SECRETS_PATH_CREDENTIAL SECRETS_PATH_DOTENV
test-audit-reliability.mjs, line 373
await fs.writeFile(path.join(project, '.env'), 'PASSWORD=synthetic-private');
test-audit-reliability.mjs, line 377
assert.ok(manifest.projects[0].omitted.some(f => f.path === '.env'));
scripts/check-clean-for-publish.mjs, line 32
'\npublish blocked: not logged in to npm (the token in ~/.npmrc has expired).\n' +
Show more
test-work-handoff.mjs, line 88
for (const input of ['../outside', '.env', ...(process.platform !== 'win32' ? ['link'] : [])]) await assert.rejects(runWorkCheck(repo, { ...check, files: [input] }));
src/config.js, line 133
return raw?.geminiApiKey || process.env.GEMINI_API_KEY || null;
src/telemetry.js, line 23
const POSTHOG_KEY = process.env.MEMOIR_POSTHOG_KEY || 'phc_vS7ZKfmZAcGnaCE7Zt4hvwFioJBs6jr8gutyapDpqFXW';
src/cloud/constants.js, line 2
export const SUPABASE_ANON_KEY = process.env.MEMOIR_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ
src/commands/doctor.js, line 13
const SENSITIVE_FILENAMES = ['.env', 'credentials', 'token.json'];
src/migrate/translator.js, line 8
if (process.env.GEMINI_API_KEY) {
src/migrate/translator.js, line 9
return process.env.GEMINI_API_KEY;
src/work/store.js, line 232
(rel.startsWith('.memoir/') || /(^|\/)(\.env(?:\..*)?|credentials[^/]*|id_rsa|id_ed2
src/workspace/tracker.js, line 12
const sensitive = /(^|\/)(\.env(?:\..*)?|\.npmrc|\.pypirc|credentials[^/]*|secrets?[^/]*|id_rsa|id_ed25519)$|\.(pem|key|p12|pfx)$/i;
.gitignore, line 3
.DS_Store
Agent instructions
Pass
No instruction to ignore earlier instructions or to hide a step.
Obfuscation
Pass
No encoded or minified payload found.
Licence
Pass
A licence file is present (LICENSE).
Maintainer activity
Pass
The latest commit is within the last 365 days.
Note
Hygiene check of commit 72cce64d31dd. can delete files. can reach a host that is not written as a fixed address. can reach attacker.invalid. The description does not name that host. can reach registry.npmjs.org. The description does not name that host. can reach generativelanguage.googleapis.com. The description does not name that host. reads a credential or environment file.
Date and source
Date checked:
github.com
Commit graded: 72cce64d31ddbf49350c1593a7edec030d355cb5
This link goes to the original host. PlugVet does not host or check what you download from there, and installing it is your own decision and risk.