gpio-config — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited gpio-config (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
| Keywords/Patterns | Platform |
|---|---|
| Raspberry Pi, RPi, BCM2835, BCM2711, BCM2712, config.txt, dtoverlay, gpiozero, RPi.GPIO, pigpio, /boot/firmware/ | RPi |
| ESP32, ESP-IDF, WROOM, WROVER, menuconfig, sdkconfig, Arduino (with ESP context), esp_err_t, gpio_config(), ESP32-S2, S3, C3, C6 | ESP32 |
RPi Variants:
| Identifier | Models Covered | Key Differences |
|---|---|---|
| rpi3 | Pi 3B/3B+ | UART/BT conflict on mini-UART, 2× PWM channels, config at /boot/config.txt |
| rpi4 | Pi 4B | 4× PWM channels, dual SPI, config at /boot/firmware/config.txt |
| rpi5 | Pi 5 | RP1 southbridge (different peripheral base), 4× PWM, config at /boot/firmware/config.txt |
| rpi_zero2w | Zero 2W | Same SoC as Pi 3, UART/BT conflict, limited current budget |
ESP32 Variants:
| Identifier | Key Differences |
|---|---|
| esp32 | 34 GPIO, dual-core, ADC2 conflicts with WiFi, Hall sensor on GPIO36/39 |
| esp32s2 | 43 GPIO, single-core, no Bluetooth, native USB, no ADC2/WiFi conflict |
| esp32s3 | 45 GPIO, dual-core, BLE5, native USB, no ADC2/WiFi conflict |
| esp32c3 | 22 GPIO, single-core RISC-V, BLE5 only, no ADC2/WiFi conflict |
| esp32c6 | 30 GPIO, RISC-V, WiFi 6, 802.15.4, no ADC2/WiFi conflict |
ESP32 Modules:
| Module | Additional Reserved Pins |
|---|---|
| WROOM | GPIO6–11 (flash SPI) |
| WROVER | GPIO6–11 (flash SPI) + GPIO16–17 (PSRAM) |
ALWAYS load the platform pin database. Load other files only when their trigger condition is met.
| File | Trigger |
|---|---|
| references/platforms/rpi-pins.md | Platform is RPi (any variant) |
| references/platforms/esp32-pins.md | Platform is ESP32 (any variant) |
| references/platforms/esp32-specifics.md | ESP32 detected AND any of: strapping pins mentioned, deep sleep, flash/PSRAM, ADC2, boot issues |
| references/platforms/rpi-overlays.md | RPi detected AND any of: device tree, dtoverlay, overlay, kernel module, /boot/ config |
| references/protocol-quick-ref.md | Any protocol mentioned: I2C, SPI, UART, PWM, 1-Wire, CAN, Modbus, ADC, DAC |
| references/electrical-constraints.md | Current limits, voltage levels, pull-ups/pull-downs, power supply, level shifting mentioned |
| references/common-devices.md | Specific sensor, module, display, or breakout board mentioned by name or part number |
Extract from the user's request:
Apply Section 1 rules. Result: confirmed platform + variant + module type, OR ask the user.
Apply Section 2 table. Load all triggered reference files before proceeding.
If any critical information is missing (platform, protocol count, or device identity), ask ONE concise follow-up question covering all gaps. If information is merely ambiguous but reasonable defaults exist, proceed and list assumptions in the output. Never ask more than one follow-up message.
Assign pins using platform-specific strategy:
RPi strategy:
ESP32 strategy:
Invoke the validation script:
python scripts/validate_pinmap.py --format json <<'EOF'
<input JSON>
EOFIf validation returns errors: reassign conflicting pins and re-validate. Loop a maximum of 3 times. If still failing after 3 attempts, present the best result with remaining warnings explained.
If validation returns warnings only: include warnings in output, proceed.
Generate the response using Section 4 format. Then invoke the generation script:
python scripts/generate_config.py --format json --framework <framework> <<'EOF'
<input JSON>
EOFInclude all script outputs in the structured response.
Location: scripts/validate_pinmap.py
CLI usage:
python scripts/validate_pinmap.py --format json [--verbose] < input.json
python scripts/validate_pinmap.py --helpInput JSON schema:
{
"platform": "rpi | esp32",
"variant": "rpi3 | rpi4 | rpi5 | rpi_zero2w | esp32 | esp32s2 | esp32s3 | esp32c3 | esp32c6",
"module": "WROOM | WROVER | null",
"wifi_enabled": true,
"pins": [
{
"gpio": 17,
"function": "I2C_SDA | I2C_SCL | SPI_MOSI | SPI_MISO | SPI_SCLK | SPI_CS | UART_TX | UART_RX | PWM | DIGITAL_IN | DIGITAL_OUT | ANALOG_IN | 1WIRE | CAN_TX | CAN_RX",
"protocol_bus": 0,
"device": "BME280",
"pull": "up | down | none",
"notes": ""
}
]
}Output JSON schema:
{
"valid": true,
"errors": [
{
"gpio": 6,
"code": "RESERVED_PIN | CONFLICT | INPUT_ONLY_AS_OUTPUT | STRAPPING_UNACKNOWLEDGED | ADC2_WIFI | CURRENT_EXCEEDED",
"message": "Human-readable error",
"severity": "error"
}
],
"warnings": [
{
"gpio": 12,
"code": "STRAPPING_PIN | UART_BT_CONFLICT | PWM_AUDIO_CONFLICT | DEPRECATED_LIB | CURRENT_WARNING | BOOT_STATE",
"message": "Human-readable warning",
"severity": "warning"
}
],
"summary": {
"total_pins": 8,
"errors": 0,
"warnings": 2,
"current_draw_ma": 32
}
}Exit codes: 0 = valid (warnings ok), 1 = errors found, 2 = invalid input
Location: scripts/generate_config.py
CLI usage:
python scripts/generate_config.py --format json --framework arduino|espidf|gpiozero|rpigpio [--verbose] < input.json
python scripts/generate_config.py --helpInput JSON schema: same as validate_pinmap.py input, plus:
{
"platform": "rpi | esp32",
"variant": "rpi3 | rpi4 | rpi5 | rpi_zero2w | esp32 | esp32s2 | esp32s3 | esp32c3 | esp32c6",
"module": "WROOM | WROVER | null",
"wifi_enabled": true,
"pins": [],
"framework": "arduino | espidf | gpiozero | rpigpio"
}Output JSON schema:
{
"config_lines": ["dtoverlay=i2c1", "dtparam=spi=on"],
"init_code": "# Full initialization code as a single string with newlines",
"wiring_notes": [
"Connect BME280 SDA to GPIO2 (physical pin 3). Add 4.7kΩ pull-up to 3.3V."
],
"warnings": ["GPIO12 is a strapping pin — see boot state notes"],
"alternatives": [
{
"original_gpio": 12,
"alternative_gpio": 17,
"reason": "Avoids MTDI strapping pin risk"
}
]
}Exit codes: 0 = success, 1 = generation error, 2 = invalid input
User request: "Set up a weather station with BME280 temperature sensor and SSD1306 OLED display on Raspberry Pi 4"
Generated pinmap (excerpt):
{
"platform": "rpi",
"variant": "rpi4",
"pins": [
{"gpio": 2, "function": "I2C_SDA", "protocol_bus": 1, "device": "BME280+SSD1306", "pull": "external_up"},
{"gpio": 3, "function": "I2C_SCL", "protocol_bus": 1, "device": "BME280+SSD1306", "pull": "external_up"},
{"gpio": 17, "function": "DIGITAL_OUT", "device": "Status LED", "pull": "none"}
]
}Generated initialization code (gpiozero, excerpt):
from gpiozero import LED
import smbus2
bus = smbus2.SMBus(1) # I2C bus 1: SDA=GPIO2, SCL=GPIO3
# BME280 at 0x76, SSD1306 at 0x3C — shared I2C bus, no conflict
status_led = LED(17)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.