21 Commits
Author SHA1 Message Date
Dylan M. TaylorandCodebuff 4ae3089420 security: bounded keyword, repair and journal-recovery work (CodeRabbit) (#2763)
* security: harden dev-tool scripts and the testkit skip-log test (CodeRabbit)

- tools/install-sam3.sh: the HF bearer token went through curl's argv, where
  `ps` exposes it to other local accounts (CWE-214); it now travels through
  a private 0600 temp config file removed on exit.
- packaging/macos/import-cert.sh: the decoded .p12 was written to a
  predictable path in a shared TMPDIR, where a precreated attacker-readable
  file would receive it (CWE-377); both cert and keychain are created inside
  a mktemp -d 0700 directory now.
- docs/showcase/run.py: the reply reader copied whole responses with no size
  cap (obvious quadratic-copy and unbounded-memory DoS). It accumulates in a
  bytearray now and closes the connection past 128 MiB.
- tools/spectral_camera_table.py: alias/make/model strings and comments from
  the rawtoaces-data checkout moved directly into Rust source; one newline in
  a comment became Rust code. External text is now escaped as Rust literal
  content and comments are normalized to one printable line.
- crates/testkit: appended_lines_accumulate reused a pip-predictable temp
  dir by creating it with create_dir_all and carried on after failing to
  remove its log path, so the append would follow an attacker-planted
  symlink (CWE-59); it now fails the test if the directory exists instead of
  testing through it, and removes any existing log path only after clearly
  checking it, always failing the test instead of proceeding when removal fails.

Findings #10080/#10078 PR (rows 2, 6, 8, 9, 15 of the
storytold-lightcraft-agent-findings report).

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* security: bounded catalog keyword, repair, journal recovery work (CodeRabbit)

- keywords: from_file searched every hierarchical level for each flat keyword
  and `same` lower-cased two strings per comparison — one lowered level set
  instead (CWE-407, row 97).
- keywords: parse_keyword_list scanned each keyword's growing synonym list per
  line — a lowered-synonym set per keyword instead (CWE-407, row 100).
- keywords: keyword_tree stored every level's prefix twice, so a photo XMP
  keyword nested deeper than a list file accepts made stored bytes quadratic
  in the level count; the tree caps at MAX_LEVELS, and keyword_list_text names
  whatever lies deeper from the photos' and list's own spellings, so the file
  still reads back (CWE-400, row 103).
- catalog: repair validated a folder chain once per album — memoized by chain
  now, so a valid chain in a flat snapshot costs one walk overall (CWE-407,
  row 101).
- store: journal append and truncate follow a planted catalog.log symlink;
  both refuse any non-regular file at the name before writing through it
  (CWE-59, row 107), like the staging writes since #1502.
- journal: resync re-validated the whole remaining suffix UTF-8 for every
  candidate record start; a whole line is validated once (covering every
  candidate in it), and an invalid line's re-checks stop at a 64 MiB budget,
  with the tail then staying a torn one the damaged path keeps and reports
  (CWE-407, row 109).

New tests: from_file case-insensitive level match, synonyms kept once, a
4096-level keyword cut at MAX_LEVELS, a link at catalog.log refused, and
resync over a fragment of 1024 record starts.

Findings rows 97, 100, 101, 103, 107, 109 of the
storytold-lightcraft-agent-findings report.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

