Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -892,17 +892,25 @@ function writeCurrentModels(data) {
fs.writeFileSync(CURRENT_MODELS_FILE, JSON.stringify(data, null, 2), 'utf-8');
}

const _authKey = crypto.createHash('sha256').update(AUTH_FILE).digest();
function encryptAuthValue(v) { const iv = crypto.randomBytes(16); const c = crypto.createCipheriv('aes-256-cbc', _authKey, iv); return 'enc:' + iv.toString('hex') + ':' + Buffer.concat([c.update(v, 'utf-8'), c.final()]).toString('hex'); }
function decryptAuthValue(v) { if (!v || !v.startsWith('enc:')) return v; try { const [, h, d] = v.split(':'); const dc = crypto.createDecipheriv('aes-256-cbc', _authKey, Buffer.from(h, 'hex')); return Buffer.concat([dc.update(Buffer.from(d, 'hex')), dc.final()]).toString('utf-8'); } catch (e) { return v; } }
Comment on lines +895 to +897

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,80p'
echo "== locate cli.js =="
fd -a '^cli\.js$' . || true
echo "== relevant cli.js lines =="
if [ -f cli.js ]; then
  sed -n '840,930p' cli.js | cat -n
fi
echo "== AUTH_FILE references =="
rg -n "AUTH_FILE|auth.json|authJson|encryptAuthValue|decryptAuthValue|createCipheriv|createDecipheriv|crypto" cli.js || true
echo "== git diff stat/name =="
git diff --stat || true
git diff -- cli.js | sed -n '1,220p' || true

Repository: SakuraByteCore/codexmate

Length of output: 9346


Do not derive the encryption key from AUTH_FILE.

sha256(AUTH_FILE) is predictable; anyone who can read the known ~/.codex/auth.json path can derive the same key and decrypt the stored API key. Use a real secret/key store for this protection, or avoid encrypting credentials with path-derived material. Also prefer authenticated encryption (for example AES-GCM) and reject failed authentication tags rather than falling back to treating malformed ciphertext as plaintext.
[high_effort_and_high_review]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 895 - 897, Replace the path-derived _authKey encryption
in encryptAuthValue and decryptAuthValue with a real secret sourced from the
established secure key store, or remove this custom credential encryption in
favor of secure storage. Use authenticated encryption such as AES-GCM, persist
the nonce and authentication tag with ciphertext, and make decryptAuthValue
reject authentication failures instead of returning malformed ciphertext as
plaintext.

function updateAuthJson(apiKey) {
assertToolConfigWriteAllowed('codex');
let authData = {};
if (fs.existsSync(AUTH_FILE)) {
try {
const content = fs.readFileSync(AUTH_FILE, 'utf-8');
if (content.trim()) authData = JSON.parse(content);
if (content.trim()) {
const raw = JSON.parse(content);
for (const k of Object.keys(raw)) authData[k] = decryptAuthValue(raw[k]);
}
} catch (e) { }
Comment on lines +906 to 908

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail closed instead of overwriting unreadable auth data.

If parsing or decrypting any value fails—including a valid JSON value that is not a string—the empty catch leaves authData empty, and lines 910-912 silently replace the file with only the new API key. Validate the object/value schema and abort without writing on failure; malformed enc: values should be rejected, not preserved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` around lines 906 - 908, Update the auth-data parsing and decryption
flow around the catch block to validate that the parsed value is an object and
every auth value is a decryptable string, rejecting malformed or non-string
values instead of preserving them. On any parse or decryption failure, abort
before the later write that replaces the file, leaving existing auth data
unchanged.

}
authData['OPENAI_API_KEY'] = apiKey;
fs.writeFileSync(AUTH_FILE, JSON.stringify(authData, null, 2), { encoding: 'utf-8', mode: 0o600 });
const out = {};
for (const k of Object.keys(authData)) out[k] = encryptAuthValue(authData[k]);
fs.writeFileSync(AUTH_FILE, JSON.stringify(out, null, 2), { encoding: 'utf-8', mode: 0o600 });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce 0600 for existing auth files too.

fs.writeFileSync(..., { mode: 0o600 }) only applies when creating a file; it does not change the mode of an existing file. A pre-existing permissive auth file remains readable after this update. Use an atomic replacement that writes into a newly created 0600 file, or explicitly chmod the existing file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli.js` at line 913, Update the auth-file write flow around AUTH_FILE so
existing files are also enforced as mode 0600. Either atomically replace the
file using a newly created 0600 file or explicitly chmod the existing file after
writing, while preserving the current JSON output behavior.

}

function isPlainObject(value) {
Expand Down
Loading