#!/usr/bin/env bash
# ssh-exec.sh — camada de execução remota do SSHepherd.
#
# Tudo que o framework faz no servidor passa por aqui. Este script:
#   1. carrega o perfil do servidor
#   2. classifica a operação por risco
#   3. recusa comandos da lista de proibição, sempre
#   4. mostra o comando é o alvo antes de qualquer alteração
#   5. executa com multiplexação de sessão e tempo limite
#   6. registra o evento no log, com segredos redigidos
#
# Uso:
#   ssh-exec.sh --profile <id> --op <operação> [--arg k=v ...] [--confirmed] [--dry-run]
#   ssh-exec.sh --profile <id> --raw '<comando>' --risk leitura [--confirmed]
#
# Códigos de saída:
#   0   sucesso
#   10  operação recusada pelo usuário
#   11  comando bloqueado pela lista de proibição
#   12  perfil inválido ou ausente
#   13  falha de conexão
#   14  pré-condição não atendida
#   124 tempo esgotado (command_timeout do config.toml)
#   >0  código de retorno do comando remoto
#
# --risk só SOBE o nível de risco, nunca baixa. Em --raw o piso e "alteração".

set -uo pipefail

SSHEPHERD_DIR="${SSHEPHERD_DIR:-.sshepherd}"
PROFILE_ID=""
OPERATION=""
RAW_COMMAND=""
RISK=""
CONFIRMED=0
DRY_RUN=0
declare -a OP_ARGS=()

# ---------------------------------------------------------------------------
# Classificação de risco por operação. O conjunto e fechado e conhecido:
# classificar por nome de operação e mais confiavel do que analisar texto de comando.
# ---------------------------------------------------------------------------
risk_for_operation() {
  case "$1" in
    whoami|osinfo|status|audit|logs|disk|releases|check-access|verify)
      echo "leitura" ;;
    create-ops-user|install-key|harden|provision|deploy|rollback|restart|fix)
      echo "alteração" ;;
    remove-key|prune-releases|disable-root|change-port)
      echo "destrutivo" ;;
    *)
      echo "alteração" ;;   # desconhecido nunca e tratado como leitura
  esac
}

# Risco vira numero para dar para comparar. So existe para uma regra: --risk
# pode SUBIR o nível, nunca baixar. Sem isso, quem chama escolhe o próprio risco
# e o portão de confirmação deixa de existir.
risk_level() {
  case "$1" in
    leitura)               echo 0 ;;
    destrutivo)            echo 2 ;;
    alteração|alteracao)   echo 1 ;;
    *)                     echo 1 ;;
  esac
}

# ---------------------------------------------------------------------------
# Leitura do config.toml. Chaves são únicas entre as secoes, então busca direta
# pelo nome resolve sem precisar entender secao.
# ---------------------------------------------------------------------------
config_get() {
  local key="$1" default="$2" file="${SSHEPHERD_DIR}/config.toml" value=""
  if [ -f "$file" ]; then
    value=$(grep -E "^[[:space:]]*${key}[[:space:]]*=" "$file" 2>/dev/null | head -n1 \
      | sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]*#.*$//; s/[[:space:]]*$//; s/^"//; s/"$//')
  fi
  printf '%s' "${value:-$default}"
}

# ---------------------------------------------------------------------------
# Lista de proibição. Recusada mesmo com confirmação explicita do usuário.
# ---------------------------------------------------------------------------
is_forbidden() {
  local cmd="$1"
  local patterns=(
    'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*f?[[:space:]]+/([[:space:]]|$)'
    # Substring solta de propósito: o padrão antigo exigia uma flag ENTRE `rm` e
    # `--no-preserve-root`, então `rm --no-preserve-root -rf /` passava. Não existe
    # uso legítimo dessa flag num comando que o framework envia.
    '[-]-no-preserve-root'
    'mkfs(\.[a-z0-9]+)?[[:space:]]'
    'dd[[:space:]]+.*of=/dev/(sd|nvme|vd|xvd)'
    '>[[:space:]]*/dev/(sd|nvme|vd|xvd)'
    ':\(\)\{.*\|.*&.*\};:'
    'systemctl[[:space:]]+(stop|disable|mask)[[:space:]]+(ssh|sshd)([[:space:]]|$)'
    'ufw[[:space:]]+(--force[[:space:]]+)?(reset|disable)([[:space:]]|$)'
    'userdel[[:space:]]+.*(root|deploy)'
    'chmod[[:space:]]+-R[[:space:]]+777[[:space:]]+/([[:space:]]|$)'
    'history[[:space:]]+-c'
    'shred[[:space:]]'
  )
  for p in "${patterns[@]}"; do
    if echo "$cmd" | grep -qE "$p"; then
      echo "$p"
      return 0
    fi
  done
  return 1
}

