-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Reorganize profile section: Categorized Accordion Layout for Profiles & Scan Methods #1474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
4c5312e
c7347fa
4a3f622
3c2a866
d99e6f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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) | ||||||
|
|
||||||
| # 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> """.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}"> | ||||||
|
||||||
| <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
AI
Mar 30, 2026
There was a problem hiding this comment.
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.
| <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
AI
Mar 30, 2026
There was a problem hiding this comment.
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
AI
Mar 30, 2026
There was a problem hiding this comment.
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.
| <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
AI
Mar 30, 2026
There was a problem hiding this comment.
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.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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
doneRepository: 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 Add these keys to all 23 locale files with appropriate translations. 🤖 Prompt for AI Agents |
||
| 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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
|
||
|
|
||
| // 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 | ||
|
|
@@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential error if At line 683, This could occur if:
🛡️ 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 |
||
|
|
||
| $(".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
|
||
|
|
||
| $(".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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. References to non-existent element IDs These handlers attempt to uncheck elements with IDs These 🔧 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 |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: OWASP/Nettacker
Length of output: 5248
🏁 Script executed:
sed -n '222,241p' nettacker/api/core.pyRepository: OWASP/Nettacker
Length of output: 622
🏁 Script executed:
sed -n '243,260p' nettacker/api/core.pyRepository: 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