Commit graph

2 commits

Author SHA1 Message Date
Arena Agent
80c3f578dd ci(cad): gate nigig-build with cargo-deny (Phase 0.4)
Some checks failed
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
Payment domain and storage / isolated-payment-tests (push) Has been cancelled
Blocked since Phase 0 because cargo-deny resolves from the lockfile, and
unblocked once Cargo.lock was committed in 0.2. I predicted this would
surface a large backlog. It did: 5 advisories, 6 licence rejections, 126
git-source errors and 20 unlicensed crates.

The triage is the work here, not the config.

ONE ACTUAL VULNERABILITY. RUSTSEC-2026-0187: unbounded recursion parsing
nested PDF objects in lopdf 0.31, reached via printpdf 0.7. A ~21 KB
crafted file aborts the process with SIGABRT, and because it is a stack
overflow rather than a panic, catch_unwind cannot contain it.

Not exploitable here, and I checked rather than assumed. The advisory is
specifically about Document::load*, the parsing entry points. This crate
never parses a PDF -- arch_pdf.rs only writes them through printpdf's
drawing API, there is no lopdf import anywhere in the workspace, and
there is no PDF read path of any kind. There is also no fix in range:
printpdf 0.7 pins lopdf 0.31 and `cargo update -p lopdf` moves nothing.
Clearing it means printpdf 0.8+, a breaking change across arch_pdf.rs.

So it is ignored with the reasoning written down AND with the condition
that invalidates it: the moment anything in this crate reads a PDF -- an
import feature, a thumbnailer, a preview pane -- this becomes a live DoS
and the ignore must go. An ignore without its expiry condition is how
real vulnerabilities get inherited.

The other four advisories are unmaintained/unsound transitive crates
(proc-macro-error, ttf-parser, atomic-polyfill, glib VariantStrIter),
none with a safe upgrade, all arriving through the GUI/platform stack.
Listed individually rather than disabling the unmaintained class, so a
new one still fails.

Six licence rejections were permissive licences simply absent from the
allow list. MPL-2.0 (option-ext) is weak copyleft, so it is granted to
that one crate rather than added globally -- MPL reciprocity is per-file
and only bites if the crate is vendored and edited.

The 126 git-source errors and 20 unlicensed crates are artifacts, not
findings. Every git dependency is pinned to a full rev, which is stronger
than a version range and already enforced by its own CI step; the
unlicensed crates are first-party and vendored forks with no license
field. Both handled WITHOUT allow-ing the class: sources are governed by
the rev-pinning step, and the 20 crates are clarified by name, so a
genuinely new unlicensed dependency still fails. Blanket-disabling would
have hidden exactly the case worth catching.

Separate file from deny.toml on purpose. Merging them would mean
loosening the payment rules to fit a Makepad + Robius graph, and the
payment policy is the one worth keeping tight. Verified both pay crates
still pass unchanged.

Negative-tested in both directions: removing the lopdf ignore fails the
gate, and removing MIT from the allow list produces 79 rejections. A gate
that cannot fail is worse than no gate.

622 lib + 154 integration, 0 failed.
2026-07-28 19:51:37 +00:00
c6e122c3fc feat(pay): complete Phase 1 and fix M-Pesa store defects B1/B4
Some checks failed
Payment domain and storage / isolated-payment-tests (push) Has been cancelled
Phase 1 of NIGIG_PAY_CONSOLIDATED_REVIEW.md is now closed.

1.1 build governance:
- Declare license = "MIT" on both payment crates. cargo-deny correctly
  reported them as unlicensed, which would block any distribution review.
- Version-pin the nigig-pay-domain path dependency; a bare path dependency
  is a wildcard requirement.

1.2 quality gates:
- Add deny.toml and a CI job running cargo-deny over both payment crates.
  Advisories, bans, licences and sources all pass. The config bans the
  makepad-* crates outright and restricts sources to crates.io.

1.3 canonical ownership (ADR 0002):
- Record the domain/storage/platform/UI layering and its one-way deps.
- The review's A5 "fork farm" table is stale: one copy each of parser.rs,
  store.rs, pending_store.rs and pay_flow_handler.rs, not three.
- Fix B1: store.rs parsed category, sub_category, status and confidence
  from disk then overwrote them with Default::default(), losing every user
  categorisation on reload. Persistence also wrote display names, which are
  not reversible, so this adds stable storage tokens with a legacy-display
  fallback so existing rows still load.
- Fix B4: clean/restore mapped '|' to '~' and reversed every '~', so
  "JOHN~DOE" loaded as "JOHN|DOE". Replaced with bijective backslash
  escaping covering the separator, newlines and carriage returns.
- B6 (f64 money) deliberately deferred to Phase 6: it is a type change that
  ripples into UI consumers.

These were previously recorded as untestable because nigig-core is not a
workspace member. That was wrong: the three files involved need only serde,
chrono, one log! macro and one app_data_dir() helper. The new
tools/test-mpesa-store-clean.sh supplies those shims in a throwaway crate
and runs 9 tests, two of which reproduced the defects before the fix.

1.4 boundary: enforced twice, by a CI manifest/import check and by the
deny.toml ban list.

1.5 shims: three re-exports in nigig-pay/src/lib.rs had zero callers and are
deleted. The remaining four carry a caller count and a named migration
target so they have a deletion plan rather than an open-ended lifetime.

1.6 scope (ADR 0001): accepted that Nigig Pay is a read-only tracker and
launcher, not a payment processor, until an authorised provider integration
exists. This is the decision the review required before further UI work.

SECURITY: a live Cloudflare API token was found committed in README.md,
present since the initial commit and pushed to a public remote. Removed and
recorded as R-SEC-001 in PAYMENT_RISK_REGISTER.md. Redaction does not revoke
it; it remains in history and must be rotated by the owner.

Validated on rustc 1.97.1: domain and storage each pass test --locked, fmt,
clippy -D warnings and cargo-deny; storage also passes --features sqlcipher;
domain benches run; 9 M-Pesa store tests pass. 49 manifests parse.
2026-07-27 15:23:20 +00:00