forked from EmpireProject/Empire
-
-
Notifications
You must be signed in to change notification settings - Fork 696
Expand file tree
/
Copy pathpyproject.toml
More file actions
237 lines (218 loc) · 9.07 KB
/
Copy pathpyproject.toml
File metadata and controls
237 lines (218 loc) · 9.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
[tool.poetry]
name = "empire-bc-security-fork"
version = "7.0.2"
description = ""
authors = ["BC Security <info@bc-security.org>"]
readme = "README.md"
homepage = "https://github.com/BC-SECURITY/Empire"
repository = "https://github.com/BC-SECURITY/Empire.git"
# A directory include ships everything beneath it; a glob include keeps only
# what it matches, which is how `empire/server/**/*.py` dropped every yaml,
# ps1, dll and profile.
packages = [{ include = "empire" }]
# `**/.git` keeps a nested repository's metadata directory -- and only that
# directory, not its files -- out of a build. It was added for the profiles
# submodule's gitlink, which poetry's VCS exclusion could not see; that
# submodule is gone, and this repository's own top-level `.git` was never a
# build candidate, being outside the `empire` package include.
exclude = ["empire/test", "**/.git"]
[tool.poetry.dependencies]
python = ">=3.13,<3.15"
urllib3 = "^2.7.0"
requests = "^2.34.2"
macholib = "^1.16.4"
zlib_wrapper = "^0.1.3"
jinja2 = "^3.1.6"
pyparsing = "^3.3.2"
PyMySQL = "^1.2.0"
SQLAlchemy = "^2.0.51"
alembic = "^1.18.5"
PyYAML = "^6.0.3"
cryptography = "^49.0.0"
fastapi = "^0.138.1"
uvicorn = "^0.49.0"
aiofiles = "^25.1.0"
python-multipart = "^0.0.32"
pyjwt = "^2.13.0"
python-socketio = "^5.16.3"
Flask = "^3.1.3"
click = "^8.4.2"
werkzeug = "^3.1.8"
pysecretsocks = {git = "https://github.com/BC-SECURITY/PySecretSOCKS.git", rev = "da5be0e"}
# The official donut-shellcode release doesn't support arm64
# and on newer linux kernels, it's starting to fail due to
# security restrictions with execstack.
# https://github.com/TheWover/donut/issues/139
donut-shellcode = { git = "https://github.com/BC-SECURITY/donut.git", rev = "b361c0d3" }
python-obfuscator = "^0.0.2"
pyinstaller = "^6.21.0"
packaging = "^26.2"
netaddr = "^1.3.0"
bcrypt = "^5.0.0"
pydantic-settings = "^2.14.2"
prettytable = "^3.18.0"
platformdirs = "^4.10.0"
[tool.poetry.group.dev.dependencies]
httpx = "^0.28.1" # For starlette TestClient
pytest = "^9.1.1"
pytest-xdist = "^3.6.0"
ruff = "^0.15.20"
pytest-cov = "^7.1.0"
pytest-env = "^1.6.0"
pytest-rerunfailures = "^16.3"
[tool.poetry.scripts]
empire-server = "empire.main:main"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
extend-exclude = [
'empire/server/data',
'empire/server/downloads',
# This file is a pain to untangle for some of the linting rules
# it is from a 3rd party library, and its mostly untested.
'empire/server/common/malleable',
"empire/server/Empire-Compiler",
".claude/worktrees",
]
target-version = "py313"
[tool.ruff.lint]
# Will revisit line lengths later. Black won't format the strings and comments
# by default. And the experimental flag still has some issues.
# fixes right now: https://github.com/psf/black/issues/2188
extend-ignore = [
"E501",
# TRY002/003: a custom exception class per raise is too invasive for now (~230 hits).
"TRY003", # raise-vanilla-args
"TRY002", # raise-vanilla-class
]
select = [
"F", # pyflakes
"E", # pycodestyle
"W", # pycodestyle
# "C90", # mccabe
"I", # isort
"UP", # pyupgrade
"S", # flake8-bandit
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"PT", # flake8-pytest-style
"RET", # flake8-return
"SLF", # flake8-self
"SIM", # flake8-simplify
"TC", # flake8-type-checking
# "ARG", # flake8-unused-arguments
"PTH", # flake8-use-pathlib
# "ERA", # eradicate
"PLC", # pylint-convention
"PLE", # pylint-error
"PLW", # pylint-warning
"PLR", # pylint-refactor
"TRY", # tryceratops
"FAST", # fastapi
# "PERF", # perflint
"FURB", # refurb
"RUF", # ruff
]
[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi.Query", "fastapi.params.Query", "fastapi.File"]
[tool.ruff.lint.per-file-ignores]
# listeners/stagers/modules are untested payload-generation files (the payload
# runs on the victim, not the server), so:
# PLR/PLW - complexity warnings not worth refactoring until tests exist
# S104 - listeners bind all interfaces by design
# S108 - /tmp paths are victim-side payload options (server build dirs use tempfile)
# S110 - try/except/pass in emitted agent logic
# S311 - non-crypto/cosmetic RNG (server keys/nonces/session-ids use secrets)
# S602/S603/S607 - subprocess spawns compilers/tools to build payloads
# S608 - SQL baked into emitted payload, not server queries
# TRY301 - risky to refactor untested payload-gen (modules only)
"empire/server/listeners/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLW2901", "S104", "S108", "S311"]
"empire/server/stagers/*" = ["PLR0911", "PLR0912", "PLR0915", "S108", "S311", "S602", "S603", "S607"]
"empire/server/modules/*" = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "S108", "S110", "S311", "S602", "S608", "TRY301"]
# It's hard to limit arguments on the endpoint functions.
"empire/server/api/*" = ["PLR0913"]
"empire/server/listeners/http_malleable/listener.py" = ["SLF"]
# Can't control how many fixtures are needed for the tests.
"empire/test/*" = ["PLR0913", "S", "SLF"]
# S603: the wheel probe spawns the console script it is there to verify.
".github/scripts/*" = ["S603"]
"empire/server/modules/powershell/persistence/elevated/schtasks.py" = ["PLR2004"]
"empire/server/modules/powershell/persistence/elevated/wmi.py" = ["PLR2004"]
"empire/server/modules/powershell/persistence/elevated/wmi_updater.py" = ["PLR2004"]
# A bunch of imports of the app and MainMenu object would break the behavior
# if moved to the top of the file. This could be addressed by restructuring
# the code base.
"empire/server/api/app.py" = ["PLC0415"]
"empire/server/api/v2/shared_dependencies.py" = ["PLC0415"]
# S603/S607: server.py spawns subprocesses to launch supporting services.
"empire/server/server.py" = ["PLC0415", "S603", "S607"]
# Deferred alembic imports (PLC0415) + many branches in startup/backup (PLR).
# S603: mysqldump backups. S608: schema query from the server's own configured DB name.
"empire/server/core/db/base.py" = ["PLC0415", "PLR0911", "PLR0912", "PLR0915", "S603", "S608"]
# update_database lazy-imports db.base so the update flow doesn't
# pay db.base's module-level side effects (engine init, create_all)
# on a --help or pre-flight failure.
"empire/server/core/config/data_manager.py" = ["PLC0415"]
# Same reason: main() lazy-imports the server so the `install` refusal is
# reachable before db.base connects to a MySQL that isn't there yet.
"empire/main.py" = ["PLC0415"]
# Intentional S findings in server code (same C2 rationale as the block above):
"empire/server/api/v2/listener/listener_dto.py" = ["S104"] # listener bind default
"empire/server/core/config/config_manager.py" = ["S104"] # API bind default
"empire/server/core/agent_communication_service.py" = ["S104"] # client_ip placeholder, not a bind
# subprocess spawns for payload compilers/tooling (S602/S603/S607):
"empire/server/core/dotnet.py" = ["S603"]
"empire/server/core/go.py" = ["S603", "S607"]
"empire/server/core/obfuscation_service.py" = ["S602"]
"empire/server/core/stager_generation_service.py" = ["S603", "S607"]
"empire/server/utils/data_util.py" = ["S602", "S607"]
"empire/server/utils/file_util.py" = ["S603"]
"empire/server/utils/shellcode_compiler.py" = ["S603"]
# A few imports should be local because they need to be reset between tests.
"empire/test/conftest.py" = ["PLC0415"]
"empire/test/test_logs.py" = ["PLC0415"]
"empire/test/test_listener_generate_launcher.py" = ["PLC0415"]
"empire/test/test_listener_port_forward_pivot.py" = ["PLC0415"]
"empire/test/test_alembic.py" = ["PLC0415"]
"empire/test/test_data_manager_update.py" = ["PLC0415"]
# jwt_auth has a module-level SECRET_KEY query that must run after the
# session-scoped DB setup the `client` fixture triggers.
"empire/test/test_password_hashing.py" = ["PLC0415"]
[tool.coverage.run]
# parallel + multiprocessing concurrency so coverage from each pytest-xdist
# worker subprocess is recorded to a separate data file and combined, instead
# of workers clobbering a single .coverage file.
parallel = true
concurrency = ["multiprocessing"]
omit = [
"empire/server/data/**",
"empire/test/**",
# Agent-side Jinja payload templates (*.py.j2) render code that runs on the
# victim implant, not the server -- server tests cannot meaningfully cover
# them, so they are excluded from the coverage denominator.
"empire/server/**/*.py.j2",
]
[tool.coverage.report]
# https://coverage.readthedocs.io/en/latest/config.html
exclude_also = [
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
omit = [
"empire/server/data/**",
"empire/test/**",
# Agent-side Jinja payload templates (*.py.j2) render code that runs on the
# victim implant, not the server -- server tests cannot meaningfully cover
# them, so they are excluded from the coverage denominator.
"empire/server/**/*.py.j2",
]
ignore_errors = true