Jscad Stl Validator
STL file validator — checks triangle count, degenerate faces, bounding box
Global Arguments
| Argument | Type | Description |
|---|---|---|
| allowedRoots | array | Optional operator-set confinement roots for validateFile. When |
| maxFileBytes | number | Maximum size in bytes validateFile will read from disk. Checked |
| Argument | Type | Description |
|---|---|---|
| cadModelName | string | Name of the @magistr/jscad-cad model whose stored output STL to validate |
| version? | number | Data version to validate (defaults to latest) |
| Argument | Type | Description |
|---|---|---|
| filePath | string | Absolute path to the STL file to validate |
Resources
2026.08.02.1
Real-fixes the four remaining latent bugs (LB2–LB5) tracked in the LOCAL
jscad-stl-validator-latent-bugs issue-lifecycle model (NEVER a swamp.club Lab
issue). LB1 (path traversal, fixed in 2026.08.01.1) is untouched. The pure
domain service extensions/models/jscad/stl_validator.ts — previously declared
BYTE-FROZEN — is deliberately un-frozen for these fixes; every benign contract
pin in the test suites stays byte-identical.
Behavior changes:
- LB2 (format misdetection, MEDIUM) — FIXED. A binary buffer whose 80-byte
header happens to spell
"solid"AND whose claimed (offset-80) triangle count does NOT match the actual buffer size is now correctly classified as binary (restoring the real "Size mismatch" diagnosis and the actual triangle data), instead of falling through to the ASCII parser. The discriminator: when the claimed count is> 0but the size doesn't match exactly, the buffer's decoded text is checked for ASCII geometry keywords (facet normal/endsolid); only a buffer that genuinely looks like ASCII text keeps the ASCII path. The existing positive/negative reclassification pins (exact-size-match → binary; claimed-count-0 → ASCII) are unaffected. - LB3 (unbounded read + issues[] amplification, MEDIUM) — FIXED, in two
independent halves:
- Domain half:
issues[]no longer grows linearly with a hostile triangle count. At most 10 individualTriangle i: contains NaN or Infinity valuesstrings are pushed; beyond that, one(<k> further triangle issue(s) suppressed)note is added, and the existing<n> degenerate triangle(s) found ...summary is always kept. The bounding box is now computed with incrementalmin/maxaccumulators instead of retaining aTriangle[]array — memory is O(1) in triangle count. Output is byte-identical for triangle counts at or below the cap (single-NaN/single-Infinity pins unaffected). - Application half: added a new defaulted global argument
maxFileBytes(default268435456, 256 MiB).validateFilenowDeno.stats the resolved path and rejects withRefusing to read "<path>": file exceeds maxFileBytes (<size> > <cap>)BEFORE callingDeno.readFile, so an oversized file is never buffered into memory. Read only fromcontext.globalArgs(never per-call arguments), mirroring theallowedRootstrust-boundary convention. Order isresolveStlPath(policy) →Deno.statsize check →Deno.readFile, preserving the LB1 traversal pin, the missing-file pin (stat NotFound →Cannot read), and the directory pin (stat ok + small size →readFileEISDIR →Cannot read).
- Domain half:
- LB4 (weak ASCII validation, LOW) — FIXED. ASCII validation now parses
facets into triangles (grouping vertices in consecutive triples) and runs the
SAME degenerate-triangle check as binary (duplicate-vertex OR near-zero
cross-product area), via a shared, non-exported
isDegenerateTrihelper. A fully degenerate (duplicate-vertex) ASCII facet is now reportedvalid: false,degenerateTriangles: 1, with the same<n> degenerate triangle(s) found ...summary as binary, and excluded from the bounding box. Malformed vertex remainders (count not a multiple of 3) are ignored for the geometry check. The canonical (non-degenerate, finite) ASCII contract pins injscad/stl_validator_test.tsare byte-identical. - LB5 (NaN/Infinity asymmetry, binary vs ASCII, LOW) — FIXED. Folded into
the LB4 rewrite: ASCII vertices now run the SAME
isFinite/isNaNguard as binary, via a shared, non-exportedisFiniteCoordshelper. A non-finite ASCII triangle is flagged with aTriangle i: contains NaN or Infinity valuesissue (subject to the LB3 cap), counted as degenerate, and EXCLUDED from the bounding box — so an Infinity-valued coordinate can no longer poison the bounding box's max/min, and an all-NaN facet can no longer produce the nonsensical invertedmin:[Infinity,...]/max:[-Infinity,...]box. Binary and ASCII now classify an identical NaN vertex equivalently. extensions/models/jscad_stl_validator.ts: modelversionbumped to2026.08.02.1. Added an identityupgrades[]entry (the model previously had none) — the onlyglobalArgumentschange is the new defaultedmaxFileBytesfield, so existing instances re-parse cleanly with zod filling the default; the upgrade transform is(old) => old.manifest.yamlversion bumped to2026.08.02.1in sync.- Test suites: flipped the four buggy pins in
Notes trimmed at a line boundary to fit the registry's 4900-byte per-version cap. Full section: https://github.com/umag/swamp-workspace/blob/d850d8e8372d4c0008c9245959a090b37095de7a/jscad-stl-validator/CHANGELOG.md
2026.08.01.1
Fixes the HIGH-severity path-traversal / arbitrary-file-read finding (LB1) in
validateFile, tracked in the LOCAL jscad-stl-validator-latent-bugs
issue-lifecycle model (NEVER a swamp.club Lab issue). validateFile is an
operator-supplied-absolute-path method by design; this fix is defense in depth
for any future less-trusted exposure.
Behavior change: validateFile now rejects any filePath that is not
absolute, and any filePath containing a literal . or .. path segment —
previously such a path (e.g. a traversal-shaped path) was read verbatim with no
confinement at all. The traversal/absolute-path check runs BEFORE any filesystem
access, so a traversal attempt against a nonexistent target is still refused as
a policy violation, never silently masked by a "file not found" error.
Legitimate callers passing a clean absolute path (as shown in this README's
examples) are unaffected.
- Added
extensions/models/jscad/safe_path.ts— a new infrastructure guard,resolveStlPath, implementing the always-on checks above plus canonicalization viaDeno.realPath. The pure domain servicejscad/stl_validator.tsstays BYTE-FROZEN; path safety is an infrastructure concern, kept out of the domain service. - Added an OPT-IN
allowedRootsglobal argument (default[], read viacontext.globalArgs— never the per-call arguments) that confinesvalidateFileto one or more operator-set root directories, using canonicalized (Deno.realPath'd) separator-boundary prefix matching so a sibling directory sharing a name prefix (/rootFOOvs./root) or a symlinked root cannot be mistaken for containment. EmptyallowedRoots(the default) preserves the historical unconfined contract. extensions/models/jscad_stl_validator.ts:validateFilenow resolvesargs.filePaththroughresolveStlPathbefore callingDeno.readFile. Error messages still key to the ORIGINALargs.filePath(not the canonicalized path), so the existing missing-file/directory-pathCannot read "<path>"throw-tests stay green; policy violations get a distinctRefusing to read "<path>": ...message. Modelversionbumped to2026.08.01.1.manifest.yamlversion bumped to2026.08.01.1in sync; addedextensions/models/jscad/safe_path_test.tstoadditionalFiles.deno.json'schecktask extended to typecheckjscad/safe_path.ts+jscad/safe_path_test.ts.- Test suite: added
extensions/models/jscad/safe_path_test.ts(11 unit tests covering reject-relative, reject-./..-segment including the nonexistent-traversal-target ordering guarantee, accept-clean-absolute,allowedRootsaccept/reject, separator-boundary/rootFOO-vs-/root, symlink-escape rejection, and root/target canonicalization). Flipped the LB1 "../traversal path read verbatim" pin injscad_stl_validator_adversarial_test.tsto assert rejection; relabeled the "no base dir, two unrelated roots both succeed" pin from a HIGH bug-pin to aregression:-prefixed default-contract test (still asserts both calls succeed — that is the intended behavior whenallowedRootsis not configured). AddedallowedRootsaccept/deny cases plus a test proving a smuggled per-callargs.allowedRootshas no effect tojscad_stl_validator_methods_test.ts. All prior binary/ASCII happy paths and the missing-file/directory-path throw-path tests are unchanged and still green. - README.md: documented the
validateFileoperator-trust boundary and the opt-inallowedRootsconfinement, including the residual "report reflects file bytes" oracle risk to keep in mind when narrowingallowedRoots. - LB2/LB3/LB4/LB5 remain pinned as characterized (not fixed) latent bugs — out
of scope for this fix, tracked in the same issue-lifecycle model. The sibling
jscad-stl-slicerextension shares the same unconfined-path pattern; deliberately NOT fixed here — tracked as its own follow-up.
Release 2026.07.16.2 — align model versions with manifests
Maintenance release across the @magistr extensions. For most packages this
carries no functional change: the only edit is the model's version: field,
brought back in line with its manifest version so the published model type
version and the package version no longer drift.
Functional changes in this release are limited to:
anime-cron: normalizeTitle now strips a ": subtitle" suffix and a trailing parenthesized year before comparison, fixing dedup false-misses where the torrent title carries a subtitle or year that the AniList romaji does not.
arckit: first publish. Standalone ArcKit port — a 12-phase architecture governance state machine with 65 bundled templates, driven by a bundled skill.
Also tracks three extensions (kaiten, observability-agent, music-library) that previously existed only as untracked working-tree directories, recovered from stashes.
Added 1, removed 1 models
Merge pull request #5 from umag/extensions/jscad-stl-pair
extensions: add 15 more @magistr extensions + auto-discover CI
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned