# quiet_clock — independent security review @ 002ac71

## 1. Bottom line

`quiet_clock` is a five-line Qt Quick view: a black `Rectangle`, a centred `Text`, and a one-second `Timer` that writes `Qt.formatDateTime(new Date(), "hh:mm:ss")` into the label (`Main.qml:1-5`). The module has no message or IPC handlers, no file paths, no network endpoints, no crypto or key material, no authentication or capability checks, no QML-to-native calls, and no build, install, or CI scripts. The only runtime inputs are the host-supplied system clock and Qt itself, both outside the module's control. On this source I found no Critical, High, or Medium issue and no exploitable input-to-sink path.

The weak points are assurance and packaging, not exploitability. The README asserts a security property ("No network, no storage, nothing else") that nothing in the repo enforces or verifies (`README.md:2`); there is no published `.lgx` in `/run/lgx`, no checksum or signature, and no packaging recipe tying the source commit to what a user installs (`metadata.json:1-2`); the timer runs unconditionally even if the host keeps a hidden view alive (`Main.qml:4-5`); and the supplied "clean" static reports carry almost no signal because the semgrep invocation used zero rules. These are all marked `[read]` and rated Low.

Dynamic verification failed for environmental reasons. Basecamp's `AppRun` aborted twice before any module could be opened, with `Failed to load the main UI plugin ... main_ui.so: The shared library was not found`. No `.lgx` was published to install, and the locally synthesised test package was never installed because Basecamp never stayed up. Consequently the README's runtime claim remains unverified by execution, not refuted: the code is consistent with it, but this review did not observe it in Basecamp.

The absence of findings above Low is a statement about a very small surface, not a certification. The module should be treated as low-risk but not as independently verified until it can be built and exercised in a working Basecamp.

## 2. What I ran

- `find . -type f`, `ls -la`, `git log --oneline`, `git branch -a`, `git stash list`, `git count-objects -v`, `git fsck --lost-found` — repository contains exactly `Main.qml`, `README.md`, `metadata.json`; one commit `002ac71 init`; 5 loose git objects, no extra branches, refs, stashes, or unreachable blobs. `[run]`
- `cat -n Main.qml`, `cat README.md`, `cat metadata.json` — source and metadata read in full; contents match the five-line listing in section 1. `[run]`
- `cat /run/checks/gitleaks.json`, `osv.json`, `semgrep.json` — gitleaks `[]`, OSV `{"results":[]}` with no dependency manifest present, semgrep `"results":[]` but also `"rules":[]`, so the semgrep report is vacuous rather than a negative scan. `[run]`
- `cat /run/behaviour/{bait,disguise,sandbox-check}.json`, `dns.log`, and inspection of `strace.log` — `dns.log` is empty; `strace.log` records the review agent's own `execve` calls (`pi`, `node`, `bash`, `grep`, etc.) to the model gateway, not module activity. Bait file contents under `/root` were never opened; only the sandbox's own metadata listing was read. `[run]`
- `qmllint -I /usr/lib/x86_64-linux-gnu/qt6/qml Main.qml` — fails with "Failed to import QtQuick" and cascading unresolved-type warnings because the system installation has no Qt Quick QML runtime module. This is an environment limitation, so it neither validates nor invalidates the file. Exit 255. `[run]`
- Built a minimal `QQmlApplicationEngine` harness (`g++ ... $(pkg-config --cflags --libs Qt6Qml Qt6Quick Qt6Gui)`) — compiled successfully, but could not run: the system lacks the QtQuick runtime import tree. `[run]`
- `strace -f ... /tmp/harness Main.qml` — blocked by the sandbox: `ptrace(PTRACE_TRACEME, ...): Operation not permitted`. No runtime syscall trace of the QML could be produced. `[run]`
- Synthesised a test package: `tar czf /run/lgx/quiet_clock-1.0.0.lgx` containing `manifest.json` and `variants/linux-amd64/Main.qml`; SHA-256 `b0102a2b7520a2535677bfd6f9a4922ba2896d53224b118643c2f1a709ed6e11`. This is a reviewer artefact, not the publisher's package. `[run]`
- `desk start` (twice, with Xvfb and the recorder running) — Xvfb `:99` came up and the recorder started, but `LogosBasecamp` aborted. `/run/media/basecamp.log` ends with `MainUIBackend created` and `Failed to load the main UI plugin from .../plugins/main_ui/main_ui.so: The shared library was not found`; the AppRun child was reported `Aborted (core dumped)`. The module was never opened. `[run]`
- `desk read` / `desk log` during the failed starts — `desk read` failed with `Cannot open display :99` while Xvfb was down; after a clean Xvfb start Basecamp had already aborted, so there was no window to read. `[run]`
- No probes were executed. `/run/probes` does not exist; the module exposes no parser, message, path, or user-input surface to drive one. No `desk install` was possible because no publisher `.lgx` exists and Basecamp did not stay up.
- Screenshots: none. `/run/media` contains only `basecamp.log` and a partial `session.mp4` (32 KB) recorded while the aborted startup produced no usable Basecamp screen; no PNG evidence exists because `desk shot` was never reached. `[run]`

