Skip to content

Commit 6104e3c

Browse files
committed
Update version to 5.1.2 in pyproject.toml and related files; enhance BloodHound CE configuration handling with managed and legacy paths; improve command execution logging and telemetry context; refine error reporting in command execution and clock synchronization processes.
1 parent 489b011 commit 6104e3c

23 files changed

Lines changed: 1960 additions & 492 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<img width="740" height="198" alt="adscan_wordmark_horizontal_transparent_cropped" src="https://github.com/user-attachments/assets/4902f205-d9bc-453e-b2ac-8c7d7fa2f329" />
44

5-
[![Version](https://img.shields.io/badge/version-5.1.1--lite-blue.svg)](https://github.com/ADscanPro/adscan/releases)
5+
[![Version](https://img.shields.io/badge/version-5.1.2--lite-blue.svg)](https://github.com/ADscanPro/adscan/releases)
66
[![downloads](https://static.pepy.tech/badge/adscan)](https://pepy.tech/projects/adscan)
77
[![License: BSL 1.1](https://img.shields.io/badge/license-BSL%201.1-blue.svg)](https://github.com/ADscanPro/adscan/blob/main/LICENSE)
88
[![Platform](https://img.shields.io/badge/platform-Linux-lightgrey.svg)](https://github.com/ADscanPro/adscan)

adscan.py

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3950,6 +3950,8 @@ def is_venv():
39503950
is_first_run,
39513951
mark_first_run_complete,
39523952
show_first_run_helper,
3953+
log_cli_command_context,
3954+
build_cli_runtime_snapshot,
39533955
)
39543956

39553957

@@ -8750,6 +8752,17 @@ def _start_bloodhound_ce():
87508752
except (HostHelperError, OSError) as exc:
87518753
telemetry.capture_exception(exc)
87528754
print_error("Failed to start BloodHound CE via host helper.")
8755+
marked_sock = mark_sensitive(sock_path, "path")
8756+
marked_compose = mark_sensitive(compose_path, "path")
8757+
print_info_debug(
8758+
"[bloodhound-ce] host-helper startup failure context: "
8759+
f"socket={marked_sock} socket_exists={Path(sock_path).exists()} "
8760+
f"compose_path={marked_compose}"
8761+
)
8762+
print_instruction(
8763+
"Inspect host-helper logs at ~/.adscan/logs/host-helper.log "
8764+
"and retry `adscan start`."
8765+
)
87538766
print_exception(show_locals=False, exception=exc)
87548767
return False
87558768

@@ -9417,6 +9430,11 @@ def _questionary_select(self, title: str, options: list[str], default_idx: int =
94179430

94189431
# Convert selected value back to index for compatibility with _curses_select
94199432
if selected_value is None:
9433+
try:
9434+
print_info_debug(f"[questionary] Cancelled: {title}")
9435+
print_telemetry_only(f"[questionary][answer] {title}: [cancelled]")
9436+
except Exception:
9437+
pass
94209438
return None
94219439

94229440
# Log user selection for telemetry/debug
@@ -13217,6 +13235,9 @@ def _extract_raw_args(original_input: str, cmd: str) -> str:
1321713235

1321813236
command_name = parts[0].lower()
1321913237
args_list = parts[1:]
13238+
marked_user_input = mark_sensitive(user_input.strip(), "text")
13239+
print_info_debug(f"[cli] Execute command: {marked_user_input}")
13240+
log_cli_command_context(self, command_name, args_list, source="cli")
1322013241
arg_string = " ".join(
1322113242
args_list
1322213243
) # For compatibility with do_* methods expecting a string
@@ -13627,36 +13648,53 @@ def do_info(self, _args):
1362713648
"""
1362813649
from adscan_internal.rich_output import BRAND_COLORS
1362913650

13651+
snapshot = build_cli_runtime_snapshot(shell=self)
13652+
1363013653
content = Text()
1363113654
content.append("Hosts: ", style=f"bold {BRAND_COLORS['info']}")
13632-
content.append(str(self.hosts) + "\n", style="white")
13655+
content.append(str(snapshot.get("hosts")) + "\n", style="white")
1363313656
content.append("Interface: ", style=f"bold {BRAND_COLORS['info']}")
13634-
content.append(str(self.interface) + "\n", style="white")
13657+
content.append(str(snapshot.get("interface")) + "\n", style="white")
1363513658
content.append("My IP: ", style=f"bold {BRAND_COLORS['info']}")
13636-
content.append(str(self.myip) + "\n", style="white")
13659+
content.append(str(snapshot.get("myip")) + "\n", style="white")
1363713660
content.append("Starting Domain: ", style=f"bold {BRAND_COLORS['info']}")
13638-
content.append(str(self.domain) + "\n", style="white")
13661+
content.append(str(snapshot.get("starting_domain")) + "\n", style="white")
13662+
content.append("Starting Domain Auth: ", style=f"bold {BRAND_COLORS['info']}")
13663+
content.append(str(snapshot.get("starting_domain_auth", "unknown")) + "\n", style="white")
1363913664
content.append("Configured Domains: ", style=f"bold {BRAND_COLORS['info']}")
13640-
content.append(str(self.domains) + "\n", style="white")
13665+
content.append(str(snapshot.get("configured_domains")) + "\n", style="white")
1364113666
content.append("Automatic Mode: ", style=f"bold {BRAND_COLORS['info']}")
13642-
content.append(str(self.auto) + "\n", style="white")
13667+
content.append(str(snapshot.get("automatic_mode")) + "\n", style="white")
1364313668
content.append("Pentest Type: ", style=f"bold {BRAND_COLORS['info']}")
1364413669
content.append(
13645-
str(self.type) + "\n" if self.type else "Not set\n", style="white"
13670+
(
13671+
str(snapshot.get("pentest_type")) + "\n"
13672+
if snapshot.get("pentest_type")
13673+
else "Not set\n"
13674+
),
13675+
style="white",
1364613676
)
1364713677
content.append("Current Workspace: ", style=f"bold {BRAND_COLORS['info']}")
13648-
content.append(str(self.current_workspace_dir) + "\n", style="white")
13649-
content.append("Neo4j Server: ", style=f"bold {BRAND_COLORS['info']}")
13650-
content.append(str(self.neo4j_host) + "\n", style="white")
13651-
content.append("Neo4j Port: ", style=f"bold {BRAND_COLORS['info']}")
13652-
content.append(str(self.neo4j_port) + "\n", style="white")
13653-
content.append("Neo4j User: ", style=f"bold {BRAND_COLORS['info']}")
13654-
content.append(str(self.neo4j_db_user) + "\n", style="white")
13655-
# Neo4j password is intentionally omitted for security
13678+
content.append(str(snapshot.get("current_workspace")) + "\n", style="white")
13679+
content.append("BloodHound CE URL: ", style=f"bold {BRAND_COLORS['info']}")
13680+
content.append(
13681+
mark_sensitive(str(snapshot.get("bloodhound_ce_url")), "host") + "\n",
13682+
style="white",
13683+
)
13684+
content.append("BloodHound CE User: ", style=f"bold {BRAND_COLORS['info']}")
13685+
content.append(
13686+
mark_sensitive(str(snapshot.get("bloodhound_ce_user")), "user") + "\n",
13687+
style="white",
13688+
)
13689+
content.append("BloodHound CE Password: ", style=f"bold {BRAND_COLORS['info']}")
13690+
content.append(
13691+
mark_sensitive(str(snapshot.get("bloodhound_ce_password")), "password")
13692+
+ "\n",
13693+
style="white",
13694+
)
1365613695
content.append("Telemetry: ", style=f"bold {BRAND_COLORS['info']}")
13657-
env_val = os.getenv("ADSCAN_TELEMETRY", None)
13658-
enabled = telemetry._is_telemetry_enabled()
13659-
suffix = " (session override)" if env_val is not None else " (persisted)"
13696+
enabled = bool(snapshot.get("telemetry_enabled"))
13697+
suffix = f" ({snapshot.get('telemetry_source', 'persisted')})"
1366013698
content.append(f"{'ON' if enabled else 'OFF'}{suffix}\n", style="white")
1366113699

1366213700
content.append("\nDomain Information:\n", style="bold green")

adscan_core/telemetry.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,7 @@ def _is_session_capture_enabled() -> bool:
762762
HOST_SESSION_CAPTURE_COMMANDS = frozenset({"install", "check", "update", "upgrade"})
763763
CONTAINER_SESSION_CAPTURE_COMMANDS = frozenset({"start", "ci"})
764764
SESSION_WORKSPACE_CONTEXT_COMMANDS = frozenset({"start", "ci"})
765+
_SESSION_TRACE_ID_ENV = "ADSCAN_SESSION_TRACE_ID"
765766
_SESSION_WORKSPACE_CONTEXT_FIELDS = frozenset(
766767
{"workspace_type", "lab_provider", "lab_name", "lab_slug", "lab_name_whitelisted"}
767768
)
@@ -817,6 +818,41 @@ def _filter_workspace_context_metadata(
817818
return filtered
818819

819820

821+
def _resolve_session_scope() -> str:
822+
"""Return session scope for telemetry correlation."""
823+
if os.getenv("ADSCAN_CONTAINER_RUNTIME") == "1":
824+
return "runtime"
825+
return "launcher"
826+
827+
828+
def _resolve_session_trace_id() -> str | None:
829+
"""Return a sanitized session trace identifier from the environment."""
830+
raw = str(os.getenv(_SESSION_TRACE_ID_ENV, "")).strip()
831+
if not raw:
832+
return None
833+
sanitized = re.sub(r"[^a-zA-Z0-9._:-]+", "", raw)
834+
if not sanitized:
835+
return None
836+
return sanitized[:128]
837+
838+
839+
def _enrich_session_metadata_context(
840+
metadata: Optional[dict[str, Any]],
841+
) -> dict[str, Any]:
842+
"""Return metadata enriched with session scope and trace correlation fields."""
843+
enriched: dict[str, Any] = dict(metadata or {})
844+
845+
if not enriched.get("session_scope"):
846+
enriched["session_scope"] = _resolve_session_scope()
847+
848+
if not enriched.get("session_trace_id"):
849+
trace_id = _resolve_session_trace_id()
850+
if trace_id:
851+
enriched["session_trace_id"] = trace_id
852+
853+
return enriched
854+
855+
820856
def build_command_session_metadata(
821857
*,
822858
command_type: Optional[str],
@@ -835,9 +871,7 @@ def build_command_session_metadata(
835871
Returns:
836872
Metadata dictionary or None when no metadata is available.
837873
"""
838-
metadata: dict[str, Any] = {}
839-
if base_metadata:
840-
metadata.update(base_metadata)
874+
metadata: dict[str, Any] = _enrich_session_metadata_context(base_metadata)
841875
if command_type:
842876
metadata["command_type"] = str(command_type)
843877
if extra:
@@ -3665,10 +3699,17 @@ def _vercel_metadata_fields(metadata: Optional[dict[str, Any]]) -> dict[str, Any
36653699
environment = metadata.get("environment")
36663700
command_type = metadata.get("command_type")
36673701
command_success = metadata.get("command_success")
3702+
session_scope = metadata.get("session_scope")
3703+
session_trace_id = metadata.get("session_trace_id")
36683704
if environment:
36693705
updates["environment"] = environment.lower()
36703706
if command_type:
36713707
updates["command_type"] = command_type.lower()
3708+
if session_scope:
3709+
updates["session_scope"] = str(session_scope).lower()
3710+
if session_trace_id:
3711+
updates["session_trace_id"] = str(session_trace_id)
3712+
updates["trace_id"] = str(session_trace_id)
36723713
if workspace_type:
36733714
updates["workspace_type"] = workspace_type
36743715
if command_success is not None:
@@ -3739,6 +3780,9 @@ def _summarize_vercel_payload_context(payload: dict[str, Any]) -> str:
37393780
field_order = (
37403781
"environment",
37413782
"command_type",
3783+
"session_scope",
3784+
"session_trace_id",
3785+
"trace_id",
37423786
"workspace_type",
37433787
"target_type",
37443788
"target_name",
@@ -3930,7 +3974,7 @@ def capture_session_end(console=None, metadata: Optional[dict] = None):
39303974
# monotonic measurement even if the system clock changed.
39313975
effective_started_at = finished_at - timedelta(seconds=duration_seconds)
39323976
session_id = f"{TELEMETRY_ID}_{int(finished_at.timestamp())}"
3933-
metadata_with_env = dict(metadata or {})
3977+
metadata_with_env = _enrich_session_metadata_context(metadata)
39343978

39353979
# Prefer an explicit environment passed by the caller (e.g., host launcher
39363980
# passing its environment into the container). This prevents dev/ci runs
@@ -3975,6 +4019,8 @@ def capture_session_end(console=None, metadata: Optional[dict] = None):
39754019
print_info_debug(
39764020
"[DEBUG] capture_session_end metadata summary: "
39774021
f"command_type={metadata_with_env.get('command_type')!r}, "
4022+
f"session_scope={metadata_with_env.get('session_scope')!r}, "
4023+
f"session_trace_id={metadata_with_env.get('session_trace_id')!r}, "
39784024
f"workspace_type={metadata_with_env.get('workspace_type')!r}, "
39794025
f"lab_provider={metadata_with_env.get('lab_provider')!r}, "
39804026
f"lab_name={metadata_with_env.get('lab_name')!r}, "
@@ -4066,6 +4112,16 @@ def capture_session_end(console=None, metadata: Optional[dict] = None):
40664112
"session_id": session_id,
40674113
"environment": session_env,
40684114
}
4115+
command_type = metadata_with_env.get("command_type")
4116+
if command_type:
4117+
properties["command_type"] = str(command_type).lower()
4118+
session_scope = metadata_with_env.get("session_scope")
4119+
if session_scope:
4120+
properties["session_scope"] = str(session_scope).lower()
4121+
session_trace_id = metadata_with_env.get("session_trace_id")
4122+
if session_trace_id:
4123+
properties["session_trace_id"] = str(session_trace_id)
4124+
properties["trace_id"] = str(session_trace_id)
40694125
if session_url:
40704126
properties["session_url"] = session_url
40714127
# Keep legacy key for backward compatibility

adscan_core/version_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from adscan_core.path_utils import get_adscan_home, get_effective_user_home
1919

20-
VERSION = "5.1.1"
20+
VERSION = "5.1.2"
2121
_LAUNCHER_VERSION_ENV = "ADSCAN_LAUNCHER_VERSION"
2222
_RUNTIME_IMAGE_ENV = "ADSCAN_RUNTIME_IMAGE"
2323

adscan_internal/cli/check.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,15 +1383,27 @@ def check_docker_compose(
13831383
deps.print_instruction("Run: adscan install --only bloodhound")
13841384
all_ok = False
13851385

1386-
# Check BloodHound CE configuration
1387-
bloodhound_config = deps.expand_effective_user_path(
1386+
# Check BloodHound CE configuration (managed path first, legacy fallback)
1387+
managed_compose_path = deps.expand_effective_user_path(
1388+
"~/.adscan/bloodhound/bloodhound-ce/docker-compose.yml"
1389+
)
1390+
legacy_compose_path = deps.expand_effective_user_path(
13881391
"~/.config/bloodhound/docker-compose.yml"
13891392
)
1390-
if deps.path_exists(bloodhound_config):
1391-
deps.print_success(f"BloodHound CE configuration found: {bloodhound_config}")
1393+
if deps.path_exists(managed_compose_path):
1394+
deps.print_success(
1395+
f"BloodHound CE managed configuration found: {managed_compose_path}"
1396+
)
1397+
elif deps.path_exists(legacy_compose_path):
1398+
deps.print_warning(
1399+
f"BloodHound CE legacy configuration found: {legacy_compose_path}"
1400+
)
1401+
deps.print_info(
1402+
"ADscan now uses the managed isolated BloodHound CE stack under ~/.adscan."
1403+
)
13921404
else:
13931405
deps.print_warning(
1394-
f"BloodHound CE configuration not found at {bloodhound_config}"
1406+
"BloodHound CE configuration not found in managed or legacy locations"
13951407
)
13961408
deps.print_info("This will be created automatically when needed.")
13971409

0 commit comments

Comments
 (0)