Skip to content

plugins: add ZimaOS 0.1.16 - #205

Open
leandrofelix-dev wants to merge 2 commits into
Ryoku-dev:mainfrom
leandrofelix-dev:plugin/zimaos
Open

leandrofelix-dev wants to merge 2 commits into
Ryoku-dev:mainfrom
leandrofelix-dev:plugin/zimaos

Conversation

@leandrofelix-dev

Copy link
Copy Markdown
Contributor

What is this?

ZimaOS, a bar widget: QS Bar card for a ZimaOS server: device name, link rate, storage, and a shortcut to the dashboard.

Checklist

  • One folder under its catalogue with a manifest and a real preview image (not a placeholder).
  • One entry added to that catalogue's registry.json (lastUpdated in YYYY-MM-DD; official: false for community items).
  • Tested live in Ryoku, it renders and behaves correctly (installed with ryoku plugin add).
  • tests/validate-catalogue.sh passes from the repo root.
  • I own or have permission to submit this content, and it is licensed for redistribution.
  • It does not overwrite user configuration or run privileged commands without clear consent.

Opened with ryoku plugin share; the product manifest and the registry entry were generated from the installed plugin.

@neur0map

neur0map commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

🦊 mika's review

Summary

  • Add a ZimaOS bar panel with sign-in and server status.
  • Poll device, network, and storage information and provide server shortcuts.
  • Register the community plugin with configurable host and refresh interval.

Estimated review effort: 3/5 · risk: high

📁 Walkthrough details

plugins/registry.json

  • Registers ZimaOS 0.1.16 as a community top-bar plugin.
    • Adds catalogue metadata, upstream URL, preview, and manifest hash.

plugins/zimaos/manifest.json

  • Defines the plugin's entry points, dependencies, and user settings.
    • Configures the top-bar glyph and panel, host address, and polling interval.

plugins/zimaos/product-manifest.json

  • Lists packaged files, checksums, permissions, and installation destinations.
    • Marks the Python client executable and excludes catalogue assets from installation.

plugins/zimaos/bin/zima

  • Implements ZimaOS authentication, token refresh, and server status requests.
  • Stores host-bound sessions and caches validated device images.
    • Normalizes server addresses, verifies HTTPS certificates, and blocks redirects.
    • Collects device, network, utilization, storage, and backup-link data.

plugins/zimaos/service/Main.qml

  • Coordinates client processes, polling, authentication, and shared panel state.
    • Queues operations and ignores status results from previous hosts.
    • Calculates network rates and exposes server links and storage data.

plugins/zimaos/content/Panel.qml

  • Adds the sign-in form and authenticated ZimaOS device panel.
    • Displays device imagery, network indicators, and storage and application shortcuts.
    • Validates login inputs and clears the password after submission.

plugins/zimaos/content/Widget.qml

  • Adds a themed bar glyph that toggles the ZimaOS panel.
    • Reflects panel and online state in the glyph appearance.

plugins/zimaos/content/I18n.qml

  • Adds Portuguese and Brazilian Portuguese translations with English fallback.
    • Selects translations from the system locale.

plugins/zimaos/README.md

  • Documents installation, panel behavior, network access, and session storage in Portuguese.
    • Describes server endpoints, HTTPS behavior, and external commands.

plugins/zimaos/AGENTS.md

  • Provides plugin authoring rules and the Ryoku validation workflow.
    • Documents host APIs and restrictions on commands, networking, and files.

plugins/zimaos/LICENSE

  • Adds the MIT license for the plugin.
    • Includes copyright and redistribution terms.

🔒 Security

  • 🟠 major · CWE-319 — plugins/zimaos/bin/zima: Security — A bare address defaults to HTTP, so the login password and subsequent access and refresh tokens travel unencrypted. A network observer on the LAN can capture them; require HTTPS by default or obtain explicit informed con…
    • Reachability: A user enters the suggested bare LAN IP and signs in; login and authenticated requests then use HTTP.

Findings

