Skip to content

fix: 2026-07-28 deep-audit bug-fix sprint (issues #82-91) - #92

Merged
SoundMatt merged 12 commits into
mainfrom
fix/deep-audit-20260728-bugfix-sprint
Jul 29, 2026
Merged

fix: 2026-07-28 deep-audit bug-fix sprint (issues #82-91)#92
SoundMatt merged 12 commits into
mainfrom
fix/deep-audit-20260728-bugfix-sprint

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Summary

Fixes all 10 concrete, actionable defects filed by the 2026-07-28/29 deep audit (issue #66 is a separable large feature request — AUTOSAR rule pack + Coverity import + CBMC proof coverage — and is intentionally left open/out of scope for this bug-fix pass).

Test plan

  • cmake -B build -DCMAKE_BUILD_TYPE=Debug && cmake --build build --parallel — clean build, no warnings introduced
  • ctest --test-dir build — 40/40 suites pass, including a new tests/test_xfusa_audit_20260728.c with one (or a small group of) regression test(s) per issue, each verified to fail against the pre-fix code
  • Full suite re-run clean under -fsanitize=address,undefined (no leak-detector, unsupported on macOS)
  • Manual end-to-end verification of every fix against a scratch project (documented in the corresponding commit messages)
  • ./build/cfusa check --dir . self-check still runs cleanly (exit 1 on pre-existing findings, no crash)

Closes #82, closes #83, closes #84, closes #85, closes #86, closes #87, closes #88, closes #89, closes #90, closes #91.

SoundMatt added 10 commits July 28, 2026 20:23
…is c-FuSa

x-FuSa spec §2.4.1 (MUST): the `standard` field is a canonical lowercase
id used identically everywhere in the JSON, never a display string — a
clause reference belongs in the separate `clause` field. `check`/`lint`/
`analyze`/`cyber` findings were instead emitting combined display strings
like "MISRA-C:2012 R15.1" or "ISO 21434 / CERT-C STR31-C" straight from
the rule registry, with `clause` always empty.

`cfusa_rule_t` now carries `standard_id` (canonical id) and `clause`
(rule/clause reference) as two separate fields instead of one combined
`standard` string, and every rule table (lint/analyze/cyber/safety) is
updated to populate them. `Finding.standard`/`Finding.clause` in the JSON
report, and the SARIF rule declarations' help text, are derived from the
new fields.

Also fixes §2.9's SARIF mapping MUST: `tool.driver.name` was emitting the
binary name "cfusa" instead of the §1.1 tool name "c-FuSa" — every other
JSON output already used "c-FuSa" in the common header; SARIF was the one
holdout, which made GitHub Code Scanning attribute findings to "cfusa"
instead of "c-FuSa".

Closes #84, closes #91.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
…suffix

`cfusa check`/`cfusa lint`'s CFUSA-L004 rule (MISRA-C 2012 Rule 17.2, no
recursion) flagged a local function as "recursive" whenever an unrelated
function it called merely had a name that *ends with* the local
function's name — e.g. a static `evaluate()` calling an external
`rcp_e2e_wd_evaluate()` or `helper_evaluate()`, neither of which is a
self-call or forms a call cycle.

The self-call scan looked for the literal substring "fn_name(" in each
line of the function body with no identifier-boundary check on the left
side of the match, so any callee name merely ending with the caller's
name matched too. Add `l004_self_call()`, a small word-boundary-aware
scanner: it still skips string/character literals, but additionally
requires the character immediately before a candidate match to not be an
identifier character (or be the start of line).

Real recursion (a function actually calling itself) is unaffected.

Closes #82.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
x-FuSa spec §1.2.5 defines `.fusa-hara.json` as an INPUT file authored by
a project (like `.fusa-reqs.json`), with a plain schema of
`{project, standard, createdAt, operationalSituations, hazards,
safetyGoals, attestation?}` — no `schemaVersion`/`kind`/`tool`/
`toolVersion`/`language` fields, since those §3.1 common-header fields
belong to *report documents*, not this input file. §3.1's `kind` enum is
also closed and does not contain a bare `"hara"` value (the corresponding
report kind is `"hara-report"`, used by `hara --format json`'s separate
output path).

`hara init`'s scaffold wrote all five report-envelope fields (including
`"kind": "hara"`, not even a member of the closed enum) straight into the
input file it creates, unlike c-FuSa's own hand-authored `.fusa-hara.json`
at the repo root, which correctly has none of them. Drop the envelope
fields from the scaffold so a freshly-initialised file matches the §1.2.5
schema exactly.

Closes #90.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
…to sas.md

x-FuSa spec §9.3: `sas --format json` writes `sas.json`, and a tool MUST
also write the human-readable `sas.md` companion — `sas.json` is not a
replacement for it.

`--output`'s default was hardcoded to `"sas.md"` regardless of `--format`,
so `cfusa sas --format json --dir .` (no `--output`) wrote the JSON body
into a file literally named `sas.md`. Worse, the later "did we already
write sas.md?" companion-generation guard compared only the output path,
saw `out_path == "sas.md"`, and concluded the real Markdown companion had
already been produced — so neither of the two files §9.3 requires ever
actually existed; there was one file, named sas.md, containing JSON.

Make the default `--output` path depend on `--format` when not given
explicitly: "sas.json" for `--format json`, "sas.md" for md/text/default.
The existing companion-generation logic then does the right thing
unmodified — it writes JSON to sas.json and the real Markdown companion to
sas.md.

Closes #89.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
… than cwd

x-FuSa spec §2.2: `--dir <path>` (project root, default cwd) applies to
"all" commands, and §6's own documented `qualify` CLI surface includes it
explicitly.

`cmd_qualify`'s `getopt_long` table had no `"dir"`/`-d` entry at all, so
`cfusa qualify --dir <anything>` failed unconditionally with exit `2`
(usage error) — the flag simply didn't exist, regardless of position.
Since FuSaOps orchestrates every x-FuSa tool by invoking it with
`--dir <project-root>` against whatever project it is analysing (almost
never its own cwd), and `qualify` is one of the §9.1 commands FuSaOps
consumes, this broke `qualify` for every project other than "run cfusa
from inside the target directory".

Add `--dir`/`-d` to `cmd_qualify`, and resolve a relative `--binary` path
against it rather than always against the process cwd.

Closes #88.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
x-FuSa spec §3.1's `kind` enum is closed — `"misra-coverage"` is not a
member (the closest match is `"gap-report"`). §2.4.1 requires `standard`
to be a canonical lowercase id, never a display string — `misra` emitted
`"MISRA C:2012"` instead of the registry's `"misra-c"`. And §9.3 requires
a standards command's JSON to use the canonical `objectives[]` +
`summary{total,satisfied,partial,gaps}` shape, not an ad hoc `rules[]`
array with a per-rule `"rule"` field (the same anti-pattern already fixed
for the other six standards commands).

`misra --format json` violated all three: `"kind": "misra-coverage"`,
`"standard": "MISRA C:2012"`, and a `rules[]`/`covered`/`gaps` payload
shape with no `summary` object.

Closes #87.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
x-FuSa spec §9.3 (MUST): a standards command's JSON must carry a
`summary{total,satisfied,partial,gaps}` object satisfying
`satisfied + partial + gaps == total`. `iso26262`/`iec61508`/`do178`/
`iso21434`/`unece`/`iec62443` each emitted ad hoc top-level counters
(`covered`/`gaps`[/`na`][/`mandatoryGaps`/`recommendedGaps`]) with no
`summary` object at all — only `slsa` implemented the canonical shape
correctly. `iso26262` additionally had a latent counting bug: its
per-objective status already distinguished a mandatory "gap" from a
recommended "partial", but the top-level counter combined both into one
`gaps` value, so the two were inconsistent within the same document; it
now tracks them the same way `iec61508`/`iec62443` already did, and
exposes the recommended count as a new `recommendedGaps` field.

`iso21434` and `unece` additionally have `"manual"`/`"na"` per-objective
statuses for organisational evidence that isn't tool-checkable — not
members of §9.3's closed `satisfied|partial|gap` enum. Per that section's
own consumer fail-safe rule ("map any unrecognised status to gap"), the
new summary counts them as gaps for consistency.

Also, x-FuSa spec §2.4.1: `iec62443`'s gap-report emitted the non-registry
command name `"iec62443"` as its `standard` id instead of the split
`"iec62443-4-1"`/`"iec62443-4-2"` registry ids (issue #12 predates that
split). This command only implements the Component Requirements (-4-2)
part, so its `standard` is now `"iec62443-4-2"`.

Closes #85, closes #86.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
…change

x-FuSa spec §1.6.2 (MUST — carry-forward across regeneration): an
artifact-producing command must not silently discard an existing
`attestation` object when it regenerates the artifact — it must carry the
prior attestation forward unchanged, so staleness is detected via the
carried-forward `contentHash` no longer matching the fresh content
(falling back to "heuristic"), rather than the review record vanishing
outright.

`fmea`/`tara`/`safety-case`/`sas` each read the prior attestation back
correctly, but then gated *emission* of the `"attestation"` block on
`cfusa_qb_attestation_valid()` (independence + non-stale hash) instead of
on `attestation.present`. The moment the artifact's analysed content
changed — exactly the case §1.6.2 exists for — the attestation was
dropped from the regenerated document entirely instead of being carried
forward and re-evaluated as stale, leaving no audit trail that a review
had ever happened.

Gate emission on `attestation.present` (carrying the loaded object's
actual `status` forward verbatim) in all four commands; validity/
staleness stays a *consumer*-side concern, as the spec text says.

Closes #83.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
Adds tests/test_xfusa_audit_20260728.c, one (or a small group of) test(s)
per issue fixed in this sprint (#82-#91): the CFUSA-L004 suffix false
positive and its real-recursion negative case, fmea's attestation
carry-forward-as-stale across a content change, the canonical
standard/clause fields on a lint finding plus the SARIF driver name,
summary{} presence and the satisfied+partial+gaps==total invariant across
all six standards commands (iso26262/iec61508/do178/iso21434/unece plus
iec62443's canonical id), misra's canonical gap-report schema, qualify
accepting --dir, sas writing both sas.json and a real sas.md companion,
and hara init's envelope-free §1.2.5 scaffold.

Each test reproduces its issue's failure mode directly against the
library code (not by shelling out to the built binary), and was verified
to fail against the pre-fix code before the corresponding fix commit.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
2026-07-28/29 deep-audit bug-fix sprint (issues #82-#91) — see
include/cfusa/version.h for the itemised change list.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
Comment thread tests/test_xfusa_audit_20260728.c Fixed
Comment thread tests/test_xfusa_audit_20260728.c Fixed
Comment thread tests/test_xfusa_audit_20260728.c Fixed
Three synthetic argv[] arrays were passed to cmd_fmea()/cmd_sas() with
argc one less than the array's actual element count (excluding the
NULL terminator) — e.g. a 7-element argv (including "--attest" and its
value) called with argc=6. This under-count wasn't visible to
getopt_long on macOS/BSD libc, but on glibc (the CI Linux runners) it
made getopt_long report "--attest requires an argument" and left the
command in an inconsistent state that went on to segfault, failing the
Linux legs of CI while passing locally.

Verified against a real Ubuntu 22.04 container (matching the CI image)
with both gcc and the system toolchain: all 40 suites now pass there,
where test_xfusa_audit_20260728 previously segfaulted.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c-FuSa found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

CodeQL (3 high-severity) flagged three fopen(path, "w") call sites in
tests/test_xfusa_audit_20260728.c as creating world-writable files —
the same class of finding the repo already fixed elsewhere by routing
file creation through cfusa_fopen_write() (restrictive mode + O_EXCL-ish
semantics) instead of raw fopen(). Switch all three sites to it.

c-FuSa's own self-check (CFUSA-CY003 / CWE-78) flagged l004_reset_dir()'s
system("rm -rf ...") cleanup helper as OS command execution with a
non-literal argument. The helper never needed shell semantics in the
first place — each fixture subdirectory only ever holds one specific,
statically-known file — so replace it with direct remove()/rmdir() calls
instead of shelling out.

Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit 0fe2ffb into main Jul 29, 2026
10 checks passed
@SoundMatt
SoundMatt deleted the fix/deep-audit-20260728-bugfix-sprint branch July 29, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants