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
179 changes: 141 additions & 38 deletions nettacker/api/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,22 +213,83 @@ def profiles():
Returns:
HTML content or available profiles
"""
res = ""
for profile in sorted(Nettacker.load_profiles().keys()):
label = (
"success"
if (profile == "scan")
else "warning"
if (profile == "brute")
else "danger"
if (profile == "vulnerability")
else "default"
)
all_profiles = Nettacker.load_profiles()
if "all" in all_profiles:
del all_profiles["all"]
if "..." in all_profiles:
del all_profiles["..."]

categories = {
"scan": {
"title": _("scan_modules_title"),
"desc": _("scan_modules_desc"),
"label": "success",
"profiles": [],
},
"brute": {
"title": _("brute_modules_title"),
"desc": _("brute_modules_desc"),
"label": "warning",
"profiles": [],
},
"vuln": {
"title": _("vuln_modules_title"),
"desc": _("vuln_modules_desc"),
"label": "danger",
"profiles": [],
},
}

for profile in sorted(all_profiles.keys()):
modules = all_profiles[profile]
cats = set(m.split("_")[-1] for m in modules)

for cat in cats:
if cat in categories:
categories[cat]["profiles"].append(profile)
elif cat == "vulnerability" or cat == "vuln":
categories["vuln"]["profiles"].append(profile)
Comment on lines +243 to +251
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find modules that don't end with _scan, _brute, _vuln, or _vulnerability
ast-grep --pattern 'load_modules()' 
# Alternatively, check actual module names
fd -e yaml . nettacker/modules/ --exec basename {} .yaml | while read mod; do
  suffix="${mod##*_}"
  if [[ "$suffix" != "scan" && "$suffix" != "brute" && "$suffix" != "vuln" && "$suffix" != "vulnerability" ]]; then
    echo "Uncategorized module: $mod"
  fi
done

Repository: OWASP/Nettacker

Length of output: 5248


🏁 Script executed:

sed -n '222,241p' nettacker/api/core.py

Repository: OWASP/Nettacker

Length of output: 622


🏁 Script executed:

sed -n '243,260p' nettacker/api/core.py

Repository: OWASP/Nettacker

Length of output: 701


Module suffix mismatch causes silent exclusion from profile categorization.

The category derivation logic at line 245 (m.split("_")[-1]) extracts the last underscore-separated segment. However, the code only recognizes specific suffixes: "scan", "brute", "vuln", or "vulnerability" (lines 247-249). Modules with other suffixes (e.g., "wp_xmlrpc_bruteforce" → "bruteforce", "ssl_weak_version" → "version") are silently skipped and will not appear in the profile accordion UI. Over 180 existing modules are affected by this.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/api/core.py` around lines 243 - 251, The code that derives
categories from module names (loop over all_profiles -> modules -> cats) only
checks exact suffixes and ignores variants like "bruteforce" or "version";
update the classification logic in that block to normalize and map suffixes
using substring/startswith checks (e.g., if suffix.startswith("brute") or
"brute" in suffix -> treat as "brute"; if suffix.startswith("scan") or "scan" in
suffix -> "scan"; if "vuln" in suffix or suffix == "vulnerability" -> "vuln";
otherwise fall back to known categories or a default) so profiles are appended
to categories["..."]["profiles"] consistently; modify the code that computes
cats from modules (referenced variables: all_profiles, modules, cats,
categories, profile) to perform this normalized mapping rather than exact
equality checks.


# Dedup and sort
for cat in categories:
categories[cat]["profiles"] = sorted(list(set(categories[cat]["profiles"])))

res = """
<div class="panel-group" id="profile_accordion">
"""
for cat_name, cat_info in categories.items():
res += """
<label><input id="{0}" type="checkbox" class="checkbox checkbox-{0}">
<a class="label label-{1}">{0}</a></label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;""".format(
profile, label
<div class="panel panel-default">
<div class="panel-heading" style="cursor: pointer;" data-toggle="collapse" data-parent="#profile_accordion" href="#collapse_{0}">
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

The collapse toggle is implemented by putting an href attribute on a <div class="panel-heading" ... href="#collapse_{0}">, but href is not a valid attribute for div elements. This is invalid HTML and can lead to inconsistent behavior across browsers/assistive tech.

Prefer data-target="#collapse_{0}" (or wrap the clickable area in an <a> element with href) while keeping data-toggle="collapse".

Suggested change
<div class="panel-heading" style="cursor: pointer;" data-toggle="collapse" data-parent="#profile_accordion" href="#collapse_{0}">
<div class="panel-heading" style="cursor: pointer;" data-toggle="collapse" data-parent="#profile_accordion" data-target="#collapse_{0}">

Copilot uses AI. Check for mistakes.
<h4 class="panel-title">
<i class="fa fa-chevron-right"></i>
<span class="label label-{1}" style="margin-right: 10px;">{2}</span>
<small>{3}</small>
<label class="pull-right" onclick="event.stopPropagation();">
<input type="checkbox" class="check-all-category" data-category="{0}">
<small>{4}</small>
</label>
</h4>
</div>
<div id="collapse_{0}" class="panel-collapse collapse">
<div class="panel-body text-justify">
""".format(
cat_name, cat_info["label"], cat_info["title"], cat_info["desc"], _("select_all")
)