# ---------------------------------------------------------------------------
# Redação de segredos. Aplicada antes de qualquer gravacao ou exibicao.
# A função vive em redact.sh porque deploy, secrets e db mostram saída do
# servidor também, e a rede tem de ser a mesma em todos.
# ---------------------------------------------------------------------------
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/redact.sh"

# ---------------------------------------------------------------------------
# Leitura do perfil. Parser mínimo para não depender de jq no servidor do usuário.
# ---------------------------------------------------------------------------
json_field() {
  local file="$1" key="$2"
  grep -oE "\"${key}\"[[:space:]]*:[[:space:]]*(\"[^\"]*\"|[0-9]+|true|false)" "$file" 2>/dev/null \
    | head -n1 | sed -E 's/.*:[[:space:]]*//; s/^"//; s/"$//'
}

usage() {
  sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'
  exit 0
}

while [ $# -gt 0 ]; do
  case "$1" in
    --profile)   PROFILE_ID="$2"; shift 2 ;;
    --op)        OPERATION="$2"; shift 2 ;;
    --raw)       RAW_COMMAND="$2"; shift 2 ;;
    --risk)      RISK="$2"; shift 2 ;;
    --arg)       OP_ARGS+=("$2"); shift 2 ;;
    --confirmed) CONFIRMED=1; shift ;;
    --dry-run)   DRY_RUN=1; shift ;;
    --dir)       SSHEPHERD_DIR="$2"; shift 2 ;;
    -h|--help)   usage ;;
    *) echo "argumento desconhecido: $1" >&2; exit 12 ;;
  esac
done

[ -n "$PROFILE_ID" ] || { echo "ERRO: --profile é obrigatório" >&2; exit 12; }

# O risco e um conjunto fechado de tres valores. Sem esta validação, `--risk`
# entrava cru na linha do log e uma quebra de linha no valor permitia acrescentar
# um evento inteiro, forjado, ao lado do verdadeiro: a trilha de auditoria deixava
# de ser evidencia. A versão PowerShell ja tinha ValidateSet no mesmo parâmetro.
case "${RISK:-}" in
  ''|leitura|alteração|alteracao|destrutivo) ;;
  *) echo "ERRO: --risk aceita apenas leitura, alteração ou destrutivo" >&2; exit 12 ;;
esac

PROFILE_FILE="${SSHEPHERD_DIR}/servers/${PROFILE_ID}.json"
if [ ! -f "$PROFILE_FILE" ]; then
  echo "ERRO: perfil '${PROFILE_ID}' não encontrado em ${PROFILE_FILE}" >&2
  echo "Rode /sshp-setup para criar o primeiro perfil." >&2
  exit 12
fi

HOST=$(json_field "$PROFILE_FILE" host)
PORT=$(json_field "$PROFILE_FILE" port)
USER_NAME=$(json_field "$PROFILE_FILE" user)
KEY_PATH=$(json_field "$PROFILE_FILE" key_path)
ENVIRONMENT=$(json_field "$PROFILE_FILE" environment)
PORT="${PORT:-22}"
ENVIRONMENT="${ENVIRONMENT:-desconhecido}"

[ -n "$HOST" ] || { echo "ERRO: perfil sem campo 'host'" >&2; exit 12; }
[ -n "$USER_NAME" ] || { echo "ERRO: perfil sem campo 'user'" >&2; exit 12; }

# EC-01: chave apontada pelo perfil pode ter sido movida ou apagada.
if [ -n "$KEY_PATH" ] && [ ! -f "${KEY_PATH/#\~/$HOME}" ]; then
  echo "ERRO: a chave do perfil não existe em ${KEY_PATH}" >&2
  echo "Atualize o perfil ou gere a chave de novo com keygen.sh." >&2
  exit 12
