From 4989941642eea8f8f70e2957886c97dd103d9bf5 Mon Sep 17 00:00:00 2001
From: Humza <204067870+SirHumza@users.noreply.github.com>
Date: Fri, 25 Sep 2026 18:29:54 +0200
Subject: [PATCH] orbisRPC v1.0.0: signed-only updates, heap fix, plugin name
fallback, dead code purge, production release key, secret guard, docs
accuracy
---
.github/workflows/ci.yml | 2 +
.gitignore | 1 +
README.md | 20 +++++----
docs/TROUBLESHOOTING.md | 8 ++--
docs/injecting.md | 10 +----
installer/Makefile | 4 +-
installer/installer.c | 4 +-
installer/ui.c | 33 --------------
installer/ui.h | 4 --
orbisrpc/daemon.c | 2 +
orbisrpc/detect.c | 2 +-
orbisrpc/release_pubkey.h | 20 ++++-----
orbisrpc/tmdb.c | 17 ++++---
orbisrpc/updater.c | 93 +++++----------------------------------
tests/Makefile | 2 +-
tests/e2e_consumer.py | 22 +++++++++
tests/test_utils.c | 6 ---
17 files changed, 79 insertions(+), 171 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9bf79f1..cd68a20 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,6 +11,8 @@ jobs:
run: make -C tests asan
- name: e2e contracts
run: python3 tests/e2e_consumer.py
+ env:
+ ORBISRPC_STRICT_SECRETS: "1"
sdk-payload:
runs-on: ubuntu-latest
steps:
diff --git a/.gitignore b/.gitignore
index 32d24b0..679a8c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ installer/pkg.gp4
installer/assets/daemon.elf
installer/assets/evict.elf
installer/sce_sys/param.sfo
+tests/test_utils_asan
diff --git a/README.md b/README.md
index 36edd74..fca3139 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
@@ -20,22 +22,24 @@ playing to Discord: name, cover art, timer. No PC at runtime.
= fnlen && !memcmp(nl, filename, fnlen) &&
- (nl[fnlen]=='\n'||nl[fnlen]=='\r'||nl[fnlen]==' '||nl[fnlen]=='\t'||nl[fnlen]==0||nl[fnlen]=='*')){
- for(int i=0;i<64;i++){
- char c=hex[i];
- want_hex[i]=(c>='A'&&c<='F')?(char)(c-'A'+'a'):c;
- }
- want_hex[64]=0;
- return 0;
- }
- }
- p = e ? e+1 : p+linelen;
- }
- return -1;
-}
-
/* Download a release asset by exact name. Returns heap body or NULL. */
static char *fetch_asset(const jl_val_t *assets, const char *want_name,
size_t cap, int *status, size_t *out_len){
@@ -428,12 +377,14 @@ int updater_check_and_stage(void){
free(sbody);
if(!have_manifest){ free(mbody); mbody = NULL; }
}
- /* Compat fallback: SHA256SUMS (unsigned but hash-pinned). */
- char *sums = NULL;
+ /* Refuse unsigned updates outright. SHA256SUMS comes from the
+ * same release as the binaries, so it pins nothing against
+ * release-asset compromise โ exactly what the signed manifest
+ * defends against (ORX-UPDATE-002). */
if(!have_manifest){
- sums = fetch_asset(assets, "SHA256SUMS", 65536, &status, NULL);
- if(!sums)
- log_msg("updater: WARN ORX-UPDATE-002: no manifest and no SHA256SUMS; refusing unsigned update");
+ log_msg("updater: no valid signed manifest; refusing update (ORX-UPDATE-002)");
+ jl_free(r);
+ return 0;
}
/* Two-phase commit: download + verify EVERY asset first, then
* activate all at once. A failure anywhere stages nothing, so
@@ -468,31 +419,8 @@ int updater_check_and_stage(void){
pend_fail = 1;
break;
}
- if(have_manifest){
- if(manifest_check(&mf, nm->str, (unsigned char*)bin, al) != 0){
- log_msg("updater: asset %s not in manifest or hash mismatch; refusing (ORX-UPDATE-002)", nm->str);
- free(bin);
- pend_fail = 1;
- break;
- }
- } else if(sums){
- char want[65];
- if(sums_lookup(sums, nm->str, want) != 0){
- log_msg("updater: asset %s not in SHA256SUMS; refusing", nm->str);
- free(bin);
- pend_fail = 1;
- break;
- }
- char got[65];
- if(sha256_hex((unsigned char*)bin, al, got) != 0 || strcmp(got, want) != 0){
- log_msg("updater: asset %s hash mismatch; refusing", nm->str);
- free(bin);
- pend_fail = 1;
- break;
- }
- } else {
- /* No manifest and no SHA256SUMS: refuse unsigned bytes. */
- log_msg("updater: asset %s refused: no trust anchor (ORX-UPDATE-002)", nm->str);
+ if(manifest_check(&mf, nm->str, (unsigned char*)bin, al) != 0){
+ log_msg("updater: asset %s not in manifest or hash mismatch; refusing (ORX-UPDATE-002)", nm->str);
free(bin);
pend_fail = 1;
break;
@@ -519,7 +447,6 @@ int updater_check_and_stage(void){
log_msg("updater: incomplete set; staged nothing (versions stay matched)");
}
for(int pi = 0; pi < pend_n; pi++) free(pend[pi].bin);
- free(sums);
free(mbody);
}
}
diff --git a/tests/Makefile b/tests/Makefile
index 4bdb7e4..f8732d6 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -9,7 +9,7 @@ MBEDTLS_DIR := ../third_party/mbedtls/library
MBEDTLS_SRCS := $(filter-out $(MBEDTLS_DIR)/net_sockets.c $(MBEDTLS_DIR)/timing.c $(MBEDTLS_DIR)/entropy_poll.c,$(wildcard $(MBEDTLS_DIR)/*.c))
ORBIS_SRCS := ../orbisrpc/jsonlite.c ../orbisrpc/b64.c ../orbisrpc/sfo.c ../orbisrpc/tmdb_crypto.c ../orbisrpc/updater_util.c ../orbisrpc/art.c ../orbisrpc/log.c ../orbisrpc/health.c ../orbisrpc/manifest.c ../orbisrpc/compat.c ../orbisrpc/cfg.c ../orbisrpc/appdb.c ../orbisrpc/discord.c
-INST_SRCS := ../installer/icfg.c ../installer/nettest.c ../installer/send.c
+INST_SRCS := ../installer/icfg.c
# SQLite amalgamation: -O0 for host iteration speed (payload uses -O2).
SQLITE_DIR := ../third_party/sqlite/sqlite-amalgamation-3510100
SQLITE_FLAGS := -O0 -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -I$(SQLITE_DIR)
diff --git a/tests/e2e_consumer.py b/tests/e2e_consumer.py
index 1d3db17..86ce0e0 100755
--- a/tests/e2e_consumer.py
+++ b/tests/e2e_consumer.py
@@ -34,6 +34,28 @@ check("host/unit-tests", r.returncode == 0 and "utility tests passed" in r.stdou
cfg = src("config/config.json")
check("install/template-has-no-token", "SET_ME" in cfg)
check("install/art-pack-default", "orbisrpc-host" in cfg)
+# 2b. no Discord-session-shaped token anywhere the PKG or repo ships.
+# Fatal when ORBISRPC_STRICT_SECRETS=1 (CI); a local working copy may hold
+# a dev token and gets a warning instead.
+tok_re = re.compile(rb"MT[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{20,}")
+leaks = []
+for rel in ("installer/installer.c", "installer/config.json",
+ "installer/assets/config.json", "config/config.json"):
+ try:
+ with open(os.path.join(ROOT, rel), "rb") as fh:
+ if tok_re.search(fh.read()):
+ leaks.append(rel)
+ except OSError:
+ pass
+if leaks:
+ _msg = "token-shaped string in " + ", ".join(leaks)
+ if os.environ.get("ORBISRPC_STRICT_SECRETS") == "1":
+ check("secrets/no-token-in-installer", False, _msg)
+ else:
+ print("WARN secrets/no-token-in-installer (set ORBISRPC_STRICT_SECRETS=1 "
+ "to fail) - " + _msg)
+else:
+ check("secrets/no-token-in-installer", True)
# 3. single-instance lock with recycled-PID guard
lock = src("orbisrpc/lock.c")
check("runtime/single-lock", "lock_acquire" in src("orbisrpc/daemon.c"))
diff --git a/tests/test_utils.c b/tests/test_utils.c
index b0f5771..a63722f 100644
--- a/tests/test_utils.c
+++ b/tests/test_utils.c
@@ -11,7 +11,6 @@
#include "../orbisrpc/discord.h"
#include "../orbisrpc/detect.h"
#include "../installer/icfg.h"
-#include "../installer/nettest.h"
#include "sqlite3.h"
#include