Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
{"id":"REQ-REQCOV-TRUNC-001","title":"cfusa trace --req-coverage truncates UNANNOTATED listing at 20 with '... and N more'; parity with go-FuSa TestRunTraceReqCoverage_UncoveredListTruncated","standard":"go-FuSa parity","level":"ASIL-A"},
{"id":"REQ-SIGN-KEYGEN001","title":"cfusa sign --keygen <path> generates a 32-byte random key (64 hex chars), writes to path with mode 0600, overwrites existing; exit 3 on write error; parity with go-FuSa TestSignKeygen_*","standard":"go-FuSa parity","level":"ASIL-A"},
{"id":"REQ-DO178-DAL001","title":"cfusa do178 --dal with invalid value returns exit 2 (usage error); accepts both a|b|c|d and DAL-A|DAL-B|DAL-C|DAL-D formats; parity with go-FuSa TestRunDo178_InvalidDALv2","standard":"go-FuSa parity","level":"ASIL-A"},
{"id":"REQ-SPEC22-001","title":"when --output <file> is given cfusa unece/iso21434/comp write no bytes to stdout (spec §2.2 'stdout MUST be empty'); parity with go-FuSa TestConform_OutputNoStdout_*","standard":"FuSaOps §2.2","level":"ASIL-B"}
{"id":"REQ-SPEC22-001","title":"when --output <file> is given cfusa unece/iso21434/comp write no bytes to stdout (spec §2.2 'stdout MUST be empty'); parity with go-FuSa TestConform_OutputNoStdout_*","standard":"FuSaOps §2.2","level":"ASIL-B"},
{"id":"REQ-SLSA-LEVEL001","title":"cfusa slsa --level accepts L1/L2/L3/L4 prefix format and returns exit 2 for invalid level (L5); parity with go-FuSa TestRunSLSA_AllLevels/TestRunSLSA_BadLevel","standard":"go-FuSa parity","level":"ASIL-A"},
{"id":"REQ-SLSA-OUT001","title":"cfusa slsa --output <file> writes report to file and prints 'SLSA gap report written to' to stderr; exit 3 for bad output path; parity with go-FuSa TestRunSLSA_OutputFile/TestRunSLSA_BadOutputPath","standard":"go-FuSa parity","level":"ASIL-A"},
{"id":"REQ-IEC62443-SL001","title":"cfusa iec62443 --sl accepts SL-1/SL-2/SL-3/SL-4 format and returns exit 2 for invalid SL (SL-5); parity with go-FuSa TestRunIEC62443_AllSLs/TestRunIEC62443_BadSL","standard":"go-FuSa parity","level":"ASIL-A"},
{"id":"REQ-IEC62443-OUT001","title":"cfusa iec62443 --output <file> writes report to file and prints 'IEC 62443 gap report written to' to stderr; exit 3 for bad output path; parity with go-FuSa TestRunIEC62443_OutputFile/TestRunIEC62443_BadOutputPath","standard":"go-FuSa parity","level":"ASIL-A"}
]
}
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.

## [Unreleased]

## [0.5.28] — 2026-06-13

### Fixed
- **`cfusa slsa --level L1/L2/L3/L4`**: SLSA level flag now accepts the `Lx` prefix format in addition to bare integers. Previously `--level L1` was parsed by `atoi()` as 0, causing exit 2 (invalid level). Parity with go-FuSa `TestRunSLSA_AllLevels`.
- **`cfusa slsa` text header**: Changed "SLSA v1.0 Gap Report" → "SLSA Supply-Chain Gap Report" for consistency with go-FuSa output and spec-level language.
- **`cfusa slsa --output <file>`**: Now prints confirmation "SLSA gap report written to \<file\>" to stderr after writing. Parity with go-FuSa `TestRunSLSA_OutputFile`.
- **`cfusa iec62443 --output <file>`**: Now prints confirmation "IEC 62443 gap report written to \<file\>" to stderr after writing. Parity with go-FuSa `TestRunIEC62443_OutputFile`.

### Requirements
- REQ-SLSA-LEVEL001
- REQ-SLSA-OUT001
- REQ-IEC62443-SL001
- REQ-IEC62443-OUT001

