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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Run these before declaring work complete. Don't bypass hooks — fix the underly
- **Raise for failures; no `{"error": str}` unions.** FastMCP surfaces raised exceptions as tool-call errors. Clean for the LLM, clean for the return type.
- **Plain dicts, null-omitted, over Pydantic.** LLMs pay tokens for every null-coerced empty field. Return `dict[str, Any]`, drop `None`/`""`/`[]`/`{}`, keep `0` and `False`.
- **Disambiguate sibling tools by name.** Encode actor + action + destination when tools are close. Overloading one verb (three `download*` variants) is a pick-the-wrong-one landmine.
- **One auth vocabulary across a tool family.** When sibling tools take the same authentication inputs, name them identically so the agent carries one mental model as it chains them: `nt_hash` for an NTLM hash in `LMHASH:NTHASH` form (never `hash`/`hashes`), `kerberos: bool` to switch on Kerberos. Where a tool accepts an AES key or a credential cache, expose each as its own explicit param (`aes_key`, `ccache`) — never one string disambiguated by length or content. For hosts, `target`/`targets` is the host(s) a tool acts against and `dc_ip` is the domain controller — keep both roles distinct rather than overloading one name.
- **After renames, grep the skill/agent/doc layer.** Markdown callers don't fail a build, and LLMs read that prose to form tool-call intent.

---
Expand Down
2 changes: 1 addition & 1 deletion capabilities/network-ops/capability.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema: 1
name: network-ops
version: "2.2.1"
version: "2.3.0"
description: >
Network operations and Active Directory exploitation. Multi-agent
pipeline for autonomous red teaming with Nmap scanning, Netexec
Expand Down
258 changes: 238 additions & 20 deletions capabilities/network-ops/tests/test_tool_fixes.py

Large diffs are not rendered by default.

652 changes: 326 additions & 326 deletions capabilities/network-ops/tools/bloodyad.py

Large diffs are not rendered by default.

