mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
review fixes: block Codeman's own credential-bearing JSON, make the inside-anchor test bite
Widening the servable extensions to EDITABLE_EXTENSIONS made ~/.codeman JSON previewable for the first time, and the blocklist named only state.json. But settings.json holds a credential BY SCHEMA (voiceSettings.apiKey), push-keys.json holds the VAPID PRIVATE key, and intents.json is written 0600 precisely because captured prompts can carry secrets — all three were one authenticated click away once an agent printed the path. Blocked alongside state.json, whose rule now also catches state-* siblings. The never-re-cuts-inside-an-anchor test used an unmatchable URL tail, so it passed with the guard deleted; the fixture now carries a matchable /tmp path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,12 +80,23 @@ const SENSITIVE_PATTERNS: RegExp[] = [
|
||||
/\/\.claude\/\.credentials\.json$/,
|
||||
/\/\.codeman[^/]*\/hook-secret$/,
|
||||
/\/\.codeman[^/]*\/users\.json$/,
|
||||
// Codeman's own state file. Named once `.json` became previewable outside the
|
||||
// workspace: `SessionState.envOverrides` persists whatever the user set for a
|
||||
// session, and the env allowlist admits key-shaped names (`GEMINI_API_KEY`,
|
||||
// `CLAUDE_CODE_*`), so this file can hold a live credential. Same reasoning
|
||||
// as the two entries above, and it leaves the rest of ~/.codeman attachable.
|
||||
/\/\.codeman[^/]*\/state\.json$/,
|
||||
// Codeman's own state files. Named once `.json` became previewable outside
|
||||
// the workspace: `SessionState.envOverrides` persists whatever the user set
|
||||
// for a session, and the env allowlist admits key-shaped names
|
||||
// (`GEMINI_API_KEY`, `CLAUDE_CODE_*`), so state can hold a live credential.
|
||||
// `state[^/]*` rather than `state`: siblings like state-inner.json carry the
|
||||
// same payload. Same reasoning as the two entries above, and it leaves the
|
||||
// rest of ~/.codeman attachable.
|
||||
/\/\.codeman[^/]*\/state[^/]*\.json$/,
|
||||
// settings.json holds a credential BY SCHEMA (`voiceSettings.apiKey`, the
|
||||
// Deepgram key); push-keys.json holds the VAPID PRIVATE key (enough to forge
|
||||
// push notifications to every subscribed device); intents.json is written
|
||||
// 0600 precisely because captured prompts can contain secrets, and is
|
||||
// deliberately kept out of /api/search — it must not be readable through a
|
||||
// different route instead.
|
||||
/\/\.codeman[^/]*\/settings\.json$/,
|
||||
/\/\.codeman[^/]*\/push-keys\.json$/,
|
||||
/\/\.codeman[^/]*\/intents\.json$/,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,11 +100,14 @@ describe('response viewer file-path linkifier', () => {
|
||||
it('never re-cuts text already inside an anchor', () => {
|
||||
// marked autolinks URLs; a path-looking tail inside one must stay whole, and
|
||||
// a nested <a> is invalid markup that would swallow the outer link's click.
|
||||
const root = linkify('<p><a href="https://example.com/x/y.png">https://example.com/x/y.png</a></p>');
|
||||
// ⚠️ The URL's tail MUST be a string the pattern matches on its own
|
||||
// (`/tmp/...` here): with an unmatchable tail this test passes with the
|
||||
// inside-anchor guard deleted, i.e. it pins nothing.
|
||||
const root = linkify('<p><a href="https://example.com/tmp/shot.png">https://example.com/tmp/shot.png</a></p>');
|
||||
|
||||
expect(paths(root)).toHaveLength(0);
|
||||
expect(root.querySelectorAll('a')).toHaveLength(1);
|
||||
expect(root.querySelector('a')!.getAttribute('href')).toBe('https://example.com/x/y.png');
|
||||
expect(root.querySelector('a')!.getAttribute('href')).toBe('https://example.com/tmp/shot.png');
|
||||
});
|
||||
|
||||
it('leaves text with no path untouched', () => {
|
||||
|
||||
@@ -79,6 +79,14 @@ describe('isSensitivePath', () => {
|
||||
// workspace.
|
||||
['codeman state file', `${HOME}/.codeman/state.json`],
|
||||
['codeman state file on a named instance', `${HOME}/.codeman-beta/state.json`],
|
||||
['codeman state sibling (same payload)', `${HOME}/.codeman/state-inner.json`],
|
||||
// settings.json holds voiceSettings.apiKey by schema; push-keys.json holds
|
||||
// the VAPID PRIVATE key; intents.json is 0600 because captured prompts can
|
||||
// contain secrets and is deliberately kept out of /api/search.
|
||||
['codeman settings (Deepgram key)', `${HOME}/.codeman/settings.json`],
|
||||
['codeman push keys (VAPID private)', `${HOME}/.codeman/push-keys.json`],
|
||||
['codeman intent profiles', `${HOME}/.codeman/intents.json`],
|
||||
['codeman intents on a named instance', `${HOME}/.codeman-beta/intents.json`],
|
||||
];
|
||||
|
||||
it.each(blocked)('blocks the %s', (_label, path) => {
|
||||
|
||||
Reference in New Issue
Block a user