Skip to content
Open
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
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: CI

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
perl-syntax:
name: Perl syntax check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Perl
uses: shogo82148/actions-setup-perl@v1
with:
perl-version: '5.36'
- name: Install cpanminus (for dependency installs)
run: |
sudo apt-get update
sudo apt-get install -y cpanminus
- name: Install Perl dependencies
run: |
set -e
cpanm --notest XML::TreePP Math::Round Excel::Writer::XLSX Data::Table Excel::Writer::XLSX::Chart Getopt::Std || true
- name: Verify Perl modules
run: |
set -e
perl -MXML::TreePP -e 'print "XML::TreePP OK\n"'
perl -MMath::Round -e 'print "Math::Round OK\n"'
perl -MExcel::Writer::XLSX -e 'print "Excel::Writer::XLSX OK\n"'
- name: Install Perl::Critic
run: |
set -e
cpanm --notest Perl::Critic
- name: Run Perl::Critic
run: |
set -e
FILES=$(git ls-files '*.pl' '*.pm')
if [ -n "$FILES" ]; then
echo "$FILES" | xargs perlcritic
else
echo "No Perl files to lint."
fi
- name: Check Perl syntax
run: |
set -e
if [ -f parse_nessus_xml.v24.pl ]; then
perl -c parse_nessus_xml.v24.pl
else
echo "No Perl scripts found to check."
fi
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,47 @@ This is a program to parse a series of Nessus XMLv2 files into a XLSX file. The
This script has been designed and maitained by Melcara.

For more information and questions please contact Cody Dumont cody@melcara.com
## Recent changes

