Skip to content

Commit bccfc03

Browse files
committed
[docs] guard protected root imports
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent e88409d commit bccfc03

4 files changed

Lines changed: 51 additions & 16 deletions

File tree

‎docs/decisions/0040-sha-pinned-connected-github-import.md‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,19 @@ the selected `.py` files sequentially to the current board directory.**
7272
surface shows every exact remote path and exact board target path. It
7373
rejects empty/dot names, separators, NUL, duplicate targets, paths outside
7474
the captured directory, and targets over PBLE/1's 128-byte UTF-8 path
75-
ceiling. Preserving a remote hierarchy is a future, separately specified
76-
extension.
75+
ceiling. The action also captures the board-reported `fs_root` and mirrors
76+
PBLE/1's protected top-level names: when the captured directory equals that
77+
root, a target beginning with lowercase `pyble` or `pble`, or named exactly
78+
`boot.py` or `_boot.py`, is blocked before a board listing, Git blob fetch,
79+
or PUT. The surface warns whenever the captured destination is the board
80+
root. A path-specific localized failure names a selected protected target
81+
and instructs the user to close Import, create and enter a child directory
82+
such as `/examples` in Files, and then reopen Import so the new destination
83+
is captured. The same basename remains valid below a child directory.
84+
Ordinary non-protected basenames remain valid at the board root; this is
85+
targeted preflight, not a blanket root-import ban or confirm-to-continue
86+
warning.
87+
Preserving a remote hierarchy is a future, separately specified extension.
7788

7889
5. **Overwrite is always an informed choice.** Review lists the captured board
7990
directory and marks exact target names already present. Existing
@@ -150,11 +161,13 @@ the selected `.py` files sequentially to the current board directory.**
150161

151162
11. **The delivery remains test-driven and clean-room.** Unit tests cover URL
152163
and host rejection, ref-to-SHA pinning, lazy traversal, entry filtering,
153-
safe target derivation, UTF-8/NUL/size bounds, rate/error mapping, operation
164+
safe target derivation (including protected-root rejection and nested-path
165+
acceptance), UTF-8/NUL/size bounds, rate/error mapping, operation
154166
generations, and result accounting. Widget tests use an injected fake
155-
GitHub client and `FakeConnection` to prove overwrite consent, all-fetches-
156-
before-first-PUT, sequential order, session changes, cancellation, partial
157-
truth, refresh, and zero open/run/mkdir calls. Goldens cover compact and
167+
GitHub client and `FakeConnection` to prove actionable protected-root
168+
guidance, overwrite consent, all-fetches-before-first-PUT, sequential
169+
order, session changes, cancellation, partial truth, refresh, and zero
170+
open/run/mkdir calls. Goldens cover compact and
158171
wide layouts, portrait/landscape, loading/error/review/partial states, 2×
159172
text, high contrast, and keyboard inset. An integration/HIL row imports a
160173
small public pinned fixture into a disposable board directory and verifies

‎docs/specifications/App/TDD.md‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,17 @@ target = joinBoardPath(capturedCwd, basename(entry.remotePath))
906906
No remote ancestor is preserved and no `mkdir` is called. Path derivation is a
907907
pure, tested function. It rejects empty, `.`/`..`, slash/backslash, NUL,
908908
duplicate target, non-canonical escape, and any complete board path over
909-
PBLE/1's 128-byte UTF-8 ceiling. The review view renders exact remote → board
909+
PBLE/1's 128-byte UTF-8 ceiling. The action captures the board-reported
910+
`fsRoot` alongside `capturedCwd`; presentation shows localized root-destination
911+
guidance whenever those canonical paths are equal. Target derivation also
912+
mirrors the PBLE/1 filesystem jail's protected top-level names. At the captured
913+
root, a basename beginning with lowercase `pyble` or `pble`, or exactly equal
914+
to `boot.py` or `_boot.py`, raises a sanitized `protectedRootTarget` failure
915+
before the board listing, Git blob fetch, or PUT. The same basename below any
916+
child directory remains valid. The localized error names only the safe target
917+
path and explains that the user must close Import, create and enter a child
918+
folder such as `/examples` in Files, and reopen Import because `capturedCwd`
919+
is immutable for that action. The review view renders exact remote → board
910920
path pairs before a network content fetch or PUT.
911921

