refactor(core/bootloader): extract fw check from main.c
What changed, and why it matters
This commit is a straightforward code cleanup: it moves the existing firmware-validation logic out of the bootloader's main.c into a new file (fw_check.c/h) and updates the surrounding code to use the new structure. There is no change to what the bootloader actually checks or how strict it is. It is a pure refactor with no security-relevant behavior change visible in the diff.
No security action required. Treat as normal refactoring; standard code review and regression testing are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change extracts the firmware image verification chain (vendor header, keys, lock, image header, model, signatures, minimum version, secure-monitor validation, and image contents) from bootloader_main() into a new fw_check() function. It also changes fw_info_t from a struct of pointers to a struct that embeds a copy of the vendor header and tracks header_present/firmware_present/firmware_present_backup. The same checks are performed in the same order, and the same glitch-protection duplication of firmware_present is preserved. protob.c is updated because fw_info_t.vhdr is now a value member instead of a pointer, and the condition for reporting firmware_present now uses header_present rather than a non-null vhdr pointer.
Changed components
Trezor core bootloader firmware image validationbootloader protocol buffer Features message generationbootloader workflow UIInspect captured patch +255 / −139
diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader
index 9f6c2d62..f3a54067 100644
--- a/core/SConscript.bootloader
+++ b/core/SConscript.bootloader
@@ -156,6 +156,7 @@ SOURCE_BOOTLOADER = [
'embed/projects/bootloader/wire/wire_iface_ble.c',
'embed/projects/bootloader/protob/protob.c',
'embed/projects/bootloader/protob/pb/messages.pb.c',
+ 'embed/projects/bootloader/fw_check.c',
'embed/projects/bootloader/version_check.c',
]
diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu
index b047b02e..68fdb0c8 100644
--- a/core/SConscript.bootloader_emu
+++ b/core/SConscript.bootloader_emu
@@ -123,6 +123,7 @@ SOURCE_BOOTLOADER = [
'embed/projects/bootloader/wire/wire_iface_ble.c',
'embed/projects/bootloader/protob/protob.c',
'embed/projects/bootloader/protob/pb/messages.pb.c',
+ 'embed/projects/bootloader/fw_check.c',
'embed/projects/bootloader/version_check.c',
'embed/projects/bootloader/emulator.c',
]
diff --git a/core/embed/projects/bootloader/fw_check.c b/core/embed/projects/bootloader/fw_check.c
new file mode 100644
index 00000000..998698ae
--- /dev/null
+++ b/core/embed/projects/bootloader/fw_check.c
@@ -0,0 +1,150 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <trezor_model.h>
+#include <trezor_rtl.h>
+
+#include <util/flash.h>
+#include <util/flash_otp.h>
+#include <util/image.h>
+
+#include "fw_check.h"
+#include "version_check.h"
+
+#ifdef TREZOR_EMULATOR
+#include "emulator.h"
+#endif
+
+secbool check_vendor_header_lock(const vendor_header *vhdr) {
+ uint8_t lock[FLASH_OTP_BLOCK_SIZE];
+ ensure(flash_otp_read(FLASH_OTP_BLOCK_VENDOR_HEADER_LOCK, 0, lock,
+ FLASH_OTP_BLOCK_SIZE),
+ NULL);
+ if (0 ==
+ memcmp(lock,
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ FLASH_OTP_BLOCK_SIZE)) {
+ return sectrue;
+ }
+ uint8_t hash[32];
+ vendor_header_hash(vhdr, hash);
+ return sectrue * (0 == memcmp(lock, hash, 32));
+}
+
+void fw_check(fw_info_t *fw_info) {
+ memset(fw_info, 0, sizeof(*fw_info));
+
+ const image_header *hdr = NULL;
+
+ // detect whether the device contains a valid firmware
+ volatile secbool vhdr_present = secfalse;
+ volatile secbool vhdr_keys_ok = secfalse;
+ volatile secbool vhdr_lock_ok = secfalse;
+ volatile secbool img_hdr_ok = secfalse;
+ volatile secbool model_ok = secfalse;
+ volatile secbool signatures_ok = secfalse;
+ volatile secbool version_ok = secfalse;
+ volatile secbool secmon_valid = secfalse;
+
+ vhdr_present =
+ read_vendor_header((const uint8_t *)FIRMWARE_START, &fw_info->vhdr);
+
+ if (sectrue == vhdr_present) {
+ vhdr_keys_ok = check_vendor_header_keys(&fw_info->vhdr);
+ }
+
+ if (sectrue == vhdr_keys_ok) {
+ vhdr_lock_ok = check_vendor_header_lock(&fw_info->vhdr);
+ }
+
+ if (sectrue == vhdr_lock_ok) {
+ hdr = read_image_header(
+ (const uint8_t *)(size_t)(FIRMWARE_START + fw_info->vhdr.hdrlen),
+ FIRMWARE_IMAGE_MAGIC, FIRMWARE_MAXSIZE);
+ if (hdr ==
+ (const image_header *)(size_t)(FIRMWARE_START + fw_info->vhdr.hdrlen)) {
+ img_hdr_ok = sectrue;
+ }
+ }
+ if (sectrue == img_hdr_ok) {
+ model_ok = check_image_model(hdr);
+ }
+
+ if (sectrue == model_ok) {
+ signatures_ok = check_image_header_sig(
+ hdr, fw_info->vhdr.vsig_m, fw_info->vhdr.vsig_n, fw_info->vhdr.vpub);
+ }
+
+ if (sectrue == signatures_ok) {
+ version_ok = check_firmware_min_version(hdr->monotonic);
+ }
+
+ if (sectrue == version_ok) {
+ fw_info->header_present = version_ok;
+ }
+
+#ifdef USE_SECMON_VERIFICATION
+ size_t secmon_start = (size_t)IMAGE_CODE_ALIGN(
+ FIRMWARE_START + fw_info->vhdr.hdrlen + IMAGE_HEADER_SIZE);
+
+ const secmon_header_t *secmon_hdr =
+ read_secmon_header((const uint8_t *)secmon_start, FIRMWARE_MAXSIZE);
+
+ volatile secbool secmon_header_present = secfalse;
+ volatile secbool secmon_model_valid = secfalse;
+ volatile secbool secmon_header_sig_valid = secfalse;
+ volatile secbool secmon_contents_valid = secfalse;
+
+ if (sectrue == fw_info->header_present) {
+ secmon_header_present =
+ secbool_and(fw_info->header_present, (secmon_hdr != NULL) * sectrue);
+ }
+
+ if (sectrue == secmon_header_present) {
+ secmon_model_valid =
+ secbool_and(secmon_header_present, check_secmon_model(secmon_hdr));
+ }
+
+ if (sectrue == secmon_model_valid) {
+ secmon_header_sig_valid =
+ secbool_and(secmon_model_valid, check_secmon_header_sig(secmon_hdr));
+ }
+
+ if (sectrue == secmon_header_sig_valid) {
+ secmon_contents_valid = secbool_and(
+ secmon_header_sig_valid,
+ check_secmon_contents(secmon_hdr, secmon_start - FIRMWARE_START,
+ &FIRMWARE_AREA));
+ secmon_valid = secmon_contents_valid;
+ }
+
+#else
+ secmon_valid = fw_info->header_present;
+#endif
+
+ if (sectrue == secmon_valid) {
+ ensure_firmware_min_version(hdr->monotonic);
+ fw_info->firmware_present = check_image_contents(
+ hdr, IMAGE_HEADER_SIZE + fw_info->vhdr.hdrlen, &FIRMWARE_AREA);
+ fw_info->firmware_present_backup = fw_info->firmware_present;
+ }
+
+ fw_info->hdr = hdr;
+}
diff --git a/core/embed/projects/bootloader/fw_check.h b/core/embed/projects/bootloader/fw_check.h
new file mode 100644
index 00000000..f0aa4571
--- /dev/null
+++ b/core/embed/projects/bootloader/fw_check.h
@@ -0,0 +1,75 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include <util/image.h>
+
+/**
+ * @brief Firmware information collected by the bootloader when validating
+ * images present in flash.
+ *
+ * This structure is filled by `fw_check()` and then used by the bootloader to
+ * decide whether it can safely boot the firmware image.
+ */
+typedef struct {
+ vendor_header vhdr; /**< Parsed vendor header copied from flash.
+ Contains vendor/product identifiers,
+ versioning and policy flags (e.g.,
+ lock, minimum versions). */
+
+ const image_header *hdr; /**< Pointer to the validated image header of
+ the selected firmware (primary or
+ backup). NULL if no valid header was
+ found. */
+
+ volatile secbool header_present; /**< True if a header structure was
+ found and passed basic checks. */
+
+ volatile secbool firmware_present; /**< True if a valid, bootable
+firmware image is present. */
+
+ volatile secbool firmware_present_backup; /**< True if a valid, bootable
+ firmware image is present - backup for
+ glitch protection. */
+} fw_info_t;
+
+/**
+ * @brief Verify whether the vendor header is the same as the locked version.
+ *
+ * @param vhdr Pointer to the vendor header to validate.
+ * @return sectrue when the vendor header is the same or there is no lock;
+ * secfalse otherwise.
+ */
+secbool check_vendor_header_lock(const vendor_header *vhdr);
+
+/**
+ * @brief Perform comprehensive verification of the firmware image available
+ * in flash (both primary and backup).
+ *
+ * Populates `fw_info` with details about discovered headers and whether the
+ * image is valid and bootable.
+ *
+ * @param fw_info Output structure to be filled by this function; must be
+ * provided by the caller and remain valid for subsequent boot
+ * decisions.
+ */
+void fw_check(fw_info_t *fw_info);
diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c
index 49e074f2..f7ae76cd 100644
--- a/core/embed/projects/bootloader/main.c
+++ b/core/embed/projects/bootloader/main.c
@@ -31,7 +31,6 @@
#include <sys/system.h>
#include <sys/systick.h>
#include <sys/types.h>
-#include <util/flash_otp.h>
#include <util/flash_utils.h>
#include <util/image.h>
#include <util/rsod.h>
@@ -89,6 +88,7 @@
#endif
#include "bootui.h"
+#include "fw_check.h"
#include "rust_ui_common.h"
#include "ui_helpers.h"
#include "version_check.h"
@@ -105,10 +105,14 @@ void failed_jump_to_firmware(void);
volatile secbool dont_optimize_out_true = sectrue;
void (*volatile firmware_jump_fn)(void) = failed_jump_to_firmware;
-static secbool is_manufacturing_mode(vendor_header *vhdr) {
+static secbool is_manufacturing_mode(void) {
unit_properties_init();
- if ((vhdr->vtrust & VTRUST_ALLOW_PROVISIONING) != VTRUST_ALLOW_PROVISIONING) {
+ vendor_header vhdr;
+ memset(&vhdr, 0, sizeof(vhdr));
+ (void)!read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr);
+
+ if ((vhdr.vtrust & VTRUST_ALLOW_PROVISIONING) != VTRUST_ALLOW_PROVISIONING) {
return secfalse;
}
@@ -347,23 +351,6 @@ static void drivers_deinit(void) {
#endif
}
-static secbool check_vendor_header_lock(const vendor_header *const vhdr) {
- uint8_t lock[FLASH_OTP_BLOCK_SIZE];
- ensure(flash_otp_read(FLASH_OTP_BLOCK_VENDOR_HEADER_LOCK, 0, lock,
- FLASH_OTP_BLOCK_SIZE),
- NULL);
- if (0 ==
- memcmp(lock,
- "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
- "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
- FLASH_OTP_BLOCK_SIZE)) {
- return sectrue;
- }
- uint8_t hash[32];
- vendor_header_hash(vhdr, hash);
- return sectrue * (0 == memcmp(lock, hash, 32));
-}
-
void failed_jump_to_firmware(void) { error_shutdown("(glitch)"); }
void real_jump_to_firmware(void) {
@@ -504,11 +491,7 @@ int bootloader_main(void) {
boot_ucb_erase();
#endif
- vendor_header vhdr;
- volatile secbool vhdr_present = secfalse;
- vhdr_present = read_vendor_header((const uint8_t *)FIRMWARE_START, &vhdr);
-
- secbool manufacturing_mode = is_manufacturing_mode(&vhdr);
+ secbool manufacturing_mode = is_manufacturing_mode();
secbool stay_in_bootloader = boot_sequence();
@@ -564,99 +547,10 @@ int bootloader_main(void) {
hal_delay(400);
#endif
- const image_header *hdr = NULL;
-
- // detect whether the device contains a valid firmware
- volatile secbool vhdr_keys_ok = secfalse;
- volatile secbool vhdr_lock_ok = secfalse;
- volatile secbool img_hdr_ok = secfalse;
- volatile secbool model_ok = secfalse;
- volatile secbool signatures_ok = secfalse;
- volatile secbool version_ok = secfalse;
- volatile secbool header_present = secfalse;
- volatile secbool firmware_present = secfalse;
- volatile secbool firmware_present_backup = secfalse;
volatile secbool auto_upgrade = secfalse;
- volatile secbool secmon_valid = secfalse;
-
- if (sectrue == vhdr_present) {
- vhdr_keys_ok = check_vendor_header_keys(&vhdr);
- }
-
- if (sectrue == vhdr_keys_ok) {
- vhdr_lock_ok = check_vendor_header_lock(&vhdr);
- }
-
- if (sectrue == vhdr_lock_ok) {
- hdr = read_image_header(
- (const uint8_t *)(size_t)(FIRMWARE_START + vhdr.hdrlen),
- FIRMWARE_IMAGE_MAGIC, FIRMWARE_MAXSIZE);
- if (hdr == (const image_header *)(size_t)(FIRMWARE_START + vhdr.hdrlen)) {
- img_hdr_ok = sectrue;
- }
- }
- if (sectrue == img_hdr_ok) {
- model_ok = check_image_model(hdr);
- }
-
- if (sectrue == model_ok) {
- signatures_ok =
- check_image_header_sig(hdr, vhdr.vsig_m, vhdr.vsig_n, vhdr.vpub);
- }
-
- if (sectrue == signatures_ok) {
- version_ok = check_firmware_min_version(hdr->monotonic);
- }
- if (sectrue == version_ok) {
- header_present = version_ok;
- }
-
-#ifdef USE_SECMON_VERIFICATION
- size_t secmon_start = (size_t)IMAGE_CODE_ALIGN(FIRMWARE_START + vhdr.hdrlen +
- IMAGE_HEADER_SIZE);
-
- const secmon_header_t *secmon_hdr =
- read_secmon_header((const uint8_t *)secmon_start, FIRMWARE_MAXSIZE);
-
- volatile secbool secmon_header_present = secfalse;
- volatile secbool secmon_model_valid = secfalse;
- volatile secbool secmon_header_sig_valid = secfalse;
- volatile secbool secmon_contents_valid = secfalse;
-
- if (sectrue == header_present) {
- secmon_header_present =
- secbool_and(header_present, (secmon_hdr != NULL) * sectrue);
- }
-
- if (sectrue == secmon_header_present) {
- secmon_model_valid =
- secbool_and(secmon_header_present, check_secmon_model(secmon_hdr));
- }
-
- if (sectrue == secmon_model_valid) {
- secmon_header_sig_valid =
- secbool_and(secmon_model_valid, check_secmon_header_sig(secmon_hdr));
- }
-
- if (sectrue == secmon_header_sig_valid) {
- secmon_contents_valid = secbool_and(
- secmon_header_sig_valid,
- check_secmon_contents(secmon_hdr, secmon_start - FIRMWARE_START,
- &FIRMWARE_AREA));
- secmon_valid = secmon_contents_valid;
- }
-
-#else
- secmon_valid = header_present;
-#endif
-
- if (sectrue == secmon_valid) {
- ensure_firmware_min_version(hdr->monotonic);
- firmware_present = check_image_contents(
- hdr, IMAGE_HEADER_SIZE + vhdr.hdrlen, &FIRMWARE_AREA);
- firmware_present_backup = firmware_present;
- }
+ fw_info_t fw;
+ fw_check(&fw);
#if PRODUCTION && !defined STM32U5
// for STM32U5, this check is moved to boardloader
@@ -669,7 +563,7 @@ int bootloader_main(void) {
stay_in_bootloader = sectrue;
break;
case BOOT_COMMAND_INSTALL_UPGRADE:
- if (firmware_present == sectrue) {
+ if (fw.firmware_present == sectrue) {
// continue without user interaction
auto_upgrade = sectrue;
}
@@ -678,7 +572,8 @@ int bootloader_main(void) {
break;
}
- ensure(dont_optimize_out_true * (firmware_present == firmware_present_backup),
+ ensure(dont_optimize_out_true *
+ (fw.firmware_present == fw.firmware_present_backup),
NULL);
// delay to detect touch or skip if we know we are staying in bootloader
@@ -686,7 +581,7 @@ int bootloader_main(void) {
uint32_t touched = 0;
#ifndef USE_POWER_MANAGER
#ifdef USE_TOUCH
- if (firmware_present == sectrue && stay_in_bootloader != sectrue) {
+ if (fw.firmware_present == sectrue && stay_in_bootloader != sectrue) {
// Wait until the touch controller is ready
// (on hardware this may take a while)
if (touch_initialized != secfalse) {
@@ -714,7 +609,8 @@ int bootloader_main(void) {
#endif
#endif
- ensure(dont_optimize_out_true * (firmware_present == firmware_present_backup),
+ ensure(dont_optimize_out_true *
+ (fw.firmware_present == fw.firmware_present_backup),
NULL);
notify_send(NOTIFY_BOOT);
@@ -724,18 +620,16 @@ int bootloader_main(void) {
// ... or we have stay_in_bootloader flag to force it
// ... or strict upgrade was confirmed in the firmware (auto_upgrade flag)
// ... or there is no valid firmware
- if (touched || stay_in_bootloader == sectrue || firmware_present != sectrue ||
- auto_upgrade == sectrue) {
+ if (touched || stay_in_bootloader == sectrue ||
+ fw.firmware_present != sectrue || auto_upgrade == sectrue) {
workflow_result_t result;
#ifdef LAZY_DISPLAY_INIT
display_touch_init(secfalse, &touch_initialized);
#endif
- if (header_present == sectrue) {
- fw_info_t fw = {
- .vhdr = &vhdr, .hdr = hdr, .firmware_present = firmware_present};
- if (auto_upgrade == sectrue && firmware_present == sectrue) {
+ if (fw.header_present == sectrue) {
+ if (auto_upgrade == sectrue && fw.firmware_present == sectrue) {
result = workflow_auto_update(&fw);
} else {
result = workflow_bootloader(&fw);
@@ -778,11 +672,11 @@ int bootloader_main(void) {
}
}
} else {
- ensure(
- dont_optimize_out_true * (firmware_present == firmware_present_backup),
- NULL);
+ ensure(dont_optimize_out_true *
+ (fw.firmware_present == fw.firmware_present_backup),
+ NULL);
- if (sectrue == firmware_present) {
+ if (sectrue == fw.firmware_present) {
firmware_jump_fn = real_jump_to_firmware;
}
diff --git a/core/embed/projects/bootloader/protob/protob.c b/core/embed/projects/bootloader/protob/protob.c
index 5e470ab8..55622550 100644
--- a/core/embed/projects/bootloader/protob/protob.c
+++ b/core/embed/projects/bootloader/protob/protob.c
@@ -114,12 +114,12 @@ secbool send_msg_features(protob_io_t *iface, const fw_info_t *fw) {
MSG_SEND_ASSIGN_VALUE(bootloader_mode, true);
MSG_SEND_ASSIGN_STRING(model, MODEL_NAME);
MSG_SEND_ASSIGN_STRING(internal_model, MODEL_INTERNAL_NAME);
- if (fw != NULL && fw->vhdr != NULL && fw->hdr != NULL) {
+ if (fw != NULL && fw->hdr != NULL && fw->header_present == sectrue) {
MSG_SEND_ASSIGN_VALUE(firmware_present, true);
MSG_SEND_ASSIGN_VALUE(fw_major, (fw->hdr->version & 0xFF));
MSG_SEND_ASSIGN_VALUE(fw_minor, ((fw->hdr->version >> 8) & 0xFF));
MSG_SEND_ASSIGN_VALUE(fw_patch, ((fw->hdr->version >> 16) & 0xFF));
- MSG_SEND_ASSIGN_STRING_LEN(fw_vendor, fw->vhdr->vstr, fw->vhdr->vstr_len);
+ MSG_SEND_ASSIGN_STRING_LEN(fw_vendor, fw->vhdr.vstr, fw->vhdr.vstr_len);
MSG_SEND_ASSIGN_VALUE(firmware_corrupted, sectrue != fw->firmware_present);
} else {
MSG_SEND_ASSIGN_VALUE(firmware_present, false);
diff --git a/core/embed/projects/bootloader/protob/protob.h b/core/embed/projects/bootloader/protob/protob.h
index 53535b5c..c5c91185 100644
--- a/core/embed/projects/bootloader/protob/protob.h
+++ b/core/embed/projects/bootloader/protob/protob.h
@@ -25,6 +25,7 @@
#include "pb/messages.pb.h"
+#include "fw_check.h"
#include "wire/codec_v1.h"
typedef struct {
@@ -41,12 +42,6 @@ typedef struct {
secbool send_user_abort(protob_io_t *iface, const char *msg);
-typedef struct {
- const vendor_header *vhdr;
- const image_header *hdr;
- secbool firmware_present;
-} fw_info_t;
-
secbool send_msg_features(protob_io_t *iface, const fw_info_t *fw);
secbool send_msg_failure(protob_io_t *iface, FailureType type, const char *msg);
diff --git a/core/embed/projects/bootloader/workflow/wf_bootloader.c b/core/embed/projects/bootloader/workflow/wf_bootloader.c
index 39177b6c..eb3744e8 100644
--- a/core/embed/projects/bootloader/workflow/wf_bootloader.c
+++ b/core/embed/projects/bootloader/workflow/wf_bootloader.c
@@ -105,7 +105,7 @@ typedef enum {
// for the result.
static screen_t handle_intro(const fw_info_t* fw,
workflow_result_t* out_result) {
- intro_result_t ui = ui_screen_intro(fw->vhdr, fw->hdr, fw->firmware_present);
+ intro_result_t ui = ui_screen_intro(&fw->vhdr, fw->hdr, fw->firmware_present);
if (ui == INTRO_MENU) return SCREEN_MENU;
if (ui == INTRO_HOST) return SCREEN_WAIT_FOR_HOST;
// no other valid INTRO result -> fatal
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.