Severity Category Fix effort File Issue
🟠 major correctness medium plugins/zimaos/bin/zima Possible Bug — If an earlier authenticated request refreshes a rotating token and a later request returns 401 with a transient refresh failure, authed_get clears the in-memory tokens and this branch skips saving them. The old refresh…
🟡 minor correctness medium plugins/zimaos/bin/zima Possible Bug — The image cache key contains only the model path, not the server host. Switching between two servers with the same model reuses the first server's image, and a server without an image also inherits the old one; scope the…
🟡 minor correctness low plugins/zimaos/bin/zima Bug — Include s_code in the authentication check. A failed refresh during the storage request currently leaves authed true even though the session has been cleared.
🟡 minor correctness medium plugins/zimaos/content/Panel.qml Possible Bug — The storage card displays only the volume name and never uses storageLine, storageAvail, or storageFraction. After login, users cannot see the advertised free space even when the storage API returns it; render the…
🟡 minor correctness medium plugins/zimaos/content/Panel.qml Possible Bug — The service calculates downText and upText, but the panel never displays them. Consequently the advertised link rates are unavailable after login; add the calculated rates to the network section.
🟡 minor correctness low plugins/zimaos/service/Main.qml Bug — After useHost() sets activeHost, changes to the address in plugin settings are ignored for the rest of the service lifetime. Clear the override when savedHost changes so the configured address takes effect.

⚡ Quick fixes by effort

Low effort (apply from the diff):

  • plugins/zimaos/service/Main.qml — Apply address changes from plugin settings
  • plugins/zimaos/bin/zima — Persist cleared expired session tokens
  • plugins/zimaos/bin/zima — Detect storage request authentication failures
  • plugins/zimaos/bin/zima — Handle app-grid authentication failures
  • plugins/zimaos/service/Main.qml:18 — Bug — After useHost() sets activeHost, changes to the address in plugin settings are ignored for the rest of the service lifetime. Clear the override when savedHost changes so the configured address takes effect.
  • plugins/zimaos/bin/zima:647 — Bug — Include s_code in the authentication check. A failed refresh during the storage request currently leaves authed true even though the session has been cleared.

Medium effort (a function-level change):

  • plugins/zimaos/bin/zima:714 — Possible Bug — If an earlier authenticated request refreshes a rotating token and a later request returns 401 with a transient refresh failure, authed_get clears the in-memory tokens and this branch skips saving them. The old refresh…
  • plugins/zimaos/content/Panel.qml:321 — Possible Bug — The storage card displays only the volume name and never uses storageLine, storageAvail, or storageFraction. After login, users cannot see the advertised free space even when the storage API returns it; render the…
  • plugins/zimaos/content/Panel.qml:264 — Possible Bug — The service calculates downText and upText, but the panel never displays them. Consequently the advertised link rates are unavailable after login; add the calculated rates to the network section.
  • plugins/zimaos/bin/zima:664 — Possible Bug — The image cache key contains only the model path, not the server host. Switching between two servers with the same model reuses the first server's image, and a server without an image also inherits the old one; scope the…
🤖 Prompt for AI agents (click, copy, paste into Claude/Codex)
Fix the following issues found by mika-agent on Ryoku-dev/ryostore#205 (branch plugin/zimaos):

- plugins/zimaos/service/Main.qml:18-23 — Bug (minor severity, low effort): After `useHost()` sets `activeHost`, changes to the address in plugin settings are ignored for the rest of the service lifetime. Clear the override when `savedHost` changes so the configured address takes effect.
  suggested fix:

readonly property string savedHost: {
var raw = settings && settings.host !== undefined && settings.host !== null
? ("" + settings.host).trim() : "";
return raw;
}
onSavedHostChanged: svc.activeHost = ""
readonly property string host: svc.activeHost.length > 0 ? svc.activeHost : svc.savedHost

