From bc1961d76384bcd76a58b32e48a1ffa8cf6a2de8 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Wed, 25 Jun 2025 18:57:17 -0400 Subject: [PATCH 01/13] Pull from PR#3101 * --- app/api/v2/managers/operation_api_manager.py | 14 ++++++++++---- app/service/planning_svc.py | 11 +++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index e7409a62f..cdae1d57c 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -98,10 +98,11 @@ async def create_potential_link(self, operation_id: str, data: dict, access: Bas agent = await self.get_agent(operation, data) if data['executor']['name'] not in agent.executors: raise JsonHttpBadRequest(f'Agent {agent.paw} missing specified executor') - encoded_command = self._encode_string(agent.replace(self._encode_string(data['executor']['command']), - file_svc=self.services['file_svc'])) executor = self.build_executor(data=data.pop('executor', {}), agent=agent) ability = self.build_ability(data=data.pop('ability', {}), executor=executor) + await self._call_ability_plugin_hooks(ability, executor) + encoded_command = self._encode_string(agent.replace(self._encode_string(data['executor']['command']), + file_svc=self.services['file_svc'])) link = Link.load(dict(command=encoded_command, plaintext_command=encoded_command, paw=agent.paw, ability=ability, executor=executor, status=operation.link_status(), score=data.get('score', 0), jitter=data.get('jitter', 0), cleanup=data.get('cleanup', 0), pin=data.get('pin', 0), @@ -146,7 +147,7 @@ async def setup_operation(self, data: dict, access: BaseWorld.Access): data['source'] = await self._construct_and_dump_source(fact_source_id) operation = OperationSchema().load(data) await operation.update_operation_agents(self.services) - allowed = self._get_allowed_from_access(access) + allowed = self._get_allowed_from_access(access)d operation.access = allowed return operation @@ -171,6 +172,11 @@ async def _construct_and_dump_source(self, source_id: str): source = (await self.services['data_svc'].locate('sources', match=dict(name='basic'))) return SourceSchema().dump(source[0]) + async def _call_ability_plugin_hooks(self, ability, executor): + """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" + for _hook, fcall in executor.HOOKS.items(): + await fcall(ability, executor) + async def validate_operation_state(self, data: dict, existing: Operation = None): if not existing: if data.get('state') in Operation.get_finished_states(): @@ -286,4 +292,4 @@ def build_ability(self, data: dict, executor: Executor): data['description'] = 'Manual command ability' data['executors'] = [ExecutorSchema().dump(executor)] ability = AbilitySchema().load(data) - return ability + return ability \ No newline at end of file diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index 335da59d9..309c7d0a3 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -351,9 +351,7 @@ async def _generate_new_links(self, operation, agent, abilities, link_status): executor = await agent.get_preferred_executor(ability) if not executor: continue - - if executor.HOOKS and executor.language and executor.language in executor.HOOKS: - await executor.HOOKS[executor.language](ability, executor) + await self._call_ability_plugin_hooks(ability, executor) if executor.command: link = Link.load(dict(command=self.encode_string(executor.test), paw=agent.paw, score=0, ability=ability, executor=executor, status=link_status, @@ -392,6 +390,11 @@ async def _generate_cleanup_links(self, operation, agent, link_status): links.append(lnk) return links + async def _call_ability_plugin_hooks(self, ability, executor): + """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" + for _hook, fcall in executor.HOOKS.items(): + await fcall(ability, executor) + @staticmethod async def _apply_adjustments(operation, links): """Apply operation source ability adjustments to links @@ -405,4 +408,4 @@ async def _apply_adjustments(operation, links): for adjustment in [a for a in operation.source.adjustments if a.ability_id == a_link.ability.ability_id]: if operation.has_fact(trait=adjustment.trait, value=adjustment.value): a_link.visibility.apply(adjustment) - a_link.status = a_link.states['HIGH_VIZ'] + a_link.status = a_link.states['HIGH_VIZ'] \ No newline at end of file From 0eff1305c50759f1fd63d22424f538b8fad4b2e3 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:01:30 -0400 Subject: [PATCH 02/13] fix!: Typo --- app/api/v2/managers/operation_api_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index cdae1d57c..d68653145 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -147,7 +147,7 @@ async def setup_operation(self, data: dict, access: BaseWorld.Access): data['source'] = await self._construct_and_dump_source(fact_source_id) operation = OperationSchema().load(data) await operation.update_operation_agents(self.services) - allowed = self._get_allowed_from_access(access)d + allowed = self._get_allowed_from_access(access) operation.access = allowed return operation From ca6b4794cc46701a145598e3fa3e14ace4e0a801 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:12:42 -0400 Subject: [PATCH 03/13] fix!: Conditional to Catch None Attribute Error Running Operations * Planning Services attempts to fcall() and assume all abilities utilizes Builder Plugin and attempts to build payload. --- app/service/planning_svc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index 309c7d0a3..a7a15b783 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -351,7 +351,8 @@ async def _generate_new_links(self, operation, agent, abilities, link_status): executor = await agent.get_preferred_executor(ability) if not executor: continue - await self._call_ability_plugin_hooks(ability, executor) + if executor.HOOKS and executor.language and executor.language in executor.HOOKS: + await self._call_ability_plugin_hooks(ability, executor) if executor.command: link = Link.load(dict(command=self.encode_string(executor.test), paw=agent.paw, score=0, ability=ability, executor=executor, status=link_status, From de989ef96bb465939e91dfdf34645d7601baca3a Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:04:01 -0400 Subject: [PATCH 04/13] fix!: Caldera Operations Api Manager creates Builder Payloads during Runtime * Conditional required to validate that only abilities utilizing builder plugin is used. * Required encoded_command before building of ability and executor --- app/api/v2/managers/operation_api_manager.py | 9 +++++---- app/service/planning_svc.py | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index d68653145..7a454069e 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -98,11 +98,11 @@ async def create_potential_link(self, operation_id: str, data: dict, access: Bas agent = await self.get_agent(operation, data) if data['executor']['name'] not in agent.executors: raise JsonHttpBadRequest(f'Agent {agent.paw} missing specified executor') + encoded_command = self._encode_string(agent.replace(self._encode_string(data['executor']['command']), + file_svc=self.services['file_svc'])) executor = self.build_executor(data=data.pop('executor', {}), agent=agent) ability = self.build_ability(data=data.pop('ability', {}), executor=executor) await self._call_ability_plugin_hooks(ability, executor) - encoded_command = self._encode_string(agent.replace(self._encode_string(data['executor']['command']), - file_svc=self.services['file_svc'])) link = Link.load(dict(command=encoded_command, plaintext_command=encoded_command, paw=agent.paw, ability=ability, executor=executor, status=operation.link_status(), score=data.get('score', 0), jitter=data.get('jitter', 0), cleanup=data.get('cleanup', 0), pin=data.get('pin', 0), @@ -174,8 +174,9 @@ async def _construct_and_dump_source(self, source_id: str): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" - for _hook, fcall in executor.HOOKS.items(): - await fcall(ability, executor) + if executor.HOOKS and executor.language and executor.language in executor.HOOKS: + for _hook, fcall in executor.HOOKS.items(): + await fcall(ability, executor) async def validate_operation_state(self, data: dict, existing: Operation = None): if not existing: diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index a7a15b783..ea00bc5a1 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -351,8 +351,7 @@ async def _generate_new_links(self, operation, agent, abilities, link_status): executor = await agent.get_preferred_executor(ability) if not executor: continue - if executor.HOOKS and executor.language and executor.language in executor.HOOKS: - await self._call_ability_plugin_hooks(ability, executor) + await self._call_ability_plugin_hooks(ability, executor) if executor.command: link = Link.load(dict(command=self.encode_string(executor.test), paw=agent.paw, score=0, ability=ability, executor=executor, status=link_status, @@ -393,8 +392,9 @@ async def _generate_cleanup_links(self, operation, agent, link_status): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" - for _hook, fcall in executor.HOOKS.items(): - await fcall(ability, executor) + if executor.HOOKS and executor.language and executor.language in executor.HOOKS: + for _hook, fcall in executor.HOOKS.items(): + await fcall(ability, executor) @staticmethod async def _apply_adjustments(operation, links): From 7cc2819ce8dae49c7a21c1c24b6c991db76b1b85 Mon Sep 17 00:00:00 2001 From: deacon-mp <61169193+deacon-mp@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:29:01 -0400 Subject: [PATCH 05/13] Update planning_svc.py Added EOF new line to fix linting issue --- app/service/planning_svc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index ea00bc5a1..c89652023 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -409,4 +409,5 @@ async def _apply_adjustments(operation, links): for adjustment in [a for a in operation.source.adjustments if a.ability_id == a_link.ability.ability_id]: if operation.has_fact(trait=adjustment.trait, value=adjustment.value): a_link.visibility.apply(adjustment) - a_link.status = a_link.states['HIGH_VIZ'] \ No newline at end of file + a_link.status = a_link.states['HIGH_VIZ'] + From 3ecb69c9f0ec8da7906255102e44e02b049e758b Mon Sep 17 00:00:00 2001 From: deacon-mp <61169193+deacon-mp@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:39:37 -0400 Subject: [PATCH 06/13] Update conftest.py no longer able to use event_loop replaced all instances of event_loop with loop --- tests/conftest.py | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c83aca648..08f93f647 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -83,8 +83,8 @@ async def app_svc(): # async def _init_app_svc(): # return AppService(None) - # def _app_svc(event_loop): - # return event_loop.run_until_complete(_init_app_svc()) + # def _app_svc(loop): + # return loop.run_until_complete(_init_app_svc()) # return _app_svc return AppService(None) @@ -120,15 +120,15 @@ def event_svc(contact_svc, init_base_world): async def rest_svc(): """ The REST service requires the test's loop in order to be initialized in the same Thread - as the test. This mitigates the issue where the service's calls to `asyncio.get_event_loop` + as the test. This mitigates the issue where the service's calls to `asyncio.get_loop` would result in a RuntimeError indicating that there is no currentevent loop in the main thread. """ async def _init_rest_svc(): return RestService() - def _rest_svc(event_loop): - return event_loop.run_until_complete(_init_rest_svc()) + def _rest_svc(loop): + return loop.run_until_complete(_init_rest_svc()) return _rest_svc # return RestService() @@ -196,14 +196,14 @@ def _generate_operation(name, agents, adversary, *args, **kwargs): @pytest.fixture -def demo_operation(event_loop, data_svc, operation, adversary): - tadversary = event_loop.run_until_complete(data_svc.store(adversary())) +def demo_operation(loop, data_svc, operation, adversary): + tadversary = loop.run_until_complete(data_svc.store(adversary())) return operation(name='my first op', agents=[], adversary=tadversary) @pytest.fixture -def obfuscator(event_loop, data_svc): - event_loop.run_until_complete(data_svc.store( +def obfuscator(loop, data_svc): + loop.run_until_complete(data_svc.store( Obfuscator(name='plain-text', description='Does no obfuscation to any command, instead running it in plain text', module='plugins.stockpile.app.obfuscators.plain_text') @@ -332,7 +332,7 @@ def agent_config(): @pytest.fixture -async def api_v2_client(event_loop, aiohttp_client, contact_svc): +async def api_v2_client(loop, aiohttp_client, contact_svc): def make_app(svcs): warnings.filterwarnings( "ignore", @@ -408,11 +408,11 @@ async def initialize(): @pytest.fixture -def api_cookies(event_loop, api_v2_client): +def api_cookies(loop, api_v2_client): async def get_cookie(): r = await api_v2_client.post('/enter', allow_redirects=False, data=dict(username='admin', password='admin')) return r.cookies - return event_loop.run_until_complete(get_cookie()) + return loop.run_until_complete(get_cookie()) @pytest.fixture @@ -437,7 +437,7 @@ def _parse_datestring(datestring): @pytest.fixture -def test_adversary(event_loop): +def test_adversary(loop): expected_adversary = {'name': 'ad-hoc', 'description': 'an empty adversary profile', 'adversary_id': 'ad-hoc', @@ -445,12 +445,12 @@ def test_adversary(event_loop): 'tags': [], 'has_repeatable_abilities': False} test_adversary = AdversarySchema().load(expected_adversary) - event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_adversary)) + loop.run_until_complete(BaseService.get_service('data_svc').store(test_adversary)) return test_adversary @pytest.fixture -def test_planner(event_loop): +def test_planner(loop): expected_planner = {'name': 'test planner', 'description': 'test planner', 'module': 'test', @@ -460,26 +460,26 @@ def test_planner(event_loop): 'ignore_enforcement_modules': [], 'id': '123'} test_planner = PlannerSchema().load(expected_planner) - event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_planner)) + loop.run_until_complete(BaseService.get_service('data_svc').store(test_planner)) return test_planner @pytest.fixture -def test_source(event_loop): +def test_source(loop): test_fact = Fact(trait='remote.host.fqdn', value='dc') test_source = Source(id='123', name='test', facts=[test_fact], adjustments=[]) - event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) + loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) return test_source @pytest.fixture -def test_source_existing_relationships(event_loop): +def test_source_existing_relationships(loop): test_fact_1 = Fact(trait='test_1', value='1') test_fact_2 = Fact(trait='test_2', value='2') test_relationship = Relationship(source=test_fact_1, edge='test_edge', target=test_fact_2) test_source = Source(id='123', name='test', facts=[test_fact_1, test_fact_2], adjustments=[], relationships=[test_relationship]) - event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) + loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) return test_source @@ -502,9 +502,9 @@ def test_operation(test_adversary, test_planner, test_source): @pytest.fixture -def test_agent(event_loop): +def test_agent(loop): agent = Agent(paw='123', sleep_min=2, sleep_max=8, watchdog=0, executors=['sh'], platform='linux') - event_loop.run_until_complete(BaseService.get_service('data_svc').store(agent)) + loop.run_until_complete(BaseService.get_service('data_svc').store(agent)) return agent @@ -514,7 +514,7 @@ def test_executor(test_agent): @pytest.fixture -def test_ability(test_executor, event_loop): +def test_ability(test_executor, loop): ability = AbilitySchema().load(dict(ability_id='123', tactic='discovery', technique_id='auto-generated', @@ -522,7 +522,7 @@ def test_ability(test_executor, event_loop): name='Manual Command', description='test ability', executors=[ExecutorSchema().dump(test_executor)])) - event_loop.run_until_complete(BaseService.get_service('data_svc').store(ability)) + loop.run_until_complete(BaseService.get_service('data_svc').store(ability)) return ability @@ -577,13 +577,13 @@ def finished_operation_payload(test_operation): @pytest.fixture -def setup_finished_operation(event_loop, finished_operation_payload): +def setup_finished_operation(loop, finished_operation_payload): finished_operation = OperationSchema().load(finished_operation_payload) - event_loop.run_until_complete(BaseService.get_service('data_svc').store(finished_operation)) + loop.run_until_complete(BaseService.get_service('data_svc').store(finished_operation)) @pytest.fixture -def setup_operations_api_test(event_loop, api_v2_client, test_operation, test_agent, test_ability, +def setup_operations_api_test(loop, api_v2_client, test_operation, test_agent, test_ability, active_link, finished_link, expected_link_output): test_operation = OperationSchema().load(test_operation) test_operation.agents.append(test_agent) @@ -597,16 +597,16 @@ def setup_operations_api_test(event_loop, api_v2_client, test_operation, test_ag test_operation.chain.append(finished_link) test_objective = Objective(id='123', name='test objective', description='test', goals=[]) test_operation.objective = test_objective - event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) + loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) @pytest.fixture -def setup_empty_operation(event_loop, test_operation): +def setup_empty_operation(loop, test_operation): test_operation = OperationSchema().load(test_operation) test_operation.set_start_details() test_objective = Objective(id='123', name='test objective', description='test', goals=[]) test_operation.objective = test_objective - event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) + loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) @pytest.fixture() From d8ce9ac79dfc090850662344bd8e4ce4ee042cb5 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:11:12 -0400 Subject: [PATCH 07/13] Fixes Flake8 Linter Run Errors --- app/api/v2/managers/operation_api_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index 7a454069e..65f0b0699 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -293,4 +293,4 @@ def build_ability(self, data: dict, executor: Executor): data['description'] = 'Manual command ability' data['executors'] = [ExecutorSchema().dump(executor)] ability = AbilitySchema().load(data) - return ability \ No newline at end of file + return ability From 55454a255e6a52eb2f495cc1406334eb77b2bd19 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:42:17 -0400 Subject: [PATCH 08/13] fix: Direct Executer Function Call from Map --- app/api/v2/managers/operation_api_manager.py | 3 +-- app/service/planning_svc.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index 65f0b0699..51071cf7b 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -175,8 +175,7 @@ async def _construct_and_dump_source(self, source_id: str): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" if executor.HOOKS and executor.language and executor.language in executor.HOOKS: - for _hook, fcall in executor.HOOKS.items(): - await fcall(ability, executor) + await executor.HOOKS[executor.language](ability, executor) async def validate_operation_state(self, data: dict, existing: Operation = None): if not existing: diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index c89652023..15141af02 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -393,8 +393,7 @@ async def _generate_cleanup_links(self, operation, agent, link_status): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" if executor.HOOKS and executor.language and executor.language in executor.HOOKS: - for _hook, fcall in executor.HOOKS.items(): - await fcall(ability, executor) + await executor.HOOKS[executor.language](ability, executor) @staticmethod async def _apply_adjustments(operation, links): From 53a379fde083591afe0e42125586081c8c8d44ed Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:36:26 -0400 Subject: [PATCH 09/13] Revert "Update conftest.py" This reverts commit 3ecb69c9f0ec8da7906255102e44e02b049e758b. --- tests/conftest.py | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 08f93f647..c83aca648 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -83,8 +83,8 @@ async def app_svc(): # async def _init_app_svc(): # return AppService(None) - # def _app_svc(loop): - # return loop.run_until_complete(_init_app_svc()) + # def _app_svc(event_loop): + # return event_loop.run_until_complete(_init_app_svc()) # return _app_svc return AppService(None) @@ -120,15 +120,15 @@ def event_svc(contact_svc, init_base_world): async def rest_svc(): """ The REST service requires the test's loop in order to be initialized in the same Thread - as the test. This mitigates the issue where the service's calls to `asyncio.get_loop` + as the test. This mitigates the issue where the service's calls to `asyncio.get_event_loop` would result in a RuntimeError indicating that there is no currentevent loop in the main thread. """ async def _init_rest_svc(): return RestService() - def _rest_svc(loop): - return loop.run_until_complete(_init_rest_svc()) + def _rest_svc(event_loop): + return event_loop.run_until_complete(_init_rest_svc()) return _rest_svc # return RestService() @@ -196,14 +196,14 @@ def _generate_operation(name, agents, adversary, *args, **kwargs): @pytest.fixture -def demo_operation(loop, data_svc, operation, adversary): - tadversary = loop.run_until_complete(data_svc.store(adversary())) +def demo_operation(event_loop, data_svc, operation, adversary): + tadversary = event_loop.run_until_complete(data_svc.store(adversary())) return operation(name='my first op', agents=[], adversary=tadversary) @pytest.fixture -def obfuscator(loop, data_svc): - loop.run_until_complete(data_svc.store( +def obfuscator(event_loop, data_svc): + event_loop.run_until_complete(data_svc.store( Obfuscator(name='plain-text', description='Does no obfuscation to any command, instead running it in plain text', module='plugins.stockpile.app.obfuscators.plain_text') @@ -332,7 +332,7 @@ def agent_config(): @pytest.fixture -async def api_v2_client(loop, aiohttp_client, contact_svc): +async def api_v2_client(event_loop, aiohttp_client, contact_svc): def make_app(svcs): warnings.filterwarnings( "ignore", @@ -408,11 +408,11 @@ async def initialize(): @pytest.fixture -def api_cookies(loop, api_v2_client): +def api_cookies(event_loop, api_v2_client): async def get_cookie(): r = await api_v2_client.post('/enter', allow_redirects=False, data=dict(username='admin', password='admin')) return r.cookies - return loop.run_until_complete(get_cookie()) + return event_loop.run_until_complete(get_cookie()) @pytest.fixture @@ -437,7 +437,7 @@ def _parse_datestring(datestring): @pytest.fixture -def test_adversary(loop): +def test_adversary(event_loop): expected_adversary = {'name': 'ad-hoc', 'description': 'an empty adversary profile', 'adversary_id': 'ad-hoc', @@ -445,12 +445,12 @@ def test_adversary(loop): 'tags': [], 'has_repeatable_abilities': False} test_adversary = AdversarySchema().load(expected_adversary) - loop.run_until_complete(BaseService.get_service('data_svc').store(test_adversary)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_adversary)) return test_adversary @pytest.fixture -def test_planner(loop): +def test_planner(event_loop): expected_planner = {'name': 'test planner', 'description': 'test planner', 'module': 'test', @@ -460,26 +460,26 @@ def test_planner(loop): 'ignore_enforcement_modules': [], 'id': '123'} test_planner = PlannerSchema().load(expected_planner) - loop.run_until_complete(BaseService.get_service('data_svc').store(test_planner)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_planner)) return test_planner @pytest.fixture -def test_source(loop): +def test_source(event_loop): test_fact = Fact(trait='remote.host.fqdn', value='dc') test_source = Source(id='123', name='test', facts=[test_fact], adjustments=[]) - loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) return test_source @pytest.fixture -def test_source_existing_relationships(loop): +def test_source_existing_relationships(event_loop): test_fact_1 = Fact(trait='test_1', value='1') test_fact_2 = Fact(trait='test_2', value='2') test_relationship = Relationship(source=test_fact_1, edge='test_edge', target=test_fact_2) test_source = Source(id='123', name='test', facts=[test_fact_1, test_fact_2], adjustments=[], relationships=[test_relationship]) - loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_source)) return test_source @@ -502,9 +502,9 @@ def test_operation(test_adversary, test_planner, test_source): @pytest.fixture -def test_agent(loop): +def test_agent(event_loop): agent = Agent(paw='123', sleep_min=2, sleep_max=8, watchdog=0, executors=['sh'], platform='linux') - loop.run_until_complete(BaseService.get_service('data_svc').store(agent)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(agent)) return agent @@ -514,7 +514,7 @@ def test_executor(test_agent): @pytest.fixture -def test_ability(test_executor, loop): +def test_ability(test_executor, event_loop): ability = AbilitySchema().load(dict(ability_id='123', tactic='discovery', technique_id='auto-generated', @@ -522,7 +522,7 @@ def test_ability(test_executor, loop): name='Manual Command', description='test ability', executors=[ExecutorSchema().dump(test_executor)])) - loop.run_until_complete(BaseService.get_service('data_svc').store(ability)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(ability)) return ability @@ -577,13 +577,13 @@ def finished_operation_payload(test_operation): @pytest.fixture -def setup_finished_operation(loop, finished_operation_payload): +def setup_finished_operation(event_loop, finished_operation_payload): finished_operation = OperationSchema().load(finished_operation_payload) - loop.run_until_complete(BaseService.get_service('data_svc').store(finished_operation)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(finished_operation)) @pytest.fixture -def setup_operations_api_test(loop, api_v2_client, test_operation, test_agent, test_ability, +def setup_operations_api_test(event_loop, api_v2_client, test_operation, test_agent, test_ability, active_link, finished_link, expected_link_output): test_operation = OperationSchema().load(test_operation) test_operation.agents.append(test_agent) @@ -597,16 +597,16 @@ def setup_operations_api_test(loop, api_v2_client, test_operation, test_agent, t test_operation.chain.append(finished_link) test_objective = Objective(id='123', name='test objective', description='test', goals=[]) test_operation.objective = test_objective - loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) @pytest.fixture -def setup_empty_operation(loop, test_operation): +def setup_empty_operation(event_loop, test_operation): test_operation = OperationSchema().load(test_operation) test_operation.set_start_details() test_objective = Objective(id='123', name='test objective', description='test', goals=[]) test_operation.objective = test_objective - loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) + event_loop.run_until_complete(BaseService.get_service('data_svc').store(test_operation)) @pytest.fixture() From bd61e6c2093c149d991f0a05f555cbe3d065da78 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:18:02 -0400 Subject: [PATCH 10/13] fix: additional executor checks --- app/api/v2/managers/operation_api_manager.py | 4 +++- app/service/planning_svc.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index 51071cf7b..3b209b5ac 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -174,7 +174,9 @@ async def _construct_and_dump_source(self, source_id: str): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" - if executor.HOOKS and executor.language and executor.language in executor.HOOKS: + if hasattr(executor, 'HOOKS') and executor.HOOKS and \ + hasattr(executor, 'language') and executor.language and \ + executor.language in executor.HOOKS: await executor.HOOKS[executor.language](ability, executor) async def validate_operation_state(self, data: dict, existing: Operation = None): diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index 15141af02..7170c805b 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -392,7 +392,9 @@ async def _generate_cleanup_links(self, operation, agent, link_status): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" - if executor.HOOKS and executor.language and executor.language in executor.HOOKS: + if hasattr(executor, 'HOOKS') and executor.HOOKS and \ + hasattr(executor, 'language') and executor.language and \ + executor.language in executor.HOOKS: await executor.HOOKS[executor.language](ability, executor) @staticmethod From 69589393a65183822289365b475bdb1aeded377b Mon Sep 17 00:00:00 2001 From: Ricky Chen <124005584+HackedRico@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:19:51 -0400 Subject: [PATCH 11/13] Python Linting Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/service/planning_svc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index 7170c805b..f2e2aace4 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -411,4 +411,3 @@ async def _apply_adjustments(operation, links): if operation.has_fact(trait=adjustment.trait, value=adjustment.value): a_link.visibility.apply(adjustment) a_link.status = a_link.states['HIGH_VIZ'] - From 92c8019a8a51def65059dc73c6ef0eefc5bef800 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Mon, 6 Oct 2025 16:39:07 -0400 Subject: [PATCH 12/13] fix: flake8 linting error due to conditionals --- app/api/v2/managers/operation_api_manager.py | 6 +++--- app/service/planning_svc.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/api/v2/managers/operation_api_manager.py b/app/api/v2/managers/operation_api_manager.py index 3b209b5ac..7754bf977 100644 --- a/app/api/v2/managers/operation_api_manager.py +++ b/app/api/v2/managers/operation_api_manager.py @@ -174,9 +174,9 @@ async def _construct_and_dump_source(self, source_id: str): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" - if hasattr(executor, 'HOOKS') and executor.HOOKS and \ - hasattr(executor, 'language') and executor.language and \ - executor.language in executor.HOOKS: + if (hasattr(executor, 'HOOKS') and executor.HOOKS and + hasattr(executor, 'language') and executor.language and + executor.language in executor.HOOKS): await executor.HOOKS[executor.language](ability, executor) async def validate_operation_state(self, data: dict, existing: Operation = None): diff --git a/app/service/planning_svc.py b/app/service/planning_svc.py index f2e2aace4..2996490dd 100644 --- a/app/service/planning_svc.py +++ b/app/service/planning_svc.py @@ -392,9 +392,9 @@ async def _generate_cleanup_links(self, operation, agent, link_status): async def _call_ability_plugin_hooks(self, ability, executor): """Calls any plugin hooks (at runtime) that exist for the ability and executor.""" - if hasattr(executor, 'HOOKS') and executor.HOOKS and \ - hasattr(executor, 'language') and executor.language and \ - executor.language in executor.HOOKS: + if (hasattr(executor, 'HOOKS') and executor.HOOKS and + hasattr(executor, 'language') and executor.language and + executor.language in executor.HOOKS): await executor.HOOKS[executor.language](ability, executor) @staticmethod From fd8f4aadc86ecd92d1d832b76ac476d06104b268 Mon Sep 17 00:00:00 2001 From: Ricky <124005584+HackedRico@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:16:19 -0400 Subject: [PATCH 13/13] Pull Workflow from Upstream --- .github/workflows/quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 6d4860fa2..f961c5c46 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -59,4 +59,4 @@ jobs: uses: SonarSource/sonarqube-scan-action@v6.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file