Follow-up: Part C & D — Hardware-Safety (in response to PR #686)
Global environment (same as original submission)
| Field | Value |
|---|---|
| OS | Windows 11, PowerShell 5.1 |
| IDE | VS Code + Antigravity IDE, TuyaOpen IDE Preview v0.1.5 |
| SDK | TuyaOpen SDK main branch (IDE-managed) |
| Hardware | Tuya T5AI-Core (BK7258) |
| Labels | preview-tester bug (+ ux/feature-request where noted) |
PART C — ULTRA / WOW — Inconsistent validation & prompt-injection → RCE
These are not new topics — they are the same class you just fixed, but missed in the next file over. That’s why they are high-value: the fix pattern exists (and is correct) at one call site, but was not applied to the identical call site next door.
C1 —
Path traversal in tos.py new — arbitrary directory creation outside the SDK
Labels: preview-tester bug Severity: High/Critical
The inconsistency: cli_config.py:139-155 (_normalize_save_name) correctly blocks traversal for tos.py config save:
if "/" in name or "\\" in name or os.sep in name: return ""
if ".." in name: return ""
The same os.path.join(root, input) in cli_new.py has zero check:
| CLI | Line |
|---|---|
tos.py new project |
cli_new.py:475-477 → os.path.join(work_root, new_project_name) |
tos.py new board |
cli_new.py:539-543 → os.path.join(boards/... , new_board_name) |
tos.py new platform |
cli_new.py:424-426 → os.path.join(platforms_root, ...) |
Repro:
$ tos.py new project
Input new project name.
input: ../../evil
# → directory `C:/<project>/../../evil` == `C:/evil` created via copy_directory at cli_new.py:486
Fix: Reuse _normalize_save_name for all three new_* prompts.
C2 — Kconfig injection via board/platform name
Labels: preview-tester bug Severity: Medium-High
cli_new.py:491-497 and 506-516:
add_board_context = f'''
config BOARD_CHOICE_{new_board_name_}
bool "{new_board_name}" # ← raw, no escaping
rsource "./{new_board_name}/Kconfig"
Only '-' → '_' is sanitized. A name like a"\nconfig PWNED\n\tbool "x closes the string and injects a new config. Same via replace_string_in_file at 561-562.
Fix: Validate ^[A-Za-z0-9_-]+$; escape " as \" if free-form is needed.
C3 — T5AI toolchain install always broken on macOS (GNU-only commands in Darwin branch)
Labels: preview-tester bug Severity: High (macOS — supported host)
platform/T5AI/toolchain_get.sh:138-145 (shared by all OS, including Darwin*)):
sha256sum "$TOOLCHAIN_FILE" | awk '{print $1}'
stat -c %s "$TOOLCHAIN_FILE"
sha256sum / stat -c are GNU coreutils. macOS ships shasum -a 256 / stat -f %z. On Mac both fail → verify_toolchain_package returns 1 → retry exhausts → every T5AI build fails even on perfect network.
Fix: command -v sha256sum >/dev/null && sha256sum || shasum -a 256 and stat -c / stat -f fallback.
C4 —
Prompt-injection → shell RCE chain via “Create with AI”
Labels: preview-tester bug Severity: Critical
Tuya’s headline feature “Create with AI” → natural language → LLM outputs CONFIG_PROJECT_NAME → written to app_default.config → cli_build.py:218-227 runs:
cmake -G Ninja ... -DTOS_PROJECT_NAME={project_name} # shell=True, unquoted
Prompt: "Create a project named a & calc &" → LLM → CONFIG_PROJECT_NAME="a & calc &" → cmd.exe /c ... -DTOS_PROJECT_NAME=a & calc & → calc.exe
Two injection points (cmake_configure + build_setup at 168-170). This is a prompt-injection → file write → shell RCE chain reachable through the primary AI workflow.
Fix: B1’s fix (shell=False list form) kills the chain; also validate CONFIG_PROJECT_NAME against ^[A-Za-z0-9_.-]+$.
C5 — rm_rf on Windows mishandles trailing \
Labels: preview-tester bug Severity: Low
util_files.py:17-21: rmdir /S /Q "C:\path\" — trailing \ escapes the closing ", cmd.exe sees unterminated string. Fix: rstrip("\\/") or use shutil.rmtree.
PART D — HARDWARE SAFETY / PLATFORM SPEC — Breaking-change, not injection
Cross-checked
platform.jsonvsboard.jsonpinouts — the exact check the vibe-coding skill must do before picking pins.
D1 —
expansionPins exposes flash UART + QSPI flash pins as free — brick risk
Labels: preview-tester bug Severity: High
platform.json→uart.ports[0]role=downloadtx 11 / rx 10(RXD 10 "Firmware download RX",TXD 11 "Firmware download TX")platform.json→qspi.ports[0](QSPI0 flash)clk 22 / cs 23 / d0 24 / d1 25 / d2 26 / d3 27— chip boots from this busboard.json→expansionPins(33 entries) includes10,11and22,23,24,25,26— 7 critical system pins marked freely wireable:
expansionPins: [2,3,4,5,6,7,8, 10,11, 12... 22,23,24,25,26 ...]
Log UART 0/1 is correctly excluded — same logic was applied for log but missed for flash/QSPI. Vibe coding can legally pick 10/11 for a sensor → next tos.py flash drives same pins from USB-serial + peripheral → flash hang / QSPI corruption → brick on sealed Core board.
Fix: Remove 10,11,22,23,24,25,26 from expansionPins; generate expansionPins by subtracting all system-reserved pins (flash UART + QSPI + log UART + onboard devices) from SoC pinout.
D2 — platform.json for T5AI omits flashAndDebug — the safety check that would have caught D1 is skipped
Labels: preview-tester bug Severity: Medium
AGENTS.md documents platform.json must have flashAndDebug.flash.pins / flashAndDebug.debug. The actual T5AI platform.json (1878 lines) has no top-level flashAndDebug key. The skill rule “also check flashAndDebug.flash.pins” therefore never runs for this platform.
Fix: Add flashAndDebug to T5AI spec and derive expansionPins from it automatically.
D3 — IRQ capability mismatch: GPIOs 10/11 are free but not interrupt-capable
Labels: preview-tester bug Severity: Medium
platform.json→gpio.spec.irq.pins: [[0,9], [12,55]]— IRQ not supported on 10-11 (the gap).board.json→expansionPinsincludes10,11withfunctions: ["GPIO", ...]
Assigning an interrupt-driven peripheral (button IRQ_RISE, onchip:gpio with irq) to 10/11 will register but never fire — silent runtime failure with no build warning.
Fix: Filter expansionPins against irq.pins when IRQ is requested, or warn at generation time.
Thank you again for the fast [PR #686] turnaround — happy to verify these locally on T5AI-Core or open separate issues for D1/D3 as you prefer. These should make the vibe-coding pin-selection path as robust as the CLI hardening you just landed.