- plugins/zimaos/bin/zima:97-101 — Security (major severity, medium effort): A bare address defaults to HTTP, so the login password and subsequent access and refresh tokens travel unencrypted. A network observer on the LAN can capture them; require HTTPS by default or obtain explicit informed con…
- plugins/zimaos/bin/zima:714-718 — Possible Bug (major severity, medium effort): If an earlier authenticated request refreshes a rotating token and a later request returns 401 with a transient refresh failure, `authed_get` clears the in-memory tokens and this branch skips saving them. The old refresh…
- plugins/zimaos/content/Panel.qml:321-335 — Possible Bug (minor severity, medium effort): The storage card displays only the volume name and never uses `storageLine`, `storageAvail`, or `storageFraction`. After login, users cannot see the advertised free space even when the storage API returns it; render the…
- plugins/zimaos/content/Panel.qml:264-279 — Possible Bug (minor severity, medium effort): The service calculates `downText` and `upText`, but the panel never displays them. Consequently the advertised link rates are unavailable after login; add the calculated rates to the network section.
- plugins/zimaos/bin/zima:664-670 — Possible Bug (minor severity, medium effort): The image cache key contains only the model path, not the server host. Switching between two servers with the same model reuses the first server's image, and a server without an image also inherits the old one; scope the…
- plugins/zimaos/bin/zima:647-653 — Bug (minor severity, low effort): Include `s_code` in the authentication check. A failed refresh during the storage request currently leaves `authed` true even though the session has been cleared.
suggested fix:

if u_code == 401 or n_code == 401 or c_code == 401 or s_code == 401:
result["authError"] = "session expired"
if session.get("access_token"):
save_session(path, session)
sys.stdout.write(json.dumps(result, ensure_ascii=False) + "\n")
return
result["authed"] = True


For each: verify it is real in the current code, apply the smallest correct fix, and run the repo's tests. Skip anything already resolved and say why.

🧪 No relevant tests detected in the diff.

🚦 Merge recommendation: changes_required


🦊 Generated by Mika — the Ryoku org's self-hosted AI code reviewer. Machine findings: verify before merging.

@neur0map neur0map left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changes requested — 0 critical and 4 major issue(s): see the inline comments and the walkthrough for fixes and an agent-ready prompt.

Comment thread plugins/zimaos/bin/zima
Comment on lines +85 to +97
def request_bytes(host, method, path, token=None, body=None, timeout=TIMEOUT):
url = "http://" + host + path
data = None
headers = {"Accept": "*/*"}
if body is not None:
data = json.dumps(body).encode("utf-8")
headers["Content-Type"] = "application/json"
if token:
headers["Authorization"] = token
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as res:
return res.status, res.read()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Security · fix: low effort

Disable redirects for API requests. urllib forwards the Authorization header across redirects, so a server or network attacker can redirect an authenticated request to another host and steal the access token.

All requests use plaintext HTTP, including the login password and authorization tokens. Anyone able to observe or modify traffic between the client and server can steal credentials or sessions; support verified HTTPS and require it for authenticated requests.

urlopen follows redirects, including cross-host redirects, with the Authorization header attached. A compromised server or network attacker can redirect an authenticated GET to an attacker-controlled host and receive the access token; reject redirects for API requests or validate their destination and strip credentials.

Suggested change

Suggested change
def request_bytes(host, method, path, token=None, body=None, timeout=TIMEOUT):
url = "http://" + host + path
data = None
headers = {"Accept": "*/*"}
if body is not None:
data = json.dumps(body).encode("utf-8")
headers["Content-Type"] = "application/json"
if token:
headers["Authorization"] = token
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as res:
return res.status, res.read()
def request_bytes(host, method, path, token=None, body=None, timeout=TIMEOUT):
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
url = "http://" + host + path
data = None
headers = {"Accept": "*/*"}
if body is not None:
data = json.dumps(body).encode("utf-8")
headers["Content-Type"] = "application/json"
if token:
headers["Authorization"] = token
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.build_opener(NoRedirect()).open(req, timeout=timeout) as res:
return res.status, res.read()

Comment thread plugins/zimaos/bin/zima Outdated
Comment on lines +155 to +160
def save_session(path, data):
tmp = path + ".tmp"
with open(tmp, "w", encoding="utf-8") as handle:
json.dump(data, handle, ensure_ascii=False)
handle.write("\n")
os.chmod(tmp, 0o600)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Security · fix: low effort

