Skip to content

Commit d9ac1b9

Browse files
Merge pull request #162 from blacklanternsecurity/misc-bugfixes
Misc bugfixes
2 parents d451e3b + 93fbfd6 commit d9ac1b9

8 files changed

Lines changed: 786 additions & 550 deletions

File tree

bbot_server/compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ services:
3232
mongodb:
3333
image: mongo:latest
3434
restart: unless-stopped
35+
ulimits:
36+
nofile:
37+
soft: 64000
38+
hard: 64000
3539
volumes:
3640
- bbot-mongodb:/data/db
3741

bbot_server/modules/findings/findings_models.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
SEVERITY_LEVELS = {"INFO": 1, "LOW": 2, "MEDIUM": 3, "HIGH": 4, "CRITICAL": 5}
99

1010
# Confidence levels as constants
11-
CONFIDENCE_LEVELS = {"UNKNOWN": 1, "LOW": 2, "MODERATE": 3, "HIGH": 4, "CONFIRMED": 5}
11+
CONFIDENCE_LEVELS = {"UNKNOWN": 1, "LOW": 2, "MEDIUM": 3, "HIGH": 4, "CONFIRMED": 5}
1212

1313
# severity colors for rich, etc. (bash color names)
1414
SEVERITY_COLORS = {
@@ -81,8 +81,6 @@ class Finding(BaseAssetFacet):
8181
description="Numeric confidence score of the vulnerability (1-5)",
8282
ge=1,
8383
le=5,
84-
default=1,
85-
alias="confidence",
8684
)
8785
temptation: Optional[Annotated[int, "indexed"]] = Field(
8886
description="Likelihood of an attacker taking interest in this finding (1-5)",

compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ services:
3737
mongodb:
3838
image: mongo:latest
3939
restart: unless-stopped
40+
ulimits:
41+
nofile:
42+
soft: 64000
43+
hard: 64000
4044
volumes:
4145
- ./mongodb:/data/db
4246

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "bbot-server"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
description = ""
55
authors = [{name = "TheTechromancer"}]
66
license = "AGPL-3.0"
@@ -17,14 +17,14 @@ dependencies = [
1717
"redis>=5.2.1",
1818
"taskiq-redis>=1.0.3",
1919
"fastapi-mcp>=0.3.3",
20-
"bbot",
2120
"click==8.1.8",
2221
"mcp==1.7.0",
2322
"jmespath>=1.0.1",
2423
"uvicorn>=0.35.0",
2524
"pymongo>=4.15.3",
2625
"cloudcheck>=9.2.0",
2726
"textual>=7.5.0",
27+
"bbot",
2828
]
2929

3030
[project.scripts]
@@ -47,12 +47,12 @@ build-backend = "uv_build"
4747
[tool.uv]
4848
default-groups = ["dev"]
4949

50-
[tool.uv.sources]
51-
bbot = { git = "https://github.com/blacklanternsecurity/bbot", rev = "3.0" }
52-
5350
[tool.uv.build-backend]
5451
module-root = ""
5552

53+
[tool.uv.sources]
54+
bbot = { git = "https://github.com/blacklanternsecurity/bbot", rev = "dev" }
55+
5656
[tool.ruff]
5757
line-length = 119
5858
format.exclude = ["bbot/test/test_step_1/test_manager_*"]

tests/test_applets/test_applet_assets.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,66 @@ async def test_applet_custom_attributes(bbot_server, bbot_events):
426426
# count with custom attribute filter
427427
count = await bbot_server.count_assets(query={"custom_tag": "important"})
428428
assert count == 2
429+
430+
431+
# Verifies the MongoDB predicates the frontend's isEmpty / isNotEmpty operators emit
432+
# (`$in: [null, []]` and `$nin: [null, []]`) correctly distinguish missing-field, explicit-null,
433+
# empty-list, and populated documents on an array field like `open_ports`.
434+
async def test_applet_assets_empty_query_open_ports(bbot_server, bbot_events):
435+
bbot_server = await bbot_server(needs_worker=True)
436+
437+
# skip testing of the http interface (since direct collection mutation isn't supported)
438+
if not bbot_server.is_native:
439+
return
440+
441+
# ingest BBOT events to create some assets
442+
scan1_events, _scan2_events = bbot_events
443+
for e in scan1_events:
444+
await bbot_server.insert_event(e)
445+
await asyncio.sleep(INGEST_PROCESSING_DELAY)
446+
447+
# pick four distinct hosts and force each into a different open_ports shape
448+
host_absent = "evilcorp.com" # field removed entirely
449+
host_null = "www.evilcorp.com" # explicit null
450+
host_empty = "api.evilcorp.com" # empty list
451+
host_populated = "cname.evilcorp.com" # populated
452+
453+
collection = bbot_server.assets.collection
454+
await collection.update_one({"host": host_absent, "type": "Asset"}, {"$unset": {"open_ports": ""}})
455+
await collection.update_one({"host": host_null, "type": "Asset"}, {"$set": {"open_ports": None}})
456+
await collection.update_one({"host": host_empty, "type": "Asset"}, {"$set": {"open_ports": []}})
457+
await collection.update_one({"host": host_populated, "type": "Asset"}, {"$set": {"open_ports": [80]}})
458+
459+
# sanity: confirm the four documents are in the expected shapes
460+
docs = {
461+
d["host"]: d
462+
async for d in collection.find(
463+
{"host": {"$in": [host_absent, host_null, host_empty, host_populated]}, "type": "Asset"}
464+
)
465+
}
466+
assert "open_ports" not in docs[host_absent]
467+
assert docs[host_null]["open_ports"] is None
468+
assert docs[host_empty]["open_ports"] == []
469+
assert docs[host_populated]["open_ports"] == [80]
470+
471+
target_hosts = {host_absent, host_null, host_empty, host_populated}
472+
473+
# isEmpty contract: $in: [null, []] matches missing-field, explicit-null, and empty-list documents.
474+
is_empty_results = [
475+
a
476+
async for a in bbot_server.query_assets(
477+
type="Asset",
478+
query={"host": {"$in": list(target_hosts)}, "open_ports": {"$in": [None, []]}},
479+
)
480+
]
481+
assert {a["host"] for a in is_empty_results} == {host_absent, host_null, host_empty}
482+
483+
# isNotEmpty contract: $nin: [null, []] matches only the populated document.
484+
is_not_empty_results = [
485+
a
486+
async for a in bbot_server.query_assets(
487+
type="Asset",
488+
query={"host": {"$in": list(target_hosts)}, "open_ports": {"$nin": [None, []]}},
489+
)
490+
]
491+
assert {a["host"] for a in is_not_empty_results} == {host_populated}

tests/test_applets/test_applet_findings.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
import asyncio
22
from hashlib import sha1
33
from tests.test_applets.base import BaseAppletTest
4+
from bbot_server.modules.findings.findings_models import Finding
45
from bbot_server.modules.targets.targets_models import CreateTarget
56

67

8+
def test_finding_confidence_kwarg():
9+
# string form
10+
f = Finding(name="X", host="x.com", description="d", severity="HIGH", confidence="HIGH")
11+
assert f.confidence == "HIGH"
12+
assert f.confidence_score == 4
13+
# numeric form
14+
f = Finding(name="X", host="x.com", description="d", severity="HIGH", confidence=5)
15+
assert f.confidence == "CONFIRMED"
16+
assert f.confidence_score == 5
17+
# MEDIUM (renamed from MODERATE) — regression for the rename
18+
f = Finding(name="X", host="x.com", description="d", severity="HIGH", confidence="MEDIUM")
19+
assert f.confidence == "MEDIUM"
20+
assert f.confidence_score == 3
21+
22+
723
class TestAppletFindings(BaseAppletTest):
824
needs_worker = True
925

@@ -25,8 +41,8 @@ async def after_scan_1(self):
2541
assert {f.host for f in findings} == {"www.evilcorp.com", "www2.evilcorp.com"}
2642
assert {f.severity for f in findings} == {"HIGH"}
2743
assert {f.severity_score for f in findings} == {4}
28-
assert {f.confidence for f in findings} == {"UNKNOWN"}
29-
assert {f.confidence_score for f in findings} == {1}
44+
assert {f.confidence for f in findings} == {"HIGH"}
45+
assert {f.confidence_score for f in findings} == {4}
3046

3147
# risk should auto-sync from finding_max_severity via CVSS: HIGH -> 7.0
3248
www_asset = await self.bbot_server.get_asset(host="www.evilcorp.com")
@@ -76,8 +92,8 @@ async def after_scan_2(self):
7692
assert {f.host for f in findings} == {"www.evilcorp.com", "www2.evilcorp.com", "api.evilcorp.com"}
7793
assert {f.severity for f in findings} == {"HIGH", "CRITICAL"}
7894
assert {f.severity_score for f in findings} == {4, 5}
79-
assert {f.confidence for f in findings} == {"UNKNOWN"}
80-
assert {f.confidence_score for f in findings} == {1}
95+
assert {f.confidence for f in findings} == {"HIGH"}
96+
assert {f.confidence_score for f in findings} == {4}
8197
assert {f.url for f in findings} == {
8298
"http://www.evilcorp.com/",
8399
"http://www2.evilcorp.com/",
@@ -107,8 +123,8 @@ async def after_scan_2(self):
107123
assert finding_by_id.host == "api.evilcorp.com"
108124
assert finding_by_id.severity == "CRITICAL"
109125
assert finding_by_id.severity_score == 5
110-
assert finding_by_id.confidence == "UNKNOWN"
111-
assert finding_by_id.confidence_score == 1
126+
assert finding_by_id.confidence == "HIGH"
127+
assert finding_by_id.confidence_score == 4
112128
assert finding_by_id.url == "https://api.evilcorp.com/"
113129
assert finding_by_id.description == "That's a whippin'"
114130

tests/test_cli/test_cli_findingctl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ def test_cli_findingctl(bbot_server_http, bbot_worker, bbot_out_file):
3737
assert {f.host for f in findings} == {"www.evilcorp.com", "www2.evilcorp.com", "api.evilcorp.com"}
3838
assert {f.severity for f in findings} == {"HIGH", "CRITICAL"}
3939
assert {f.severity_score for f in findings} == {4, 5}
40-
assert {f.confidence for f in findings} == {"UNKNOWN"}
41-
assert {f.confidence_score for f in findings} == {1}
40+
assert {f.confidence for f in findings} == {"HIGH"}
41+
assert {f.confidence_score for f in findings} == {4}
4242
assert {f.url for f in findings} == {
4343
"http://www.evilcorp.com/",
4444
"http://www2.evilcorp.com/",

0 commit comments

Comments
 (0)