ctf-agent 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/AGENTS.md +131 -0
  2. package/Dockerfile +192 -0
  3. package/README.md +522 -0
  4. package/agents/ctf-analyzer.md +58 -0
  5. package/agents/ctf-controller.md +151 -0
  6. package/agents/ctf-speedrun.md +74 -0
  7. package/bin/ctf-agent.js +119 -0
  8. package/docker-compose.yml +13 -0
  9. package/mcp_config.json +19 -0
  10. package/package.json +44 -0
  11. package/references/ctf-triage-ladder.md +68 -0
  12. package/references/exploit-databases.md +575 -0
  13. package/references/llm-safety-and-policy-compliance.md +173 -0
  14. package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
  15. package/references/security-events-and-intelligence.md +566 -0
  16. package/references/version-matrix.md +59 -0
  17. package/rules/ctf-execution-rules.md +44 -0
  18. package/rules/ctf-safety-framing-rules.md +140 -0
  19. package/rules/flag-validation-rules.md +25 -0
  20. package/scripts/__init__.py +1 -0
  21. package/scripts/ctf-tools.lock +205 -0
  22. package/scripts/ctf_init.py +1026 -0
  23. package/scripts/ctf_update.py +583 -0
  24. package/scripts/ctfd_client.py +81 -0
  25. package/scripts/cve_lookup.py +453 -0
  26. package/scripts/extract_flags.py +155 -0
  27. package/scripts/install_as_agent.py +278 -0
  28. package/scripts/install_ctf_tools.sh +603 -0
  29. package/scripts/parallel_triage.py +395 -0
  30. package/scripts/prompt_policy_sanitizer.py +283 -0
  31. package/scripts/scope_guard.py +444 -0
  32. package/scripts/skill_validator.py +504 -0
  33. package/scripts/workspace_cleaner.py +141 -0
  34. package/skills/ctf-ai-ml/SKILL.md +120 -0
  35. package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
  36. package/skills/ctf-ai-ml/llm-attacks.md +487 -0
  37. package/skills/ctf-ai-ml/model-attacks.md +422 -0
  38. package/skills/ctf-crypto/SKILL.md +320 -0
  39. package/skills/ctf-crypto/advanced-math.md +798 -0
  40. package/skills/ctf-crypto/classic-ciphers.md +651 -0
  41. package/skills/ctf-crypto/ecc-attacks.md +347 -0
  42. package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
  43. package/skills/ctf-crypto/exotic-crypto.md +528 -0
  44. package/skills/ctf-crypto/historical.md +113 -0
  45. package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
  46. package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
  47. package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
  48. package/skills/ctf-crypto/modern-ciphers.md +649 -0
  49. package/skills/ctf-crypto/prng-attacks.md +257 -0
  50. package/skills/ctf-crypto/prng.md +664 -0
  51. package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
  52. package/skills/ctf-crypto/rsa-attacks.md +487 -0
  53. package/skills/ctf-crypto/stream-ciphers.md +390 -0
  54. package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
  55. package/skills/ctf-forensics/3d-printing.md +121 -0
  56. package/skills/ctf-forensics/SKILL.md +379 -0
  57. package/skills/ctf-forensics/disk-advanced.md +497 -0
  58. package/skills/ctf-forensics/disk-and-memory.md +491 -0
  59. package/skills/ctf-forensics/disk-recovery.md +699 -0
  60. package/skills/ctf-forensics/linux-forensics.md +511 -0
  61. package/skills/ctf-forensics/network-advanced.md +583 -0
  62. package/skills/ctf-forensics/network.md +645 -0
  63. package/skills/ctf-forensics/peripheral-capture.md +287 -0
  64. package/skills/ctf-forensics/signals-and-hardware.md +713 -0
  65. package/skills/ctf-forensics/steganography.md +694 -0
  66. package/skills/ctf-forensics/stego-advanced-2.md +475 -0
  67. package/skills/ctf-forensics/stego-advanced.md +481 -0
  68. package/skills/ctf-forensics/stego-image.md +691 -0
  69. package/skills/ctf-forensics/windows.md +625 -0
  70. package/skills/ctf-malware/SKILL.md +181 -0
  71. package/skills/ctf-malware/c2-and-protocols.md +274 -0
  72. package/skills/ctf-malware/pe-and-dotnet.md +108 -0
  73. package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
  74. package/skills/ctf-misc/SKILL.md +498 -0
  75. package/skills/ctf-misc/bashjails.md +323 -0
  76. package/skills/ctf-misc/ctfd-navigation.md +465 -0
  77. package/skills/ctf-misc/dns.md +255 -0
  78. package/skills/ctf-misc/encodings-advanced.md +504 -0
  79. package/skills/ctf-misc/encodings.md +431 -0
  80. package/skills/ctf-misc/games-and-vms-2.md +254 -0
  81. package/skills/ctf-misc/games-and-vms-3.md +690 -0
  82. package/skills/ctf-misc/games-and-vms-4.md +229 -0
  83. package/skills/ctf-misc/games-and-vms.md +529 -0
  84. package/skills/ctf-misc/linux-privesc.md +333 -0
  85. package/skills/ctf-misc/pyjails.md +671 -0
  86. package/skills/ctf-misc/rf-sdr.md +91 -0
  87. package/skills/ctf-osint/SKILL.md +198 -0
  88. package/skills/ctf-osint/geolocation-and-media.md +464 -0
  89. package/skills/ctf-osint/social-media.md +312 -0
  90. package/skills/ctf-osint/web-and-dns.md +341 -0
  91. package/skills/ctf-pwn/SKILL.md +214 -0
  92. package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
  93. package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
  94. package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
  95. package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
  96. package/skills/ctf-pwn/advanced-exploits.md +773 -0
  97. package/skills/ctf-pwn/advanced.md +326 -0
  98. package/skills/ctf-pwn/field-notes.md +245 -0
  99. package/skills/ctf-pwn/format-string.md +694 -0
  100. package/skills/ctf-pwn/heap-fsop.md +285 -0
  101. package/skills/ctf-pwn/heap-techniques-2.md +333 -0
  102. package/skills/ctf-pwn/heap-techniques.md +513 -0
  103. package/skills/ctf-pwn/kernel-bypass.md +421 -0
  104. package/skills/ctf-pwn/kernel-techniques.md +366 -0
  105. package/skills/ctf-pwn/kernel.md +636 -0
  106. package/skills/ctf-pwn/overflow-basics.md +611 -0
  107. package/skills/ctf-pwn/rop-advanced.md +725 -0
  108. package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
  109. package/skills/ctf-pwn/sandbox-escape.md +313 -0
  110. package/skills/ctf-reverse/SKILL.md +163 -0
  111. package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
  112. package/skills/ctf-reverse/anti-analysis.md +693 -0
  113. package/skills/ctf-reverse/field-notes.md +376 -0
  114. package/skills/ctf-reverse/languages-compiled.md +666 -0
  115. package/skills/ctf-reverse/languages-platforms.md +592 -0
  116. package/skills/ctf-reverse/languages.md +553 -0
  117. package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
  118. package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
  119. package/skills/ctf-reverse/patterns-ctf.md +670 -0
  120. package/skills/ctf-reverse/patterns-runtime.md +274 -0
  121. package/skills/ctf-reverse/patterns.md +572 -0
  122. package/skills/ctf-reverse/platforms-hardware.md +387 -0
  123. package/skills/ctf-reverse/platforms.md +664 -0
  124. package/skills/ctf-reverse/tools-advanced-2.md +421 -0
  125. package/skills/ctf-reverse/tools-advanced.md +407 -0
  126. package/skills/ctf-reverse/tools-dynamic.md +679 -0
  127. package/skills/ctf-reverse/tools-emulation.md +319 -0
  128. package/skills/ctf-reverse/tools.md +573 -0
  129. package/skills/ctf-web/SKILL.md +153 -0
  130. package/skills/ctf-web/auth-and-access-2.md +82 -0
  131. package/skills/ctf-web/auth-and-access.md +783 -0
  132. package/skills/ctf-web/auth-infra.md +321 -0
  133. package/skills/ctf-web/auth-jwt.md +186 -0
  134. package/skills/ctf-web/client-side-advanced.md +739 -0
  135. package/skills/ctf-web/client-side.md +529 -0
  136. package/skills/ctf-web/cves.md +373 -0
  137. package/skills/ctf-web/field-notes.md +482 -0
  138. package/skills/ctf-web/node-and-prototype.md +200 -0
  139. package/skills/ctf-web/server-side-2.md +337 -0
  140. package/skills/ctf-web/server-side-advanced-2.md +559 -0
  141. package/skills/ctf-web/server-side-advanced-3.md +125 -0
  142. package/skills/ctf-web/server-side-advanced-4.md +480 -0
  143. package/skills/ctf-web/server-side-advanced.md +378 -0
  144. package/skills/ctf-web/server-side-deser.md +443 -0
  145. package/skills/ctf-web/server-side-exec-2.md +799 -0
  146. package/skills/ctf-web/server-side-exec.md +457 -0
  147. package/skills/ctf-web/server-side.md +629 -0
  148. package/skills/ctf-web/sql-injection.md +790 -0
  149. package/skills/ctf-web/web3.md +374 -0
  150. package/skills/ctf-writeup/SKILL.md +90 -0
  151. package/skills/solve-challenge/SKILL.md +269 -0
  152. package/skills.json +16 -0
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CTF-Agent Machine-Enforced Scope Guard
4
+ Enforces runtime boundary compliance for Security Context Objects (SCO) and Task Envelopes (TE).
5
+ Validates targets against authorized educational sandboxes and blocks prohibited actions.
6
+ Includes strict anti-SSRF, IP obfuscation detection, and target category classification.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import re
12
+ import ipaddress
13
+ import urllib.parse
14
+ import argparse
15
+ import json
16
+ from enum import Enum
17
+ from pathlib import Path
18
+ from typing import Dict, Any, List, Optional, Tuple
19
+
20
+ # Safe UTF-8 output on Windows consoles
21
+ if hasattr(sys.stdout, "reconfigure"):
22
+ try:
23
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
24
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
25
+ except Exception:
26
+ pass
27
+
28
+ REPO_ROOT = Path(__file__).resolve().parent.parent
29
+
30
+
31
+ class TargetCategory(str, Enum):
32
+ LOCAL_FILE = "local_file"
33
+ PRIVATE_LAB = "private_lab"
34
+ CHALLENGE_INSTANCE = "challenge_instance"
35
+ PLATFORM_PORTAL = "platform_portal"
36
+ CUSTOM_AUTHORIZED = "custom_authorized"
37
+
38
+
39
+ # Allowed protocols for CTF target interactions
40
+ PERMITTED_SCHEMES = {"http", "https", "tcp", "nc", "ssl", "ws", "wss", "ssh"}
41
+
42
+ # Strictly prohibited URL schemes (SSRF / LFI / local code execution risks)
43
+ DANGEROUS_SCHEMES = {"javascript", "file", "data", "gopher", "dict", "ldap", "vbscript"}
44
+
45
+ # Authorized Challenge Infrastructure (ephemeral instancers, challenge subdomains)
46
+ CHALLENGE_DOMAIN_PATTERNS = [
47
+ r"^(?:[a-zA-Z0-9-]+\.)*chal{1,2}s?\.io$",
48
+ r"^(?:[a-zA-Z0-9-]+\.)*chal\.pw$",
49
+ r"^(?:[a-zA-Z0-9-]+\.)*picoctf\.net$",
50
+ r"^challenge\.flagyard\.com$",
51
+ r"^play\.flagyard\.com$",
52
+ r"^(?:[a-zA-Z0-9-]+\.)*lab\.hackthebox\.com$",
53
+ r"^lab\.hackthebox\.com$",
54
+ r"^app\.hackthebox\.com$",
55
+ r"^(?:[a-zA-Z0-9-]+\.)*web-security-academy\.net$",
56
+ r"^(?:[a-zA-Z0-9-]+\.)*root-me\.org$",
57
+ r"^(?:[a-zA-Z0-9-]+\.)*cyberdefenders\.org$",
58
+ r"^(?:[a-zA-Z0-9-]+\.)*pwni\.ng$",
59
+ r"^(?:[a-zA-Z0-9-]+\.)*ctf\.su$",
60
+ r"^(?:[a-zA-Z0-9-]+\.)*seccon\.games$",
61
+ r"^(?:[a-zA-Z0-9-]+\.)*kctf\.dev$",
62
+ r"^(?:[a-zA-Z0-9-]+\.)*instancer\.[a-zA-Z0-9-.]+$",
63
+ ]
64
+
65
+ # Authorized Platform Portals (scoreboards / account portals)
66
+ PLATFORM_PORTAL_PATTERNS = [
67
+ r"^(?:www\.)?picoctf\.org$",
68
+ r"^play\.picoctf\.org$",
69
+ r"^(?:[a-zA-Z0-9-]+\.)*ctfd\.io$",
70
+ r"^ctfd\.io$",
71
+ r"^(?:www\.)?hackthebox\.(?:com|eu)$",
72
+ r"^(?:www\.)?tryhackme\.com$",
73
+ r"^(?:www\.)?flagyard\.com$",
74
+ r"^(?:www\.)?overthewire\.org$",
75
+ ]
76
+
77
+ # Authorized Private / Lab TLDs and names
78
+ PRIVATE_LAB_DOMAIN_PATTERNS = [
79
+ r"^localhost$",
80
+ r"^(?:[a-zA-Z0-9-]+\.)*local$",
81
+ r"^(?:[a-zA-Z0-9-]+\.)*internal$",
82
+ r"^(?:[a-zA-Z0-9-]+\.)*lan$",
83
+ r"^(?:[a-zA-Z0-9-]+\.)*test$",
84
+ r"^(?:[a-zA-Z0-9-]+\.)*lab$",
85
+ r"^(?:[a-zA-Z0-9-]+\.)*ctf$",
86
+ r"^(?:[a-zA-Z0-9-]+\.)*htb$",
87
+ r"^(?:[a-zA-Z0-9-]+\.)*thm$",
88
+ ]
89
+
90
+ # Known SSRF / DNS wildcard evasion domains to block
91
+ DNS_REBINDING_DOMAINS = [
92
+ r"^(?:[a-zA-Z0-9-]+\.)*nip\.io$",
93
+ r"^(?:[a-zA-Z0-9-]+\.)*sslip\.io$",
94
+ r"^(?:[a-zA-Z0-9-]+\.)*xip\.io$",
95
+ ]
96
+
97
+ # Strictly prohibited command signatures
98
+ PROHIBITED_COMMAND_PATTERNS = [
99
+ (r"\brm\s+-[rR]f\s+/(?:\s|$|\*)", "Destructive command: Root filesystem deletion"),
100
+ (r"\bformat\s+[cC]:", "Destructive command: Drive format"),
101
+ (r"\bdel\s+/[fF]\s+/[sS]\s+/[qQ]\s+[cC]:\\Windows", "Destructive command: Windows system deletion"),
102
+ (r"\bmasscan\s+(?:0\.0\.0\.0/0|(?:\d{1,3}\.){3}\d{1,3}/(?:[0-7]|1[0-5]))", "Prohibited: Wide-area indiscriminate network scanning"),
103
+ (r"\bnmap\s+(?:-[^\s]+\s+)*(?:0\.0\.0\.0/0|(?:\d{1,3}\.){3}\d{1,3}/[1-9]\b)", "Prohibited: Wide-area indiscriminate network scanning"),
104
+ (r"\b(?:echo|cat)\s+.*>>\s+/(?:etc/cron|etc/init\.d)", "Prohibited: System-level persistence establishment"),
105
+ (r"\bReg(?:\.exe)?\s+ADD\s+HK(?:LM|CU)\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Prohibited: Windows autorun persistence"),
106
+ ]
107
+
108
+
109
+ class ScopeViolationError(Exception):
110
+ """Raised when an operation, target, or command violates educational sandbox scope."""
111
+ pass
112
+
113
+
114
+ class ScopeGuard:
115
+ """Enforces target isolation, private network boundaries, anti-SSRF rules, and action restrictions."""
116
+
117
+ @staticmethod
118
+ def load_env_whitelist(workspace_path: Optional[Path] = None) -> List[str]:
119
+ """Loads allowed hosts and URLs from .env or .env.example if present."""
120
+ allowed: List[str] = []
121
+ candidates = []
122
+ if workspace_path:
123
+ candidates.extend([workspace_path / ".env", workspace_path / ".env.example"])
124
+ candidates.extend([REPO_ROOT / ".env", REPO_ROOT / ".env.example"])
125
+
126
+ for env_file in candidates:
127
+ if env_file.is_file():
128
+ try:
129
+ for line in env_file.read_text(encoding="utf-8").splitlines():
130
+ line = line.strip()
131
+ if line and not line.startswith("#") and "=" in line:
132
+ k, v = line.split("=", 1)
133
+ k = k.strip().upper()
134
+ v = v.strip()
135
+ if k in ("TARGET_HOST", "TARGET_URL", "CTFD_URL") and v:
136
+ allowed.append(v)
137
+ except Exception:
138
+ pass
139
+ return allowed
140
+
141
+ @classmethod
142
+ def check_ip_obfuscation(cls, host: str) -> Tuple[bool, Optional[str]]:
143
+ """
144
+ Detects alternate IP representations (integer, hex, octal, dword) used to bypass scope/SSRF checks.
145
+ Returns (is_obfuscated, reason).
146
+ """
147
+ raw = host.strip()
148
+
149
+ # 1. Decimal integer representation (e.g. 2130706433)
150
+ if raw.isdigit() and len(raw) > 3:
151
+ try:
152
+ num = int(raw)
153
+ if 0 <= num <= 4294967295:
154
+ return True, f"Obfuscated integer IP representation rejected: {raw}"
155
+ except ValueError:
156
+ pass
157
+
158
+ # 2. Hexadecimal representation (e.g. 0x7f000001 or dotted 0x7f.0.0.1)
159
+ if raw.lower().startswith("0x") or re.search(r"\b0x[0-9a-fA-F]+\b", raw):
160
+ return True, f"Obfuscated hexadecimal IP representation rejected: {raw}"
161
+
162
+ # 3. Octal representation (e.g. 017700000001 or dotted octal 0177.0.0.1)
163
+ if re.match(r"^0[0-7]+$", raw) and len(raw) > 3:
164
+ return True, f"Obfuscated octal IP representation rejected: {raw}"
165
+
166
+ # 4. Dotted representation with octal octets (e.g. 0177.0.0.1, 127.000.000.001)
167
+ if "." in raw:
168
+ parts = raw.split(".")
169
+ if len(parts) == 4 and all(p.isdigit() for p in parts):
170
+ for p in parts:
171
+ if len(p) > 1 and p.startswith("0"):
172
+ return True, f"Obfuscated octal-formatted dotted IP rejected: {raw}"
173
+
174
+ return False, None
175
+
176
+ @classmethod
177
+ def validate_target(
178
+ cls,
179
+ target: str,
180
+ workspace_path: Optional[Path] = None,
181
+ ) -> Tuple[bool, str]:
182
+ """
183
+ Validates whether a target is inside an authorized CTF educational sandbox.
184
+ Backwards-compatible tuple (is_authorized, reason_string).
185
+ """
186
+ is_auth, reason, _ = cls.validate_target_with_category(target, workspace_path=workspace_path)
187
+ return is_auth, reason
188
+
189
+ @classmethod
190
+ def validate_target_with_category(
191
+ cls,
192
+ target: str,
193
+ workspace_path: Optional[Path] = None,
194
+ ) -> Tuple[bool, str, TargetCategory]:
195
+ """
196
+ Validates whether a target is inside an authorized CTF educational sandbox.
197
+ Returns:
198
+ (is_authorized, reason_string, target_category)
199
+ """
200
+ if not target or not target.strip():
201
+ return False, "Target is empty", TargetCategory.PRIVATE_LAB
202
+
203
+ raw_target = target.strip()
204
+
205
+ # Check for dangerous schemes upfront
206
+ lower_target = raw_target.lower()
207
+ for d_scheme in DANGEROUS_SCHEMES:
208
+ if lower_target.startswith(f"{d_scheme}:"):
209
+ return False, f"Scope Violation: Dangerous scheme '{d_scheme}:' is strictly prohibited.", TargetCategory.PRIVATE_LAB
210
+
211
+ # Check for protocol-relative URLs without scheme (//example.com)
212
+ if raw_target.startswith("//"):
213
+ return False, "Scope Violation: Protocol-relative URLs ('//') are not permitted.", TargetCategory.PRIVATE_LAB
214
+
215
+ # 1. Local filesystem path check (only when no protocol scheme is specified)
216
+ if "://" not in raw_target:
217
+ local_p = Path(raw_target)
218
+ # Must look like a real path or existing file
219
+ if (
220
+ local_p.exists()
221
+ or (local_p.is_absolute() and ("/" in raw_target or "\\" in raw_target))
222
+ or raw_target.startswith(("./", ".\\", "../", "..\\"))
223
+ or raw_target.endswith((".bin", ".elf", ".py", ".pcap", ".pcapng", ".exe", ".so", ".apk", ".zip", ".tar", ".gz"))
224
+ ):
225
+ return True, f"Authorized local filesystem target: {local_p}", TargetCategory.LOCAL_FILE
226
+
227
+ # 2. URL parsing & scheme extraction
228
+ scheme = None
229
+ host = raw_target
230
+ port = None
231
+
232
+ if "://" in raw_target:
233
+ parsed = urllib.parse.urlsplit(raw_target)
234
+ scheme = parsed.scheme.lower()
235
+ if scheme not in PERMITTED_SCHEMES:
236
+ return False, f"Scope Violation: Prohibited or unsupported scheme '{scheme}'. Allowed: {', '.join(sorted(PERMITTED_SCHEMES))}", TargetCategory.PRIVATE_LAB
237
+
238
+ if not parsed.netloc and not parsed.hostname:
239
+ return False, f"Scope Violation: Malformed URL '{raw_target}' missing host.", TargetCategory.PRIVATE_LAB
240
+
241
+ host = parsed.hostname or ""
242
+ if not host and parsed.netloc:
243
+ # Handle host with port or bracketed IPv6
244
+ host = parsed.netloc.split("@")[-1] # Strip any userinfo
245
+ if ":" in host and not host.startswith("["):
246
+ host = host.split(":")[0]
247
+
248
+ try:
249
+ port = parsed.port
250
+ except ValueError:
251
+ return False, f"Scope Violation: Invalid port specification in '{raw_target}'.", TargetCategory.PRIVATE_LAB
252
+
253
+ else:
254
+ # Format: host:port or ip:port or pure host
255
+ if ":" in raw_target and not raw_target.startswith("["):
256
+ parts = raw_target.split(":")
257
+ if len(parts) == 2 and parts[1].split("/")[0].isdigit():
258
+ host = parts[0]
259
+ port_str = parts[1].split("/")[0]
260
+ try:
261
+ port = int(port_str)
262
+ except ValueError:
263
+ return False, f"Scope Violation: Invalid port '{port_str}'.", TargetCategory.PRIVATE_LAB
264
+
265
+ if not host:
266
+ return False, "Unable to extract valid host from target", TargetCategory.PRIVATE_LAB
267
+
268
+ # Strip brackets from IPv6 host
269
+ if host.startswith("[") and host.endswith("]"):
270
+ host = host[1:-1]
271
+
272
+ # 3. Port range verification (if specified)
273
+ if port is not None:
274
+ if not (1 <= port <= 65535):
275
+ return False, f"Scope Violation: Port {port} outside valid range (1-65535).", TargetCategory.PRIVATE_LAB
276
+
277
+ # 4. Anti-SSRF: Obfuscated IP check
278
+ is_obf, obf_reason = cls.check_ip_obfuscation(host)
279
+ if is_obf:
280
+ return False, f"Scope Violation: {obf_reason}", TargetCategory.PRIVATE_LAB
281
+
282
+ # 5. Anti-SSRF: DNS rebinding wildcard domains check (e.g. nip.io, sslip.io)
283
+ for rebind_pat in DNS_REBINDING_DOMAINS:
284
+ if re.match(rebind_pat, host, re.IGNORECASE):
285
+ return False, f"Scope Violation: Wildcard DNS rebinding domain '{host}' is prohibited.", TargetCategory.PRIVATE_LAB
286
+
287
+ # 6. Check Loopback hostnames
288
+ if host.lower() in ("localhost", "0.0.0.0"):
289
+ return True, f"Authorized loopback target: {host}", TargetCategory.PRIVATE_LAB
290
+
291
+ # 7. Check IP addresses (IPv4 & IPv6)
292
+ try:
293
+ ip_obj = ipaddress.ip_address(host)
294
+
295
+ if ip_obj.is_loopback:
296
+ return True, f"Authorized loopback IP: {ip_obj}", TargetCategory.PRIVATE_LAB
297
+
298
+ if ip_obj.is_private:
299
+ return True, f"Authorized private lab/container IP (RFC1918): {ip_obj}", TargetCategory.PRIVATE_LAB
300
+
301
+ # Check if public IP is explicitly whitelisted in .env
302
+ env_allowed = cls.load_env_whitelist(workspace_path)
303
+ if any(host == item or host in item for item in env_allowed):
304
+ return True, f"Authorized competition IP from .env whitelist: {host}", TargetCategory.CUSTOM_AUTHORIZED
305
+
306
+ return False, f"Scope Violation: Public IP {host} is outside authorized private sandbox.", TargetCategory.PRIVATE_LAB
307
+
308
+ except ValueError:
309
+ # Target is a domain name
310
+ pass
311
+
312
+ # 8. Check Challenge Infrastructure Patterns (highest priority for CTF instances)
313
+ for pat in CHALLENGE_DOMAIN_PATTERNS:
314
+ if re.match(pat, host, re.IGNORECASE):
315
+ return True, f"Authorized CTF challenge instance: {host}", TargetCategory.CHALLENGE_INSTANCE
316
+
317
+ # 9. Check Platform Portal Patterns
318
+ for pat in PLATFORM_PORTAL_PATTERNS:
319
+ if re.match(pat, host, re.IGNORECASE):
320
+ return True, f"Authorized CTF platform portal: {host}", TargetCategory.PLATFORM_PORTAL
321
+
322
+ # 10. Check Private / Lab Domain Patterns
323
+ for pat in PRIVATE_LAB_DOMAIN_PATTERNS:
324
+ if re.match(pat, host, re.IGNORECASE):
325
+ return True, f"Authorized private lab domain: {host}", TargetCategory.PRIVATE_LAB
326
+
327
+ # 11. Check .env whitelist for custom domains
328
+ env_allowed = cls.load_env_whitelist(workspace_path)
329
+ for item in env_allowed:
330
+ if host == item or item.endswith(f"://{host}") or f"://{host}:" in item:
331
+ return True, f"Authorized domain from .env configuration: {host}", TargetCategory.CUSTOM_AUTHORIZED
332
+
333
+ return False, (
334
+ f"Scope Violation: Domain '{host}' is not an authorized CTF challenge instance, platform, or private lab. "
335
+ f"Add to .env (TARGET_HOST/TARGET_URL) to authorize."
336
+ ), TargetCategory.PRIVATE_LAB
337
+
338
+ @classmethod
339
+ def validate_command(cls, command: str) -> Tuple[bool, str]:
340
+ """
341
+ Validates command string against prohibited destructive, wide-scan, or persistence patterns.
342
+ """
343
+ if not command or not command.strip():
344
+ return True, "Empty command"
345
+
346
+ cmd = command.strip()
347
+ for pattern, reason in PROHIBITED_COMMAND_PATTERNS:
348
+ if re.search(pattern, cmd, re.IGNORECASE):
349
+ return False, f"Prohibited Action Blocked: {reason} (matched rule: {pattern})"
350
+
351
+ return True, "Command passed scope guard validation"
352
+
353
+ @classmethod
354
+ def create_security_context_object(
355
+ cls,
356
+ target: str,
357
+ category: str = "general",
358
+ mode: str = "speedrun",
359
+ workspace_path: Optional[Path] = None,
360
+ ) -> Dict[str, Any]:
361
+ """
362
+ Creates a formally validated Security Context Object (SCO) for agent orchestration.
363
+ Raises ScopeViolationError if the target fails scope verification.
364
+ """
365
+ is_valid, reason, target_cat = cls.validate_target_with_category(target, workspace_path=workspace_path)
366
+ if not is_valid:
367
+ raise ScopeViolationError(reason)
368
+
369
+ return {
370
+ "version": "1.2",
371
+ "type": "educational_ctf_challenge",
372
+ "scope": {
373
+ "target": target,
374
+ "category": category,
375
+ "target_type": target_cat.value,
376
+ "mode": mode,
377
+ "authorization_status": "authorized_sandbox",
378
+ "validation_reason": reason,
379
+ },
380
+ "environment": {
381
+ "isolated": True,
382
+ "sandbox_guarantee": True,
383
+ "production_targets": False,
384
+ },
385
+ "constraints": {
386
+ "max_subagent_depth": 1,
387
+ "persistence_allowed": False,
388
+ "destructive_actions_allowed": False,
389
+ "timeout_seconds": 60,
390
+ },
391
+ }
392
+
393
+
394
+ def main():
395
+ parser = argparse.ArgumentParser(description="CTF-Agent Machine-Enforced Scope Guard v1.2")
396
+ parser.add_argument("--validate-target", help="Check if target is inside authorized educational sandbox")
397
+ parser.add_argument("--validate-cmd", help="Check if command contains prohibited or destructive actions")
398
+ parser.add_argument("--create-sco", help="Generate a verified Security Context Object for target")
399
+ parser.add_argument("--json", action="store_true", help="Output results in JSON format")
400
+
401
+ args = parser.parse_args()
402
+
403
+ results: Dict[str, Any] = {}
404
+
405
+ if args.validate_target:
406
+ valid, reason, cat = ScopeGuard.validate_target_with_category(args.validate_target)
407
+ results = {
408
+ "target": args.validate_target,
409
+ "authorized": valid,
410
+ "category": cat.value,
411
+ "reason": reason,
412
+ }
413
+
414
+ elif args.validate_cmd:
415
+ valid, reason = ScopeGuard.validate_command(args.validate_cmd)
416
+ results = {
417
+ "command": args.validate_cmd,
418
+ "authorized": valid,
419
+ "reason": reason,
420
+ }
421
+
422
+ elif args.create_sco:
423
+ try:
424
+ sco = ScopeGuard.create_security_context_object(args.create_sco)
425
+ results = {"status": "success", "sco": sco}
426
+ except ScopeViolationError as e:
427
+ results = {"status": "error", "error": str(e)}
428
+
429
+ else:
430
+ parser.print_help()
431
+ sys.exit(1)
432
+
433
+ if args.json:
434
+ print(json.dumps(results, indent=2))
435
+ else:
436
+ if results.get("authorized") or results.get("status") == "success":
437
+ print(f"[OK] Scope Guard: {results.get('reason', 'Security Context Created')}")
438
+ else:
439
+ print(f"[!] Scope Guard Refusal: {results.get('reason', results.get('error'))}", file=sys.stderr)
440
+ sys.exit(1)
441
+
442
+
443
+ if __name__ == "__main__":
444
+ main()