diff --git a/CHANGELOG.md b/CHANGELOG.md
index eea688a2..f6485155 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [3.0.1] - 2025-07-11
+
+- Added agent language as filter to avoid displaying incompatible modules
+- Added folder display to agent modules
+
## [3.0.0] - 2025-03-25
### Added
@@ -411,7 +416,9 @@ Including but not limited to:
- Initial Release
-[Unreleased]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v3.0.0...HEAD
+[Unreleased]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v3.0.1...HEAD
+
+[3.0.1]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v3.0.0...v3.0.1
[3.0.0]: https://github.com/BC-SECURITY/Starkiller-Sponsors/compare/v2.8.2...v3.0.0
diff --git a/package.json b/package.json
index 33b20637..38a775c7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "starkiller",
- "version": "3.0.0",
+ "version": "3.0.1",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/src/components/agents/AgentExecuteModule.vue b/src/components/agents/AgentExecuteModule.vue
index b5e4d56e..333f280d 100644
--- a/src/components/agents/AgentExecuteModule.vue
+++ b/src/components/agents/AgentExecuteModule.vue
@@ -9,19 +9,71 @@
- Executing on Agents: {{ agents.join(", ") }}
+ Executing on Agents:
+ {{ agents.map((agent) => agent.name || agent.session_id).join(", ") }}
-
+
+
+ No modules are compatible with all selected agents.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Browse Modules
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+
Submit
+
@@ -158,6 +214,9 @@ export default {
errorState: false,
ignoreAdminCheck: false,
ignoreLanguageCheck: false,
+ treeSelected: [],
+ treeOpen: [],
+ showTreeDialog: false,
};
},
computed: {
@@ -165,30 +224,106 @@ export default {
return useModuleStore();
},
modules() {
- return this.moduleStore.modules.filter((el) => el.enabled === true);
+ return this.moduleStore.modules.filter((el) => el.enabled);
+ },
+ compatibleModules() {
+ if (!this.agents.length) return this.modules;
+ const getCompatibleLanguages = (agentLang) => {
+ switch ((agentLang || "").toLowerCase()) {
+ case "powershell":
+ case "csharp":
+ case "go":
+ return ["powershell", "csharp", "bof"];
+ case "ironpython":
+ return ["powershell", "csharp", "bof", "python"];
+ case "python":
+ return ["python"];
+ default:
+ return [];
+ }
+ };
+ const agentLangSets = this.agents.map((agent) =>
+ getCompatibleLanguages(agent.language || agent.type),
+ );
+ const sharedLangs = agentLangSets.reduce((acc, curr) =>
+ acc.filter((lang) => curr.includes(lang)),
+ );
+ return this.modules.filter((mod) =>
+ sharedLangs.includes(mod.language?.toLowerCase()),
+ );
},
- selectOptions() {
- return this.modules.map((el) => el.id);
+ autocompleteOptions() {
+ return this.compatibleModules.map((mod) => ({
+ text: mod.id,
+ value: mod.id,
+ }));
},
- moduleOptions() {
- const options = this.selectedItem.options || {};
+ treeOptions() {
+ const structure = {};
+ this.compatibleModules.forEach((mod) => {
+ const parts = mod.id.split("_");
+ const language = parts[0] || "unknown";
- if (options && options.Agent) {
- delete options.Agent;
- }
+ let category = parts[1] || "uncategorized";
+ if (
+ parts.length >= 3 &&
+ ["situational", "lateral", "code"].includes(parts[1])
+ ) {
+ const joined = `${parts[1]}_${parts[2]}`;
+ if (
+ [
+ "situational_awareness",
+ "lateral_movement",
+ "code_execution",
+ ].includes(joined)
+ ) {
+ category = joined;
+ }
+ }
+
+ if (!structure[language]) structure[language] = {};
+ if (!structure[language][category]) structure[language][category] = [];
+ structure[language][category].push({
+ id: mod.id,
+ name: mod.id,
+ });
+ });
+ return Object.entries(structure)
+ .sort(([langA], [langB]) => langA.localeCompare(langB)) // Sort languages
+ .map(([lang, categories]) => {
+ const children = Object.entries(categories)
+ .sort(([catA], [catB]) => catA.localeCompare(catB)) // Sort categories
+ .flatMap(([cat, modules]) => {
+ modules.sort((a, b) => a.name.localeCompare(b.name)); // Sort modules
+
+ if (modules.length === 1) {
+ return modules;
+ }
+ return {
+ id: `${lang}_${cat}`,
+ name: cat,
+ children: modules,
+ };
+ });
+
+ return {
+ id: lang,
+ name: lang,
+ children,
+ };
+ });
+ },
+ moduleOptions() {
+ const options = this.selectedItem.options || {};
+ if (options.Agent) delete options.Agent;
Object.keys(this.moduleOptionDefaults || {}).forEach((key) => {
- if (options[key]) {
- options[key].value = this.moduleOptionDefaults[key];
- }
+ if (options[key]) options[key].value = this.moduleOptionDefaults[key];
});
return options;
},
moduleInfo() {
- if (Object.keys(this.selectedItem).length === 0) {
- return {};
- }
-
+ if (Object.keys(this.selectedItem).length === 0) return {};
return {
authors: this.selectedItem.authors,
description: this.selectedItem.description,
@@ -238,6 +373,9 @@ export default {
await this.moduleStore.getModules();
},
methods: {
+ toggleTree() {
+ this.showTreeDialog = true;
+ },
async handleSelect(item) {
this.errorState = false;
if (!item) {
@@ -258,9 +396,19 @@ export default {
this.reset = true;
}, 500);
},
+ handleTreeSelect(selected) {
+ if (selected.length > 0) {
+ const moduleId = selected[0];
+ const match = this.compatibleModules.find((m) => m.id === moduleId);
+ if (match) {
+ this.selectedModule = moduleId;
+ this.handleSelect(moduleId);
+ this.showTreeDialog = false;
+ }
+ }
+ },
rowClass(item) {
- if (item.status === "rejected") return "red";
- return "";
+ return item.status === "rejected" ? "red" : "";
},
emitModuleChange(newVal) {
this.$emit("moduleChange", newVal);
@@ -269,26 +417,21 @@ export default {
return this.$refs.generalform.$refs.form.validate();
},
async create() {
- if (this.agents.length < 1 || this.loading || !this.validate()) {
- return;
- }
-
+ if (this.agents.length < 1 || this.loading || !this.validate()) return;
this.loading = true;
-
const result = await Promise.allSettled(
this.agents.map((agent) =>
moduleApi.executeModule(
this.selectedModule,
{
...this.form,
- Agent: agent,
+ Agent: agent.session_id,
},
this.ignoreAdminCheck,
this.ignoreLanguageCheck,
),
),
);
-
if (result.some((item) => item.status === "rejected")) {
const split = result.reduce(
(acc, val) => {
@@ -297,7 +440,6 @@ export default {
},
{ rejected: [], fulfilled: [] },
);
-
if (this.agents.length > 1) {
this.$snack.warn(
`Module failed to execute for ${split.rejected.length} out of ${this.agents.length} agents.`,
@@ -317,15 +459,21 @@ export default {
this.selectedModule = "";
this.$emit("submitted");
}
-
this.loading = false;
},
},
};
-
diff --git a/src/views/AgentEdit.vue b/src/views/AgentEdit.vue
index 43402fb1..46694c6d 100644
--- a/src/views/AgentEdit.vue
+++ b/src/views/AgentEdit.vue
@@ -153,8 +153,8 @@
Execute Module
-
+
+