fix(installer): harden greenfield detector contracts
This commit is contained in:
@@ -710,9 +710,11 @@ trap 'restore_snapshot; exit 1' ERR INT TERM
|
||||
|
||||
sync_framework
|
||||
|
||||
# Ensure persistent directories exist
|
||||
# Ensure persistent directories exist. Credentials are private material and
|
||||
# must never inherit a permissive umask/default mode.
|
||||
mkdir -p "$TARGET_DIR/memory"
|
||||
mkdir -p "$TARGET_DIR/credentials"
|
||||
chmod 0700 "$TARGET_DIR/credentials"
|
||||
|
||||
# Reconcile contract files from defaults/ into the framework root: framework-owned
|
||||
# files (CONSTITUTION/AGENTS/STANDARDS) are overwritten every upgrade (a divergent
|
||||
@@ -781,22 +783,74 @@ record_phase_outcome() {
|
||||
fi
|
||||
}
|
||||
|
||||
redact_install_stream() {
|
||||
# Keep this bootstrap copy behaviorally identical to tools/install.sh's
|
||||
# state_redact_stream; neither installer can assume the other is installed.
|
||||
python3 /dev/fd/3 3<<'PY'
|
||||
import os, re, sys
|
||||
text = sys.stdin.read()
|
||||
secret_name = re.compile(r"(?:TOKEN|PASSWORD|PASSWD|SECRET|API_KEY|AUTH|CREDENTIAL|CANARY)", re.I)
|
||||
secrets = {value for name, value in os.environ.items() if secret_name.search(name) and len(value) >= 4}
|
||||
for value in sorted(secrets, key=len, reverse=True):
|
||||
text = text.replace(value, "[REDACTED]")
|
||||
patterns = (
|
||||
(re.compile(r"(?im)^(\s*(?:proxy-)?authorization\s*:\s*)[^\r\n]+"), r"\1[REDACTED]"),
|
||||
(re.compile(r"(?im)^(\s*(?:set-)?cookie\s*:\s*)[^\r\n]+"), r"\1[REDACTED]"),
|
||||
(re.compile(r"(?i)(Bearer\s+)[^\s'\"]+"), r"\1[REDACTED]"),
|
||||
(re.compile(r"(?i)((?:[_-]?auth(?:Token)?|token|password|passwd|secret|api[_-]?key)\s*[=:]\s*)[^\s'\"]+"), r"\1[REDACTED]"),
|
||||
)
|
||||
for pattern, replacement in patterns:
|
||||
text = pattern.sub(replacement, text)
|
||||
url_pattern = re.compile(r"https?://[^\s'\"<>]+", re.I)
|
||||
def redact_url(match):
|
||||
url = match.group(0)
|
||||
scheme_end = url.find("://") + 3
|
||||
authority_end = len(url)
|
||||
for separator in "/?#":
|
||||
position = url.find(separator, scheme_end)
|
||||
if position != -1:
|
||||
authority_end = min(authority_end, position)
|
||||
authority = url[scheme_end:authority_end]
|
||||
at = authority.rfind("@")
|
||||
if at != -1:
|
||||
return url[:scheme_end] + "[REDACTED]@" + authority[at + 1:] + url[authority_end:]
|
||||
return url
|
||||
sys.stdout.write(url_pattern.sub(redact_url, text))
|
||||
PY
|
||||
}
|
||||
|
||||
run_captured() {
|
||||
local label="$1" output status=0
|
||||
local label="$1" redacted redactor_pid capture_fd status=0 redact_status=0
|
||||
shift
|
||||
output="$(mktemp "${TMPDIR:-/tmp}/mosaic-post-install.XXXXXX.log")"
|
||||
if "$@" >"$output" 2>&1; then status=0; else status=$?; fi
|
||||
redacted="$(mktemp "${TMPDIR:-/tmp}/mosaic-post-redacted.XXXXXX")"
|
||||
chmod 0600 "$redacted" || { rm -f "$redacted"; exit 1; }
|
||||
# Preserve in-shell command behavior without ever staging plaintext output on
|
||||
# disk. Process substitution carries raw bytes only through a pipe.
|
||||
exec {capture_fd}> >(redact_install_stream > "$redacted")
|
||||
redactor_pid=$!
|
||||
set +e
|
||||
"$@" >&"$capture_fd" 2>&1
|
||||
status=$?
|
||||
exec {capture_fd}>&-
|
||||
wait "$redactor_pid"
|
||||
redact_status=$?
|
||||
set -e
|
||||
if [[ "$redact_status" -ne 0 ]]; then
|
||||
rm -f "$redacted"
|
||||
fail "Could not redact '$label' diagnostics; refusing to expose or persist raw output."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${MOSAIC_INSTALL_COMMAND_LOG:-}" ]]; then
|
||||
if ! { printf '\n=== %s (exit=%s) ===\n' "$label" "$status"; cat "$output"; } >> "$MOSAIC_INSTALL_COMMAND_LOG" \
|
||||
if ! { printf '\n=== %s (exit=%s) ===\n' "$label" "$status"; cat "$redacted"; } >> "$MOSAIC_INSTALL_COMMAND_LOG" \
|
||||
|| ! sync "$MOSAIC_INSTALL_COMMAND_LOG"; then
|
||||
cat "$output" >&2
|
||||
rm -f "$output"
|
||||
cat "$redacted" >&2
|
||||
rm -f "$redacted"
|
||||
fail "Could not durably append '$label' diagnostics to the install command log."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ "$status" -ne 0 ]]; then cat "$output" >&2; fi
|
||||
rm -f "$output"
|
||||
if [[ "$status" -ne 0 ]]; then cat "$redacted" >&2; fi
|
||||
rm -f "$redacted"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,15 @@ copy_claude_settings_guarded() {
|
||||
guard_args+=(--allow-inactive-enforcement)
|
||||
fi
|
||||
|
||||
if command -v mosaic >/dev/null 2>&1; then
|
||||
if mosaic "${guard_args[@]}"; then
|
||||
local mosaic_cli="${MOSAIC_CLI_PATH:-}"
|
||||
# Unified install passes P3's committed absolute artifact. Standalone
|
||||
# framework installs may resolve PATH once, but still invoke the resulting
|
||||
# absolute path rather than a bare command.
|
||||
if [[ -z "$mosaic_cli" ]]; then
|
||||
mosaic_cli="$(command -v mosaic 2>/dev/null || true)"
|
||||
fi
|
||||
if [[ "$mosaic_cli" == /* && -x "$mosaic_cli" ]]; then
|
||||
if "$mosaic_cli" "${guard_args[@]}"; then
|
||||
return 0
|
||||
fi
|
||||
echo "[mosaic-link] Enforcement hooks were NOT wired into $dst (see message above)." >&2
|
||||
@@ -77,7 +84,7 @@ copy_claude_settings_guarded() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[mosaic-link] ERROR: 'mosaic' CLI not found on PATH — cannot confirm lease-enforcement" >&2
|
||||
echo "[mosaic-link] ERROR: P3 absolute mosaic CLI unavailable — cannot confirm lease-enforcement" >&2
|
||||
echo "[mosaic-link] activation capability. enforcement requested but activation half absent —" >&2
|
||||
echo "[mosaic-link] needs a published CLI carrying launch-runtime activation + a broker" >&2
|
||||
echo "[mosaic-link] supervisor; refusing to wire a dead gate (see #869)." >&2
|
||||
|
||||
Reference in New Issue
Block a user