## [0.5.27] — 2026-06-13

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16)
project(cfusa
VERSION 0.5.27
VERSION 0.5.28
DESCRIPTION "C functional safety toolkit"
LANGUAGES C
)
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ LABEL org.opencontainers.image.title="c-FuSa" \
org.opencontainers.image.source="https://github.com/SoundMatt/c-FuSa" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="SoundMatt" \
org.opencontainers.image.version="0.5.27" \
org.opencontainers.image.version="0.5.28" \
io.x-fusa.tool="c-FuSa" \
io.x-fusa.language="c" \
io.x-fusa.binary="cfusa" \
Expand Down
5 changes: 4 additions & 1 deletion cmd/cfusa/cmd_iec62443.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@
return 3;
}

if (output && out != stdout) fclose(out);
if (output && out != stdout) {
fclose(out);

Check warning

Code scanning / cfusa

return value of 'fclose' may be unchecked — system call failures must be handled (CERT-C ERR33-C) Warning

return value of 'fclose' may be unchecked — system call failures must be handled (CERT-C ERR33-C)
fprintf(stderr, "IEC 62443 gap report written to %s\n", output);
}
return (gaps_m > 0) ? 1 : 0;
}
14 changes: 11 additions & 3 deletions cmd/cfusa/cmd_slsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@
while ((c = getopt_long(argc, argv, "d:l:f:o:h", long_opts, NULL)) != -1) {
switch (c) {
case 'd': dir = optarg; break;
case 'l': level = atoi(optarg); break;
case 'l': {
const char *lv = optarg;
if (lv[0] == 'L' || lv[0] == 'l') lv++;
level = atoi(lv);
break;
}
case 'f': fmt_s = optarg; break;
case 'o': output = optarg; break;
case 'h':
Expand Down Expand Up @@ -177,7 +182,7 @@
ok ? "[x] Satisfied" : "[ ] Gap");
}
} else if (!strcmp(fmt_s, "text")) {
fprintf(out, "SLSA v1.0 Gap Report\n"
fprintf(out, "SLSA Supply-Chain Gap Report\n"
"Project: %s v%s Level: %d Generated: %s\n"
"Satisfied: %d / %d objectives (%d gaps)\n\n",
cfg.project, cfg.version, level, ts,
Expand All @@ -202,6 +207,9 @@
return 3;
}

if (output && out != stdout) fclose(out);
if (output && out != stdout) {
fclose(out);

Check warning

Code scanning / cfusa

return value of 'fclose' may be unchecked — system call failures must be handled (CERT-C ERR33-C) Warning

return value of 'fclose' may be unchecked — system call failures must be handled (CERT-C ERR33-C)
fprintf(stderr, "SLSA gap report written to %s\n", output);
}
return gaps > 0 ? 1 : 0;
}
4 changes: 2 additions & 2 deletions include/cfusa/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

#define CFUSA_VERSION_MAJOR 0
#define CFUSA_VERSION_MINOR 5
#define CFUSA_VERSION_PATCH 27
#define CFUSA_VERSION_STRING "0.5.27"
#define CFUSA_VERSION_PATCH 28
#define CFUSA_VERSION_STRING "0.5.28"
#define CFUSA_SCHEMA_VERSION "1.9"
#define CFUSA_SPEC_VERSION "1.9"

Expand Down
104 changes: 104 additions & 0 deletions tests/test_cli_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,102 @@
TEST_ASSERT_EQUAL(3, rc);
}

/* ---- slsa --level L1/L2/L3/L4 format ---- */

//cfusa:req REQ-SLSA-LEVEL001
//cfusa:test REQ-SLSA-LEVEL001
void test_slsa_level_l1_accepted(void)
{
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR, "--level", "L1", NULL};

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
int rc = cmd_slsa(5, argv);
TEST_ASSERT_TRUE(rc <= 1);
}

//cfusa:req REQ-SLSA-LEVEL001
//cfusa:test REQ-SLSA-LEVEL001
void test_slsa_bad_level_returns_2(void)
{
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR, "--level", "L5", NULL};

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
int rc = cmd_slsa(5, argv);
TEST_ASSERT_EQUAL(2, rc);
}

