Adversarial audit of prplOS MR !3019 — Ambiorix v11.44.1 upstep + QoS forward-port
MR: prpl-foundation/prplos/prplos!3019,
"FEAT-664/PCF-2755 ambiorix v11 upstep",
sah-next_v11 → latest-24.10, opened 2026-07-28, state opened at time of audit.
This report is addressed to the prplOS reviewers of !3019 and to the SoftAtHome contributors who authored the change. Its method is deliberately adversarial: every imported commit was read on the assumption that something unwanted could ride along, and every claim below is tied to a number, a file, or a commit URL. Where the evidence does not support a strong statement, the report says so.
Scope & method
MR !3019 repoints two prplOS feed pins in profiles/: feed_amx.yml
main@96c1a3b220cee49d → amx_v11@623ddb685006992a (23 Ambiorix components),
and feed_prplos.yml main@5219cc0498e1ba50 →
dev_forward_port_qos_optimizations_main@7eba715c3a1a84f1 (4 QoS components).
Across those 27 components the two pins resolve to 1,013 non-merge commits and
0 merge commits (manifest.jsonl, manifest-summary.tsv), computed as git rev-list --no-merges <old_sha>..<new_sha> with set semantics
rather than as a linear range: 20 of 27 components are non-fast-forward relative
to their old release tag (inputs/ranges-notes.md §5).
For divergence from the real default branches, see Default-branch coverage of the pinned branch, which compares the proposed pins with current fetched default-branch history.
Two baselines are used (PLAN.md decision 2). Baseline A is the current feed
pin (old_sha), what prplOS ships today, and the floor of the rated commit
universe. Baseline B is the public SoftAtHome upstream tag
(inputs/baseline-b.tsv), used only to mark which commits are prpl_only; the
four QoS components have no public upstream, so prpl_only is not computed for
them (inputs/upstream-notes.md).
Two evidence streams feed this report. Deterministic checks (checks/, C1–C10 plus C10b)
are read-only git and GitLab-API scripts covering patch-id re-authoring, DCO
presence, static test/CI metrics, pin consistency, feed anomalies, description
integrity, CI template pinning, prpl-only provenance, commit-context loss and
default-branch coverage. Per-commit rating
covered all 1,013 commits via a pre-classifier, two independently framed LLM
raters and an Opus 5 adjudication pass (see Rater method & limitations).
Scope limit, agreed up front (PLAN.md decision 5): nothing was built, run, or
measured: no component test suite, no prplOS cram run, and no dynamic memory
data (valgrind, massif, RSS). All test and CI signal is static.
Headline findings
- acl-manager ships a script that deletes persistent ACL configuration on a
transient read error, and the init script still calls it.
6daa7e419901addsscripts/access_check.sh, which runsrm -rf "$DIR"on any/cfg/etc/acl/*/directory whose*.jsoncat fails or that lacks+x;5604dad367c2installs it to/usr/binand calls it fromacl-manager.sh. At the new pin the script is byte-identical andacl-manager.shstill runs it fromboot)andstart), but the makefile install line is gone, dropped incidentally by the pin commit itself (a BAF-autogenerated procd refactor, not a fix), leaving the destructive path one restored makefile line from firing. Neither commit is signed off; both are titled "Issue: NET-7541 missing parameter in the inform message". - mod-amxb-ubus carries three verified defects at the new pin
(
94253172aa4a):amxb_ubus_msg_new()setsmsg->recv_msg = helperthen deleteshelper, leavingrecv_msgdangling (f71cfd1a170c); an oversized non-HTABLE reply is logged and dropped instead of sent, breaking the three-message protocol (fa68fac4cc11); and a use-after-free fix dereferencesamxb_ubus_ctx->dmabove its own guard (a9580943f0b8). - The MR's only heap-memory-optimization commit is one of the 22 BLOCKERs.
libamxd
c860ee03fe93changesparam->flagsandamxd_param_build_description()fromamxc_var_t*toamxc_set_t*in public headers, makesamxd_param_copy()storesource->nameunowned behindshared_name=1, and deletes 10 destroy assertions fromtest_amxd_param_delete. All three still hold at pince4148d5ffe2. - libamxj can hang, and drops shipped public symbols.
c69c3e9f6655loops inamxj_read_json()ignoringamxj_read()'s return whileamxj_read()exits onread() < 0without touchingparse_state: a directory path spins forever, untested and unfixed in range.fefa24f5ca2bremovesamxj_convert(),amxj_writer_set_config()andamxc_var_push_jstring_t()fromamxj_variant.hunder the subject "return written length for amxj_write". - Twenty of the 22 BLOCKERs are still present in the tree this MR would
ship. Two were repaired later in the same range: libamxb
47d6b909735b(fixed by1337f81fb464) and libamxpfa1fa51ad42c, which deletedbool verifyfrom the fully publicstruct _amxp_exprunder a v2.12.1 → v2.13.0 bump beforea694f57ea76crestored it ahead of thesah-v2.13.0tag; so no release shipped that break. Among the 20 that do ship: amxo-cg5daee2209ae6renames-Gjsonto-Gjson-dmwhilegen_json_write_dm()still passes the old"json"key, so-Gjson-dm,<file>dumps to stdout; and amx-clid1f6a2f09a68still leavestest_cli_can_set_output_format_jsonunregistered. - Six malformed-ODL parser tests were deleted and never restored. libamxo
3828c4edd099removestest_parse_list_parse_errors()and its six assertions; they are still absent at pinede9e06f2f03. The body itself states that the new heap-owned strings leak on syntax errors and that no%destructorexists. - Nothing in this MR is hash-pinned. All 27 changed packages switch
PKG_SOURCE_URLfrom a tagged tarball to a.gitclone withPKG_SOURCE_VERSIONset to a branch name, and all 27 addPKG_MIRROR_HASH:=skipwhere none had it before (checks/c5-feed.md). Every build fetches whatever the branch resolves to at fetch time, not the 1,013 commits audited here. Separately, 26 of 27 components include a shared CI template from a floatingmainwith no version pin (25 of 27 at the old sha;checks/c7-ci-template.md). - Attribution was stripped at scale on re-import.
checks/c1-reauthored.tsvrecords 206 patch-id twin pairs across 23 of 27 components: the same patch re-committed under a different identity. 168 changed author, 153 lost aSigned-off-by, and 33 have their new-side body reduced to a bareGitOrigin-RevId:trailer. Across the import, 808 of 1,013 commits carry noSigned-off-bytrailer, 802 of them from one account,sahbot. - Commit context was lost on re-import. 102 twin pairs have default-side prpl Jira keys absent from the pinned-side twin; one is a reverse-direction backport.[^c9-backport] Six pairs replace all prpl references with NET references, and 21 lose their non-trailer descriptions (C9; categories overlap).
- The MR description does not describe the MR. Its top table cites two
hashes that resolve nowhere (
amx_v11@f4904420,dev_forward_port_qos_optimizations_main@81364654; the actual pins are623ddb685006…and7eba715c3a1a…). It says "Three files, two commits" where the diff is 12 files, 4 commits, and "Every MR below must merge before this one" while all 6 dependency MRs are stillopened(checks/c4-pins.tsv). - Seven TR-181 QoS classification parameters disappear from the cram
fixture, undisclosed.
.gitlab/tests/cram/fixtures/obuspa.expectedloses 7 lines and gains none, allDevice.QoS.Classification.{i}.*, and those exact seven trace to four tr181-qos ODL commits (17ed9e44376e,2a8103485299,c0243336c52b,c5b5fa485a59) that drop%persistentparameters as a stated RAM optimisation. The description mentions only theForwardingPolicyencoding change. - 171 default-branch commits have no exact patch-ID twin in the pin: 64 are present verbatim, 36 have a near twin, and 71 remain unresolved by automated checks. Hand review identifies 2 confirmed missing main-side code fixes in amx-cli and amxo-cg, plus libsahtrace release reverts the pin never received, while 10 of the 16 unresolved code rows are present in modified form.
The 22 BLOCKER commits
A BLOCKER is a commit the adjudicator judged must not merge as-is: a verified defect, a removal of functionality or test coverage without a disclosed replacement, or a silent API/ABI break. "At new pin" says whether the defect survives in the tree this MR would ship.
| # | Component | Commit | Subject | Verified defect | At new pin |
|---|---|---|---|---|---|
| 1 | acl-manager | 6daa7e419901 |
Issue: NET-7541 missing parameter in the inform message | scripts/access_check.sh does rm -rf "$DIR" on any /cfg/etc/acl/*/ dir whose *.json cat fails or that lacks +x; subject unrelated |
yes (94ca16a48c13) |
| 2 | acl-manager | 5604dad367c2 |
Issue: NET-7541 missing parameter in the inform message | Installs access_check.sh to /usr/bin and runs it from acl-manager.sh start, activating the rm -rf on real images |
partly; init call yes, install line dropped |
| 3 | amx-cli | d1f6a2f09a68 |
test: Non root user has full data model access (no ACL checking) (HOP-10263) | test/test_cli_args/test_main.c drops cmocka_unit_test(test_cli_can_set_output_format_json) and registers test_cli_can_set_user twice |
yes (7f7f06cac383) |
| 4 | amxo-cg | 5daee2209ae6 |
amxo-cg: Allow parsing odl to get config parameter value (HOP-9269) | Generator key renamed to json-dm but gen_json_write_dm() still passes "json", so -Gjson-dm,<file> loses the filename and dumps to stdout; also renames -Gjson (CLI break) |
yes (8038690031af) |
| 5 | libamxb | 2561970b058e |
amxb_msg: improve USP to legacy amxb_add format conversion | usp_add_compat.c does atoi(strrchr(...) + 1) with no NULL check on a path taken from a peer's AddResp, and writes into the variant's string in place |
yes (215ca9d7) |
| 6 | libamxb | 47d6b909735b |
feat: Add backward compatibility layer for amxb_del operations with USP DELETE | amxb_msg_del_result_converter() discards the result of amxb_msg_usp_error_to_amxd_status(), so amxb_del() returns raw USP 7xxx codes |
no; repaired in range by 1337f81fb464 |
| 7 | libamxc | 9b30ac91dd64 |
amxc_set: Add mutual difference operation (HOP-10075) | Unequal-count branches adjust flag_*->count without adjusting set->count, so amxc_set_get_count(set, NULL) returns a stale total; untested |
yes (e078c08dc998) |
| 8 | libamxc | c68e2e4a2ae9 |
Allow amxc_var_set_path to create lists | AUTO_ADD numeric tokens loop for(i=0;i<=index;i++) on an unbounded strtol index calling amxc_var_get_index() each pass: a.1000000.b costs O(n²) time and n allocations; also changes htable-key semantics |
yes (e078c08dc998) |
| 9 | libamxd | 5dad2f13efc9 |
Add type setters for parameters (wifi) | amxd_param_set_cstring_t() does free(param->value.data.s) then strdup(data): self-assignment is use-after-free, data==NULL is UB, failed strdup still returns amxd_status_ok. 13 new public setters, no tests |
yes (ce4148d5ffe2) |
| 10 | libamxd | c860ee03fe93 |
amxd: heap memory optimization for flags and parameter names | amxd_param_copy() sets shared_name=1 and stores source->name unowned; param->flags and amxd_param_build_description() change amxc_var_t*→amxc_set_t* in public headers; test_amxd_param_delete drops 10 destroy assertions |
yes (ce4148d5ffe2) |
| 11 | libamxj | c69c3e9f6655 |
[AMXJ] Add function to read JSON data from a file to a variant (HOP-12151) | amxj_read_json() ignores amxj_read()'s return and amxj_read() exits on read()<0 without touching parse_state: a directory path spins forever; untested |
yes (47808b29b526) |
| 12 | libamxj | fefa24f5ca2b |
amxj: return written length for amxj_write | amxj_convert(), amxj_writer_set_config() and amxc_var_push_jstring_t() removed from amxj_variant.h; the deleted "Release v1.1.0" CHANGELOG entry never returns |
yes (47808b29b526) |
| 13 | libamxo | 3828c4edd099 |
odl read: odl parser reads incorrect content in large odl files (HOP-9918) | test_parse_list_parse_errors() and six malformed-ODL assertions deleted with no replacement; body admits heap-owned strings leak on syntax errors, no %destructor |
yes (ede9e06f2f03) |
| 14 | libamxp | fa1fa51ad42c |
Issue: HOP-8397 [Amxs] Add API to monitor search path subscriptions on the client side | bool verify deleted from the fully public struct _amxp_expr (callers stack-allocate amxp_expr_t), shipped v2.12.1→v2.13.0; eval semantics also reworked |
no; verify re-added by a694f57ea76c |
| 15 | libamxt | ecde650b576a |
amx-cli: Pseudo terminal not properly closed on exit (HOP-8991) | amxt_tty_open()'s error path calls amxt_tty_close(), but tty_fd is assigned only after six when_failed gates, so close(fd) hits calloc's 0 = stdin; close() never clears O_NONBLOCK, the stated root cause |
yes (76f55e74a274) |
| 16 | libsahtrace | 4bcf7fd756de |
Release libsahtrace version sah-next-4.2_v0.1.1 | src/Makefile unconditionally emits -DCONFIG_SAH_TRACE_COLORS/_SIMPLE/_USER_TRACES_ENABLED/_SYSLOG_CONSOLE=$(var) while sahtrace.c gates on #ifdef, so colours, user traces and syslog LOG_CONS are always on |
yes (74747f6aaffd) |
| 17 | lua-amx | 2bcadfa8916e |
fix: Add unsubscribe method to lua bindings (HOP-11177) | lamx_bus_subscribe() defers amxb_subscription_new and leaves subscription=NULL, but the new lamx_bus_unsubscribe() dereferences amx_sub->subscription->object unguarded |
yes (de53846578bb) |
| 18 | mod-amxb-ubus | a9580943f0b8 |
fix: Disconnect DM signal slots on ubus disconnect to prevent use-after-free | Adds amxd_dm_t* dm = amxb_ubus_ctx->dm; above the existing when_null(amxb_ubus_ctx, exit), dereferencing before the guard; the UAF fix itself is correct |
yes (94253172aa4a) |
| 19 | mod-amxb-ubus | f71cfd1a170c |
feat: implement USP message handling and fragmentation support for ubus backend | amxb_ubus_msg_new() sets msg->recv_msg = helper, takes the item, then amxc_var_delete(&helper); recv_msg dangles and is used by amxb_msg_handle(); original HTABLE variant leaks; malloc unchecked |
yes (94253172aa4a) |
| 20 | mod-amxb-ubus | fa68fac4cc11 |
ubus: cannot read objects under Device. root path (HOP-7321) | ubus_send_reply() moved into the below-threshold branch only; an oversized non-HTABLE ret logs "sending large ubus msg" and is dropped, breaking the 3-message protocol |
yes (94253172aa4a) |
| 21 | mod-ba-cli | e02882f991d9 |
feat: Add Wait For Object support (NET-8097) | mod_pcb_cli_free_wait_for_object_ctx() frees ctx without amxp_slot_disconnect_signal_with_priv, leaving a slot with dangling priv |
yes (eadc720ee6aa) |
| 22 | mod-busstats | 4dc7d66433d4 |
Issue: HOP-5947 Amx backend: RPC to reset a plugin statistics | s_reset_bus_stats() never amxc_array_delete()s the caller-owned array from amxb_list_uris(): a leak per ResetBusStats call; also omits continue after the NULL-ctx log |
yes (a4cd3ea9d0ae) |
Twenty of the 22 carry no Signed-off-by (exceptions: #11, #18), and twenty are
present in the tree the new pins would ship; #2 only partially, since the
init-script call survives but the makefile install line was dropped by the pin
commit itself. The two exceptions, #6 and #14, were repaired later in the same
range. The BLOCKER count is the residue after adjudication, not raw rater output:
raters raised 75 candidates and 53 were downgraded (see below).
Attribution stripping and DCO
checks/c1-reauthored.tsv records 206 patch-id twin pairs across 23 of 27
components: commits sharing an identical git patch-id --stable between the
pre-MR baseline (old_sha ∪ origin/main) and the new branch: the same patch
re-committed under a different identity. Zero twins: acl-manager, libamxtui,
libqosmodule, mod-lua-amx. 168 twins changed author on the new side; 153
had a Signed-off-by on the old side dropped on the new (143 of those also
changed author); 33 have their new-side body reduced to a bare
GitOrigin-RevId: trailer. Original-author domain breakdown: softathome.com 168,
bot@prplfoundation.org 18, other 13, inango-systems.com 6, sagemcom.com 1.
Two concrete pairs (more in checks/c1-notes.md):
- libamxd, patch-id
3e52777c9b7e…(C1's validation pair,checks/c1-notes.md): old16b107d3e56b(a sagemcom.com contributor, signed off) → newcc1b875087e3(sahbot, softathome.com, body =GitOrigin-RevId:only). - mod-ba-cli: old
ff36059fe0a6(a groupe-telnet.net contributor, signed off) → newfd6cf5931852(sahbot).
808 of 1,013 new commits (80%) carry no Signed-off-by
(checks/c2-dco.tsv);¹ 802 of 808 (99%) are one account, sahbot <support.opensource@softathome.com>, the remainder a softathome.com contributor (3),
prplbot@prplfoundation.org (2), a skyworth.com contributor (1). Worst
per-component unsigned rates: libamxb 169/187, libamxd 90/105, libamxp 67/70,
libamxc 63/69, libamxo 59/71, mod-ba-cli 48/55.
¹ manifest.jsonl counts 810 unsigned; 2 commits (c5480ee9, 3c4f4de3) carry
Signed-off-by inside the body rather than as Git trailers, so c2-dco.py's
trailer-only parser excludes them.
The prplOS MR's own 4 commits are all signed off (checks/c2-prplos-mr.md):
softathome.com contributors (a30b48a0, 47ec0ea6, f302925a,
dac8228a). The MR submitter (inputs/mr3019.json) authored none of the four.
This is a provenance fact, not a per-commit verdict: per
adjudicate/ADJUDICATION.md rule 6, no commit was rated SCRUTINIZE or BLOCKER
solely for re-authorship. The point stands independently; the 153 dropped
sign-offs include contributions from at least four organisations outside
SoftAtHome, and a DCO chain terminating at a bot account cannot be audited back
to the original author.
Jira references, trailers and descriptions lost
Across 206 patch-id twin pairs, 102 have at least one prpl Jira key on the default-side commit that is absent everywhere on the pinned-side twin.[^c9-backport] 6 lose all prpl references while acquiring a NET reference, and 1 loses a body or trailer reference that survives in the subject. 158 pairs drop trailer keys; the 178 pairs adding keys mostly add GitOrigin-RevId (168), with Issue (64), References (10), Co-authored-by (1) and Signed-off-by (1) also added, in overlapping sets.
21 pairs lose all non-trailer description lines and 15 retain fewer lines; shrinking is a line-count signal, not proof of semantic loss. This reconciles with the earlier 33 GitOrigin-only bodies: 14 already lacked a description on the old side, while 2 other pairs lose their description without a GitOrigin-only body, so 33 − 14 + 2 = 21. Among distinct keys on the default side, 9 of 67 PPW, 5 of 11 PCF, and 1 of 2 PPM keys survive somewhere in their corresponding twins; no LCM keys occur in scope.
For a groupe-telnet.net contributor's libamxd attributed commit, the sahbot copy drops the sign-off and References: PCF-1062, adds Issue: NET-6836, and keeps the description, rewritten around the NET reference. The attributed commit's author date is 2025-06-12 and the copy's is 2025-05-12 because SoftAtHome merged internally before the public prpl MR merged; authorship, sign-off and Jira reference establish the attributed commit as the original despite that date order.
For a sagemcom.com contributor's libamxd attributed commit, the sahbot copy drops the sign-off, Fixes: PPW-646, and the entire problem/solution description. Only the subject still carries PPW-646; the body is a bare GitOrigin-RevId line.
Of the 206 new-side twins, 172 carry a terminal GitOrigin-RevId reference, but 0 of those revision IDs resolve as local commits or are reachable from the fetched prpl-foundation origin/* histories, so these back-pointers cannot recover the original context from the audited public history (checks/c9b-gitorigin-resolvable.tsv). Git recognizes 169 as proper trailer-block entries; 3 are terminal revision lines attached to prose or malformed blocks, included explicitly in this reference check.
Reproduce with python3 checks/c9-jira-trailers.py, python3 checks/c9b-gitorigin-resolvable.py and python3 checks/followup-integration-checks.py. The per-component and prefix tables are in checks/c9-jira-trailers.md; trailer-key churn depends on its documented parser conventions, while whole-message Jira counts do not. The GitOrigin check uses cached origin refs and git cat-file -e with lazy fetching disabled; it makes no claim about unfetched or private histories.
[^c9-backport]: The 102 includes one direction-inverted pair: libamxb 84751576 on the default branch is a prpl-side backport of 71ca42da, with a cherry-pick footer and a prplfoundation.org sign-off marked [backport]. In this pair the backport added PPM-3259; the stable-side original did not drop it. The 102 is therefore a directional message-comparison count, not uniformly a count of references stripped by re-import. Reproduce the direction check with python3 checks/followup-integration-checks.py.
Pin/supply-chain integrity
All 27 of 27 changed packages switch PKG_SOURCE_URL from a tagged tarball
to a .git clone with PKG_SOURCE_VERSION set to a branch name, and all 27 add
PKG_MIRROR_HASH:=skip where none had it before (checks/c5-feed.md,
inputs/ranges-notes.md §3). Every build now fetches whatever the branch
resolves to at fetch time, not a hash-verified commit: the 1,013 commits audited
here are those branches as of the audit, and a later push to any of the 27
silently changes what prplOS builds with no diff to !3019.
PKG_VERSION anomalies (checks/c5-feed.md): libamxb goes from v4.17.1 to
v4.11.8, numerically lower. A same-run prpl-only commit on mod-amxb-ubus
(b5c8773a788c,
"Set minimum libamxb version to 4.11.8") cites the same figure, so this may not be
a copy/paste error; it is not resolved here. libsahtrace's new PKG_VERSION uses
an underscore (prpl_stable-v11.44.1) where the other 22 use a dash. In Config.in,
tr181-qos renames SAH_AMX_TR181_QOS_ORDER → ..._START_ORDER and adds
..._STOP_ORDER=51, a rename that can silently drop a defconfig fragment
referencing the old name; amxrt's SAH_SERVICES_AMXRT_STOP_ORDER=90 and
libsahtrace's SAH_USER_TRACES_ENABLED=y are additive.
The description's top table cites two hashes that resolve nowhere:
amx_v11@f4904420, dev_forward_port_qos_optimizations_main@81364654 (rc=128 on
git rev-parse --verify, 0 matches in git log --all); the actual pins are
623ddb685006… (subject: "PROVISORY: libamxb: set dummy version") and
7eba715c3a1a… (checks/c6-description.md §1). Its per-component branch-head
table was spot-checked and is accurate. Its claim of "Three files, two
commits" is not: the diff is 12 files, 4 commits: 2 profile YAMLs plus 10
cram-test files, with the 7 per-target 005-build-config.t files each touched by
two of the four (checks/c6-description.md §2).
All 6 dependency MRs' head SHAs match exactly (feed_amx!124, feed-prplos!994,
libqoscommon!6, libqosmodule!5, libqosnode!4, tr181-qos!40), yet all 6
remain opened (checks/c4-pins.tsv) despite the description's "Every MR below
must merge before this one." None of the 23 ambiorix components has its own MR
from prpl/stable-v11.44.1 into main (component_mr_check rows, all no). A
live force-push check found no drift; all 27 branch heads still match
components.tsv, though a tip commit labelled "PROVISORY" signals a standing
amend/force-push risk that has not fired yet (checks/c5-feed.md §2).
Default-branch coverage of the pinned branch
Of 171 default-branch commits with no exact patch-ID twin in the pin, 64 have their patch changes present verbatim (the main-side patch reverse-applies to the pinned tree), 36 have a near twin, and 71 remain unresolved by the automated checks. These are successive checks for content presence, not a count of missing commits. Under those checks, 21 of 23 components moved to prpl/stable-v11.44.1 have unresolved rows; confirmed missing main-side code fixes are in amx-cli and amxo-cg. The four QoS components use dev_forward_port_qos_optimizations; they and libamxtui have no exact-twin misses, and acl-manager's misses are all present verbatim.
| Component | no-exact-twin | present-verbatim | near-twin | unresolved | confirmed-missing-code |
|---|---|---|---|---|---|
| acl-manager | 3 | 3 | 0 | 0 | 0 |
| amx-cli | 12 | 5 | 3 | 4 | 1 |
| amx-fcgi | 6 | 3 | 1 | 2 | 0 |
| amxo-cg | 13 | 1 | 7 | 5 | 1 |
| amxrt | 9 | 4 | 1 | 4 | 0 |
| libamxa | 9 | 3 | 2 | 4 | 0 |
| libamxb | 17 | 6 | 4 | 7 | 0 |
| libamxc | 8 | 5 | 1 | 2 | 0 |
| libamxd | 7 | 2 | 1 | 4 | 0 |
| libamxj | 4 | 1 | 2 | 1 | 0 |
| libamxm | 4 | 1 | 1 | 2 | 0 |
| libamxo | 7 | 4 | 1 | 2 | 0 |
| libamxp | 6 | 4 | 0 | 2 | 0 |
| libamxrt | 7 | 4 | 0 | 3 | 0 |
| libamxs | 7 | 4 | 1 | 2 | 0 |
| libamxt | 5 | 2 | 1 | 2 | 0 |
| libamxtui | 0 | 0 | 0 | 0 | 0 |
| libsahtrace | 11 | 1 | 1 | 9 | 0 |
| lua-amx | 18 | 6 | 4 | 8 | 0 |
| mod-amxb-ubus | 3 | 0 | 1 | 2 | 0 |
| mod-ba-cli | 9 | 3 | 2 | 4 | 0 |
| mod-busstats | 2 | 0 | 1 | 1 | 0 |
| mod-lua-amx | 4 | 2 | 1 | 1 | 0 |
| libqoscommon | 0 | 0 | 0 | 0 | 0 |
| libqosmodule | 0 | 0 | 0 | 0 | 0 |
| libqosnode | 0 | 0 | 0 | 0 | 0 |
| tr181-qos | 0 | 0 | 0 | 0 | 0 |
The unresolved column retains the automated absent bucket so the coverage chain remains reproducible; the last column counts only hand-confirmed missing code fixes. It does not count near twins as proven equivalents or unreviewed rows as missing content.
Hand review of the 16 unresolved code rows finds 10 present in modified form, 3 libsahtrace release reverts the pin never received, and 2 confirmed missing main-side fixes. The remaining 1 row is libsahtrace cfa8e0ad7d, a release change in that same reverted chain: the pin still retains the empty-macro block that release removed, so it is not counted as a third confirmed missing fix. Across the selected hand-review set, 19 of 26 automated absent rows are present in modified form; that set comprises all unresolved code rows plus sampled non-code rows, not a random sample of all unresolved commits.
- amx-cli 93740e7127, a prplfoundation.org contributor's include-guard fix: at the pin, include_priv/amx_cli_ctrl.h tests
_AMX_CLI_CTRL_H__but defines__AMX_CLI_CTRL_H__, so the mismatch remains. - amxo-cg c1ad3f8346, an inango-systems.com contributor's path-mapping fix: at the pin, odl/amxo-cg.odl still maps
'ManagementServer.'toDevice.CWMPManagementServer.rather than main'sDevice.ManagementServer..
The libsahtrace reverts 3d4538bdd2, 0f54024ec2 and 91425b02d3 remove release code on the default branch that the pin still retains. This is a reverted-code divergence in the opposite direction from a missing fix and requires a separate decision; the hand evidence is in notes/followup-verify2.md, Part 2.
The mod-amxb-ubus pair 96ef54b14173 and d30df3cbe9c0 shows why patch IDs overcount absence: a trailing comma survives the whitespace-insensitive comparison, and changed registration context defeats reverse-apply. Yet amxb_ubus_reset_stats and its .reset_stats registration are both present in src/amxb_ubus.c at the pin; the automated result is a subject-based near twin, not missing functionality.
libamxd 07e50d86 is a near twin of pinned c860ee03, which implements the same heap optimization more broadly: 13 files, +187/-203 at the pin versus 10 files, +37/-67 on main. The optimization is present at the pin in a different implementation; the absence of an exact patch-ID twin does not establish a missing optimization (notes/followup-verify2.md, Part 3).
libamxd as the worked example: 27 commits are unique to its default branch and 105 to the pin. The merge-base is d5bad5c7c933 (author date 2025-01-07), and the first commit unique to the stable side is dd72f2f6b776 by sahbot, dated 2025-01-13, six days later. All 23 fetched prpl/stable-v11.44.1 tips still equal the pins recorded in the MR, so the pins had not moved when this audit ran; 9 components also carry a prpl/stable-v12 branch, and the 4 QoS components are pinned on dev_forward_port_qos_optimizations instead. Every component resolves origin/HEAD to main (0 fallbacks) and no default branch has merge commits outside the pin (0 default-only merges).
Why the branch exists: the bot-authored start soon after divergence and the re-authored twins are consistent with a bot-driven mirror/import branch, an inference from history rather than evidence of the maintainers' intent. C4 records no component MR from the stable branch into main, while QoS uses the separate forward-port branch. The branch name does not make it the repository's real default or establish that it preserves all default-branch fixes.
The earlier 0/32 "fast-forward into default" figure describes stable-tip containment: none of those stable tips is reachable from its default branch. This is an ancestry observation, not a content-absence count or a test of whether a default branch can advance to its stable tip; checks/c10b-branch-ancestry.tsv records both directions.
C10 checks exact patch IDs; C10b seeds a temporary index from the pin and tests reverse-apply, then searches for whitespace-insensitive patch or normalized-subject twins. Neither a failed apply nor a near-twin heuristic proves semantic absence or equivalence. Reproduce the automated chain with python3 checks/c10b-near-twins.py and the per-component table, branch ancestry and aggregation of the verifier's recorded hand judgments with python3 checks/c10b-correction.py; full methods are in checks/c10b-near-twins.md.
Prpl-only commits riding along
checks/c8-prpl-only.tsv lists 105 commits on the 23 ambiorix components'
prpl/stable-v11.44.1 branches that are not reachable from each component's
SoftAtHome upstream tag, matching inputs/baseline-b.tsv exactly, 23/23. (The 4
QoS components have no public upstream and are excluded per PLAN.md.) Of the
105: 4 unsigned, 10 touch test paths, 65 touch CI config
(checks/CHECKS.md §C8). Matching subjects against recurring scaffolding patterns
(baf: add initial baf file, ci: restore prpl ci, ci: baf: update autogenerated files and similar) classifies 83 of 105 as CI/BAF scaffolding,
22 as non-scaffolding.
| component | prpl-only commits | unsigned | CI/BAF scaffolding |
|---|---|---|---|
| acl-manager | 4 | 0 | 4 |
| amx-cli | 5 | 0 | 4 |
| amx-fcgi | 5 | 0 | 4 |
| amxo-cg | 4 | 0 | 4 |
| amxrt | 4 | 1 | 3 |
| libamxa | 4 | 0 | 4 |
| libamxb | 5 | 0 | 4 |
| libamxc | 6 | 1 | 3 |
| libamxd | 6 | 0 | 3 |
| libamxj | 4 | 0 | 4 |
| libamxm | 4 | 0 | 4 |
| libamxo | 7 | 0 | 3 |
| libamxp | 2 | 0 | 2 |
| libamxrt | 4 | 0 | 4 |
| libamxs | 3 | 0 | 3 |
| libamxt | 5 | 0 | 4 |
| libamxtui | 2 | 0 | 2 |
| libsahtrace | 6 | 2 | 4 |
| lua-amx | 7 | 0 | 4 |
| mod-amxb-ubus | 6 | 0 | 4 |
| mod-ba-cli | 4 | 0 | 4 |
| mod-busstats | 4 | 0 | 4 |
| mod-lua-amx | 4 | 0 | 4 |
| total | 105 | 4 | 83 |
Notable non-scaffolding commits:
- amxrt
65887941751d"fix: use /proc/$pid/cmdline for process matching", skyworth.com, unsigned, and also the component's new pin. - lua-amx
c5ee5450c1e5"add amx_poll_object lua script", an inango-systems.com contributor, signed off. SCRUTINIZE:log()shells out viaos.execute('logger …'..string.format ('%q', msg)), and Lua%qdoes not escape$or backticks, so the argv text is shell-evaluated, but the argv comes from init scripts, not an attacker. - mod-amxb-ubus
b5c8773a788c"Set minimum libamxb version to 4.11.8" (see above), signed off. - libsahtrace
4bcf7fd756de"Release libsahtrace version sah-next-4.2_v0.1.1", unsigned, and BLOCKER #16.
The picture is not "a feature branch smuggled in behind a version bump": 83 of 105 prpl-only commits are CI and packaging scaffolding, which a fork import is expected to carry.
Test and CI impact per component
prplOS-level test expectations
Of the MR's 12 changed files, 10 are under .gitlab/tests/cram/
(inputs/mr3019-diffs.json). The one file the description names,
046-qos-classification.t, changes as described: ForwardingPolicy goes from
16128 to 250 and the firewall mark from 0x3f00/0x3fc0 to 0x3e80/0x3fc0
(ip rule shows class 0xfa = 250), consistent with "0–255, masked and
shifted." Two files go beyond the description:
fixtures/obuspa.expectedloses 7 lines and adds 0, allDevice.QoS.Classification.{i}.*:DestClientID,DestClientIDExclude,IPLengthExclude,SourceClientID,SourceClientIDExclude,TCPACK,TCPACKExclude(commita30b48a0): seven classification match criteria leave the fixture, not aForwardingPolicyencoding change (checks/c6-description.md§3). Adjudication traced each to a tr181-qos ODL commit dropping the matching%persistentparameter as a stated RAM optimisation:17ed9e44376e(IPLengthExclude),2a8103485299(SourceClientID ×2),c0243336c52b(DestClientID ×2),c5b5fa485a59(TCPACK ×2). At the new pin,src/,include/andtest/reference none of them, so no implemented behaviour is lost; what changes is the exposed TR-181 surface and any persisted values. All four are SCRUTINIZE, not BLOCKER, because each commit body discloses the removal; the MR description does not.ubus-datamodel-list.tloses 2 lines:IPDiagnostics.X_PRPLWARE-COM_DownloadResult.Configand.Process(commit47ec0ea6).IPDiagnosticsis not among any of the 27 components in the MR's own table, and this file is not mentioned in the description.
The remaining 7 files are per-target 005-build-config.t fixtures tracking the
CONFIG_SAH_AMX_TR181_QOS_ORDER → ..._START_ORDER rename plus new
..._STOP_ORDER, CONFIG_SAH_USER_TRACES_ENABLED and
CONFIG_SAH_SERVICES_AMXRT_STOP_ORDER symbols, inconsistently across targets.
Component test content, old pin vs new pin
Static test-content metrics (checks/c3-test-metrics.tsv; cases = cmocka
unit_test + setup_teardown + bare unit_test registrations):
| component | test files (old→new) | cases (old→new) | asserts (old→new) | skips (old→new) | commented-out (old→new) | .gitlab-ci.yml changed |
|---|---|---|---|---|---|---|
| acl-manager | 34→41 | 63→84 | 121→177 | 0→0 | 0→0 | False |
| amx-cli | 55→55 | 359→359 | 231→231 | 0→0 | 0→0 | False |
| amx-fcgi | 75→79 | 366→375 | 208→218 | 0→0 | 0→0 | False |
| amxo-cg | 66→66 | 123→123 | 122→122 | 0→0 | 0→0 | False |
| amxrt | 8→8 | 9→9 | 3→3 | 0→0 | 0→0 | False |
| libamxa | 122→134 | 477→492 | 890→934 | 0→0 | 0→0 | False |
| libamxb | 54→165 | 294→1798 | 962→2719 | 0→20 | 0→0 | False |
| libamxc | 177→183 | 1286→1331 | 5944→6008 | 0→2 | 0→0 | False |
| libamxd | 181→181 | 1108→1132 | 5337→5447 | 0→0 | 0→0 | False |
| libamxj | 23→24 | 102→126 | 294→327 | 0→0 | 0→0 | False |
| libamxm | 9→9 | 55→55 | 213→213 | 0→0 | 0→0 | True |
| libamxo | 170→170 | 537→537 | 994→987 | 0→0 | 0→0 | False |
| libamxp | 84→85 | 785→815 | 2433→2506 | 0→0 | 0→0 | False |
| libamxrt | 51→51 | 312→315 | 362→370 | 0→0 | 1→1 | True |
| libamxs | 31→31 | 144→171 | 773→962 | 0→0 | 0→0 | True |
| libamxt | 37→37 | 261→261 | 792→792 | 0→0 | 0→0 | False |
| libamxtui | 29→29 | 124→124 | 347→347 | 0→0 | 0→0 | False |
| libsahtrace | 0→0 | 0→0 | 0→0 | 0→0 | 0→0 | True |
| lua-amx | 121→121 | 421→424 | 574→576 | 0→0 | 0→0 | False |
| mod-amxb-ubus | 38→70 | 85→106 | 113→139 | 0→0 | 0→0 | True |
| mod-ba-cli | 33→40 | 380→407 | 256→278 | 0→0 | 0→0 | False |
| mod-busstats | 9→9 | 3→3 | 14→14 | 0→0 | 1→1 | False |
| mod-lua-amx | 11→11 | 21→21 | 17→17 | 0→0 | 0→0 | True |
| libqoscommon | 0→0 | 0→0 | 0→0 | 0→0 | 0→0 | True |
| libqosmodule | 13→13 | 18→18 | 58→58 | 0→0 | 0→0 | False |
| libqosnode | 0→0 | 0→0 | 0→0 | 0→0 | 0→0 | True |
| tr181-qos | 77→77 | 473→485 | 1233→1236 | 0→0 | 0→0 | False |
| sum of 27 | 1508→1689 | 7806→9571 | 22291→24681 | 0→22 | 2→2 | 8/27 changed |
In aggregate, test content grows: +181 files, +1,765 cases, +2,390 assertions.
Four components tripped raw threshold rules, all benign on follow-up
(checks/c3-flags.md): libamxa's 3 "deleted" test files are renames into
test/common/; libamxb's skips 0→20 are all in test/amxb_msg_compat/, absent
at the old sha; new coverage shipping partly self-skipped, not skips added to
passing tests; libamxc's 0→2 is the same shape smaller; libamxo's asserts 994→987
trace to two bug fixes reworking assertions.
These aggregates do not contradict findings 5–6: a net-positive assertion count
over 1,013 commits says nothing about the six malformed-ODL cases deleted in
libamxo 3828c4edd099 or the JSON output-format case dropped in amx-cli
d1f6a2f09a68, both targeted removals inside a growing suite. .gitlab-ci.yml
changed for 8 of 27, baf.yml for 15 of 27; Makefile test: targets and
coverage keywords are unchanged everywhere.
CI template pinning (checks/c7-ci-template.md): 25 of 27 components floated
on the unpinned shared template at the old sha, 26 of 27 at the new. Four
changed include source: libsahtrace, libqoscommon and libqosnode moved off a
separate, also-floating SoftAtHome template onto the prpl-foundation one, and
mod-lua-amx went from none to the shared one. No include is version-pinned, so a
change to that template's main changes every component's pipeline at once.
Coverage and JUnit data (inputs/ci-compare.tsv) is effectively absent:
old_coverage and all old test totals are blank or 0 for all 27; new_coverage
is populated for 2 (libqosmodule 100.00%, tr181-qos 90.00%) and new_tests_total
for none. The API data cannot confirm that any of these suites passed at either
pin.
Memory relevance summary
MR !3019 is presented in the context of memory work, so, plainly: of 1,013
commits, 2 are classified memory-optimization and 9 performance: 1.1% of
the import. The two memory commits:
- libamxd
c860ee03fe93"amxd: heap memory optimization for flags and parameter names". BLOCKER (#10 above): public-header type change, unowned name borrow, 10 deleted destroy assertions. - libamxj
e050bc6db129"libamxj: fix extreme memory overallocation when parsing json string". OK.
The 9 performance commits: libamxb 8acf31f194ed (OK) and f5d182e39b6c
(SCRUTINIZE); libamxc 2e880eec321b, 7e4efc003c35 (OK) and c81a575cd8ba
(SCRUTINIZE); libamxd 7e7f6a0f1cc6 (OK); libamxrt 26f27be2c6f4 and
49daeb3e1044 (SCRUTINIZE); tr181-qos a6d4e48a2702 (SCRUTINIZE). Separately,
the four tr181-qos ODL commits behind the seven removed
Device.QoS.Classification.* parameters describe themselves as RAM
optimisations; they are categorised api-change because their diff is a
data-model surface change.
No dynamic measurement was done (PLAN.md decision 5): no valgrind or massif
run, no RSS sampling, no before/after footprint comparison on any target. Nothing
here supports or refutes a claim that the v11.44.1 upstep reduces memory use on a
prplOS device; that needs a separate measured comparison against baseline A. What
this audit can say is that the memory-motivated content of the import is two
commits, one of them a BLOCKER, plus four unmeasured data-model reductions.
Per-component notes
Ordered by BLOCKER count, then SCRUTINIZE. "api" = api-change + abi-change;
"unsigned" = no Signed-off-by trailer; "twins" = patch-id re-authored pairs (C1).
| component | commits | OK | SCR | BLK | api | feat | test | unsigned | twins | notable |
|---|---|---|---|---|---|---|---|---|---|---|
| mod-amxb-ubus | 48 | 39 | 6 | 3 | 2 | 3 | 4 | 38 | 5 | Three BLOCKERs, all live at the new pin: dangling recv_msg, dropped oversized replies, deref-before-guard. Test files 38→70 |
| libamxb | 187 | 114 | 71 | 2 | 20 | 29 | 26 | 170 | 22 | Largest component by far and 71 SCRUTINIZE. New amxb_msg USP compat layer landed incrementally: several intra-range commits do not compile in isolation (4191bd6249f6, cef27c69179b); PKG_VERSION drops v4.17.1→v4.11.8; amxb_subscription_new symbol removed, source compat restored later by a #define |
| libamxd | 105 | 80 | 23 | 2 | 13 | 4 | 7 | 90 | 20 | Both BLOCKERs are public-API commits (5dad2f13efc9 13 untested setters with a UAF; c860ee03fe93 the memory-optimization commit). 13 api/abi commits and 21 touching public headers |
| libamxc | 69 | 56 | 11 | 2 | 4 | 7 | 7 | 63 | 22 | 9b30ac91dd64 stale set->count; c68e2e4a2ae9 unbounded AUTO_ADD index loop. Also c02cd6fe219c (base64 padding sentinel at index 59 instead of 61) and f7edfe1a5ae3 (1-byte OOB read in naturalstrcmp), both SCRUTINIZE |
| libamxj | 27 | 17 | 8 | 2 | 5 | 1 | 0 | 19 | 3 | Smallest component with 2 BLOCKERs, and the worst api-change density (5/27). Zero test-change commits against 5 api-change commits |
| acl-manager | 21 | 11 | 8 | 2 | 1 | 3 | 0 | 12 | 0 | The rm -rf pair. No re-authored twins. Test files 34→41, so coverage grew, but no test accompanies access_check.sh |
| libamxp | 70 | 51 | 18 | 1 | 4 | 4 | 2 | 67 | 24 | Highest twin count (24) and 67/70 unsigned. BLOCK |