Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions build/dependencies.lock
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,10 @@ legacy_channels = ["development"]
[legacy_inventory]
manifest = "build/legacy-manifest.json"
schema = 1
component_count = 330
file_count = 2129
byte_count = 314637632
source_build_components = 226
component_count = 331
file_count = 2130
byte_count = 314646675
source_build_components = 227
unresolved_components = 104
inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b"

Expand Down
13 changes: 13 additions & 0 deletions build/legacy-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,19 @@
"build_recipe": "Makefile",
"resolution": "source-build"
},
{
"id": "runtime-tmp-update-bin-bloom-wifi-profiles",
"kind": "runtime",
"path": "static/build/.tmp_update/bin/bloom-wifi-profiles",
"file_count": 1,
"byte_count": 9043,
"tree_sha256": "269e3600718c2d1c88c3083c9a751ad572d2de9e5e12d50aa1883a333be74c13",
"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-bloomctl",
"kind": "runtime",
Expand Down
48 changes: 48 additions & 0 deletions build/wifi-profile-store.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://bloomos.local/schemas/wifi-profile-store-v1.json",
"title": "BloomOS Wi-Fi profile store",
"type": "object",
"additionalProperties": false,
"required": ["schema", "profiles"],
"properties": {
"schema": {"const": 1},
"profiles": {
"type": "array",
"maxItems": 16,
"items": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": ["id", "ssid", "security", "hidden", "psk"],
"properties": {
"id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,31}$"},
"ssid": {"type": "string", "pattern": "^[ -~]{1,32}$"},
"security": {"const": "wpa2-psk"},
"hidden": {"type": "boolean"},
"psk": {
"type": "string",
"anyOf": [
{"pattern": "^[ -~]{8,63}$"},
{"pattern": "^[0-9A-Fa-f]{64}$"}
]
}
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["id", "ssid", "security", "hidden"],
"properties": {
"id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,31}$"},
"ssid": {"type": "string", "pattern": "^[ -~]{1,32}$"},
"security": {"const": "open"},
"hidden": {"const": false}
}
}
]
}
}
}
}
1 change: 1 addition & 0 deletions docs/1.0_TEST_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ shoulder button.
| 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; 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 flags cannot activate listeners; process-aware listener audit; Samba hidden until a materially different source-build candidate passes feasibility |
| Wi-Fi profile store | JFFS2 private root; stdin-only bounded replacement; schema/unknown-field/count/size enforcement; WPA2-PSK, explicit open, and manual hidden WPA2; WEP and hidden open denial; unique IDs/SSIDs; redacted status; mode-0600 atomic single-profile supplicant projection; symlink, occupied-slot, live-lock, invalid-profile, and prior-byte preservation |
| GameSwitcher | MENU open/toggle; Left/Right select; L1/R1 page; A resume; X actions; START Quick Settings; B Home; no inherited brightness/view/delete behavior |
| Apps/Tweaks | Every app disposition recorded; normal configuration requires no Tweaks; supervised launch remains bounded |
| Persistent state | Versioned atomic publication; exact restoration across Details/game/GameSwitcher/Quick Settings; malformed/oversized/symlink input defaults without Safe Mode |
Expand Down
19 changes: 19 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,25 @@ read, a serialized compatibility import preserves the existing v1 marker and
never consults the markers again while canonical fields are valid. The old
reader remains only as that fail-closed migration path.

`bloom-wifi-profiles` is the separate private Wi-Fi-secret authority. Its
schema-1 store lives below the internal JFFS2 `/appconfigs` boundary, is
limited to 16 profiles and 16 KiB, and accepts a complete replacement only on
stdin. Profile IDs are non-secret stable handles; status reports only counts
and managed/unmanaged state. SSIDs and credentials are never returned. The
initial security vocabulary is WPA2-PSK plus an explicitly created open
profile. A hidden profile must be WPA2-PSK. WEP, unknown security modes,
duplicate IDs or SSIDs, control characters, links, extra fields, and unsafe
paths fail closed.

Projection is an explicit second operation: a caller names one non-secret
profile ID, the service validates the private store again, writes one
`update_config=0` supplicant network to a mode-0600 same-directory candidate,
synchronizes it, and atomically replaces `/appconfigs/wpa_supplicant.conf`.
The previous runtime configuration remains authoritative until that rename.
The service never scans, associates, changes the Wi-Fi preference, or restarts
the radio; those behaviors remain with later NET-04 API work and the existing
`bloom-network`/`bloom-wifi` owners.

Listener reconciliation is event-driven at existing fixed boundaries: boot,
Wi-Fi reconciliation, Wi-Fi preference changes, remote-key publication, and
each listener preference request. A request publishes its desired boolean and
Expand Down
29 changes: 29 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,35 @@ host also refuses to confirm `ssh-disable`; that sole-route operation must be
explicitly rolled back or allowed to expire under the watchdog. Transaction
state cannot create either developer marker or alter signed update identity.

## Wi-Fi profile-store fixtures