for profile in cat_info["profiles"]:
label_type = cat_info["label"]
res += """
<label><input id="{0}" type="checkbox" class="checkbox checkbox-{1}-profile" data-modules="{3}">
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

Profiles that belong to multiple categories are rendered once per category using the same id attribute (id="{profile}"). This produces duplicate DOM ids (invalid HTML) and will break selectors like $("#" + moduleId) / $("#mixed_profile") (jQuery returns the first match only), causing inconsistent checkbox syncing when a profile appears in more than one panel.

Use unique ids per rendered checkbox (e.g., prefix/suffix with the category) and store the actual profile name in a separate attribute (value or data-profile) so request-building and module-sync logic can still identify the profile reliably.

Suggested change
<label><input id="{0}" type="checkbox" class="checkbox checkbox-{1}-profile" data-modules="{3}">
<label><input id="{1}_{0}" type="checkbox" class="checkbox checkbox-{1}-profile" data-profile="{0}" data-modules="{3}">

Copilot uses AI. Check for mistakes.
<a class="label label-{2}">{0}</a></label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
""".format(profile, cat_name, label_type, ",".join(all_profiles[profile]))
Comment on lines +282 to +285
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

profile names and the data-modules attribute value are interpolated into HTML without escaping. Since module/profile identifiers ultimately come from YAML filenames/tags, a crafted name containing quotes/angle brackets could break attributes and allow HTML/JS injection in the web UI.

Escape values used in attributes/text (e.g., via html.escape(..., quote=True)) when rendering id, link text, and data-modules.

Copilot uses AI. Check for mistakes.

res += """
</div>
</div>
</div>
"""
res += "</div>"
return res


Expand All @@ -240,29 +301,71 @@ def scan_methods():
HTML content or available modules
"""
methods = Nettacker.load_modules()
methods.pop("all")
res = ""
for sm in methods.keys():
label = (
"success"
if sm.endswith("_scan")
else "warning"
if sm.endswith("_brute")
else "danger"
if sm.endswith("_vuln")
else "default"
)
profile = (
"scan"
if sm.endswith("_scan")
else "brute"
if sm.endswith("_brute")
else "vuln"
if sm.endswith("_vuln")
else "default"
)
res += """<label><input id="{0}" type="checkbox" class="checkbox checkbox-{2}-module">
<a class="label label-{1}">{0}</a></label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;""".format(
sm, label, profile
if "all" in methods:
methods.pop("all")

categories = {
"scan": {
"title": _("scan_modules_title"),
"desc": _("scan_modules_desc"),
"label": "success",
"modules": [],
},
"brute": {
"title": _("brute_modules_title"),
"desc": _("brute_modules_desc"),
"label": "warning",
"modules": [],
},
"vuln": {
"title": _("vuln_modules_title"),
"desc": _("vuln_modules_desc"),
"label": "danger",
"modules": [],
},
}

for sm in sorted(methods.keys()):
cat = sm.split("_")[-1]
if cat in categories:
categories[cat]["modules"].append(sm)
elif cat == "vulnerability":
categories["vuln"]["modules"].append(sm)

res = """
<div class="panel-group" id="scan_methods_accordion">
"""
for cat_name, cat_info in categories.items():
res += """
<div class="panel panel-default">
<div class="panel-heading" style="cursor: pointer;" data-toggle="collapse" data-parent="#scan_methods_accordion" href="#collapse_sm_{0}">
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