fi

# ---------------------------------------------------------------------------
# Monta o comando remoto
# ---------------------------------------------------------------------------
# Comando LIVRE: texto que veio de quem chama (--raw ou --arg), e não do conjunto
# fechado deste script. Quem chama costuma ser o agente, e o agente não pode
# ter um shell remoto arbitrário: é por ele que o segredo sairia do servidor.
COMANDO_LIVRE=0
DELEGAR=""

if [ -n "$RAW_COMMAND" ]; then
  REMOTE_CMD="$RAW_COMMAND"
  OP_LABEL="raw"
  COMANDO_LIVRE=1
  # Comando cru não passou por classificação nenhuma: o piso e "alteração" e
  # --risk só serve para subir daqui. Aceitar `--risk leitura` num --raw seria
  # entregar a chave do portão para quem esta batendo nele.
  if [ "$(risk_level "${RISK:-}")" -gt 1 ]; then RISK="destrutivo"; else RISK="alteração"; fi
else
  [ -n "$OPERATION" ] || { echo "ERRO: informe --op ou --raw" >&2; exit 12; }
  OP_LABEL="$OPERATION"
  BASE_RISK=$(risk_for_operation "$OPERATION")
  # Mesma regra: quem chama pode declarar risco MAIOR, nunca menor.
  if [ -n "$RISK" ] && [ "$(risk_level "$RISK")" -le "$(risk_level "$BASE_RISK")" ]; then
    RISK="$BASE_RISK"
  fi
  RISK="${RISK:-$BASE_RISK}"
  case "$OPERATION" in
    whoami)  REMOTE_CMD='id -un; id -u' ;;
    osinfo)  REMOTE_CMD='. /etc/os-release 2>/dev/null && echo "${ID} ${VERSION_ID}"' ;;
    disk)    REMOTE_CMD='df -h / | tail -n1' ;;
    releases) REMOTE_CMD='ls -1t "${APP_PATH:-/srv/app}/releases" 2>/dev/null | head -20' ;;
    # Registro do serviço, com limite fixo e sem paginador. É o único jeito de o
    # agente ler log pelo executor; o texto passa pela redação como tudo aqui.
    logs)    REMOTE_CMD='sudo -n journalctl --no-pager -u sshepherd-app -n 100 2>/dev/null || tail -n 100 "${APP_PATH:-/srv/app}/shared/logs/app.err" 2>/dev/null || echo "sem registro acessivel"' ;;
    # As cinco condições do Dia Zero, pelo verify-day-zero.sh com os dados do
    # perfil. Assim o caminho da chave não precisa aparecer na linha de comando.
    verify)  if [ "${#OP_ARGS[@]}" -eq 0 ]; then
               DELEGAR="verify-day-zero.sh"
               REMOTE_CMD="(verify-day-zero.sh: as cinco condições do Dia Zero)"
             else
               REMOTE_CMD="${OP_ARGS[*]}"
               COMANDO_LIVRE=1
               [ "$(risk_level "$RISK")" -lt 1 ] && RISK="alteração"
             fi ;;
    *)
      # Operação sem comando fixo: o texto vem de quem chama, exatamente como no
      # --raw, e por isso vale o mesmo piso. Sem ele, `--op status --arg '<cmd>'`
      # executava com risco `leitura`, e leitura pula plano, confirmação, portão
      # de teclado e espelho de log de uma vez só.
      REMOTE_CMD="${OP_ARGS[*]:-}"
      if [ -n "$REMOTE_CMD" ]; then
        COMANDO_LIVRE=1
        [ "$(risk_level "$RISK")" -lt 1 ] && RISK="alteração"
      fi
      ;;
  esac
  [ -n "$REMOTE_CMD" ] || { echo "ERRO: operação '${OPERATION}' sem comando associado" >&2; exit 12; }
fi

# ---------------------------------------------------------------------------
# Log. Definido antes dos guard rails porque toda recusa também e registrada.
# ---------------------------------------------------------------------------
LOG_DIR="${SSHEPHERD_DIR}/logs"
LOG_FILE="${LOG_DIR}/audit-$(date -u +%Y-%m).jsonl"
mkdir -p "$LOG_DIR"

