fix: security audit findings - critical + high + medium + low#13
fix: security audit findings - critical + high + medium + low#13ryanmcmillan merged 2 commits intomainfrom
Conversation
Greptile SummaryThis PR addresses a batch of security audit findings across the CLI, touching secret storage, network binding, config error handling, input validation, and several medium/low quality-of-life fixes. The changes are generally well-scoped and well-motivated, but one of the "High" severity fixes — the macOS keychain write path — contains an implementation gap that leaves the plaintext API key still exposed in the Key changes:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as delega CLI
participant Config as config.ts (loadConfig / getApiUrl)
participant Store as secret-store.ts (writeMacosKeychain)
participant Shell as /bin/sh (execSync)
participant Security as security (macOS CLI)
participant Keychain as macOS Keychain
CLI->>Config: loadConfig() — throws on corrupt JSON (NEW)
Config-->>CLI: DelegaConfig or Error
CLI->>Config: getApiUrl() — env → config → default (NEW unified path)
Config-->>CLI: normalized API URL
CLI->>Store: storeApiKey(apiKey)
Store->>Store: writeFileSync(tmpPath, apiKey, mode 0o600)
Store->>Shell: execSync("security ... -w $(cat tmpPath)")
Note over Shell,Security: Shell expands $(cat tmpPath) → key still in security argv ⚠️
Shell->>Security: security add-generic-password -w <plaintext_key>
Security->>Keychain: store key
Security-->>Shell: exit 0
Store->>Store: unlinkSync(tmpPath) [finally]
Reviews (1): Last reviewed commit: "fix: security audit fixes (docker bind, ..." | Re-trigger Greptile |
src/secret-store.ts
Outdated
| function writeMacosKeychain(apiKey: string): void { | ||
| node_child_process.execFileSync( | ||
| "security", | ||
| ["add-generic-password", "-U", "-a", ACCOUNT_NAME, "-s", SERVICE_NAME, "-w", apiKey], | ||
| { stdio: "ignore" }, | ||
| ); | ||
| const tmpPath = node_path.join(node_os.tmpdir(), `delega-key-${process.pid}`); | ||
| try { | ||
| node_fs.writeFileSync(tmpPath, apiKey, { encoding: "utf-8", mode: 0o600 }); | ||
| node_child_process.execSync( | ||
| `security add-generic-password -U -a "${ACCOUNT_NAME}" -s "${SERVICE_NAME}" -w "$(cat '${tmpPath}')"`, | ||
| { stdio: "ignore" }, | ||
| ); | ||
| } finally { | ||
| try { | ||
| node_fs.unlinkSync(tmpPath); | ||
| } catch {} | ||
| } |
There was a problem hiding this comment.
Secret still appears in
security process argv
The stated goal of this fix is "Stop passing macOS keychain secrets in process argv," but the implementation doesn't fully achieve it.
The shell command substitution "$(cat '${tmpPath}')" reads the key from the temp file and substitutes its value directly into the argument string before passing it to security. When execSync runs, the shell spawns security with -w <actual_key_value> in its argv — which is still visible in ps -ef output during the brief window that security runs.
The original code:
execFileSync("security", [..., "-w", apiKey])…and the new code both result in the same state: security's process argv contains -w <plaintext_key>.
To truly keep the key out of all child-process argv, consider piping the key via stdin to the security command (many macOS security commands accept password input from stdin or a prompt):
// Alternative: use execFileSync without -w in argv and pipe via stdin
// Note: verify whether `security add-generic-password` supports this on your target macOS version.
node_child_process.execFileSync(
"security",
["add-generic-password", "-U", "-a", ACCOUNT_NAME, "-s", SERVICE_NAME, "-w"],
{ input: apiKey, encoding: "utf-8", stdio: ["pipe", "ignore", "ignore"] },
);If stdin is not supported, document the limitation rather than suggesting it is fixed.
README.md
Outdated
| delega tasks delegate <task-id> <agent-id> --content "description" | ||
| delega tasks delegate <task-id> <agent-id> --content "subtask description" |
There was a problem hiding this comment.
Duplicate
delegate example lines
The fix adds a new example line for delegate with --content "description", but the existing line immediately below it (--content "subtask description") already demonstrates the same pattern. This leaves two nearly identical lines in the README.
| delega tasks delegate <task-id> <agent-id> --content "description" | |
| delega tasks delegate <task-id> <agent-id> --content "subtask description" | |
| delega tasks delegate <task-id> <agent-id> --content "subtask description" |
One of the two lines should be removed (the older example already includes --content and is sufficient).
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
/greptile review |
Critical
High
Medium
Low