C411 Trend runs unsandboxed inside omarchy-shell, like every Omarchy plugin.
It reads your C411 statistics with a personal API key you create on
c411.org with the read-only "Profile (read)" scope (profile:read). This page
says exactly what it touches, where the key goes, and which limits keep a
misbehaving server or a corrupt file from hurting the shell.
Since 2.0 the plugin reads no browser data: no cookie stores, no keyring, no decryption. 1.x did; that code is gone.
- No privilege escalation: nothing runs as root, no
sudo/pkexec, no policy files, no services, no install scripts. It is QML, JavaScript and one Python script (bin/c411trend-fetch) running as your user. - No network access except one HTTPS request to
https://c411.org/api/user/profileper poll (every 30 minutes by default; C411 allows 30 requests a minute per key). No redirects are followed, no other host is contacted, no telemetry. - The key is never written to
shell.json, never put on a command line (pscannot see it), never logged or printed, and never sent anywhere but c411.org. - No writes outside its own files:
~/.config/c411trend/api-key(the key),~/.local/state/c411trend/(history and its lock), and its entry in~/.config/omarchy/shell.jsonthrough the shell's settings API.
| Step | How |
|---|---|
| Pasting it | The panel's field is masked (echoMode: Password) and cleared as soon as you press Enter. The key goes to c411trend-fetch --store-key on stdin; the QML drops it from memory once written. |
| Storing it | Validated against a strict token pattern ([A-Za-z0-9._~+/=:-]{16,512}), then written atomically to ~/.config/c411trend/api-key, mode 0600, in a 0700 directory. A symlink at that path is replaced, never written through. |
| Reading it | Opened with O_NOFOLLOW, at most 4 KiB, re-validated; a group- or world-readable file is tightened to 0600. A key that fails the pattern is refused before any request. |
| Sending it | As Authorization: Bearer … in a curl config read from stdin (-K -). The pattern excludes quotes, backslashes, spaces and newlines, so the key cannot alter that config. |
| Scope | Only profile:read is needed. Do not tick other scopes: the plugin uses none, and a key that can only read your profile is the least you can lose. Revoke it on c411.org at any time. |
~/.local/state/c411trend/history.jsonl (directory 0700, file 0600): one
line per poll with a timestamp, uploaded/downloaded byte counts, ratio, upload
and download credit, XP, level and Zen. No key, no username, no torrent data.
Deleting the directory resets the plugin.
The latest full profile (username, team, class, alerts) is kept in memory only.
The widget's entry in shell.json holds your settings and the last what-if
question (simQuery, at most 64 characters).
| Source | Trusted? | Handling |
|---|---|---|
| Your settings | yes | Intervals, targets, language, templates. --lang accepts en or fr only. |
| The pasted key | no (validated) | Strict pattern before storing and before every use; see above. |
| c411.org's answer | no | Capped at 1 MiB while streaming and cut off 5 s after the request timeout. The HTTP status is read separately (401 → key refused, 403 → scope missing, 429 → rate limited, 5xx → server error). The body must be a JSON object; byte counts, XP and Zen must be finite, non-negative numbers below 10^21 or they are dropped (the sample is rejected without transfer totals). Booleans must be real booleans. Every string (username, team, class, block reason, freeleech sources, bonus labels) is stripped of control characters and cut (24 to 200 characters); lists are cut to 5 or 8 items; unknown shapes are dropped, not guessed at. Fields the panel does not use (id, avatar URL) are not passed on. |
history.jsonl |
no (it can be hand-edited) | Lines that are not objects with finite, non-negative numbers are skipped. Files over 16 MiB are read from the tail. |
Bar template (barFormat) |
yes, but bounded | Single-pass substitution: a value containing {…} is never expanded again. Only the widget's own keys match ({constructor} or {__proto__} stay literal text). The resulting label is cut at 160 characters. |
What-if field and when IPC |
yes, but bounded | At most 64 characters, parsed by one anchored pattern without nested repetition. Never evaluated or passed to a process. |
All text the widget shows, the bar label included, uses Text.PlainText: a
username, team name or block reason cannot inject markup. Messages come from
fixed English and French catalogues, filled with values, never built from them.
curl -q -sS --compressed --proto =https --max-redirs 0 \
--max-filesize 1048576 -m 15 -A "C411Trend (+omarchy plugin)" \
-H "Accept: application/json" -w "\n%{http_code}" \
-K - https://c411.org/api/user/profile-qcomes first so~/.curlrcis ignored: atraceorverboseline there would otherwise print the key.- The
Authorizationheader arrives on stdin as a curl config line (-K -). - Only the last line of curl's stderr is ever shown, stripped of control characters and cut to 200 characters.
| What | Limit |
|---|---|
| Response body | 1 MiB, read while streaming; curl is killed past it or 5 s after the request timeout |
| One request | 15 s (--timeout), never more than the whole-run --deadline (60 s) |
| Shell-side watchdog | 90 s, then the process is stopped |
| Polls | every refreshIntervalMin (30 min default, 5 min minimum), and when the panel opens if the last poll is over 5 minutes old: far under C411's 30 a minute |
| API key file | 4 KiB |
| Bar label | 160 characters |
| What-if question | 64 characters |
| History file | compacted past 2 MiB: every sample of the last 90 days, manual samples, one per 6 h before that; rewritten atomically under an flock |
npm test: model tests (node) and script tests (Python) run against a stub of the documented endpoint. They cover the key on stdin and never in argv,~/.curlrcisolation, key validation and storage (permissions, symlinks, injection attempts), every HTTP status the API documents, oversize and slow answers, invalid numbers and booleans, string sanitising, history permissions and compaction, bar-template and what-if bounds, and that both message catalogues carry the same placeholders.npm run check: the above, then the Omarchy plugin marketplace's Automated Security Baseline on the committed tree, which must reportpassedwith no findings and no capabilities, thenomarchy plugin validate. CI runs the baseline against a pinned marketplace commit.
docs/demo/ is for maintainers and is never run by the plugin. To take
screenshots without real data, photo-session.sh temporarily swaps your
history and bin/c411trend-fetch for a made-up history and a stub that never
contacts c411.org. It restores both, and shell.json, byte for byte on exit,
error or Ctrl-C, and checks the result. Only a SIGKILL can stop that; the
script prints where the originals are kept before it changes anything. It never
touches your API key.
Open an issue, or for anything sensitive contact the author through GitHub (XNinety9) before publishing details.