MAX_LOG_MB=$(config_get max_log_mb 5)
LOG_FULL_OUTPUT=$(config_get log_full_output false)

# Rotação: o mes que passou do tamanho vira .1 e o arquivo recomeca.
if [ -f "$LOG_FILE" ]; then
  LOG_BYTES=$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0)
  if [ "${LOG_BYTES:-0}" -gt $(( ${MAX_LOG_MB:-5} * 1024 * 1024 )) ] 2>/dev/null; then
    mv "$LOG_FILE" "${LOG_FILE}.1"
  fi
fi

json_escape() {
  printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' | tr '\n' ' '
}

# O log local mora no disco de quem opera, e quem opera pode reescrever ele.
# O que já saiu para o servidor, não. Só o que altera vale a viagem, e ela
# reaproveita a sessão multiplexada que já esta aberta.
espelhar_no_servidor() {
  [ "$RISK" = "leitura" ] && return 0
  [ "${SSHEPHERD_MIRROR_LOG:-1}" = "1" ] || return 0
  printf '%s\n' "$1" \
    | ssh "${SSH_OPTS[@]}" "${USER_NAME}@${HOST}" \
        'command -v logger >/dev/null 2>&1 && logger -t sshepherd -p local0.notice' \
        >/dev/null 2>&1 || true
}

log_event() {
  local profile="$1" env="$2" op="$3" risk="$4" confirmed="$5" code="$6" duration="$7" summary="$8"
  local safe_summary
  safe_summary=$(printf '%s' "$summary" | redact)
  printf '{"timestamp":"%s","profile_id":"%s","environment":"%s","operation":"%s","risk":"%s","confirmed":%s,"exit_code":%s,"duration_ms":%s,"summary":"%s"}\n' \
    "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
    "$(json_escape "$profile")" "$(json_escape "$env")" "$(json_escape "$op")" \
    "$(json_escape "$risk")" "$confirmed" "$code" "$duration" "$(json_escape "$safe_summary")" \
    >> "$LOG_FILE"
}

# ---------------------------------------------------------------------------
# Guard rail 1: lista de proibição. Não ha confirmação que libere.
# ---------------------------------------------------------------------------
if FORBIDDEN=$(is_forbidden "$REMOTE_CMD"); then
  echo "BLOQUEADO: o comando corresponde a uma regra de proibição do SSHepherd." >&2
  echo "  regra: ${FORBIDDEN}" >&2
  echo "  Esta recusa não pode ser sobreposta por confirmação." >&2
  log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" "false" "11" "0" "bloqueado pela lista de proibição"
  exit 11
fi

# ---------------------------------------------------------------------------
# Guard rail 2: dry-run e confirmação para tudo que altera o servidor
# ---------------------------------------------------------------------------
show_plan() {
  echo "--------------------------------------------"
  echo " Operação : ${OP_LABEL}"
  echo " Risco    : ${RISK}"
  echo " Servidor : ${USER_NAME}@${HOST}:${PORT}"
  echo " Ambiente : ${ENVIRONMENT}"
  echo " Perfil   : ${PROFILE_ID}"
  echo "--------------------------------------------"
  echo " Comando que será executado:"
  printf '%s\n' "$REMOTE_CMD" | redact | sed 's/^/   /'
  echo "--------------------------------------------"
}

MUTATION_POLICY=$(config_get mutation_policy confirm)
STRICT_PRODUCTION=$(config_get strict_production true)

# A postura vem do config.toml, não de quem chama o script.
exige_confirmacao() {
  case "$MUTATION_POLICY" in
    auto)             return 1 ;;
    destructive_only) [ "$RISK" = "destrutivo" ] ;;
    *)                [ "$(risk_level "$RISK")" -ge 1 ] ;;
  esac
}

# `produção`, `producao`, `production`, `prod`: a mesma coisa para esta regra.
e_producao() {
  printf '%s' "$ENVIRONMENT" | grep -qiE 'produ|^prod$'
}

