diff --git a/build/dependencies.lock b/build/dependencies.lock index c1080f726..358e89c6e 100644 --- a/build/dependencies.lock +++ b/build/dependencies.lock @@ -207,10 +207,10 @@ legacy_channels = ["development"] [legacy_inventory] manifest = "build/legacy-manifest.json" schema = 1 -component_count = 326 -file_count = 1967 -byte_count = 260663152 -source_build_components = 232 +component_count = 327 +file_count = 1968 +byte_count = 260664576 +source_build_components = 233 unresolved_components = 94 inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b" diff --git a/build/display-profile-policy.json b/build/display-profile-policy.json index f795e2b5f..0ed4739a4 100644 --- a/build/display-profile-policy.json +++ b/build/display-profile-policy.json @@ -25,7 +25,7 @@ "platform_helper": { "path": "static/build/.tmp_update/bin/bloom-platform", "hash_mode": "text-lf", - "sha256": "ccc3d1ad7efd29c8864a2b274c6af1ab6aff5efe9c5098dbe1b542ceaa11258d" + "sha256": "e840764164c3eca35131a5cca99872341ad433c5fb096b6788f54821a226402e" }, "built_in_profiles": { "path": "build/display-built-in-profiles.json", diff --git a/build/display-visual-pack-providers.json b/build/display-visual-pack-providers.json index cf401620f..642be8ca6 100644 --- a/build/display-visual-pack-providers.json +++ b/build/display-visual-pack-providers.json @@ -21,7 +21,7 @@ "authorities": { "display_profile_policy": { "path": "build/display-profile-policy.json", - "sha256": "3e575603d133478d706783b5f3b3df18561403aaaac346afa0cf3f733d599f0a" + "sha256": "6b171937cbc705dd742bb1ce23efaaea33b4d1cca767b8832d395a0e18e88a8f" }, "ui_asset_manifest": { "path": "assets/ui/manifest.json", diff --git a/build/legacy-manifest.json b/build/legacy-manifest.json index 600805f90..7718dd397 100644 --- a/build/legacy-manifest.json +++ b/build/legacy-manifest.json @@ -462,8 +462,8 @@ "kind": "runtime", "path": "static/build/.tmp_update/bin/bloom-dev-deploy", "file_count": 1, - "byte_count": 33126, - "tree_sha256": "225e52c83020c0ebd88732688e8731125cc56fd912b77a010eb9696f2ade7ec9", + "byte_count": 33204, + "tree_sha256": "0e3a8f926886a6f759e2c72bac34682ba450e40de3cd136f27fe857aa0c7ffc0", "source": "https://github.com/boburning/BloomOS", "source_revision": "release-commit", "license": "GPL-3.0-only", @@ -475,8 +475,8 @@ "kind": "runtime", "path": "static/build/.tmp_update/bin/bloom-dev-ssh", "file_count": 1, - "byte_count": 15073, - "tree_sha256": "70773fe6f7eaad77eda356ba3ee3b0a37fcd4d8a4d42c9c2c1af2cab8f15bc66", + "byte_count": 15525, + "tree_sha256": "a5ddf4075869bc207eb604fea194b6b6f149332aec1f4710406dc11f78437645", "source": "https://github.com/boburning/BloomOS", "source_revision": "release-commit", "license": "GPL-3.0-only", @@ -670,8 +670,8 @@ "kind": "runtime", "path": "static/build/.tmp_update/bin/bloom-platform", "file_count": 1, - "byte_count": 17120, - "tree_sha256": "f585d4ec2f23f2d91235a9b82f57fa92404b605246daf792093f205f7353db24", + "byte_count": 17216, + "tree_sha256": "b3fb607da7c08fb1ad0b4a52f0388e22bc2c16adbaed99b1bf93ed0640a452f2", "source": "https://github.com/boburning/BloomOS", "source_revision": "release-commit", "license": "GPL-3.0-only", @@ -834,6 +834,19 @@ "build_recipe": "Makefile", "resolution": "source-build" }, + { + "id": "runtime-tmp-update-bin-bloom-ssh-gate", + "kind": "runtime", + "path": "static/build/.tmp_update/bin/bloom-ssh-gate", + "file_count": 1, + "byte_count": 798, + "tree_sha256": "3c8d1cc7509ec43526ff796d01804b61b87500e6195a785d2384cf0c6f58d831", + "source": "https://github.com/boburning/BloomOS", + "source_revision": "release-commit", + "license": "GPL-3.0-only", + "build_recipe": "Makefile", + "resolution": "source-build" + }, { "id": "runtime-tmp-update-bin-bloom-test-runner", "kind": "runtime", @@ -1893,7 +1906,7 @@ "path": "static/build/.tmp_update/res/bloom", "file_count": 7, "byte_count": 72678, - "tree_sha256": "d480b8649c62bd40b1e8eb8f2ebc812cf5179b9fc3839b67b442f11fa17ceb01", + "tree_sha256": "21791c8c83e9b100d67d8d287bcd6fd66d7953645d92147ba961860f8c3d6374", "source": "https://github.com/boburning/BloomOS", "source_revision": "release-commit", "license": "GPL-3.0-or-later", diff --git a/docs/1.0_TEST_MATRIX.md b/docs/1.0_TEST_MATRIX.md index 5e381c684..7ebda025e 100644 --- a/docs/1.0_TEST_MATRIX.md +++ b/docs/1.0_TEST_MATRIX.md @@ -53,7 +53,7 @@ shoulder button. | Critical lifecycle | Exact 640x480 and 752x560 built-in projections; source/target/path and legacy-file rejection; boot/shutdown/save/low-battery/recovery/Safe Mode ignore theme/config/media; charging reflects bounded actual level; installer uses one deterministic surface; missing optional text/font fallback; signed Full-update boot/charging/shutdown/recovery checkpoint | | Settings | One sectioned surface; inline values; capability filtering; one-layer details; hidden Developer section; optional overlapping L1/R1 list paging | | Quick Settings | START from core surfaces; immediate brightness/volume/mute; capability-gated Wi-Fi opens the dedicated detail surface; Wi-Fi left/right never mutates the radio; readable battery; B/START close | -| Network services | Desired/runtime separation; carrier without IPv4 remains acquiring/not-ready; canonical DHCP restart and bounded post-address listener reconciliation; SSH shell and SFTP independent key-only states; canonical transfer roots and modes; Saves read-only; private roots denied; traversal/symlink/path bounds; same-filesystem atomic publication; authorized-key import; quiesce/health; stale FTP/Telnet/FileBrowser/inherited-Samba/WPS/hotspot/VNC flags cannot activate listeners; process-aware listener audit including inherited TCP/5900 and vncserver; inherited FTP/Telnet/FileBrowser HTTP, rejected Samba, and unresolved VNC launchers, configurations, libraries, and Tweaks exposure absent from newly assembled stable artifacts; WPS progress assets, WPS controls, inherited hotspot controls, and dormant hotspot mutation functions absent while fail-closed entrypoints and older-card process cleanup remain | +| Network services | Desired/runtime separation; carrier without IPv4 remains acquiring/not-ready; canonical DHCP restart and bounded post-address listener reconciliation; SSH shell and SFTP independent key-only states; every authenticated developer shell/exec request enters a fixed non-login gate with inherited profile startup disabled; exact SFTP dispatch; missing-gate fail-closed behavior; canonical transfer roots and modes; Saves read-only; private roots denied; traversal/symlink/path bounds; same-filesystem atomic publication; authorized-key import; quiesce/health; stale FTP/Telnet/FileBrowser/inherited-Samba/WPS/hotspot/VNC flags cannot activate listeners, including after an interactive SSH login; process-aware listener audit including inherited TCP/5900 and vncserver; inherited FTP/Telnet/FileBrowser HTTP, rejected Samba, and unresolved VNC launchers, configurations, libraries, and Tweaks exposure absent from newly assembled stable artifacts; WPS progress assets, WPS controls, inherited hotspot controls, and dormant hotspot mutation functions absent while fail-closed entrypoints and older-card process cleanup remain | | Wi-Fi profile store | JFFS2 private root; stdin-only bounded replacement and `bloomctl network profile upsert ID`; requested/document ID binding; explicit `network forget ID`; inactive-ID deletion with active-profile refusal; active forget only after a separately confirmed fallback connection; schema/unknown-field/count/size enforcement; WPA2-PSK, explicit open, and manual hidden WPA2; WEP and hidden open denial; unique IDs/SSIDs; redacted status; explicit local credential-free UI list with one active marker; sanitized `network connect status` with internal profile-handle validation and public omission; mode-0600 atomic single-profile supplicant projection; symlink, occupied-slot, live-lock, invalid-profile, and prior-byte preservation | | Wi-Fi scan API | Asynchronous start; redacted status; explicit ID-scoped local results; cancel and terminal discard; one active worker; five-second rate limit; 15-second runtime; 64-KiB raw ceiling; 32 unique printable SSIDs; eight-operation history; open/WPA2-PSK/unsupported classification; duplicate signal reconciliation; backend failure, timeout, oversize, unsafe-path, stale/live-lock, and unsupported-hardware handling | | Wi-Fi UI | Original Mini hidden; Settings, Shell Quick Settings, and GameSwitcher Quick Settings converge on `--settings-wifi`; direct radio toggle removed; bounded fixed-argv profile-list load outside render; connection state and saved-network detail; selectable Scan/Cancel row and read-only discovered networks; active-only 100-ms SDL wake; deferred GameSwitcher until cancellation discard; single-flight nonblocking fixed-argv scan client; 16-KiB output and five-second deadline; child process-group cleanup; controller-owned start/status/results/discard and cancel-during-start sequencing; 250-ms status opportunities; exact profile/scan/ack schemas; 16 profiles and 32 networks; unique bounded IDs/SSIDs; one active profile; fixed scan phases/errors; terminal history discarded; open/WPA2 profile form with shared keyboard, masked secret, fixed profile-upsert argv, escaped stdin JSON, strict ID/SSID/PSK validation, explicit secret clearing, nonblocking submit/poll, 4-KiB acknowledgement cap, exact upsert response, timeout process-group cleanup, exact saved-ID reuse, 104-bit no-follow random new handles, collision/security/full-store rejection, one editing/submitting controller with 100-ms poll opportunities, exact cancellation, terminal clearing, bounded focus return, supported scan-result selection, explicit saved-not-connected result, and no implicit association; explicit inactive-profile X/confirm forget flow; active-row refusal in Shell and store; fixed nonblocking delete; exact acknowledgement; terminal projection reload; graceful TERM cleanup before bounded KILL; exact guarded-connect status model with fixed phases/operations/failures, lease consistency, 64-byte transaction-ID and 300-second deadline ceilings, and discarded transaction/overlay identity; fixed no-follow `network connect status` loader with less-than-4-KiB output, five-second read ceiling, and stale-state clearing; separately typed single-flight nonblocking status request with process-group timeout and generic-poller refusal; controller-owned single request, 100-ms opportunities, duplicate-begin refusal, typed terminal state, teardown reaping, explicit guarded-state presentation, one-second freshness, fail-closed mutation gating for stale/unavailable/lease-armed/rollback-failed results, read-only refresh without queued intent, and exit teardown; credential and unknown-field rejection; passwords absent from arguments, logs, screenshots, durable Shell state, and evidence | @@ -242,6 +242,16 @@ identity/configuration, and a measured ~16 MiB idle memory increase that left only 8,000 KiB available after buffers. Stable UI therefore hides Samba unless a future Bloom source build passes every required gate. SFTP remains supported. +The signed interactive-SSH lockdown checkpoint is recorded in +[`validation/network/plus-interactive-ssh-lockdown-20260831.json`](validation/network/plus-interactive-ssh-lockdown-20260831.json). +On one Mini Plus, a fresh no-command key-only SSH request entered the fixed +non-login gate with `ENV=/dev/null`, non-interactive execution remained +functional, password-only authentication and disabled SFTP were refused, and +an immediate independent audit found no Telnet process or listener. The exact +candidate was promoted to known-good with aggregate health green. This does not +certify another device or enabled SFTP, and inactive retired configuration +remnants found during the Full checkpoint remain under issue #629. + ## Exit criteria No stable tag is permitted while an automated gate fails, a P0/P1 remains open, diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5987aead2..f655d4ecf 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1008,7 +1008,13 @@ of the remaining inherited auxiliary services remain follow-up work. `bloom-network-services` separately owns desired and runtime state for SSH, SFTP, and the capability-gated Samba candidate. SSH shell permission and SFTP -permission are independent. In SFTP-only mode every authorized key receives a +permission are independent. When SSH shell permission is enabled, Dropbear +forces every authenticated shell, exec, and subsystem request through the fixed +`bloom-ssh-gate`. Empty requests start a non-login `/bin/sh` with +`ENV=/dev/null`, preventing inherited `/etc/profile` from reactivating retired +listeners; non-empty requests preserve the authenticated original command, and +the exact built-in SFTP request enters the confined SFTP gate. A missing gate +fails closed before the listener starts. In SFTP-only mode every authorized key receives a Dropbear forced command with forwarding, agent, X11, and PTY access disabled; shell and arbitrary exec requests therefore enter only the SFTP protocol gate. The pinned OpenSSH SFTP subsystem is statically built with a fixed root identity @@ -1078,7 +1084,8 @@ Monotonic deadlines make wall-clock repair irrelevant to recovery. SSH/SFTP helper changes use the fixed `ssh-shadow` transaction operation. The existing key-only port 22 process remains active while a separately tracked, -transaction-authorized key-only process listens on port 2222. The host proves a +transaction-authorized key-only process listens on port 2222 through the same +non-login shell gate. The host proves a new non-multiplexed connection through that listener; confirmation then closes it through port 22. Rollback and watchdog paths close it before restoring prior service preferences. Outside that bounded transaction the listener audit marks diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index a25d9592e..870256d4d 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -90,6 +90,13 @@ developer installations retain automatic SSH until it is disabled in Network settings. Removing `.bloom-dev` also disables that compatibility behavior unless SSH has already been explicitly enabled in Network settings. +Developer SSH never starts a firmware login shell. The fixed +`bloom-ssh-gate` launches interactive `/bin/sh` without login semantics and +sets `ENV=/dev/null`, while authenticated non-interactive commands retain their +normal command behavior. This prevents inherited `/etc/profile` actions from +reactivating retired listeners. SFTP-only mode remains independently restricted +by the authorized-key forced command and confined SFTP gate. + ## Wi-Fi device harness Copy `tools/targets.example.toml` to the ignored `tools/targets.toml` and replace @@ -235,7 +242,7 @@ temporary executable over the same key-only channel, verify its byte count and SHA-256 on both ends, and remove it after rollback. The complete settings, platform, Wi-Fi, listener/service, transaction, remote -key, SFTP, transfer-policy, and boot-recovery runtime set forms one guarded 17-file +key, SSH shell gate, SFTP, transfer-policy, and boot-recovery runtime set forms one guarded 21-file `network-helpers` deployment group. Host and device manifest validation reject every partial group so incompatible schema generations cannot be mixed on a live device. Activate that bundle only through the rollback-timed network diff --git a/docs/DEVICE_SUPPORT.md b/docs/DEVICE_SUPPORT.md index 67701adca..669cc0a69 100644 --- a/docs/DEVICE_SUPPORT.md +++ b/docs/DEVICE_SUPPORT.md @@ -64,6 +64,7 @@ Exact device revisions, firmware versions, build commits, test dates, contributo | 2026-08-26 | Miyoo Mini Plus (model ID 354) | exact NET-01 Warm candidate `4beb681a`; installed known-good `v0.1.0-dev-b073b423` retained | Network service schema 2, canonical non-secret settings migration, idempotent SSH request, independent confirmation, explicit component rollback, listener/key preservation, and aggregate health | The complete PLAT-01/NET-01/REC-04 Warm bundle reported Wi-Fi available/associated, SSH on/running, SFTP off/stopped, and Samba off/stopped. Legacy desired state migrated once to canonical generation 113; repeated status and idempotent SSH enable preserved settings SHA-256, the sole key hash, and tracked listener PID. No-auth SSH was refused as publickey-only. Independent confirmation passed. A device-found redundant rollback replay was corrected, and the final explicit rollback restored the installed files without restarting PID 1197; exact settings, key, harness, service matrix, clean listener audit, known-good update, and green health were retained. Machine-readable evidence: [`validation/network/plus-net01-network-service-v2-20260826.json`](validation/network/plus-net01-network-service-v2-20260826.json). | Remote exact-hash SSH/process/listener/settings/transaction/framebuffer evidence is not physical controls, LCD, audio, gameplay, save, or state certification. The automatic screenshot remains local because it contains private library titles. Mini V1-V4 and Flip remain untested. | | 2026-08-29 | Miyoo Mini Plus (model ID 354) | signed and confirmed `v0.1.0-dev-e69a36de`; workflow run 33231170143 | UI-12 built-in boot, charging, update, recovery, Safe Mode, saving, and shutdown resource adoption | The exact 199,809,395-byte signed archive matched SHA-256 `874188cf...c6386` locally and on-device, created verified snapshot `20260829T034848Z-pre-update-1466`, installed in one update cycle, and was confirmed known-good after two successful boots. The maintainer physically observed the new boot and charging artwork; the controlled poweroff completed. Exact installed binary and lifecycle-resource hashes matched the payload, remote boot/update/recovery/Safe Mode frames had three equal framebuffer pages, and Bloom Shell returned byte-for-byte after the display probes. Wi-Fi association, the sole key, WPA projection, network-helper hashes, listener policy, idle transaction state, five verified snapshots, and aggregate health remained intact. Machine-readable evidence: [`validation/ui/plus-ui12-lifecycle-surfaces-20260829.json`](validation/ui/plus-ui12-lifecycle-surfaces-20260829.json). | The maintainer was not present for the brief Saving/Shutdown artwork, so its physical appearance remains pending even though the actual poweroff completed. Remote frames do not certify tactile controls, physical LCD quality, audio, gameplay, or save semantics. Mini V1-V4 and Flip/my285 remain pending. | | 2026-08-31 | Miyoo Mini Plus (model ID 354) | signed and confirmed `v0.1.0-dev-c1fc6624`; workflow run 33455349733 | UI-12 physical Saving and Shutdown lifecycle correction | The exact 185,669,001-byte signed archive matched SHA-256 `d06dbe0f...01f11`, installed over a verified pre-update snapshot, and retained exact installed `bootScreen` and runtime hashes across two boots. The maintainer physically observed both the Saving and Shutdown screens, the device powered off, and it returned on the same exact version. Post-confirmation aggregate health, five verified save snapshots, the idle network transaction, inactive recovery lease, clean key-only SSH listener policy, and inactive development overlay were all green. Machine-readable evidence: [`validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json`](validation/ui/plus-ui12-shutdown-c1fc6624-20260831.json). | This is a maintainer observation without video capture and covers only Mini Plus. It does not certify Mini V1-V4, Flip/my285, audio, gameplay, save semantics, or long-duration soak behavior. | +| 2026-08-31 | Miyoo Mini Plus (model ID 354) | signed and confirmed `v0.1.0-dev-3a828a29`; workflow run 33464738492 | Interactive developer SSH profile lockdown and inherited Telnet prevention | The exact 185,667,740-byte signed archive matched SHA-256 `bef1ee7c...92427`, installed over verified snapshot `20260901T032842Z-pre-update-3039`, returned automatically in 362 seconds, and was confirmed known-good after one boot. A fresh no-command interactive key-only SSH request entered non-login `/bin/sh` with `ENV=/dev/null`; non-interactive execution remained functional, password-only authentication and disabled SFTP were refused, no Telnet process or listener appeared, and the independent audit reported only reviewed key-only SSH. The sole authorized key and pinned host key remained unchanged. Exact installed helper hashes matched the reviewed tree; final aggregate health, five verified snapshots, idle network transaction, inactive recovery lease, inactive development overlay, and transfer cleanup were green. Machine-readable evidence: [`validation/network/plus-interactive-ssh-lockdown-20260831.json`](validation/network/plus-interactive-ssh-lockdown-20260831.json). | Enabled SFTP was not changed or retested. Three inactive retired FileBrowser/FTP configuration remnants remain under issue #629, while their executables and stale enablement flags are absent; NET-10 therefore remains open. This is Mini Plus remote evidence, not physical controls/LCD, audio, gameplay, save semantics, or other-device certification. | | 2026-08-16 | Miyoo Mini Flip (model ID 285) | `v0.1.0-dev-bc086aa6`, `v0.1.0-dev-95de9362`, `v0.1.0-dev-fbf1a03e`, `v0.1.0-dev-3ed5ba7d` | Upgrade install, identity, display/input/battery/lid inventory, Wi-Fi capability, clean-entry boot, instrumented shutdown, Windows FAT check, Play Activity WAL compatibility, key-only SSH | Clean install and boot; native 752×560 panel; two input devices; hall sensor present with open-lid raw value 1; live AXP battery 64%; Wi-Fi capability true. A controlled shutdown cycle entered and returned clean. Play Activity converted a seeded rollback-journal database to WAL, preserved its rows, passed integrity and quick checks, and left no WAL/SHM sidecars after three UI opens. Bloom-built static Dropbear 2025.88 accepted repeated Ed25519 logins, refused password-only authentication, reported model 285 over the live harness, and retained about 74 MB available memory. | Earlier cycles entered or returned dirty, although CHKDSK found no filesystem damage. The first Activity Tracker open/exit briefly glitched the display, then two repeats were normal with no logged crash. Longer soak testing, reboot-path isolation, reboot-time SSH activation, and lid behavior remain required. | The V2 result is a maintainer validation baseline, not certification of V2 or evidence for V1, V3, or V4. The first Plus shutdown retest was invalid because the card entered Linux dirty after a failed Windows command-line eject. After repairing and using Windows Safely Remove Hardware, the card entered Linux without the not-properly-unmounted warning and returned to Windows clean after BloomOS shutdown. diff --git a/docs/SECURITY.md b/docs/SECURITY.md index d485ac187..9c7d31194 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -12,6 +12,13 @@ on the developer workstation. BloomOS bundles a dedicated Dropbear server with public-key authentication and modern SSH algorithms. `bloom-device` pins the configured identity, permits public-key authentication only, and refuses unknown or changed host keys. +Every developer SSH shell and exec request also passes through the fixed +`bloom-ssh-gate`. Interactive requests start `/bin/sh` without login-shell +semantics and with `ENV=/dev/null`, so inherited `/etc/profile` startup actions +cannot reactivate retired listeners. Non-interactive requests retain the +authenticated caller's original command. The exact built-in SFTP subsystem +request is dispatched to the confined `bloom-sftp-gate`; a missing shell gate +prevents SSH from starting. Inherited Onion listener flags are migration-only input. Bloom reconciliation never starts FTP, Telnet, whole-card FileBrowser HTTP, inherited Samba, WPS, or diff --git a/docs/program/BloomOS_Work_Program_v3.json b/docs/program/BloomOS_Work_Program_v3.json index 8bb55b32b..72878968d 100644 --- a/docs/program/BloomOS_Work_Program_v3.json +++ b/docs/program/BloomOS_Work_Program_v3.json @@ -1170,12 +1170,13 @@ 627, 630, 636, - 639 + 639, + 646 ], "risk_tier": 3, "deployment_tier": "full", "device_gate_required": true, - "last_audited_main": "f11077378821fc182846ba6abedce094e8836f60", + "last_audited_main": "35ca0698c93004d2c65b39cc404999b940fc5267", "evidence": [ "GitHub PR #621 removes the dormant whole-card FileBrowser HTTP executable, database, web theme, icon, first-run marker, boot initializer, and start/authentication code from newly assembled stable payloads while retaining fail-closed legacy entrypoints, listener detection, and process cleanup for rollback-compatible remnants on older cards; it removes 15 tracked files and 14910652 bytes", "the corrected stable inventory contains 333 components, 1975 files, and 261293981 bytes with 232 source-build and 101 unresolved components; 74 affected Shell tests, three RetroArch identity tests, CI ARM orchestration and risk-scope tests, Python compilation, POSIX syntax, manifest, provenance, transfer policy, dependency inventory, and diff checks passed", @@ -1199,7 +1200,12 @@ "GitHub PR #639 removes the provenance-unresolved 41692-byte vncserver and approximately 221340-byte libvncserver.so.1 payloads, the unauthenticated TCP/5900 Tweaks launcher and controls, and current enablement copy while retaining an explicit retirement route, migration-only vncServer cleanup, exact vncserver listener and process classification, and exact older-card process termination; the stable inventory falls to 326 components, 1967 files, and 260659274 bytes with 232 source-built and 94 unresolved components", "the pinned Miyoo toolchain compiled the affected Tweaks consumer as a stripped 32-bit ARM EABI5 binary with SHA-256 8885a1b5b656801126dfc142f01f9f4f413af585f3ad9ef50d13d9fb954a20cf; the final focused networking, manifest, provenance, payload, CI-scope, and compatibility set passed 47 of 47 tests, the post-correction isolation set passed 31 of 31, scoped cppcheck passed in 1.20 seconds, native and AddressSanitizer each passed 595 of 595 tests in 18.77 and 31.71 seconds, and the final exhaustive local Shell gate passed 818 of 818 tests across eight shards", "GitHub checks passed with formatting in 14 seconds, static analysis in 20 seconds, native tests in 1 minute 28 seconds, AddressSanitizer in 1 minute 46 seconds, Docusaurus in 2 minutes 11 seconds, hosted exhaustive Shell in 4 minutes 53 seconds, and full ARM packaging in 5 minutes 21 seconds; PR #639 merged by squash as f11077378821fc182846ba6abedce094e8836f60", - "no deployment, update, reboot, Wi-Fi, profile, listener, ROM, Save, input, overlay, or recovery-state mutation occurred for PR #639; older installed or rollback payloads may still retain inherited VNC bytes, NET-08 still owns the transactional hotspot replacement, and NET-10 remains in progress until a coherent signed Full Mini Plus installation and rollback checkpoint verifies the retired services and listener lockdown without recreating the prior Wi-Fi rollback regression" + "no deployment, update, reboot, Wi-Fi, profile, listener, ROM, Save, input, overlay, or recovery-state mutation occurred for PR #639; older installed or rollback payloads may still retain inherited VNC bytes, NET-08 still owns the transactional hotspot replacement, and NET-10 remains in progress until a coherent signed Full Mini Plus installation and rollback checkpoint verifies the retired services and listener lockdown without recreating the prior Wi-Fi rollback regression", + "issue #645 and GitHub PR #646 correct the remaining interactive developer-login path after direct observation showed that the firmware login shell sourced inherited /etc/profile and reactivated BusyBox Telnet; implementation commit 9c746652fbabbb057878d041dd4eaaae2db0be1c forces authenticated shell, exec, subsystem, and transaction-shadow requests through a fixed non-login gate with ENV=/dev/null, preserves exact confined SFTP dispatch and non-interactive commands, and fails closed when the gate is missing", + "PR #646 host validation passed every selected SSH, SFTP, network-service, listener-lockdown, deployment, platform, provenance, transfer-policy, dependency-lock, CI-scope, syntax, and inventory gate after one stale proactive Bloom-component count was corrected without rerunning unaffected tests; the inventory is 327 components, 1968 files, and 260663740 bytes with 233 source-built and 94 unresolved components", + "after UI-12 completed its physical gate and merged, PR #646 merged current main and exact head 3a828a298d92dc5072fdb28e458414e981aee03e passed all five required hosted checks, including 824 Shell tests, a 4 minute 36 second ARM/package build, native, AddressSanitizer, and static analysis; signed workflow 33464738492 passed in 7 minutes 31 seconds and produced the exact 185667740-byte archive SHA-256 bef1ee7c8d95ca5e5ea614820290fa6386b7619800651392e06a32952f892427 with verified metadata, payload inventory, and Ed25519 signature", + "the signed Mini Plus candidate installed over snapshot 20260901T032842Z-pre-update-3039, returned automatically in 362 seconds, and was confirmed known-good after one boot; a fresh no-command interactive key-only request entered non-login /bin/sh with ENV=/dev/null, non-interactive execution remained functional, password-only authentication and disabled SFTP were refused, no Telnet process or listener appeared, the sole key and pinned host key remained unchanged, exact helper hashes matched the reviewed tree, and final aggregate health, five verified snapshots, idle transaction, inactive recovery lease, inactive development overlay, and transfer cleanup were green", + "the same Full checkpoint found three inactive legacy configuration remnants under config/filebrowser, config/bftpd.conf, and config/bftpdauth.conf; their retired executables and all eight stale enablement flags were absent, no configuration contents were inspected, issue #629 owns their upgrade-safe removal, and NET-10 remains in progress rather than treating PR #646 as completion" ] }, { diff --git a/docs/validation/network/plus-interactive-ssh-lockdown-20260831.json b/docs/validation/network/plus-interactive-ssh-lockdown-20260831.json new file mode 100644 index 000000000..ec9d153e1 --- /dev/null +++ b/docs/validation/network/plus-interactive-ssh-lockdown-20260831.json @@ -0,0 +1,101 @@ +{ + "schema": 1, + "date": "2026-08-31", + "checkpoint": "PR 646 signed interactive SSH profile lockdown", + "device": "miyoo-mini-plus", + "baseline": { + "version": "0.1.0-dev-c1fc6624", + "update_phase": "known_good", + "aggregate_health": "healthy", + "listener_audit": "clean with reviewed key-only SSH", + "authorized_key_count": 1 + }, + "signed_release": { + "version": "0.1.0-dev-3a828a29", + "commit": "3a828a298d92dc5072fdb28e458414e981aee03e", + "workflow_run": 33464738492, + "workflow_url": "https://github.com/boburning/BloomOS/actions/runs/33464738492", + "archive_size": 185667740, + "archive_sha256": "bef1ee7c8d95ca5e5ea614820290fa6386b7619800651392e06a32952f892427", + "manifest_valid": true, + "payload_manifest_valid": true, + "ed25519_signature_valid": true, + "snapshot_id": "20260901T032842Z-pre-update-3039", + "boot_attempts_before_confirmation": 1, + "return_seconds": 362, + "final_update_phase": "known_good" + }, + "installed_payload": { + "bloom_dev_ssh_sha256": "6c75aee4ce63002f2294c73e6e7c8f0452650c1b3b4927ddc2b061afae83498e", + "bloom_ssh_gate_sha256": "294b93eafea6746c8be3ac3d38d19d398a9cf2c4114e1d07122fa0ac9c490be5", + "bloom_platform_sha256": "e840764164c3eca35131a5cca99872341ad433c5fb096b6788f54821a226402e", + "bloom_dev_deploy_sha256": "ba538a9056239af64697adf790ce4b553b5f518920ad29d2962eed8648e438d4", + "matches_reviewed_tree": true + }, + "acceptance": { + "interactive_request_had_no_remote_command": true, + "interactive_shell_non_login": true, + "interactive_env": "/dev/null", + "noninteractive_exec_passed": true, + "password_only_authentication_refused": true, + "disabled_sftp_handoff_refused": true, + "telnet_process_after_interactive_session": false, + "telnet_listener_after_interactive_session": false, + "independent_listener_audit_clean": true, + "reviewed_listeners": [ + "key_only_ssh" + ], + "authorized_key_count": 1, + "authorized_key_unchanged": true, + "pinned_host_key_unchanged": true + }, + "post_confirmation": { + "aggregate_health": "healthy", + "save_snapshots": { + "healthy": true, + "total": 5, + "unverified": 0 + }, + "network_transaction": "idle", + "network_recovery_lease": "inactive", + "development_overlay_active": false, + "transfer_staging_removed": true + }, + "host_validation": { + "focused_selection": "114 of 115 passed before the isolated worktree initialized the four exact CI provenance submodules; the affected 13-test manifest file then passed 13 of 13", + "ci_scope_and_arm_orchestration": "passed", + "required_hosted_checks": { + "check_build": "passed", + "shell_test": "passed, 824 tests", + "test_build": "passed", + "address_sanitizer": "passed", + "static_analysis": "passed" + } + }, + "separate_follow_up": { + "issue": 629, + "inactive_retired_config_remnants_observed": [ + "config/filebrowser", + "config/bftpd.conf", + "config/bftpdauth.conf" + ], + "retired_executables_present": false, + "stale_enablement_flags_present": false, + "config_contents_inspected": false, + "net_10_complete": false + }, + "privacy": { + "device_serial_recorded": false, + "authorized_key_material_recorded": false, + "authorized_key_digest_recorded": false, + "credentials_recorded": false, + "retired_database_contents_inspected": false, + "private_paths_recorded": false + }, + "limitations": [ + "SFTP remained disabled; the device proved exact disabled-gate refusal while enabled confined SFTP handoff remains covered by host tests and prior signed device evidence.", + "This remote SSH, process, listener, file-hash, update, and health checkpoint is not physical controls, LCD, audio, gameplay, or save-semantics evidence.", + "This checkpoint covers only Miyoo Mini Plus, not Mini V1-V4 or Mini Flip/my285.", + "The inactive legacy configuration remnants are outside PR 646 and keep NET-10 open under issue 629." + ] +} diff --git a/static/build/.tmp_update/bin/bloom-dev-deploy b/static/build/.tmp_update/bin/bloom-dev-deploy index f76923abc..d5b9a8d54 100755 --- a/static/build/.tmp_update/bin/bloom-dev-deploy +++ b/static/build/.tmp_update/bin/bloom-dev-deploy @@ -89,6 +89,7 @@ allowed_path() { network-helpers:bin/bloom-network-sync|\ network-helpers:bin/bloom-network-services|network-helpers:bin/bloom-network-transaction|\ network-helpers:bin/bloom-platform|network-helpers:bin/bloom-dev-ssh|\ + network-helpers:bin/bloom-ssh-gate|\ network-helpers:bin/bloom-sftp-gate|network-helpers:bin/bloom-sftp-server|\ network-helpers:bin/bloom-transfer-policy|\ network-helpers:bin/bloom-remote-keys|\ @@ -205,6 +206,7 @@ validate_manifest() { bin/bloom-network-transaction \ bin/bloom-platform \ bin/bloom-dev-ssh \ + bin/bloom-ssh-gate \ bin/bloom-sftp-gate \ bin/bloom-sftp-server \ bin/bloom-transfer-policy \ diff --git a/static/build/.tmp_update/bin/bloom-dev-ssh b/static/build/.tmp_update/bin/bloom-dev-ssh index e25623491..68165daad 100755 --- a/static/build/.tmp_update/bin/bloom-dev-ssh +++ b/static/build/.tmp_update/bin/bloom-dev-ssh @@ -20,6 +20,7 @@ LOCK_OWNER="$LOCK_DIR/owner" PORT="${BLOOM_DEV_SSH_PORT:-22}" SHADOW_PORT="${BLOOM_DEV_SSH_SHADOW_PORT:-2222}" DROPBEAR="${BLOOM_DROPBEAR:-$SYS_DIR/bin/bloom-dropbearmulti}" +SSH_GATE=${BLOOM_SSH_GATE_BIN:-$SYS_DIR/bin/bloom-ssh-gate} HOST_KEY_DIR="$SD_ROOT/.bloom/ssh" HOST_KEY="$HOST_KEY_DIR/dropbear_ed25519_host_key" SFTP_SERVER=${BLOOM_SFTP_SERVER:-$SYS_DIR/bin/bloom-sftp-server} @@ -115,9 +116,11 @@ start_shadow() { "$NETWORK_TRANSACTION" authorize-shadow "$transaction_id" "$requested_port" >/dev/null || return 1 [ "$(cat "$STATE_FILE" 2>/dev/null || true)" = running_key_only ] || return 1 validate_authorized_keys && ifconfig wlan0 >/dev/null 2>&1 || return 1 - [ -x "$DROPBEAR" ] && [ -s "$HOST_KEY" ] && [ -s "$ROOT_SSH_DIR/authorized_keys" ] || return 1 + [ -x "$DROPBEAR" ] && [ -x "$SSH_GATE" ] && [ -s "$HOST_KEY" ] && + [ -s "$ROOT_SSH_DIR/authorized_keys" ] || return 1 stop_shadow "$DROPBEAR" dropbear -F -r "$HOST_KEY" -D "$ROOT_SSH_DIR" -p "$requested_port" \ + -c "$SSH_GATE" \ >>"$SYS_DIR/logs/developer-ssh.log" 2>&1 3>&- & shadow_pid=$! shadow_pid_tmp="$SHADOW_PID_FILE.tmp.$$" @@ -337,6 +340,11 @@ start_ssh() { state unavailable return 1 fi + if ssh_requested && [ ! -x "$SSH_GATE" ]; then + stop_dropbear + state ssh_gate_unavailable + return 1 + fi stop_dropbear mkdir -p "$HOST_KEY_DIR" "$SYS_DIR/logs" @@ -371,8 +379,13 @@ start_ssh() { sync fi - "$DROPBEAR" dropbear -F -r "$HOST_KEY" -D "$ROOT_SSH_DIR" -p "$PORT" \ - >>"$SYS_DIR/logs/developer-ssh.log" 2>&1 3>&- & + if ssh_requested; then + "$DROPBEAR" dropbear -F -r "$HOST_KEY" -D "$ROOT_SSH_DIR" -p "$PORT" \ + -c "$SSH_GATE" >>"$SYS_DIR/logs/developer-ssh.log" 2>&1 3>&- & + else + "$DROPBEAR" dropbear -F -r "$HOST_KEY" -D "$ROOT_SSH_DIR" -p "$PORT" \ + >>"$SYS_DIR/logs/developer-ssh.log" 2>&1 3>&- & + fi server_pid=$! printf '%s\n' "$server_pid" >"$PID_FILE" sleep 1 diff --git a/static/build/.tmp_update/bin/bloom-platform b/static/build/.tmp_update/bin/bloom-platform index ba551104c..af1341916 100755 --- a/static/build/.tmp_update/bin/bloom-platform +++ b/static/build/.tmp_update/bin/bloom-platform @@ -229,6 +229,7 @@ platform_capability_record() { mini_plus|mini_flip) if [ -e "$(platform_path /sys/class/net/wlan0)" ] && [ -x "$(platform_path /mnt/SDCARD/.tmp_update/bin/bloom-dev-ssh)" ] && + [ -x "$(platform_path /mnt/SDCARD/.tmp_update/bin/bloom-ssh-gate)" ] && [ -x "$(platform_path /mnt/SDCARD/.tmp_update/bin/bloom-dropbearmulti)" ]; then platform_set_capability available true true runtime_paths else diff --git a/static/build/.tmp_update/bin/bloom-ssh-gate b/static/build/.tmp_update/bin/bloom-ssh-gate new file mode 100755 index 000000000..33563b42f --- /dev/null +++ b/static/build/.tmp_update/bin/bloom-ssh-gate @@ -0,0 +1,31 @@ +#!/bin/sh + +set -u + +[ "$#" -eq 0 ] || exit 2 + +SHELL_BIN=${BLOOM_SSH_SHELL_BIN:-/bin/sh} +SFTP_GATE=${BLOOM_SFTP_GATE_BIN:-/mnt/SDCARD/.tmp_update/bin/bloom-sftp-gate} + +case "$SHELL_BIN" in + /*) ;; + *) exit 1 ;; +esac +[ -x "$SHELL_BIN" ] || exit 1 + +# Dropbear invokes this helper as a non-login forced command and preserves the +# requested command in SSH_ORIGINAL_COMMAND. Keep subsequent interactive shells +# non-login as well, and prevent a client-controlled ENV file from restoring an +# inherited profile launcher. +ENV=/dev/null +export ENV + +original_command=${SSH_ORIGINAL_COMMAND:-} +if [ "$original_command" = "$SFTP_GATE" ]; then + [ -x "$SFTP_GATE" ] || exit 1 + exec "$SFTP_GATE" +fi +if [ -z "$original_command" ]; then + exec "$SHELL_BIN" +fi +exec "$SHELL_BIN" -c "$original_command" diff --git a/static/build/.tmp_update/res/bloom/visual-packs/providers.json b/static/build/.tmp_update/res/bloom/visual-packs/providers.json index cf401620f..642be8ca6 100644 --- a/static/build/.tmp_update/res/bloom/visual-packs/providers.json +++ b/static/build/.tmp_update/res/bloom/visual-packs/providers.json @@ -21,7 +21,7 @@ "authorities": { "display_profile_policy": { "path": "build/display-profile-policy.json", - "sha256": "3e575603d133478d706783b5f3b3df18561403aaaac346afa0cf3f733d599f0a" + "sha256": "6b171937cbc705dd742bb1ce23efaaea33b4d1cca767b8832d395a0e18e88a8f" }, "ui_asset_manifest": { "path": "assets/ui/manifest.json", diff --git a/test/shell/bloom_platform.bats b/test/shell/bloom_platform.bats index db002690d..046925a91 100644 --- a/test/shell/bloom_platform.bats +++ b/test/shell/bloom_platform.bats @@ -39,6 +39,7 @@ platform() { run sh "$PLATFORM" "$@"; } touch "$BLOOM_ROOT/customer/app/axp_test" touch "$SDCARD/.tmp_update/bin/bloom-wifi" \ "$SDCARD/.tmp_update/bin/bloom-dev-ssh" \ + "$SDCARD/.tmp_update/bin/bloom-ssh-gate" \ "$SDCARD/.tmp_update/bin/bloom-dropbearmulti" \ "$SDCARD/.tmp_update/bin/bloom-sftp-server" \ "$SDCARD/.tmp_update/bin/bloom-sftp-gate" diff --git a/test/shell/bloomctl_info.bats b/test/shell/bloomctl_info.bats index ff534fbd5..1aaa28543 100644 --- a/test/shell/bloomctl_info.bats +++ b/test/shell/bloomctl_info.bats @@ -19,11 +19,14 @@ setup() { "$SDCARD/.tmp_update/bin/bloomctl" \ "$SDCARD/.tmp_update/bin/bloom-platform" \ "$SDCARD/.tmp_update/bin/bloom-dev-ssh" \ + "$SDCARD/.tmp_update/bin/bloom-ssh-gate" \ "$SDCARD/.tmp_update/bin/bloom-dropbearmulti" \ "$SDCARD/.tmp_update/bin/bloom-shell" \ "$SDCARD/RetroArch/retroarch" \ "$SDCARD/.tmp_update/onionVersion/version.txt" - chmod +x "$SDCARD/.tmp_update/bin/bloom-dev-ssh" "$SDCARD/.tmp_update/bin/bloom-dropbearmulti" + chmod +x "$SDCARD/.tmp_update/bin/bloom-dev-ssh" \ + "$SDCARD/.tmp_update/bin/bloom-ssh-gate" \ + "$SDCARD/.tmp_update/bin/bloom-dropbearmulti" default_update_state="$BLOOM_TEST_ROOT/default-update-state" cat >"$default_update_state" <<'EOF' #!/bin/sh diff --git a/test/shell/dev_deploy.bats b/test/shell/dev_deploy.bats index 08ea1020c..2dcc12669 100644 --- a/test/shell/dev_deploy.bats +++ b/test/shell/dev_deploy.bats @@ -172,6 +172,7 @@ stage_network_bundle() { bin/bloom-network-transaction \ bin/bloom-platform \ bin/bloom-dev-ssh \ + bin/bloom-ssh-gate \ bin/bloom-sftp-gate \ bin/bloom-sftp-server \ bin/bloom-transfer-policy \ diff --git a/test/shell/developer_ssh.bats b/test/shell/developer_ssh.bats index 385495b37..1b30d8832 100755 --- a/test/shell/developer_ssh.bats +++ b/test/shell/developer_ssh.bats @@ -35,6 +35,8 @@ load 'support/test_helper' setup() { setup_bloom_fixture export SSH_HELPER=/workspace/static/build/.tmp_update/bin/bloom-dev-ssh + export SSH_GATE=/workspace/static/build/.tmp_update/bin/bloom-ssh-gate + export BLOOM_SSH_GATE_BIN="$SSH_GATE" export BLOOM_ROOT="$BLOOM_TEST_ROOT" export BLOOM_PROC_ROOT=/proc mkdir -p "$BLOOM_TEST_ROOT/tmp" "$SDCARD/.bloom" @@ -100,12 +102,55 @@ teardown() { ! grep -Fq -- ' -s ' "$MOCK_LOG" ! grep -Fq -- ' -g ' "$MOCK_LOG" grep -F -- '-p 2222' "$MOCK_LOG" + grep -F -- "-c $SSH_GATE" "$MOCK_LOG" "$SSH_HELPER" shadow-stop [ "$("$SSH_HELPER" shadow-status)" = stopped ] kill -0 "$primary_pid" } +@test "SSH gate keeps interactive shells non-login and redispatches commands" { + shell="$BATS_TEST_TMPDIR/shell" + shell_log="$BATS_TEST_TMPDIR/shell.log" + cat >"$shell" <<'EOF' +#!/bin/sh +printf 'argc=%s\n' "$#" >"$SHELL_LOG" +printf 'argv0=%s\n' "$0" >>"$SHELL_LOG" +printf 'env=%s\n' "$ENV" >>"$SHELL_LOG" +printf 'args=%s\n' "$*" >>"$SHELL_LOG" +EOF + chmod +x "$shell" + + run env -u SSH_ORIGINAL_COMMAND BLOOM_SSH_SHELL_BIN="$shell" \ + SHELL_LOG="$shell_log" "$SSH_GATE" + [ "$status" -eq 0 ] + grep -Fx 'argc=0' "$shell_log" + grep -Fx "argv0=$shell" "$shell_log" + grep -Fx 'env=/dev/null' "$shell_log" + grep -Fx 'args=' "$shell_log" + + run env SSH_ORIGINAL_COMMAND='printf command-ok' "$SSH_GATE" + [ "$status" -eq 0 ] + [ "$output" = command-ok ] + + run "$SSH_GATE" unexpected-argument + [ "$status" -eq 2 ] +} + +@test "SSH gate preserves only the exact restricted SFTP subsystem handoff" { + sftp_gate="$BATS_TEST_TMPDIR/sftp-gate" + cat >"$sftp_gate" <<'EOF' +#!/bin/sh +printf '%s\n' sftp-ok +EOF + chmod +x "$sftp_gate" + + run env BLOOM_SFTP_GATE_BIN="$sftp_gate" SSH_ORIGINAL_COMMAND="$sftp_gate" \ + "$SSH_GATE" + [ "$status" -eq 0 ] + [ "$output" = sftp-ok ] +} + enable_flip() { : >"$SDCARD/.bloom-dev" printf '285\n' >"$BLOOM_TEST_ROOT/tmp/deviceModel" @@ -138,6 +183,18 @@ install_valid_key() { ! grep -Fq "$MOCK_BIN/dropbear " "$MOCK_LOG" } +@test "developer SSH fails closed without the non-login shell gate" { + enable_flip + install_valid_key + export BLOOM_SSH_GATE_BIN="$BATS_TEST_TMPDIR/missing-ssh-gate" + + run "$SSH_HELPER" start + + [ "$status" -eq 1 ] + [ "$(cat "$BLOOM_TEST_ROOT/tmp/bloom-dev-ssh.state")" = ssh_gate_unavailable ] + ! grep -Fq "$MOCK_BIN/dropbear dropbear " "$MOCK_LOG" +} + @test "Flip developer SSH starts Dropbear in key-only mode" { enable_flip install_valid_key @@ -149,6 +206,7 @@ install_valid_key() { grep -F 'dropbear dropbear -F -r' "$MOCK_LOG" grep -F -- '-D ' "$MOCK_LOG" grep -F -- '-p 22' "$MOCK_LOG" + grep -F -- "-c $SSH_GATE" "$MOCK_LOG" grep -F "$SDCARD/.bloom/ssh/dropbear_ed25519_host_key" "$MOCK_LOG" cmp "$SDCARD/.bloom/authorized_keys" "$BLOOM_TEST_ROOT/home/root/.ssh/authorized_keys" } @@ -174,6 +232,7 @@ EOF grep -F 'command="/mnt/SDCARD/.tmp_update/bin/bloom-sftp-gate"' "$runtime_key" grep -F 'no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 ' "$runtime_key" ! cmp -s "$SDCARD/.bloom/authorized_keys" "$runtime_key" + ! grep -Fq -- "-c $SSH_GATE" "$MOCK_LOG" grep -F "$MOCK_BIN/mount -o bind $SDCARD/Roms $BLOOM_TEST_ROOT/tmp/bloom-sftp-root/Roms" "$MOCK_LOG" grep -F "$MOCK_BIN/mount -o bind $SDCARD/BIOS $BLOOM_TEST_ROOT/tmp/bloom-sftp-root/BIOS" "$MOCK_LOG" [ -d "$SDCARD/Imgs" ] @@ -288,3 +347,55 @@ EOF [ ! -e "$BLOOM_TEST_ROOT/tmp/bloom-dev-ssh.lock" ] [ "$(grep -Fc ' dropbear -F ' "$MOCK_LOG")" -eq 2 ] } + +@test "signed Mini Plus interactive SSH evidence preserves the exact security claim" { + run python3 - <<'PY' +import json + +root = "/workspace" +with open(root + "/docs/validation/network/plus-interactive-ssh-lockdown-20260831.json", encoding="utf-8") as stream: + evidence = json.load(stream) +with open(root + "/docs/program/BloomOS_Work_Program_v3.json", encoding="utf-8") as stream: + program = json.load(stream) + +release = evidence["signed_release"] +acceptance = evidence["acceptance"] +final = evidence["post_confirmation"] +follow_up = evidence["separate_follow_up"] +package = next(item for item in program["packages"] if item["id"] == "NET-10") +assert release["commit"] == "3a828a298d92dc5072fdb28e458414e981aee03e" +assert release["workflow_run"] == 33464738492 +assert release["archive_size"] == 185667740 +assert release["archive_sha256"] == "bef1ee7c8d95ca5e5ea614820290fa6386b7619800651392e06a32952f892427" +assert release["ed25519_signature_valid"] is True +assert release["boot_attempts_before_confirmation"] == 1 +assert release["final_update_phase"] == "known_good" +assert acceptance["interactive_request_had_no_remote_command"] is True +assert acceptance["interactive_shell_non_login"] is True +assert acceptance["interactive_env"] == "/dev/null" +assert acceptance["noninteractive_exec_passed"] is True +assert acceptance["password_only_authentication_refused"] is True +assert acceptance["disabled_sftp_handoff_refused"] is True +assert acceptance["telnet_process_after_interactive_session"] is False +assert acceptance["telnet_listener_after_interactive_session"] is False +assert acceptance["independent_listener_audit_clean"] is True +assert acceptance["authorized_key_unchanged"] is True +assert final["aggregate_health"] == "healthy" +assert final["network_transaction"] == "idle" +assert final["development_overlay_active"] is False +assert final["transfer_staging_removed"] is True +assert follow_up["issue"] == 629 +assert follow_up["retired_executables_present"] is False +assert follow_up["stale_enablement_flags_present"] is False +assert follow_up["config_contents_inspected"] is False +assert follow_up["net_10_complete"] is False +assert package["status"] == "in_progress" +assert package["github_prs"][-1] == 646 +PY + + [ "$status" -eq 0 ] + grep -F 'validation/network/plus-interactive-ssh-lockdown-20260831.json' \ + /workspace/docs/DEVICE_SUPPORT.md + grep -F 'validation/network/plus-interactive-ssh-lockdown-20260831.json' \ + /workspace/docs/1.0_TEST_MATRIX.md +} diff --git a/test/shell/legacy_manifest.bats b/test/shell/legacy_manifest.bats index 9f4380b7c..17a477170 100644 --- a/test/shell/legacy_manifest.bats +++ b/test/shell/legacy_manifest.bats @@ -6,7 +6,7 @@ --manifest /workspace/build/legacy-manifest.json [ "$status" -eq 0 ] - [[ "$output" == "legacy manifest validate: 326 components" ]] + [[ "$output" == "legacy manifest validate: 327 components" ]] [ ! -e "/workspace/static/packages/RApp/SNK - Neo Geo (GnGeo)" ] [ ! -e "/workspace/static/packages/RApp/PICO-8 (PICO-8 standalone)" ] [ ! -e "/workspace/static/packages/RApp/SCUMM (ScummVM standalone)" ] @@ -50,6 +50,7 @@ assert "runtime-tmp-update-bin-filebrowser" not in components assert "runtime-tmp-update-bin-bftpd" not in components assert "runtime-tmp-update-bin-bftpdutmp" not in components assert components["runtime-tmp-update-bin-bloom-dev-deploy"]["resolution"] == "source-build" +assert components["runtime-tmp-update-bin-bloom-ssh-gate"]["resolution"] == "source-build" assert components["runtime-tmp-update-bin-bloom-display-session-prepare"]["resolution"] == "source-build" assert components["runtime-tmp-update-res-bloom"]["resolution"] == "source-build" assert components["runtime-tmp-update-res-bloom"]["license"] == "GPL-3.0-or-later" @@ -114,7 +115,7 @@ import sys components = json.load(open(sys.argv[1], encoding="utf-8"))["components"] runtime = [item for item in components if item["id"].startswith("runtime-")] bloom = [item for item in runtime if item["source"] == "https://github.com/boburning/BloomOS"] -assert len(bloom) == 59 +assert len(bloom) == 60 assert all(item["resolution"] == "source-build" for item in bloom) assert all(item["source_revision"] == "release-commit" for item in bloom) PY diff --git a/test/shell/transfer_policy.bats b/test/shell/transfer_policy.bats index b97557ec0..7b9495ae2 100755 --- a/test/shell/transfer_policy.bats +++ b/test/shell/transfer_policy.bats @@ -109,6 +109,7 @@ teardown() { bin/bloom-network-transaction \ bin/bloom-platform \ bin/bloom-dev-ssh \ + bin/bloom-ssh-gate \ bin/bloom-sftp-gate \ bin/bloom-sftp-server \ bin/bloom-transfer-policy \ @@ -132,7 +133,7 @@ teardown() { run python3 /workspace/tools/dev-deploy-manifest.py network-helpers "$payload" \ net02-fixture 0123456789abcdef0123456789abcdef01234567 "$manifest" [ "$status" -eq 0 ] || printf '%s\n' "$output" >&3 - [ "$(jq '.files | length' "$manifest")" -eq 20 ] + [ "$(jq '.files | length' "$manifest")" -eq 21 ] rm "$payload/bin/bloom-platform" run python3 /workspace/tools/dev-deploy-manifest.py network-helpers "$payload" \ diff --git a/test/test_retroarch_build_identity.py b/test/test_retroarch_build_identity.py index 6d0cf706a..68f3cf933 100644 --- a/test/test_retroarch_build_identity.py +++ b/test/test_retroarch_build_identity.py @@ -2,6 +2,7 @@ """Regression checks for the exact RetroArch ARM cache identity.""" import pathlib +import re import subprocess import sys import tempfile @@ -36,9 +37,15 @@ def test_identity_is_stable_for_unrelated_inventory_totals(self): self.assertRegex(baseline.stdout.strip(), r"^[0-9a-f]{64}$") changed = LOCK.read_text(encoding="utf-8") - changed = changed.replace("component_count = 326", "component_count = 999") - changed = changed.replace("file_count = 1967", "file_count = 9999") - changed = changed.replace("byte_count = 260659274", "byte_count = 999999999") + for key, replacement in ( + ("component_count", "999"), + ("file_count", "9999"), + ("byte_count", "999999999"), + ): + changed, count = re.subn( + rf"(?m)^{key} = \d+$", f"{key} = {replacement}", changed + ) + self.assertEqual(count, 1) self.assertNotEqual(changed, LOCK.read_text(encoding="utf-8")) with tempfile.TemporaryDirectory() as directory: candidate = pathlib.Path(directory) / "dependencies.lock" diff --git a/tools/ci-scope.sh b/tools/ci-scope.sh index 08ceed22e..4375a633b 100755 --- a/tools/ci-scope.sh +++ b/tools/ci-scope.sh @@ -508,6 +508,7 @@ while IFS= read -r path; do ;; tools/build-sftp-server.sh|tools/patches/openssh-sftp-*.patch|\ static/build/.tmp_update/bin/bloom-dev-ssh|\ + static/build/.tmp_update/bin/bloom-ssh-gate|\ static/build/.tmp_update/bin/bloom-sftp-gate) raise_risk 3 add_shell_target /workspace/test/shell/transfer_policy.bats diff --git a/tools/dev-deploy-manifest.py b/tools/dev-deploy-manifest.py index dc13e9ddb..2768fe1e3 100755 --- a/tools/dev-deploy-manifest.py +++ b/tools/dev-deploy-manifest.py @@ -53,6 +53,7 @@ "bin/bloom-network-transaction", "bin/bloom-platform", "bin/bloom-dev-ssh", + "bin/bloom-ssh-gate", "bin/bloom-sftp-gate", "bin/bloom-sftp-server", "bin/bloom-transfer-policy", diff --git a/tools/legacy_manifest.py b/tools/legacy_manifest.py index 327634506..9b8d86dda 100644 --- a/tools/legacy_manifest.py +++ b/tools/legacy_manifest.py @@ -52,6 +52,7 @@ "runtime-tmp-update-bin-bloom-detect-model", "runtime-tmp-update-bin-bloom-dev-deploy", "runtime-tmp-update-bin-bloom-dev-ssh", + "runtime-tmp-update-bin-bloom-ssh-gate", "runtime-tmp-update-bin-bloom-diagnostics-export", "runtime-tmp-update-bin-bloom-display-session-prepare", "runtime-tmp-update-bin-bloom-controls",