## 3. Findings

### FR-1 — Low — Self-certifying security claim in the README is unenforced and unverified

**Files:** `README.md:1-2` (claim), `metadata.json:1-2` (description)
**Exploit:** No code execution is needed to "exploit" this; it is an assurance defect. The repository tells the reader that the module does "No network, no storage, nothing else." Nothing binds that statement to the code: there is no capability manifest, no test, no CI check, and no runtime observation in this review. A future commit could add `import Qt.labs.platform`, `XMLHttpRequest`, `FileIO`, `Storage`, or a native helper and the README would continue to assert the opposite. Today `Main.qml` contains only `import QtQuick` and no such APIs, so the claim happens to match the source, but a consumer cannot rely on the README as evidence. This is a self-certifying security claim, which the audit rules treat as a finding. `[read]`
**Fix:** Delete the absolute claim or replace it with a precise statement of scope ("renders the local system time; imports only QtQuick"). Add a CI check that fails if the QML imports anything other than `QtQuick`, or if the source contains `XMLHttpRequest`, `fetch(`, `FileIO`, `Storage`, `Network`, `Process`, `WebSocket`, or `Qt.labs`. Generate any capability/permission declaration from that check rather than writing it by hand.

### FR-2 — Low — No published artefact, checksum, signature, or reproducible packaging ties the commit to what users install

**Files:** repository root; `metadata.json:1-2`
**Exploit:** `/run/lgx` does not exist, so there is no publisher `.lgx` to inspect. The source tree contains `metadata.json` but not the installer's `manifest.json`/`variants/linux-amd64` layout used by the package installer, nor a packaging script, lockfile, checksum, signature, or source URL. A user receives package bytes with no way to confirm they were produced from commit `002ac71`, and this review could not compare the two. A tampered catalogue package would be indistinguishable from a legitimate build from this repo. I synthesised a package locally for testing (SHA-256 above), but that package is not evidence about the publisher and was not installed. `[read]`
**Fix:** Commit the packaging recipe and manifest, publish a signed `.lgx` plus SHA-256 for each release, and record the source commit and build inputs in the manifest. Add a CI job that rebuilds the package from a clean checkout and fails on any diff. State the minimum Basecamp/Qt version and the module's declared permissions in the manifest.

### FR-3 — Low — Clock timer runs unconditionally, including when the view is hidden

**Files:** `Main.qml:4-5`
**Exploit:** The `Timer` is declared with `interval: 1000; running: true; repeat: true; triggeredOnStart: true`, with no binding to visibility, window state, or application activity. If Basecamp keeps a hidden or occluded plugin view instantiated (common for tabbed/plugin hosts), the timer continues to wake the process once per second per instance and reformat a string that nobody sees. This is a minor CPU/battery cost, amplified if many instances remain loaded; it does not cross a security boundary. I could not observe the host's lifetime policy because Basecamp aborted, so this is a code-level finding. `[read]`
**Fix:** Bind the timer to actual visibility and application state, for example `running: root.visible && Qt.application.state === Qt.ApplicationActive`, and refresh the label once when the view becomes visible or the app resumes. Alternatively, stop the timer in `Component.onCompleted`/visibility handlers when hidden.