# --confirmed e uma AFIRMACAO de quem chama, e quem chama pode ser um agente que
# alucinou a confirmação. Para o que e destrutivo em produção isso não basta: a
# pessoa digita o identificador do perfil no teclado, e o teclado o agente não tem.
# O mesmo portão vale para comando livre: o motivo muda, o teclado não.
confirmar_no_teclado() {
  local motivo="$1"
  # Abrir /dev/tty e o teste que vale. O arquivo pode existir e o processo não
  # ter terminal de controle: ai o open falha, e e exatamente o caso do agente
  # rodando sem ninguem na frente da tela.
  if ! { exec 3</dev/tty; } 2>/dev/null; then
    echo "RECUSADO: ${motivo} exige confirmação no teclado," >&2
    echo "  e esta sessão não tem terminal ligado." >&2
    echo "  Rode este mesmo comando você, direto no seu terminal." >&2
    return 1
  fi
  echo "" >&2
  echo "Atenção: ${motivo}." >&2
  echo "Para seguir, digite o identificador do perfil e dê Enter: ${PROFILE_ID}" >&2
  printf '  > ' >&2
  local resposta=""
  IFS= read -r -t 120 resposta <&3 || { exec 3<&-; echo "  Sem resposta. Nada foi executado." >&2; return 1; }
  exec 3<&-
  if [ "$resposta" != "$PROFILE_ID" ]; then
    echo "  Não confere. Nada foi executado." >&2
    return 1
  fi
  return 0
}

if [ "$RISK" != "leitura" ]; then
  show_plan
  if [ "$DRY_RUN" -eq 1 ]; then
    echo "Modo dry-run: nada foi executado."
    log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" "false" "0" "0" "dry-run, não executado"
    exit 0
  fi
  if exige_confirmacao && [ "$CONFIRMED" -ne 1 ]; then
    echo "RECUSADO: operação de risco '${RISK}' exige --confirmed." >&2
    log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" "false" "10" "0" "sem confirmação explicita"
    exit 10
  fi
  # Vale mesmo com mutation_policy = "auto": desligar a pergunta de rotina não
  # pode desligar o freio do irreversível em produção. Para abrir mão dele e
  # preciso trocar strict_production no config.toml, de propósito.
  if [ "$RISK" = "destrutivo" ] && [ "$STRICT_PRODUCTION" = "true" ] && e_producao; then
    if ! confirmar_no_teclado "destrutivo em '${ENVIRONMENT}'"; then
      log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" "false" "10" "0" "confirmação de teclado não obtida"
      exit 10
    fi
  fi
  # Comando livre é shell remoto arbitrário. Com ele, o agente lê o cofre com
  # `sudo cat`, despeja o ambiente do serviço ou codifica a saída em base64 para
  # passar pela redação. Não existe lista de proibição que cubra isso: regex é
  # lombada, não muro. Por isso comando livre é operação de PESSOA, em qualquer
  # ambiente e com qualquer política: quem digita o identificador no teclado é
  # quem responde por ele. O agente entrega o comando pronto e a pessoa roda.
  if [ "$COMANDO_LIVRE" -eq 1 ]; then
    if ! confirmar_no_teclado "comando livre (fora do conjunto fixo do executor)"; then
      log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" "false" "10" "0" "comando livre sem teclado"
      exit 10
    fi
  fi
fi

# ---------------------------------------------------------------------------
# Execução
# ---------------------------------------------------------------------------
SSH_OPTS=(
  -o BatchMode=yes
  -o StrictHostKeyChecking=accept-new
  -o ForwardAgent=no
  -o ConnectTimeout="${SSHEPHERD_CONNECT_TIMEOUT:-$(config_get connect_timeout 10)}"
  -p "$PORT"
)

# Multiplexar conexões no Windows quebra: o ssh do Git Bash/MSYS não sustenta o
# socket de controle, a conexão cai no meio e o servidor conta cada queda como
# tentativa falha. Algumas quedas e o fail2ban bane o IP de quem opera.
case "$(uname -s 2>/dev/null)" in
  MINGW*|MSYS*|CYGWIN*)
    SSH_OPTS+=(-o ControlMaster=no -o ControlPath=none)
    ;;
  *)
    CONTROL_DIR="${SSHEPHERD_DIR}/.control"
    mkdir -p "$CONTROL_DIR"
    chmod 700 "$CONTROL_DIR" 2>/dev/null || true
    SSH_OPTS+=(
      -o ControlMaster=auto
      -o ControlPath="${CONTROL_DIR}/%r@%h:%p"
      -o ControlPersist="${SSHEPHERD_CONTROL_PERSIST:-$(config_get control_persist 600)}"
    )
    ;;