NET-03 separates private station profiles from non-secret Bloom settings. Use
only synthetic fixtures during host development. A complete schema-1 store is
received on stdin; credentials are never command arguments:

```sh
bloom-wifi-profiles replace < synthetic-profiles.json
bloom-wifi-profiles status --json
bloom-wifi-profiles project fixture-profile-id
```

`replace` is bounded to 16 profiles and 16 KiB. `project` accepts only the
non-secret stable ID, emits no SSID or credential, and writes a single managed
supplicant configuration through a mode-0600 same-directory candidate and
atomic rename. Neither operation starts/stops Wi-Fi or associates to a
network. Run the focused gate with the pinned shell-test image:

```sh
./tools/run-shell-tests.sh "$PWD" "$PINNED_SHELL_IMAGE" 1 \
test/shell/wifi_profiles.bats
```

Do not copy a live `/appconfigs/wpa_supplicant.conf` into the repository,
fixture output, shell history, logs, screenshots, or evidence. Device preflight
may inspect only filesystem type, mode, byte count, structural counts, and
redacted health. A future NET-04 connect/forget surface must retain stdin or an
equivalent fixed private input boundary and the existing rollback rules.

An activated update is not considered successful merely because the installer
or MainUI started. The runtime records each unconfirmed boot in durable
`.bloom/update` state. After device checks pass, developers can promote the
Expand Down
12 changes: 12 additions & 0 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,16 @@ offline indexes may remain on the SD card. Credential bootstrap accepts secrets
only through hidden local prompts and pinned encrypted transport, never through
process arguments.

Wi-Fi profiles follow that same boundary. `bloom-wifi-profiles replace` reads
one bounded schema-1 document from stdin and publishes it mode 0600 below a
mode-0700 `/appconfigs/bloom-network` root. It exposes no export or raw-list
command. Public status contains only the profile count and whether the current
supplicant file carries Bloom's managed marker. Projection accepts only a
non-secret profile ID, revalidates the store, and atomically publishes a
single-network mode-0600 configuration with `update_config=0`, preventing
`wpa_supplicant` from becoming a shadow writer. WEP, implicit open-network
selection, hidden open profiles, unsafe links, duplicate identities, control
characters, oversized stores, occupied atomic slots, and unknown fields are
rejected without replacing the prior store or runtime configuration.

Security-sensitive migrations and repair operations must be bounded, logged, reversible where possible, and validated before activation.
16 changes: 12 additions & 4 deletions docs/program/BloomOS_Work_Program_v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -804,16 +804,24 @@
"id": "NET-03",
"track": "Network",
"title": "Bloom Wi-Fi secret profile store and runtime projection",
"status": "planned",
"status": "merge_ready",
"dependencies": [
"NET-01"
],
"github_prs": [],
"github_prs": [
485
],
"risk_tier": 3,
"deployment_tier": "warm",
"device_gate_required": true,
"last_audited_main": "3a7e78290285806e1ce3eb0c126887644ff665f2",
"evidence": []
"last_audited_main": "5852e08bb31b9d539999fec08992dfac19b0d143",
"evidence": [
"issue #484 tracks the main-based NET-03 slice after NET-02 merged as 5852e08bb31b9d539999fec08992dfac19b0d143; current main has no canonical Wi-Fi profile owner and still consumes the inherited single /appconfigs/wpa_supplicant.conf directly",
"Mini Plus read-only preflight confirmed /appconfigs is writable JFFS2, /mnt/SDCARD is VFAT, the current supplicant configuration is a regular non-symlink file, and only structural counts and required headers were inspected without reading or exporting SSIDs or credentials",
"PR #485 publishes one bounded schema-1 JFFS2 profile authority with stdin-only replacement, redacted status, WPA2-PSK, explicit open, manual hidden WPA2, WEP and hidden-open denial, unique IDs and SSIDs, mode-0600 atomic single-profile projection, and no scan, association, preference, radio, or listener mutation; focused adversarial validation passed 13/13 after manifest, provenance, transfer-policy, and exact 314646675-byte dependency preflight",
"exact head bf2280480d44087f368805ce946a72f902269e53 passed risk-scoped shell workflow 33170790616 in 1m33s after the focused legacy manifest and provenance suite passed 13/13 locally in 271 seconds; the prior failed run 33170145418 exposed only two stale exact inventory assertions, corrected on the same PR branch before any device fixture",
"the exact 9043-byte helper with SHA-256 b812644ae75139dd92df6e24b31904b67433a58d9db2d8d07f06210be159de2f was transferred through strict key-only SSH and exercised only under /tmp/bloom-net03-fixture-bf228048 with synthetic profiles; the root was mode 0700, store and projected config were mode 0600, exactly one managed network was emitted, live /appconfigs/wpa_supplicant.conf retained SHA-256 d7fbadd510e76c423638513337620e17944d51b4505ff314354971d97114bdef, temporary state was removed, and aggregate device health remained green without a radio, listener, update, reboot, or network-transaction mutation"
]
},
{
"id": "NET-04",
Expand Down
Loading