marla.environment
The single point of contact between MARLA and NASimEmu; nothing outside
this subpackage imports nasimemu directly.
The single point of contact between MARLA and NASimEmu.
Per spec section 3.4/3.1, the RL Orchestrator is the only component that
interacts directly with NASimEmu, and it does so exclusively through this
adapter. Nothing outside marla.environment should import
nasimemu directly.
- class marla.environment.nasimemu_adapter.EnvironmentState(raw_observation, host_rows, host_addresses, subnet_graph=<factory>, step_idx=0)[source]
Bases:
objectEverything the RL Orchestrator needs from the current visible observation.
- Parameters:
- host_rows: ndarray
- raw_observation: ndarray
- class marla.environment.nasimemu_adapter.NasimEmuAdapter(scenario, max_episode_steps, completion_reward, premature_finish_penalty)[source]
Bases:
objectAdapter isolating NASimEmu-specific behavior (spec section 3.4).
- Parameters:
- legal_actions(state)[source]
- Parameters:
state (EnvironmentState)
- Return type:
- objective_satisfied()[source]
All sensitive/value hosts compromised (NASimEmu’s native goal).
A direct query of the underlying environment’s own current state, not derived from any particular
EnvironmentStatesnapshot – there is nothing to pass in.- Return type:
- reset(seed=None)[source]
Start a new episode, generating a fresh scenario instance.
NASimEmu’s own
_generate_env()reseedsrandom/numpyinternally only at env construction time; to get reproducible per-episode scenario generation we (re-)seed immediately before callingreset(), which is what actually regenerates the scenario.- Parameters:
seed (int | None)
- Return type:
- step(action)[source]
Advance the simulation by exactly one compound decision.
FINISH never touches the underlying simulator (see
marla.environment.finish); every other action callsNASimEmuEnv.stepexactly once.- Parameters:
action (ActionDescriptor)
- Return type:
- to_pyg_data(state)[source]
- Parameters:
state (EnvironmentState)
- Return type:
- class marla.environment.nasimemu_adapter.TransitionResult(state, nasimemu_reward, terminated, truncated, info)[source]
Bases:
objectResult of a single compound-decision environment advance.
- Parameters:
state (EnvironmentState | None)
nasimemu_reward (float)
terminated (bool)
truncated (bool)
info (dict)
- state: EnvironmentState | None
Stable action IDs and the FINISH wrapper action.
NASimEmu’s own action space is combinatorial: an action is a
((subnet, host), action_list_index) pair, where action_list_index
indexes a per-scenario list of [ServiceScan, OSScan, SubnetScan,
ProcessScan, *named exploits, *named privescs]. There is no built-in
“legal actions” enumerator; the environment simply lets an agent attempt any
combination and returns a failed ActionResult at cost if the
action’s preconditions aren’t met.
MARLA’s legal action set is therefore: every host address currently visible
in the observation, crossed with every entry of the scenario’s action list,
plus the MARLA-level finish action. Illegal attempts (missing services,
unreachable targets, etc.) stay in the candidate set by design – the Plan
Maker’s knowledge base is expected to down-weight them via visible
prerequisites, and the environment itself charges their cost on failure.
- class marla.environment.actions.ActionDescriptor(action_id, action_type, target_key, parameters=<factory>, is_finish=False)[source]
Bases:
objectA single legal action, identified by a semantically stable ID.
Advisory correlation with the Plan Maker must use
action_id, never positional/vector order – the candidate set size and order can change from step to step as hosts are discovered.- Parameters:
- marla.environment.actions.build_legal_actions(nasim_env, host_addresses)[source]
Enumerate legal action descriptors for the given visible host addresses.
nasim_envis the underlyingnasimemu.env.NASimEmuEnvinstance (already reset, soexploit_list/privesc_listare populated for the current scenario).
- marla.environment.actions.host_target_key(subnet, host)[source]
Stable, human-readable key for a host address, e.g.
host-1-2.
- marla.environment.actions.resolve_action_target(nasim_env, action_id)[source]
Resolve a non-FINISH action ID back into NASimEmu’s own action space.
Returns
(target_address, action_list_index), orNoneforfinish(which never reaches the underlying simulator, seemarla.environment.finish). RaisesValueErrorifaction_iddoes not correspond to any action NASimEmu currently knows about (e.g. an unknown exploit/privesc name).
Convert a visible NASimEmu observation into a Torch Geometric graph.
Only fields derivable from the partially-observable observation are used
(spec section 12): node type, access level, reachability, known
service/process fractions, a coarse “has any services/processes been
identified” scan-status proxy, the objective-target flag (a value-bearing
host, matching the capture_target objective = all sensitive hosts
compromised), and normalized value fields. No hidden simulator state (e.g.
undiscovered services, true exploit success probabilities) is encoded.
The feature width is fixed (NODE_FEATURE_DIM) and independent of a
given scenario’s number of distinct services/processes/OSes, so the same
graph encoder works across scenarios with different vocabularies.
- class marla.environment.graph.GraphObservation(data, node_key_to_index)[source]
Bases:
objectA single-episode-step graph observation plus the target lookup table.
- data: torch_geometric.data.Data
- marla.environment.graph.build_graph_observation(host_rows, host_addresses, subnet_graph)[source]
Build a graph from visible host rows (
raw_observation[:-1]).host_addresses[i]must correspond tohost_rows[i].subnet_graphis the set of(from_subnet, to_subnet)edges discovered so far via subnet scans (tracked by the adapter across an episode – NASimEmu itself does not persist this).
A JSON-serializable, visible-only summary of the current observation.
Sent to the Plan Maker as the advisory request’s observation field
(spec section 9) and used by the deterministic RAG retriever to derive
observation flags (spec section 8). Built from exactly the same visible
HostVector fields used for graph node features (spec section 12) –
no hidden simulator state.
Lists specific discovered facts (which services/processes/OS are
confirmed present), not just counts – a count alone (“2 services
known”) gives the Plan Maker nothing to cross-reference against a
specific exploit/privesc action’s own required service/OS/process (see
environment/actions.py’s parameters), so its confidence scores
can’t actually reflect whether a given action’s prerequisites are met.
NASimEmu’s partial-observability wrapper only ever merges in positive
(nonzero) facts and never clears one back to unknown (see
nasimemu.env.PartiallyObservableWrapper.__update_obs), so an absent
name here means “not yet confirmed”, never “confirmed absent” – nothing
below claims a service/process/OS is confirmed missing.
- marla.environment.observation_summary.build_observation_summary(state)[source]
Per-host visible state, plus the scenario’s fixed capture-target progress.
NASimEmu’s objective is always “gain root access on every sensitive (
is_sensitive_target) host” (spec section 1’scapture_targetobjective – the only supported type);sensitive_hosts_totalandsensitive_hosts_with_root_accessmake that progress explicit rather than requiring the Plan Maker to infer it by scanning every host’svalue/accessfields itself.- Parameters:
state (EnvironmentState)
- Return type:
FINISH reward semantics.
FINISH is a MARLA wrapper action, not a NASimEmu action. Selecting it
never calls into the underlying simulator: NASimEmu’s own TerminalAction
path unconditionally returns reward=0 and triggers an internal
auto-reset (see NASimEmuEnv.step), which is not what MARLA needs (a
configured completion reward or premature-finish penalty, and precise
control over when the next episode’s scenario is generated). Since a
compound decision advances NASimEmu at most once and FINISH performs zero
NASimEmu actions, this trivially satisfies that invariant while avoiding a
redundant double-reset.