The temporary session file is created using the process umask and only restricted after the tokens are written. If the existing state directory is traversable and the umask permits it, another local user can read the tokens during that window; create the file with mode 0600 before writing.

CWE: CWE-732
Reachability: A local user can traverse an existing plugin state directory while a login or status refresh writes session.json.tmp under a permissive umask.

Comment thread plugins/zimaos/bin/zima Outdated
Comment on lines +485 to +501
session = load_session(path)
result = {
"ok": True,
"authed": False,
"authError": "",
"username": session.get("username") or "",
"dashboardUrl": "http://" + host + "/",
"filesUrl": "http://" + host + "/modules/icewhale_files/",
"peerDropUrl": "http://" + host + "/modules/icewhale_peerdrop/",
"backupUrl": "",
}
result.update(device)
result["deviceImage"] = ensure_device_image(host, payload, path)
if not session.get("access_token") and not session.get("refresh_token"):
sys.stdout.write(json.dumps(result, ensure_ascii=False) + "\n")
return
u_code, u_payload = authed_get(host, "/v1/sys/utilization", session)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Security · fix: medium effort

The session is not associated with its server, so changing the configured host causes the next status request to send the previous server's access token to the new host. Bind sessions to the normalized host and discard or isolate them when it changes.

CWE: CWE-522
Reachability: A signed-in user changes the host setting to another server; refresh() invokes status, which loads the shared session and sends its token to that server.