912922
`GithubBoardImporter.review()` first requires the captured session stamp to be
@@ -1999,7 +2009,7 @@ This is where the Technical Design meets **Test-Driven Development** ([§0](#0-n
19992009
| `lib/connect/` | saved-board store | scan list (label/PyBLE-XXXX + RSSI), diagnostics, unsupported-version prompt, rename privacy-warn + length bound, Identify shown iff has_identify, configure-LED prompt when identify_led null, EUNSUPPORTED surfaced | — | — | scan→connect→HELLO→DeviceInfo; setLabel/setIdentifyLed/identify round-trips | permission rationale, rename warning |
20002010
| `lib/blocks/` | bridge decode/limits, host epochs, fresh-snapshot correlation, action lock, restore state; GPIO/Time/NeoPixel/TFT definitions, codegen, validation, imports, and name safety; catalog schema/IDs/distinct-role materialization; production-generation of all eight fixtures (no pin/default/profile or device gate); target adoption/path derivation/128-byte preflight, sidecar v1 codec/CRC/exact-source and scratch-round-trip validation; bounded tokenizer/parser/model precedence, imports, names, numeric/range/function/NeoPixel/TFT/resource limits, supported/rejected grammar, all-or-nothing and semantic reparse equality | focused layout, sole Run ownership, off-canvas notices, inspector threshold, console expansion; shared adaptive example/import chooser; Preview non-mutation; empty Create; confirmed/cancelled/failed Replace with workspace+target rollback and host-acknowledged success; duplicate-role/diagnostic/stale-document errors; non-destructive optional/invalid toolbox IDs; no implicit board action; keyboard/semantics | wide landscape with inspector; narrower landscape without; stacked portrait; empty Examples state; exact-reopen/import diagnostics, target paths, and source preview; compact bottom sheet, wider dialog, constrained action overflow, large text, and keyboard inset | source-first/sidecar-last write ordering, no-PUT overlength preflight, local-session stamp lifecycle, board-swap/disconnect refusal before the next sidecar/Run verb, and CRC anchor reuse | every catalog workspace restore→generate; exact pair reopen preserving serialization/target; composed bound-document Python subset→workspace→generate→semantic reparse; torn-pair failures; GPIO/NeoPixel/TFT workspace pair Save→Run on iPadOS + Android | all Blocks/example/import/actions/wiring/diagnostic/validation/semantics labels |
20012011
| `lib/plots/` | SeriesParser | plot render | plot golden | — | live plot from console | — |
2002-
| `lib/github_import/` | canonical URL/host parser; default/explicit ref→full-SHA pin; lazy response models and regular-`.py` filter; safe basename target derivation; strict UTF-8/NUL and 256 KiB/file + 1 MiB/batch bounds; rate/error mapping; operation generations; result accounting | fake-GitHub + FakeConnection resolve/browse/select/review; exact target preview; overwrite consent/recheck; all fetches before first PUT; stable sequential PUT order; pre/during-commit cancellation; session swap; partial truth + refresh; no mkdir/open/run; keyboard/focus/semantics | compact sheet + wide dialog in portrait/landscape; loading, empty, validation, rate-limit, review, overwrite, upload, partial, and complete states; 2× text, high contrast, keyboard inset | — | deterministic mock-HTTP→FakeConnection flow; pinned public fixture→disposable board directory with exact bytes and no execution (HIL) | every field/action/loading/error/rate/overwrite/progress/result/semantics label |
2012+
| `lib/github_import/` | canonical URL/host parser; default/explicit ref→full-SHA pin; lazy response models and regular-`.py` filter; safe basename target derivation with protected-root rejection; strict UTF-8/NUL and 256 KiB/file + 1 MiB/batch bounds; rate/error mapping; operation generations; result accounting | fake-GitHub + FakeConnection resolve/browse/select/review; exact target preview; actionable protected-root guidance with zero board I/O; overwrite consent/recheck; all fetches before first PUT; stable sequential PUT order; pre/during-commit cancellation; session swap; partial truth + refresh; no mkdir/open/run; keyboard/focus/semantics | compact sheet + wide dialog in portrait/landscape; loading, empty, validation, rate-limit, review, overwrite, upload, partial, and complete states; 2× text, high contrast, keyboard inset | — | deterministic mock-HTTP→FakeConnection flow; pinned public fixture→disposable child directory with exact bytes and no execution (HIL) | every field/action/loading/error/rate/overwrite/progress/result/semantics label |
20032013
| `lib/localization/` | key registry | — | — | — | — | parity gate (en vs locale) |
20042014
| `lib/app/` About | runtime build-info adapter + blank/failure formatting | global disconnected/connected entry, full-route/back preservation, offline `LicenseRegistry` navigation, semantics, zero `Connection` calls | phone + iPadOS/Android portrait/landscape, 2× text/high contrast | — | installed version/build smoke on iPadOS + Android | every About/action/privacy/version string |
20052015

‎docs/specifications/App/specs.md‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,18 @@ to `[red]` before implementation:
253253
`join(capturedCwd, basename(remotePath))`. This subset MUST NOT preserve the
254254
remote parent hierarchy or call `mkdir`. Empty/dot names, separators, NUL,
255255
duplicate/escaping targets, and targets exceeding PBLE/1's 128-byte UTF-8
256-
path ceiling MUST fail before download. Existing regular files MUST be named
257-
in a separate explicit overwrite confirmation; an existing directory or
258-
other non-regular conflict MUST block import. The board-listing seam MUST
256+
path ceiling MUST fail before download. The action MUST capture the
257+
board-reported `fs_root` with `capturedCwd` and visibly warn when the two are
258+
equal. At that root, a target whose top-level name begins with lowercase
259+
`pyble` or `pble`, or is exactly `boot.py` or `_boot.py`, MUST be blocked
260+
before the first board listing, Git blob fetch, or PUT. The localized failure
261+
MUST name the protected target and instruct the user to close Import, create
262+
and enter a child folder such as `/examples` in Files, and reopen Import; the
263+
same basename MUST remain valid below a child folder, and an ordinary
264+
non-protected basename MUST remain valid at the board root without a separate
265+
confirmation. Existing regular files MUST be named in a separate explicit
266+
overwrite confirmation; an existing directory or other non-regular conflict
267+
MUST block import. The board-listing seam MUST
259268
preserve PBLE/1's completeness bit, and `FILE_LIST more=1` (or a test double
260269
that cannot prove completeness) MUST block import rather than treating omitted
261270
names as absent. The directory MUST be listed again immediately before the
@@ -308,12 +317,15 @@ to `[red]` before implementation:
308317
invoking Files action on dismissal; important error/final-result
309318
announcements MUST occur once. MUST
310319
- **A33-SUB-AC-10 (verification)** — Unit coverage MUST prove URL/host parsing,
311-
ref-to-SHA pinning, lazy traversal, filtering, target safety, byte bounds,
320+
ref-to-SHA pinning, lazy traversal, filtering, target safety including
321+
protected-root rejection and nested-path acceptance, byte bounds,
312322
strict UTF-8/NUL rejection, rate/error mapping, stale-operation suppression,
313323
and result accounting. Fake-client/`FakeConnection` widget tests MUST prove
314-
overwrite consent, all fetches before the first PUT, sequential order,
315-
cancellation/session boundaries, partial truth, refresh, and zero
316-
mkdir/open/run calls. Goldens MUST cover compact/wide responsive states,
324+
visible root-destination guidance, path-specific protected-root refusal with
325+
zero board I/O, nested acceptance, overwrite consent, all fetches before the
326+
first PUT, sequential order, cancellation/session boundaries, partial truth,
327+
refresh, and zero mkdir/open/run calls. Goldens MUST cover compact/wide
328+
responsive states,
317329
loading/error/review/partial outcomes, portrait/landscape, 2× text, high
318330
contrast, and keyboard inset. Integration/HIL MUST verify exact imported
319331
bytes in a disposable board directory and no automatic execution. MUST

‎docs/specifications/protocol.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ before wrap, so the quiescence seam cannot reintroduce ABA.
476476

477477
**Resume on reconnect (F-10):** a link drop mid-`PUT` resets the in-RAM transfer state, but the jailed `<dest>.pbltmp` + its watermark persist on flash. On reconnect a `FILE_PUT_BEGIN` for the same dest returns `resume_offset` = the existing temp length (the contiguous prefix the board itself wrote; the worker re-CRCs `temp[0,len)` to re-seed the running whole-file CRC and set the watermark). `temp_len > total_size` → truncate to 0 (`resume_offset = 0`). The app resumes `FILE_PUT_DATA` from `resume_offset`; the whole-file CRC at `FILE_PUT_END` stays the only correctness gate — a bad/foreign prefix → `ECRC`, temp deleted, **old file kept byte-for-byte** (never a silent corruption or restart-from-zero).
478478

479-
**Workspace jail (F-17):** every path is canonicalized against `fs_root` at a **single chokepoint** before any vfs op; traversal (`../`) / absolute escapes outside `fs_root`, the reserved `.pbltmp` suffix, and reserved agent prefixes → `EACCES` (SEC-4). **`.py` / data only** — the agent never requires, generates, or accepts `.mpy` / `.pyc` transfer artifacts; no server-side compilation.
479+
**Workspace jail (F-17):** every path is canonicalized against `fs_root` at a **single chokepoint** before any vfs op; traversal (`../`) / absolute escapes outside `fs_root`, the reserved `.pbltmp` suffix, and protected top-level names → `EACCES` (SEC-4). The protected-name predicate is case-sensitive and applies only to the first canonical component relative to `fs_root`: names beginning with lowercase `pyble` or `pble`, plus exact `boot.py` and `_boot.py`, are protected. The same basename below a child directory is allowed, and ordinary root names such as `main.py` remain allowed. **`.py` / data only** — the agent never requires, generates, or accepts `.mpy` / `.pyc` transfer artifacts; no server-side compilation.
480480

481481
## 6. Run / Stop / Console
482482

0 commit comments

Comments
 (0)