mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Make hot-dev local-only by default
Keep the managed dev runtime on loopback unless PULSE_DEV_LAN=true is set, so installed LAN agents cannot accidentally attach to a developer session. Harden managed status checks for restricted local shells. Refs architecture post-RC canonicalization follow-up.
This commit is contained in:
@@ -183,10 +183,12 @@ server-side update execution surfaces.
|
||||
owns the run-scoped `HOT_DEV_VERIFY_LOCK_FILE` handoff so overlapping browser
|
||||
proof cannot reuse stale first-run credentials.
|
||||
The managed and foreground hot-dev entrypoints must share one network-default
|
||||
contract: Vite binds `FRONTEND_DEV_HOST=0.0.0.0` by default so LAN devices can
|
||||
open the dev browser shell, while local browser/proof URLs normalize wildcard
|
||||
binds back to `127.0.0.1` and advertise the detected LAN browser entrypoint
|
||||
separately.
|
||||
contract: local dev binds frontend and backend traffic to loopback by default
|
||||
so installed LAN agents cannot accidentally treat a developer laptop as the
|
||||
active Pulse control plane. LAN exposure for agent/mobile testing must be an
|
||||
explicit `PULSE_DEV_LAN=true` opt-in; only that mode may bind Vite/backend
|
||||
listeners to `0.0.0.0`, include LAN origins, or advertise the detected LAN
|
||||
browser entrypoint.
|
||||
The hot-dev supervisor must also recover its managed PID file from a live
|
||||
`hot-dev-bg.sh supervise` process before treating the runtime as unmanaged.
|
||||
Backend health monitoring must distinguish HTTP startup grace from a missing
|
||||
|
||||
@@ -5,7 +5,7 @@ import path from 'path';
|
||||
import { URL } from 'node:url';
|
||||
import { configDefaults } from 'vitest/config';
|
||||
|
||||
const frontendDevHost = process.env.FRONTEND_DEV_HOST ?? '0.0.0.0';
|
||||
const frontendDevHost = process.env.FRONTEND_DEV_HOST ?? '127.0.0.1';
|
||||
const frontendDevPort = Number(
|
||||
process.env.FRONTEND_DEV_PORT ?? process.env.VITE_PORT ?? process.env.PORT ?? 5173,
|
||||
);
|
||||
@@ -48,7 +48,7 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: frontendDevPort,
|
||||
host: frontendDevHost, // Listen on all interfaces for remote access
|
||||
host: frontendDevHost,
|
||||
strictPort: true,
|
||||
proxy: {
|
||||
'/ws': {
|
||||
|
||||
+61
-5
@@ -39,9 +39,15 @@ fail() {
|
||||
|
||||
http_status_code() {
|
||||
local url="$1"
|
||||
local code
|
||||
code="$(curl -sS -m 2 -o /dev/null -w '%{http_code}' "${url}" 2>/dev/null || true)"
|
||||
[[ -n "${code}" ]] || code="000"
|
||||
local output code
|
||||
output="$(curl -sS -m 2 -o /dev/null -w $'\n%{http_code}' "${url}" 2>&1 || true)"
|
||||
code="${output##*$'\n'}"
|
||||
if [[ ! "${code}" =~ ^[0-9][0-9][0-9]$ ]]; then
|
||||
code="000"
|
||||
fi
|
||||
if [[ "${code}" == "000" && "${output}" == *"Operation not permitted"* ]]; then
|
||||
code="blocked"
|
||||
fi
|
||||
printf "%s\n" "${code}"
|
||||
}
|
||||
|
||||
@@ -50,6 +56,11 @@ http_status_ok() {
|
||||
[[ "${code}" =~ ^2[0-9][0-9]$ || "${code}" =~ ^3[0-9][0-9]$ ]]
|
||||
}
|
||||
|
||||
http_status_blocked() {
|
||||
local code="$1"
|
||||
[[ "${code}" == "blocked" ]]
|
||||
}
|
||||
|
||||
is_port_listening() {
|
||||
local port="$1"
|
||||
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
|
||||
@@ -75,6 +86,41 @@ process_parent_id() {
|
||||
ps -o ppid= -p "${pid}" 2>/dev/null | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
process_probe_denied() {
|
||||
local output="$1"
|
||||
[[ "${output}" == *"Operation not permitted"* || "${output}" == *"operation not permitted"* || "${output}" == *"not permitted"* ]]
|
||||
}
|
||||
|
||||
pid_signal_probe() {
|
||||
local pid="${1:-}"
|
||||
local output
|
||||
|
||||
[[ -n "${pid}" ]] || return 1
|
||||
if output="$(kill -0 "${pid}" 2>&1)"; then
|
||||
return 0
|
||||
fi
|
||||
if process_probe_denied "${output}"; then
|
||||
return 2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
pid_may_be_running() {
|
||||
local status
|
||||
pid_signal_probe "${1:-}"
|
||||
status=$?
|
||||
[[ "${status}" -eq 0 || "${status}" -eq 2 ]]
|
||||
}
|
||||
|
||||
process_inspection_restricted() {
|
||||
local output
|
||||
|
||||
if output="$(ps -o pid= -p "$$" 2>&1 >/dev/null)"; then
|
||||
return 1
|
||||
fi
|
||||
process_probe_denied "${output}"
|
||||
}
|
||||
|
||||
discover_managed_supervisor_pid() {
|
||||
local pid command
|
||||
|
||||
@@ -99,7 +145,7 @@ recover_managed_pid_file() {
|
||||
[[ "${PID_FILE}" == "${DEFAULT_HOT_DEV_BG_PID_FILE}" ]] || return 1
|
||||
discovered_pid="$(discover_managed_supervisor_pid || true)"
|
||||
[[ -n "${discovered_pid}" ]] || return 1
|
||||
kill -0 "${discovered_pid}" 2>/dev/null || return 1
|
||||
pid_may_be_running "${discovered_pid}" || return 1
|
||||
mkdir -p "$(dirname "${PID_FILE}")"
|
||||
printf "%s\n" "${discovered_pid}" > "${PID_FILE}"
|
||||
return 0
|
||||
@@ -209,6 +255,10 @@ port_has_unmanaged_listener() {
|
||||
local session_pid="$2"
|
||||
local listener_pid
|
||||
|
||||
if [[ -n "${session_pid}" ]] && process_inspection_restricted; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS= read -r listener_pid; do
|
||||
[[ -n "${listener_pid}" ]] || continue
|
||||
if ! pid_is_managed "${listener_pid}" "${session_pid}"; then
|
||||
@@ -259,7 +309,7 @@ is_running() {
|
||||
fi
|
||||
local pid
|
||||
pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
|
||||
if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then
|
||||
if [[ -n "${pid}" ]] && pid_may_be_running "${pid}"; then
|
||||
return 0
|
||||
fi
|
||||
rm -f "${PID_FILE}"
|
||||
@@ -285,6 +335,8 @@ describe_listener() {
|
||||
owner="unmanaged"
|
||||
if pid_is_managed "${listener_pid}" "${session_pid}"; then
|
||||
owner="managed"
|
||||
elif [[ -n "${session_pid}" ]] && process_inspection_restricted; then
|
||||
owner="unknown"
|
||||
fi
|
||||
command="$(process_command "${listener_pid}")"
|
||||
log "Port ${port}: ${owner} listener pid=${listener_pid} pgid=${pgid:-unknown} cmd=${command:-unknown}"
|
||||
@@ -730,6 +782,10 @@ status_bg() {
|
||||
|
||||
if http_status_ok "${frontend_code}" && http_status_ok "${proxy_code}" && http_status_ok "${backend_code}"; then
|
||||
log "Runtime summary: frontend shell, proxy, and backend are healthy. Use ${browser_url} in the browser."
|
||||
elif process_inspection_restricted && [[ "${frontend_code}" == "000" && "${backend_code}" == "000" ]] && is_port_listening "${FRONTEND_DEV_PORT}" && is_port_listening "${PULSE_DEV_API_PORT}"; then
|
||||
log "Runtime summary: local HTTP probes are unavailable from this restricted shell, but frontend and backend listeners are present. Use ${browser_url} in the browser or rerun status outside the restricted shell."
|
||||
elif http_status_blocked "${frontend_code}" || http_status_blocked "${proxy_code}" || http_status_blocked "${backend_code}"; then
|
||||
log "Runtime summary: local HTTP probes are blocked by this shell. Use ${browser_url} in the browser or rerun status outside the restricted shell."
|
||||
elif http_status_ok "${frontend_code}" && ! http_status_ok "${proxy_code}" && http_status_ok "${backend_code}"; then
|
||||
log "Runtime summary: frontend shell is up and the backend is healthy, but the frontend proxy path is unhealthy."
|
||||
elif http_status_ok "${frontend_code}" && ! http_status_ok "${backend_code}"; then
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# PULSE_DATA_DIR=/path Override data directory
|
||||
# PULSE_DEV_API_PORT=7655 Backend API port (default: 7655)
|
||||
# FRONTEND_DEV_PORT=5173 Frontend dev server port (default: 5173)
|
||||
# PULSE_DEV_LAN=true Expose frontend/backend on the LAN for agent/mobile testing (default: false)
|
||||
# LOG_LEVEL=debug Opt into verbose backend logs (default: info)
|
||||
# PULSE_DEV_DISABLE_BACKGROUND_AI=false
|
||||
# Allow automatic Patrol/discovery/alert AI in dev
|
||||
|
||||
@@ -43,6 +43,23 @@ hot_dev_detect_all_ipv4s() {
|
||||
hostname -I 2>/dev/null || true
|
||||
}
|
||||
|
||||
hot_dev_truthy() {
|
||||
local value
|
||||
value="$(printf '%s\n' "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
case "${value}" in
|
||||
1|true|yes|y|on)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
hot_dev_lan_enabled() {
|
||||
hot_dev_truthy "${PULSE_DEV_LAN:-false}"
|
||||
}
|
||||
|
||||
hot_dev_configure_network_defaults() {
|
||||
FRONTEND_PORT="${FRONTEND_PORT:-${PORT:-5173}}"
|
||||
PORT="${PORT:-${FRONTEND_PORT}}"
|
||||
@@ -52,9 +69,16 @@ hot_dev_configure_network_defaults() {
|
||||
fi
|
||||
LAN_IP="${LAN_IP:-0.0.0.0}"
|
||||
|
||||
FRONTEND_DEV_HOST="${FRONTEND_DEV_HOST:-0.0.0.0}"
|
||||
if hot_dev_lan_enabled; then
|
||||
FRONTEND_DEV_HOST="${FRONTEND_DEV_HOST:-0.0.0.0}"
|
||||
PULSE_DEV_API_HOST="${PULSE_DEV_API_HOST:-${LAN_IP}}"
|
||||
BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0}"
|
||||
else
|
||||
FRONTEND_DEV_HOST="${FRONTEND_DEV_HOST:-127.0.0.1}"
|
||||
PULSE_DEV_API_HOST="${PULSE_DEV_API_HOST:-127.0.0.1}"
|
||||
BIND_ADDRESS="${BIND_ADDRESS:-127.0.0.1}"
|
||||
fi
|
||||
FRONTEND_DEV_PORT="${FRONTEND_DEV_PORT:-${FRONTEND_PORT}}"
|
||||
PULSE_DEV_API_HOST="${PULSE_DEV_API_HOST:-${LAN_IP}}"
|
||||
PULSE_DEV_API_PORT="${PULSE_DEV_API_PORT:-7655}"
|
||||
|
||||
if [[ -z "${PULSE_DEV_API_URL:-}" ]]; then
|
||||
@@ -80,21 +104,23 @@ hot_dev_configure_network_defaults() {
|
||||
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},http://0.0.0.0:${FRONTEND_DEV_PORT:-7655},http://0.0.0.0:5173"
|
||||
fi
|
||||
|
||||
local ip
|
||||
for ip in ${ALL_IPS:-}; do
|
||||
if [[ "${ip}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
if [[ "${ip}" != "127.0.0.1" ]]; then
|
||||
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},http://${ip}:${FRONTEND_DEV_PORT:-7655}"
|
||||
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},http://${ip}:5173"
|
||||
if hot_dev_lan_enabled; then
|
||||
local ip
|
||||
for ip in ${ALL_IPS:-}; do
|
||||
if [[ "${ip}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
if [[ "${ip}" != "127.0.0.1" ]]; then
|
||||
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},http://${ip}:${FRONTEND_DEV_PORT:-7655}"
|
||||
ALLOWED_ORIGINS="${ALLOWED_ORIGINS},http://${ip}:5173"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
export FRONTEND_PORT PORT
|
||||
export LAN_IP ALL_IPS
|
||||
export FRONTEND_DEV_HOST FRONTEND_DEV_PORT
|
||||
export PULSE_DEV_API_HOST PULSE_DEV_API_PORT PULSE_DEV_API_URL PULSE_DEV_WS_URL
|
||||
export ALLOWED_ORIGINS
|
||||
export BIND_ADDRESS ALLOWED_ORIGINS
|
||||
}
|
||||
|
||||
hot_dev_normalize_host() {
|
||||
@@ -233,6 +259,13 @@ hot_dev_reconcile_agent_bind_address() {
|
||||
hot_dev_host_is_loopback "${host}" && continue
|
||||
hot_dev_host_matches_local_interface "${host}" || continue
|
||||
|
||||
if ! hot_dev_lan_enabled; then
|
||||
if declare -F log_warn >/dev/null 2>&1; then
|
||||
log_warn "${label}=${url} points at this machine, but hot-dev is local-only by default. Leaving BIND_ADDRESS=${bind_address}; set PULSE_DEV_LAN=true to expose this dev backend to installed agents."
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
BIND_ADDRESS="0.0.0.0"
|
||||
export BIND_ADDRESS
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ EOF
|
||||
assert_contains "default verify proof keeps chromium project pin" "${output}" "--project=chromium"
|
||||
}
|
||||
|
||||
test_managed_wrapper_defaults_to_lan_capable_frontend() {
|
||||
test_managed_wrapper_defaults_to_local_only_runtime() {
|
||||
local output
|
||||
output="$(
|
||||
LAN_IP=192.168.50.10 \
|
||||
@@ -220,15 +220,17 @@ test_managed_wrapper_defaults_to_lan_capable_frontend() {
|
||||
source "${HOT_DEV_BG_PATH}"
|
||||
printf "frontend_host=%s\n" "${FRONTEND_DEV_HOST}"
|
||||
printf "api_host=%s\n" "${PULSE_DEV_API_HOST}"
|
||||
printf "bind=%s\n" "${BIND_ADDRESS}"
|
||||
printf "browser=%s\n" "$(hot_dev_local_browser_url "${FRONTEND_DEV_HOST}" "${FRONTEND_DEV_PORT}")"
|
||||
printf "lan_browser=%s\n" "$(hot_dev_lan_browser_url "${FRONTEND_DEV_HOST}" "${FRONTEND_DEV_PORT}" "${LAN_IP}")"
|
||||
'
|
||||
)"
|
||||
|
||||
assert_contains "managed wrapper exposes Vite on all interfaces by default" "${output}" "frontend_host=0.0.0.0"
|
||||
assert_contains "managed wrapper uses the shared LAN API default" "${output}" "api_host=192.168.50.10"
|
||||
assert_contains "managed wrapper keeps Vite on loopback by default" "${output}" "frontend_host=127.0.0.1"
|
||||
assert_contains "managed wrapper uses the loopback API default" "${output}" "api_host=127.0.0.1"
|
||||
assert_contains "managed wrapper binds the backend to loopback by default" "${output}" "bind=127.0.0.1"
|
||||
assert_contains "managed wrapper keeps local browser entrypoint on loopback" "${output}" "browser=http://127.0.0.1:5173"
|
||||
assert_contains "managed wrapper advertises a LAN browser entrypoint" "${output}" "lan_browser=http://192.168.50.10:5173"
|
||||
assert_contains "managed wrapper does not advertise a LAN browser entrypoint by default" "${output}" "lan_browser="
|
||||
}
|
||||
|
||||
test_verify_bg_holds_runtime_lock_for_proof_duration() {
|
||||
@@ -752,18 +754,28 @@ test_hot_dev_reconciles_agent_reachable_bind_address() {
|
||||
ALL_IPS="192.168.50.10 10.10.10.5"
|
||||
|
||||
BIND_ADDRESS=127.0.0.1
|
||||
unset PULSE_DEV_LAN
|
||||
PULSE_PUBLIC_URL=http://192.168.50.10:7655
|
||||
unset PULSE_AGENT_CONNECT_URL PULSE_AGENT_URL
|
||||
hot_dev_reconcile_agent_bind_address
|
||||
printf "lan_bind=%s\n" "${BIND_ADDRESS}"
|
||||
printf "default_lan_bind=%s\n" "${BIND_ADDRESS}"
|
||||
|
||||
BIND_ADDRESS=127.0.0.1
|
||||
PULSE_DEV_LAN=true
|
||||
PULSE_PUBLIC_URL=http://192.168.50.10:7655
|
||||
unset PULSE_AGENT_CONNECT_URL PULSE_AGENT_URL
|
||||
hot_dev_reconcile_agent_bind_address
|
||||
printf "opt_in_lan_bind=%s\n" "${BIND_ADDRESS}"
|
||||
|
||||
BIND_ADDRESS=127.0.0.1
|
||||
PULSE_DEV_LAN=true
|
||||
PULSE_PUBLIC_URL=https://pulse.example.com
|
||||
unset PULSE_AGENT_CONNECT_URL PULSE_AGENT_URL
|
||||
hot_dev_reconcile_agent_bind_address
|
||||
printf "external_bind=%s\n" "${BIND_ADDRESS}"
|
||||
|
||||
BIND_ADDRESS=0.0.0.0
|
||||
PULSE_DEV_LAN=true
|
||||
PULSE_AGENT_CONNECT_URL=http://192.168.50.10:7655
|
||||
unset PULSE_PUBLIC_URL PULSE_AGENT_URL
|
||||
hot_dev_reconcile_agent_bind_address
|
||||
@@ -771,7 +783,8 @@ test_hot_dev_reconciles_agent_reachable_bind_address() {
|
||||
'
|
||||
)"
|
||||
|
||||
assert_contains "hot-dev exposes backend for LAN public URL" "${output}" "lan_bind=0.0.0.0"
|
||||
assert_contains "hot-dev stays local-only for LAN public URL by default" "${output}" "default_lan_bind=127.0.0.1"
|
||||
assert_contains "hot-dev exposes backend for LAN public URL when explicitly opted in" "${output}" "opt_in_lan_bind=0.0.0.0"
|
||||
assert_contains "hot-dev leaves external public URLs on loopback" "${output}" "external_bind=127.0.0.1"
|
||||
assert_contains "hot-dev preserves already exposed backend bind" "${output}" "already_exposed_bind=0.0.0.0"
|
||||
}
|
||||
@@ -871,6 +884,100 @@ test_hot_dev_bg_recovers_stale_pid_file_from_live_supervisor() {
|
||||
assert_contains "managed_session_pid returns recovered supervisor pid" "${output}" "managed_pid=4242"
|
||||
}
|
||||
|
||||
test_hot_dev_bg_preserves_pid_file_when_signal_probe_is_denied() {
|
||||
local test_dir output
|
||||
test_dir="$(mktemp -d)"
|
||||
temp_dirs+=("${test_dir}")
|
||||
|
||||
output="$(
|
||||
HOT_DEV_BG_PATH="${HOT_DEV_BG}" \
|
||||
bash -lc '
|
||||
source "${HOT_DEV_BG_PATH}"
|
||||
PID_FILE="'"${test_dir}"'/hot-dev-bg.pid"
|
||||
printf "4242\n" > "${PID_FILE}"
|
||||
kill() {
|
||||
if [[ "${1:-}" == "-0" && "${2:-}" == "4242" ]]; then
|
||||
printf "kill: 4242: Operation not permitted\n" >&2
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
recover_managed_pid_file() {
|
||||
printf "recover_called=yes\n"
|
||||
return 1
|
||||
}
|
||||
|
||||
if is_running; then
|
||||
printf "running=yes\n"
|
||||
else
|
||||
printf "running=no\n"
|
||||
fi
|
||||
if [[ -f "${PID_FILE}" ]]; then
|
||||
printf "pid_file=%s\n" "$(cat "${PID_FILE}")"
|
||||
else
|
||||
printf "pid_file=missing\n"
|
||||
fi
|
||||
'
|
||||
)"
|
||||
|
||||
assert_contains "permission-denied process probes are treated as alive" "${output}" "running=yes"
|
||||
assert_contains "permission-denied process probes do not delete the pid file" "${output}" "pid_file=4242"
|
||||
assert_not_contains "permission-denied process probes do not trigger recovery" "${output}" "recover_called=yes"
|
||||
}
|
||||
|
||||
test_hot_dev_bg_status_degrades_ownership_when_process_inspection_is_restricted() {
|
||||
local test_dir output
|
||||
test_dir="$(mktemp -d)"
|
||||
temp_dirs+=("${test_dir}")
|
||||
|
||||
output="$(
|
||||
HOT_DEV_BG_PATH="${HOT_DEV_BG}" \
|
||||
bash -lc '
|
||||
source "${HOT_DEV_BG_PATH}"
|
||||
PID_FILE="'"${test_dir}"'/hot-dev-bg.pid"
|
||||
printf "4242\n" > "${PID_FILE}"
|
||||
kill() {
|
||||
if [[ "${1:-}" == "-0" && "${2:-}" == "4242" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
process_inspection_restricted(){ return 0; }
|
||||
is_port_listening(){ return 0; }
|
||||
listener_pids(){ printf "14111\n"; }
|
||||
process_group_id(){ return 1; }
|
||||
process_command(){ return 1; }
|
||||
http_status_code(){ printf "000\n"; }
|
||||
|
||||
status_bg
|
||||
'
|
||||
)"
|
||||
|
||||
assert_contains "restricted status keeps the managed runtime pid" "${output}" "[hot-dev-bg] Running (pid: 4242)"
|
||||
assert_contains "restricted status marks listener ownership as unknown" "${output}" "[hot-dev-bg] Port 5173: unknown listener pid=14111"
|
||||
assert_not_contains "restricted status does not accuse split ownership" "${output}" "Detected split ownership"
|
||||
assert_contains "restricted status reports unavailable health probes without claiming a crash" "${output}" "local HTTP probes are unavailable from this restricted shell"
|
||||
}
|
||||
|
||||
test_hot_dev_bg_http_status_reports_blocked_local_probe() {
|
||||
local output
|
||||
|
||||
output="$(
|
||||
HOT_DEV_BG_PATH="${HOT_DEV_BG}" \
|
||||
bash -lc '
|
||||
source "${HOT_DEV_BG_PATH}"
|
||||
curl() {
|
||||
printf "curl: (7) Failed to connect to 127.0.0.1 port 5173: Operation not permitted\n" >&2
|
||||
printf "000\n"
|
||||
return 7
|
||||
}
|
||||
printf "code=%s\n" "$(http_status_code "http://127.0.0.1:5173/")"
|
||||
'
|
||||
)"
|
||||
|
||||
assert_contains "local network sandbox denial is reported distinctly" "${output}" "code=blocked"
|
||||
}
|
||||
|
||||
test_hot_dev_bg_usage_prefers_managed_wrappers() {
|
||||
local output
|
||||
output="$("${HOT_DEV_BG}" 2>&1 || true)"
|
||||
@@ -1403,7 +1510,7 @@ main() {
|
||||
test_cli_parses_takeover_flag
|
||||
test_verify_command_injects_managed_runtime_env
|
||||
test_default_verify_command_runs_runtime_and_layout_proofs
|
||||
test_managed_wrapper_defaults_to_lan_capable_frontend
|
||||
test_managed_wrapper_defaults_to_local_only_runtime
|
||||
test_verify_bg_holds_runtime_lock_for_proof_duration
|
||||
test_managed_dev_runtime_restarts_existing_session_for_verification
|
||||
test_takeover_avoids_killing_current_shell_lineage
|
||||
@@ -1424,6 +1531,9 @@ main() {
|
||||
test_hot_dev_health_monitor_probes_api_health
|
||||
test_hot_dev_bg_script_advertises_managed_entrypoint
|
||||
test_hot_dev_bg_recovers_stale_pid_file_from_live_supervisor
|
||||
test_hot_dev_bg_preserves_pid_file_when_signal_probe_is_denied
|
||||
test_hot_dev_bg_status_degrades_ownership_when_process_inspection_is_restricted
|
||||
test_hot_dev_bg_http_status_reports_blocked_local_probe
|
||||
test_hot_dev_bg_usage_prefers_managed_wrappers
|
||||
test_integration_readme_uses_managed_backend_restart_wrapper
|
||||
test_integration_readme_documents_retired_trial_start_contract
|
||||
@@ -1452,4 +1562,6 @@ main() {
|
||||
echo "hot-dev-bg ownership diagnostics smoke tests passed."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
|
||||
@@ -138,36 +138,57 @@ test_hot_dev_avoids_self_killing_npm_wrapper() {
|
||||
assert_not_contains "hot-dev no longer broad-kills npm dev wrappers" "${output}" 'pkill -f "npm run dev"'
|
||||
}
|
||||
|
||||
test_hot_dev_network_defaults_are_lan_capable() {
|
||||
test_hot_dev_network_defaults_are_local_first_with_explicit_lan_opt_in() {
|
||||
local output
|
||||
output="$(
|
||||
HOT_DEV_RUNTIME_LIB="${HOT_DEV_RUNTIME_LIB}" \
|
||||
bash -c '
|
||||
set -euo pipefail
|
||||
source "${HOT_DEV_RUNTIME_LIB}"
|
||||
unset FRONTEND_PORT PORT FRONTEND_DEV_HOST FRONTEND_DEV_PORT
|
||||
unset FRONTEND_PORT PORT FRONTEND_DEV_HOST FRONTEND_DEV_PORT BIND_ADDRESS PULSE_DEV_LAN
|
||||
unset PULSE_DEV_API_HOST PULSE_DEV_API_PORT PULSE_DEV_API_URL PULSE_DEV_WS_URL
|
||||
unset ALLOWED_ORIGINS
|
||||
LAN_IP=192.168.50.10
|
||||
ALL_IPS="192.168.50.10 10.10.10.5"
|
||||
hot_dev_configure_network_defaults
|
||||
printf "frontend_host=%s\n" "${FRONTEND_DEV_HOST}"
|
||||
printf "frontend_port=%s\n" "${FRONTEND_DEV_PORT}"
|
||||
printf "api_host=%s\n" "${PULSE_DEV_API_HOST}"
|
||||
printf "api_url=%s\n" "${PULSE_DEV_API_URL}"
|
||||
printf "ws_url=%s\n" "${PULSE_DEV_WS_URL}"
|
||||
printf "origins=%s\n" "${ALLOWED_ORIGINS}"
|
||||
printf "default_frontend_host=%s\n" "${FRONTEND_DEV_HOST}"
|
||||
printf "default_frontend_port=%s\n" "${FRONTEND_DEV_PORT}"
|
||||
printf "default_api_host=%s\n" "${PULSE_DEV_API_HOST}"
|
||||
printf "default_api_url=%s\n" "${PULSE_DEV_API_URL}"
|
||||
printf "default_ws_url=%s\n" "${PULSE_DEV_WS_URL}"
|
||||
printf "default_bind=%s\n" "${BIND_ADDRESS}"
|
||||
printf "default_origins=%s\n" "${ALLOWED_ORIGINS}"
|
||||
|
||||
unset FRONTEND_PORT PORT FRONTEND_DEV_HOST FRONTEND_DEV_PORT BIND_ADDRESS
|
||||
unset PULSE_DEV_API_HOST PULSE_DEV_API_PORT PULSE_DEV_API_URL PULSE_DEV_WS_URL
|
||||
unset ALLOWED_ORIGINS
|
||||
PULSE_DEV_LAN=true
|
||||
hot_dev_configure_network_defaults
|
||||
printf "lan_frontend_host=%s\n" "${FRONTEND_DEV_HOST}"
|
||||
printf "lan_api_host=%s\n" "${PULSE_DEV_API_HOST}"
|
||||
printf "lan_api_url=%s\n" "${PULSE_DEV_API_URL}"
|
||||
printf "lan_ws_url=%s\n" "${PULSE_DEV_WS_URL}"
|
||||
printf "lan_bind=%s\n" "${BIND_ADDRESS}"
|
||||
printf "lan_origins=%s\n" "${ALLOWED_ORIGINS}"
|
||||
'
|
||||
)"
|
||||
|
||||
assert_contains "network defaults expose the frontend on all interfaces" "${output}" "frontend_host=0.0.0.0"
|
||||
assert_contains "network defaults keep the canonical frontend dev port" "${output}" "frontend_port=5173"
|
||||
assert_contains "network defaults target the detected LAN API host" "${output}" "api_host=192.168.50.10"
|
||||
assert_contains "network defaults derive the API URL" "${output}" "api_url=http://192.168.50.10:7655"
|
||||
assert_contains "network defaults derive the websocket URL" "${output}" "ws_url=ws://192.168.50.10:7655"
|
||||
assert_contains "network defaults allow the detected LAN frontend origin" "${output}" "http://192.168.50.10:5173"
|
||||
assert_contains "network defaults keep hot-dev frontend loopback-only" "${output}" "default_frontend_host=127.0.0.1"
|
||||
assert_contains "network defaults keep the canonical frontend dev port" "${output}" "default_frontend_port=5173"
|
||||
assert_contains "network defaults target loopback API host" "${output}" "default_api_host=127.0.0.1"
|
||||
assert_contains "network defaults derive the loopback API URL" "${output}" "default_api_url=http://127.0.0.1:7655"
|
||||
assert_contains "network defaults derive the loopback websocket URL" "${output}" "default_ws_url=ws://127.0.0.1:7655"
|
||||
assert_contains "network defaults bind backend to loopback" "${output}" "default_bind=127.0.0.1"
|
||||
assert_contains "network defaults retain loopback frontend origins" "${output}" "http://127.0.0.1:5173"
|
||||
assert_contains "network defaults allow the wildcard dev origin Electron may report" "${output}" "http://0.0.0.0:5173"
|
||||
assert_not_contains "network defaults do not allow detected LAN frontend origins by default" "${output}" "default_origins=http://192.168.50.10"
|
||||
assert_not_contains "network defaults do not allow wildcard dev origin by default" "${output}" "default_origins=http://0.0.0.0:5173"
|
||||
assert_contains "LAN opt-in exposes the frontend on all interfaces" "${output}" "lan_frontend_host=0.0.0.0"
|
||||
assert_contains "LAN opt-in targets the detected LAN API host" "${output}" "lan_api_host=192.168.50.10"
|
||||
assert_contains "LAN opt-in derives the API URL" "${output}" "lan_api_url=http://192.168.50.10:7655"
|
||||
assert_contains "LAN opt-in derives the websocket URL" "${output}" "lan_ws_url=ws://192.168.50.10:7655"
|
||||
assert_contains "LAN opt-in binds backend to all interfaces" "${output}" "lan_bind=0.0.0.0"
|
||||
assert_contains "LAN opt-in allows the detected LAN frontend origin" "${output}" "lan_origins=http://192.168.50.10"
|
||||
assert_contains "LAN opt-in allows the wildcard dev origin Electron may report" "${output}" "http://0.0.0.0:5173"
|
||||
}
|
||||
|
||||
test_hot_dev_browser_urls_distinguish_bind_and_browser_hosts() {
|
||||
@@ -195,7 +216,7 @@ test_pulse_process_count_counts_matching_processes
|
||||
test_hot_dev_uses_resilient_backend_process_count
|
||||
test_hot_dev_keeps_backend_launch_errors_in_debug_log
|
||||
test_hot_dev_avoids_self_killing_npm_wrapper
|
||||
test_hot_dev_network_defaults_are_lan_capable
|
||||
test_hot_dev_network_defaults_are_local_first_with_explicit_lan_opt_in
|
||||
test_hot_dev_browser_urls_distinguish_bind_and_browser_hosts
|
||||
|
||||
if (( failures > 0 )); then
|
||||
|
||||
Reference in New Issue
Block a user