- 2026-02-10 Added CI workflow to run Perl syntax checks and install Perl
dependencies with cpanm (branch: ci/add-github-actions, PR #1).
- 2026-02-10 Local development: Strawberry Perl and required CPAN modules
installed for developer convenience and local perl -c checks.

## Changes (since adding mock data)

- Added GitHub Actions CI step to install and run `Perl::Critic` (lint) and
verify Perl modules: changes in `.github/workflows/ci.yml`.
- Created a safe, non-sensitive mock Nessus XMLv2 file: `scan.nessus` for
functional testing of the parser.
- Fixed `parse_nessus_xml.v24.pl` to use lexical filehandles / three-arg
`open` calls and to avoid exiting when encountering unknown plugin
families so mock data can be parsed during tests.
- Added an SBOM in CycloneDX JSON format: `sbom.cyclonedx.json` listing
detected Perl module dependencies and key files.
- Opened PR `ci/add-github-actions -> master` to introduce these changes.

## Project ToDo (short)

- Review and tune `Perl::Critic` policy/severity in CI so lint failures are
actionable but not blocking for non-critical style issues.
- Lock dependency versions and add a `cpanfile` (or equivalent) with exact
module versions for reproducible installs; update `sbom.cyclonedx.json`
with exact versions.
- Add automated SBOM generation to CI so the SBOM stays up-to-date on
accepted merges.
- Add small unit / integration tests that run the parser against
`scan.nessus` and assert an XLSX is created and contains expected sheets
(e.g., `UserAccountData`, `Summary Report Data`).
- Add `LICENSE` and CONTRIBUTING guidelines if desired.

## Todo by kalvinparker

- **License:** Ensure a LICENSE file is present and matches project intent.
- **Contributing:** Open pull requests from topic branches; include a clear
description and tests for new behavior.
- **CI required:** All PRs must pass CI (lint/tests) before merging.
- **Commit messages:** Use concise, conventional-style commit messages
(e.g. eat:, ix:, chore:).
- **Security:** Report vulnerabilities privately to the repository owner or
the email listed in the repo metadata.
Binary file added nessus_report_20260210233823.xlsx
Binary file not shown.
30 changes: 15 additions & 15 deletions parse_nessus_xml.v24.pl → parse_nessus_xml.v26.01.pl
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,9 @@
elsif($opt{"d"}){
$dir = $opt{"d"};
print "The target directory is \"$dir\"\.\n";
opendir DIR, $dir;
my @files = readdir(DIR);
closedir DIR;
opendir my $dh, $dir or die "Cannot opendir '$dir': $!";
my @files = readdir $dh;
closedir $dh;
my @xml = grep {$_ =~ /((xml)|(XML)|(nessus))$/} @files;
#@xml_files = grep {$_ !~ /^\./} @xml_files;
my @verified;
Expand All @@ -220,11 +220,11 @@

foreach (@xml){
my $f = "$dir/$_";
open FILE, $f;
my $tmp_data = <FILE>;
close FILE;
if($tmp_data =~ /(NessusClientData_v2)/m){print "File $_ is a Valid Nessus Ver2 format and will be parsed.\n\n";push @verified,$f}
else{print "This file \"$_\" is not using the Nessus version 2 format, and will NOT be parsed!!!\n\n";}
open my $fh, '<', $f or die "Can't open $f: $!";
my $tmp_data = <$fh>;
close $fh;
if($tmp_data =~ /(NessusClientData_v2)/m){ print "File $_ is a Valid Nessus Ver2 format and will be parsed.\n\n"; push @verified, $f }
else { print "This file \"$_\" is not using the Nessus version 2 format, and will NOT be parsed!!!\n\n"; }
}
# end of foreach (@xml)
$/ = $eol_marker;
Expand All @@ -235,9 +235,9 @@
print "The target file is \"$target_file\"\.\n";
my $eol_marker = $/;
undef $/;
open FILE, $target_file;
my $tmp_data = <FILE>;
close FILE;
open my $fh, '<', $target_file or die "Can't open $target_file: $!";
my $tmp_data = <$fh>;
close $fh;
if($tmp_data =~ /(NessusClientData_v2)/m){
print "File $target_file is a Valid Nessus Ver2 format and will be parsed.\n\n";
my @dirs = split /\\|\//,$target_file;
Expand All @@ -258,9 +258,9 @@
if($opt{"r"}){
my $recast_file = $opt{"r"};
print "The recast option is selected, the recast definition file is \"$recast_file\"\.\nPlease note all the following Plugin ID's will have thier severity changed accordingly.\n\n";
open FILE, $recast_file or die "Can't open the $recast_file file\n";
my @tmp_data = <FILE>;
close FILE;
open my $fh, '<', $recast_file or die "Can't open the $recast_file file\n";
my @tmp_data = <$fh>;
close $fh;
chomp @tmp_data;
print "PLUGIN ID\tOLD SEV\tNEW SEV\n";
foreach my $p (@tmp_data){
Expand Down Expand Up @@ -1218,7 +1218,7 @@ sub normalizeHostData {
elsif($h_report->{'-pluginFamily'} =~ /Windows/){push @windows, $h_report;}
elsif($h_report->{'-pluginFamily'} =~ /Incident Response/){push @IncidentResponse, $h_report;}
elsif($h_report->{'-pluginFamily'} eq ""){push @port_scan, $h_report;}
else{ print "\nThere is a new plugin family added, it is $h_report->{'-pluginFamily'}\n";exit;}
else{ push @general, $h_report; }

if ($h_report->{cvss_base_score} || $h_report->{cvss_vector} || $h_report->{cvss_temporal_score}) {
if (not defined $cvss_score{$host->{"host-ip"}}) {
Expand Down
43 changes: 43 additions & 0 deletions sbom.cyclonedx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": "2026-02-10T23:50:00Z",
"tools": [
{ "vendor": "local", "name": "ksyp_nessus-parser-sbom-generator", "version": "0.1" }
],
"component": {
"type": "application",
"name": "ksyp_nessus-parser",
"version": "bc6bbeb",
"purl": "pkg:git/kalvinparker/ksyp_nessus-parser@bc6bbeb"
}
},
"components": [
{ "type": "library", "name": "XML::TreePP", "purl": "pkg:cpan/XML-TreePP" },
{ "type": "library", "name": "Data::Dumper", "purl": "pkg:cpan/Data-Dumper" },
{ "type": "library", "name": "Math::Round", "purl": "pkg:cpan/Math-Round" },
{ "type": "library", "name": "Excel::Writer::XLSX", "purl": "pkg:cpan/Excel-Writer-XLSX" },
{ "type": "library", "name": "Excel::Writer::XLSX::Chart", "purl": "pkg:cpan/Excel-Writer-XLSX-Chart" },
{ "type": "library", "name": "Data::Table", "purl": "pkg:cpan/Data-Table" },
{ "type": "library", "name": "Getopt::Std", "purl": "pkg:cpan/Getopt-Std" },
{ "type": "tool", "name": "Perl::Critic", "purl": "pkg:cpan/Perl-Critic", "scope": "development" },

{ "type": "file", "name": "parse_nessus_xml.v24.pl", "purl": "pkg:generic/parse_nessus_xml.v24.pl" },
{ "type": "file", "name": "scan.nessus", "purl": "pkg:generic/scan.nessus" },
{ "type": "file", "name": ".github/workflows/ci.yml", "purl": "pkg:generic/.github/workflows/ci.yml" }
],
"dependencies": [
{ "ref": "pkg:git/kalvinparker/ksyp_nessus-parser@bc6bbeb", "dependsOn": [
"pkg:cpan/XML-TreePP",
"pkg:cpan/Data-Dumper",
"pkg:cpan/Math-Round",
"pkg:cpan/Excel-Writer-XLSX",
"pkg:cpan/Excel-Writer-XLSX-Chart",
"pkg:cpan/Data-Table",
"pkg:cpan/Getopt-Std",
"pkg:cpan/Perl-Critic"
] }
]
}
77 changes: 77 additions & 0 deletions scan.nessus
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<NessusClientData_v2>
<Policy>
<policyName>Mock Policy - Internal Assessment</policyName>
<policyComments>Mock policy used for functional testing only</policyComments>
<policyFamilyTags>
<family>Authentication</family>
<family>Configuration</family>
<family>Network Services</family>
</policyFamilyTags>
</Policy>

<Report name="Mock Scan Report">
<ReportHost name="host-001.example.local">
<HostProperties>
<tag name="HOST_END_TIME">2026-02-10T12:00:00Z</tag>
<tag name="HOST_START_TIME">2026-02-10T11:45:00Z</tag>
<tag name="operating-system">MockOS 1.0</tag>
<tag name="mac-address">00:11:22:33:44:55</tag>
</HostProperties>

<!-- Summary data (safe mock values) -->
<ReportItem pluginName="Summary - Mock" pluginFamily="General" pluginID="99999" severity="0" port="0" protocol="tcp">
<description>Summary of the mock scan results (no sensitive data).</description>
<output>
Total Hosts: 1\nTotal Findings: 3\nHigh: 0\nMedium: 1\nLow: 2
</output>
</ReportItem>

<!-- Policy plugin family example -->
<ReportItem pluginName="Weak Password Policy Detected (mock)" pluginFamily="General" pluginID="10001" severity="2" port="0" protocol="tcp">
<description>This mock finding represents a policy-family result (Authentication).</description>
<solution>Review password policy and ensure minimum complexity is enforced.</solution>
<output>Account policy: minimum length 6 (mock value)</output>
<see_also>https://example.com/mock-guidance</see_also>
</ReportItem>

<!-- User account reporting example -->
<ReportItem pluginName="Local User Accounts (mock report)" pluginFamily="General" pluginID="20002" severity="1" port="0" protocol="tcp">
<description>Mock user account enumeration for testing user account reporting features.</description>
<output>
User: `mock_admin`\nStatus: Enabled\nLastLogin: 2026-01-01T00:00:00Z\nNotes: sample, non-production account
</output>
</ReportItem>

<!-- Network service example -->
<ReportItem pluginName="Open Port (mock)" pluginFamily="General" pluginID="30003" severity="1" port="22" protocol="tcp">
<description>Mock open service used to exercise port scanning and service reporting.</description>
<output>Service: ssh (mock) - banner suppressed</output>
</ReportItem>

</ReportHost>

<!-- Another host with different mock data -->
<ReportHost name="host-002.example.local">
<HostProperties>
<tag name="HOST_END_TIME">2026-02-10T12:05:00Z</tag>
<tag name="HOST_START_TIME">2026-02-10T11:50:00Z</tag>
<tag name="operating-system">MockOS 2.0</tag>
</HostProperties>

<ReportItem pluginName="Policy Family - Configuration (mock)" pluginFamily="Configuration" pluginID="10002" severity="0" port="0" protocol="tcp">
<description>A mock non-critical configuration check.</description>
<output>Configuration check: pass (mock)</output>
</ReportItem>

<ReportItem pluginName="Local Users Found (mock)" pluginFamily="Configuration" pluginID="20003" severity="1" port="0" protocol="tcp">
<description>Another mock user account entry for testing.</description>
<output>
User: `backup_user`\nStatus: Disabled\nNotes: sample account
</output>
</ReportItem>

</ReportHost>

</Report>
</NessusClientData_v2>
Binary file added ~$nessus_report_20260210233823.xlsx
Binary file not shown.