esac
[ -n "$KEY_PATH" ] && SSH_OPTS+=(-i "${KEY_PATH/#\~/$HOME}" -o IdentitiesOnly=yes)

# ConnectTimeout so cobre ABRIR a conexão. Comando remoto que pendura depois de
# aberta segurava a sessão para sempre; e o que `timeout` resolve aqui.
case "$OP_LABEL" in
  provision|harden|deploy|fix) COMMAND_TIMEOUT=$(config_get long_command_timeout 900) ;;
  *)                           COMMAND_TIMEOUT=$(config_get command_timeout 120) ;;
esac
RUNNER=(ssh)
if command -v timeout >/dev/null 2>&1 && [ "${COMMAND_TIMEOUT:-0}" -gt 0 ] 2>/dev/null; then
  RUNNER=(timeout --signal=TERM --kill-after=10 "$COMMAND_TIMEOUT" ssh)
fi

START_MS=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
if [ -n "$DELEGAR" ]; then
  OUTPUT=$(bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/${DELEGAR}" \
    --host "$HOST" --user "$USER_NAME" --port "$PORT" ${KEY_PATH:+--key "$KEY_PATH"} 2>&1)
else
  OUTPUT=$("${RUNNER[@]}" "${SSH_OPTS[@]}" "${USER_NAME}@${HOST}" "$REMOTE_CMD" 2>&1)
fi
CODE=$?
END_MS=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
DURATION=$((END_MS - START_MS))

if [ $CODE -eq 124 ]; then
  echo "TEMPO ESGOTADO: o comando passou de ${COMMAND_TIMEOUT}s e foi interrompido." >&2
  echo "  A operação pode ter ficado pela metade no servidor. Confira antes de repetir." >&2
  printf '%s\n' "$OUTPUT" | redact >&2
  log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" \
    "$([ $CONFIRMED -eq 1 ] && echo true || echo false)" "124" "$DURATION" "tempo esgotado em ${COMMAND_TIMEOUT}s"
  exit 124
fi

if [ $CODE -eq 255 ]; then
  echo "FALHA DE CONEXAO com ${USER_NAME}@${HOST}:${PORT}" >&2
  printf '%s\n' "$OUTPUT" | redact >&2
  echo "" >&2
  echo "Causas mais comuns, em ordem:" >&2
  echo "  1. servidor ainda ligando ou desligado" >&2
  echo "  2. chave não instalada no usuário '${USER_NAME}'" >&2
  echo "  3. porta ${PORT} fechada no firewall do servidor ou do provedor" >&2
  log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" "$([ $CONFIRMED -eq 1 ] && echo true || echo false)" "13" "$DURATION" "falha de conexão"
  exit 13
fi

printf '%s\n' "$OUTPUT" | redact

# Redige ANTES de cortar. Cortar primeiro podia levar embora o `TOKEN=` ou o
# `-----BEGIN` que a redação usa para reconhecer o segredo, e o log ficava com
# o pedaço final do segredo em claro, sem marcador nenhum.
SAFE_OUTPUT=$(printf '%s' "$OUTPUT" | redact)
if [ "$LOG_FULL_OUTPUT" = "true" ]; then
  SUMMARY="$SAFE_OUTPUT"
else
  SUMMARY=$(printf '%s' "$SAFE_OUTPUT" | tail -c 400)
fi

log_event "$PROFILE_ID" "$ENVIRONMENT" "$OP_LABEL" "$RISK" \
  "$([ $CONFIRMED -eq 1 ] && echo true || echo false)" "$CODE" "$DURATION" "$SUMMARY"

espelhar_no_servidor "$(printf 'op=%s risco=%s perfil=%s ambiente=%s codigo=%s duracao_ms=%s' \
  "$OP_LABEL" "$RISK" "$PROFILE_ID" "$ENVIRONMENT" "$CODE" "$DURATION")"

exit $CODE