/* ---- slsa --output writes file ---- */

#define SLSA_OUT_TEST_FILE "/tmp/cfusa_slsa_out_test.json"

//cfusa:req REQ-SLSA-OUT001
//cfusa:test REQ-SLSA-OUT001
void test_slsa_output_writes_file(void)
{
remove(SLSA_OUT_TEST_FILE);
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR, "--format", "json",

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
"--output", SLSA_OUT_TEST_FILE, NULL};
int rc = cmd_slsa(7, argv);
TEST_ASSERT_TRUE(rc <= 1);
FILE *f = fopen(SLSA_OUT_TEST_FILE, "r");
TEST_ASSERT_NOT_NULL(f);
if (f) fclose(f);
}

//cfusa:req REQ-SLSA-OUT001
//cfusa:test REQ-SLSA-OUT001
void test_slsa_bad_output_returns_3(void)
{
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR,

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
"--output", "/nonexistent/dir/slsa.json", NULL};
int rc = cmd_slsa(5, argv);
TEST_ASSERT_EQUAL(3, rc);
}

/* ---- iec62443 --sl SL-x format and bad SL ---- */

//cfusa:req REQ-IEC62443-SL001
//cfusa:test REQ-IEC62443-SL001
void test_iec62443_sl_format_accepted(void)
{
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR, "--sl", "SL-1", NULL};

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
int rc = cmd_iec62443(5, argv);
TEST_ASSERT_TRUE(rc != 2);
}

//cfusa:req REQ-IEC62443-SL001
//cfusa:test REQ-IEC62443-SL001
void test_iec62443_bad_sl_returns_2(void)
{
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR, "--sl", "SL-5", NULL};

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
int rc = cmd_iec62443(5, argv);
TEST_ASSERT_EQUAL(2, rc);
}

/* ---- iec62443 --output writes file ---- */

#define IEC62443_OUT_TEST_FILE "/tmp/cfusa_iec62443_out_test.json"

//cfusa:req REQ-IEC62443-OUT001
//cfusa:test REQ-IEC62443-OUT001
void test_iec62443_output_writes_file(void)
{
remove(IEC62443_OUT_TEST_FILE);
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR, "--format", "json",

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
"--output", IEC62443_OUT_TEST_FILE, NULL};
int rc = cmd_iec62443(7, argv);
TEST_ASSERT_TRUE(rc <= 1);
FILE *f = fopen(IEC62443_OUT_TEST_FILE, "r");
TEST_ASSERT_NOT_NULL(f);
if (f) fclose(f);
}

//cfusa:req REQ-IEC62443-OUT001
//cfusa:test REQ-IEC62443-OUT001
void test_iec62443_bad_output_returns_3(void)
{
char *argv[] = {"cfusa", "--dir", CLI_TEST_DIR,

Check notice

Code scanning / cfusa

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4) Note test

pointer arithmetic detected — verify bounds (MISRA-C:2012 R18.4)
"--output", "/nonexistent/dir/iec62443.json", NULL};
int rc = cmd_iec62443(5, argv);
TEST_ASSERT_EQUAL(3, rc);
}

/* ---- init already-exists returns 2 ---- */

//cfusa:req REQ-INIT-EXISTS001
Expand Down Expand Up @@ -678,6 +774,14 @@
RUN_TEST(test_iso21434_bad_format_returns_3);
RUN_TEST(test_iec62443_bad_format_returns_3);
RUN_TEST(test_slsa_bad_format_returns_3);
RUN_TEST(test_slsa_level_l1_accepted);
RUN_TEST(test_slsa_bad_level_returns_2);
RUN_TEST(test_slsa_output_writes_file);
RUN_TEST(test_slsa_bad_output_returns_3);
RUN_TEST(test_iec62443_sl_format_accepted);
RUN_TEST(test_iec62443_bad_sl_returns_2);
RUN_TEST(test_iec62443_output_writes_file);
RUN_TEST(test_iec62443_bad_output_returns_3);
RUN_TEST(test_init_already_exists_returns_2);
RUN_TEST(test_init_module_flag_accepted);
RUN_TEST(test_sas_prepared_by);
Expand Down
Loading