---------

Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-23 22:45:21 -04:00
Dylan M. Taylor 5ca49b3eab ci(sonar): don't cancel superseded runs on main, so coverage is published (#2737) 2026-10-23 17:01:32 -04:00
Dylan M. Taylor 91fcaaaca3 coverage: keep the body scan linear and build the zero counter with dict.fromkeys (SonarCloud S8786/S7519) (#2713)
- ORF_DECODED's Camedia branch drops the redundant \\S+ before .*; with two anchored greedies the scanner can no longer retry the same substring count-for-count at lower and lower split points (same match set, verified on 14 body strings).
- {c: 0 for c in classes} is now dict.fromkeys(classes, 0), which the rule wants and which stones the per-class counters.
2026-10-23 13:35:38 -04:00
Dylan M. Taylor d5e639b3d1 packaging: bash tests, named positional parameters and stderr errors (SonarCloud S7688/S7679/S7682/S7677) (#2655)
Bash scripts use [[ ]] for tests, functions name their positional
parameters and end with an explicit return, and the app-specific packaging
check writes its ::error:: annotations to stderr. No behaviour change.
2026-10-23 11:45:18 -04:00
Dylan M. Taylor 1c03b7b18f Merge pull request #2700 from dylanmtaylor/sonar/security-reliability
fix: document the deliberate buffer leak during thread teardown (SonarCloud)
2026-10-23 10:31:35 -04:00
Dylan M. Taylor 6fb6815cd9 fix: document the deliberate buffer leak during thread teardown (SonarCloud)
Dropping a wgpu buffer after the device's thread-locals are gone can
abort; the device owns the memory either way.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-23 09:44:05 -04:00
Dylan M. Taylor beb044bf35 Merge pull request #2494 from storytold/sonar-fix/path-cmd-hardening
security: validate CLI paths/args in dev tools (SonarCloud S2083/S8707/S8705)
2026-10-22 19:27:49 -04:00
Dylan M. Taylor ac2d7623e3 security: tighten readable/regular file checks in CLI tools 2026-10-22 19:18:41 -04:00
Dylan M. Taylor bfb23db2ca Use chrono instead of hand-rolled UTC conversion (#2457)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled Howard Hinnant civil/days-from-civil copies and the
leap-year/days-in-month table with chrono as a workspace dependency:
- apps/lightcraft/src/logging.rs: timestamp (log line time)
- crates/catalog/src/dates.rs: civil + weekday
- crates/catalog/src/stacks.rs: iso_seconds (date -> seconds)
- crates/engine/src/cmd/face_recognize.rs: capture_secs (native only)
- crates/meta/src/datetime.rs: DateTime::unix_seconds and the valid() day check
Output formats and semantics are unchanged (pre-epoch clamp, day rollover that
normalize_iso relies on, leap-second tolerance, EXIF/XMP parsing). chrono uses
only std, so wasm stays safe. AGENTS.md/CLAUDE.md now say time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed

chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. timestamp now uses the fallible DateTime::from_timestamp
and reads such a clock as the epoch, keeping the old pre-epoch clamp.

timestamp and catalog's civil write years past 9999 with {:04} like the old
{y:04} did, because %Y would add a '+'. Tests cover the out-of-range and
5-digit-year cases.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve old out-of-range date conversion (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant
past chrono's year range to 1970-01-01, changing output the old hand-rolled
Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s was
318857-05-20 17:46:40, not the epoch). Restore the old conversion domain with a
cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact).

- apps/lightcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
  pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/catalog/src/dates.rs: `civil` cycle-based (the callers stay inside
  0000-9999, but a far clock no longer resets to the epoch).

Pinned old outputs in tests:
  logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch),
          10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
  civil 10_000_000_000_000 -> 318857-05-20T17:46:40,
        -10_000_000_000_000 -> -314918-08-13T06:13:20, i64 extremes

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-21 19:36:36 -04:00
Dylan M. Taylor d79b1f33e3 security: enforce HTTPS on curl downloads (SonarCloud S6506) (#2493)
Add --proto '=https' to curl invocations so redirects cannot downgrade to
plaintext HTTP. Resolves SonarCloud S6506 (shell/docker/githubactions).
2026-10-21 19:35:59 -04:00
Dylan M. Taylor 3a9b35c687 ci: exclude dev tool scripts from SonarCloud coverage gate
tools/ and docs/ scripts aren't unit-tested; exclude them from coverage so the
new-code coverage gate doesn't block hardening-only changes.
2026-10-21 17:31:18 -04:00
Dylan M. Taylor 68d5118a9d security: validate CLI paths/args in dev tools (SonarCloud S2083/S8707/S8705/S7044)
Resolve and validate command-line path arguments before use, enforce HTTPS at
download sinks, and confirm path containment. Hardens local codegen/fetch tools
against path-traversal and command-argument-injection findings.
2026-10-21 17:13:05 -04:00
Dylan M. Taylor 3dc3b955df ci: add sonar-project.properties for CI-based analysis
Marks the project ineligible for SonarCloud Automatic Analysis, resolving the
CI-vs-automatic-analysis conflict so the SonarCloud workflow succeeds.
2026-10-21 16:00:43 -04:00
Dylan M. Taylor 511908ab06 ci: add SonarCloud scan via shared reusable workflow
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
2026-10-21 15:47:01 -04:00
Dylan M. Taylor abb49bdd76 ci: switch to shared nix workflow from craft-common-actions (#1980) 2026-10-21 02:38:14 -04:00
Dylan M. Taylor 666f78432b docs: add SonarCloud and CodeRabbit badges to README (#2240)
* docs: add SonarCloud and CodeRabbit badges to README

* docs: put SonarCloud and CodeRabbit badges on their own line
2026-10-20 11:31:44 -04:00
Dylan M. Taylor fb7ac08b9a ci: configure Dependabot for Cargo and GitHub Actions
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
2026-10-18 12:31:40 -06:00
Dylan M. Taylor 31a677fe3d docs: drop references to competing commercial software (#2213) 2026-10-18 10:41:49 -04:00
Dylan M. Taylor 0802892132 ci: refactor workflows to use storytold/craft-common-actions 2026-10-15 21:19:37 -04:00
Dylan M. Taylor 0ed5fcd938 Linux: default to XWayland for file drag-and-drop on Wayland desktops 2026-10-10 09:02:26 -04:00
Dylan M. Taylor 96ab5617b8 chore: bump github actions to fix Node 20 deprecation warnings 2026-10-09 20:08:30 -04:00