@theronap/cortex-mcp 0.9.32 → 0.9.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/capture.mjs +5 -3
- package/lib/diagnose.mjs +24 -0
- package/lib/redact.mjs +8 -0
- package/lib/redact.test.mjs +33 -0
- package/lib/setup.mjs +11 -18
- package/package.json +1 -1
package/lib/capture.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from 'fs'
|
|
|
2
2
|
import { homedir } from 'os'
|
|
3
3
|
import { resolve } from 'path'
|
|
4
4
|
import { createHash } from 'crypto'
|
|
5
|
-
import { fetchCortex, classify, resolveBase } from './diagnose.mjs'
|
|
5
|
+
import { fetchCortex, classify, resolveBase, readWiredToken } from './diagnose.mjs'
|
|
6
6
|
import { extractSession } from './edge_extract.mjs'
|
|
7
7
|
import { extractTyped } from './extract_typed.mjs'
|
|
8
8
|
import { redactSecrets } from './redact.mjs'
|
|
@@ -86,8 +86,10 @@ export async function runCapture() {
|
|
|
86
86
|
// would recurse (and re-ingest the summarizer's prompt as a phantom session). Bail immediately.
|
|
87
87
|
if (process.env.CORTEX_SUMMARIZING) { process.stderr.write('cortex: summarizer subprocess, skipping\n'); return }
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// Env first (explicit override / legacy inlined hooks), else the token wired into this machine's
|
|
90
|
+
// MCP config — so hook commands carry no secret (token-hygiene, 2026-07-02).
|
|
91
|
+
const token = process.env.CORTEX_TOKEN || readWiredToken()
|
|
92
|
+
if (!token) { process.stderr.write('cortex: no CORTEX_TOKEN in env or wired config, skipping\n'); return }
|
|
91
93
|
const base = resolveBase(process.env.CORTEX_URL)
|
|
92
94
|
|
|
93
95
|
let hook = {}
|
package/lib/diagnose.mjs
CHANGED
|
@@ -9,9 +9,33 @@
|
|
|
9
9
|
// 4xx/5xx means infrastructure handled the request, not Cortex auth — re-running setup or
|
|
10
10
|
// regenerating the token will not help; it is usually transient and worth a retry.
|
|
11
11
|
|
|
12
|
+
import { readFileSync } from 'fs'
|
|
13
|
+
import { homedir } from 'os'
|
|
14
|
+
import { join } from 'path'
|
|
15
|
+
|
|
12
16
|
export const isUuid = (s) =>
|
|
13
17
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s ?? '')
|
|
14
18
|
|
|
19
|
+
// Find the token already wired on this machine (Claude's ~/.claude.json first, then Codex's
|
|
20
|
+
// config.toml). Lives HERE — the shared leaf module — so both setup/repair and the capture hook
|
|
21
|
+
// use one resolver: hook commands carry NO inline token (token-hygiene, 2026-07-02 — an inlined
|
|
22
|
+
// `CORTEX_TOKEN=…` in hooks.json shows the secret to anything that reads or greps the file, and
|
|
23
|
+
// from there to captured transcripts); `capture` resolves the token itself from the same config
|
|
24
|
+
// the MCP server already holds.
|
|
25
|
+
export function readWiredToken() {
|
|
26
|
+
try {
|
|
27
|
+
const cfg = JSON.parse(readFileSync(join(homedir(), '.claude.json'), 'utf8'))
|
|
28
|
+
const t = cfg?.mcpServers?.cortex?.env?.CORTEX_TOKEN
|
|
29
|
+
if (t) return t
|
|
30
|
+
} catch { /* fall through */ }
|
|
31
|
+
try {
|
|
32
|
+
const toml = readFileSync(join(homedir(), '.codex', 'config.toml'), 'utf8')
|
|
33
|
+
const m = toml.match(/\[mcp_servers\.cortex\.env\][\s\S]*?CORTEX_TOKEN\s*=\s*"([^"]+)"/)
|
|
34
|
+
if (m) return m[1]
|
|
35
|
+
} catch { /* fall through */ }
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
15
39
|
// The production alias — exempt from Vercel Deployment Protection.
|
|
16
40
|
export const CANONICAL_BASE = 'https://cortex-console.vercel.app'
|
|
17
41
|
|
package/lib/redact.mjs
CHANGED
|
@@ -21,6 +21,14 @@ const PATTERNS = [
|
|
|
21
21
|
[/xox[baprs]-[A-Za-z0-9-]{10,}/g, '[REDACTED:slack]'],
|
|
22
22
|
[/eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, '[REDACTED:jwt]'],
|
|
23
23
|
[/(Bearer\s+)[A-Za-z0-9._-]{20,}/g, '$1[REDACTED]'],
|
|
24
|
+
// Cortex's own login token wherever it appears in KEY=value / KEY: value form (hooks.json,
|
|
25
|
+
// config.toml, shell commands — the 2026-07-02 finding: a grep of hooks.json put the live token
|
|
26
|
+
// in a transcript and nothing below caught it). Specific pattern first for the accurate label.
|
|
27
|
+
[/(CORTEX_TOKEN["']?\s*[=:]\s*["']?)[0-9a-fA-F][0-9a-fA-F-]{30,}/g, '$1[REDACTED:cortex-token]'],
|
|
28
|
+
// Generic secret-shaped assignment: an UPPER_SNAKE name ending in TOKEN/SECRET/PASSWORD/
|
|
29
|
+
// API_KEY/APIKEY assigned a ≥16-char value. Conservative: the name-suffix + length floor keep
|
|
30
|
+
// ordinary prose, short placeholders, and bare UUIDs (record ids) intact.
|
|
31
|
+
[/([A-Z][A-Z0-9_]{2,}(?:TOKEN|SECRET|PASSWORD|API_KEY|APIKEY)["']?\s*[=:]\s*["']?)[A-Za-z0-9._/+-]{16,}/g, '$1[REDACTED:env]'],
|
|
24
32
|
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, '[REDACTED:private-key]'],
|
|
25
33
|
]
|
|
26
34
|
|
package/lib/redact.test.mjs
CHANGED
|
@@ -48,6 +48,39 @@ test('leaves ordinary prose and record UUIDs untouched', () => {
|
|
|
48
48
|
expect(redactSecrets(prose)).toBe(prose)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
+
// The 2026-07-02 finding: a grep of ~/.codex/hooks.json printed the live CORTEX_TOKEN (a bare
|
|
52
|
+
// UUID — no pattern above catches those) into a transcript. The inline form is gone from hooks
|
|
53
|
+
// (capture self-resolves now), but transcripts can still carry config lines from ANY tool read.
|
|
54
|
+
test('redacts CORTEX_TOKEN in env-assignment and TOML/JSON forms', () => {
|
|
55
|
+
const uuid = 'ee0455d0-49bd-4141-984d-ece9311f9407'
|
|
56
|
+
const cases = [
|
|
57
|
+
`CORTEX_TOKEN=${uuid} npx -y @theronap/cortex-mcp@latest capture`, // legacy hook command
|
|
58
|
+
`"CORTEX_TOKEN": "${uuid}"`, // ~/.claude.json
|
|
59
|
+
`CORTEX_TOKEN = "${uuid}"`, // config.toml
|
|
60
|
+
]
|
|
61
|
+
for (const s of cases) {
|
|
62
|
+
const out = redactSecrets(s)
|
|
63
|
+
expect(out).toContain('[REDACTED:cortex-token]')
|
|
64
|
+
expect(out).not.toContain(uuid)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('redacts generic secret-shaped env assignments', () => {
|
|
69
|
+
const out = redactSecrets('export SUPABASE_SERVICE_SECRET=abcDEF0123456789xyz && run')
|
|
70
|
+
expect(out).toBe('export SUPABASE_SERVICE_SECRET=[REDACTED:env] && run')
|
|
71
|
+
const out2 = redactSecrets('MY_API_KEY: "0123456789abcdef0123"')
|
|
72
|
+
expect(out2).toContain('[REDACTED:env]')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('generic env pattern leaves short placeholders and prose intact', () => {
|
|
76
|
+
const cases = [
|
|
77
|
+
'set YOUR_TOKEN=xxx in the env', // short placeholder value
|
|
78
|
+
'the CORTEX_TOKEN is stored in ~/.claude.json', // prose, no assignment value
|
|
79
|
+
'record 5203eacd-321b-43f7-bae2-4d6e7cae96ab', // bare UUID, no key name
|
|
80
|
+
]
|
|
81
|
+
for (const s of cases) expect(redactSecrets(s)).toBe(s)
|
|
82
|
+
})
|
|
83
|
+
|
|
51
84
|
test('handles empty / non-string input safely', () => {
|
|
52
85
|
expect(redactSecrets('')).toBe('')
|
|
53
86
|
expect(redactSecrets(null)).toBe(null)
|
package/lib/setup.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'fs'
|
|
2
2
|
import { homedir } from 'os'
|
|
3
3
|
import { join, dirname } from 'path'
|
|
4
|
-
import { checkToken, resolveBase } from './diagnose.mjs'
|
|
4
|
+
import { checkToken, resolveBase, readWiredToken } from './diagnose.mjs'
|
|
5
5
|
import { installSkills } from './skills.mjs'
|
|
6
6
|
|
|
7
7
|
// One-command employee onboarding. Wires both:
|
|
@@ -155,7 +155,10 @@ export async function runSetup(argv, version) {
|
|
|
155
155
|
let existingHooks
|
|
156
156
|
try { existingHooks = readJson(codexHooks) } catch { existingHooks = {} } // malformed → start fresh, don't block
|
|
157
157
|
const bak = backup(codexHooks)
|
|
158
|
-
|
|
158
|
+
// NO inline token (token-hygiene, 2026-07-02): `capture` resolves CORTEX_TOKEN itself via
|
|
159
|
+
// readWiredToken() from the config.toml this same setup run writes. An inlined secret in
|
|
160
|
+
// hooks.json shows up in every read/grep of the file — and from there in captured transcripts.
|
|
161
|
+
const captureCmd = `npx -y ${spec} capture`
|
|
159
162
|
ensureDir(codexHooks)
|
|
160
163
|
writeFileSync(codexHooks, JSON.stringify(mergeCodexHooks(existingHooks, captureCmd), null, 2))
|
|
161
164
|
log(` ✓ Capture hook → ${codexHooks}${bak ? ' (backup saved)' : ''}`)
|
|
@@ -176,7 +179,9 @@ export async function runSetup(argv, version) {
|
|
|
176
179
|
s.hooks = s.hooks ?? {}
|
|
177
180
|
s.hooks.Stop = Array.isArray(s.hooks.Stop) ? s.hooks.Stop : []
|
|
178
181
|
|
|
179
|
-
|
|
182
|
+
// NO inline token — same hygiene as the Codex hook above; `capture` self-resolves from
|
|
183
|
+
// ~/.claude.json (which this setup run writes). The filter below drops the old inlined form.
|
|
184
|
+
const captureCmd = `npx -y ${spec} capture`
|
|
180
185
|
// Remove any prior cortex capture hook (idempotent: drop old token / old path forms).
|
|
181
186
|
for (const grp of s.hooks.Stop) {
|
|
182
187
|
if (Array.isArray(grp.hooks)) {
|
|
@@ -276,21 +281,9 @@ export async function runSetup(argv, version) {
|
|
|
276
281
|
log('')
|
|
277
282
|
}
|
|
278
283
|
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
export
|
|
282
|
-
try {
|
|
283
|
-
const cfg = readJson(join(homedir(), '.claude.json'))
|
|
284
|
-
const t = cfg?.mcpServers?.cortex?.env?.CORTEX_TOKEN
|
|
285
|
-
if (t) return t
|
|
286
|
-
} catch { /* fall through */ }
|
|
287
|
-
try {
|
|
288
|
-
const toml = readFileSync(join(homedir(), '.codex', 'config.toml'), 'utf8')
|
|
289
|
-
const m = toml.match(/\[mcp_servers\.cortex\.env\][\s\S]*?CORTEX_TOKEN\s*=\s*"([^"]+)"/)
|
|
290
|
-
if (m) return m[1]
|
|
291
|
-
} catch { /* fall through */ }
|
|
292
|
-
return null
|
|
293
|
-
}
|
|
284
|
+
// readWiredToken moved to diagnose.mjs (the shared leaf module) so `capture` can use the same
|
|
285
|
+
// resolver — hook commands no longer inline the token. Re-exported for compatibility.
|
|
286
|
+
export { readWiredToken }
|
|
294
287
|
|
|
295
288
|
// `repair`: re-run the FULL setup at THIS version using the already-wired token. The one-command fix
|
|
296
289
|
// for a machine set up with an older version (e.g. when skills were installed to the old nested path,
|