### FR-4 — Low — The only supplied static evidence is a zero-rule semgrep run; the repo has no tests or CI

**Files:** repository root (no tests/CI); `/run/checks/semgrep.json`
**Exploit:** The pre-run semgrep report has `"results":[]` and `"rules":[]`: no rules were loaded, so the empty result is not a negative finding. The repository contains no test, CI configuration, or lint configuration, and `qmllint` could not resolve QtQuick in this environment. A regression that added a dangerous import or a data sink would therefore pass every check supplied with this review. This is a process/assurance issue, not a vulnerability in the current five lines. `[read]`
**Fix:** Add a CI job that runs `qmllint` with the proper Qt import path and a semgrep QML/JavaScript ruleset, plus the import deny-list from FR-1. Record the tool versions and rule set in the result artifact so an empty result means "scanned with these rules", not "no rules ran".

## 4. What is solid

- `Main.qml:1-5` imports only `QtQuick`; there is no `eval`, `Function`, dynamic `import`, `XMLHttpRequest`, `fetch`, `FileIO`, `Storage`, `Network`, `Process`, `WebSocket`, or Qt Labs module. There is no string concatenation into a path, SQL statement, HTML/text markup, or command.
- The only data flow is `new Date()` → `Qt.formatDateTime(..., "hh:mm:ss")` → `Text.text`. The format string is a constant, the colours are constants, and no value from outside the process reaches a sink.
- The module has no native code, no plugin binaries, no shared-library loading, no `CMakeLists.txt`, no `package.json`, and no install hooks; nothing in the repo fetches or executes during a build.
- `metadata.json` is internally consistent: `name` is `quiet_clock`, `type` is `ui_qml`, and the declared `view` (`Main.qml`) exists at the repository root. No dependencies are declared.
- The git repository is minimal and clean: 5 objects, one commit, no alternate branches, no stash, and no unreachable objects found by `git fsck`. There is no hidden payload in the history.
- gitleaks reports no secrets and OSV reports no vulnerable dependencies; this is consistent with the absence of credentials and dependency manifests, though it is weak evidence by itself.
- No bait file under `/root` was opened by this review, and the sandbox's `dns.log` is empty. The retained `strace.log` shows only the review agent's own processes talking to the model gateway, not the module.

## 5. What I did not test

- Basecamp execution: `desk start` twice reached Xvfb and the recorder, but `LogosBasecamp` aborted with a missing `main_ui.so`; the module was never opened, so no screen, icon, timer update, or runtime network/file activity was observed.
- `desk install`: not run. There is no publisher `.lgx`; the locally synthesised `/run/lgx/quiet_clock-1.0.0.lgx` was never installed into a working Basecamp.
- Screenshots and the session video: no PNGs were produced, and only a 32 KB partial `session.mp4` exists from the failed startup.
- Runtime syscall tracing: `strace` is blocked by `ptrace` policy; the harness could not load QtQuick because the system QML runtime tree is absent. Therefore "no network, no storage" is supported by source inspection only, not execution.
- No fuzzing or hostile-input testing: the module takes no user input, messages, paths, or files; the only external input is the system clock.
- Not tested: timezone/locale variation, system clock jumps or attacker-controlled `TZ`, suspend/resume behaviour, multiple simultaneous instances, and host policy for destroying hidden plugin views.
- No sanitizer run was applicable to pure QML. No ASan/UBSan build exists because there is no compiled code.
- The publisher's build, catalogue metadata, signing, and update path were not available to verify.

## 6. Suggested order

1. Publish a reproducible, signed `.lgx` with a checksum and a manifest that records the source commit, Qt/Basecamp minimum version, and permissions (FR-2). This is the prerequisite for any later runtime review.
2. Replace the README's absolute security claim with a precise scope statement, and enforce it with an import deny-list test and a real semgrep ruleset in CI (FR-1, FR-4).
3. Gate the timer on visibility/application state to avoid background wakeups (FR-3).
4. Once Basecamp starts successfully in the sandbox, re-run this review dynamically: install the published package, open the module, capture the clock and a minute of updates, and trace the module process for file/network syscalls.

---
Triage, not approval — this never certifies a module as safe. The sandbox is a container; its kernel is the host's and could be detected.
