mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-20 16:47:59 +02:00
fix(security): stderr skip, independent audit, home MCP trust, dry-run conflict check
- pre-push: send skip diagnostic to stderr (not stdout) so consumers relying on stderr for warnings receive the message - pre-push: move ECC_PREPUSH_AUDIT outside RUN_CHECKS gate so audit-only configurations still check dependencies - mcp-health-check: classify home config paths as trusted before applying workspace opt-in gate; when cwd == home, ~/.claude.json was incorrectly blocked as untrusted workspace config - install-global-git-hooks: check conflicting global core.hooksPath in dry-run mode too, so dry-run accurately reflects what apply would do
This commit is contained in:
@@ -89,7 +89,7 @@ if [[ -f "package.json" ]]; then
|
||||
# arbitrary code execution (package.json scripts run as you). Opt-in only:
|
||||
# set ECC_PREPUSH_RUN_CHECKS=1 for repos you trust.
|
||||
if [[ "${ECC_PREPUSH_RUN_CHECKS:-0}" != "1" ]]; then
|
||||
log "Node project detected but ECC_PREPUSH_RUN_CHECKS!=1; skipping repo script execution (set =1 to opt in)."
|
||||
printf '[ECC pre-push] Node project detected but ECC_PREPUSH_RUN_CHECKS!=1; skipping repo script execution (set =1 to opt in).\n' >&2
|
||||
else
|
||||
pm="$(detect_pm)"
|
||||
log "Node project detected (package manager: $pm)"
|
||||
@@ -104,7 +104,9 @@ if [[ -f "package.json" ]]; then
|
||||
fi
|
||||
done
|
||||
|
||||
fi
|
||||
if [[ "${ECC_PREPUSH_AUDIT:-0}" == "1" ]]; then
|
||||
pm="${pm:-$(detect_pm)}"
|
||||
ran_any_check=1
|
||||
log "Running dependency audit (ECC_PREPUSH_AUDIT=1)"
|
||||
case "$pm" in
|
||||
@@ -115,7 +117,6 @@ if [[ -f "package.json" ]]; then
|
||||
*) npm audit --omit=dev || fail "npm audit failed" ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# SECURITY: go test / pytest execute repo-controlled code (TestMain,
|
||||
|
||||
@@ -41,23 +41,21 @@ log "Mode: $MODE"
|
||||
log "Source hooks: $SOURCE_DIR"
|
||||
log "Global hooks destination: $DEST_DIR"
|
||||
|
||||
if [[ "$MODE" == "apply" ]]; then
|
||||
prev_hooks_path="$(git config --global core.hooksPath || true)"
|
||||
if [[ -n "$prev_hooks_path" && "$prev_hooks_path" != "$DEST_DIR" ]]; then
|
||||
# SECURITY: never silently displace another tool's global hooks — that
|
||||
# turns every commit/push in every repo into ECC code execution and breaks
|
||||
# the user's existing security controls. Require explicit opt-in to replace.
|
||||
if [[ "${ECC_FORCE_GLOBAL_HOOKS:-0}" != "1" ]]; then
|
||||
log "ERROR: global core.hooksPath already set to: $prev_hooks_path"
|
||||
log "Refusing to overwrite. Options:"
|
||||
log " 1) Per-repo install (recommended): git config core.hooksPath \"$DEST_DIR\""
|
||||
log " 2) Force replace: ECC_FORCE_GLOBAL_HOOKS=1 $0"
|
||||
log " 3) Restore afterwards: git config --global core.hooksPath \"$prev_hooks_path\""
|
||||
exit 1
|
||||
fi
|
||||
log "WARNING: replacing previous global hooksPath: $prev_hooks_path (ECC_FORCE_GLOBAL_HOOKS=1)"
|
||||
log "Restore with: git config --global core.hooksPath \"$prev_hooks_path\""
|
||||
prev_hooks_path="$(git config --global core.hooksPath || true)"
|
||||
if [[ -n "$prev_hooks_path" && "$prev_hooks_path" != "$DEST_DIR" ]]; then
|
||||
# SECURITY: never silently displace another tool's global hooks — that
|
||||
# turns every commit/push in every repo into ECC code execution and breaks
|
||||
# the user's existing security controls. Require explicit opt-in to replace.
|
||||
if [[ "${ECC_FORCE_GLOBAL_HOOKS:-0}" != "1" ]]; then
|
||||
log "ERROR: global core.hooksPath already set to: $prev_hooks_path"
|
||||
log "Refusing to overwrite. Options:"
|
||||
log " 1) Per-repo install (recommended): git config core.hooksPath \"$DEST_DIR\""
|
||||
log " 2) Force replace: ECC_FORCE_GLOBAL_HOOKS=1 $0"
|
||||
log " 3) Restore afterwards: git config --global core.hooksPath \"$prev_hooks_path\""
|
||||
exit 1
|
||||
fi
|
||||
log "WARNING: replacing previous global hooksPath: $prev_hooks_path (ECC_FORCE_GLOBAL_HOOKS=1)"
|
||||
log "Restore with: git config --global core.hooksPath \"$prev_hooks_path\""
|
||||
fi
|
||||
|
||||
if [[ -d "$DEST_DIR" ]]; then
|
||||
|
||||
@@ -540,16 +540,25 @@ async function probeServer(serverName, resolvedConfig) {
|
||||
try {
|
||||
const src = String(resolvedConfig.source || '');
|
||||
const cwd = process.cwd();
|
||||
const isWorkspaceSource = src === require('path').join(cwd, '.claude.json')
|
||||
|| src === require('path').join(cwd, '.claude', 'settings.json')
|
||||
|| src.startsWith(cwd + require('path').sep + '.claude' + require('path').sep);
|
||||
if (isWorkspaceSource && !/^(1|true|yes)$/i.test(String(process.env.ECC_MCP_ALLOW_WORKSPACE_PROBE || ''))) {
|
||||
return {
|
||||
ok: false,
|
||||
failureCode: null,
|
||||
reason: 'untrusted workspace MCP config skipped (set ECC_MCP_ALLOW_WORKSPACE_PROBE=1 to probe)',
|
||||
source: resolvedConfig.source
|
||||
};
|
||||
const home = require('os').homedir();
|
||||
const pathMod = require('path');
|
||||
// A config file in the user's home directory (~/.claude.json or
|
||||
// ~/.claude/settings.json) is always trusted regardless of cwd.
|
||||
const isHomeSource = src === pathMod.join(home, '.claude.json')
|
||||
|| src === pathMod.join(home, '.claude', 'settings.json')
|
||||
|| src.startsWith(pathMod.join(home, '.claude') + pathMod.sep);
|
||||
if (!isHomeSource) {
|
||||
const isWorkspaceSource = src === pathMod.join(cwd, '.claude.json')
|
||||
|| src === pathMod.join(cwd, '.claude', 'settings.json')
|
||||
|| src.startsWith(cwd + pathMod.sep + '.claude' + pathMod.sep);
|
||||
if (isWorkspaceSource && !/^(1|true|yes)$/i.test(String(process.env.ECC_MCP_ALLOW_WORKSPACE_PROBE || ''))) {
|
||||
return {
|
||||
ok: false,
|
||||
failureCode: null,
|
||||
reason: 'untrusted workspace MCP config skipped (set ECC_MCP_ALLOW_WORKSPACE_PROBE=1 to probe)',
|
||||
source: resolvedConfig.source
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fail closed on path errors for workspace sources is handled below;
|
||||
|
||||
Reference in New Issue
Block a user