farai 0.2.4 → 0.2.6
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.
- package/dist/cli/index.js +17841 -12325
- package/dist/cli/index.js.map +111 -90
- package/docker/kali/Dockerfile +27 -10
- package/docker/kali/farai-image-doctor.py +7 -1
- package/docker/kali/farai-tool-manifest.json +82 -6
- package/docker/kali/farai_mitmproxy_mcp.py +114 -0
- package/package.json +2 -2
package/docker/kali/Dockerfile
CHANGED
|
@@ -27,13 +27,13 @@ FROM kalilinux/kali-rolling
|
|
|
27
27
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
28
28
|
|
|
29
29
|
ARG TARGETARCH
|
|
30
|
-
ARG NAABU_VERSION=2.6.1
|
|
31
30
|
ARG INTERACTSH_VERSION=1.3.1
|
|
32
31
|
ARG TYPESCRIPT_VERSION=5.9.3
|
|
33
32
|
ARG TYPESCRIPT_LANGUAGE_SERVER_VERSION=4.3.4
|
|
34
33
|
ARG PYRIGHT_VERSION=1.1.408
|
|
34
|
+
ARG KALI_CONTRACT=curated-security-runtime-2026-09
|
|
35
35
|
|
|
36
|
-
LABEL org.farai.kali.contract="
|
|
36
|
+
LABEL org.farai.kali.contract="${KALI_CONTRACT}"
|
|
37
37
|
|
|
38
38
|
COPY farai-tool-manifest.json /usr/local/share/farai/farai-tool-manifest.json
|
|
39
39
|
|
|
@@ -46,28 +46,45 @@ RUN apt-get update \
|
|
|
46
46
|
RUN set -eux; \
|
|
47
47
|
case "${TARGETARCH:-amd64}" in \
|
|
48
48
|
amd64) \
|
|
49
|
-
|
|
49
|
+
pd_arch=amd64; \
|
|
50
50
|
interactsh_arch=amd64; interactsh_sha=d553fb3dd4c2684c953aedeb555f13f0d1ca94bd75112c949a5fd975d385a903 ;; \
|
|
51
51
|
arm64) \
|
|
52
|
-
|
|
52
|
+
pd_arch=arm64; \
|
|
53
53
|
interactsh_arch=arm64; interactsh_sha=475aa78c59afad115149a5b0d6d9c99d2410a4f12d0c6f5a24c32e7338fc6adb ;; \
|
|
54
54
|
*) echo "unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
|
|
55
55
|
esac; \
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
install_pd() { \
|
|
57
|
+
tool="$1"; \
|
|
58
|
+
version="$(python3 -c 'import json,sys; print(json.load(open("/usr/local/share/farai/farai-tool-manifest.json", encoding="utf-8"))["pinnedTools"][sys.argv[1]]["version"])' "${tool}")"; \
|
|
59
|
+
checksum="$(python3 -c 'import json,sys; print(json.load(open("/usr/local/share/farai/farai-tool-manifest.json", encoding="utf-8"))["pinnedTools"][sys.argv[1]]["sha256"][sys.argv[2]])' "${tool}" "${pd_arch}")"; \
|
|
60
|
+
archive="/tmp/${tool}.zip"; \
|
|
61
|
+
directory="/tmp/${tool}"; \
|
|
62
|
+
curl -fsSL "https://github.com/projectdiscovery/${tool}/releases/download/v${version}/${tool}_${version}_linux_${pd_arch}.zip" -o "${archive}"; \
|
|
63
|
+
echo "${checksum} ${archive}" | sha256sum -c -; \
|
|
64
|
+
mkdir -p "${directory}"; \
|
|
65
|
+
unzip -q "${archive}" -d "${directory}"; \
|
|
66
|
+
install -m 0755 "${directory}/${tool}" "/usr/local/bin/${tool}"; \
|
|
67
|
+
}; \
|
|
68
|
+
for tool in dnsx httpx katana naabu nuclei subfinder tlsx urlfinder vulnx; do install_pd "${tool}"; done; \
|
|
60
69
|
curl -fsSL "https://github.com/projectdiscovery/interactsh/releases/download/v${INTERACTSH_VERSION}/interactsh-client_${INTERACTSH_VERSION}_linux_${interactsh_arch}.zip" -o /tmp/interactsh.zip; \
|
|
61
70
|
echo "${interactsh_sha} /tmp/interactsh.zip" | sha256sum -c -; \
|
|
62
71
|
unzip -q /tmp/interactsh.zip -d /tmp/interactsh; \
|
|
63
72
|
install -m 0755 /tmp/interactsh/interactsh-client /usr/local/bin/interactsh-client; \
|
|
64
|
-
|
|
73
|
+
templates_version="$(python3 -c 'import json; print(json.load(open("/usr/local/share/farai/farai-tool-manifest.json", encoding="utf-8"))["pinnedAssets"]["nuclei-templates"]["version"])')"; \
|
|
74
|
+
templates_checksum="$(python3 -c 'import json; print(json.load(open("/usr/local/share/farai/farai-tool-manifest.json", encoding="utf-8"))["pinnedAssets"]["nuclei-templates"]["sha256"])')"; \
|
|
75
|
+
curl -fsSL "https://github.com/projectdiscovery/nuclei-templates/archive/refs/tags/v${templates_version}.zip" -o /tmp/nuclei-templates.zip; \
|
|
76
|
+
echo "${templates_checksum} /tmp/nuclei-templates.zip" | sha256sum -c -; \
|
|
77
|
+
unzip -q /tmp/nuclei-templates.zip -d /tmp/nuclei-templates; \
|
|
78
|
+
mkdir -p /usr/local/share/farai/nuclei-templates; \
|
|
79
|
+
cp -a "/tmp/nuclei-templates/nuclei-templates-${templates_version}/." /usr/local/share/farai/nuclei-templates/; \
|
|
80
|
+
rm -rf /tmp/dnsx /tmp/httpx /tmp/katana /tmp/naabu /tmp/nuclei /tmp/subfinder /tmp/tlsx /tmp/urlfinder /tmp/vulnx /tmp/interactsh /tmp/nuclei-templates \
|
|
81
|
+
/tmp/dnsx.zip /tmp/httpx.zip /tmp/katana.zip /tmp/naabu.zip /tmp/nuclei.zip /tmp/subfinder.zip /tmp/tlsx.zip /tmp/urlfinder.zip /tmp/vulnx.zip /tmp/interactsh.zip /tmp/nuclei-templates.zip
|
|
65
82
|
|
|
66
83
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
|
|
67
84
|
&& ln -sf /root/.local/bin/uv /usr/local/bin/uv \
|
|
68
85
|
&& ln -sf /root/.local/bin/uvx /usr/local/bin/uvx
|
|
69
86
|
|
|
70
|
-
RUN UV_TOOL_DIR=/opt/uv/tools UV_TOOL_BIN_DIR=/opt/uv/bin uv tool install --with
|
|
87
|
+
RUN UV_TOOL_DIR=/opt/uv/tools UV_TOOL_BIN_DIR=/opt/uv/bin uv tool install --with 'mcp>=1.25.0,<2' mitmproxy-mcp==0.6.1 \
|
|
71
88
|
&& chmod -R a+rX /opt/uv \
|
|
72
89
|
&& PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install -g \
|
|
73
90
|
@playwright/mcp@latest \
|
|
@@ -24,6 +24,8 @@ MANIFEST = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
|
|
|
24
24
|
CONTRACT = MANIFEST["contract"]
|
|
25
25
|
APT_PACKAGES = tuple(MANIFEST["aptPackages"])
|
|
26
26
|
WORKFLOWS = MANIFEST["workflows"]
|
|
27
|
+
PINNED_TOOLS = MANIFEST["pinnedTools"]
|
|
28
|
+
PINNED_ASSETS = MANIFEST["pinnedAssets"]
|
|
27
29
|
REQUIRED = tuple(dict.fromkeys(command for commands in WORKFLOWS.values() for command in commands))
|
|
28
30
|
|
|
29
31
|
|
|
@@ -61,6 +63,7 @@ def missing_packages():
|
|
|
61
63
|
commands = installed_commands()
|
|
62
64
|
missing = [name for name in REQUIRED if name not in commands]
|
|
63
65
|
missing_apt_packages = missing_packages()
|
|
66
|
+
missing_assets = [name for name, asset in PINNED_ASSETS.items() if not Path(asset["path"]).exists()]
|
|
64
67
|
payload = {
|
|
65
68
|
"contract": CONTRACT,
|
|
66
69
|
"manifest": str(MANIFEST_PATH),
|
|
@@ -69,6 +72,9 @@ payload = {
|
|
|
69
72
|
"requiredCount": len(REQUIRED),
|
|
70
73
|
"missing": missing,
|
|
71
74
|
"missingPackages": missing_apt_packages,
|
|
75
|
+
"missingAssets": missing_assets,
|
|
76
|
+
"pinnedTools": PINNED_TOOLS,
|
|
77
|
+
"pinnedAssets": PINNED_ASSETS,
|
|
72
78
|
"workflows": WORKFLOWS,
|
|
73
79
|
}
|
|
74
80
|
|
|
@@ -80,4 +86,4 @@ if len(sys.argv) == 3 and sys.argv[1] == "--write":
|
|
|
80
86
|
else:
|
|
81
87
|
print(json.dumps(payload, ensure_ascii=True, separators=(",", ":")))
|
|
82
88
|
|
|
83
|
-
raise SystemExit(0 if not missing and not missing_apt_packages else 1)
|
|
89
|
+
raise SystemExit(0 if not missing and not missing_apt_packages and not missing_assets else 1)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contract": "curated-security-runtime",
|
|
2
|
+
"contract": "curated-security-runtime-2026-09",
|
|
3
3
|
"aptPackages": [
|
|
4
4
|
"amass",
|
|
5
5
|
"arjun",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"dirsearch",
|
|
17
17
|
"dnsenum",
|
|
18
18
|
"dnsrecon",
|
|
19
|
-
"dnsx",
|
|
20
19
|
"enum4linux-ng",
|
|
21
20
|
"evil-winrm",
|
|
22
21
|
"exploitdb",
|
|
@@ -54,7 +53,6 @@
|
|
|
54
53
|
"nmap",
|
|
55
54
|
"nodejs",
|
|
56
55
|
"npm",
|
|
57
|
-
"nuclei",
|
|
58
56
|
"openssh-client",
|
|
59
57
|
"openssl",
|
|
60
58
|
"poppler-utils",
|
|
@@ -75,7 +73,6 @@
|
|
|
75
73
|
"sslscan",
|
|
76
74
|
"sslyze",
|
|
77
75
|
"steghide",
|
|
78
|
-
"subfinder",
|
|
79
76
|
"tcpdump",
|
|
80
77
|
"tesseract-ocr",
|
|
81
78
|
"testssl.sh",
|
|
@@ -96,6 +93,78 @@
|
|
|
96
93
|
"yara",
|
|
97
94
|
"zip"
|
|
98
95
|
],
|
|
96
|
+
"pinnedTools": {
|
|
97
|
+
"dnsx": {
|
|
98
|
+
"version": "1.3.1",
|
|
99
|
+
"sha256": {
|
|
100
|
+
"amd64": "438b964653056dd51dcfe614b1a16f8bced3cc48a1d27bc07cc6fdf2ef2a9533",
|
|
101
|
+
"arm64": "dd657dd1ccee5e137eca2dbad0e97dbd067555f744adb97efcda774f1b2fbde1"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"httpx": {
|
|
105
|
+
"version": "1.11.0",
|
|
106
|
+
"sha256": {
|
|
107
|
+
"amd64": "5dce96e7315cff24be7aab3b032f23f7a71b7711a17f8189ebe601f2588f2f87",
|
|
108
|
+
"arm64": "2b5d9ec3d3750df41e04e4216b1400d2ad862d1cb9ebe68ae501b2299cb5b3a5"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"katana": {
|
|
112
|
+
"version": "1.7.0",
|
|
113
|
+
"sha256": {
|
|
114
|
+
"amd64": "fe1142d92f418549338ea46d67a472124878482e225d279e9a42700c75d76a4d",
|
|
115
|
+
"arm64": "9a6885fe9fda850129b110e0f079d42529b0693dd81b59c316f04084935300f0"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"naabu": {
|
|
119
|
+
"version": "2.6.1",
|
|
120
|
+
"sha256": {
|
|
121
|
+
"amd64": "018c4c9884dea971eda860435ede3021d1150732f34cfd245498c6726d8cab90",
|
|
122
|
+
"arm64": "3adc2bb2395c3efff89623499b20eea66ef54924c485d3ae86762393a31736ea"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"nuclei": {
|
|
126
|
+
"version": "3.11.1",
|
|
127
|
+
"sha256": {
|
|
128
|
+
"amd64": "ea63d4ae232808cd7c6bc00d0142428e231fab59dae01042246097d195835ab6",
|
|
129
|
+
"arm64": "8044e3d9768ba0a744b2872c1a87e813006f013da97ca9f50f7661a4203bec07"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"subfinder": {
|
|
133
|
+
"version": "2.16.0",
|
|
134
|
+
"sha256": {
|
|
135
|
+
"amd64": "1b7f9c608e9a5bd59e609a5e09710d63c5485e92d3d49dc2c16eb4fdbe10cb60",
|
|
136
|
+
"arm64": "c81d49559c0f630177be9e347e502e7a3d474aacc6ff78291ffcb4964367d63d"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"tlsx": {
|
|
140
|
+
"version": "1.3.0",
|
|
141
|
+
"sha256": {
|
|
142
|
+
"amd64": "d982c55da676d7f6bfe588550ed19946e3c73234b4a3a11caf105f3ab98564bf",
|
|
143
|
+
"arm64": "70a65a286eaeb336d04c06fd1783541096f7b06c5c2e7a22467406ea2c323449"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"urlfinder": {
|
|
147
|
+
"version": "0.0.3",
|
|
148
|
+
"sha256": {
|
|
149
|
+
"amd64": "655ea7378a0555149831dcc4c3f6aef34f3ce27eda0ce2f2721e2ca5526015d4",
|
|
150
|
+
"arm64": "6af33108d6b76bbd261fa696c69dccf44b9eb5a68dc47b4c41df5cf32f8b8281"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"vulnx": {
|
|
154
|
+
"version": "2.0.2",
|
|
155
|
+
"sha256": {
|
|
156
|
+
"amd64": "387abd1c42b0357e1d4257b54528fc38e1d0357b12f05ab12117a4c628a5aaf8",
|
|
157
|
+
"arm64": "13c5888dde027b84bd3b6cf69c36c87e1e7aad7e7159bdeeb576f4962dcc1d06"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"pinnedAssets": {
|
|
162
|
+
"nuclei-templates": {
|
|
163
|
+
"version": "10.4.8",
|
|
164
|
+
"path": "/usr/local/share/farai/nuclei-templates",
|
|
165
|
+
"sha256": "cb4f88504ebf9d462c2db4c8d2bdf6cb94ecc7b7f4ed09b7f4fd20ceb6bc8002"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
99
168
|
"workflows": {
|
|
100
169
|
"shell-and-network-utilities": [
|
|
101
170
|
"curl",
|
|
@@ -136,9 +205,11 @@
|
|
|
136
205
|
"theHarvester"
|
|
137
206
|
],
|
|
138
207
|
"service-discovery": [
|
|
208
|
+
"httpx",
|
|
139
209
|
"masscan",
|
|
140
210
|
"naabu",
|
|
141
|
-
"nmap"
|
|
211
|
+
"nmap",
|
|
212
|
+
"tlsx"
|
|
142
213
|
],
|
|
143
214
|
"callback-verification": [
|
|
144
215
|
"interactsh-client",
|
|
@@ -150,7 +221,9 @@
|
|
|
150
221
|
"dirsearch",
|
|
151
222
|
"feroxbuster",
|
|
152
223
|
"ffuf",
|
|
153
|
-
"gobuster"
|
|
224
|
+
"gobuster",
|
|
225
|
+
"katana",
|
|
226
|
+
"urlfinder"
|
|
154
227
|
],
|
|
155
228
|
"web-assessment": [
|
|
156
229
|
"commix",
|
|
@@ -166,6 +239,9 @@
|
|
|
166
239
|
"whatweb",
|
|
167
240
|
"wpscan"
|
|
168
241
|
],
|
|
242
|
+
"vulnerability-intelligence": [
|
|
243
|
+
"vulnx"
|
|
244
|
+
],
|
|
169
245
|
"credential-access": [
|
|
170
246
|
"hashcat",
|
|
171
247
|
"hydra",
|
|
@@ -17,6 +17,7 @@ from mitmproxy_mcp.core import server
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
UPSTREAM_TRAFFIC_RECORDER = server.TrafficRecorder
|
|
20
|
+
UPSTREAM_MITM_CONTROLLER = server.MitmController
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def env_int(name: str, default: int, minimum: int, maximum: int) -> int:
|
|
@@ -33,6 +34,84 @@ MAX_MESSAGES = env_int("FARAI_PROXY_MAX_MESSAGES", 200, 1, 1000)
|
|
|
33
34
|
REPLAY_CORRELATION_HEADER = "X-Farai-Replay-Correlation"
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
def env_json_strings(name: str) -> list[str]:
|
|
38
|
+
try:
|
|
39
|
+
value = json.loads(os.environ.get(name, "[]"))
|
|
40
|
+
except json.JSONDecodeError:
|
|
41
|
+
return []
|
|
42
|
+
if not isinstance(value, list):
|
|
43
|
+
return []
|
|
44
|
+
return normalize_pass_through_hosts(value)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def normalize_pass_through_hosts(values: Any) -> list[str]:
|
|
48
|
+
if not isinstance(values, list):
|
|
49
|
+
raise ValueError("pass_through_hosts must be a list")
|
|
50
|
+
hosts: list[str] = []
|
|
51
|
+
for value in values:
|
|
52
|
+
if not isinstance(value, str):
|
|
53
|
+
raise ValueError("pass_through_hosts must contain strings")
|
|
54
|
+
host = value.strip().lower()
|
|
55
|
+
if not host or any(char in host for char in "\r\n\0"):
|
|
56
|
+
raise ValueError("pass_through_hosts contains an invalid host")
|
|
57
|
+
if host not in hosts:
|
|
58
|
+
hosts.append(host)
|
|
59
|
+
return hosts
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def pass_through_pattern(host: str) -> str:
|
|
63
|
+
if host.startswith("*."):
|
|
64
|
+
return rf"^(?:[^:]+\.)?{re.escape(host[2:])}(?::\d+)?$"
|
|
65
|
+
return rf"^{re.escape(host)}(?::\d+)?$"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class FaraiMitmController(UPSTREAM_MITM_CONTROLLER):
|
|
69
|
+
def __init__(self, dump_file: str | None = None):
|
|
70
|
+
super().__init__(dump_file=dump_file)
|
|
71
|
+
configured_tls = os.environ.get("FARAI_PROXY_TLS", "relaxed").strip().lower()
|
|
72
|
+
self.farai_tls_mode = configured_tls if configured_tls in {"strict", "relaxed"} else "relaxed"
|
|
73
|
+
self.farai_pass_through_hosts = env_json_strings("FARAI_PROXY_PASS_THROUGH_HOSTS")
|
|
74
|
+
|
|
75
|
+
async def start(
|
|
76
|
+
self,
|
|
77
|
+
port: int = 8080,
|
|
78
|
+
host: str = "127.0.0.1",
|
|
79
|
+
dump_file: str | None = None,
|
|
80
|
+
):
|
|
81
|
+
result = await super().start(port=port, host=host, dump_file=dump_file)
|
|
82
|
+
self.apply_farai_policy()
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
def apply_farai_policy(self) -> None:
|
|
86
|
+
if self.master is None:
|
|
87
|
+
return
|
|
88
|
+
self.master.options.update(
|
|
89
|
+
ssl_insecure=self.farai_tls_mode == "relaxed",
|
|
90
|
+
ignore_hosts=[pass_through_pattern(host) for host in self.farai_pass_through_hosts],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def farai_policy_state(self) -> dict[str, Any]:
|
|
94
|
+
return {
|
|
95
|
+
"tls": self.farai_tls_mode,
|
|
96
|
+
"passThroughHosts": list(self.farai_pass_through_hosts),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def set_farai_policy(
|
|
100
|
+
self,
|
|
101
|
+
tls_mode: str | None = None,
|
|
102
|
+
pass_through_hosts: list[str] | None = None,
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
if tls_mode is not None:
|
|
105
|
+
normalized_tls = tls_mode.strip().lower()
|
|
106
|
+
if normalized_tls not in {"strict", "relaxed"}:
|
|
107
|
+
raise ValueError("tls_mode must be strict or relaxed")
|
|
108
|
+
self.farai_tls_mode = normalized_tls
|
|
109
|
+
if pass_through_hosts is not None:
|
|
110
|
+
self.farai_pass_through_hosts = normalize_pass_through_hosts(pass_through_hosts)
|
|
111
|
+
self.apply_farai_policy()
|
|
112
|
+
return self.farai_policy_state()
|
|
113
|
+
|
|
114
|
+
|
|
36
115
|
class FlowStore:
|
|
37
116
|
def __init__(self, db_path: str):
|
|
38
117
|
self.db_path = db_path
|
|
@@ -138,6 +217,13 @@ class FaraiTrafficRecorder(UPSTREAM_TRAFFIC_RECORDER):
|
|
|
138
217
|
self._capture(flow)
|
|
139
218
|
|
|
140
219
|
def error(self, flow: http.HTTPFlow) -> None:
|
|
220
|
+
message = flow_error(flow)
|
|
221
|
+
if flow.response is None and message and upstream_tls_verification_failed(message):
|
|
222
|
+
flow.response = http.Response.make(
|
|
223
|
+
502,
|
|
224
|
+
b"farai proxy: upstream tls verification failed; use relaxed tls or pass-through for this host\n",
|
|
225
|
+
{"Content-Type": "text/plain; charset=utf-8", "X-Farai-Proxy-Error": "upstream-tls-verification-failed"},
|
|
226
|
+
)
|
|
141
227
|
super().error(flow)
|
|
142
228
|
self._capture(flow)
|
|
143
229
|
|
|
@@ -624,6 +710,11 @@ def flow_error(flow: Any) -> str | None:
|
|
|
624
710
|
return text_value(getattr(error, "msg", error))
|
|
625
711
|
|
|
626
712
|
|
|
713
|
+
def upstream_tls_verification_failed(message: str) -> bool:
|
|
714
|
+
normalized = message.lower()
|
|
715
|
+
return "certificate verify failed" in normalized or "certificate verification failed" in normalized
|
|
716
|
+
|
|
717
|
+
|
|
627
718
|
def text_value(value: Any, encoding: str = "utf-8") -> str:
|
|
628
719
|
if isinstance(value, bytes):
|
|
629
720
|
return value.decode(encoding, errors="replace")
|
|
@@ -720,6 +811,28 @@ async def proxy_scope_get() -> str:
|
|
|
720
811
|
return json.dumps(recorder.scope_state(), indent=2)
|
|
721
812
|
|
|
722
813
|
|
|
814
|
+
@server.mcp.tool()
|
|
815
|
+
async def proxy_policy_get() -> str:
|
|
816
|
+
controller = server.controller
|
|
817
|
+
if not isinstance(controller, FaraiMitmController):
|
|
818
|
+
return json.dumps({"tls": "strict", "passThroughHosts": []}, indent=2)
|
|
819
|
+
return json.dumps(controller.farai_policy_state(), indent=2)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
@server.mcp.tool()
|
|
823
|
+
async def proxy_policy_set(
|
|
824
|
+
tls_mode: str | None = None,
|
|
825
|
+
pass_through_hosts: list[str] | None = None,
|
|
826
|
+
) -> str:
|
|
827
|
+
controller = server.controller
|
|
828
|
+
if not isinstance(controller, FaraiMitmController):
|
|
829
|
+
raise RuntimeError("Farai proxy policy controller is unavailable.")
|
|
830
|
+
return json.dumps(
|
|
831
|
+
controller.set_farai_policy(tls_mode, pass_through_hosts),
|
|
832
|
+
indent=2,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
|
|
723
836
|
@server.mcp.tool()
|
|
724
837
|
async def proxy_intercept_configure(
|
|
725
838
|
enabled: bool,
|
|
@@ -787,6 +900,7 @@ async def proxy_intercept_resolve(
|
|
|
787
900
|
|
|
788
901
|
|
|
789
902
|
server.TrafficRecorder = FaraiTrafficRecorder
|
|
903
|
+
server.MitmController = FaraiMitmController
|
|
790
904
|
|
|
791
905
|
|
|
792
906
|
if __name__ == "__main__":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "farai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Cyber-first freestyle local AI agent",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"tui:test": "bun test ./test/tui-*.test.ts ./test/tui-*.test.tsx",
|
|
47
47
|
"doctor": "bun run src/cli/index.ts doctor",
|
|
48
48
|
"typecheck": "tsc --noEmit",
|
|
49
|
-
"test": "bun test ./test",
|
|
49
|
+
"test": "bun test ./test/*.test.ts ./test/*.test.tsx",
|
|
50
50
|
"verify": "bun run typecheck && bun run test && bun run test:package",
|
|
51
51
|
"release:check": "bun run verify && bun audit",
|
|
52
52
|
"eval:smoke": "bun run test/evals/smoke.ts",
|