Comment on lines +282 to +299
Card {
id: diskCard
visible: root.service && root.service.authed
width: parent.width
implicitHeight: 104 * root.s
color: diskMa.containsMouse ? root.fillHover : root.fillIdle
border.color: diskMa.containsMouse ? root.seal : root.sep

Text {
anchors.left: parent.left
anchors.top: parent.top
anchors.right: folderMark.left
anchors.leftMargin: 16 * root.s
anchors.topMargin: 14 * root.s
anchors.rightMargin: 8 * root.s
text: i18n.tr("Open") + " " + (root.service && root.service.storageTitle.length > 0
? root.service.storageTitle
: i18n.tr("storage"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

The panel never displays downText, upText, or storageLine, despite fetching those values and advertising link rates and free storage. Add these values to the authenticated card so users can see the promised measurements.

Comment thread plugins/zimaos/product-manifest.json Outdated
Comment on lines +57 to +71
"source": "assets/zima-mark.png",
"destination": "assets/zima-mark.png",
"sha256": "e67752d7533cee5453c398ca5aaf19ee25e17c4b36f5896904d771623997cd70",
"mode": "0644",
"size": 6014,
"install": false
},
{
"source": "bin/__pycache__/zimacpython-314.pyc",
"destination": "bin/__pycache__/zimacpython-314.pyc",
"sha256": "f9fe0e66d22e1ae6899bcea7437ab16a35d82a94f54f59f2fa02801e82d2d027",
"mode": "0644",
"size": 32652,
"install": true
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Data Integrity · fix: low effort

The product manifest requires bin/__pycache__/zimacpython-314.pyc, but that file is absent from the PR and ignored by .gitignore. Catalogue validation or installation cannot verify and fetch the listed file; remove the cache entry and regenerate the manifest and registry checksum.

assets/zima-mark.png is marked install: false, although Widget.qml loads it for the bar glyph. Install the asset so catalogue installations do not render a blank widget.

Comment thread plugins/zimaos/service/Main.qml Outdated
Comment on lines +184 to +191
function refresh() {
if (!svc.hostReady || svc.binPath === "" || svc.stateFile === "")
return;
if (proc.running && proc.opKind === "status")
return;
for (var i = 0; i < svc._queue.length; i++) {
if (svc._queue[i].kind === "status")
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

useHost() triggers onHostChanged, which can start a status request before login() queues the login. If that status request is still running when login succeeds, refresh() skips the post-login status, leaving the sign-in form visible until the next timer tick.

Comment thread plugins/zimaos/service/Main.qml Outdated
Comment on lines +265 to +267
svc.deviceName = parsed.deviceName ? ("" + parsed.deviceName) : svc.deviceName;
svc.osVersion = parsed.osVersion ? ("" + parsed.osVersion) : "";
svc.deviceModel = parsed.deviceModel ? ("" + parsed.deviceModel) : "";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion (bug)

Clear stale device names

Suggested change

Suggested change
svc.deviceName = parsed.deviceName ? ("" + parsed.deviceName) : svc.deviceName;
svc.osVersion = parsed.osVersion ? ("" + parsed.osVersion) : "";
svc.deviceModel = parsed.deviceModel ? ("" + parsed.deviceModel) : "";
svc.deviceName = parsed.deviceName ? ("" + parsed.deviceName) : "";
svc.osVersion = parsed.osVersion ? ("" + parsed.osVersion) : "";
svc.deviceModel = parsed.deviceModel ? ("" + parsed.deviceModel) : "";

To apply: click the sparkletip, or ask @mika-agent ask <question>.

Comment on lines +271 to +273
svc.filesUrl = parsed.filesUrl ? ("" + parsed.filesUrl) : "";
svc.backupUrl = parsed.backupUrl ? ("" + parsed.backupUrl) : "";
svc.peerDropUrl = parsed.peerDropUrl ? ("" + parsed.peerDropUrl) : "";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

Clear deviceName when the response omits it. Retaining the previous name can identify a newly selected host as the old device.

Suggested change

Suggested change
svc.filesUrl = parsed.filesUrl ? ("" + parsed.filesUrl) : "";
svc.backupUrl = parsed.backupUrl ? ("" + parsed.backupUrl) : "";
svc.peerDropUrl = parsed.peerDropUrl ? ("" + parsed.peerDropUrl) : "";
svc.deviceName = parsed.deviceName ? ("" + parsed.deviceName) : "";
svc.osVersion = parsed.osVersion ? ("" + parsed.osVersion) : "";
svc.deviceModel = parsed.deviceModel ? ("" + parsed.deviceModel) : "";

Comment on lines +297 to +302
if (disk) {
svc.storageName = disk.name ? ("" + disk.name) : "";
svc.storageTotal = svc.num(disk.total);
svc.storageUsed = svc.num(disk.used);
svc.storageAvail = svc.num(disk.avail);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion (bug)

Clear stale storage when unavailable

Suggested change

Suggested change
if (disk) {
svc.storageName = disk.name ? ("" + disk.name) : "";
svc.storageTotal = svc.num(disk.total);
svc.storageUsed = svc.num(disk.used);
svc.storageAvail = svc.num(disk.avail);
}
if (disk) {
svc.storageName = disk.name ? ("" + disk.name) : "";
svc.storageTotal = svc.num(disk.total);
svc.storageUsed = svc.num(disk.used);
svc.storageAvail = svc.num(disk.avail);
} else {
svc.storageName = "";
svc.storageTotal = -1;
svc.storageUsed = -1;
svc.storageAvail = -1;
}

To apply: click the sparkletip, or ask @mika-agent ask <question>.

Comment on lines +305 to +310
function applyResult(kind, text) {
var parsed = null;
try {
parsed = JSON.parse((text || "").trim());
} catch (e) {
parsed = { ok: false, error: "bad response" };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

Clear storage fields when a successful authenticated response contains no storage. Otherwise, a removed pool or a host without storage continues to show the previous pool's capacity.

Suggested change

Suggested change
function applyResult(kind, text) {
var parsed = null;
try {
parsed = JSON.parse((text || "").trim());
} catch (e) {
parsed = { ok: false, error: "bad response" };
if (disk) {
svc.storageName = disk.name ? ("" + disk.name) : "";
svc.storageTotal = svc.num(disk.total);
svc.storageUsed = svc.num(disk.used);
svc.storageAvail = svc.num(disk.avail);
} else {
svc.storageName = "";
svc.storageTotal = -1;
svc.storageUsed = -1;
svc.storageAvail = -1;
}

@leandrofelix-dev leandrofelix-dev changed the title plugins: add ZimaOS 0.1.12 plugins: add ZimaOS 0.1.15 Nov 9, 2026

@neur0map neur0map left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changes requested — 0 critical and 2 major issue(s): see the inline comments and the walkthrough for fixes and an agent-ready prompt.

Comment thread plugins/zimaos/bin/zima
Comment on lines +689 to +691
g_code, g_payload = authed_get(host, "/v2/app_management/web/appgrid", session)
if g_code == 200:
result["backupUrl"] = find_backup(g_payload, host)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Possible Bug · fix: low effort

A token refreshed while fetching the app grid is never saved because the session is persisted before that request. If the server rotates refresh tokens and the access token expires at this point, the next poll uses the old tokens and can force the user to sign in again; persist the session after the final authenticated request.

Comment thread plugins/zimaos/product-manifest.json Outdated
Comment on lines +33 to +38
"source": "LICENSE",
"destination": "LICENSE",
"sha256": "97483adbcfff65229fdf21ea76178690f1bfe3ccd5fbde97e344e04d625dc4bd",
"mode": "0644",
"size": 1062,
"install": false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Data Integrity · fix: low effort

The MIT license requires its copyright and permission notice to accompany copies or substantial portions of the software, but the product manifest excludes LICENSE from installation. Catalogue installations therefore distribute the plugin without that notice; install the license alongside the plugin.

Comment on lines +266 to +269
var n = Number(value);
return isFinite(n) ? n : -1;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

Number(null) returns zero, so missing metrics reported as null appear as real zero values. Treat null and undefined as unavailable before converting.

Suggested change

Suggested change
var n = Number(value);
return isFinite(n) ? n : -1;
}
function num(value) {
if (value === null || value === undefined)
return -1;
var n = Number(value);
return isFinite(n) ? n : -1;
}

Comment on lines +394 to +406
onHostChanged: {
svc._prevStamp = -1;
svc.downBps = 0;
svc.upBps = 0;
if (!svc.hostReady) {
svc.online = false;
svc.authed = false;
svc.deviceName = "";
svc.deviceImage = "";
svc.errorText = "";
return;
}
svc.refresh();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

Changing the host while a status process is running drops the new refresh because refresh() returns for the old status operation. Its result is then applied without checking its host, so the panel can show the old server and links until the next poll, or longer if the new server is unreachable; discard stale results and queue a refresh for the new host.

@neur0map neur0map left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changes requested — 0 critical and 1 major issue(s): see the inline comments and the walkthrough for fixes and an agent-ready prompt.

Comment thread plugins/zimaos/bin/zima
Comment on lines +39 to +42
def normalize_host(raw):
text = (raw or "").strip()
# A typed LAN host has no scheme and stays http. https:// is opt-in and
# keeps certificate verification. Anything else is rejected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Security · fix: medium effort

Bare addresses default to HTTP, and the login request sends the password over that connection; subsequent requests also expose access and refresh tokens. A network observer on the LAN can capture these credentials. Require HTTPS for authentication, or make plaintext access an explicit, prominently warned opt-in.

CWE: CWE-319
Reachability: A user enters a bare IP address or hostname in the login panel and signs in while an attacker can observe the network.

Comment thread plugins/zimaos/bin/zima
Comment on lines +714 to +717
if u_code == 401 or n_code == 401 or c_code == 401:
result["authError"] = "session expired"
if session.get("access_token"):
save_session(path, session)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: low effort

If token refresh fails, authed_get clears both tokens, but this branch only saves the session when an access token remains. The expired tokens therefore survive on disk and are retried on every poll, repeatedly attempting refresh instead of persisting the expired session. Save the cleared session when authentication expires.

Suggested change

Suggested change
if u_code == 401 or n_code == 401 or c_code == 401:
result["authError"] = "session expired"
if session.get("access_token"):
save_session(path, session)
save_session(path, session)

Comment on lines +8 to +16
readonly property string lang: {
var n = Qt.locale().name;
if (i18n.catalogs[n])
return n;
var base = n.split("_")[0];
return i18n.catalogs[base] ? base : "en";
}

readonly property var catalogs: ({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

This private catalog only translates Portuguese and falls back to English for every other locale, bypassing the shell's I18n catalog. Users of other supported shell languages will see the new panel in English. Route these strings through the shared catalog and add the required source and English keys.

Comment on lines +17 to +23
property string activeHost: ""
readonly property string savedHost: {
var raw = settings && settings.host !== undefined && settings.host !== null
? ("" + settings.host).trim() : "";
return raw;
}
readonly property string host: svc.activeHost.length > 0 ? svc.activeHost : svc.savedHost

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

After a login from the panel, activeHost remains set, so later changes to the host setting are ignored by the host binding. The widget continues polling the old server until the service is restarted or another login changes activeHost. Keep the active host synchronized with settings changes.

@leandrofelix-dev leandrofelix-dev changed the title plugins: add ZimaOS 0.1.15 plugins: add ZimaOS 0.1.16 Nov 9, 2026
The catalogue screen requires an https project URL, and plugin share does not copy one into the registry entry.

@neur0map neur0map left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changes requested — 0 critical and 2 major issue(s): see the inline comments and the walkthrough for fixes and an agent-ready prompt.

Comment thread plugins/zimaos/bin/zima
Comment on lines +97 to +101
if host.startswith("https://"):
return "https", host[len("https://") :]
if host.startswith("http://"):
return "http", host[len("http://") :]
return "http", host

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Security · fix: medium effort

A bare address defaults to HTTP, so the login password and subsequent access and refresh tokens travel unencrypted. A network observer on the LAN can capture them; require HTTPS by default or obtain explicit informed consent before using HTTP.

CWE: CWE-319
Reachability: A user enters the suggested bare LAN IP and signs in; login and authenticated requests then use HTTP.

Comment thread plugins/zimaos/bin/zima
Comment on lines +647 to +653
if not isinstance(raw, str):
return ""
parsed = urllib.parse.urlparse(raw)
if parsed.scheme or parsed.netloc or parsed.path != "/v2/zimaos/device/image":
return ""
query = urllib.parse.parse_qs(parsed.query, keep_blank_values=False)
model = (query.get("model") or [""])[0]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

Include s_code in the authentication check. A failed refresh during the storage request currently leaves authed true even though the session has been cleared.

Suggested change

Suggested change
if not isinstance(raw, str):
return ""
parsed = urllib.parse.urlparse(raw)
if parsed.scheme or parsed.netloc or parsed.path != "/v2/zimaos/device/image":
return ""
query = urllib.parse.parse_qs(parsed.query, keep_blank_values=False)
model = (query.get("model") or [""])[0]
if u_code == 401 or n_code == 401 or c_code == 401 or s_code == 401:
result["authError"] = "session expired"
if session.get("access_token"):
save_session(path, session)
sys.stdout.write(json.dumps(result, ensure_ascii=False) + "\n")
return
result["authed"] = True

Comment thread plugins/zimaos/bin/zima
Comment on lines +664 to +670
if not rel:
return dest if os.path.isfile(dest) else ""
try:
if os.path.isfile(dest) and os.path.getsize(dest) > 32:
with open(stamp, "r", encoding="utf-8") as handle:
if handle.read().strip() == rel:
return dest

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

The image cache key contains only the model path, not the server host. Switching between two servers with the same model reuses the first server's image, and a server without an image also inherits the old one; scope the cache to the host and model.

Handle authentication failure on the final app-grid request as well. Otherwise, a failed token refresh there reports an authenticated session and leaves expired tokens on disk.

Comment on lines +264 to +279
Item {
width: parent.width
implicitHeight: Math.max(ipText.implicitHeight, dashBtn.height)

Text {
id: ipText
anchors.left: parent.left
anchors.right: dashBtn.left
anchors.rightMargin: 12 * root.s
anchors.verticalCenter: parent.verticalCenter
text: root.service && root.service.ipAddress.length > 0 ? root.service.ipAddress : ""
color: root.sumi
font.family: Theme.mono
font.pixelSize: 12 * root.s
elide: Text.ElideRight
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

The service calculates downText and upText, but the panel never displays them. Consequently the advertised link rates are unavailable after login; add the calculated rates to the network section.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants