nx_safe_archive_ingest.nx
buildroot/runtime/nx_safe_archive_ingest.nx
about
nx_safe_archive_ingest.nx -- SAFE sovereign ingestion of an UNTRUSTED .zip archive into the
permanent library. Composes the shipped zip + deflate primitives (rule 15 DRY) under a
NEVER-POISON-THE-LIBRARY safety envelope -- the #26 NEVER-BRICK law applied to ingestion:
an untrusted archive can NEVER, BY CONSTRUCTION:
(1) write outside the dest dir -- Zip-Slip-proof: the stored path is NEVER used as a
filesystem path; output is dest + "/" + a sanitized
BASENAME (chars mapped to [A-Za-z0-9._-], ".." rejected).
(2) exhaust memory/disk -- zip-bomb caps: per-entry uncompressed cap, total cap,
compression-ratio cap, entry-count cap, archive cap.
(3) be executed -- data-only: files written 0644 (no exec bit), content is
sanitized to printable text; nothing is ever run/chmod+x.
(4) smuggle an unknown codec -- method whitelist: 0 (stored) / 8 (deflate) only.
(5) smuggle a binary/script as library -- content-type whitelist by extension; non-text SKIPPED
(never banked); even "text" is stripped of control bytes
(defuses terminal-escape injection).
(6) link out via symlink -- entries with unix S_IFLNK mode are SKIPPED.
(7) bomb via nested archive -- we do NOT recurse; a .zip entry is non-text -> SKIPPED.
Zip64-sentinel sizes are SKIPPED (not yet handled = refuse, never guess).
usage: nx_safe_archive_ingest <archive.zip> [dest-dir] (default dest = knowledge/library)
exit 0 always on a well-formed run (per-entry verdicts on stdout); nonzero only on unreadable
archive / not-a-zip. Rule 11: every cap is a named const (data-driven). license_tier: ORIGINAL
dependencies 4 imports · 0 importers
imports: nx_syscalls.nxnx_zip_header.nxnx_deflate.nxnx_pdf_text.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 27 | const SAI_MAGIC_2048: i64 = 2048 |
| 28 | const SAI_MAGIC_16777216: i64 = 16777216 |
| 31 | const SAI_MAX_ARCHIVE: i64 = 67108864 // 64 MiB total archive bytes |
| 32 | const SAI_MAX_ENTRIES: i64 = 4096 |
| 33 | const SAI_MAX_ENTRY_USIZE: i64 = 33554432 // 32 MiB uncompressed per entry |
| 34 | const SAI_MAX_TOTAL_USIZE: i64 = 268435456 // 256 MiB uncompressed total |
| 35 | const SAI_MAX_RATIO: i64 = 200 // uncompressed/compressed ceiling (zip-bomb guard) |
| 36 | const SAI_CDFH_SIG: i64 = 0x02014b50 |
| 38 | const SAI_BANKED: i64 = 1 |
| 39 | const SAI_SKIPPED: i64 = 2 |
| 40 | const SAI_REJECTED: i64 = 3 |
functions
| 42 | func sai_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 43 | func sai_putn(v: i64) -> i64 |
| 54 | func sai_put_name(buf: *u8, off: i64, len: i64) -> i64 |
| 60 | func sai_lc(c: i64) -> i64 { if c >= 0x41 { if c <= 0x5a { return c + 0x20 } } return c } called by 1: sai_ext_eq |
| 63 | func sai_safe_char(c: i64) -> i64 called by 1: sai_safe_basename |
| 75 | func sai_safe_basename(name: *u8, off: i64, len: i64, out: *u8, cap: i64) -> i64 |
| 105 | func sai_ext_eq(b: *u8, start: i64, end: i64, lit: *u8) -> i64 |
| 117 | func sai_name_is_cruft(b: *u8, blen: i64) -> i64 called by 1: sai_process_entry |
| 125 | func sai_is_skip_ext(b: *u8, blen: i64) -> i64 |
| 168 | func sai_ext_is_pdf(b: *u8, blen: i64) -> i64 |
| 179 | func sai_is_text_content(c: *u8, len: i64) -> i64 called by 1: sai_process_entry |
| 194 | func sai_scat(dst: *u8, o: i64, s: *u8, slen: i64) -> i64 { var i: i64 = 0; while i < slen { dst[o + i] = s[i]; i = i + 1 } return o + i } called by 1: sai_bank_text |
| 195 | func sai_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } called by 1: main |
| 200 | func sai_bank_text(dest: *u8, destlen: i64, base: *u8, blen: i64, content: *u8, clen: i64) -> i64 |
| 229 | func sai_process_entry(buf: *u8, n: i64, off: i64, dest: *u8, destlen: i64, tot_p: *i64) -> i64 |
| 297 | func main(argc: i64, argv: *i64) -> i64 |