pἰUdZddlmZddlZddlZddlZddlmZdZdZ dZ ia de d <ia d e d <ead e d <dade d<d0dZd1dZd2dZd3dZd4dZd5dZd6dZd7d!Zd8d$Zd6d%Zd7d&Zd9d'Zd9d(Zd:d;d-Zdav Shared reader for the local feature flags file. The file is written by: - Go resident-agent FeatureFlags plugin (IM360 mode) - Python FeatureFlagsSync plugin (AV mode) Other subsystems (e.g. message_status_publisher) use this module to check individual flag values at runtime. Supported JSON shapes on disk (readers / ``is_enabled`` / ``get_params``): - New shape ``{"flags": ["mqtt_tracking"], "params": {"flag": ["A", "B"]}}`` (mirrors the sync API response; carries per-flag string-list params). - Legacy object ``{"mqtt_tracking": true, ...}`` (still accepted). - JSON array of enabled names ``["mqtt_tracking"]`` (still accepted). - Legacy wrapper ``{"flags": ["mqtt_tracking", ...]}`` (still accepted). The sync API checksum collapses to the legacy sorted-names array when no params are present, so this agent and older agents agree on the bool-only case. With params, the canonical form expands to ``{"flags": [...], "params": {...}}`` with all keys and list members sorted. The sync plugin also writes ``FLAGS_PLAIN_PATH`` (``/var/imunify360/feature_flags``): plain text, one enabled flag name per line (sorted), for scripts. ) annotationsN)Anyz"/var/imunify360/feature_flags.jsonz/var/imunify360/feature_flagsmqtt_tracked_methodsdict[str, Any] _cached_flagsdict[str, list[str]]_cached_paramsfrozenset[str]_cached_mqtt_methodsfloat _cached_mtimerawrreturnc|iSt|tr#i}|D]}t|trd||<|St|tr;|d}t|trt |S|SiS)z@Map file JSON to a flat name->value dict for :func:`is_enabled`.NTflags) isinstanceliststrdictget_normalize_flags_from_file)routiteminners \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/feature_flags.pyrr4s { #t  ! !D$$$ ! D  #t   eT " " 5-e44 4 Ic@t|tsiS|d}t|tsiSi}|D]C\}}t|trt|t s0d|D}|r|||<D|S)zExtract ``params`` mapping from new-shape file content. Only the new ``{"flags": [...], "params": {name: [...]}}`` shape carries params; every other (legacy) shape returns an empty mapping. paramsc<g|]}t|t|Srr).0vs r z%_params_from_file..Us';;; 1c(:(:;1;;;r)rrritemsrr)r raw_paramsrnamevaluescleaneds r_params_from_filer+Fs c4  ""J j$ ' ' "C"((**  f$$$ Jvt,D,D  ;;f;;;  CI Jr+tuple[dict[str, Any], dict[str, list[str]]]c^ tjt}n2#t$r%iaiatada t t fcYSwxYw|tkrt t fS tt5}tj |}dddn #1swxYwYt|at|an #ttjf$riaiaYnwxYwtt t"da|a t t fS)Nr r!)ospathgetmtime FLAGS_PATHOSErrorrr frozensetr ropenjsonloadrr+JSONDecodeErrorrMQTT_TRACKED_METHODS_FLAG)mtimefrs r _read_stater;[sr-  ,, --- ({{ n,,,, - n,, *   )A,,C               2377 *3// T) * %4b99M . ((sE$',AA3CB( C(B,,C/B,0!CC/.C/c(t\}}|SNr;)r_s r _read_flagsr@xs}}HE1 Lrc(t\}}|Sr=r>)r?rs r _read_paramsrB}s IAv Mrr list[str]ct|ttfs$tdt |jt |}td|DS)aReturn sorted enabled flag names for JSON and plain-text sidecar. Accepts the same shapes as :func:`_normalize_flags_from_file` (array, flat map, ``{"flags": [...]}``) so checksums and sidecars match Go ``enabledNamesSortedForChecksum`` / :func:`is_enabled`. z flags must be list or dict, not c3$K|] \}}||V dSr=r!r#kr$s r z,enabled_flag_names_sorted..s+881a8!888888r) rrr TypeErrortype__name__rsortedr&)r normalizeds renabled_flag_names_sortedrNsx edD\ * *  EtE{{/C E E   ,E22J 88 0 0 2 2888 8 88rnamesbytescrt|}tj|ddS)z|JSON array bytes used for sync MD5 when no params are present (matches correlation_api ``checksum_for_sync_flag_list``).T sort_keysindentrLr5dumpsencode)rOordereds rcanonical_sync_flag_list_bytesrZs2UmmG :ga 8 8 8 ? ? A AArrc|st|St|dt|Dd}tj|ddS)aeJSON bytes for the sync MD5 over the full response shape. Mirrors correlation_api ``checksum_for_sync_response``: collapses to the legacy sorted-names array when ``params`` is empty so old agents keep matching, otherwise expands to the deterministic ``{"flags": [...], "params": {...}}`` form with all keys and list members sorted. c4i|]\}}|t|Sr!rLrFs r z1canonical_sync_response_bytes..$CCCDAq1fQiiCCCrrrTrRrS)rZrLr&r5rWrXrOr canonicals rcanonical_sync_response_bytesrcsu 5-e444CCF6<<>>,B,BCCCI :i4 : : : A A C CCrr/rcr t|d5}tj|}dddn #1swxYwYn##tttjf$rYdSwxYwt |}t|}t||}tj |d S)zMD5 hex of the canonical sync-response form for ``path``. Returns "" if the file is missing or invalid. Computes the same MD5 the server returned, so a matching checksum lets the agent skip the response payload on the next sync. zutf-8)encodingNF)usedforsecurity) r4r5r6r2UnicodeDecodeErrorr7rNr+rchashlibmd5 hexdigest)r/r:rrOrpayloads r!sync_checksum_hex_from_flags_filerms $ ) ) ) Q)A,,C                ')= >rr %c * *E s # #F+E6::G ;w 6 6 6 @ @ B BBs,A4 A8A8AA A cdtd|DD}tj|ddS)z.sKKKQDKKKrc<h|]}t|t|Sr!r")r#xs r z1legacy_feature_flags_map_bytes..s'!I!I!IjC6H6H!I!!I!I!IrTrRrSrV)rOds rlegacy_feature_flags_map_bytesrusOKK&!I!IU!I!I!IJJKKKA :a4 2 2 2 9 9 ; ;;rct|dt|Dd}tj|ddS)zPersisted form for ``FLAGS_PATH`` carrying both flags and params. Same canonical shape as ``canonical_sync_response_bytes`` so the file is self-describing and round-trips through ``sync_checksum_hex_from_flags_file``. c4i|]\}}|t|Sr!r]rFs rr^z,sync_response_file_bytes..r_rr`TrRrS)rLr&r5rWrXras rsync_response_file_bytesrxs_CCF6<<>>,B,BCCCI :i4 : : : A A C CCrc|t|}|sdSd|dzS)zPBody for ``FLAGS_PLAIN_PATH``: one name per line, trailing newline if non-empty.r )rNjoinrX)rrOs r$plain_text_payload_for_enabled_flagsr|s? %e , ,E s IIe  t # + + - --rct|tr)tj|ddSt dt |j)zBSerialize dict flags for writing ``FLAGS_PATH`` (legacy map only).TrRrSzflags must be dict, not )rrr5rWrXrIrJrK)rs r$serialize_feature_flags_file_payloadr~sX%Dz%4:::AACCC EtE{{/CEE F FFrF flag_namedefaultboolcnt}||}||St|S)zReturn whether *flag_name* is enabled. If the file is missing, unreadable, or the flag is absent, *default* is returned. Defaults to False so unknown flags are treated as disabled unless the caller explicitly opts in. )r@rr)rrrvalues r is_enabledrs4 MME IIi E } ;;rc`tt|dS)zReturn the per-flag string params from the on-disk file. Empty list when the file is missing/unreadable, the flag is unknown, or the value did not come from the new structured shape (legacy bool-only flags carry no params by definition). r!)rrBr)rs r get_paramsrs&  ""9b11 2 22rc,ttS)uFrozen set of method names whose status events should be enriched for MQTT tracing. Driven entirely by the server-side ``mqtt_tracked_methods`` flag's params list — the agent has no hard-coded list, so adding/removing tracked types is a server-side config change with no agent rollout. Cached: ``_read_state`` pre-builds the frozenset and invalidates it when the flags file's mtime changes. On the hot path — every Reportable message in ``the_sink._call_unlocked`` — this is a single ``os.stat`` syscall plus an identity-stable frozenset return. Two consecutive calls within the same mtime window return the same instance. )r;r r!rrrrsMMM r)rrrr)rrrr)rr,)rr)rr)rrrrC)rOrCrrP)rOrCrrrrP)r/rrr)rrrrP)F)rrrrrr)rrrrC)rr )!__doc__ __future__rrir5r.typingrr1FLAGS_PLAIN_PATHr8r__annotations__r r3r rrr+r;r@rBrNrZrcrmrurxr|r~rrrr!rrrs4#"""""  1 23 " """"'))))) (1y{{2222 $*)))):  9 9 9 9BBBBDDDD(CCCC$<<<< D D D D....GGGG     3333      r