From grimoire
Hardens IoT devices for production: removes default credentials, disables JTAG/UART, encrypts data at rest, and adds tamper detection.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-iot-device-hardeningThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Remove default credentials, disable JTAG/UART debug interfaces, encrypt data at rest, and implement physical tamper detection — preventing both remote and physical compromise of deployed IoT devices.
Remove default credentials, disable JTAG/UART debug interfaces, encrypt data at rest, and implement physical tamper detection — preventing both remote and physical compromise of deployed IoT devices.
Adopted by: OWASP IoT Top 10 I9 (Insecure Default Settings) and I10 (Lack of Physical Hardening). ETSI EN 303 645 Section 5.1 mandates unique per-device credentials and prohibits default passwords. The UK Product Security and Telecommunications Infrastructure (PSTI) Act 2024 bans universal default passwords for consumer IoT — manufacturers face fines up to £10M. NIST SP 800-193 requires firmware resilience against physical access. Medical IoT (FDA guidance 2023), industrial IoT (IEC 62443), and automotive IoT (UNECE WP.29) all mandate these controls. Impact: Mirai botnet's primary infection vector was default credentials (admin/admin, root/1234) on Telnet and HTTP — 600,000 devices compromised within weeks of release. Shodan 2023 found 2.3 million IoT devices still using factory default credentials. Physical hardware attacks allow credential extraction from unprotected UART/JTAG interfaces in under 5 minutes with a $30 FTDI adapter and publicly available firmware extraction tools. NIST estimates 30% of IoT breaches involve physical access to the device. Why best: Remote hardening (network services, TLS) protects against internet-facing attacks; physical hardening adds a layer against supply chain attacks, insider threats, and theft of deployed devices. Default credentials are unique in that they're publicly known to attackers before the device is even deployed — per-device unique credentials eliminate this entire attack class at zero ongoing cost.
Sources: OWASP IoT Top 10 I9, I10; ETSI EN 303 645 sections 5.1 and 5.2; UK PSTI Act 2024; FDA "Cybersecurity in Medical Devices" guidance (2023)
Generate unique per-device credentials at manufacturing time:
# Manufacturing provisioning script — runs once per device
import secrets
import hashlib
def provision_device(device_serial: str, device_mac: str) -> dict:
# Generate cryptographically random password (not derived from serial/MAC)
admin_password = secrets.token_urlsafe(16)
# Generate unique device certificate
private_key = generate_ec_key()
cert = sign_device_certificate(
private_key=private_key,
subject=f"CN={device_serial},O=Company,C=US",
ca_key=MANUFACTURING_CA_KEY
)
# Store: password hash in device, plaintext in HSM-backed provisioning DB
device_config = {
"admin_password_hash": bcrypt.hash(admin_password),
"device_cert": cert,
"device_key": private_key,
"serial": device_serial,
}
# Label on physical device: serial + initial password (for setup only)
# Force password change on first login
return device_config
# NEVER: password = hashlib.md5(device_serial.encode()).hexdigest()
# NEVER: same default password across entire product line
Disable JTAG and UART debug interfaces in production:
/* STM32 — disable JTAG/SWD via option bytes at end of production test */
void disable_debug_interfaces(void) {
FLASH_OBProgramInitTypeDef ob_init = {0};
HAL_FLASHEx_OBGetConfig(&ob_init);
/* Disable SWD interface — write-once OTP operation */
ob_init.OptionType = OPTIONBYTE_USER;
ob_init.USERType = OB_USER_TZEN;
ob_init.USERConfig = OB_TZEN_ENABLE; /* Enable TrustZone */
/* Set RDP (Read-Out Protection) Level 2 — no debug, no readback */
ob_init.OptionType |= OPTIONBYTE_RDP;
ob_init.RDPLevel = OB_RDP_LEVEL_2; /* PERMANENT — cannot be reversed */
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&ob_init);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
}
# For embedded Linux: disable UART console in production
# Remove console= from kernel command line in bootloader config
# /etc/inittab or systemd getty: comment out ttyS0 entries
# Disable JTAG in Device Tree
# &jtag { status = "disabled"; };
Encrypt sensitive data at rest using hardware security:
/* Use hardware AES engine + device-unique key (UID) for storage encryption */
#include "aes.h"
/* Derive storage key from device UID — unique per device, never leaves hardware */
void derive_storage_key(uint8_t key_out[32]) {
/* STM32 unique device ID: 96-bit value burned at manufacturing */
uint32_t uid[3] = {
HAL_GetUIDw0(), HAL_GetUIDw1(), HAL_GetUIDw2()
};
/* HKDF with device UID as IKM and fixed info */
hkdf_sha256(key_out, 32,
(uint8_t *)uid, sizeof(uid),
(uint8_t *)"storage-key-v1", 15,
NULL, 0);
}
void encrypt_credentials(const char *plaintext, uint8_t *ciphertext_out) {
uint8_t key[32];
derive_storage_key(key);
uint8_t nonce[12];
crypto_get_random(nonce, sizeof(nonce));
aes_gcm_encrypt(key, nonce, (uint8_t *)plaintext, strlen(plaintext),
ciphertext_out);
memset(key, 0, sizeof(key)); /* clear key from stack */
}
Implement tamper detection and response:
/* Hardware tamper pin connected to case-open switch */
void tamper_detection_init(void) {
/* Configure tamper pin as interrupt */
GPIO_InitTypeDef gpio = {
.Pin = TAMPER_PIN,
.Mode = GPIO_MODE_IT_RISING, /* rising = case opened */
.Pull = GPIO_PULLDOWN,
};
HAL_GPIO_Init(TAMPER_PORT, &gpio);
HAL_NVIC_EnableIRQ(TAMPER_IRQn);
}
void TAMPER_IRQHandler(void) {
if (__HAL_GPIO_EXTI_GET_FLAG(TAMPER_PIN)) {
/* Log tamper event to persistent counter */
increment_tamper_counter();
log_security_event("physical_tamper_detected");
/* Policy: clear sensitive keys on tamper */
secure_erase_credentials();
/* Require re-provisioning — device will not boot normally */
set_boot_flag(BOOT_FLAG_TAMPERED);
HAL_NVIC_SystemReset();
}
}
Force credential change on first login:
# Embedded web server — enforce password change workflow
def handle_login(username: str, password: str, session) -> Response:
if not verify_credentials(username, password):
return Response(401, "Invalid credentials")
user = get_user(username)
# Force password change if using factory default
if user.must_change_password:
session["pending_password_change"] = True
return redirect("/change-password")
session["authenticated"] = True
return redirect("/dashboard")
def handle_change_password(new_password: str, session) -> Response:
if len(new_password) < 12:
return Response(400, "Password must be at least 12 characters")
if not has_complexity(new_password):
return Response(400, "Password must include letters, numbers, and symbols")
update_password(session["user"], bcrypt.hash(new_password))
session["pending_password_change"] = False
return Response(200, "Password updated")
CONFIG_CONSOLE_UART=y in Kconfig; audit release build configs before tapeout/production flash.npx claudepluginhub jeffreytse/grimoire --plugin grimoireDisables unnecessary services, enforces TLS, segments devices onto isolated VLANs, and secures management APIs on IoT devices.
Provides IoT security practices for resource-constrained devices: X.509/secure element identity, secure boot chains, TLS/DTLS comms, firmware signing, network segmentation, device attestation, and vuln management.
IoT and embedded device security testing methodology covering hardware recon, firmware acquisition/analysis, bootloader attacks, runtime exploits, wireless protocol attacks, and cloud-IoT API abuse.