Same issue as above for scan methods: href is attached to a <div class="panel-heading" ...>, which is invalid HTML. Use data-target or an <a href> toggle element to keep the collapse behavior valid and consistent.

Suggested change
<div class="panel-heading" style="cursor: pointer;" data-toggle="collapse" data-parent="#scan_methods_accordion" href="#collapse_sm_{0}">
<div class="panel-heading" style="cursor: pointer;" data-toggle="collapse" data-parent="#scan_methods_accordion" data-target="#collapse_sm_{0}">

Copilot uses AI. Check for mistakes.
<h4 class="panel-title">
<i class="fa fa-chevron-right"></i>
<span class="label label-{1}" style="margin-right: 10px;">{2}</span>
<small>{3}</small>
<label class="pull-right" onclick="event.stopPropagation();">
<input type="checkbox" class="check-all-sm-category" data-category="{0}">
<small>{4}</small>
</label>
</h4>
</div>
<div id="collapse_sm_{0}" class="panel-collapse collapse">
<div class="panel-body text-justify">
""".format(
cat_name, cat_info["label"], cat_info["title"], cat_info["desc"], _("select_all")
)

for module in cat_info["modules"]:
label_type = cat_info["label"]
res += """
<label><input id="{0}" type="checkbox" class="checkbox checkbox-sm-{1}-module">
<a class="label label-{2}">{0}</a></label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
""".format(module, cat_name, label_type)
Comment on lines +360 to +363
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

Module names are interpolated into HTML (id and visible text) without escaping. While module ids are usually derived from internal filenames, escaping here would prevent attribute breakage/XSS if a module name ever contains special characters.

Consider applying HTML/attribute escaping when rendering {module} and the id attribute.

Copilot uses AI. Check for mistakes.

res += """
</div>
</div>
</div>
"""
res += "</div>"
return res
7 changes: 7 additions & 0 deletions nettacker/locale/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ API_invalid: invalid API key
API_key: " * API is accessible from https://nettacker-api.z3r0d4y.com:{0}/ via API Key: {1}"
API_options: API options
API_port: API port number
scan_modules_title: Scan Modules
scan_modules_desc: (Information Gathering and Reconnaissance)
brute_modules_title: Brute Force Modules
brute_modules_desc: (Authentication Attacks)
vuln_modules_title: Vulnerability Modules
vuln_modules_desc: (Exploit and Vulnerability Detection)
select_all: Select all
Comment on lines +12 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check which locale files are missing the new keys
for key in scan_modules_title scan_modules_desc brute_modules_title brute_modules_desc vuln_modules_title vuln_modules_desc select_all; do
  echo "=== Files missing key: $key ==="
  for f in $(fd -e yaml . nettacker/locale/); do
    if ! grep -q "^${key}:" "$f"; then
      echo "  $f"
    fi
  done
done

Repository: OWASP/Nettacker

Length of output: 4760


Add missing i18n keys to all 23 other locale files.

All 7 new keys (scan_modules_title, scan_modules_desc, brute_modules_title, brute_modules_desc, vuln_modules_title, vuln_modules_desc, select_all) are only present in en.yaml. They are missing from all other 23 locale files (ar, bn, de, el, es, fa, fr, hi, hy, id, it, iw, ja, ko, nl, ps, pt-br, ru, tr, ur, uz, vi, zh-cn). Since the code uses _() calls to translate these strings, users with non-English locale settings will see missing translations or key fallback behavior.

Add these keys to all 23 locale files with appropriate translations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/locale/en.yaml` around lines 12 - 18, Add the seven missing i18n
keys (scan_modules_title, scan_modules_desc, brute_modules_title,
brute_modules_desc, vuln_modules_title, vuln_modules_desc, select_all) to each
of the 23 locale YAML files (ar, bn, de, el, es, fa, fr, hi, hy, id, it, iw, ja,
ko, nl, ps, pt-br, ru, tr, ur, uz, vi, zh-cn); for each file add the same key
names and either provide the correct translated string or, if a translation is
not available yet, copy the English value from en.yaml as a placeholder so the
keys exist and the _() lookups no longer fall back or break. Ensure YAML
formatting matches existing files (key: value) and that the seven keys are
placed alongside the other module-related entries in each locale file.

Method: Method
skip_service_discovery: skip service discovery before scan and enforce all modules to scan anyway
no_live_service_found: no any live service found to scan.
Expand Down
141 changes: 120 additions & 21 deletions nettacker/web/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,20 +350,30 @@ $(document).ready(function () {
// profiles
var p = [];
var n = 0;
$("#profiles input:checked").each(function () {
if (this.id !== "all_profiles") {
$("#profiles input[type='checkbox']:checked").each(function () {
if (
this.id !== "all_profiles" &&
!$(this).hasClass("check-all-category")
) {
p[n] = this.id;
n += 1;
}
});
// Deduplicate profiles as they might appear in multiple categories
p = Array.from(new Set(p));
var profiles = p.join(",");
Comment on lines +353 to 364
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

The profile collection logic relies on this.id and then deduplicates ids via new Set(p). With the current backend change allowing a profile to be displayed in multiple categories, the DOM ends up with duplicate ids, and the current approach can't distinguish between multiple rendered checkboxes for the same logical profile.

Once profile checkboxes use unique DOM ids, collect the logical profile identifier from a dedicated attribute (value / data-profile) instead of id, and deduplicate based on that field.

Copilot uses AI. Check for mistakes.

// scan_methods
n = 0;
sm = [];
$("#selected_modules input:checked").each(function () {
sm[n] = this.id;
n += 1;
$("#selected_modules input[type='checkbox']:checked").each(function () {
if (
this.id !== "all" &&
!$(this).hasClass("check-all-sm-category")
) {
sm[n] = this.id;
n += 1;
}
});
var selected_modules = sm.join(",");
// language
Expand Down Expand Up @@ -666,48 +676,137 @@ $(document).ready(function () {
$(".checkbox").prop("checked", $(this).prop("checked"));
});

$(".checkbox-brute").click(function () {
$(".checkbox-brute-module").prop("checked", $(this).prop("checked"));
$(document).on("change", "#profiles input[type='checkbox']", function () {
if ($(this).hasClass("check-all-category") || this.id === "all_profiles") {
return;
}
var modules = $(this).data("modules").split(",");
var isChecked = $(this).prop("checked");
for (var i = 0; i < modules.length; i++) {
var moduleId = modules[i];
if (isChecked) {
$("#" + moduleId).prop("checked", true);
} else {
// Only uncheck if no other checked profile includes this module
var stillNeeded = false;
$("#profiles input[type='checkbox']:checked").each(function () {
if (
this.id !== "all_profiles" &&
!$(this).hasClass("check-all-category")
) {
var otherModules = $(this).data("modules").split(",");
if (otherModules.indexOf(moduleId) !== -1) {
stillNeeded = true;
return false;
}
}
});
if (!stillNeeded) {
$("#" + moduleId).prop("checked", false);
}
}
}
});
Comment on lines +679 to 709
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential error if data-modules attribute is missing or empty.

At line 683, $(this).data("modules").split(",") will throw an error if data-modules is undefined. Similarly at line 697, accessing .data("modules") on a checkbox without this attribute returns undefined, and calling .split(",") on it will throw TypeError: Cannot read property 'split' of undefined.

This could occur if:

  1. A profile checkbox somehow lacks the data-modules attribute
  2. The inner loop (line 692) iterates over checkboxes that aren't profile checkboxes
🛡️ Defensive fix
   $(document).on("change", "#profiles input[type='checkbox']", function () {
     if ($(this).hasClass("check-all-category") || this.id === "all_profiles") {
       return;
     }
-    var modules = $(this).data("modules").split(",");
+    var modulesData = $(this).data("modules");
+    if (!modulesData) return;
+    var modules = modulesData.split(",");
     var isChecked = $(this).prop("checked");
     for (var i = 0; i < modules.length; i++) {
       var moduleId = modules[i];
       if (isChecked) {
         $("#" + moduleId).prop("checked", true);
       } else {
         // Only uncheck if no other checked profile includes this module
         var stillNeeded = false;
         $("#profiles input[type='checkbox']:checked").each(function () {
           if (
             this.id !== "all_profiles" &&
             !$(this).hasClass("check-all-category")
           ) {
-            var otherModules = $(this).data("modules").split(",");
+            var otherData = $(this).data("modules");
+            if (!otherData) return true; // continue
+            var otherModules = otherData.split(",");
             if (otherModules.indexOf(moduleId) !== -1) {
               stillNeeded = true;
               return false;
             }
           }
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/web/static/js/main.js` around lines 679 - 709, The handler bound to
"#profiles input[type='checkbox']" calls $(this).data("modules").split(",") and
later uses $(this).data("modules") inside the checked-iteration, which will
throw if data-modules is missing or empty; update the handler to defensively
read the attribute (e.g., var modulesVal = $(this).data("modules"); if
(!modulesVal) continue/skip or set modules = []; and likewise when iterating
"#profiles input[type='checkbox']:checked" only process checkboxes that have a
non-empty data("modules") before calling .split(",") (use otherModules =
otherModulesVal ? otherModulesVal.split(",") : []), so moduleId comparisons and
stillNeeded logic skip missing data-modules entries.


$(".checkbox-scan").click(function () {
$(".checkbox-scan-module").prop("checked", $(this).prop("checked"));
$(".checkbox-brute-profile").click(function () {
if (this.id === "brute") {
$(".checkbox-sm-brute-module").prop("checked", $(this).prop("checked"));
}
});

$(".checkbox-vulnerability").click(function () {
$(".checkbox-vuln-module").prop("checked", $(this).prop("checked"));
$(".checkbox-scan-profile").click(function () {
if (this.id === "scan") {
$(".checkbox-sm-scan-module").prop("checked", $(this).prop("checked"));
}
});

$(".checkbox-vuln-profile").click(function () {
if (this.id === "vulnerability" || this.id === "vuln") {
$(".checkbox-sm-vuln-module").prop("checked", $(this).prop("checked"));
}
});
Comment on lines +711 to 727
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

These handlers run for every profile checkbox in the category but only do something when the checkbox id happens to be exactly scan/brute/vulnerability/vuln. In the reorganized UI, selecting profiles is already handled by the #profiles ... change handler; keeping these special-case click handlers adds dead/duplicated logic and makes behavior harder to reason about.

Consider removing these click handlers and (if needed) implement any special “select all modules” behavior via the existing category-level checkboxes or within the single change handler.

Copilot uses AI. Check for mistakes.
Comment on lines +711 to 727
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if any module yaml files define profiles with these exact names
rg -l '"scan"|"brute"|"vuln"|"vulnerability"' --type yaml nettacker/modules/ | head -20
# Also check the profile loading logic
rg -n 'profiles.*=.*\[' nettacker/modules/ --type yaml | grep -E '"(scan|brute|vuln|vulnerability)"' | head -10

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Find HTML templates that might define profiles
fd -type f -name "*.html" nettacker/web/ | head -20

Repository: OWASP/Nettacker

Length of output: 229


🏁 Script executed:

# Search for where profiles are defined or listed
rg -n "profile" nettacker/web/static/js/main.js -A 2 -B 2 | head -50

Repository: OWASP/Nettacker

Length of output: 1597


🏁 Script executed:

# Look for checkbox elements with IDs mentioned in the review
rg -n "checkbox.*brute|checkbox.*scan|checkbox.*vuln|id.*=.*['\"]brute['\"]|id.*=.*['\"]scan['\"]" nettacker/web/ --type html --type js

Repository: OWASP/Nettacker

Length of output: 1242


🏁 Script executed:

# Check if there are any profile-related data structures or definitions
rg -n "brute.*profile|scan.*profile|profile.*brute|profile.*scan" nettacker/web/ -A 2 -B 2 | head -40

Repository: OWASP/Nettacker

Length of output: 754


🏁 Script executed:

# Find the HTML file that contains the profile checkboxes
fd -type f -name "*.html" nettacker/web/

Repository: OWASP/Nettacker

Length of output: 229


🏁 Script executed:

# Look at the index.html file to see how profiles are structured
rg -n "checkbox.*profile|profiles" nettacker/web/static/index.html -A 3 -B 1 | head -80

Repository: OWASP/Nettacker

Length of output: 503


🏁 Script executed:

# Check if there are template files that generate these checkboxes
fd -type f -name "*.html" nettacker/web/templates/

Repository: OWASP/Nettacker

Length of output: 229


🏁 Script executed:

# Find Python files that might define or render profiles
fd -type f -name "*.py" nettacker/web/

Repository: OWASP/Nettacker

Length of output: 229


🏁 Script executed:

# Search for where the profile variable is generated in Python
rg -n "profile.*=" nettacker/web/ -A 2 | head -100

Repository: OWASP/Nettacker

Length of output: 1893


🏁 Script executed:

# Look for profile definitions or profile data
rg -n "profiles\s*=" nettacker/ --include="*.py" -A 5 | head -60

Repository: OWASP/Nettacker

Length of output: 500


🏁 Script executed:

# Find Python files in nettacker/web
find nettacker/web -name "*.py"

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Search for where the profile template variable is set
rg -n "profile\"\|\"profile" nettacker/web/ -A 3

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Look for Flask/Django views that render the index template
rg -n "render.*profile\|profile.*render" nettacker/web/ -A 2

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Let's look at the actual index.html file to see the full profile section
cat nettacker/web/static/index.html | grep -A 100 "id=\"profiles\"" | head -120

Repository: OWASP/Nettacker

Length of output: 5305


🏁 Script executed:

# Find all Python files in the web directory
find nettacker/web -name "*.py" -type f

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Search more broadly for where profiles are generated
rg "profile\s*=" nettacker/ -l

Repository: OWASP/Nettacker

Length of output: 114


🏁 Script executed:

# Look at the engine.py file to see how profiles are generated
rg -n "profile" nettacker/api/engine.py -B 2 -A 2 | head -60

Repository: OWASP/Nettacker

Length of output: 330


🏁 Script executed:

# Search for the web views/routes that render the index template
find nettacker -name "*.py" -path "*/web/*" -type f | xargs grep -l "render\|template\|index"

Repository: OWASP/Nettacker

Length of output: 43


🏁 Script executed:

# Look for where the profile HTML is generated
rg -n "checkbox.*profile\|checkbox-brute\|checkbox-scan\|checkbox-vuln" nettacker/

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Find where the profiles() function is defined
rg -n "def profiles" nettacker/

Repository: OWASP/Nettacker

Length of output: 100


🏁 Script executed:

# Check the imports in engine.py to see where profiles comes from
head -50 nettacker/api/engine.py | rg -n "from\|import"

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Search for profile generation across the codebase
rg -n "def profiles\|profiles\s*=" nettacker/ -A 5

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Look at the profiles() function implementation
sed -n '209,300p' nettacker/api/core.py

Repository: OWASP/Nettacker

Length of output: 3127


🏁 Script executed:

# Check the entire profiles() function
rg -n "def profiles" nettacker/api/core.py -A 80

Repository: OWASP/Nettacker

Length of output: 3311


🏁 Script executed:

# Look for actual profile names in the modules
find nettacker/modules -name "*.py" -type f | head -5

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Check what actual profiles exist
rg -n "load_profiles\|all_profiles\|profile.*=" nettacker/api/core.py -B 5 -A 10 | head -60

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Get the Nettacker class definition to understand profile loading
rg -n "class Nettacker\|def load_profiles" nettacker/ -A 20 | head -80

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Find where Nettacker class is defined
find nettacker -name "*.py" -type f | xargs grep -l "class Nettacker"

Repository: OWASP/Nettacker

Length of output: 80


🏁 Script executed:

# Search for .py files that might define profiles
find nettacker/modules -name "*.py" | head -10

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Look for JSON or YAML profile definitions
find nettacker -name "*.json" -o -name "*.yaml" -o -name "*.yml" | head -20

Repository: OWASP/Nettacker

Length of output: 561


🏁 Script executed:

# Look at the Nettacker class in app.py
rg -n "def load_profiles\|class Nettacker" nettacker/core/app.py -A 30 | head -100

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Search for any configuration files that might list profiles
find nettacker -name "profiles*" -o -name "*profile*" | grep -v "__pycache__" | grep -v ".pyc"

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Look for where profiles are actually stored or referenced
rg -n "\"drupal\"\|\"wordpress\"\|\"ssl\"" nettacker/ -l

Repository: OWASP/Nettacker

Length of output: 41


🏁 Script executed:

# Look at the check-all-category handler that the review mentions
rg -n "check-all-category" nettacker/web/static/js/main.js -B 2 -A 10

Repository: OWASP/Nettacker

Length of output: 2165


🏁 Script executed:

# Examine the full context around lines 711-727 and 739-745
sed -n '705,750p' nettacker/web/static/js/main.js

Repository: OWASP/Nettacker

Length of output: 1401


Remove these dead code click handlers or refactor for actual use.

These handlers check conditions like this.id === "brute" and this.id === "scan", but the id attributes on profile checkboxes are set to actual profile names (e.g., "drupal", "wordpress") generated by the profiles() function in nettacker/api/core.py. The conditional checks will never be true, making the inner logic unreachable.

Category-level selection is already handled properly by the .check-all-category change handler (line 739-745), which uses the data-category attribute to select all profiles in a category. Individual profile changes are handled by the change handler on line 679, which syncs modules via the data-modules attribute.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/web/static/js/main.js` around lines 711 - 727, Remove the dead
click handlers for ".checkbox-brute-profile", ".checkbox-scan-profile", and
".checkbox-vuln-profile" (the blocks checking this.id ===
"brute"/"scan"/"vulnerability") because profile checkbox IDs are actual profile
names from profiles() and those conditions are never true; either delete these
handlers or refactor them to use the existing data attributes (data-category or
data-modules) used by the ".check-all-category" change handler and the
individual profile change handler so category-level toggles and module-syncing
continue to work correctly (refer to the selectors ".check-all-category",
".checkbox-sm-*-module", and the profiles() generator in nettacker/api/core.py
when implementing the change).


$(".check-all-profiles").click(function () {
$("#profiles input[type='checkbox']").not(this).prop("checked", $(this).prop("checked"));
var isChecked = $(this).prop("checked");
$("#profiles input[type='checkbox']")
.not(this)
.not(".check-all-category")
.prop("checked", isChecked)
.trigger("change");
$(".check-all-category").prop("checked", isChecked);
});

$(document).on("change", ".check-all-category", function () {
var category = $(this).data("category");
var isChecked = $(this).prop("checked");
$(".checkbox-" + category + "-profile")
.prop("checked", isChecked)
.trigger("change");
});

$(document).on("show.bs.collapse", "#profile_accordion", function (e) {
$(e.target)
.prev(".panel-heading")
.find(".fa")
.removeClass("fa-chevron-right")
.addClass("fa-chevron-down");
});

$(document).on("hide.bs.collapse", "#profile_accordion", function (e) {
$(e.target)
.prev(".panel-heading")
.find(".fa")
.removeClass("fa-chevron-down")
.addClass("fa-chevron-right");
});

$(".check-all-scans").click(function () {
$(".checkbox-brute-module").prop("checked", $(this).prop("checked"));
$(".checkbox-scan-module").prop("checked", $(this).prop("checked"));
$(".checkbox-vuln-module").prop("checked", $(this).prop("checked"));
$("#selected_modules input[type='checkbox']").not(this).prop("checked", $(this).prop("checked"));
$(".check-all-sm-category").prop("checked", $(this).prop("checked"));
});

$(document).on("change", ".check-all-sm-category", function () {
var category = $(this).data("category");
$(".checkbox-sm-" + category + "-module").prop("checked", $(this).prop("checked"));
});

$(document).on("show.bs.collapse", "#scan_methods_accordion", function (e) {
$(e.target)
.prev(".panel-heading")
.find(".fa")
.removeClass("fa-chevron-right")
.addClass("fa-chevron-down");
});

$(document).on("hide.bs.collapse", "#scan_methods_accordion", function (e) {
$(e.target)
.prev(".panel-heading")
.find(".fa")
.removeClass("fa-chevron-down")
.addClass("fa-chevron-right");
});

$(".checkbox-vuln-module").click(function () {
$(document).on("click", ".checkbox-sm-vuln-module", function () {
if (!$(this).is(":checked")) {
$(".checkAll").prop("checked", false);
$(".checkbox-vulnerability").prop("checked", false);
$("#vulnerability").prop("checked", false);
$("#vuln").prop("checked", false);
$(".check-all-scans").prop("checked", false);
}
});

$(".checkbox-scan-module").click(function () {
$(document).on("click", ".checkbox-sm-scan-module", function () {
if (!$(this).is(":checked")) {
$(".checkAll").prop("checked", false);
$(".checkbox-scan").prop("checked", false);
$("#scan").prop("checked", false);
$(".check-all-scans").prop("checked", false);
}
});

$(".checkbox-brute-module").click(function () {
$(document).on("click", ".checkbox-sm-brute-module", function () {
if (!$(this).is(":checked")) {
$(".checkAll").prop("checked", false);
$(".checkbox-brute").prop("checked", false);
$("#brute").prop("checked", false);
$(".check-all-scans").prop("checked", false);
}
});
Comment on lines +789 to 812
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

References to non-existent element IDs #scan, #brute, #vulnerability, #vuln.

These handlers attempt to uncheck elements with IDs #scan, #brute, #vulnerability, and #vuln, but according to the HTML generation in nettacker/api/core.py:296-371, no elements with these exact IDs are created. The scan_methods() function generates checkboxes with IDs matching module names (e.g., id="ssh_brute") and category-level checkboxes use class="check-all-sm-category" with data-category attributes.

These .prop('checked', false) calls silently no-op since the selectors match nothing.

🔧 Proposed fix - use category checkbox selectors
   $(document).on("click", ".checkbox-sm-vuln-module", function () {
     if (!$(this).is(":checked")) {
       $(".checkAll").prop("checked", false);
-      $("#vulnerability").prop("checked", false);
-      $("#vuln").prop("checked", false);
+      $(".check-all-sm-category[data-category='vuln']").prop("checked", false);
       $(".check-all-scans").prop("checked", false);
     }
   });

   $(document).on("click", ".checkbox-sm-scan-module", function () {
     if (!$(this).is(":checked")) {
       $(".checkAll").prop("checked", false);
-      $("#scan").prop("checked", false);
+      $(".check-all-sm-category[data-category='scan']").prop("checked", false);
       $(".check-all-scans").prop("checked", false);
     }
   });

   $(document).on("click", ".checkbox-sm-brute-module", function () {
     if (!$(this).is(":checked")) {
       $(".checkAll").prop("checked", false);
-      $("#brute").prop("checked", false);
+      $(".check-all-sm-category[data-category='brute']").prop("checked", false);
       $(".check-all-scans").prop("checked", false);
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/web/static/js/main.js` around lines 789 - 812, Event handlers for
.checkbox-sm-vuln-module, .checkbox-sm-scan-module and .checkbox-sm-brute-module
are trying to uncheck non-existent IDs (`#scan`, `#brute`, `#vulnerability`, `#vuln`);
update those handlers to target the actual category-level checkbox selectors
used by the generator—i.e., use the class-based category checkbox selector
(class "check-all-sm-category" with the appropriate data-category attribute) and
the existing .checkAll / .check-all-scans classes instead of the missing IDs so
unchecking a module properly unchecks its category checkbox (locate the handlers
for .checkbox-sm-vuln-module, .checkbox-sm-scan-module and
.checkbox-sm-brute-module and replace the ID selectors with the corresponding
".check-all-sm-category[data-category='...']" selectors for each category).

Expand Down
Loading
Loading