CoolFace
Apppublic

Dhyaneesh/pwned

sourceHugging Faceupdated 6mo agoView on Hugging Face
1likes
simulation_engine.py1169 linesDownload Raw Back to root
1"""Deterministic phase 4 execution engine for the Pwned environment."""2 3from __future__ import annotations4 5from dataclasses import dataclass, field6import ipaddress7import posixpath8from typing import Literal9 10try:11    from .action_interpreter import InternalAction12    from .contracts import InteractionTurn, SimulationState13    from .public_signal_parsing import extract_absolute_posix_paths14except ImportError:15    from action_interpreter import InternalAction16    from contracts import InteractionTurn, SimulationState17    from public_signal_parsing import extract_absolute_posix_paths18 19 20PRIVILEGE_ORDER = {21    "none": 0,22    "user": 1,23    "root": 2,24}25DEFAULT_WORKING_DIRECTORY = "/home/analyst"26 27 28@dataclass29class ExecutionResult:30    """Internal execution result returned by the simulation engine."""31 32    state: SimulationState33    raw_output: str34    success: bool35    done: bool = False36    terminal_reason: str | None = None37    internal_exploit: bool = False38    discovered_host_ids_added: list[str] = field(default_factory=list)39    discovered_decoy_ids_added: list[str] = field(default_factory=list)40    pivot_host_id: str | None = None41    pivot_source_subnet: str | None = None42    exploit_outcome: Literal["none", "successful", "failed"] = "none"43    action_risk_kind: Literal["scan", "enumerate", "read", "pivot"] | None = None44    risk_events: list[45        Literal[46            "scan",47            "enumerate",48            "read",49            "pivot",50            "failed_exploit",51            "successful_exploit",52            "privesc_attempt_failed",53            "privesc_success",54            "decoy_interaction",55        ]56    ] = field(default_factory=list)57    metadata: dict[str, object] = field(default_factory=dict)58 59 60class SimulationEngine:61    """Execute parsed internal actions against the hidden simulation state."""62 63    def execute(64        self,65        state: SimulationState,66        action: InternalAction,67    ) -> ExecutionResult:68        next_state = state.model_copy(deep=True)69        source_host_id = next_state.current_foothold70 71        if source_host_id is None or source_host_id not in next_state.hosts:72            return ExecutionResult(73                state=next_state,74                raw_output="Execution failed: no active foothold is available.",75                success=False,76                action_risk_kind="enumerate",77                metadata={78                    "execution_status": "failure",79                    "internal_action": action.kind,80                },81            )82 83        source_host = next_state.hosts[source_host_id]84        if not source_host.compromised:85            return ExecutionResult(86                state=next_state,87                raw_output="Permission denied: no active compromised foothold is available.",88                success=False,89                metadata={90                    "execution_status": "failure",91                },92            )93 94        source_host.footprint += 195        if action.verb != "exfiltrate":96            object.__setattr__(next_state, "current_buffer", None)97            object.__setattr__(next_state, "current_buffer_path", None)98 99        if action.verb == "nmap":100            result = self._execute_scan(next_state, action, source_host_id)101            self._record_public_turn(result.state, action.raw_command, result.raw_output)102            return result103        if action.verb == "ls":104            result = self._execute_list(next_state, action, source_host_id)105            self._record_public_turn(result.state, action.raw_command, result.raw_output)106            return result107        if action.verb == "whoami":108            result = self._execute_whoami(next_state, action)109            self._record_public_turn(result.state, action.raw_command, result.raw_output)110            return result111        if action.verb == "cat":112            result = self._execute_read(next_state, action, source_host_id)113            self._record_public_turn(result.state, action.raw_command, result.raw_output)114            return result115        if action.verb == "ssh":116            result = self._execute_pivot(next_state, action, source_host_id)117            self._record_public_turn(result.state, action.raw_command, result.raw_output)118            return result119        if action.verb == "exfiltrate":120            result = self._execute_exfiltrate(next_state, action, source_host_id)121            self._record_public_turn(result.state, action.raw_command, result.raw_output)122            return result123 124        return ExecutionResult(125            state=next_state,126            raw_output=f"Execution failed: unsupported internal action `{action.verb}`.",127            success=False,128            metadata={129                "execution_status": "failure",130                "internal_action": action.kind,131            },132        )133 134    def _execute_scan(135        self,136        state: SimulationState,137        action: InternalAction,138        source_host_id: str,139    ) -> ExecutionResult:140        target_spec = action.target_host141        assert target_spec is not None142 143        target_host_ids = self._resolve_scan_targets(state, source_host_id, target_spec)144        if not target_host_ids:145            return ExecutionResult(146                state=state,147                raw_output=(148                    f"Scan failed: target {target_spec} is unreachable from "149                    f"{source_host_id}."150                ),151                success=False,152                action_risk_kind="scan",153                metadata={154                    "execution_status": "failure",155                    "internal_action": action.kind,156                },157            )158 159        discovered_host_ids_added: list[str] = []160        discovered_decoy_ids_added: list[str] = []161        rendered_targets: list[str] = []162 163        for target_host_id in target_host_ids:164            first_discovery = self._mark_discovered(state, target_host_id)165            self._remember_public_services(state, target_host_id)166            if first_discovery:167                target_host = state.hosts[target_host_id]168                if target_host.decoy:169                    discovered_decoy_ids_added.append(target_host_id)170                else:171                    discovered_host_ids_added.append(target_host_id)172 173            target_host = state.hosts[target_host_id]174            services = "\n".join(175                f"{port}/tcp open {version}"176                for port, version in sorted(177                    target_host.services.items(),178                    key=lambda item: int(item[0]),179                )180            )181            rendered_targets.append(182                "\n".join(183                    [184                        (185                            f"Scan discovered host {target_host_id}"186                            if first_discovery187                            else f"Scan cached for host {target_host_id}"188                        ),189                        services,190                    ]191                )192            )193 194        return ExecutionResult(195            state=state,196            raw_output="\n".join(rendered_targets),197            success=True,198            discovered_host_ids_added=discovered_host_ids_added,199            discovered_decoy_ids_added=discovered_decoy_ids_added,200            action_risk_kind="scan",201            risk_events=["scan"],202            metadata={203                "execution_status": "success",204                "internal_action": action.kind,205                "target_host": target_spec,206                "new_discovery": bool(discovered_host_ids_added),207            },208        )209 210    def _execute_list(211        self,212        state: SimulationState,213        action: InternalAction,214        source_host_id: str,215    ) -> ExecutionResult:216        host = state.hosts[source_host_id]217        target_path = action.target_path or "."218        normalized_dir = self._resolve_directory_path(target_path)219        entries = self._list_directory_entries(host.files, normalized_dir)220        if not entries:221            return ExecutionResult(222                state=state,223                raw_output=f"ls: cannot access '{target_path}': No such file or directory",224                success=False,225                action_risk_kind="enumerate",226                risk_events=["enumerate"],227                metadata={228                    "execution_status": "failure",229                    "internal_action": action.kind,230                },231            )232 233        for visible_path in self._listed_file_paths(host.files, normalized_dir):234            self._remember_public_file(state, source_host_id, visible_path)235 236        return ExecutionResult(237            state=state,238            raw_output="\n".join(entries),239            success=True,240            action_risk_kind="enumerate",241            risk_events=["enumerate"],242            metadata={243                "execution_status": "success",244                "internal_action": action.kind,245            },246        )247 248    def _execute_whoami(249        self,250        state: SimulationState,251        action: InternalAction,252    ) -> ExecutionResult:253        if state.current_foothold is not None and state.active_privileges != "none":254            state.record_public_privilege(state.current_foothold, state.active_privileges)255            state.public_current_privilege = state.active_privileges256        return ExecutionResult(257            state=state,258            raw_output=state.active_privileges,259            success=True,260            action_risk_kind="enumerate",261            risk_events=["enumerate"],262            metadata={263                "execution_status": "success",264                "internal_action": action.kind,265            },266        )267 268    def _execute_read(269        self,270        state: SimulationState,271        action: InternalAction,272        source_host_id: str,273    ) -> ExecutionResult:274        target_path = action.target_path275        assert target_path is not None276 277        host = state.hosts[source_host_id]278        resolved_path = self._resolve_file_path(host.files, target_path)279        file_record = None if resolved_path is None else host.files.get(resolved_path)280 281        if file_record is not None:282            if resolved_path not in state.public_known_files.get(source_host_id, []):283                return ExecutionResult(284                    state=state,285                    raw_output=f"cat: {target_path}: No such file or directory",286                    success=False,287                    action_risk_kind="read",288                    metadata={289                        "execution_status": "failure",290                        "internal_action": action.kind,291                    },292                )293            return self._execute_local_read(294                state=state,295                action=action,296                source_host_id=source_host_id,297                resolved_path=resolved_path,298                file_record=file_record,299            )300 301        remote_target_id = self._resolve_remote_read_target(302            state,303            source_host_id,304            target_path,305        )306        attempted_remote_target_id = remote_target_id307        if attempted_remote_target_id is None:308            attempted_remote_target_id = self._infer_remote_read_attempt_target(309                state,310                source_host_id,311                target_path,312            )313        if attempted_remote_target_id is None:314            return ExecutionResult(315                state=state,316                raw_output=f"cat: {target_path}: No such file or directory",317                success=False,318                action_risk_kind="read",319                metadata={320                    "execution_status": "failure",321                    "internal_action": action.kind,322                },323            )324 325        remote_host = state.hosts[attempted_remote_target_id]326        if remote_host.decoy:327            return ExecutionResult(328                state=state,329                raw_output=f"Permission denied: {target_path}",330                success=False,331                internal_exploit=False,332                exploit_outcome="failed",333                risk_events=["decoy_interaction"],334                metadata={335                    "execution_status": "failure",336                    "internal_action": action.kind,337                    "target_host": attempted_remote_target_id,338                },339            )340 341        vulnerability = next(342            (343                record344                for record in remote_host.vulnerabilities345                if record.trigger_type == "file_read"346                and record.trigger_value == target_path347                and self._privilege_allows(348                    state.active_privileges,349                    record.requires_privilege,350                )351            ),352            None,353        )354        if vulnerability is None or remote_target_id is None:355            return ExecutionResult(356                state=state,357                raw_output=f"Permission denied: {target_path}",358                success=False,359                internal_exploit=False,360                exploit_outcome="failed",361                risk_events=["failed_exploit"],362                metadata={363                    "execution_status": "failure",364                    "internal_action": action.kind,365                    "target_host": attempted_remote_target_id,366                },367            )368 369        remote_file = remote_host.files.get(target_path)370        remote_host.compromised = True371        remote_host.active_privileges = "user"372        self._remember_public_compromise(state, attempted_remote_target_id)373        self._remember_public_file(state, attempted_remote_target_id, target_path)374 375        return ExecutionResult(376            state=state,377            raw_output=(378                remote_file.content379                if remote_file is not None380                else f"Exploit triggered via {target_path}"381            ),382            success=True,383            internal_exploit=True,384            exploit_outcome="successful",385            risk_events=["successful_exploit"],386            metadata={387                "execution_status": "success",388                "internal_action": action.kind,389                "target_host": attempted_remote_target_id,390                "exploit_trigger": target_path,391            },392        )393 394    def _execute_pivot(395        self,396        state: SimulationState,397        action: InternalAction,398        source_host_id: str,399    ) -> ExecutionResult:400        target_host_id = action.target_host401        target_user = action.target_user402        assert target_host_id is not None403        assert target_user is not None404 405        if target_host_id == source_host_id:406            return ExecutionResult(407                state=state,408                raw_output=(409                    f"Access denied: target {target_host_id} is already the "410                    "current foothold."411                ),412                success=False,413                action_risk_kind="pivot",414                metadata={415                    "execution_status": "failure",416                    "internal_action": action.kind,417                },418            )419 420        if target_host_id not in self._reachable_from(state, source_host_id):421            return ExecutionResult(422                state=state,423                raw_output=(424                    f"Access denied: target {target_host_id} is unreachable from "425                    f"{source_host_id}."426                ),427                success=False,428                action_risk_kind="pivot",429                metadata={430                    "execution_status": "failure",431                    "internal_action": action.kind,432                },433            )434 435        target_host = state.hosts[target_host_id]436        source_subnet = state.hosts[source_host_id].subnet437        public_credentials = state.public_known_credentials.get(target_host_id, [])438        matching_ssh_vulns = [439            vuln440            for vuln in target_host.vulnerabilities441            if vuln.trigger_type == "ssh_user" and vuln.trigger_value == target_user442        ]443        matching_credentials = [444            credential445            for credential in public_credentials446            if credential.username == target_user447        ]448        matching_ssh_vuln = None449        credential_match = None450        exact_source_pair = next(451            (452                (vuln, credential)453                for vuln in matching_ssh_vulns454                for credential in matching_credentials455                if vuln.source_file is not None456                and credential.source_file == vuln.source_file457            ),458            None,459        )460        if exact_source_pair is not None:461            matching_ssh_vuln, credential_match = exact_source_pair462        else:463            password_credential = next(464                (465                    credential466                    for credential in matching_credentials467                    if credential.password is not None468                ),469                None,470            )471            if password_credential is not None:472                credential_match = password_credential473            else:474                credential_match = next(iter(matching_credentials), None)475                if matching_ssh_vulns:476                    matching_ssh_vuln = matching_ssh_vulns[0]477 478        if target_host.decoy:479            return ExecutionResult(480                state=state,481                raw_output="Connection refused",482                success=False,483                internal_exploit=False,484                exploit_outcome="failed",485                risk_events=["decoy_interaction"],486                metadata={487                    "execution_status": "failure",488                    "internal_action": action.kind,489                    "target_host": target_host_id,490                },491            )492 493        def _pivot_success_result(494            *,495            risk_events: list,496            internal_exploit: bool,497            exploit_outcome: Literal["none", "successful", "failed"],498        ) -> ExecutionResult:499            prior_compromised = target_host.compromised500            prior_privilege = target_host.active_privileges501            next_privilege = (502                prior_privilege503                if prior_compromised and prior_privilege is not None504                else "user"505            )506            target_host.compromised = True507            target_host.active_privileges = next_privilege508            self._remember_public_compromise(state, target_host_id)509            state.clear_public_privilege(target_host_id)510            object.__setattr__(state, "current_foothold", target_host_id)511            object.__setattr__(state, "active_privileges", next_privilege)512            state.public_current_privilege = None513            return ExecutionResult(514                state=state,515                raw_output=f"Access granted. Current foothold: {target_host_id}",516                success=True,517                pivot_host_id=target_host_id,518                pivot_source_subnet=source_subnet,519                risk_events=risk_events,520                internal_exploit=internal_exploit,521                exploit_outcome=exploit_outcome,522                metadata={523                    "execution_status": "success",524                    "internal_action": action.kind,525                    "target_host": target_host_id,526                },527            )528 529        if matching_ssh_vuln is not None:530            if credential_match is None:531                pass532            elif not self._privilege_allows(533                state.active_privileges,534                matching_ssh_vuln.requires_privilege,535            ):536                return ExecutionResult(537                    state=state,538                    raw_output="Permission denied (publickey)",539                    success=False,540                    internal_exploit=False,541                    exploit_outcome="failed",542                    risk_events=["failed_exploit"],543                    metadata={544                        "execution_status": "failure",545                        "internal_action": action.kind,546                        "target_host": target_host_id,547                    },548                )549            elif (550                matching_ssh_vuln.source_file is not None551                and credential_match.source_file != matching_ssh_vuln.source_file552            ):553                return ExecutionResult(554                    state=state,555                    raw_output="Permission denied (publickey)",556                    success=False,557                    internal_exploit=False,558                    exploit_outcome="failed",559                    risk_events=["failed_exploit"],560                    metadata={561                        "execution_status": "failure",562                        "internal_action": action.kind,563                        "target_host": target_host_id,564                    },565                )566            else:567                exploit_backed = credential_match.password is None568                return _pivot_success_result(569                    risk_events=(570                        ["successful_exploit", "pivot"]571                        if exploit_backed572                        else ["pivot"]573                    ),574                    internal_exploit=exploit_backed,575                    exploit_outcome="successful" if exploit_backed else "none",576                )577 578        passwordless_file_read_hint = (579            credential_match is not None580            and credential_match.password is None581            and any(582                vuln.trigger_type == "file_read"583                and vuln.source_file is not None584                and vuln.source_file == credential_match.source_file585                for vuln in target_host.vulnerabilities586            )587        )588        passwordless_ssh_hint = (589            credential_match is not None590            and credential_match.password is None591            and any(592                vuln.trigger_type == "ssh_user"593                and vuln.source_file is not None594                and vuln.source_file == credential_match.source_file595                for vuln in target_host.vulnerabilities596            )597        )598 599        if credential_match is not None and credential_match.password is not None:600            return _pivot_success_result(601                risk_events=["pivot"],602                internal_exploit=False,603                exploit_outcome="none",604            )605 606        if credential_match is not None and target_host.compromised and passwordless_file_read_hint:607            return _pivot_success_result(608                risk_events=["pivot"],609                internal_exploit=False,610                exploit_outcome="none",611            )612 613        if credential_match is not None and not target_host.compromised and passwordless_ssh_hint:614            return ExecutionResult(615                state=state,616                raw_output="Permission denied (publickey)",617                success=False,618                internal_exploit=False,619                exploit_outcome="failed",620                risk_events=["failed_exploit"],621                metadata={622                    "execution_status": "failure",623                    "internal_action": action.kind,624                    "target_host": target_host_id,625                },626            )627 628        if credential_match is not None and not passwordless_file_read_hint:629            return _pivot_success_result(630                risk_events=["pivot"],631                internal_exploit=False,632                exploit_outcome="none",633            )634 635        if credential_match is not None:636            return ExecutionResult(637                state=state,638                raw_output="Permission denied (publickey)",639                success=False,640                action_risk_kind="pivot",641                metadata={642                    "execution_status": "failure",643                    "internal_action": action.kind,644                    "target_host": target_host_id,645                },646            )647 648        if target_host.compromised:649            return ExecutionResult(650                state=state,651                raw_output="Permission denied (publickey)",652                success=False,653                action_risk_kind="pivot",654                metadata={655                    "execution_status": "failure",656                    "internal_action": action.kind,657                    "target_host": target_host_id,658                },659            )660 661        return ExecutionResult(662            state=state,663            raw_output="Connection refused",664            success=False,665            internal_exploit=False,666            exploit_outcome="failed",667            risk_events=["failed_exploit"],668            metadata={669                "execution_status": "failure",670                "internal_action": action.kind,671                "target_host": target_host_id,672            },673        )674 675    def _execute_exfiltrate(676        self,677        state: SimulationState,678        action: InternalAction,679        source_host_id: str,680    ) -> ExecutionResult:681        del source_host_id682        target = action.target_path683        assert target is not None684 685        matching_flag = next(686            (687                (path, file_record)688                for host in state.hosts.values()689                for path, file_record in host.files.items()690                if file_record.is_flag691                and file_record.flag_id is not None692                and target == file_record.flag_id693            ),694            None,695        )696 697        if matching_flag is None:698            return ExecutionResult(699                state=state,700                raw_output="Requested flag is not in the current buffer.",701                success=False,702                action_risk_kind=None,703                metadata={704                    "execution_status": "failure",705                    "internal_action": action.kind,706                },707            )708 709        flag_path, flag_record = matching_flag710        flag_id = flag_record.flag_id711        if flag_id is None or flag_id not in state.exfiltration_buffer:712            return ExecutionResult(713                state=state,714                raw_output="Requested flag is not in the current buffer.",715                success=False,716                action_risk_kind=None,717                metadata={718                    "execution_status": "failure",719                    "internal_action": action.kind,720                },721            )722 723        return ExecutionResult(724            state=state,725            raw_output=f"Flag exfiltration successful: {flag_id}",726            success=True,727            done=True,728            terminal_reason="exfiltration",729            action_risk_kind=None,730            metadata={731                "execution_status": "success",732                "internal_action": action.kind,733                "terminal_reason": "exfiltration",734            },735        )736 737    def _reachable_from(self, state: SimulationState, source_host_id: str) -> set[str]:738        return {source_host_id, *state.network_graph.adjacency.get(source_host_id, [])}739 740    def _resolve_scan_targets(741        self,742        state: SimulationState,743        source_host_id: str,744        target_spec: str,745    ) -> list[str]:746        reachable = self._reachable_from(state, source_host_id)747 748        if "/" not in target_spec:749            if target_spec in state.hosts and target_spec in reachable:750                return [target_spec]751            return []752 753        try:754            network = ipaddress.ip_network(target_spec, strict=False)755        except ValueError:756            return []757 758        matched_hosts: list[str] = []759        for host_id in sorted(reachable):760            if host_id not in state.hosts:761                continue762            try:763                if ipaddress.ip_address(host_id) in network:764                    matched_hosts.append(host_id)765            except ValueError:766                continue767 768        return matched_hosts769 770    def _mark_discovered(self, state: SimulationState, host_id: str) -> bool:771        if host_id in state.discovered_host_ids:772            return False773        state.discovered_host_ids.append(host_id)774        return True775 776    def _privilege_allows(self, active: str | None, required: str) -> bool:777        active_key = active if active is not None else "none"778        return PRIVILEGE_ORDER.get(active_key, 0) >= PRIVILEGE_ORDER.get(required, 0)779 780    def _remember_public_compromise(self, state: SimulationState, host_id: str) -> None:781        if host_id not in state.public_compromised_host_ids:782            state.public_compromised_host_ids.append(host_id)783            state.public_compromised_host_ids.sort()784 785    def _remember_public_services(self, state: SimulationState, host_id: str) -> None:786        state.record_public_services(host_id)787 788    def _execute_local_read(789        self,790        state: SimulationState,791        source_host_id: str,792        action: InternalAction,793        resolved_path: str,794        file_record,795    ) -> ExecutionResult:796        host = state.hosts[source_host_id]797        if not self._privilege_allows(state.active_privileges, file_record.required_privilege):798            return ExecutionResult(799                state=state,800                raw_output=f"Permission denied: {action.target_path}",801                success=False,802                action_risk_kind="read",803                metadata={804                    "execution_status": "failure",805                    "internal_action": action.kind,806                },807            )808 809        self._remember_public_file(state, source_host_id, resolved_path)810        object.__setattr__(state, "current_buffer", file_record.content)811        object.__setattr__(state, "current_buffer_path", resolved_path)812 813        revealed_targets, newly_visible_host_ids = self._reveal_credentials_from_source_file(814            state,815            source_host_id,816            resolved_path,817        )818        self._reveal_local_paths_from_content(819            state,820            source_host_id=source_host_id,821            source_path=resolved_path,822            raw_output=file_record.content,823        )824 825        if (826            host.privesc_requirement is not None827            and state.active_privileges == "user"828            and resolved_path == host.privesc_requirement.trigger_path829        ):830            host.active_privileges = "root"831            object.__setattr__(state, "active_privileges", "root")832            state.record_public_privilege(source_host_id, "root")833            return ExecutionResult(834                state=state,835                raw_output=f"{file_record.content}\n[+] You are now root.",836                success=True,837                risk_events=["read", "privesc_success"],838                discovered_host_ids_added=[839                    host_id840                    for host_id in newly_visible_host_ids841                    if not state.hosts[host_id].decoy842                ],843                discovered_decoy_ids_added=[844                    host_id845                    for host_id in newly_visible_host_ids846                    if state.hosts[host_id].decoy847                ],848                metadata={849                    "execution_status": "success",850                    "internal_action": action.kind,851                    "privilege_change": "root",852                },853            )854 855        if file_record.is_flag and file_record.flag_id is not None:856            if file_record.flag_id not in state.exfiltration_buffer:857                state.exfiltration_buffer.append(file_record.flag_id)858 859        return ExecutionResult(860            state=state,861            raw_output=file_record.content,862            success=True,863            risk_events=["read"],864            discovered_host_ids_added=[865                host_id866                for host_id in newly_visible_host_ids867                if not state.hosts[host_id].decoy868            ],869            discovered_decoy_ids_added=[870                host_id871                for host_id in newly_visible_host_ids872                if state.hosts[host_id].decoy873            ],874            metadata={875                "execution_status": "success",876                "internal_action": action.kind,877                **({} if not file_record.is_flag else {"flag_id": file_record.flag_id}),878                **(879                    {}880                    if not revealed_targets881                    else {"revealed_credential_targets": revealed_targets}882                ),883            },884        )885 886    def _reveal_local_paths_from_content(887        self,888        state: SimulationState,889        *,890        source_host_id: str,891        source_path: str,892        raw_output: str,893    ) -> None:894        host = state.hosts[source_host_id]895        for disclosed_path in extract_absolute_posix_paths(raw_output):896            if disclosed_path == source_path:897                continue898            if disclosed_path not in host.files:899                continue900            state.record_public_file(901                source_host_id,902                disclosed_path,903                kind="content_disclosure",904                source_host_id=source_host_id,905                source_path=source_path,906            )907 908    def _resolve_remote_read_target(909        self,910        state: SimulationState,911        source_host_id: str,912        target_path: str,913    ) -> str | None:914        candidates = self._remote_read_candidates(state, source_host_id)915        visible_candidates = [916            host_id917            for host_id in candidates918            if target_path in state.public_known_files.get(host_id, [])919        ]920        if len(visible_candidates) == 1:921            return visible_candidates[0]922        return None923 924    def _infer_remote_read_attempt_target(925        self,926        state: SimulationState,927        source_host_id: str,928        target_path: str,929    ) -> str | None:930        candidates = self._remote_read_candidates(state, source_host_id)931        visible_candidates = [932            host_id933            for host_id in candidates934            if target_path in state.public_known_files.get(host_id, [])935        ]936        if len(visible_candidates) == 1:937            return visible_candidates[0]938        if len(visible_candidates) > 1:939            return None940        public_candidates = [941            host_id942            for host_id in candidates943            if self._host_is_publicly_visible(state, host_id)944        ]945        if target_path.startswith("/") and len(public_candidates) == 1:946            return public_candidates[0]947        return None948 949    def _remote_read_candidates(950        self,951        state: SimulationState,952        source_host_id: str,953    ) -> list[str]:954        reachable = self._reachable_from(state, source_host_id)955        return [956            host_id957            for host_id in sorted(state.hosts)958            if host_id != source_host_id959            and host_id in reachable960            and not state.hosts[host_id].compromised961        ]962 963    def _reveal_credentials_from_source_file(964        self,965        state: SimulationState,966        source_host_id: str,967        resolved_path: str,968    ) -> tuple[list[str], list[str]]:969        source_host = state.hosts[source_host_id]970        revealed_targets: list[str] = []971        newly_visible_host_ids: list[str] = []972        for credential in source_host.credentials:973            if credential.source_file != resolved_path:974                continue975            was_visible = self._host_is_publicly_visible(state, credential.target)976            credential.exposed = True977            state.public_known_credentials.setdefault(credential.target, [])978            self._remember_public_credential(state, credential)979            if not was_visible and self._host_is_publicly_visible(state, credential.target):980                newly_visible_host_ids.append(credential.target)981            revealed_targets.append(credential.target)982        for target_host_id, target_host in state.hosts.items():983            for vulnerability in target_host.vulnerabilities:984                if (985                    vulnerability.trigger_type == "file_read"986                    and vulnerability.source_file == resolved_path987                ):988                    was_visible = self._host_is_publicly_visible(state, target_host_id)989                    self._remember_public_file(990                        state,991                        target_host_id,992                        vulnerability.trigger_value,993                        kind="clue_disclosure",994                        source_host_id=source_host_id,995                        source_path=resolved_path,996                    )997                    if not was_visible and self._host_is_publicly_visible(state, target_host_id):998                        newly_visible_host_ids.append(target_host_id)999                    revealed_targets.append(target_host_id)1000        return sorted(set(revealed_targets)), sorted(set(newly_visible_host_ids))1001 1002    def _remember_public_file(1003        self,1004        state: SimulationState,1005        host_id: str,1006        path: str,1007        *,1008        kind: Literal["local_read", "clue_disclosure"] = "local_read",1009        source_host_id: str | None = None,1010        source_path: str | None = None,1011    ) -> None:1012        state.record_public_file(1013            host_id,1014            path,1015            kind=kind,1016            source_host_id=source_host_id,1017            source_path=source_path,1018        )1019 1020    def _remember_public_credential(1021        self,1022        state: SimulationState,1023        credential,1024    ) -> None:1025        visible = state.public_known_credentials.setdefault(credential.target, [])1026        if any(1027            existing.username == credential.username1028            and existing.password == credential.password1029            and existing.target == credential.target1030            and existing.source_file == credential.source_file1031            for existing in visible1032        ):1033            return1034        visible.append(credential.model_copy(deep=True))1035        visible.sort(1036            key=lambda record: (1037                record.target or "",1038                record.username,1039                record.password or "",1040                record.source_file or "",1041            )1042        )1043 1044    def _host_is_publicly_visible(self, state: SimulationState, host_id: str | None) -> bool:1045        if host_id is None:1046            return False1047        return host_id in {1048            *state.discovered_host_ids,1049            *state.public_known_files.keys(),1050            *state.public_known_credentials.keys(),1051            *state.public_compromised_host_ids,1052            *state.public_known_privileges.keys(),1053        }1054 1055    def _resolve_directory_path(self, target_path: str) -> str:1056        if target_path in {".", "./"}:1057            return DEFAULT_WORKING_DIRECTORY1058        if target_path.startswith("/"):1059            return target_path.rstrip("/") or "/"1060        return f"{DEFAULT_WORKING_DIRECTORY.rstrip('/')}/{target_path}".rstrip("/")1061 1062    def _listed_file_paths(1063        self,1064        files: dict[str, object],1065        directory_path: str,1066    ) -> list[str]:1067        normalized = directory_path.rstrip("/") or "/"1068        listed_paths: list[str] = []1069        for path in sorted(files):1070            if normalized == "/":1071                if path.count("/") == 1:1072                    listed_paths.append(path)1073                continue1074            prefix = f"{normalized}/"1075            if not path.startswith(prefix):1076                continue1077            relative = path[len(prefix):]1078            if relative and "/" not in relative:1079                listed_paths.append(path)1080        return listed_paths1081 1082    def _list_directory_entries(1083        self,1084        files: dict[str, object],1085        directory_path: str,1086    ) -> list[str]:1087        normalized = directory_path.rstrip("/") or "/"1088        entries: set[str] = set()1089 1090        for path in sorted(files):1091            if path == normalized:1092                entries.add(path.split("/")[-1])1093                continue1094 1095            if normalized == "/":1096                if not path.startswith("/"):1097                    continue1098                relative = path[1:]1099            else:1100                prefix = f"{normalized}/"1101                if not path.startswith(prefix):1102                    continue1103                relative = path[len(prefix) :]1104 1105            if not relative:1106                continue1107            entries.add(relative.split("/", maxsplit=1)[0])1108 1109        return sorted(entries)1110 1111    def _resolve_file_path(1112        self,1113        files: dict[str, object],1114        target_path: str,1115    ) -> str | None:1116        candidates: list[str] = []1117 1118        if target_path.startswith("/"):1119            candidates.append(posixpath.normpath(target_path))1120        elif target_path.startswith("./"):1121            candidates.append(1122                posixpath.normpath(1123                    f"{DEFAULT_WORKING_DIRECTORY.rstrip('/')}/{target_path[2:]}"1124                )1125            )1126        else:1127            candidates.append(1128                posixpath.normpath(1129                    f"{DEFAULT_WORKING_DIRECTORY.rstrip('/')}/{target_path}"1130                )1131            )1132 1133        for candidate in candidates:1134            if candidate in files:1135                return candidate1136 1137        return None1138 1139    def _record_public_turn(1140        self,1141        state: SimulationState,1142        command: str,1143        raw_output: str,1144    ) -> None:1145        if state.last_command is not None:1146            object.__setattr__(1147                state,1148                "recent_turns",1149                (1150                    state.recent_turns1151                    + [1152                        InteractionTurn(1153                            command=state.last_command,1154                            raw_output=state.current_raw_output,1155                            step=max(0, state.step_count - 1),1156                        )1157                    ]1158                )[-3:],1159            )1160        object.__setattr__(state, "last_command", command)1161        object.__setattr__(state, "current_raw_output", raw_output)1162        state.validate_public_surface_state()1163 1164    def _privilege_allows(self, active_privileges: str, required_privilege: str) -> bool:1165        return PRIVILEGE_ORDER.get(active_privileges, 0) >= PRIVILEGE_ORDER.get(1166            required_privilege,1167            0,1168        )1169