50 changes: 29 additions & 21 deletions capabilities/network-ops/tools/certipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async def certipy(
self,
action: str,
args: list[str],
target: str,
dc_ip: str,
username: str | None = None,
domain: str | None = None,
password: str | None = None,
Expand Down Expand Up @@ -114,11 +114,11 @@ async def certipy(
shadow - Abuse Shadow Credentials for account takeover
template - Manage certificate templates
args: A list of arguments specific to the subcommand.
target: The IP address of the Domain Controller.
dc_ip: The IP address of the Domain Controller.
username: The username for authentication.
domain: The domain name.
password: The password for authentication.
nt_hash: The NTLM hash for authentication.
nt_hash: NTLM hash for authentication, as LMHASH:NTHASH or the NT hash alone.
relay_timeout: Only for action="relay": seconds to keep the listener up
waiting for a relayed certificate before returning (defaults to the
toolset's relay_timeout). Ignored for every other action.
Expand All @@ -138,9 +138,11 @@ async def certipy(
if password:
cmd.extend(["-p", password])
elif nt_hash:
cmd.extend(["-hashes", f":{nt_hash}"])
# certipy's -hashes expects LMHASH:NTHASH; accept the family-wide
# full form verbatim and pad an empty LM half when given the NT hash alone.
cmd.extend(["-hashes", nt_hash if ":" in nt_hash else f":{nt_hash}"])

cmd.extend(["-dc-ip", target])
cmd.extend(["-dc-ip", dc_ip])
cmd.extend(args)

logger.info(f"Running '{self.certipy_cmd} {action}' with args: {' '.join(args)}")
Expand All @@ -163,7 +165,7 @@ async def certipy(
@tool_method(catch=True, variants=["specialized", "all"])
async def certipy_find_vulnerable_templates(
self,
target: str,
dc_ip: str,
username: str,
domain: str,
password: str | None = None,
Expand All @@ -173,17 +175,17 @@ async def certipy_find_vulnerable_templates(
Finds vulnerable AD CS certificate templates using the certipy-ad runner.

Args:
target: The IP address of the Domain Controller.
dc_ip: The IP address of the Domain Controller.
username: The username for authentication.
domain: The domain name.
password: The password for authentication.
nt_hash: The NTLM hash for authentication.
nt_hash: NTLM hash for authentication, as LMHASH:NTHASH or the NT hash alone.
"""
logger.info(f"Finding vulnerable certificate templates on {target}")
logger.info(f"Finding vulnerable certificate templates on {dc_ip}")
return await self.certipy(
action="find",
args=["-vulnerable", "-stdout"],
target=target,
dc_ip=dc_ip,
username=username,
domain=domain,
password=password,
Expand All @@ -193,7 +195,7 @@ async def certipy_find_vulnerable_templates(
@tool_method(catch=True, variants=["specialized", "all"])
async def certipy_request_certificate(
self,
target: str,
dc_ip: str,
ca_server: str,
ca_name: str,
template: str,
Expand All @@ -207,14 +209,14 @@ async def certipy_request_certificate(
Requests a certificate using a specified template via the certipy-ad runner.

Args:
target: IP address of the Domain Controller.
dc_ip: IP address of the Domain Controller.
ca_server: Hostname or IP of the Certificate Authority server.
ca_name: Name of the Certificate Authority (e.g., "corp-CA").
template: Name of the certificate template to use.
username: Username for authentication.
domain: Domain name.
password: Password for authentication.
nt_hash: NTLM hash for authentication.
nt_hash: NTLM hash for authentication, as LMHASH:NTHASH or the NT hash alone.
on_behalf_of: User to impersonate for the certificate request (for ESC4).
"""
args = [
Expand All @@ -231,7 +233,7 @@ async def certipy_request_certificate(
return await self.certipy(
action="req",
args=args,
target=target,
dc_ip=dc_ip,
username=username,
domain=domain,
password=password,
Expand All @@ -242,7 +244,7 @@ async def certipy_request_certificate(
async def certipy_certificate_auth(
self,
pfx_file: str,
target: str,
dc_ip: str,
*,
username: str = "",
domain: str = "",
Expand All @@ -252,7 +254,7 @@ async def certipy_certificate_auth(

Args:
pfx_file: Path to the PFX certificate file.
target: IP address of the Domain Controller.
dc_ip: IP address of the Domain Controller.
username: The username for authentication.
domain: The domain name.
"""
Expand All @@ -261,7 +263,7 @@ async def certipy_certificate_auth(
args=["-pfx", pfx_file],
username=username,
domain=domain,
target=target,
dc_ip=dc_ip,
)

# General methods
Expand Down Expand Up @@ -343,6 +345,9 @@ async def certipy_auth(self, args: list[str], input: str | None = None) -> str:
"""
Execute a certipy-ad auth command.

Raw escape hatch for arbitrary ``auth`` arguments. Prefer
``certipy_certificate_auth`` for the common PFX-based authentication flow.

<documentation>
Authenticate to Active Directory services using certificates. This command enables certificate-based authentication to obtain Kerberos tickets, NT hashes, or establish LDAP connections.

Expand Down Expand Up @@ -508,7 +513,7 @@ async def certipy_cert(self, args: list[str], input: str | None = None) -> str:
@tool_method(catch=True, variants=["generic", "all"])
async def certipy_find(
self,
target: str,
dc_ip: str,
args: list[str] | None = None,
username: str | None = None,
domain: str | None = None,
Expand Down Expand Up @@ -555,20 +560,20 @@ async def certipy_find(
</documentation>

Args:
target: The IP address of the Domain Controller.
dc_ip: The IP address of the Domain Controller.
args: Additional arguments for the find command (e.g., ["-vulnerable", "-stdout"]).
username: The username for authentication.
domain: The domain name (combined with username as user@domain).
password: The password for authentication.
nt_hash: The NTLM hash for authentication.
nt_hash: NTLM hash for authentication, as LMHASH:NTHASH or the NT hash alone.
input: Optional stdin for the command. The default (and any empty
value) auto-confirms certipy's interactive prompts with "y";
pass an explicit answer such as "n" to respond differently.
"""
return await self.certipy(
action="find",
args=args or [],
target=target,
dc_ip=dc_ip,
username=username,
domain=domain,
password=password,
Expand All @@ -581,6 +586,9 @@ async def certipy_req(self, args: list[str], input: str | None = None) -> str:
r"""
Execute a certipy-ad req command.

Raw escape hatch for arbitrary ``req`` arguments. Prefer
``certipy_request_certificate`` for the common structured request flow.

<documentation>
Request and retrieve certificates from Active Directory Certificate Services (AD CS). This command supports multiple enrollment protocols and certificate template types.

Expand Down
32 changes: 16 additions & 16 deletions capabilities/network-ops/tools/coercion.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async def coerce_petitpotam(
username: str | None = None,
password: str | None = None,
domain: str | None = None,
hashes: str | None = None,
nt_hash: str | None = None,
no_pass: bool = False,
kerberos: bool = False,
dc_ip: str | None = None,
Expand Down Expand Up @@ -147,7 +147,7 @@ async def coerce_petitpotam(
username: Username for authentication to the target.
password: Password for authentication.
domain: Domain name for authentication.
hashes: NTLM hashes in [LMHASH]:NTHASH format.
nt_hash: NTLM hashes in [LMHASH]:NTHASH format.
no_pass: Don't prompt for password (for unauthenticated or Kerberos).
kerberos: Use Kerberos authentication from ccache (KRB5CCNAME).
dc_ip: Domain controller IP address override.
Expand All @@ -164,9 +164,9 @@ async def coerce_petitpotam(
args.extend(["-p", password])
if domain:
args.extend(["-d", domain])
if hashes:
args.extend(["-hashes", hashes])
if no_pass or (not password and not hashes):
if nt_hash:
args.extend(["-hashes", nt_hash])
if no_pass or (not password and not nt_hash):
args.append("-no-pass")
if kerberos:
args.append("-k")
Expand Down Expand Up @@ -199,7 +199,7 @@ async def coerce_dfscoerce(
username: str | None = None,
password: str | None = None,
domain: str | None = None,
hashes: str | None = None,
nt_hash: str | None = None,
no_pass: bool = False,
kerberos: bool = False,
dc_ip: str | None = None,
Expand Down Expand Up @@ -230,7 +230,7 @@ async def coerce_dfscoerce(
target="dc01.corp.local",
username="admin",
domain="corp.local",
hashes=":31d6cfe0d16ae931b73c59d7e0c089c0"
nt_hash=":31d6cfe0d16ae931b73c59d7e0c089c0"
)

<documentation>
Expand Down Expand Up @@ -262,7 +262,7 @@ async def coerce_dfscoerce(
username: Username for authentication to the target.
password: Password for authentication.
domain: Domain name for authentication.
hashes: NTLM hashes in [LMHASH]:NTHASH format.
nt_hash: NTLM hashes in [LMHASH]:NTHASH format.
no_pass: Don't prompt for password (for unauthenticated or Kerberos).
kerberos: Use Kerberos authentication from ccache (KRB5CCNAME).
dc_ip: Domain controller IP address override.
Expand All @@ -278,9 +278,9 @@ async def coerce_dfscoerce(
args.extend(["-p", password])
if domain:
args.extend(["-d", domain])
if hashes:
args.extend(["-hashes", hashes])
if no_pass or (not password and not hashes):
if nt_hash:
args.extend(["-hashes", nt_hash])
if no_pass or (not password and not nt_hash):
args.append("-no-pass")
if kerberos:
args.append("-k")
Expand Down Expand Up @@ -311,7 +311,7 @@ async def coerce_shadowcoerce(
username: str | None = None,
password: str | None = None,
domain: str | None = None,
hashes: str | None = None,
nt_hash: str | None = None,
debug: bool = False,
env: dict[str, str] | None = None,
input: str | None = None,
Expand Down Expand Up @@ -343,7 +343,7 @@ async def coerce_shadowcoerce(
target="fs01.corp.local",
username="admin",
domain="corp.local",
hashes=":31d6cfe0d16ae931b73c59d7e0c089c0"
nt_hash=":31d6cfe0d16ae931b73c59d7e0c089c0"
)

<documentation>
Expand Down Expand Up @@ -372,7 +372,7 @@ async def coerce_shadowcoerce(
username: Username for authentication to the target.
password: Password for authentication.
domain: Domain name for authentication.
hashes: NTLM hashes in [LMHASH]:NTHASH format.
nt_hash: NTLM hashes in [LMHASH]:NTHASH format.
debug: Enable debug output.
env: Optional environment variables.
input: Optional stdin input.
Expand All @@ -385,8 +385,8 @@ async def coerce_shadowcoerce(
args.extend(["-p", password])
if domain:
args.extend(["-d", domain])
if hashes:
args.extend(["-hashes", hashes])
if nt_hash:
args.extend(["-hashes", nt_hash])
if debug:
args.append("-debug")

Expand Down
Loading
Loading