Commit graph

7 commits

Author SHA1 Message Date
Arena Agent
80425bbfb8 fix: repair the makepad fork and unblock the build
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
nigig-map / test (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
The repository has been uncompilable since the d6d1f99c fork bump. Root
cause was in gitdab.com/andodeki/makepad, not here, so the fix went there
first: commit 5eda8056 on portallist_flow_adaptive_view.

TWO defects, both introduced by the "Update fork to upstream dev 5d4483f"
merge, both pure losses rather than intentional changes:

1. widgets/Cargo.toml: the makepad-gltf / makepad-csg / makepad-test
   dependency lines were relocated from [dependencies] to below
   [features]. Cargo then parses each as a feature whose value should be
   an array, giving "invalid type: map, expected a sequence", and the
   gltf/csg/test/maps features cease to exist.

2. widgets/src/lib.rs: the feature-gated re-export block for those same
   crates (plus makepad_fast_inflate and makepad_mbtile_reader) was
   deleted outright. Fixing only the manifest surfaced this as
   "no `makepad_csg` in the root".

Both restored verbatim from 2c5cd97, the last rev that resolved. Neither
is a judgement call: the moved lines are byte-identical and the deleted
block is copied back unchanged.

This repo is then repinned from d6d1f99c to the fixed rev, full 40-char
SHA per the pinning convention CI enforces.

Verified end to end after removing the local git redirect used during
development, so this resolves against the real remote:
  cargo metadata            resolves
  nigig-build --lib         685 passed
  cad_integration           154 passed
  spreadsheet-engine        225 passed
  doc-engine                 53 passed
  nigig-map (maps feature)  compiles
  Cargo.lock                unchanged, --locked passes

Also resolved committed conflict markers in two workflow files, which
had made nigig-build.yml invalid YAML -- the CI config could not be
parsed at all:

- nigig-build.yml: kept --include='*.rs' on the by-value-getter gate.
  Without it the gate scans ARCHITECTURE.md and fails on its own
  documentation, which is the bug fixed in 4f32b1c.
- pdf.yml: kept upstream's side. Enumerating targets via
  `cargo fuzz list` and failing when the list is empty is strictly
  better than a hardcoded target list that silently passes vacuously if
  a target is renamed.

That makes four files in three commits now carrying committed conflict
markers from this merge. Worth checking how they are reaching main --
`git diff --check` catches exactly this and is already a step in the
nigig-build workflow, but it only runs on paths under that workflow's
filter.
2026-07-31 19:32:08 +00:00
8c9ccb92cc Update makepad fork to latest dev branch (d6d1f99c)
Some checks failed
nigig-build.yml / Update makepad fork to latest dev branch (d6d1f99c) (push) Failing after 0s
pdf.yml / Update makepad fork to latest dev branch (d6d1f99c) (push) Failing after 0s
nigig-map / test (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
- Sync with upstream commit 5d4483f (latest map improvements + platform updates)
- Include location API, audio echo cancellation, bridge-dz overlay
- Add new libraries: geodata, map_nav, i_float, i_shape, i_tree, converse, llama vision
- Preserve all fork-specific re-exports (gltf, csg, test)
- All 102+ map improvements now available: 2D/3D toggle, shadows, labels, overlays, pattern fills
2026-07-31 18:47:37 +00:00
00f1dfbc12 fix(pdf): fuzz every target, and close two ADR 0004 gaps
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Three defects found by auditing the ADR merge criteria against the code
rather than against memory.

1. The scheduled fuzz job ran five hardcoded targets. Four had been
   added since and were never fuzzed: parse_revision_chain, decrypt,
   eval_function and parse_colorspace. eval_function is the sharpest of
   those - it executes PostScript taken verbatim from an untrusted file.
   The list now comes from `cargo fuzz list` and the job fails rather
   than passing vacuously if it comes back empty.

   `cargo fuzz list` reads the manifest, so a target file added without
   its [[bin]] entry would still be skipped silently. The engine job,
   which runs on every push, now checks the two agree. Both directions
   of that guard were exercised before committing.

2. ADR 0004 rule 3 promises an annotation whose appearance cannot be
   generated "keeps its original /AP and is reported as skipped". The
   keeping worked - to_dict clones the source dictionary - but nothing
   reported it: SaveReport only tracked skipped appearances for form
   fields. A caller who moved a stamp was never told its artwork still
   showed the old position. Adds
   SaveReport::annotation_appearances_skipped and
   appearance_is_generated, which enumerates the out-of-scope types
   explicitly so a new AnnotationType fails to compile until classified.

3. SetContents and SetFlags had no round-trip test. Both were
   implemented and unit-tested against the in-memory model, but neither
   was ever reparsed from written bytes - the assertion ADR 0004 calls
   central.

New fixture annotations/stamp.pdf carries real /AP artwork for a Stamp
(undrawable: must be preserved and reported) beside a Square (drawable:
must not be reported), so the reporting cannot pass by reporting
everything. The stamp test was mutation-checked: it fails when the
reporting line is removed.

ADR 0003 and 0004 merge criteria are now ticked. 0003's were genuine
paperwork - every box traced to an existing named test. 0004's were not,
and its ADR now records what was missing rather than implying it always
worked.

TEST_TARGET=pdf 447 -> 451 passing. rustfmt and clippy -D warnings clean.
2026-07-31 18:34:25 +00:00
01e16b6383 feat(pdf): implement encryption (Phase 8)
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Phase 8 feature 3 of 10, designed in REVIEWS/adr/0005-pdf-encryption.md.

An encrypted PDF did something worse than fail: it succeeded. Probing a
structurally valid RC4-encrypted file through the parser gave

  parsed OK: pages=1
  page 0 content bytes=44
    content parsed into 0 ops

No error and no warning. The document reported a page, the page reported
content, and the content interpreted to nothing because it was ciphertext.
The user saw a blank page and was told the file was fine. That is the defect
class Phase 0 existed to remove, and it was the worst one left in the PDF
stack because it was silent.

New pdf-cos/src/encrypt.rs implements the standard security handler for
reading:

- V1/R2 RC4 40-bit, V2/R3 RC4 40 to 128-bit, V4/R4 crypt filters selecting
  RC4 or AES-128, and V5/R6 AES-256 with the SHA-256 based revision 6 hash.
- The empty user password, which is the common case for a document
  encrypted only to set permissions, and explicit user or owner passwords.
  The owner path recovers the user password from /O and re-derives.
- Per-object keys, as the spec requires. Reusing one keystream across
  objects would be a real cryptographic break, so the object and generation
  numbers are mixed in by construction and a test asserts the keys differ.

Every primitive comes from audited RustCrypto crates: aes, cbc, rc4, md-5
and sha2, all MIT OR Apache-2.0, which deny.toml already permits. Phase 0
deleted a hand-rolled MD5/SHA/AES/RC4 implementation from this codebase and
called it a CVE factory; ADR 0005 keeps that rule.

Refusals rather than half-open documents: a public-key or otherwise
unsupported handler is refused and named, an unsupported V/R combination is
refused, and a wrong password returns a distinct error so a caller can
prompt again rather than reporting a damaged file.

Permissions are parsed and exposed but deliberately not enforced, and the
code says why: once content is decrypted a caller can read it regardless, so
enforcing here would imply a guarantee that does not exist.

Saving an encrypted document stays refused, as ADR 0003 established.
Decrypting and then writing plaintext would silently strip the protection
the author applied, which is not a decision a library should make.

Fixtures: tests/corpus/encrypted/ gains RC4 40-bit, RC4 128-bit, AES-128 and
an unsupported-handler document. The generator implements the handler's
algorithms independently from the specification, so a fixture that decrypts
shows the reader agrees with the spec rather than merely with itself. Each
plaintext contains a marker the tests assert on, and one test additionally
asserts the decrypted content interprets to real render commands, because
asserting Ok from the parser is exactly what the old broken behaviour did.

Fuzzing: adds a decrypt target covering key derivation, which consumes
attacker-controlled /O, /U, /P, /Length, filter names and file id. Run for
real rather than compile-checked: 1,953,940 executions, no crashes.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (383 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (427 tests, 6 ignored)
Both rustfmt and clippy -D warnings clean.
2026-07-28 17:39:38 +00:00
dc2bf234c6 test(pdf): harden the xref revision chain
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Incremental save made /Prev chain walking load-bearing for every document,
not only saved ones: XRefTable::parse now follows offsets taken straight from
the file on every parse. Phase 6 established that code consuming untrusted
input needs corpus and fuzz coverage. That path had neither, so this adds it
and fixes what it found.

Corpus (7 new fixtures, generated by the checked-in script as usual):

- revisions/two.pdf, three.pdf: chained revisions that override a form
  value. These are direct regression tests for the two bugs the previous
  commit fixed. Before it, two.pdf read back as "first" rather than
  "second", because find_xref_start never matched its own keyword and fell
  through to the oldest section in the file.
- revisions/added_page.pdf: a revision that rewrites /Pages, so the newer
  definition must win for structure as well as for values.
- malformed/prev_loop.pdf, prev_out_of_range.pdf, prev_negative.pdf and
  prev_chain_bomb.pdf: the hostile shapes.

Two robustness defects found by those fixtures:

- A broken /Prev orphaned every object the unreachable sections defined,
  even though the bytes were still in the file, so a document with one bad
  offset failed to open at all. The chain now sets a recovered flag and
  sweeps the file for object headers, filling only genuine gaps: entries a
  parsed section supplied always win, because those reflect the document's
  own view of which revision is current, and scanning cannot tell newer
  from older.
- A negative /Prev was filtered to None, which silently ended the chain as
  though the file had no history. It is now treated as a broken link and
  triggers the same recovery.

Also caps the chain at 64 revisions. A legitimate document has a handful; a
file with thousands is an attack, not a history. prev_chain_bomb.pdf asserts
the cap holds and that parsing stays fast.

The recovered flag is public so a caller can distinguish a cleanly parsed
document from a salvaged one rather than being handed a guess silently. A
test asserts it stays false for healthy files, or it would mean nothing.

Fuzzing: adds parse_revision_chain, which splices fuzzer input onto a valid
base document so the fuzzer spends its time on chain shapes rather than on
rediscovering PDF syntax. Run for real, not merely compile-checked:

  parse_revision_chain  1,926,164 runs
  parse_xref            1,387,713 runs
  parse_document        1,279,328 runs

No crashes. The two re-run targets cover the file this commit changes.

One fixture-generator bug fixed on the way: the helper that reads a file's
startxref took the first token after rfind without skipping the keyword,
producing a startxref that pointed at its own text.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (318 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (362 tests, 6 ignored)
Both rustfmt and clippy -D warnings clean.
2026-07-28 16:58:16 +00:00
d3ccc2e00f test(pdf): close the three gaps carried from Phases 4 to 7
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Three items were carried forward as known gaps rather than quietly dropped.
This addresses all three; two are closed outright and one is bounded by an
environment limit that is now documented rather than implied.

1. Fuzzing had never actually run (Phase 6 step 6.3).

The five cargo-fuzz targets were only compile-checked, so "zero panics on
arbitrary input" was an aspiration. They have now been run under nightly
libFuzzer:

  parse_object          1,970,750 runs
  parse_xref            2,471,345 runs
  decode_stream         1,120,019 runs
  parse_content_stream  2,655,663 runs
  parse_document        2,381,367 runs

About 10.6 million executions in total, no crashes and no new findings. That
is a real result rather than a green checkmark: the three crashes the corpus
found in Phase 6 were the ones worth finding, and the fuzzer confirms the
fixes hold under adversarial input.

2. Combo dropdown overlay (Phase 4 step 4.3).

A combo box that cannot be opened is a text field with extra steps, so the
open list is real state, not a rendering detail. Clicking a combo box opens
its options; the dropdown takes a click before any field underneath it,
matching the draw order; choosing a row sets the value through
DocumentFormEditor; clicking elsewhere dismisses it without changing the
value. render_open_combo() returns placement data so the drawing code stays
trivial and the geometry is testable without a renderer.

3. Makepad event delivery.

Upstream added a makepad_test framework, so this is now testable in
principle. Adds a test host binary and six UI tests that drive the widget
through the Studio protocol: a real click on the fixture link must surface
OpenUri on the host, typing must reach the field, and a click on empty space
must emit nothing so the positive assertions are not vacuous.

They are #[ignore] by default because the Studio hub cannot start an app in
this sandbox: the harness launches with --stdin-loop, which Makepad refuses
without a Studio websocket, and the build exits 101 before startup.
Upstream own spreadsheet-ui and map UI suites fail identically here with the
same error, so this is the environment rather than this code. The tests are
checked in and compiled by cargo test so they cannot rot, CI runs them where
a hub exists, and the module documents how to run them by hand.

Getting there also fixed a real defect in the test host: it copied
ui.main_view.render() from the spreadsheet app startup hook, but a plain
View has no render method, so the app errored at startup.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (270 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (321 tests, 6 ignored)
  cargo +nightly fuzz run <target> -- -max_total_time=60   (5 targets)
Both rustfmt and clippy -D warnings clean.
2026-07-27 18:09:57 +00:00
b23df6a5cb feat(pdf): complete Phase 6 testing infrastructure
Some checks failed
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Payment domain and storage / isolated-payment-tests (push) Has been cancelled
Phase 6 of REVIEWS/DART_PDF_VS_MAKEPAD_PDF_GAP_ANALYSIS.md: "Real PDFs. Real
regressions. No test theater."

Step 6.1, corpus. 27 fixtures across basic, fonts, forms, annotations,
images, edge and malformed, in the layout the review specifies. They are
produced by tests/corpus/generate.py rather than committed as opaque blobs,
because a corpus you cannot read is a corpus you cannot trust; CI regenerates
them and fails if they differ. Hand-rolled rather than library-produced,
since fixtures for a parser must contain constructs a library refuses to
emit.

Step 6.2, corpus tests (pdf-document/tests/corpus.rs, 27 tests). Text,
vectors, Flate, multipage, CID fonts, every form field type, inherited field
keys, link actions, hidden annotations, XObjects, inline images with
embedded EI bytes, rotation, crop boxes, nested CTMs and content arrays.

Step 6.3, robustness (pdf-document/tests/robustness.rs, 3 tests) plus five
cargo-fuzz targets. cargo-fuzz needs nightly and libFuzzer so it cannot gate
a stable CI run; the harness covers the same ground deterministically by
mutating the real corpus with a fixed-seed PRNG, so a failure is reproducible
from the seed rather than only from a saved artefact. The fuzz targets remain
the deeper coverage-guided search and run on a schedule.

Three crashes on untrusted input, all found by this work and all previously
reachable from a malformed file:

- collect_pages_ref recursed forever on a /Kids cycle. Stack overflow aborts
  the process; it cannot be caught. Now tracks visited nodes and bounds depth.
- PdfDocument::resolve and the COS lexer recursed once per nesting level, so
  a file of 5000 open brackets overflowed the stack. Both are now bounded.
- decode_85_group multiplied an accumulator that a malformed group can
  overflow, and subtracted below zero on a digit outside the valid range.
  Both panic in a debug build. Now saturating.

Step 6.4, CI (.forgejo/workflows/pdf.yml). An engine job that runs the
corpus and robustness suites under rustfmt and clippy -D warnings; a separate
makepad-integration job so a missing system library is not reported as a PDF
regression; and a scheduled fuzz job. The engine job also enforces the two
architectural rules mechanically rather than in prose: no Makepad dependency
or import in the engine crates, and no process or URL launching anywhere in
them.

Also fixes .gitignore: the blanket *.pdf rule silently excluded all 27
fixtures, which would have left CI unable to run them on a fresh clone.

Validation:
  TEST_TARGET=pdf ./tools/test-rust-clean.sh      (233 tests)
  TEST_TARGET=pdf-ui ./tools/test-rust-clean.sh   (268 tests)
Both rustfmt and clippy -D warnings clean; all five fuzz targets compile.
2026-07-27 17:08:23 +00:00