



  

<!DOCTYPE html>
<html
  lang="en"
  
  data-color-mode="auto" data-light-theme="light" data-dark-theme="dark"
  data-a11y-animated-images="system" data-a11y-link-underlines="true"
  
  >




  <head>
    <meta charset="utf-8">
  <link rel="dns-prefetch" href="https://github.githubassets.com">
  <link rel="dns-prefetch" href="https://avatars.githubusercontent.com">
  <link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
  <link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
  <link rel="preconnect" href="https://github.githubassets.com" crossorigin>
  <link rel="preconnect" href="https://avatars.githubusercontent.com">

<script type="importmap">{"imports":{"react":"https://github.githubassets.com/assets/react-e27d1b3e03961e68.js","react-dom":"https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js","react-dom/client":"https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js","react-is":"https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js","react-reconciler":"https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js","react/compiler-runtime":"https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js","react/jsx-dev-runtime":"https://github.githubassets.com/assets/react-jsx-dev-runtime-ea55d68667d559e5.js","react/jsx-runtime":"https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js","scheduler":"https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js"}}</script>
<meta name="react-profiling" content="0" data-turbo-transient="true" />
<meta name="react-import-map" content="react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@777f63f87cd0" data-turbo-track="reload" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-e27d1b3e03961e68.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js" />

  


  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="light" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link data-color-theme="light_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link data-color-theme="light_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind-0f910d8806d5761b.css" /><link data-color-theme="light_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind_high_contrast-a7abd4d4c49ac593.css" /><link data-color-theme="light_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia-5fc4c4a9cb41e596.css" /><link data-color-theme="light_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia_high_contrast-ed0cee9214dedabd.css" /><link data-color-theme="dark" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link data-color-theme="dark_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="dark_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind-78a2491d538dab5a.css" /><link data-color-theme="dark_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind_high_contrast-c26b63d7c532d0a2.css" /><link data-color-theme="dark_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia-a6f60cea68c08405.css" /><link data-color-theme="dark_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia_high_contrast-f66faf28b2ea18b6.css" /><link data-color-theme="dark_dimmed" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed-6701f6218c53cf5b.css" /><link data-color-theme="dark_dimmed_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed_high_contrast-7037fa46ae124d97.css" />

  <style type="text/css">
    :root {
      --tab-size-preference: 4;
    }

    pre, code {
      tab-size: var(--tab-size-preference);
    }
  </style>

    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-primitives-97df7784617ce1ea.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-9be9fe6313f476af.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-62747e27e61258dc.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/github-b04ca6fccda778e2.css" />
  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/repository-72bd7d974b5ac1cc.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-4c5c0895f723f870.css" />

  

  <script type="application/json" id="client-env">{"locale":"en","featureFlags":["actions_caches_react_shell","actions_enable_background_steps","actions_new_hosted_runner_image_select_sizes_and_versions","actions_runners_react_shell","agent_author_search_expansion","agent_author_search_expansion_ui_pulls","alternate_user_config_repo","async_conversion_coverage_enabled","billing_billable_licenses_cost_center_bucket_fix","billing_budget_expiration","billing_cost_center_list_assigned_resources","billing_discount_threshold_notification","code_quality_enablement_banner_targeting","code_quality_remove_preview","code_view_raf_sticky_lines","codespaces_prebuild_region_target_update","coding_agent_third_party_model_ui","copilot_agent_snippy","copilot_ahp_tool_call_timing","copilot_api_agentic_issue_marshal_yaml","copilot_automation_repo_mcp_servers","copilot_automations_pagination","copilot_base_model_policy_row","copilot_chat_auto_mode_v2","copilot_chat_clear_model_selection_for_default_change","copilot_chat_early_task_provisioning","copilot_chat_persist_session_drafts","copilot_chat_vision_dotcom_chat_ga_gate","copilot_css_textarea_autosize","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_removal_on_marketplace","copilot_fix_failed_workflows_all_skus","copilot_hide_hovercard","copilot_immersive_task_hyperlinking","copilot_mc_cli_resume_any_users_task","copilot_mission_control_agent_merge_fix_ci","copilot_mission_control_agent_merge_resolve_conflicts","copilot_mission_control_awps_batching","copilot_mission_control_early_stop","copilot_mission_control_managed_sandbox_environments","copilot_mission_control_needs_attention","copilot_mission_control_reasoning_effort","copilot_mission_control_sandbox_remote_bypass","copilot_mission_control_task_alive_updates","copilot_mission_control_task_sharing","copilot_org_policy_page_focus_mode","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_swe_agent_authorization_status_ui","copilot_swe_agent_automation_resource_scoped_writes","copilot_swe_agent_discussion_comment_trigger","copilot_swe_agent_discussion_opened_trigger","copilot_swe_agent_discussion_updated_trigger","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_issue_assigned_trigger","copilot_swe_agent_issue_comment_trigger","copilot_swe_agent_issue_labeled_trigger","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_pull_request_assigned_trigger","copilot_swe_agent_pull_request_comment_trigger","copilot_swe_agent_pull_request_labeled_trigger","copilot_swe_agent_pull_request_merged_trigger","copilot_swe_agent_pull_request_opened_trigger","copilot_swe_agent_pull_request_ready_for_review_trigger","copilot_swe_agent_pull_request_review_requested_trigger","copilot_swe_agent_pull_request_review_submitted_trigger","copilot_swe_agent_pull_request_synchronize_trigger","copilot_swe_agent_sub_issue_added_trigger","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_task_scoped_alive_channel","copilot_token_based_billing","copilot_unconfigured_is_inherited","copilot_user_can_upgrade_plan_field","copilot_web_integration_cutover","copilot_workbench_sunset_redirect","dashboard_agents_module_auth_token_check","dashboard_indexeddb_caching","fgpat_permissions_selector_redesign","glc_code_quality_repo_settings_workflow_config","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","in_product_messaging_datadog_monitoring","ipm_ubb_individual_budget_banner","issue_fields_multi_select","issue_inline_avatars","issue_pinned_views","issue_pinned_views_team_vs_personal","issue_relative_time_micro","issue_viewer_paved_path","issues_expanded_file_types","issues_hide_closed_sub_issues","issues_lazy_load_comment_box_suggestions","issues_react_chrome_container_query_fix","labels_archiving","labels_archiving_info","landing_pages_ninetailed","lifecycle_label_name_updates","marketing_cookie_consent_banner","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_queue_restricted_pushers_warning","merge_status_header_feedback","milestone_closed_issues_prioritization","octocaptcha_origin_optimization","primer_react_css_anchor_positioning","primer_react_merged_forwarded_refs","primer_react_timeline_list_semantics","prs_copilot_app_open_action","prs_css_anchor_positioning","pull_request_copilot_attribution_header","pull_request_overview_merge_control","pull_request_overview_panel_edit_description","pull_request_persister","pull_request_stacks_navigation_shortcuts","pull_request_virtualization_image_estimate","pull_request_virtualization_loader_batching","pull_request_virtualization_scroll_compensation","pull_request_virtualization_scroll_intent","quick_search_lazy_suggestions","react_blob_isolate_code_lines","react_blob_ssr_content_visibility","react_data_router_tanstack_allowed","react_logged_out_repository_header","react_query_props_with_key","react_sandbox_future_tanstack","repo_app_turbo","repo_issues_sidebar_layout","repo_pulls_dashboard_declutter","repo_pulls_dashboard_ga","repo_pulls_dashboard_persistence","repos_contributors_limited_default_range","review_involves_filter","rule_ignored_file_paths","rulesets_actor_list_editor","sample_network_conn_type","security_center_artifact_filters_popover","see_who_reacted","semantic_similarity_duplicate_issue_detection","session_logs_ungroup_reasoning_text","set_sha256_on_repo_creation_form","site_banner_desktop_copilot_app","site_ghca_pixel_mona","site_github_app_ga_page","site_github_app_ga_page_highlight","site_github_app_mobile_native_share","site_global_banner_dev_days_attendee","site_global_nav_spark_models_removed","speculation_rules_ui_service","suggest_custom_property_values_copilot","suppress_automated_browser_vitals","swp_forms_disable_octocaptcha","thread_resolution_reason","update_issue_suggestions","viewscreen_sandbox","warn_inaccessible_attachments","webp_support","workstream_plugin_bootstrap"],"githubDomain":"https://github.com","copilotApiOverrideUrl":"https://api.githubcopilot.com","cmcApiUrl":"https://api.github.com/cmc_internal/api"}</script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/high-contrast-cookie-3663bcaacc724f4c.js"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wp-runtime-721126455a2225e3.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-foundation-156c6259e31cc551.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-runtime-f6ce38f47c2e1d56.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fetch-utilities-804a14996977c014.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ser-fb5d13278eb42203.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tp-ea613009a991e211.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/environment-107a7753389a8a2d.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-runtime.25915bc98fb322a6.module.css" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/catalyst-52ed81112a548e10.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/selector-observer-e8810f64c443fb9b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/relative-time-element-fe7e72f699fdf493.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/by-9da36843ba9d191d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ja9-9a1160b939f2cd52.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/3h-55903cf2303047ad.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hk-0d35a1220587d888.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jz5-c964b1a3671f2a92.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hj-1d800d09e9a8720a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j0-1ad12374da9777f2.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/github-elements-531d35538b61fd0d.js" defer="defer"></script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/element-registry-bf5e3b2aee197122.js" defer="defer"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/runtime-helpers-5e0b3ae3c4036207.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/aria-live-752acdc868e2da64.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hotkey-e87ffadced20a3c9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-core-75bc55d9971828b7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a9-34620c7b13885011.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ur-643d2814115561a9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4t6-5d0869970ac0aac0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pb0-a5c5ec36d311aeef.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/6n-afcf86ccabc4cad2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zj-f08c728cf00018aa.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/o6-2a1c68d269710ee9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/h9a-c6405aa090de0e84.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/vy6-3c3e6e96629c2ce4.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/js-4eb97d5803e657c0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/g7-baf9e817ae102b01.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/unc-96389cade7bad821.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/nu-65322a6997c6c93c.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/behaviors-5dd3809b5c8bac3c.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/react-core.3792087592bf207a.module.css" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-menu-4318f0f4c608f3dd.js" defer="defer"></script>
  
  <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/primer-react-7afef75d393cf294.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/op-acddf38478d45ef9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ncx-461e4b09f2e67ee5.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/qmp-c7cfba40cf5a93b0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j6-148a74299e5a01ab.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/t7l-a52ba56147f77909.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4u-ec4874f70bfd5d76.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/s0-481bd34f90bc2bd7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/y5-ed17e73b4c0bd15e.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/n4-be595475cdbf7d4b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j8-23a3c5ff8eef412d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/la-27679061f16e1495.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/iu-9806a0291a782cbe.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ftx-39fb490e3c1d778f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zi-32a0c3c081ea5cce.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sg-7a5d50475874c0e1.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pd-6a8710840033e042.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/86g-ec28c25ed7d5de06.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sn-1bab7384818953b7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/iq-6d4ecf6d2fb6993c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/p2-7d8e7cee5b46cb45.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7d-2e3cc25d719f3a95.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4d-76d8acdf21a33266.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tsb-e0f6288ab985d9d2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/lf-910ef220ce85c660.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/2j-2f19c1afd2ceaf93.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/me-f1476ec44a29f34a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/p2u-e654a57b1a70c47d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/5d-d97cc006d4da4d9b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/r5-38f013bc2c0fd298.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/r6s-9039f5619230a653.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/9e-cece06724bf6ea3b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/x9-3e077a3a37a73f9f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pj-87629cb5389d7663.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/eq-3764fd5f66494289.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/13-d889936dff7e5c0f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/44-8193eea4af825ead.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/v4-9c1327d212e52a0d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/id-7f39706ba9007db6.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ah-271362a1ed227d03.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/s1g-d434d712bca59d3a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/vz-d7f4a583896f872e.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/47-c8c0ce2700acc5be.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-view-4118a687d0150749.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/pj.75627bfeb9ceb1da.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/ah.2623f7c6ecbe2ecd.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/47.78770c57a48a810c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-view.37656c95a9729fb9.module.css" />


  <title>revenant/CHANGELOG.md at main · lobotomoe/revenant · GitHub</title>



  <meta name="route-pattern" content="/:user_id/:repository/blob/*name(/*path)" data-turbo-transient>
  <meta name="route-controller" content="blob" data-turbo-transient>
  <meta name="route-action" content="show" data-turbo-transient>
  <meta name="fetch-nonce" content="v2:9618f966-06fe-9f32-d17d-f63f52ed3de2">

    
  <meta name="current-catalog-service-hash" content="f3abb0cc802f3d7b95fc8762b94bdcb13bf39634c40c357301c4aa1d67a256fb">


  <meta name="request-id" content="DACA:1748:A00E998:D121173:6ACA5DD0" data-pjax-transient="true"/><meta name="html-safe-nonce" content="52bb745dff671b0f55b8cfcec6dad9e2b8fab521b9c2231968529c4f179a599b" data-pjax-transient="true"/><meta name="visitor-payload" content="eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJEQUNBOjE3NDg6QTAwRTk5ODpEMTIxMTczOjZBQ0E1REQwIiwidmlzaXRvcl9pZCI6IjU2OTY3OTQxNzAyMjUyMjEwNzIiLCJyZWdpb25fZWRnZSI6ImZyYSIsInJlZ2lvbl9yZW5kZXIiOiJmcmEifQ==" data-pjax-transient="true"/><meta name="visitor-hmac" content="0a173de56ca1e2e112741e172a216cb5e09d797bbea17683d5646fcc6a130c75" data-pjax-transient="true"/>


    <meta name="hovercard-subject-tag" content="repository:1158110957" data-turbo-transient>


  <meta name="github-keyboard-shortcuts" content="repository,source-code,file-tree,copilot" data-turbo-transient="true" />
  

  <meta name="selected-link" value="repo_source" data-turbo-transient>
  <link rel="assets" href="https://github.githubassets.com/">

    <meta name="google-site-verification" content="Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I">

<meta name="octolytics-url" content="https://collector.github.com/github/collect" />





  <meta name="analytics-location" content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-turbo-transient="true" />

  




    <meta name="user-login" content="">

  

    <meta name="viewport" content="width=device-width">

    

      <meta name="description" content="Cross-platform Python client for ARX CoSign electronic signatures via SOAP API - revenant/CHANGELOG.md at main · lobotomoe/revenant">

      <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">

    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
    <meta property="fb:app_id" content="1401488693436528">
    <meta name="apple-itunes-app" content="app-id=1477376905, app-argument=https://github.com/lobotomoe/revenant/blob/main/CHANGELOG.md" />

      <meta name="twitter:image" content="https://opengraph.githubassets.com/7e572330d5e3f3b6a24d8e355b9ea44b1c15df70818fb551c597d65263da4ec5/lobotomoe/revenant" /><meta name="twitter:site" content="@github" /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:title" content="revenant/CHANGELOG.md at main · lobotomoe/revenant" /><meta name="twitter:description" content="Cross-platform Python client for ARX CoSign electronic signatures via SOAP API - lobotomoe/revenant" />
  <meta property="og:image" content="https://opengraph.githubassets.com/7e572330d5e3f3b6a24d8e355b9ea44b1c15df70818fb551c597d65263da4ec5/lobotomoe/revenant" /><meta property="og:image:alt" content="Cross-platform Python client for ARX CoSign electronic signatures via SOAP API - lobotomoe/revenant" /><meta property="og:image:width" content="1200" /><meta property="og:image:height" content="600" /><meta property="og:site_name" content="GitHub" /><meta property="og:type" content="object" /><meta property="og:title" content="revenant/CHANGELOG.md at main · lobotomoe/revenant" /><meta property="og:url" content="https://github.com/lobotomoe/revenant/blob/main/CHANGELOG.md" /><meta property="og:description" content="Cross-platform Python client for ARX CoSign electronic signatures via SOAP API - lobotomoe/revenant" />
  




      <meta name="hostname" content="github.com">



        <meta name="expected-hostname" content="github.com">


  <meta http-equiv="x-pjax-version" content="d05be8f8b6905b6460fc264e80447721dfee1e32cf9e4291c4bf58f160c9f705" data-turbo-track="reload">
  <meta http-equiv="x-pjax-csp-version" content="c4a65e47b0c850e1157ae8e66298070851d7e3b558038de5a3b4ff7a93e630ed" data-turbo-track="reload">
  <meta http-equiv="x-pjax-css-version" content="a80b0545b169f440b929ce2b977c76b6988f014cca6513fb83c5df6581635fb3" data-turbo-track="reload">
  <meta http-equiv="x-pjax-js-version" content="af51c6f9d659be0324665373df3ee542f817dbd7f66f352a087935f2ceabe545" data-turbo-track="reload">

  <meta name="turbo-cache-control" content="no-preview" data-turbo-transient="">

      <meta name="turbo-cache-control" content="no-cache" data-turbo-transient>

    <meta data-hydrostats="publish">

  <meta name="go-import" content="github.com/lobotomoe/revenant git https://github.com/lobotomoe/revenant.git">

  <meta name="octolytics-dimension-user_id" content="24254622" /><meta name="octolytics-dimension-user_login" content="lobotomoe" /><meta name="octolytics-dimension-repository_id" content="1158110957" /><meta name="octolytics-dimension-repository_nwo" content="lobotomoe/revenant" /><meta name="octolytics-dimension-repository_public" content="true" /><meta name="octolytics-dimension-repository_is_fork" content="false" /><meta name="octolytics-dimension-repository_network_root_id" content="1158110957" /><meta name="octolytics-dimension-repository_network_root_nwo" content="lobotomoe/revenant" />
  



    

    <meta name="turbo-body-classes" content="logged-out env-production page-responsive">
  <meta name="disable-turbo" content="false">


  <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">


  <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">

  <meta name="release" content="3b1bb5b10f5c7fd1769d56e38b15dfddb34052a8" data-turbo-track="reload">
  <meta name="ui-target" content="full">

  <link rel="mask-icon" href="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" color="#000000">
  <link rel="alternate icon" class="js-site-favicon" type="image/png" href="https://github.githubassets.com/favicons/favicon.png">
  <link rel="icon" class="js-site-favicon" type="image/svg+xml" href="https://github.githubassets.com/favicons/favicon.svg" data-base-href="https://github.githubassets.com/favicons/favicon">

<meta name="theme-color" content="#1e2327">
<meta name="color-scheme" content="light dark" />


  <link rel="manifest" href="/manifest.json" crossOrigin="use-credentials">

  </head>

  <body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
    <div data-turbo-body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
      <div id="__primerPortalRoot__" style="z-index: 1000; position: absolute; width: 100%;" data-turbo-permanent></div>
      

    <div class="position-relative header-wrapper js-header-wrapper ">
      <a href="#start-of-content" data-skip-target-assigned="false" class="px-2 tmp-py-4 color-bg-accent-emphasis color-fg-on-emphasis show-on-focus js-skip-to-content">Skip to content</a>

      <span data-view-component="true" class="progress-pjax-loader Progress position-fixed width-full">
    <span style="width: 0%;" data-view-component="true" class="Progress-item progress-pjax-loader-bar left-0 top-0 color-bg-accent-emphasis"></span>
</span>      
      <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/19-c660652a0a1639be.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog-47c5223d8d1af8ef.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog.d53d993320d57e14.module.css" />

<react-partial
  partial-name="keyboard-shortcuts-dialog"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"docsUrl":"https://docs.github.com/get-started/accessibility/keyboard-shortcuts"}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>





      

          <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-install-banner-partial-3c87deb7f0f20434.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-install-banner-partial.7b357012e82003e7.module.css" />

<react-partial
  partial-name="app-install-banner-partial"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>


          

                <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/et9-7eeec247c287fca4.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/94v-e20946127faa8f6e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fz5-892a9e99cc92858d.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jag-441e702bfd4c7904.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hw-8ce7888b57d0a23c.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/rq-0ec82a12cf538a4d.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/1m-a7a4c022483b2713.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/marketing-header-70703fe17422eb58.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-brand-css.fbc35b3d20e988ff.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/1m.fec8b575d0e5234f.module.css" />

<react-partial
  partial-name="marketing-header"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"color_mode":"dark","logged_in":false,"marketing_page":false,"home_path":"/","login_path":"/login?return_to=https%3A%2F%2Fgithub.com%2Flobotomoe%2Frevenant%2Fblob%2Fmain%2FCHANGELOG.md","signup_path":"/signup?ref_cta=Sign+up\u0026ref_loc=header+logged+out\u0026ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow\u0026source=header-repo\u0026source_repo=lobotomoe%2Frevenant","signup_enabled":true,"is_signup_controller":false,"show_search_and_nav":true,"hide_search":false,"private_mode_enabled":false,"should_use_dotcom_links":true,"auth_hydro_click":"{\"event_type\":\"authentication.click\",\"payload\":{\"location_in_page\":\"site header menu\",\"repository_id\":null,\"auth_type\":\"SIGN_UP\",\"originating_url\":\"https://github.com/lobotomoe/revenant/blob/main/CHANGELOG.md\",\"user_id\":null}}","auth_hydro_click_hmac":"f85466e8f58fff0d99adb53c78826bce1e6b4c4e8e31ac9fa0dd9f65c09a2e37","overlay":false,"fixed":false}}</script>
  <div data-target="react-partial.reactRoot"><div data-color-mode="dark" data-light-theme="light" data-dark-theme="dark"><header class="MarketingHeader-module__root__Tk7n3 HeaderMktg header-logged-out" role="banner" data-marketing-header="true" data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" data-is-top="true"><h2 class="MarketingHeader-module__visuallyHidden__sqKsl">Navigation Menu</h2><button type="button" class="MarketingHeader-module__backdrop__sw4RU" aria-label="Close navigation menu"></button><div class="MarketingHeader-module__bar__mBSyE"><div class="MarketingHeader-module__topRow__yeury"><div class="MarketingHeader-module__toggleSlot__hDxbh"><button type="button" class="HeaderMenuToggle-module__toggle__i8EiC" aria-label="Toggle navigation" aria-expanded="false"><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span></button></div><a href="/" aria-label="Homepage" class="HeaderLogo-module__logo__UFyHI" data-analytics-event="{&quot;action&quot;:&quot;homepage&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;logo&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;homepage_link_logo_header&quot;}"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github" viewBox="0 0 24 24" width="32" height="32" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path></svg></a><div class="AuthCTAs-module__mobileActions__NNzeV"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/login?return_to=https%3A%2F%2Fgithub.com%2Flobotomoe%2Frevenant%2Fblob%2Fmain%2FCHANGELOG.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/lobotomoe/revenant/blob/main/CHANGELOG.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="f85466e8f58fff0d99adb53c78826bce1e6b4c4e8e31ac9fa0dd9f65c09a2e37"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_3dd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_3dd_">Appearance settings</div></div></div><div class="MarketingHeader-module__menu__GIy3y"><div class="MarketingHeader-module__menuWrapper__owstH"><nav class="MarketingNavigation-module__nav__W0KYY" aria-label="Global"><ul class="MarketingNavigation-module__list__tFbMb"><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_nd_">Platform<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5knd_">AI CODE CREATION</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot" data-analytics-event="{&quot;action&quot;:&quot;github_copilot&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>GitHub Copilot</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Write better code with AI</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/ai/github-app" data-analytics-event="{&quot;action&quot;:&quot;github_copilot_app&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_app_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656"></path></svg>GitHub Copilot app</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Direct agents from issue to merge</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/mcp" data-analytics-event="{&quot;action&quot;:&quot;mcp_registry&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;mcp_registry_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mcp NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.52 1.12a3.578 3.578 0 0 1 6.078 2.98 3.578 3.578 0 0 1 2.982 6.08l-3.292 3.293a.252.252 0 0 0 0 .354l.843.843a.749.749 0 1 1-1.06 1.06l-.844-.843a1.75 1.75 0 0 1 0-2.474L13.52 9.12a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0L7.731 9.03A.75.75 0 0 1 6.67 7.97l2.85-2.85a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0l-4.799 4.8A.75.75 0 0 1 .72 5.92Z"></path><path d="M7.52 3.12a.749.749 0 1 1 1.06 1.06L5.731 7.03A2.079 2.079 0 0 0 8.67 9.97l2.85-2.85a.749.749 0 1 1 1.06 1.06l-2.849 2.85A3.578 3.578 0 0 1 4.67 5.97Z"></path></svg>MCP Registry</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Integrate external tools</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9knd_">DEVELOPER WORKFLOWS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/actions" data-analytics-event="{&quot;action&quot;:&quot;actions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;actions_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-workflow NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h3.5C6.216 0 7 .784 7 1.75v3.5A1.75 1.75 0 0 1 5.25 7H4v4a1 1 0 0 0 1 1h4v-1.25C9 9.784 9.784 9 10.75 9h3.5c.966 0 1.75.784 1.75 1.75v3.5A1.75 1.75 0 0 1 14.25 16h-3.5A1.75 1.75 0 0 1 9 14.25v-.75H5A2.5 2.5 0 0 1 2.5 11V7h-.75A1.75 1.75 0 0 1 0 5.25Zm1.75-.25a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Zm9 9a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Z"></path></svg>Actions</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Automate any workflow</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/codespaces" data-analytics-event="{&quot;action&quot;:&quot;codespaces&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;codespaces_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codespaces NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 11.25c0-.966.784-1.75 1.75-1.75h12.5c.966 0 1.75.784 1.75 1.75v3A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm2-9.5C2 .784 2.784 0 3.75 0h8.5C13.216 0 14 .784 14 1.75v5a1.75 1.75 0 0 1-1.75 1.75h-8.5A1.75 1.75 0 0 1 2 6.75Zm1.75-.25a.25.25 0 0 0-.25.25v5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5a.25.25 0 0 0-.25-.25Zm-2 9.5a.25.25 0 0 0-.25.25v3c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-3a.25.25 0 0 0-.25-.25Z"></path><path d="M7 12.75a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Zm-4 0a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1-.75-.75Z"></path></svg>Codespaces</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Instant dev environments</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/issues" data-analytics-event="{&quot;action&quot;:&quot;issues&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;issues_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-issue-opened NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path></svg>Issues</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Plan and track work</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-review" data-analytics-event="{&quot;action&quot;:&quot;code_review&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_review_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg>Code Review</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Manage code changes</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-quality" data-analytics-event="{&quot;action&quot;:&quot;code_quality&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_quality_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codescan-checkmark NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.28 6.28a.75.75 0 1 0-1.06-1.06L6.25 8.19l-.97-.97a.75.75 0 0 0-1.06 1.06l1.5 1.5a.75.75 0 0 0 1.06 0l3.5-3.5Z"></path><path d="M7.5 15a7.5 7.5 0 1 1 5.807-2.754l2.473 2.474a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2.474-2.473A7.472 7.472 0 0 1 7.5 15Zm0-13.5a6 6 0 1 0 4.094 10.386.748.748 0 0 1 .293-.292 6.002 6.002 0 0 0 1.117-6.486A6.002 6.002 0 0 0 7.5 1.5Z"></path></svg>Code Quality</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enforce quality at merge</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dknd_">APPLICATION SECURITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Find and fix vulnerabilities</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/code-security" data-analytics-event="{&quot;action&quot;:&quot;code_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code-square NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25Zm7.47 3.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L10.69 8 9.22 6.53a.75.75 0 0 1 0-1.06ZM6.78 6.53 5.31 8l1.47 1.47a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>Code security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Secure your code as you build</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/secret-protection" data-analytics-event="{&quot;action&quot;:&quot;secret_protection&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;secret_protection_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-lock NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"></path></svg>Secret protection</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Stop leaks before they start</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ NavGroup-module__hasSeparator__FnMrN"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_hknd_">EXPLORE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_hknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/why-github" data-analytics-event="{&quot;action&quot;:&quot;why_github&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;why_github_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Why GitHub</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog" data-analytics-event="{&quot;action&quot;:&quot;blog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;blog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Blog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog/changelog" data-analytics-event="{&quot;action&quot;:&quot;changelog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;changelog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Changelog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/marketplace" data-analytics-event="{&quot;action&quot;:&quot;marketplace&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;marketplace_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Marketplace</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/features" data-analytics-event="{&quot;action&quot;:&quot;view_all_features&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_features_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all features</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_17d_">Solutions<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_17d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5l7d_">BY COMPANY SIZE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprises&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprises_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Enterprises</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/team" data-analytics-event="{&quot;action&quot;:&quot;small_and_medium_teams&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;small_and_medium_teams_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Small and medium teams</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise/startups" data-analytics-event="{&quot;action&quot;:&quot;startups&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;startups_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Startups</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/nonprofits" data-analytics-event="{&quot;action&quot;:&quot;nonprofits&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;nonprofits_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Nonprofits</span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9l7d_">BY USE CASE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/app-modernization" data-analytics-event="{&quot;action&quot;:&quot;app_modernization&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;app_modernization_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">App Modernization</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devsecops" data-analytics-event="{&quot;action&quot;:&quot;devsecops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devsecops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevSecOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/ci-cd" data-analytics-event="{&quot;action&quot;:&quot;ci/cd&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ci/cd_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">CI/CD</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/use-case" data-analytics-event="{&quot;action&quot;:&quot;view_all_use_cases&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_use_cases_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all use cases</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dl7d_">BY INDUSTRY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dl7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/healthcare" data-analytics-event="{&quot;action&quot;:&quot;healthcare&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;healthcare_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Healthcare</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/financial-services" data-analytics-event="{&quot;action&quot;:&quot;financial_services&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;financial_services_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Financial services</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/manufacturing" data-analytics-event="{&quot;action&quot;:&quot;manufacturing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;manufacturing_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Manufacturing</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/government" data-analytics-event="{&quot;action&quot;:&quot;government&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;government_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Government</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/industry" data-analytics-event="{&quot;action&quot;:&quot;view_all_industries&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_industries_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all industries</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions" data-analytics-event="{&quot;action&quot;:&quot;view_all_solutions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_solutions_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all solutions</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_1nd_">Resources<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_1nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5lnd_">EXPLORE BY TOPIC</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=ai" data-analytics-event="{&quot;action&quot;:&quot;ai&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ai_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">AI</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=software-development" data-analytics-event="{&quot;action&quot;:&quot;software_development&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;software_development_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Software Development</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=security" data-analytics-event="{&quot;action&quot;:&quot;security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources/articles" data-analytics-event="{&quot;action&quot;:&quot;view_all_topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_topics_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all topics</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9lnd_">EXPLORE BY TYPE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/customer-stories" data-analytics-event="{&quot;action&quot;:&quot;customer_stories&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_stories_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer stories</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/events" data-analytics-event="{&quot;action&quot;:&quot;events__webinars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;events__webinars_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Events &amp; webinars</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/whitepapers" data-analytics-event="{&quot;action&quot;:&quot;ebooks__reports&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ebooks__reports_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Ebooks &amp; reports</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/executive-insights" data-analytics-event="{&quot;action&quot;:&quot;business_insights&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;business_insights_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Business insights</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://skills.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_skills&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_skills_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Skills</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dlnd_">SUPPORT &amp; SERVICES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dlnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://support.github.com" data-analytics-event="{&quot;action&quot;:&quot;customer_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_support_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer support</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/orgs/community/discussions" data-analytics-event="{&quot;action&quot;:&quot;community_forum&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;community_forum_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Community forum</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trust-center" data-analytics-event="{&quot;action&quot;:&quot;trust_center&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trust_center_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trust center</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/partners" data-analytics-event="{&quot;action&quot;:&quot;partners&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;partners_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Partners</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources" data-analytics-event="{&quot;action&quot;:&quot;view_all_resources&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_resources_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all resources</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_27d_">Open Source<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_27d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5m7d_">COMMUNITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/open-source/sponsors" data-analytics-event="{&quot;action&quot;:&quot;github_sponsors&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_sponsors_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sponsor-tiers NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.586 1C12.268 1 13.5 2.37 13.5 4.25c0 1.745-.996 3.359-2.622 4.831-.166.15-.336.297-.509.438l1.116 5.584a.75.75 0 0 1-.991.852l-2.409-.876a.25.25 0 0 0-.17 0l-2.409.876a.75.75 0 0 1-.991-.852L5.63 9.519a13.78 13.78 0 0 1-.51-.438C3.497 7.609 2.5 5.995 2.5 4.25 2.5 2.37 3.732 1 5.414 1c.963 0 1.843.403 2.474 1.073L8 2.198l.112-.125a3.385 3.385 0 0 1 2.283-1.068L10.586 1Zm-3.621 9.495-.718 3.594 1.155-.42a1.75 1.75 0 0 1 1.028-.051l.168.051 1.154.42-.718-3.592c-.199.13-.37.235-.505.314l-.169.097a.75.75 0 0 1-.72 0 9.54 9.54 0 0 1-.515-.308l-.16-.105ZM10.586 2.5c-.863 0-1.611.58-1.866 1.459-.209.721-1.231.721-1.44 0C7.025 3.08 6.277 2.5 5.414 2.5 4.598 2.5 4 3.165 4 4.25c0 1.23.786 2.504 2.128 3.719.49.443 1.018.846 1.546 1.198l.325.21.076-.047.251-.163a13.341 13.341 0 0 0 1.546-1.198C11.214 6.754 12 5.479 12 4.25c0-1.085-.598-1.75-1.414-1.75Z"></path></svg>GitHub Sponsors</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Fund open source developers</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9m7d_">PROGRAMS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://securitylab.github.com" data-analytics-event="{&quot;action&quot;:&quot;security_lab&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_lab_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security Lab</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://maintainers.github.com" data-analytics-event="{&quot;action&quot;:&quot;maintainer_community&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;maintainer_community_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Maintainer Community</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://stars.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_stars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_stars_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Stars</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://archiveprogram.github.com" data-analytics-event="{&quot;action&quot;:&quot;archive_program&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;archive_program_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Archive Program</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dm7d_">REPOSITORIES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dm7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/topics" data-analytics-event="{&quot;action&quot;:&quot;topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;topics_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Topics</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trending" data-analytics-event="{&quot;action&quot;:&quot;trending&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trending_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trending</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/collections" data-analytics-event="{&quot;action&quot;:&quot;collections&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;collections_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Collections</span></a></li></ul></div></li></ul></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_2nd_">Enterprise<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_2nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5mnd_">ENTERPRISE SOLUTIONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprise_platform&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprise_platform_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-stack NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.122.392a1.75 1.75 0 0 1 1.756 0l5.003 2.902c.83.481.83 1.68 0 2.162L8.878 8.358a1.75 1.75 0 0 1-1.756 0L2.119 5.456a1.251 1.251 0 0 1 0-2.162ZM8.125 1.69a.248.248 0 0 0-.25 0l-4.63 2.685 4.63 2.685a.248.248 0 0 0 .25 0l4.63-2.685ZM1.601 7.789a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0L1.874 8.814A.75.75 0 0 1 1.6 7.789Zm0 3.5a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0l-5.248-3.044a.75.75 0 0 1-.273-1.025Z"></path></svg>Enterprise platform</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">AI-powered developer platform</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9mnd_">AVAILABLE ADD-ONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade security features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot/copilot-business" data-analytics-event="{&quot;action&quot;:&quot;copilot_for_business&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;copilot_for_business_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>Copilot for Business</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade AI features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise/premium-support" data-analytics-event="{&quot;action&quot;:&quot;premium_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;premium_support_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-comment-discussion NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z"></path></svg>Premium Support</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade 24/7 support</span></span></a></li></ul></div></li></ul></div></div></li><li><a href="https://github.com/pricing" data-analytics-event="{&quot;action&quot;:&quot;pricing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;pricing&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;pricing_link_pricing_navbar&quot;}" class="MarketingNavigation-module__navLink__hUomM">Pricing</a></li></ul></nav><div class="MarketingHeader-module__ctaContainer__tBmPz"><div class="HeaderSearch-module__searchSlot__oVOUS"><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderSearch-module__trigger__zsF9q" type="button" aria-haspopup="dialog" aria-expanded="false" aria-label="Search or jump to, type / to search" data-analytics-event="{&quot;action&quot;:&quot;searchbar&quot;,&quot;tag&quot;:&quot;input&quot;,&quot;context&quot;:&quot;global&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;searchbar_input_global_navbar&quot;}"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"><span class="HeaderSearch-module__content__kMpxU"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search HeaderSearch-module__icon__wcrHX" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg><span class="HeaderSearch-module__label__d1iWG">Search</span><kbd class="HeaderSearch-module__kbd__HNG0o" aria-hidden="true">/</kbd></span></span></span></button><div class="d-none"></div></div><div class="AuthCTAs-module__signInWrap__q2P60"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq AuthCTAs-module__desktopActionGap__UZuXT AuthCTAs-module__hiddenBelowLg__BfKBw" href="/login?return_to=https%3A%2F%2Fgithub.com%2Flobotomoe%2Frevenant%2Fblob%2Fmain%2FCHANGELOG.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/lobotomoe/revenant/blob/main/CHANGELOG.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="f85466e8f58fff0d99adb53c78826bce1e6b4c4e8e31ac9fa0dd9f65c09a2e37"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a></div><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--secondary___gHnw_ Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/signup?ref_cta=Sign+up&amp;ref_loc=header+logged+out&amp;ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow&amp;source=header-repo&amp;source_repo=lobotomoe%2Frevenant" data-analytics-event="{&quot;action&quot;:&quot;sign_up&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_up_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/lobotomoe/revenant/blob/main/CHANGELOG.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="f85466e8f58fff0d99adb53c78826bce1e6b4c4e8e31ac9fa0dd9f65c09a2e37"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-secondary___eJ0_a">Sign up</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_fbd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_fbd_">Appearance settings</div></div></div></div></div><div class="MarketingHeader-module__bottomBorder__uZT38" aria-hidden="true"></div></header></div></div>
</react-partial>



      <div hidden="hidden" data-view-component="true" class="js-stale-session-flash stale-session-flash flash flash-warn flash-full">
  
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
        <span class="js-stale-session-flash-signed-in" hidden>You signed in with another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-signed-out" hidden>You signed out in another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-switched" hidden>You switched accounts on another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>

    <button id="icon-button-82addff3-599c-4425-aa4b-3063361bf79e" aria-labelledby="tooltip-a774a61a-5ef8-4f66-ad69-11be8617dcf2" type="button" data-view-component="true" class="Button Button--iconOnly Button--invisible Button--medium flash-close js-flash-close">  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x Button-visual">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button><tool-tip id="tooltip-a774a61a-5ef8-4f66-ad69-11be8617dcf2" for="icon-button-82addff3-599c-4425-aa4b-3063361bf79e" popover="manual" data-direction="s" data-type="label" data-view-component="true" class="sr-only position-absolute">Dismiss alert</tool-tip>


  
</div>
    </div>

  <div id="start-of-content" class="show-on-focus"></div>








    <div id="js-flash-container" class="flash-container" data-turbo-replace>






  <template class="js-flash-template">
    
<div class="flash flash-full   {{ className }}">
  <div >
    <button autofocus class="flash-close js-flash-close" type="button" aria-label="Dismiss this message">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    <div aria-atomic="true" role="alert" class="js-flash-alert">
      
      <div>{{ message }}</div>

    </div>
  </div>
</div>
  </template>
</div>


    






  <div
    class="application-main "
    data-commit-hovercards-enabled
    data-discussion-hovercards-enabled
    data-issue-and-pr-hovercards-enabled
    data-project-hovercards-enabled
  >
        <div itemscope itemtype="http://schema.org/SoftwareSourceCode" class="">
    <main id="js-repo-pjax-container" >
      
      








  

    <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ym-6bc2c9bf8112c038.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sk2-8deeef9351be3911.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ni-3e194d222c7770bf.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7x-71d30cb240cd901f.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wwk-ad869086822937b1.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a1-9f79841ecac1da8e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wzg-40a3213b3e83400b.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/5f-64f8e7d5bcbd4787.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/z5-b53a93078d026f1e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/8h-cef26f68c1331011.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fq-3ce13a77e863807a.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a7-596b507bd20acddc.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/xfx-c82d8352413aea0a.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j4-b3a9e87fa7878a98.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/global-nav-bar-29c7aef68ee9db41.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/pj.75627bfeb9ceb1da.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-nav-bar.2f2560cba06ab359.module.css" />

<react-partial
  partial-name="global-nav-bar"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"contextRegion":{"crumbs":[{"crumb_type":"user","label":"lobotomoe","is_root":false,"href":"/lobotomoe"},{"crumb_type":"repository","label":"revenant","is_root":false,"href":"/lobotomoe/revenant"}],"localNavigation":[{"id":"code","icon":"code","label":"Code","href":"/lobotomoe/revenant","selectedLinks":["repo_source","repo_downloads","repo_commits","repo_releases","repo_tags","repo_branches","repo_packages","repo_deployments","repo_attestations"],"popoverTarget":false,"commandId":"repositories:go-to-code","reactNav":{"appTarget":"code-view","anchor":"code-view-repo-link"},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"issues","icon":"issue-opened","label":"Issues","href":"/lobotomoe/revenant/issues","selectedLinks":["repo_issues","repo_labels","repo_milestones"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-issues","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"pull-requests","icon":"git-pull-request","label":"Pull requests","href":"/lobotomoe/revenant/pulls","selectedLinks":["repo_pulls","checks"],"count":8,"popoverTarget":false,"commandId":"repositories:go-to-pull-requests","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"actions","icon":"play","label":"Actions","href":"/lobotomoe/revenant/actions","selectedLinks":["repo_actions"],"popoverTarget":false,"commandId":"repositories:go-to-actions","reactNav":{"appTarget":"actions-workflows","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"projects","icon":"table","label":"Projects","href":"/lobotomoe/revenant/projects","selectedLinks":["repo_projects","new_repo_project","repo_project"],"popoverTarget":false,"commandId":"repositories:go-to-projects","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"security-and-quality","icon":"shield","label":"Security and quality","href":"/lobotomoe/revenant/security","selectedLinks":["security","overview","alerts","policy","token_scanning","code_scanning"],"count":25,"popoverTarget":false,"commandId":"repositories:go-to-security","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"insights","icon":"graph","label":"Insights","href":"/lobotomoe/revenant/pulse","selectedLinks":["repo_graphs","repo_contributors","dependency_graph","dependabot_updates","pulse","people","community"],"popoverTarget":false,"commandId":"repositories:go-to-insights","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}}],"localNavigationUpdateChannel":null,"selectedLink":"repo_source"},"owner":null,"headerLogo":{"href":"/","aria-label":"Homepage "},"notifications":{"indicatorMode":"disabled","websocketChannel":null,"fetchIndicatorSrc":"/notifications/indicator","fetchIndicatorEnabled":false},"issues":{"href":"/issues"},"pulls":{"href":"/pulls"},"contributedRepos":{"href":"/repos"},"copilot":{"show":false,"showAgentsButton":false,"showRelaunchAnnouncement":false,"copilotChatUrl":null,"copilotApiUrl":"https://api.githubcopilot.com"},"search":{"show":true,"showCommandPalette":false,"isSearchPage":false,"isJumpToSearch":false,"searchContext":{"scope":"repo:lobotomoe/revenant","current_repo_name":"revenant","current_repo_nwo":"lobotomoe/revenant","user_id":"lobotomoe"}},"commandPalette":null,"enterpriseBar":{"show":false},"globalTransactionalMessage":[],"payloadsUrl":"/_global-navigation/payloads.json?can_toggle_site_admin_and_employee_status=0\u0026is_admin_mode_on=0\u0026is_ui_opted_out=0\u0026show_ui_opt_out=0\u0026v=7","contextRegionUrl":"/_global-navigation/context-region.json"}}</script>
  <div data-target="react-partial.reactRoot"><header aria-label="Global navigation menu" data-component="Stack" class="GlobalNav styles-module__appHeader__YzYWk prc-Stack-Stack-UQ9k6" data-gap="none" data-direction="vertical" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="none"><div data-component="Stack" class="prc-Stack-Stack-UQ9k6" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="center" data-padding="none"><div data-testid="top-nav-center" data-component="Stack" class="styles-module__center__R3QRv styles-module__withLocalNavigation__rjTJ_ GlobalNavBar-module__loggedOut__Jv1rk prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="normal"><nav class="styles-module__contextRegion__VbSp2 prc-Breadcrumbs-BreadcrumbsBase-3Gb-B" aria-label="Breadcrumbs" data-overflow="menu" data-variant="normal" data-component="Breadcrumbs"><ol class="prc-Breadcrumbs-BreadcrumbsList-BKjpe"><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/lobotomoe" data-discover="true"><span class="">lobotomoe</span></a></li><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/lobotomoe/revenant" data-discover="true"><span class="styles-module__contextCrumbLast__tI2e3">revenant</span></a></li></ol></nav></div><div data-testid="top-nav-right" data-component="Stack" class="styles-module__right__mlBQg styles-module__withLocalNavigation__rjTJ_ styles-module__rightWithResponsiveCreateButton__SKn2W prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="start" data-padding="normal"></div></div><h2 class="prc-src-InternalVisuallyHidden-2YaI6">Repository navigation</h2><nav class="prc-components-UnderlineWrapper-eT-Yj prc-UnderlineNav-UnderlineWrapper-GWONT LocalNavigation-module__LocalNavigation__b0Xc0" aria-label="Repository" data-variant="inset" data-overflow-mode="wrap" data-hide-icons-breakpoint="medium"><ul class="prc-UnderlineNav-ItemsList-oj8gN prc-components-UnderlineItemList-xKlKC" role="list"><li role="presentation" aria-hidden="true" class="prc-UnderlineNav-WrapSpacer--aLgz"></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a aria-current="page" data-tab-item="code" data-react-nav="code-view" data-react-nav-anchor="code-view-repo-link" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/lobotomoe/revenant" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg></span><span data-component="text" data-content="Code">Code</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="issues" data-react-nav="repo" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/lobotomoe/revenant/issues" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-issue-opened" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path></svg></span><span data-component="text" data-content="Issues">Issues</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="pull-requests" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/lobotomoe/revenant/pulls" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-pull-request" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"></path></svg></span><span data-component="text" data-content="Pull requests">Pull requests</span><span data-component="counter"><span aria-hidden="true" data-variant="secondary" data-component="CounterLabel" class="prc-CounterLabel-CounterLabel-X-kRU">8</span><span class="prc-VisuallyHidden-VisuallyHidden-Q0qSB"> (<!-- -->8<!-- -->)</span></span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="actions" data-react-nav="actions-workflows" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/lobotomoe/revenant/actions" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-play" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"></path></svg></span><span data-component="text" data-content="Actions">Actions</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="projects" data-react-nav="repo" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/lobotomoe/revenant/projects" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-table" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25ZM6.5 6.5v8h7.75a.25.25 0 0 0 .25-.25V6.5Zm8-1.5V1.75a.25.25 0 0 0-.25-.25H6.5V5Zm-13 1.5v7.75c0 .138.112.25.25.25H5v-8ZM5 5V1.5H1.75a.25.25 0 0 0-.25.25V5Z"></path></svg></span><span data-component="text" data-content="Projects">Projects</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="security-and-quality" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/lobotomoe/revenant/security" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.467.133a1.748 1.748 0 0 1 1.066 0l5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667Zm.61 1.429a.25.25 0 0 0-.153 0l-5.25 1.68a.25.25 0 0 0-.174.238V7c0 1.358.275 2.666 1.057 3.86.784 1.194 2.121 2.34 4.366 3.297a.196.196 0 0 0 .154 0c2.245-.956 3.582-2.104 4.366-3.298C13.225 9.666 13.5 8.36 13.5 7V3.48a.251.251 0 0 0-.174-.237l-5.25-1.68ZM8.75 4.75v3a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 1.5 0ZM9 10.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg></span><span data-component="text" data-content="Security and quality">Security and quality</span><span data-component="counter"><span aria-hidden="true" data-variant="secondary" data-component="CounterLabel" class="prc-CounterLabel-CounterLabel-X-kRU">25</span><span class="prc-VisuallyHidden-VisuallyHidden-Q0qSB"> (<!-- -->25<!-- -->)</span></span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="insights" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/lobotomoe/revenant/pulse" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-graph" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 1.75V13.5h13.75a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75V1.75a.75.75 0 0 1 1.5 0Zm14.28 2.53-5.25 5.25a.75.75 0 0 1-1.06 0L7 7.06 4.28 9.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.25-3.25a.75.75 0 0 1 1.06 0L10 7.94l4.72-4.72a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg></span><span data-component="text" data-content="Insights">Insights</span></a></li></ul><div class="prc-UnderlineNav-MoreButtonContainer-Dnrq6"><div class="prc-UnderlineNav-MoreButtonDivider-dN0a-"></div><button data-component="overflow-menu-button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk prc-UnderlineNav-MoreButton-Y8soj" data-loading="false" data-size="medium" data-variant="invisible" id="_R_2ktp_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3"><span>More<span class="prc-src-InternalVisuallyHidden-2YaI6"> items</span></span></span></span><span data-component="trailingAction" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></button></div></nav><div class="d-none"></div></header></div>
</react-partial>


  



<turbo-frame id="repo-content-turbo-frame" target="_top" data-turbo-action="advance" class="">
    <div id="repo-content-pjax-container" class="repository-content " >
    



    
      
    








<react-app
  app-name="code-view"
  initial-path="/lobotomoe/revenant/blob/main/CHANGELOG.md"
  style="display: block; min-height: calc(100vh - 64px);"
  data-attempted-ssr="true"
  data-ssr="true"
  data-lazy="false"
  data-alternate="false"
  data-data-router-enabled="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-app.embeddedData">{"payload":{"codeViewBlobRoute":{"csv":null,"csvError":null,"headerInfo":{"toc":[{"level":1,"text":"Changelog","anchor":"changelog","htmlText":"Changelog"},{"level":2,"text":"Unreleased","anchor":"unreleased","htmlText":"Unreleased"},{"level":2,"text":"4.0.0 - 2026-09-03","anchor":"400---2026-09-03","htmlText":"4.0.0 - 2026-09-03"},{"level":3,"text":"Fixed","anchor":"fixed","htmlText":"Fixed"},{"level":3,"text":"Security","anchor":"security","htmlText":"Security"},{"level":3,"text":"Changed","anchor":"changed","htmlText":"Changed"},{"level":3,"text":"Removed","anchor":"removed","htmlText":"Removed"},{"level":2,"text":"3.0.5 - 2026-08-18","anchor":"305---2026-08-18","htmlText":"3.0.5 - 2026-08-18"},{"level":3,"text":"Fixed","anchor":"fixed-1","htmlText":"Fixed"},{"level":3,"text":"Security","anchor":"security-1","htmlText":"Security"},{"level":2,"text":"3.0.4 - 2026-08-18","anchor":"304---2026-08-18","htmlText":"3.0.4 - 2026-08-18"},{"level":3,"text":"Fixed","anchor":"fixed-2","htmlText":"Fixed"},{"level":2,"text":"3.0.3 - 2026-08-17","anchor":"303---2026-08-17","htmlText":"3.0.3 - 2026-08-17"},{"level":3,"text":"Fixed","anchor":"fixed-3","htmlText":"Fixed"},{"level":2,"text":"3.0.2 - 2026-08-16","anchor":"302---2026-08-16","htmlText":"3.0.2 - 2026-08-16"},{"level":3,"text":"Fixed","anchor":"fixed-4","htmlText":"Fixed"},{"level":2,"text":"3.0.1 - 2026-08-16","anchor":"301---2026-08-16","htmlText":"3.0.1 - 2026-08-16"},{"level":3,"text":"Fixed","anchor":"fixed-5","htmlText":"Fixed"},{"level":2,"text":"3.0.0 - 2026-08-12","anchor":"300---2026-08-12","htmlText":"3.0.0 - 2026-08-12"},{"level":3,"text":"Removed","anchor":"removed-1","htmlText":"Removed"},{"level":3,"text":"Fixed","anchor":"fixed-6","htmlText":"Fixed"},{"level":3,"text":"Changed","anchor":"changed-1","htmlText":"Changed"},{"level":3,"text":"Added","anchor":"added","htmlText":"Added"},{"level":2,"text":"2.1.2 - 2026-08-03","anchor":"212---2026-08-03","htmlText":"2.1.2 - 2026-08-03"},{"level":3,"text":"Fixed","anchor":"fixed-7","htmlText":"Fixed"},{"level":3,"text":"Changed","anchor":"changed-2","htmlText":"Changed"},{"level":2,"text":"2.1.1 - 2026-07-20","anchor":"211---2026-07-20","htmlText":"2.1.1 - 2026-07-20"},{"level":3,"text":"Fixed","anchor":"fixed-8","htmlText":"Fixed"},{"level":2,"text":"2.1.0 - 2026-07-20","anchor":"210---2026-07-20","htmlText":"2.1.0 - 2026-07-20"},{"level":3,"text":"Added","anchor":"added-1","htmlText":"Added"},{"level":3,"text":"Changed","anchor":"changed-3","htmlText":"Changed"},{"level":3,"text":"Fixed","anchor":"fixed-9","htmlText":"Fixed"},{"level":2,"text":"2.0.0 - 2026-05-16","anchor":"200---2026-05-16","htmlText":"2.0.0 - 2026-05-16"},{"level":3,"text":"TypeScript SDK only — Python client is unchanged","anchor":"typescript-sdk-only--python-client-is-unchanged","htmlText":"TypeScript SDK only — Python client is unchanged"},{"level":3,"text":"Breaking","anchor":"breaking","htmlText":"Breaking"},{"level":3,"text":"Added","anchor":"added-2","htmlText":"Added"},{"level":2,"text":"1.2.1 - 2026-04-16","anchor":"121---2026-04-16","htmlText":"1.2.1 - 2026-04-16"},{"level":3,"text":"Fixed","anchor":"fixed-10","htmlText":"Fixed"},{"level":2,"text":"1.2.0 - 2026-04-14","anchor":"120---2026-04-14","htmlText":"1.2.0 - 2026-04-14"},{"level":3,"text":"Added","anchor":"added-3","htmlText":"Added"},{"level":3,"text":"Improved","anchor":"improved","htmlText":"Improved"},{"level":3,"text":"Fixed","anchor":"fixed-11","htmlText":"Fixed"},{"level":2,"text":"1.1.1 - 2026-04-10","anchor":"111---2026-04-10","htmlText":"1.1.1 - 2026-04-10"},{"level":3,"text":"Improved","anchor":"improved-1","htmlText":"Improved"},{"level":2,"text":"1.1.0 - 2026-04-09","anchor":"110---2026-04-09","htmlText":"1.1.0 - 2026-04-09"},{"level":3,"text":"Added","anchor":"added-4","htmlText":"Added"},{"level":3,"text":"Changed","anchor":"changed-4","htmlText":"Changed"},{"level":3,"text":"Fixed","anchor":"fixed-12","htmlText":"Fixed"},{"level":2,"text":"1.0.0 - 2026-02-26","anchor":"100---2026-02-26","htmlText":"1.0.0 - 2026-02-26"},{"level":3,"text":"Added","anchor":"added-5","htmlText":"Added"},{"level":3,"text":"Changed","anchor":"changed-5","htmlText":"Changed"},{"level":3,"text":"Fixed","anchor":"fixed-13","htmlText":"Fixed"},{"level":2,"text":"0.2.6 - 2026-02-26","anchor":"026---2026-02-26","htmlText":"0.2.6 - 2026-02-26"},{"level":3,"text":"Changed","anchor":"changed-6","htmlText":"Changed"},{"level":2,"text":"0.2.5 - 2026-02-26","anchor":"025---2026-02-26","htmlText":"0.2.5 - 2026-02-26"},{"level":3,"text":"Added","anchor":"added-6","htmlText":"Added"},{"level":3,"text":"Changed","anchor":"changed-7","htmlText":"Changed"},{"level":3,"text":"Fixed","anchor":"fixed-14","htmlText":"Fixed"},{"level":2,"text":"0.2.4 - 2026-02-21","anchor":"024---2026-02-21","htmlText":"0.2.4 - 2026-02-21"},{"level":3,"text":"Fixed","anchor":"fixed-15","htmlText":"Fixed"},{"level":2,"text":"0.2.3 - 2026-02-21","anchor":"023---2026-02-21","htmlText":"0.2.3 - 2026-02-21"},{"level":3,"text":"Fixed","anchor":"fixed-16","htmlText":"Fixed"},{"level":2,"text":"0.2.2 - 2026-02-21","anchor":"022---2026-02-21","htmlText":"0.2.2 - 2026-02-21"},{"level":3,"text":"Fixed","anchor":"fixed-17","htmlText":"Fixed"},{"level":2,"text":"0.2.1 - 2026-02-20","anchor":"021---2026-02-20","htmlText":"0.2.1 - 2026-02-20"},{"level":3,"text":"Added","anchor":"added-7","htmlText":"Added"},{"level":2,"text":"0.2.0 - 2026-02-16","anchor":"020---2026-02-16","htmlText":"0.2.0 - 2026-02-16"},{"level":3,"text":"Changed","anchor":"changed-8","htmlText":"Changed"},{"level":3,"text":"Fixed","anchor":"fixed-18","htmlText":"Fixed"},{"level":2,"text":"0.1.0 - 2026-02-14","anchor":"010---2026-02-14","htmlText":"0.1.0 - 2026-02-14"},{"level":3,"text":"Added","anchor":"added-8","htmlText":"Added"}]},"issueTemplate":null,"discussionTemplate":null,"richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChangelog\u003c/h1\u003e\u003ca id=\"user-content-changelog\" class=\"anchor\" aria-label=\"Permalink: Changelog\" href=\"#changelog\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAll notable changes to this project will be documented in this file.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe format is based on \u003ca href=\"https://keepachangelog.com/en/1.1.0/\" rel=\"nofollow\"\u003eKeep a Changelog\u003c/a\u003e,\nand this project adheres to \u003ca href=\"https://semver.org/spec/v2.0.0.html\" rel=\"nofollow\"\u003eSemantic Versioning\u003c/a\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v4.0.0...HEAD\"\u003eUnreleased\u003c/a\u003e\u003c/h2\u003e\u003ca id=\"user-content-unreleased\" class=\"anchor\" aria-label=\"Permalink: Unreleased\" href=\"#unreleased\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v3.0.5...v4.0.0\"\u003e4.0.0\u003c/a\u003e - 2026-09-03\u003c/h2\u003e\u003ca id=\"user-content-400---2026-09-03\" class=\"anchor\" aria-label=\"Permalink: 4.0.0 - 2026-09-03\" href=\"#400---2026-09-03\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eTwo fixes -- logging in to a non-EKENG server, and the app icon on macOS -- plus\na dependency refresh that clears a withdrawn package and two advisories.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe major bump is required by one breaking change in the Rust crates:\n\u003ccode\u003eServerProfile\u003c/code\u003e in \u003ccode\u003erevenant-sign-core\u003c/code\u003e gains a public field, and the struct is\nnow \u003ccode\u003e#[non_exhaustive]\u003c/code\u003e. The Python and TypeScript packages keep their public\nsurface, and nothing changes for users of the desktop app or the CLIs.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eCustom servers now accept non-ASCII credentials.\u003c/strong\u003e Both GUIs refused a\nusername or password containing any non-Latin character, on every profile.\nThe restriction describes EKENG, whose logins are Latin letters and digits,\nbut it was applied to custom CoSign deployments too -- blocking credentials\nthe SOAP envelope escapes and carries as UTF-8 without trouble, and that both\nCLIs already accepted. It is now a profile capability, off for custom servers\nand on for EKENG, where a non-ASCII entry is a keyboard-layout slip and\nrefusing it spends none of that profile's five-attempt lockout budget.\nReported by peyuaa (#76).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eThe macOS Dock no longer shows a black egui logo in place of the app icon.\u003c/strong\u003e\nThe desktop app deliberately left its runtime icon unset on macOS, so that the\nDock tile would stay with the bundle's \u003ccode\u003e.icns\u003c/code\u003e. eframe reads an unset icon the\nother way round: it substitutes its own default egui artwork and pushes that\nthrough \u003ccode\u003esetApplicationIconImage\u003c/code\u003e, taking over the tile for as long as the app\nruns. It now passes an empty \u003ccode\u003eIconData\u003c/code\u003e -- egui's documented opt-out -- so the\ninstalled icon survives launch.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSecurity\u003c/h3\u003e\u003ca id=\"user-content-security\" class=\"anchor\" aria-label=\"Permalink: Security\" href=\"#security\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eThe desktop app no longer ships a browser-launching flaw.\u003c/strong\u003e \u003ccode\u003ewebbrowser\u003c/code\u003e,\nwhich the GUI stack uses to open links, honoured the Unix \u003ccode\u003eBROWSER\u003c/code\u003e\nenvironment variable in a way that allowed argument injection\n(RUSTSEC-2026-0257). Updated to 1.2.4.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust dependencies are now audited in CI.\u003c/strong\u003e \u003ccode\u003ecargo audit\u003c/code\u003e runs beside\n\u003ccode\u003epip-audit\u003c/code\u003e in the required Security Audit job, so a known-vulnerable crate\nfails the build instead of waiting for a weekly Scorecard alert nobody is\nblocked on -- which is how the \u003ccode\u003ewebbrowser\u003c/code\u003e advisory above went unnoticed.\nOne finding is ignored with a recorded reason and a re-evaluation trigger: the\n\u003ccode\u003ersa\u003c/code\u003e Marvin timing sidechannel, which has no released fix.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eThe Rust lockfile is refreshed to the latest compatible releases.\u003c/strong\u003e This\ntakes \u003ccode\u003echacha20\u003c/code\u003e off a yanked 0.10.1, \u003ccode\u003eevent-listener\u003c/code\u003e off the unsound 5.4.1\n(RUSTSEC-2026-0221), and \u003ccode\u003ewayland-scanner\u003c/code\u003e onto a \u003ccode\u003equick-xml\u003c/code\u003e 0.41 -- which\nremoves the last vulnerable copy of that crate from the tree and lets the two\n\u003ccode\u003equick-xml\u003c/code\u003e audit ignores go, arming that guard again.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ccode\u003eServerProfile\u003c/code\u003e in \u003ccode\u003erevenant-sign-core\u003c/code\u003e is now \u003ccode\u003e#[non_exhaustive]\u003c/code\u003e\u003c/strong\u003e and\ngains a public \u003ccode\u003eascii_credentials_only\u003c/code\u003e field. Profiles are meant to come from\n\u003ccode\u003eServerProfile::builtin\u003c/code\u003e / \u003ccode\u003e::custom_default\u003c/code\u003e, which fill in every capability\nthe deployment implies; code that constructed the struct literally must move\nto those constructors. Marking it non-exhaustive is itself the breaking part,\nand it is what keeps the next field addition off the major-version budget.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eRemoved\u003c/h3\u003e\u003ca id=\"user-content-removed\" class=\"anchor\" aria-label=\"Permalink: Removed\" href=\"#removed\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ccode\u003epython/scripts/merge_universal.py\u003c/code\u003e.\u003c/strong\u003e The release workflow has lipo'd the\nmacOS universal binaries directly since the Rust pipeline landed, leaving the\nscript unreachable from every workflow and build script. Its fallback read a\n\u003ccode\u003elipo -create\u003c/code\u003e \"same architectures\" failure as proof that both inputs held\nthe same architecture set and copied the arm64 side, which for partially\noverlapping inputs would have silently dropped the x86_64 slice. The live\npath has no such fallback. Reported by peyuaa (#75).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v3.0.4...v3.0.5\"\u003e3.0.5\u003c/a\u003e - 2026-08-18\u003c/h2\u003e\u003ca id=\"user-content-305---2026-08-18\" class=\"anchor\" aria-label=\"Permalink: 3.0.5 - 2026-08-18\" href=\"#305---2026-08-18\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSecurity release. Three fixes for background results being applied to state\nthat did not ask for them, one for the output path a signed file is written to,\nand a hardened release pipeline.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-1\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-1\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eA cancelled connection can no longer become the active server.\u003c/strong\u003e Dismissing\nthe Connect dialog did not stop the server test already running, and neither\nGUI checked, when the answer arrived, whether it was still wanted. A slow\nendpoint could return success after the user backed out and be saved as the\nactive profile, so later credentials and signing requests went somewhere the\nuser had declined. Both GUIs now tag each attempt and drop a result that no\nlonger belongs to the dialog on screen. Reported by peyuaa; found during that\nfix to affect the Python GUI as well.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA saved password can no longer be pre-filled into a different login.\u003c/strong\u003e The\nRust GUI read the saved password in the background and applied whatever came\nback to whichever login was open, so cancelling one login and starting another\ncould put the first account's password into the second. The read is now bound\nto the login and username that asked for it, and a password box the user has\ntyped in and cleared is never silently re-filled. The same fix covers identity\ndiscovery, which could otherwise save one login's signer certificate under\nanother's. Reported by peyuaa.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSigning no longer writes through a symlink at the output path.\u003c/strong\u003e When a\nsandbox forbids creating a temporary file, both the Python and Rust clients\nfall back to writing the output directly. That fallback followed a symlink\nleft at a predictable output name (\u003ccode\u003e\u0026lt;stem\u0026gt;_signed.pdf\u003c/code\u003e) and truncated whatever\nit pointed at, where the normal path replaces the link instead. The fallback\nnow refuses to follow links, refuses anything that is not a regular file, and\nfails outright on platforms that cannot express either. Reported by peyuaa\nagainst the Python client; the Rust CLI carried the same flaw.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eSecurity\u003c/h3\u003e\u003ca id=\"user-content-security-1\" class=\"anchor\" aria-label=\"Permalink: Security\" href=\"#security-1\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eThe AppImage packaging tool is now pinned and verified.\u003c/strong\u003e The release job\nfetched \u003ccode\u003eappimagetool\u003c/code\u003e from a mutable tag over plain HTTP(S) and marked it\nexecutable without checking it, then used it to pack both published Linux\nAppImages -- so whatever that URL served on release day ran inside the release\njob and shipped to users. It is now pinned to a tagged release and checked\nagainst a known digest before it is made executable. Reported by peyuaa.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v3.0.3...v3.0.4\"\u003e3.0.4\u003c/a\u003e - 2026-08-18\u003c/h2\u003e\u003ca id=\"user-content-304---2026-08-18\" class=\"anchor\" aria-label=\"Permalink: 3.0.4 - 2026-08-18\" href=\"#304---2026-08-18\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSecurity release. Three fixes in what the desktop apps report about a\nsignature. The command-line tools were not affected and their verdicts are\nunchanged.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-2\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-2\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eA tampered PDF is no longer shown as a valid signature.\u003c/strong\u003e The Rust GUI took\nits green verdict from the CMS signature alone. Altering bytes the ByteRange\ncovers while leaving the CMS object intact keeps that signature verifiable, so\na modified document rendered \u003ccode\u003eSignature VALID\u003c/code\u003e in green with a red integrity\nfailure beneath it, and named the embedded certificate as its signer. The\nverdict is now the full result -- integrity and signature -- which is what the\ncore library already meant by valid, and what the CLI has always reported.\nReported by peyuaa.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA failed signature no longer carries a trusted signer.\u003c/strong\u003e The Python GUI\nprinted \u003ccode\u003eSignature: Integrity check FAILED\u003c/code\u003e directly above a green\n\u003ccode\u003eTrust: Trusted (CA)\u003c/code\u003e, because the trust line read only the chain result. The\nchain may genuinely be trusted; that is a fact about the certificate and not\nabout a document the signature does not cover. Signer, organization and trust\nare now reported only for a signature that verified, on both the embedded and\ndetached paths. Reported by peyuaa.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA certificate that is not yet valid is no longer styled as normal.\u003c/strong\u003e The\nPython GUI classified certificates by \u003ccode\u003enotAfter\u003c/code\u003e only, so one whose validity\nperiod had not started showed the ordinary presentation and a reassuring\ncount of days remaining. It is now flagged like an expired certificate, since\nneither can be used. Reported by peyuaa.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v3.0.2...v3.0.3\"\u003e3.0.3\u003c/a\u003e - 2026-08-17\u003c/h2\u003e\u003ca id=\"user-content-303---2026-08-17\" class=\"anchor\" aria-label=\"Permalink: 3.0.3 - 2026-08-17\" href=\"#303---2026-08-17\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSecurity release completing a fix that 3.0.2 landed in only one of the three\nclients. Users of the Python package or the Rust crates should upgrade.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-3\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-3\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eThe BER size bound now covers Python and Rust as well.\u003c/strong\u003e 3.0.2 fixed this\nin the TypeScript client because the report named only the npm package, but\nthe flaw was identical in the other two: the 16 MiB ceiling sat in the\ndefinite-length branch, past the point where an indefinite-length header\nreturns, and the \u003ccode\u003e/Contents\u003c/code\u003e gap was decoded to a string with no bound at all.\nA crafted \u003ccode\u003e/ByteRange\u003c/code\u003e therefore allocated the whole gap before anything\nrejected it. Both clients now refuse an oversized gap and an oversized input\nup front, against the same shared ceiling, matching TypeScript. Originally\nreported by peyuaa.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v3.0.1...v3.0.2\"\u003e3.0.2\u003c/a\u003e - 2026-08-16\u003c/h2\u003e\u003ca id=\"user-content-302---2026-08-16\" class=\"anchor\" aria-label=\"Permalink: 3.0.2 - 2026-08-16\" href=\"#302---2026-08-16\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSecurity release. Three reported vulnerabilities in signature verification,\nplus a batch-signing fix in the desktop app. Upgrading is recommended.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-4\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-4\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eCertificate order in a CMS no longer confers trust.\u003c/strong\u003e Chain validation began\nfrom the first embedded certificate and signer identity was read from the same\none, while the signature itself was verified against the certificate named by\n\u003ccode\u003eSignerInfo.sid\u003c/code\u003e. A CMS certificate set is a \u003ccode\u003eSET OF\u003c/code\u003e, so those need not be the\nsame certificate: signing detached content with an untrusted self-signed\ncertificate and listing a trusted root ahead of it made Revenant verify the\nattacker's signature, walk the chain from the root, report \u003ccode\u003eVALID and trusted\u003c/code\u003e,\nand display the root as the signer. All three now resolve the signer through\nthe \u003ccode\u003eSignerInfo\u003c/code\u003e, and only when exactly one embedded certificate matches --\nan ambiguous set has not named a signer. Rust's \u003ccode\u003ebuild_chain\u003c/code\u003e takes the leaf as\na parameter instead of reading pool index 0. Reported by peyuaa.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLTV status is no longer raised by data anyone can append.\u003c/strong\u003e The status came\nfrom the presence of a revocation-related OID and counted the signer's\n\u003cem\u003eunsigned\u003c/em\u003e attributes alongside the signed ones. Unsigned attributes are not\ncovered by the signature, so an Adobe \u003ccode\u003eRevocationInfoArchival\u003c/code\u003e attribute\nholding the string \u003ccode\u003ebogus-not-ocsp-or-crl\u003c/code\u003e could be stapled onto a finished\ndocument and reported as LTV enabled with \u003ccode\u003ehas_ocsp\u003c/code\u003e set. The \u003ccode\u003eSignedData.crls\u003c/code\u003e\nfield is outside the signature for the same reason. Both are still reported,\nnow marked as present and not counted, and attribute values are decoded rather\nthan taken on the OID's word. This reports what a signature carries, not that\nthe revocation data was validated -- that check does not exist yet, and the\ndocumentation says so. Python and TypeScript gain\n\u003ccode\u003ehas_unauthenticated_material\u003c/code\u003e; Rust reports the same distinction as a\n\u003ccode\u003eRevocationMaterial\u003c/code\u003e enum. Rust was affected too, though the report named only\nthe Python and npm packages. Reported by peyuaa.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eA BER indefinite-length CMS is bounded before it is decoded.\u003c/strong\u003e The 16 MiB\nceiling was checked against the length a definite-length header claims. An\nindefinite-length blob claims nothing, so it was decoded whole and its extent\ndiscovered only on reaching the end marker -- a crafted PDF allocated its\nentire \u003ccode\u003e/Contents\u003c/code\u003e gap regardless of the limit. Both the extractor and the PDF\npath now refuse an oversized gap up front, against the same exported ceiling.\nFixed here in the TypeScript client only, which is what the report named; the\nPython and Rust clients carried the same flaw and are fixed in 3.0.3.\nReported by peyuaa.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBatch signing no longer overwrites files it did not create.\u003c/strong\u003e Every\ndestination is reserved before the first signing request and written with\nexclusive creation, so a queued input can no longer be replaced by an earlier\nfile's output, and same-named inputs from different folders no longer collide.\nOutputs that could not take their derived name are now reported, labelled by\nfull input path. Contributed by peyuaa.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v3.0.0...v3.0.1\"\u003e3.0.1\u003c/a\u003e - 2026-08-16\u003c/h2\u003e\u003ca id=\"user-content-301---2026-08-16\" class=\"anchor\" aria-label=\"Permalink: 3.0.1 - 2026-08-16\" href=\"#301---2026-08-16\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSecurity release. Fixes one vulnerability introduced in 3.0.0; upgrading is\nrecommended for everyone running that version.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-5\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-5\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eA CMS signature without signed attributes no longer vouches for content it\nnever covered.\u003c/strong\u003e RFC 5652 section 5.4 puts the signature over the content\nitself when \u003ccode\u003esignedAttrs\u003c/code\u003e is absent, and 3.0.0 added support for that shape.\nIt resolved the content in the wrong order: an \u003ccode\u003eencapContentInfo.eContent\u003c/code\u003e\nembedded in the blob was preferred over the bytes the caller asked about, so a\ngenuine attached signature over attacker-chosen bytes verified successfully\nand the result was then reported as covering an unrelated PDF \u003ccode\u003eByteRange\u003c/code\u003e or\ndetached payload. Because no signed attributes means no \u003ccode\u003emessageDigest\u003c/code\u003e to\nre-check, that signature verdict was the entire integrity argument:\nverification returned \u003ccode\u003evalid\u003c/code\u003e, \u003ccode\u003ehash_ok\u003c/code\u003e and \u003ccode\u003esignature_valid\u003c/code\u003e all true for a\ndocument the signer never saw. The caller's bytes are now authoritative, and a\nCMS that also embeds its own content is rejected as unverifiable unless the\ntwo are byte-identical. Verifying an attached CMS on its own terms, by passing\nno detached content, still works. Signatures that carry signed attributes were\nnever affected -- their \u003ccode\u003emessageDigest\u003c/code\u003e was always compared against the\ncaller's bytes.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v2.1.2...v3.0.0\"\u003e3.0.0\u003c/a\u003e - 2026-08-12\u003c/h2\u003e\u003ca id=\"user-content-300---2026-08-12\" class=\"anchor\" aria-label=\"Permalink: 3.0.0 - 2026-08-12\" href=\"#300---2026-08-12\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eSecurity release. Every change below is a fix for a reported vulnerability;\nupgrading is recommended for all users.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe major bump is required by one breaking API change in the Rust crates:\n\u003ccode\u003epki::validate_chain\u003c/code\u003e no longer takes a \u003ccode\u003eTransport\u003c/code\u003e, and\n\u003ccode\u003econstants::MAX_AIA_FETCHES\u003c/code\u003e is gone. The Python and TypeScript packages keep\ntheir public surface -- \u003ccode\u003eMAX_AIA_FETCHES\u003c/code\u003e was never exported from either\nentry point -- but their verification behaviour changes, as described below.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eRemoved\u003c/h3\u003e\u003ca id=\"user-content-removed-1\" class=\"anchor\" aria-label=\"Permalink: Removed\" href=\"#removed-1\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eCertificate chains are no longer completed by fetching the Authority\nInformation Access URLs printed inside a certificate.\u003c/strong\u003e Those URLs travel\ninside the document being verified, so following them let whoever produced a\nfile choose hosts for the verifying machine to contact -- opening a document\nbecame a network callback that reports the reader's address and the moment\nthey opened it, and a certificate may carry any number of them. The per-chain\nlimit did not restrain this: it counted only fetches that \u003cem\u003esucceeded\u003c/em\u003e, so\nunreachable URLs were retried without bound, each costing a connection\nattempt with retries. Issuers now come from the CMS blob, which is where\nRFC 5652 has a signer put them, and from the configured trust store. A\nsignature whose CMS omits its own intermediates stops short of a trusted\nanchor and reads as \u003ccode\u003euntrusted\u003c/code\u003e -- or \u003ccode\u003eindeterminate\u003c/code\u003e, if an anchor still\nmatched by name but the chain could not be verified -- instead of silently\nreaching out for them. No built-in\nprofile is affected: EKENG's chain has no intermediates, and its certificates'\nAIA URLs are plain HTTP, which the transport has always refused.\n\u003ccode\u003eMAX_AIA_FETCHES\u003c/code\u003e is gone from all three implementations, and Rust's\n\u003ccode\u003evalidate_chain\u003c/code\u003e no longer takes a \u003ccode\u003eTransport\u003c/code\u003e -- chain validation now has no\nway to reach the network at all.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-6\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-6\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003ePython/TypeScript: CMS verification now checks the cryptographic signer\nsignature, not only the container structure and signed digest.\u003c/strong\u003e Unsupported,\nmalformed, or unverifiable signatures fail closed. Signer identity and\noptional trust-chain validation now select the certificate named by\n\u003ccode\u003eSignerInfo.sid\u003c/code\u003e instead of relying on certificate \u003ccode\u003eSET OF\u003c/code\u003e order, and an ESS\n\u003ccode\u003esigningCertificate\u003c/code\u003e/\u003ccode\u003esigningCertificateV2\u003c/code\u003e attribute binds that identity to\nthe exact embedded certificate. TypeScript verifies directly with that\nselected certificate instead of allowing PKIjs to resolve \u003ccode\u003esid\u003c/code\u003e again, and\nboth implementations expose signer metadata only when matching ESS or a\ntrusted certificate chain authenticates it.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePython: the signer public key is read from the certificate's\n\u003ccode\u003eSubjectPublicKeyInfo\u003c/code\u003e instead of parsing the whole certificate.\u003c/strong\u003e Production\nsigner certificates (EKENG/CoSign among them) encode DN attributes such as\n\u003ccode\u003eemailAddress\u003c/code\u003e as a \u003ccode\u003ePrintableString\u003c/code\u003e containing \u003ccode\u003e@\u003c/code\u003e, which is outside that\ntype's character set; a strict X.509 parser rejects the entire certificate\nover a field signature verification never reads, turning genuine signatures\ninto false negatives.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePython and TypeScript now agree on the CMS signature input.\u003c/strong\u003e Python hashed\nthe signed attributes re-encoded as canonical DER while TypeScript hashed only\nthe bytes as transmitted, so the same document could verify in one and fail in\nthe other. Both now try the as-transmitted encoding first and the canonical\nDER re-encoding second. The two encodings carry the same parsed attributes,\nwhich are validated independently before any signature is checked, so nothing\nis loosened. Rust remains canonical-DER only and still rejects a signature\nmade over a non-DER transmitted ordering.\u003c/li\u003e\n\u003cli\u003eFailures that cannot be attributed to a specific check now name the\nunderlying error instead of reporting an opaque \"CMS signature check failed\".\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSigning workflows no longer return a signature nobody verified.\u003c/strong\u003e The\nCMS/PKCS#7 bytes a signing service sends back are now proven to be a genuine\nsignature over exactly the bytes that were submitted, before any workflow\nreports success. A compromised or impersonated service could previously make\n\u003ccode\u003esign_data\u003c/code\u003e / \u003ccode\u003esign_pdf_detached\u003c/code\u003e succeed while returning something no\nconforming verifier would accept, and the embedded flow accepted a response\nthat merely hashed correctly. Failures raise a distinct\n\u003ccode\u003eSigningResponseError\u003c/code\u003e (\u003ccode\u003eRevenantError::SigningResponse\u003c/code\u003e in Rust): the request\nsucceeded at the protocol level, so retrying is pointless and nothing is\nsaved. The raw \u003ccode\u003eSigningTransport\u003c/code\u003e methods remain unverified by design -- they\nare the low-level primitive, and calling them directly is an explicit opt-out.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust: knowing the hash that was submitted no longer stands in for checking\nwhat came back.\u003c/strong\u003e Post-sign verification treated a matching \u003ccode\u003eexpected_hash\u003c/code\u003e as\nproof and skipped the CMS \u003ccode\u003emessageDigest\u003c/code\u003e comparison entirely, so a response\ncarrying no digest at all could be reported as intact. Both are now required,\nmatching Python and TypeScript.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust: \u003ccode\u003ehas_signed_attributes\u003c/code\u003e distinguishes \"the CMS says it has none\" from\n\"the CMS did not parse\".\u003c/strong\u003e It now returns \u003ccode\u003eOption\u0026lt;bool\u0026gt;\u003c/code\u003e; previously a\ncorrupt blob borrowed the diagnostic wording of a legitimate\nno-\u003ccode\u003esignedAttrs\u003c/code\u003e signature.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e\u003ccode\u003esign_hash\u003c/code\u003e now says what it actually does.\u003c/strong\u003e It signs the hash bytes\nthemselves. What a service does with a submitted digest is service-defined and\nobserved to vary: some sign it as a pre-computed digest, others hash it again\nand sign it as ordinary content -- and only the first kind yields a signature\nthat can be attached to the document the hash came from. The response is\nchecked to be a genuine signature, and a warning is logged naming both digests\nwhen it does not bind the one submitted. The operation is not failed over\nthis: the caller asked for those bytes to be signed, and they were. To sign a\ndocument, pass the document to \u003ccode\u003esign_data\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTypeScript: the signature image pixel budget is enforced before decoding,\nnot after.\u003c/strong\u003e \u003ccode\u003eloadSignatureImage\u003c/code\u003e decoded the whole file and only then\ncompared the pixel count against its limit, by which point the decoder had\nalready allocated the full output: a 547 KB PNG declaring 12000x12000 drove\nresident memory to 1.7 GB before the check ran, and the 5 MB file cap allows\nan order of magnitude more. Dimensions are now read from the PNG \u003ccode\u003eIHDR\u003c/code\u003e chunk\nand the JPEG frame header, and refused there; \u003ccode\u003ejpeg-js\u003c/code\u003e is additionally given\nthe same limit so that a frame the header scan did not reach cannot exceed it\neither. Python and Rust already checked dimensions before decoding.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLegacy TLS now authenticates the server, and is never selected on its\nown.\u003c/strong\u003e The TLS 1.0 + RC4 transport validated no certificate at all, and any\nhost could be pushed onto it: a standard-HTTPS failure -- including a failed\ncertificate check, the one signal that something is wrong -- made the client\nretry over a transport that performs no certificate check. Two changes close\nthat. A profile now carries \u003ccode\u003etls_pins\u003c/code\u003e, the SHA-256 of the server\ncertificate's \u003ccode\u003eSubjectPublicKeyInfo\u003c/code\u003e, checked the moment the certificate\narrives and before any request byte or premaster secret is sent; the \u003ccode\u003eekeng\u003c/code\u003e\nprofile ships the key its appliance has presented since 2006. And the\ntransport no longer probes: a host reaches the legacy path only because a\nprofile declared it, and every other host is standard HTTPS or nothing.\nChain validation was never possible for these appliances -- they present a\nself-signed factory certificate naming neither the host nor a checkable\nauthority -- so a pinned key is a stronger guarantee here than the public\nPKI could have given.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-1\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-1\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eBreaking (Python/TypeScript API):\u003c/strong\u003e \u003ccode\u003eVerificationResult\u003c/code\u003e gains\n\u003ccode\u003esignature_valid\u003c/code\u003e / \u003ccode\u003esignatureValid\u003c/code\u003e (\u003ccode\u003etrue\u003c/code\u003e, \u003ccode\u003efalse\u003c/code\u003e, or \u003ccode\u003enull\u003c/code\u003e when\nverification could not be performed), and \u003ccode\u003evalid\u003c/code\u003e now additionally requires\nthat the signer signature verifies. Documents that earlier releases reported\nas valid on digest agreement alone are now reported invalid.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBreaking (Python/TypeScript API):\u003c/strong\u003e \u003ccode\u003esigner\u003c/code\u003e is \u003ccode\u003enull\u003c/code\u003e unless a matching ESS\nattribute or a trusted certificate chain authenticates the certificate.\nCallers that displayed signer identity unconditionally must handle absence.\u003c/li\u003e\n\u003cli\u003eOnly RSA PKCS#1 v1.5 signers are verified. ECDSA and RSASSA-PSS signers are\nreported as unverifiable, and therefore invalid, rather than accepted.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eConsequence, stated deliberately:\u003c/strong\u003e because signing workflows now refuse a\nresponse they cannot verify, a signing service that switched to ECDSA or\nRSASSA-PSS would fail signing outright rather than return an unverifiable\nsignature. That is the intended direction -- an unverifiable signature\nreturned under a success message is the defect being fixed -- but it means\nalgorithm support is now on the signing path, not only the verification path.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eSignature coverage is now measured and reported.\u003c/strong\u003e A signature covers only\nthe bytes named by its \u003ccode\u003e/ByteRange\u003c/code\u003e; anything after it belongs to a later\nincremental revision that the signature does not protect. Results gain\n\u003ccode\u003ecovers_whole_file\u003c/code\u003e, \u003ccode\u003ecovered_bytes\u003c/code\u003e and \u003ccode\u003etotal_bytes\u003c/code\u003e, and each signature\nreports whether it covers the whole file or only part of it. When \u003cem\u003eno\u003c/em\u003e\nsignature in a document reaches the end of the file, the trailing region is\nsigned by nobody and is called out explicitly. This is decided by arithmetic\nover the ByteRange alone -- the content of the unsigned region is never\ninspected or guessed at. Partial coverage does not by itself invalidate a\nsignature: in a sequentially signed document every signature but the last\nlegitimately covers an earlier revision.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCMS without signed attributes is now verified.\u003c/strong\u003e RFC 5652 section 5.4 makes\n\u003ccode\u003esignedAttrs\u003c/code\u003e optional and puts the signature over the content itself when it\nis absent. EKENG issues its credential documents in exactly that shape, so all\nthree implementations previously reported genuine EKENG-issued signatures as\ninvalid. The signature is now verified against the signed bytes, and because\nno \u003ccode\u003emessageDigest\u003c/code\u003e attribute exists in that form, integrity follows from the\nsignature verdict alone. Such signatures can carry no ESS binding, so signer\nidentity is exposed only when a trusted certificate chain authenticates it.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v2.1.1...v2.1.2\"\u003e2.1.2\u003c/a\u003e - 2026-08-03\u003c/h2\u003e\u003ca id=\"user-content-212---2026-08-03\" class=\"anchor\" aria-label=\"Permalink: 2.1.2 - 2026-08-03\" href=\"#212---2026-08-03\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-7\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-7\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003emacOS: sandbox write failure on auto-substituted output paths.\u003c/strong\u003e The GUI now resolves the output file/folder via native macOS Save/Open dialogs (Powerbox) on every sign operation, granting the app sandbox write permission. Previously, auto-derived paths like \u003ccode\u003eDownloads/file_signed.pdf\u003c/code\u003e triggered \u003ccode\u003eOperation not permitted (os error 1)\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-2\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-2\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eGUI: removed the separate output path text field and \"Browse...\" button.\u003c/strong\u003e The destination is now chosen directly from the native dialog when clicking \"Sign PDF\" (single file) or selecting a folder (batch mode).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v2.1.0...v2.1.1\"\u003e2.1.1\u003c/a\u003e - 2026-07-20\u003c/h2\u003e\u003ca id=\"user-content-211---2026-07-20\" class=\"anchor\" aria-label=\"Permalink: 2.1.1 - 2026-07-20\" href=\"#211---2026-07-20\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-8\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-8\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003ePython/TypeScript: signing a PDF that already has a form no longer\ndestroys it.\u003c/strong\u003e The incremental update now merges the new signature field\ninto the existing \u003ccode\u003e/AcroForm\u003c/code\u003e (preserving \u003ccode\u003e/Fields\u003c/code\u003e, \u003ccode\u003e/DR\u003c/code\u003e, \u003ccode\u003e/DA\u003c/code\u003e,\n\u003ccode\u003e/NeedAppearances\u003c/code\u003e and OR-ing \u003ccode\u003e/SigFlags\u003c/code\u003e) instead of replacing it\nwholesale -- the same fix the Rust port received in 2.1.0. Previously,\nsigning a form-bearing PDF orphaned its fields, and counter-signing a\nserver-pre-signed document (e.g. a tax-portal registration agreement)\ndropped the server's signature field from the form, so validators reported\nthe document as tampered.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003emacOS: the Dock icon is no longer overridden by a flat square at runtime.\u003c/strong\u003e\nThe desktop GUI forwarded its embedded window icon to\n\u003ccode\u003eNSApplication.setApplicationIconImage\u003c/code\u003e, which replaced the app bundle's\nrounded \u003ccode\u003e.icns\u003c/code\u003e with a square, full-bleed PNG for as long as the app ran. The\nruntime window icon is now set only on Windows (taskbar) and Linux (window\nmanager); on macOS the Dock tile comes from the bundle icon.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v2.0.0...v2.1.0\"\u003e2.1.0\u003c/a\u003e - 2026-07-20\u003c/h2\u003e\u003ca id=\"user-content-210---2026-07-20\" class=\"anchor\" aria-label=\"Permalink: 2.1.0 - 2026-07-20\" href=\"#210---2026-07-20\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-1\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-1\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eRust port: a library plus a \u003ccode\u003erevenant\u003c/code\u003e CLI, published as three crates.\u003c/strong\u003e\nA new idiomatic Rust implementation targeting the same surface as the Python\nand TypeScript ports (no GUI):\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003erevenant-sign-tls\u003c/code\u003e — from-scratch TLS 1.0 + RC4-MD5 client for the\nlegacy CoSign appliances (EKENG's \u003ccode\u003eca.gov.am\u003c/code\u003e) that no maintained Rust TLS\nlibrary can reach, mirroring \u003ccode\u003etlslite-ng\u003c/code\u003e / \u003ccode\u003enode-forge\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erevenant-sign-core\u003c/code\u003e — the client library: config and credential storage, the\nCoSign DSS SOAP client with auto-detecting transport, CMS extraction,\nPDF signature preparation / embedding / verification, and pinned/TSL\ncertificate-chain validation.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erevenant-sign\u003c/code\u003e — the \u003ccode\u003erevenant\u003c/code\u003e binary (\u003ccode\u003esign\u003c/code\u003e, \u003ccode\u003everify\u003c/code\u003e, \u003ccode\u003echeck\u003c/code\u003e, \u003ccode\u003einfo\u003c/code\u003e,\n\u003ccode\u003ecert\u003c/code\u003e, \u003ccode\u003esetup\u003c/code\u003e, \u003ccode\u003elogout\u003c/code\u003e, \u003ccode\u003ereset\u003c/code\u003e), a thin front-end over \u003ccode\u003erevenant-sign-core\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eThree new GUI languages: Turkish (tr), Georgian (ka), and Persian (fa).\u003c/strong\u003e\nThe desktop app now ships in six languages. Persian is right-to-left;\ntext in labels, messages, and input fields is right-aligned automatically\nwhen a right-to-left locale is active.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-3\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-3\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eThe Rust workspace now versions as one unit on the product line (2.1.0),\ncontinuing from the Python client rather than restarting at 0.1.x.\u003c/strong\u003e The\nlibrary crates (\u003ccode\u003erevenant-sign-core\u003c/code\u003e, \u003ccode\u003erevenant-sign-tls\u003c/code\u003e) and the shipped\nbinaries (\u003ccode\u003erevenant-sign\u003c/code\u003e, \u003ccode\u003erevenant-sign-gui\u003c/code\u003e) share a single version, so the\nCLI \u003ccode\u003e--version\u003c/code\u003e, the GUI About box, the store listings, and the signer version\nrecorded in each signed PDF (\u003ccode\u003e/Prop_Build /REx\u003c/code\u003e) all report the same number --\nmatching the single-version model the Python and TypeScript packages use.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-9\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-9\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eRust: signing a PDF that already has a form no longer destroys it.\u003c/strong\u003e The\nincremental update now merges the new signature field into the existing\n\u003ccode\u003e/AcroForm\u003c/code\u003e (preserving \u003ccode\u003e/Fields\u003c/code\u003e, \u003ccode\u003e/DR\u003c/code\u003e, \u003ccode\u003e/DA\u003c/code\u003e, \u003ccode\u003e/NeedAppearances\u003c/code\u003e and\nOR-ing \u003ccode\u003e/SigFlags\u003c/code\u003e) instead of replacing it wholesale. Previously, signing a\nform-bearing PDF orphaned its fields, and re-signing dropped the earlier\nsignature from the form.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust: \u003ccode\u003erevenant verify\u003c/code\u003e now recognises genuine signatures as trusted.\u003c/strong\u003e It\nvalidates detached CMS signatures fully in-crate against the active profile's\npinned trust anchors (the same path \u003ccode\u003echeck\u003c/code\u003e uses), instead of shelling out to\n\u003ccode\u003eopenssl\u003c/code\u003e with the system trust store — which lacks the EKENG root and\nreported every valid signature as \u003ccode\u003eINVALID\u003c/code\u003e. The external \u003ccode\u003eopenssl\u003c/code\u003e\ndependency is gone.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust: the CMS verifier no longer reports a signature \u003ccode\u003eValid\u003c/code\u003e when the\nmandatory \u003ccode\u003econtentType\u003c/code\u003e signed attribute is missing or disagrees with\n\u003ccode\u003eeContentType\u003c/code\u003e\u003c/strong\u003e (RFC 5652 §11.1). It also now verifies the ESS\n\u003ccode\u003esigningCertificate\u003c/code\u003e / \u003ccode\u003esigningCertificateV2\u003c/code\u003e binding when present (RFC 5035):\na signature whose ESSCertID hash names a different certificate than the one\nthat verified is no longer accepted. EKENG CoSign signatures carry the v1\nform and continue to verify.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust: a CMS whose signature could not be \u003cem\u003echecked\u003c/em\u003e (unusable key,\nunsupported algorithm OID, DER decode failure) is now reported \u003ccode\u003eUnverifiable\u003c/code\u003e\nrather than \u003ccode\u003eInvalid\u003c/code\u003e\u003c/strong\u003e — the latter wrongly implied the signature was forged.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust: the legacy TLS handshake is now bounded by an overall deadline,\u003c/strong\u003e so a\nserver that dribbles records (or empty handshake records) can no longer pin a\nclient thread indefinitely past the per-read socket timeout.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRust: smaller correctness and robustness fixes.\u003c/strong\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eLTV status no longer claims an OCSP response merely because an Adobe\n\u003ccode\u003eRevocationInfoArchival\u003c/code\u003e container is present (it may hold only CRLs).\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e/Contents\u003c/code\u003e hex extraction now ignores insignificant white space inside the\nhex string, per ISO 32000-1 §7.3.4.3, so signatures laid out by other tools\nare read correctly.\u003c/li\u003e\n\u003cli\u003eThe trailer \u003ccode\u003e/ID\u003c/code\u003e is carried into the incremental update as a hex string,\npreserving binary \u003ccode\u003e/ID\u003c/code\u003e values byte-for-byte instead of mangling them\nthrough a lossy UTF-8 conversion.\u003c/li\u003e\n\u003cli\u003eSigning a document whose catalog is at a non-zero generation now fails with\na clear message instead of producing a structurally inconsistent file.\u003c/li\u003e\n\u003cli\u003eSigning a document large enough that a cross-reference-stream byte offset\nwould exceed the 4-byte xref field now fails loudly instead of silently\ntruncating the offset into a corrupt xref stream.\u003c/li\u003e\n\u003cli\u003eThe legacy TLS client rejects a ServerHello that selects a version other\nthan TLS 1.0, parses IPv6 URL literals correctly, and fails loudly if the\nsocket read/write timeout cannot be set (rather than silently blocking).\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v1.2.1...v2.0.0\"\u003e2.0.0\u003c/a\u003e - 2026-05-16\u003c/h2\u003e\u003ca id=\"user-content-200---2026-05-16\" class=\"anchor\" aria-label=\"Permalink: 2.0.0 - 2026-05-16\" href=\"#200---2026-05-16\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eTypeScript SDK only — Python client is unchanged\u003c/h3\u003e\u003ca id=\"user-content-typescript-sdk-only--python-client-is-unchanged\" class=\"anchor\" aria-label=\"Permalink: TypeScript SDK only — Python client is unchanged\" href=\"#typescript-sdk-only--python-client-is-unchanged\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eBreaking\u003c/h3\u003e\u003ca id=\"user-content-breaking\" class=\"anchor\" aria-label=\"Permalink: Breaking\" href=\"#breaking\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\n\u003cp dir=\"auto\"\u003eLow-level signing functions in \u003ccode\u003erevenant-sign\u003c/code\u003e are renamed with a\n\u003ccode\u003eWithTransport\u003c/code\u003e suffix to free their unsuffixed names for new\nhigh-level wrappers:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003esignPdfEmbedded\u003c/code\u003e → \u003ccode\u003esignPdfEmbeddedWithTransport\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esignPdfDetached\u003c/code\u003e → \u003ccode\u003esignPdfDetachedWithTransport\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esignHash\u003c/code\u003e → \u003ccode\u003esignHashWithTransport\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esignData\u003c/code\u003e → \u003ccode\u003esignDataWithTransport\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003eSigningTransport\u003c/code\u003e interface method names (\u003ccode\u003etransport.signHash\u003c/code\u003e,\n\u003ccode\u003etransport.signData\u003c/code\u003e, \u003ccode\u003etransport.signPdfDetached\u003c/code\u003e) are unchanged.\nMigration: rename your imports; transport-handling code is untouched.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-2\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-2\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eHigh-level \u003ccode\u003esignHash(hashBytes, username, password, options?)\u003c/code\u003e\u003c/strong\u003e —\ndetached CMS over a precomputed 20-byte SHA-1 hash, with profile\nresolution, transport setup, and TLS registration handled internally.\nProduction callers no longer need to construct a \u003ccode\u003eSoapSigningTransport\u003c/code\u003e\nto sign a hash.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHigh-level \u003ccode\u003esignData(dataBytes, username, password, options?)\u003c/code\u003e\u003c/strong\u003e —\nsame shape, detached CMS over arbitrary bytes (server computes the\nhash).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHigh-level \u003ccode\u003egetCertInfo(username, password, options?)\u003c/code\u003e\u003c/strong\u003e — returns\nthe signer's identity (\u003ccode\u003eCertInfo\u003c/code\u003e: CN, email, organization, DN,\n\u003ccode\u003enotBefore\u003c/code\u003e, \u003ccode\u003enotAfter\u003c/code\u003e) by trying the \u003ccode\u003eenum-certificates\u003c/code\u003e SAPI\noperation first, falling back to dummy-hash signing.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHigh-level \u003ccode\u003everifyCredentials(username, password, options?)\u003c/code\u003e\u003c/strong\u003e —\nthin wrapper over \u003ccode\u003egetCertInfo\u003c/code\u003e that discards the result and surfaces\n\u003ccode\u003eAuthError\u003c/code\u003e on bad credentials. One round-trip against the appliance's\n5-attempt lockout counter; suitable for \"test before persist\"\ncredential-entry flows.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCertInfo\u003c/code\u003e type is re-exported from the top-level \u003ccode\u003erevenant-sign\u003c/code\u003e\nentry.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v1.2.0...v1.2.1\"\u003e1.2.1\u003c/a\u003e - 2026-04-16\u003c/h2\u003e\u003ca id=\"user-content-121---2026-04-16\" class=\"anchor\" aria-label=\"Permalink: 1.2.1 - 2026-04-16\" href=\"#121---2026-04-16\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-10\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-10\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eCompleted Armenian (hy) locale -- 21 strings were showing English fallback\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v1.1.1...v1.2.0\"\u003e1.2.0\u003c/a\u003e - 2026-04-14\u003c/h2\u003e\u003ca id=\"user-content-120---2026-04-14\" class=\"anchor\" aria-label=\"Permalink: 1.2.0 - 2026-04-14\" href=\"#120---2026-04-14\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-3\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-3\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003ePKI chain validation\u003c/strong\u003e against ETSI Trust Service Lists (TSL) -- verifies signer certificate chains to trusted CAs (Python and TypeScript)\u003c/li\u003e\n\u003cli\u003eTSL parser and cache with configurable TTL (24h default)\u003c/li\u003e\n\u003cli\u003ePer-profile \u003ccode\u003etsl_url\u003c/code\u003e field; EKENG profile uses the Armenian TSL\u003c/li\u003e\n\u003cli\u003eTrust status display in GUI verify panel (trusted / not publicly trusted / not checked)\u003c/li\u003e\n\u003cli\u003eImproved verify result formatting -- human-readable summary above technical details\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eImproved\u003c/h3\u003e\u003ca id=\"user-content-improved\" class=\"anchor\" aria-label=\"Permalink: Improved\" href=\"#improved\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eTest coverage expanded to 95%+ (740 tests)\u003c/li\u003e\n\u003cli\u003eNew test modules: chain validation, LTV, ASN.1 BER edge cases, i18n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-11\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-11\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eCVE-2026-40192 (pillow), CVE-2025-71176 (pytest)\u003c/li\u003e\n\u003cli\u003eCI: added \u003ccode\u003ecryptography\u003c/code\u003e and Python 3.10 backports to dev requirements\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v1.1.0...v1.1.1\"\u003e1.1.1\u003c/a\u003e - 2026-04-10\u003c/h2\u003e\u003ca id=\"user-content-111---2026-04-10\" class=\"anchor\" aria-label=\"Permalink: 1.1.1 - 2026-04-10\" href=\"#111---2026-04-10\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eImproved\u003c/h3\u003e\u003ca id=\"user-content-improved-1\" class=\"anchor\" aria-label=\"Permalink: Improved\" href=\"#improved-1\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eUpdated Russian and Armenian translations\u003c/li\u003e\n\u003cli\u003eAdded English locale for i18n completeness\u003c/li\u003e\n\u003cli\u003eRefined translatable strings across GUI modules\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v1.0.0...v1.1.0\"\u003e1.1.0\u003c/a\u003e - 2026-04-09\u003c/h2\u003e\u003ca id=\"user-content-110---2026-04-09\" class=\"anchor\" aria-label=\"Permalink: 1.1.0 - 2026-04-09\" href=\"#110---2026-04-09\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-4\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-4\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eLocalization\u003c/strong\u003e: Russian and Armenian language support for the GUI (gettext-based i18n)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSignature position preview\u003c/strong\u003e: mini page diagram in the sign form showing stamp placement\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePassword visibility toggle\u003c/strong\u003e in the login dialog\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFriendly error messages\u003c/strong\u003e: raw HTTP/TLS errors mapped to user-readable messages in the GUI\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eASCII credential warning\u003c/strong\u003e: warn when credentials contain non-Latin characters\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eUniversal macOS binaries\u003c/strong\u003e (arm64 + x86_64) -- Intel Mac users can now install from the Mac App Store, Homebrew, and DMG\u003c/li\u003e\n\u003cli\u003eMac App Store badge in README\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emerge_universal.py\u003c/code\u003e script for lipo-merging .app bundles and CLI binaries\u003c/li\u003e\n\u003cli\u003eTranslation consistency checker (\u003ccode\u003echeck_translations.py\u003c/code\u003e) and .po compiler (\u003ccode\u003ecompile_translations.py\u003c/code\u003e)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-4\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-4\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eHomebrew formula no longer requires \u003ccode\u003earch: :arm64\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eRelease pipeline builds on both \u003ccode\u003emacos-latest\u003c/code\u003e (arm64) and \u003ccode\u003emacos-15-intel\u003c/code\u003e (x86_64), merges with \u003ccode\u003elipo\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eRelease artifacts renamed from \u003ccode\u003e*-macos-arm64\u003c/code\u003e to \u003ccode\u003e*-macos-universal\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eCredential resolution moved off main thread to prevent UI freeze on macOS keychain prompts\u003c/li\u003e\n\u003cli\u003ePlatform-specific helpers (macOS menu bar, Windows icon) extracted to dedicated module\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-12\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-12\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eSupport for original EKENG cosign BER-encoded PDF signatures (indefinite-length CMS blobs) in both Python and TypeScript\u003c/li\u003e\n\u003cli\u003eBER parser guards against deeply nested structures (depth limit) and oversized length fields\u003c/li\u003e\n\u003cli\u003enpm publish step in release pipeline no longer silently swallows errors\u003c/li\u003e\n\u003cli\u003emacOS notarization step properly fails on command errors instead of silently continuing\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.2.6...v1.0.0\"\u003e1.0.0\u003c/a\u003e - 2026-02-26\u003c/h2\u003e\u003ca id=\"user-content-100---2026-02-26\" class=\"anchor\" aria-label=\"Permalink: 1.0.0 - 2026-02-26\" href=\"#100---2026-02-26\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-5\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-5\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eTypeScript/Node.js client\u003c/strong\u003e (\u003ccode\u003erevenant-sign\u003c/code\u003e on npm) -- full-featured port of the Python client with CLI, library API, and 662 tests at 97%+ coverage\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eDual ESM + CJS build via tsup\u003c/li\u003e\n\u003cli\u003eEmbedded and detached PDF signing\u003c/li\u003e\n\u003cli\u003eSignature verification (embedded and detached CMS/PKCS#7)\u003c/li\u003e\n\u003cli\u003eCMS blob inspection and certificate info extraction\u003c/li\u003e\n\u003cli\u003eMulti-profile configuration with keytar credential storage\u003c/li\u003e\n\u003cli\u003eLegacy TLS 1.0 + RC4 transport for EKENG (pure JS via node-forge)\u003c/li\u003e\n\u003cli\u003eArmenian font support (GHEA Grapalat, GHEA Mariam, Noto Sans)\u003c/li\u003e\n\u003cli\u003eSignature image embedding (PNG/JPEG) with alpha channel support\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eMicrosoft Store distribution (Revenant Sign) with Partner Center identity\u003c/li\u003e\n\u003cli\u003eRelease pipeline now includes MAS .pkg in GitHub Releases\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-5\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-5\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eMSIX package renamed to RevenantSign.msix, display name updated to \"Revenant Sign\"\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-13\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-13\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003ePDF incremental updates now use cross-reference streams for PDFs that use XRef streams (PDF 1.5+), per ISO 32000-1 S7.5.8.4 -- fixes compatibility with macOS Preview and strict PDF readers when signing pdf-lib or modern-tool-generated PDFs (both Python and TypeScript)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.2.5...v0.2.6\"\u003e0.2.6\u003c/a\u003e - 2026-02-26\u003c/h2\u003e\u003ca id=\"user-content-026---2026-02-26\" class=\"anchor\" aria-label=\"Permalink: 0.2.6 - 2026-02-26\" href=\"#026---2026-02-26\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-6\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-6\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eGUI window title simplified to \"Revenant\" (version removed to avoid screenshot staleness)\u003c/li\u003e\n\u003cli\u003eMAS minimum deployment target bumped from macOS 11.0 to 12.0 (required for arm64-only builds)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.2.4...v0.2.5\"\u003e0.2.5\u003c/a\u003e - 2026-02-26\u003c/h2\u003e\u003ca id=\"user-content-025---2026-02-26\" class=\"anchor\" aria-label=\"Permalink: 0.2.5 - 2026-02-26\" href=\"#025---2026-02-26\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-6\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-6\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003emacOS menu bar with File (Close Window) and Edit (Undo, Cut, Copy, Paste, Select All) menus\u003c/li\u003e\n\u003cli\u003eCmd+W keyboard shortcut to close windows and dialogs on macOS\u003c/li\u003e\n\u003cli\u003ePrivacy policy URL in Info.plist (MAS Guideline 5.1.1)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-7\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-7\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003ePyPI classifier updated from Beta to Production/Stable (MAS Guideline 2.2)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-14\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-14\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eMAS build: switch from system Tk 8.6 to Homebrew Tk 9.0 to resolve private API rejection (Guideline 2.5.1 -- \u003ccode\u003e_NSWindowDidOrderOnScreenNotification\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003eCI: extract Python+Tk setup and private API verification into reusable composite actions\u003c/li\u003e\n\u003cli\u003emacOS Preferences menu item no longer shows default Tk behavior\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.2.3...v0.2.4\"\u003e0.2.4\u003c/a\u003e - 2026-02-21\u003c/h2\u003e\u003ca id=\"user-content-024---2026-02-21\" class=\"anchor\" aria-label=\"Permalink: 0.2.4 - 2026-02-21\" href=\"#024---2026-02-21\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-15\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-15\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eMAS build: use separate entitlements for nested executables to avoid ITMS-90885\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.2.2...v0.2.3\"\u003e0.2.3\u003c/a\u003e - 2026-02-21\u003c/h2\u003e\u003ca id=\"user-content-023---2026-02-21\" class=\"anchor\" aria-label=\"Permalink: 0.2.3 - 2026-02-21\" href=\"#023---2026-02-21\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-16\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-16\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eMAS build: add application identifier to sandbox entitlements (ITMS-90886)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.2.1...v0.2.2\"\u003e0.2.2\u003c/a\u003e - 2026-02-21\u003c/h2\u003e\u003ca id=\"user-content-022---2026-02-21\" class=\"anchor\" aria-label=\"Permalink: 0.2.2 - 2026-02-21\" href=\"#022---2026-02-21\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-17\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-17\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eMAS build: embed provisioning profile for TestFlight and App Store submission (ITMS-90889)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.2.0...v0.2.1\"\u003e0.2.1\u003c/a\u003e - 2026-02-20\u003c/h2\u003e\u003ca id=\"user-content-021---2026-02-20\" class=\"anchor\" aria-label=\"Permalink: 0.2.1 - 2026-02-20\" href=\"#021---2026-02-20\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-7\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-7\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eLinux ARM64 build support\u003c/li\u003e\n\u003cli\u003eAutomatic Homebrew tap update on release\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/compare/v0.1.0...v0.2.0\"\u003e0.2.0\u003c/a\u003e - 2026-02-16\u003c/h2\u003e\u003ca id=\"user-content-020---2026-02-16\" class=\"anchor\" aria-label=\"Permalink: 0.2.0 - 2026-02-16\" href=\"#020---2026-02-16\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eChanged\u003c/h3\u003e\u003ca id=\"user-content-changed-8\" class=\"anchor\" aria-label=\"Permalink: Changed\" href=\"#changed-8\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003eBREAKING:\u003c/strong\u003e Position preset names renamed to Y-first (CSS/UI) convention: \u003ccode\u003eright-bottom\u003c/code\u003e -\u0026gt; \u003ccode\u003ebottom-right\u003c/code\u003e, \u003ccode\u003eright-top\u003c/code\u003e -\u0026gt; \u003ccode\u003etop-right\u003c/code\u003e, \u003ccode\u003eleft-bottom\u003c/code\u003e -\u0026gt; \u003ccode\u003ebottom-left\u003c/code\u003e, \u003ccode\u003eleft-top\u003c/code\u003e -\u0026gt; \u003ccode\u003etop-left\u003c/code\u003e, \u003ccode\u003ecenter-bottom\u003c/code\u003e -\u0026gt; \u003ccode\u003ebottom-center\u003c/code\u003e. Short aliases updated accordingly (\u003ccode\u003erb\u003c/code\u003e -\u0026gt; \u003ccode\u003ebr\u003c/code\u003e, \u003ccode\u003ert\u003c/code\u003e -\u0026gt; \u003ccode\u003etr\u003c/code\u003e, etc.)\u003c/li\u003e\n\u003cli\u003eWindows release artifact renamed from \u003ccode\u003erevenant-cli-windows-x64.zip\u003c/code\u003e to \u003ccode\u003erevenant-windows-x64.zip\u003c/code\u003e (contains both CLI and GUI)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFixed\u003c/h3\u003e\u003ca id=\"user-content-fixed-18\" class=\"anchor\" aria-label=\"Permalink: Fixed\" href=\"#fixed-18\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eMAS build: include sandbox entitlements when signing outer .app bundle\u003c/li\u003e\n\u003cli\u003eSnap: use \u003ccode\u003eadopt-info\u003c/code\u003e for version instead of \u003ccode\u003egit describe\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eIntegration tests TLS setup for env-var-only credentials\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ca href=\"https://github.com/lobotomoe/revenant/releases/tag/v0.1.0\"\u003e0.1.0\u003c/a\u003e - 2026-02-14\u003c/h2\u003e\u003ca id=\"user-content-010---2026-02-14\" class=\"anchor\" aria-label=\"Permalink: 0.1.0 - 2026-02-14\" href=\"#010---2026-02-14\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eAdded\u003c/h3\u003e\u003ca id=\"user-content-added-8\" class=\"anchor\" aria-label=\"Permalink: Added\" href=\"#added-8\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003ePDF digital signing via CoSign SAPI Web Service (SOAP/DSS)\u003c/li\u003e\n\u003cli\u003eEmbedded visible and invisible signature support with customizable appearance\u003c/li\u003e\n\u003cli\u003eArmenian font support (GHEA Grapalat, GHEA Mariam, Noto Sans)\u003c/li\u003e\n\u003cli\u003eSignature image embedding (PNG/JPEG) with alpha channel support\u003c/li\u003e\n\u003cli\u003ePDF signature verification (embedded and detached CMS/PKCS#7)\u003c/li\u003e\n\u003cli\u003eCMS blob inspection: digest algorithm, signer info, certificate chain\u003c/li\u003e\n\u003cli\u003eCertificate information extraction from X.509 and CMS blobs\u003c/li\u003e\n\u003cli\u003eMulti-profile configuration with per-profile credentials (keyring or config file)\u003c/li\u003e\n\u003cli\u003eAutomatic CoSign server discovery via DNS SRV records\u003c/li\u003e\n\u003cli\u003eLegacy TLS 1.0 + RC4-MD5 transport for EKENG (ca.gov.am)\u003c/li\u003e\n\u003cli\u003eCross-platform GUI (tkinter) with sign, verify, and setup dialogs\u003c/li\u003e\n\u003cli\u003eCLI with \u003ccode\u003esign\u003c/code\u003e, \u003ccode\u003everify\u003c/code\u003e, and \u003ccode\u003esetup\u003c/code\u003e commands\u003c/li\u003e\n\u003cli\u003eIncremental PDF updates preserving existing signatures (re-signing support)\u003c/li\u003e\n\u003cli\u003eCross-reference stream PDF support (modern PDF 1.5+ format)\u003c/li\u003e\n\u003cli\u003ePage size detection with CropBox/MediaBox/Rotate handling\u003c/li\u003e\n\u003cli\u003eCI/CD workflows for linting, testing, and release builds\u003c/li\u003e\n\u003cli\u003eMulti-platform release builds: macOS (.dmg), Windows (.msix, .zip), Linux (AppImage)\u003c/li\u003e\n\u003cli\u003ePyright strict mode type checking with 0 errors\u003c/li\u003e\n\u003cli\u003e96%+ test coverage (600+ tests)\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/article\u003e","richTextTruncated":false,"renderedFileInfo":null,"symbols":{"timed_out":false,"not_analyzed":false,"symbols":[{"name":"Changelog","fully_qualified_name":"Changelog","kind":"section_1","ident_start":2,"ident_end":11,"extent_start":0,"extent_end":40871,"ident_utf16":{"start":{"line_number":0,"utf16_col":2},"end":{"line_number":0,"utf16_col":11}},"extent_utf16":{"start":{"line_number":0,"utf16_col":0},"end":{"line_number":737,"utf16_col":0}}},{"name":"[Unreleased]","fully_qualified_name":"[Unreleased]","kind":"section_2","ident_start":256,"ident_end":268,"extent_start":253,"extent_end":270,"ident_utf16":{"start":{"line_number":7,"utf16_col":3},"end":{"line_number":7,"utf16_col":15}},"extent_utf16":{"start":{"line_number":7,"utf16_col":0},"end":{"line_number":9,"utf16_col":0}}},{"name":"[4.0.0] - 2026-09-03","fully_qualified_name":"[4.0.0] - 2026-09-03","kind":"section_2","ident_start":273,"ident_end":293,"extent_start":270,"extent_end":4032,"ident_utf16":{"start":{"line_number":9,"utf16_col":3},"end":{"line_number":9,"utf16_col":23}},"extent_utf16":{"start":{"line_number":9,"utf16_col":0},"end":{"line_number":75,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":754,"ident_end":759,"extent_start":750,"extent_end":1923,"ident_utf16":{"start":{"line_number":19,"utf16_col":4},"end":{"line_number":19,"utf16_col":9}},"extent_utf16":{"start":{"line_number":19,"utf16_col":0},"end":{"line_number":38,"utf16_col":0}}},{"name":"Security","fully_qualified_name":"Security","kind":"section_3","ident_start":1927,"ident_end":1935,"extent_start":1923,"extent_end":3009,"ident_utf16":{"start":{"line_number":38,"utf16_col":4},"end":{"line_number":38,"utf16_col":12}},"extent_utf16":{"start":{"line_number":38,"utf16_col":0},"end":{"line_number":56,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":3013,"ident_end":3020,"extent_start":3009,"extent_end":3497,"ident_utf16":{"start":{"line_number":56,"utf16_col":4},"end":{"line_number":56,"utf16_col":11}},"extent_utf16":{"start":{"line_number":56,"utf16_col":0},"end":{"line_number":65,"utf16_col":0}}},{"name":"Removed","fully_qualified_name":"Removed","kind":"section_3","ident_start":3501,"ident_end":3508,"extent_start":3497,"extent_end":4032,"ident_utf16":{"start":{"line_number":65,"utf16_col":4},"end":{"line_number":65,"utf16_col":11}},"extent_utf16":{"start":{"line_number":65,"utf16_col":0},"end":{"line_number":75,"utf16_col":0}}},{"name":"[3.0.5] - 2026-08-18","fully_qualified_name":"[3.0.5] - 2026-08-18","kind":"section_2","ident_start":4035,"ident_end":4055,"extent_start":4032,"extent_end":6530,"ident_utf16":{"start":{"line_number":75,"utf16_col":3},"end":{"line_number":75,"utf16_col":23}},"extent_utf16":{"start":{"line_number":75,"utf16_col":0},"end":{"line_number":117,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":4251,"ident_end":4256,"extent_start":4247,"extent_end":6051,"ident_utf16":{"start":{"line_number":81,"utf16_col":4},"end":{"line_number":81,"utf16_col":9}},"extent_utf16":{"start":{"line_number":81,"utf16_col":0},"end":{"line_number":108,"utf16_col":0}}},{"name":"Security","fully_qualified_name":"Security","kind":"section_3","ident_start":6055,"ident_end":6063,"extent_start":6051,"extent_end":6530,"ident_utf16":{"start":{"line_number":108,"utf16_col":4},"end":{"line_number":108,"utf16_col":12}},"extent_utf16":{"start":{"line_number":108,"utf16_col":0},"end":{"line_number":117,"utf16_col":0}}},{"name":"[3.0.4] - 2026-08-18","fully_qualified_name":"[3.0.4] - 2026-08-18","kind":"section_2","ident_start":6533,"ident_end":6553,"extent_start":6530,"extent_end":8155,"ident_utf16":{"start":{"line_number":117,"utf16_col":3},"end":{"line_number":117,"utf16_col":23}},"extent_utf16":{"start":{"line_number":117,"utf16_col":0},"end":{"line_number":146,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":6716,"ident_end":6721,"extent_start":6712,"extent_end":8155,"ident_utf16":{"start":{"line_number":123,"utf16_col":4},"end":{"line_number":123,"utf16_col":9}},"extent_utf16":{"start":{"line_number":123,"utf16_col":0},"end":{"line_number":146,"utf16_col":0}}},{"name":"[3.0.3] - 2026-08-17","fully_qualified_name":"[3.0.3] - 2026-08-17","kind":"section_2","ident_start":8158,"ident_end":8178,"extent_start":8155,"extent_end":8980,"ident_utf16":{"start":{"line_number":146,"utf16_col":3},"end":{"line_number":146,"utf16_col":23}},"extent_utf16":{"start":{"line_number":146,"utf16_col":0},"end":{"line_number":163,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":8334,"ident_end":8339,"extent_start":8330,"extent_end":8980,"ident_utf16":{"start":{"line_number":151,"utf16_col":4},"end":{"line_number":151,"utf16_col":9}},"extent_utf16":{"start":{"line_number":151,"utf16_col":0},"end":{"line_number":163,"utf16_col":0}}},{"name":"[3.0.2] - 2026-08-16","fully_qualified_name":"[3.0.2] - 2026-08-16","kind":"section_2","ident_start":8983,"ident_end":9003,"extent_start":8980,"extent_end":12148,"ident_utf16":{"start":{"line_number":163,"utf16_col":3},"end":{"line_number":163,"utf16_col":23}},"extent_utf16":{"start":{"line_number":163,"utf16_col":0},"end":{"line_number":211,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":9157,"ident_end":9162,"extent_start":9153,"extent_end":12148,"ident_utf16":{"start":{"line_number":168,"utf16_col":4},"end":{"line_number":168,"utf16_col":9}},"extent_utf16":{"start":{"line_number":168,"utf16_col":0},"end":{"line_number":211,"utf16_col":0}}},{"name":"[3.0.1] - 2026-08-16","fully_qualified_name":"[3.0.1] - 2026-08-16","kind":"section_2","ident_start":12151,"ident_end":12171,"extent_start":12148,"extent_end":13498,"ident_utf16":{"start":{"line_number":211,"utf16_col":3},"end":{"line_number":211,"utf16_col":23}},"extent_utf16":{"start":{"line_number":211,"utf16_col":0},"end":{"line_number":235,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":12301,"ident_end":12306,"extent_start":12297,"extent_end":13498,"ident_utf16":{"start":{"line_number":216,"utf16_col":4},"end":{"line_number":216,"utf16_col":9}},"extent_utf16":{"start":{"line_number":216,"utf16_col":0},"end":{"line_number":235,"utf16_col":0}}},{"name":"[3.0.0] - 2026-08-12","fully_qualified_name":"[3.0.0] - 2026-08-12","kind":"section_2","ident_start":13501,"ident_end":13521,"extent_start":13498,"extent_end":23894,"ident_utf16":{"start":{"line_number":235,"utf16_col":3},"end":{"line_number":235,"utf16_col":23}},"extent_utf16":{"start":{"line_number":235,"utf16_col":0},"end":{"line_number":391,"utf16_col":0}}},{"name":"Removed","fully_qualified_name":"Removed","kind":"section_3","ident_start":14004,"ident_end":14011,"extent_start":14000,"extent_end":15374,"ident_utf16":{"start":{"line_number":246,"utf16_col":4},"end":{"line_number":246,"utf16_col":11}},"extent_utf16":{"start":{"line_number":246,"utf16_col":0},"end":{"line_number":268,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":15378,"ident_end":15383,"extent_start":15374,"extent_end":21227,"ident_utf16":{"start":{"line_number":268,"utf16_col":4},"end":{"line_number":268,"utf16_col":9}},"extent_utf16":{"start":{"line_number":268,"utf16_col":0},"end":{"line_number":351,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":21231,"ident_end":21238,"extent_start":21227,"extent_end":22443,"ident_utf16":{"start":{"line_number":351,"utf16_col":4},"end":{"line_number":351,"utf16_col":11}},"extent_utf16":{"start":{"line_number":351,"utf16_col":0},"end":{"line_number":369,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":22447,"ident_end":22452,"extent_start":22443,"extent_end":23894,"ident_utf16":{"start":{"line_number":369,"utf16_col":4},"end":{"line_number":369,"utf16_col":9}},"extent_utf16":{"start":{"line_number":369,"utf16_col":0},"end":{"line_number":391,"utf16_col":0}}},{"name":"[2.1.2] - 2026-08-03","fully_qualified_name":"[2.1.2] - 2026-08-03","kind":"section_2","ident_start":23897,"ident_end":23917,"extent_start":23894,"extent_end":24502,"ident_utf16":{"start":{"line_number":391,"utf16_col":3},"end":{"line_number":391,"utf16_col":23}},"extent_utf16":{"start":{"line_number":391,"utf16_col":0},"end":{"line_number":401,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":23923,"ident_end":23928,"extent_start":23919,"extent_end":24272,"ident_utf16":{"start":{"line_number":393,"utf16_col":4},"end":{"line_number":393,"utf16_col":9}},"extent_utf16":{"start":{"line_number":393,"utf16_col":0},"end":{"line_number":397,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":24276,"ident_end":24283,"extent_start":24272,"extent_end":24502,"ident_utf16":{"start":{"line_number":397,"utf16_col":4},"end":{"line_number":397,"utf16_col":11}},"extent_utf16":{"start":{"line_number":397,"utf16_col":0},"end":{"line_number":401,"utf16_col":0}}},{"name":"[2.1.1] - 2026-07-20","fully_qualified_name":"[2.1.1] - 2026-07-20","kind":"section_2","ident_start":24505,"ident_end":24525,"extent_start":24502,"extent_end":25577,"ident_utf16":{"start":{"line_number":401,"utf16_col":3},"end":{"line_number":401,"utf16_col":23}},"extent_utf16":{"start":{"line_number":401,"utf16_col":0},"end":{"line_number":422,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":24531,"ident_end":24536,"extent_start":24527,"extent_end":25577,"ident_utf16":{"start":{"line_number":403,"utf16_col":4},"end":{"line_number":403,"utf16_col":9}},"extent_utf16":{"start":{"line_number":403,"utf16_col":0},"end":{"line_number":422,"utf16_col":0}}},{"name":"[2.1.0] - 2026-07-20","fully_qualified_name":"[2.1.0] - 2026-07-20","kind":"section_2","ident_start":25580,"ident_end":25600,"extent_start":25577,"extent_end":30211,"ident_utf16":{"start":{"line_number":422,"utf16_col":3},"end":{"line_number":422,"utf16_col":23}},"extent_utf16":{"start":{"line_number":422,"utf16_col":0},"end":{"line_number":498,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":25606,"ident_end":25611,"extent_start":25602,"extent_end":26717,"ident_utf16":{"start":{"line_number":424,"utf16_col":4},"end":{"line_number":424,"utf16_col":9}},"extent_utf16":{"start":{"line_number":424,"utf16_col":0},"end":{"line_number":443,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":26721,"ident_end":26728,"extent_start":26717,"extent_end":27279,"ident_utf16":{"start":{"line_number":443,"utf16_col":4},"end":{"line_number":443,"utf16_col":11}},"extent_utf16":{"start":{"line_number":443,"utf16_col":0},"end":{"line_number":453,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":27283,"ident_end":27288,"extent_start":27279,"extent_end":30211,"ident_utf16":{"start":{"line_number":453,"utf16_col":4},"end":{"line_number":453,"utf16_col":9}},"extent_utf16":{"start":{"line_number":453,"utf16_col":0},"end":{"line_number":498,"utf16_col":0}}},{"name":"[2.0.0] - 2026-05-16","fully_qualified_name":"[2.0.0] - 2026-05-16","kind":"section_2","ident_start":30214,"ident_end":30234,"extent_start":30211,"extent_end":31984,"ident_utf16":{"start":{"line_number":498,"utf16_col":3},"end":{"line_number":498,"utf16_col":23}},"extent_utf16":{"start":{"line_number":498,"utf16_col":0},"end":{"line_number":538,"utf16_col":0}}},{"name":"TypeScript SDK only — Python client is unchanged","fully_qualified_name":"TypeScript SDK only — Python client is unchanged","kind":"section_3","ident_start":30240,"ident_end":30290,"extent_start":30236,"extent_end":30292,"ident_utf16":{"start":{"line_number":500,"utf16_col":4},"end":{"line_number":500,"utf16_col":52}},"extent_utf16":{"start":{"line_number":500,"utf16_col":0},"end":{"line_number":502,"utf16_col":0}}},{"name":"Breaking","fully_qualified_name":"Breaking","kind":"section_3","ident_start":30296,"ident_end":30304,"extent_start":30292,"extent_end":30874,"ident_utf16":{"start":{"line_number":502,"utf16_col":4},"end":{"line_number":502,"utf16_col":12}},"extent_utf16":{"start":{"line_number":502,"utf16_col":0},"end":{"line_number":516,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":30878,"ident_end":30883,"extent_start":30874,"extent_end":31984,"ident_utf16":{"start":{"line_number":516,"utf16_col":4},"end":{"line_number":516,"utf16_col":9}},"extent_utf16":{"start":{"line_number":516,"utf16_col":0},"end":{"line_number":538,"utf16_col":0}}},{"name":"[1.2.1] - 2026-04-16","fully_qualified_name":"[1.2.1] - 2026-04-16","kind":"section_2","ident_start":31987,"ident_end":32007,"extent_start":31984,"extent_end":32098,"ident_utf16":{"start":{"line_number":538,"utf16_col":3},"end":{"line_number":538,"utf16_col":23}},"extent_utf16":{"start":{"line_number":538,"utf16_col":0},"end":{"line_number":544,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":32013,"ident_end":32018,"extent_start":32009,"extent_end":32098,"ident_utf16":{"start":{"line_number":540,"utf16_col":4},"end":{"line_number":540,"utf16_col":9}},"extent_utf16":{"start":{"line_number":540,"utf16_col":0},"end":{"line_number":544,"utf16_col":0}}},{"name":"[1.2.0] - 2026-04-14","fully_qualified_name":"[1.2.0] - 2026-04-14","kind":"section_2","ident_start":32101,"ident_end":32121,"extent_start":32098,"extent_end":32846,"ident_utf16":{"start":{"line_number":544,"utf16_col":3},"end":{"line_number":544,"utf16_col":23}},"extent_utf16":{"start":{"line_number":544,"utf16_col":0},"end":{"line_number":564,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":32127,"ident_end":32132,"extent_start":32123,"extent_end":32580,"ident_utf16":{"start":{"line_number":546,"utf16_col":4},"end":{"line_number":546,"utf16_col":9}},"extent_utf16":{"start":{"line_number":546,"utf16_col":0},"end":{"line_number":554,"utf16_col":0}}},{"name":"Improved","fully_qualified_name":"Improved","kind":"section_3","ident_start":32584,"ident_end":32592,"extent_start":32580,"extent_end":32710,"ident_utf16":{"start":{"line_number":554,"utf16_col":4},"end":{"line_number":554,"utf16_col":12}},"extent_utf16":{"start":{"line_number":554,"utf16_col":0},"end":{"line_number":559,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":32714,"ident_end":32719,"extent_start":32710,"extent_end":32846,"ident_utf16":{"start":{"line_number":559,"utf16_col":4},"end":{"line_number":559,"utf16_col":9}},"extent_utf16":{"start":{"line_number":559,"utf16_col":0},"end":{"line_number":564,"utf16_col":0}}},{"name":"[1.1.1] - 2026-04-10","fully_qualified_name":"[1.1.1] - 2026-04-10","kind":"section_2","ident_start":32849,"ident_end":32869,"extent_start":32846,"extent_end":33025,"ident_utf16":{"start":{"line_number":564,"utf16_col":3},"end":{"line_number":564,"utf16_col":23}},"extent_utf16":{"start":{"line_number":564,"utf16_col":0},"end":{"line_number":572,"utf16_col":0}}},{"name":"Improved","fully_qualified_name":"Improved","kind":"section_3","ident_start":32875,"ident_end":32883,"extent_start":32871,"extent_end":33025,"ident_utf16":{"start":{"line_number":566,"utf16_col":4},"end":{"line_number":566,"utf16_col":12}},"extent_utf16":{"start":{"line_number":566,"utf16_col":0},"end":{"line_number":572,"utf16_col":0}}},{"name":"[1.1.0] - 2026-04-09","fully_qualified_name":"[1.1.0] - 2026-04-09","kind":"section_2","ident_start":33028,"ident_end":33048,"extent_start":33025,"extent_end":34638,"ident_utf16":{"start":{"line_number":572,"utf16_col":3},"end":{"line_number":572,"utf16_col":23}},"extent_utf16":{"start":{"line_number":572,"utf16_col":0},"end":{"line_number":601,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":33054,"ident_end":33059,"extent_start":33050,"extent_end":33816,"ident_utf16":{"start":{"line_number":574,"utf16_col":4},"end":{"line_number":574,"utf16_col":9}},"extent_utf16":{"start":{"line_number":574,"utf16_col":0},"end":{"line_number":586,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":33820,"ident_end":33827,"extent_start":33816,"extent_end":34244,"ident_utf16":{"start":{"line_number":586,"utf16_col":4},"end":{"line_number":586,"utf16_col":11}},"extent_utf16":{"start":{"line_number":586,"utf16_col":0},"end":{"line_number":594,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":34248,"ident_end":34253,"extent_start":34244,"extent_end":34638,"ident_utf16":{"start":{"line_number":594,"utf16_col":4},"end":{"line_number":594,"utf16_col":9}},"extent_utf16":{"start":{"line_number":594,"utf16_col":0},"end":{"line_number":601,"utf16_col":0}}},{"name":"[1.0.0] - 2026-02-26","fully_qualified_name":"[1.0.0] - 2026-02-26","kind":"section_2","ident_start":34641,"ident_end":34661,"extent_start":34638,"extent_end":35804,"ident_utf16":{"start":{"line_number":601,"utf16_col":3},"end":{"line_number":601,"utf16_col":23}},"extent_utf16":{"start":{"line_number":601,"utf16_col":0},"end":{"line_number":625,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":34667,"ident_end":34672,"extent_start":34663,"extent_end":35422,"ident_utf16":{"start":{"line_number":603,"utf16_col":4},"end":{"line_number":603,"utf16_col":9}},"extent_utf16":{"start":{"line_number":603,"utf16_col":0},"end":{"line_number":617,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":35426,"ident_end":35433,"extent_start":35422,"extent_end":35521,"ident_utf16":{"start":{"line_number":617,"utf16_col":4},"end":{"line_number":617,"utf16_col":11}},"extent_utf16":{"start":{"line_number":617,"utf16_col":0},"end":{"line_number":621,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":35525,"ident_end":35530,"extent_start":35521,"extent_end":35804,"ident_utf16":{"start":{"line_number":621,"utf16_col":4},"end":{"line_number":621,"utf16_col":9}},"extent_utf16":{"start":{"line_number":621,"utf16_col":0},"end":{"line_number":625,"utf16_col":0}}},{"name":"[0.2.6] - 2026-02-26","fully_qualified_name":"[0.2.6] - 2026-02-26","kind":"section_2","ident_start":35807,"ident_end":35827,"extent_start":35804,"extent_end":36031,"ident_utf16":{"start":{"line_number":625,"utf16_col":3},"end":{"line_number":625,"utf16_col":23}},"extent_utf16":{"start":{"line_number":625,"utf16_col":0},"end":{"line_number":632,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":35833,"ident_end":35840,"extent_start":35829,"extent_end":36031,"ident_utf16":{"start":{"line_number":627,"utf16_col":4},"end":{"line_number":627,"utf16_col":11}},"extent_utf16":{"start":{"line_number":627,"utf16_col":0},"end":{"line_number":632,"utf16_col":0}}},{"name":"[0.2.5] - 2026-02-26","fully_qualified_name":"[0.2.5] - 2026-02-26","kind":"section_2","ident_start":36034,"ident_end":36054,"extent_start":36031,"extent_end":36695,"ident_utf16":{"start":{"line_number":632,"utf16_col":3},"end":{"line_number":632,"utf16_col":23}},"extent_utf16":{"start":{"line_number":632,"utf16_col":0},"end":{"line_number":650,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":36060,"ident_end":36065,"extent_start":36056,"extent_end":36283,"ident_utf16":{"start":{"line_number":634,"utf16_col":4},"end":{"line_number":634,"utf16_col":9}},"extent_utf16":{"start":{"line_number":634,"utf16_col":0},"end":{"line_number":640,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":36287,"ident_end":36294,"extent_start":36283,"extent_end":36374,"ident_utf16":{"start":{"line_number":640,"utf16_col":4},"end":{"line_number":640,"utf16_col":11}},"extent_utf16":{"start":{"line_number":640,"utf16_col":0},"end":{"line_number":644,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":36378,"ident_end":36383,"extent_start":36374,"extent_end":36695,"ident_utf16":{"start":{"line_number":644,"utf16_col":4},"end":{"line_number":644,"utf16_col":9}},"extent_utf16":{"start":{"line_number":644,"utf16_col":0},"end":{"line_number":650,"utf16_col":0}}},{"name":"[0.2.4] - 2026-02-21","fully_qualified_name":"[0.2.4] - 2026-02-21","kind":"section_2","ident_start":36698,"ident_end":36718,"extent_start":36695,"extent_end":36814,"ident_utf16":{"start":{"line_number":650,"utf16_col":3},"end":{"line_number":650,"utf16_col":23}},"extent_utf16":{"start":{"line_number":650,"utf16_col":0},"end":{"line_number":656,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":36724,"ident_end":36729,"extent_start":36720,"extent_end":36814,"ident_utf16":{"start":{"line_number":652,"utf16_col":4},"end":{"line_number":652,"utf16_col":9}},"extent_utf16":{"start":{"line_number":652,"utf16_col":0},"end":{"line_number":656,"utf16_col":0}}},{"name":"[0.2.3] - 2026-02-21","fully_qualified_name":"[0.2.3] - 2026-02-21","kind":"section_2","ident_start":36817,"ident_end":36837,"extent_start":36814,"extent_end":36928,"ident_utf16":{"start":{"line_number":656,"utf16_col":3},"end":{"line_number":656,"utf16_col":23}},"extent_utf16":{"start":{"line_number":656,"utf16_col":0},"end":{"line_number":662,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":36843,"ident_end":36848,"extent_start":36839,"extent_end":36928,"ident_utf16":{"start":{"line_number":658,"utf16_col":4},"end":{"line_number":658,"utf16_col":9}},"extent_utf16":{"start":{"line_number":658,"utf16_col":0},"end":{"line_number":662,"utf16_col":0}}},{"name":"[0.2.2] - 2026-02-21","fully_qualified_name":"[0.2.2] - 2026-02-21","kind":"section_2","ident_start":36931,"ident_end":36951,"extent_start":36928,"extent_end":37058,"ident_utf16":{"start":{"line_number":662,"utf16_col":3},"end":{"line_number":662,"utf16_col":23}},"extent_utf16":{"start":{"line_number":662,"utf16_col":0},"end":{"line_number":668,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":36957,"ident_end":36962,"extent_start":36953,"extent_end":37058,"ident_utf16":{"start":{"line_number":664,"utf16_col":4},"end":{"line_number":664,"utf16_col":9}},"extent_utf16":{"start":{"line_number":664,"utf16_col":0},"end":{"line_number":668,"utf16_col":0}}},{"name":"[0.2.1] - 2026-02-20","fully_qualified_name":"[0.2.1] - 2026-02-20","kind":"section_2","ident_start":37061,"ident_end":37081,"extent_start":37058,"extent_end":37166,"ident_utf16":{"start":{"line_number":668,"utf16_col":3},"end":{"line_number":668,"utf16_col":23}},"extent_utf16":{"start":{"line_number":668,"utf16_col":0},"end":{"line_number":675,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":37087,"ident_end":37092,"extent_start":37083,"extent_end":37166,"ident_utf16":{"start":{"line_number":670,"utf16_col":4},"end":{"line_number":670,"utf16_col":9}},"extent_utf16":{"start":{"line_number":670,"utf16_col":0},"end":{"line_number":675,"utf16_col":0}}},{"name":"[0.2.0] - 2026-02-16","fully_qualified_name":"[0.2.0] - 2026-02-16","kind":"section_2","ident_start":37169,"ident_end":37189,"extent_start":37166,"extent_end":37844,"ident_utf16":{"start":{"line_number":675,"utf16_col":3},"end":{"line_number":675,"utf16_col":23}},"extent_utf16":{"start":{"line_number":675,"utf16_col":0},"end":{"line_number":688,"utf16_col":0}}},{"name":"Changed","fully_qualified_name":"Changed","kind":"section_3","ident_start":37195,"ident_end":37202,"extent_start":37191,"extent_end":37637,"ident_utf16":{"start":{"line_number":677,"utf16_col":4},"end":{"line_number":677,"utf16_col":11}},"extent_utf16":{"start":{"line_number":677,"utf16_col":0},"end":{"line_number":682,"utf16_col":0}}},{"name":"Fixed","fully_qualified_name":"Fixed","kind":"section_3","ident_start":37641,"ident_end":37646,"extent_start":37637,"extent_end":37844,"ident_utf16":{"start":{"line_number":682,"utf16_col":4},"end":{"line_number":682,"utf16_col":9}},"extent_utf16":{"start":{"line_number":682,"utf16_col":0},"end":{"line_number":688,"utf16_col":0}}},{"name":"[0.1.0] - 2026-02-14","fully_qualified_name":"[0.1.0] - 2026-02-14","kind":"section_2","ident_start":37847,"ident_end":37867,"extent_start":37844,"extent_end":40871,"ident_utf16":{"start":{"line_number":688,"utf16_col":3},"end":{"line_number":688,"utf16_col":23}},"extent_utf16":{"start":{"line_number":688,"utf16_col":0},"end":{"line_number":737,"utf16_col":0}}},{"name":"Added","fully_qualified_name":"Added","kind":"section_3","ident_start":37873,"ident_end":37878,"extent_start":37869,"extent_end":40871,"ident_utf16":{"start":{"line_number":690,"utf16_col":4},"end":{"line_number":690,"utf16_col":9}},"extent_utf16":{"start":{"line_number":690,"utf16_col":0},"end":{"line_number":737,"utf16_col":0}}}]}},"codeViewLayoutRoute":{"repo":{"id":1158110957,"defaultBranch":"main","name":"revenant","ownerLogin":"lobotomoe","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2026-02-14T20:16:33.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/24254622?v=4","public":true,"private":false,"isOrgOwned":false,"isArchived":false},"currentUser":null,"uploadToken":"nOIHy9Y12dVLf_p9iKXMvQ7Nt2XGOj3aVPStNyPJFRF9EE4JQ1HWjHASRRByf78xy8Pbe29QENVymD377mtnRA","allShortcutsEnabled":false,"treeExpanded":true,"path":"CHANGELOG.md","symbolsExpanded":false,"refInfo":{"name":"main","listCacheKey":"v0:1791607078.0","canEdit":false,"currentOid":"189ea9665459b37aa9e625d8b62f7d6d5c6776b3"},"helpUrl":"https://docs.github.com","githubDevUrl":null},"codeViewFileTreeLayoutRoute":{"fileTree":{"":{"items":[{"name":".githooks","path":".githooks","contentType":"directory"},{"name":".github","path":".github","contentType":"directory"},{"name":"docs","path":"docs","contentType":"directory"},{"name":"python","path":"python","contentType":"directory"},{"name":"rust","path":"rust","contentType":"directory"},{"name":"typescript","path":"typescript","contentType":"directory"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":"CHANGELOG.md","path":"CHANGELOG.md","contentType":"file"},{"name":"CONTRIBUTING.md","path":"CONTRIBUTING.md","contentType":"file"},{"name":"LICENSE","path":"LICENSE","contentType":"file"},{"name":"Makefile","path":"Makefile","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"SECURITY.md","path":"SECURITY.md","contentType":"file"},{"name":"_typos.toml","path":"_typos.toml","contentType":"file"},{"name":"pyrightconfig.json","path":"pyrightconfig.json","contentType":"file"}],"totalCount":15}},"fileTreeProcessingTime":14.505944999999999,"foldersToFetch":[]},"codeViewBlobLayoutRoute":{"codeLineWrapEnabled":false,"refInfo":{"name":"main","listCacheKey":"v0:1791607078.0","canEdit":false,"refType":"branch","currentOid":"189ea9665459b37aa9e625d8b62f7d6d5c6776b3","canEditOnDefaultBranch":false,"fileExistsOnDefault":true},"path":"CHANGELOG.md","blob":{"copilotSWEAgentEnabled":false,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/lobotomoe/revenant/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"CHANGELOG.md","displayUrl":"https://github.com/lobotomoe/revenant/blob/main/CHANGELOG.md?raw=true","headerInfo":{"blobSize":"39.9 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"2fb5110","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Flobotomoe%2Frevenant%2Fblob%2Fmain%2FCHANGELOG.md","isCSV":false,"isRichtext":true,"lineInfo":{"truncatedLoc":"737","truncatedSloc":"610"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"isIssueTemplate":false,"isDiscussionTemplate":false,"language":"Markdown","languageID":222,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/lobotomoe/revenant/blob/main/CHANGELOG.md","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/lobotomoe/revenant/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/lobotomoe/revenant/raw/refs/heads/main/CHANGELOG.md","renderImageOrRaw":false,"shortPath":null,"symbolsEnabled":true,"tabSize":4,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null},"copilotInfo":null,"copilotAccessAllowed":false,"copilotSpacesEnabled":false,"modelsAccessAllowed":false,"modelsRepoIntegrationEnabled":false,"isMarketplaceEnabled":true},"codeViewBlobLayoutRoute.StyledBlob":{"rawLines":["# Changelog","","All notable changes to this project will be documented in this file.","","The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),","and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).","","## [Unreleased]","","## [4.0.0] - 2026-09-03","","Two fixes -- logging in to a non-EKENG server, and the app icon on macOS -- plus","a dependency refresh that clears a withdrawn package and two advisories.","","The major bump is required by one breaking change in the Rust crates:","`ServerProfile` in `revenant-sign-core` gains a public field, and the struct is","now `#[non_exhaustive]`. The Python and TypeScript packages keep their public","surface, and nothing changes for users of the desktop app or the CLIs.","","### Fixed","","- **Custom servers now accept non-ASCII credentials.** Both GUIs refused a","  username or password containing any non-Latin character, on every profile.","  The restriction describes EKENG, whose logins are Latin letters and digits,","  but it was applied to custom CoSign deployments too -- blocking credentials","  the SOAP envelope escapes and carries as UTF-8 without trouble, and that both","  CLIs already accepted. It is now a profile capability, off for custom servers","  and on for EKENG, where a non-ASCII entry is a keyboard-layout slip and","  refusing it spends none of that profile's five-attempt lockout budget.","  Reported by peyuaa (#76).","- **The macOS Dock no longer shows a black egui logo in place of the app icon.**","  The desktop app deliberately left its runtime icon unset on macOS, so that the","  Dock tile would stay with the bundle's `.icns`. eframe reads an unset icon the","  other way round: it substitutes its own default egui artwork and pushes that","  through `setApplicationIconImage`, taking over the tile for as long as the app","  runs. It now passes an empty `IconData` -- egui's documented opt-out -- so the","  installed icon survives launch.","","### Security","","- **The desktop app no longer ships a browser-launching flaw.** `webbrowser`,","  which the GUI stack uses to open links, honoured the Unix `BROWSER`","  environment variable in a way that allowed argument injection","  (RUSTSEC-2026-0257). Updated to 1.2.4.","- **Rust dependencies are now audited in CI.** `cargo audit` runs beside","  `pip-audit` in the required Security Audit job, so a known-vulnerable crate","  fails the build instead of waiting for a weekly Scorecard alert nobody is","  blocked on -- which is how the `webbrowser` advisory above went unnoticed.","  One finding is ignored with a recorded reason and a re-evaluation trigger: the","  `rsa` Marvin timing sidechannel, which has no released fix.","- **The Rust lockfile is refreshed to the latest compatible releases.** This","  takes `chacha20` off a yanked 0.10.1, `event-listener` off the unsound 5.4.1","  (RUSTSEC-2026-0221), and `wayland-scanner` onto a `quick-xml` 0.41 -- which","  removes the last vulnerable copy of that crate from the tree and lets the two","  `quick-xml` audit ignores go, arming that guard again.","","### Changed","","- **`ServerProfile` in `revenant-sign-core` is now `#[non_exhaustive]`** and","  gains a public `ascii_credentials_only` field. Profiles are meant to come from","  `ServerProfile::builtin` / `::custom_default`, which fill in every capability","  the deployment implies; code that constructed the struct literally must move","  to those constructors. Marking it non-exhaustive is itself the breaking part,","  and it is what keeps the next field addition off the major-version budget.","","### Removed","","- **`python/scripts/merge_universal.py`.** The release workflow has lipo'd the","  macOS universal binaries directly since the Rust pipeline landed, leaving the","  script unreachable from every workflow and build script. Its fallback read a","  `lipo -create` \"same architectures\" failure as proof that both inputs held","  the same architecture set and copied the arm64 side, which for partially","  overlapping inputs would have silently dropped the x86_64 slice. The live","  path has no such fallback. Reported by peyuaa (#75).","","## [3.0.5] - 2026-08-18","","Security release. Three fixes for background results being applied to state","that did not ask for them, one for the output path a signed file is written to,","and a hardened release pipeline.","","### Fixed","","- **A cancelled connection can no longer become the active server.** Dismissing","  the Connect dialog did not stop the server test already running, and neither","  GUI checked, when the answer arrived, whether it was still wanted. A slow","  endpoint could return success after the user backed out and be saved as the","  active profile, so later credentials and signing requests went somewhere the","  user had declined. Both GUIs now tag each attempt and drop a result that no","  longer belongs to the dialog on screen. Reported by peyuaa; found during that","  fix to affect the Python GUI as well.","- **A saved password can no longer be pre-filled into a different login.** The","  Rust GUI read the saved password in the background and applied whatever came","  back to whichever login was open, so cancelling one login and starting another","  could put the first account's password into the second. The read is now bound","  to the login and username that asked for it, and a password box the user has","  typed in and cleared is never silently re-filled. The same fix covers identity","  discovery, which could otherwise save one login's signer certificate under","  another's. Reported by peyuaa.","- **Signing no longer writes through a symlink at the output path.** When a","  sandbox forbids creating a temporary file, both the Python and Rust clients","  fall back to writing the output directly. That fallback followed a symlink","  left at a predictable output name (`\u003cstem\u003e_signed.pdf`) and truncated whatever","  it pointed at, where the normal path replaces the link instead. The fallback","  now refuses to follow links, refuses anything that is not a regular file, and","  fails outright on platforms that cannot express either. Reported by peyuaa","  against the Python client; the Rust CLI carried the same flaw.","","### Security","","- **The AppImage packaging tool is now pinned and verified.** The release job","  fetched `appimagetool` from a mutable tag over plain HTTP(S) and marked it","  executable without checking it, then used it to pack both published Linux","  AppImages -- so whatever that URL served on release day ran inside the release","  job and shipped to users. It is now pinned to a tagged release and checked","  against a known digest before it is made executable. Reported by peyuaa.","","## [3.0.4] - 2026-08-18","","Security release. Three fixes in what the desktop apps report about a","signature. The command-line tools were not affected and their verdicts are","unchanged.","","### Fixed","","- **A tampered PDF is no longer shown as a valid signature.** The Rust GUI took","  its green verdict from the CMS signature alone. Altering bytes the ByteRange","  covers while leaving the CMS object intact keeps that signature verifiable, so","  a modified document rendered `Signature VALID` in green with a red integrity","  failure beneath it, and named the embedded certificate as its signer. The","  verdict is now the full result -- integrity and signature -- which is what the","  core library already meant by valid, and what the CLI has always reported.","  Reported by peyuaa.","- **A failed signature no longer carries a trusted signer.** The Python GUI","  printed `Signature: Integrity check FAILED` directly above a green","  `Trust: Trusted (CA)`, because the trust line read only the chain result. The","  chain may genuinely be trusted; that is a fact about the certificate and not","  about a document the signature does not cover. Signer, organization and trust","  are now reported only for a signature that verified, on both the embedded and","  detached paths. Reported by peyuaa.","- **A certificate that is not yet valid is no longer styled as normal.** The","  Python GUI classified certificates by `notAfter` only, so one whose validity","  period had not started showed the ordinary presentation and a reassuring","  count of days remaining. It is now flagged like an expired certificate, since","  neither can be used. Reported by peyuaa.","","## [3.0.3] - 2026-08-17","","Security release completing a fix that 3.0.2 landed in only one of the three","clients. Users of the Python package or the Rust crates should upgrade.","","### Fixed","","- **The BER size bound now covers Python and Rust as well.** 3.0.2 fixed this","  in the TypeScript client because the report named only the npm package, but","  the flaw was identical in the other two: the 16 MiB ceiling sat in the","  definite-length branch, past the point where an indefinite-length header","  returns, and the `/Contents` gap was decoded to a string with no bound at all.","  A crafted `/ByteRange` therefore allocated the whole gap before anything","  rejected it. Both clients now refuse an oversized gap and an oversized input","  up front, against the same shared ceiling, matching TypeScript. Originally","  reported by peyuaa.","","## [3.0.2] - 2026-08-16","","Security release. Three reported vulnerabilities in signature verification,","plus a batch-signing fix in the desktop app. Upgrading is recommended.","","### Fixed","","- **Certificate order in a CMS no longer confers trust.** Chain validation began","  from the first embedded certificate and signer identity was read from the same","  one, while the signature itself was verified against the certificate named by","  `SignerInfo.sid`. A CMS certificate set is a `SET OF`, so those need not be the","  same certificate: signing detached content with an untrusted self-signed","  certificate and listing a trusted root ahead of it made Revenant verify the","  attacker's signature, walk the chain from the root, report `VALID and trusted`,","  and display the root as the signer. All three now resolve the signer through","  the `SignerInfo`, and only when exactly one embedded certificate matches --","  an ambiguous set has not named a signer. Rust's `build_chain` takes the leaf as","  a parameter instead of reading pool index 0. Reported by peyuaa.","- **LTV status is no longer raised by data anyone can append.** The status came","  from the presence of a revocation-related OID and counted the signer's","  *unsigned* attributes alongside the signed ones. Unsigned attributes are not","  covered by the signature, so an Adobe `RevocationInfoArchival` attribute","  holding the string `bogus-not-ocsp-or-crl` could be stapled onto a finished","  document and reported as LTV enabled with `has_ocsp` set. The `SignedData.crls`","  field is outside the signature for the same reason. Both are still reported,","  now marked as present and not counted, and attribute values are decoded rather","  than taken on the OID's word. This reports what a signature carries, not that","  the revocation data was validated -- that check does not exist yet, and the","  documentation says so. Python and TypeScript gain","  `has_unauthenticated_material`; Rust reports the same distinction as a","  `RevocationMaterial` enum. Rust was affected too, though the report named only","  the Python and npm packages. Reported by peyuaa.","- **A BER indefinite-length CMS is bounded before it is decoded.** The 16 MiB","  ceiling was checked against the length a definite-length header claims. An","  indefinite-length blob claims nothing, so it was decoded whole and its extent","  discovered only on reaching the end marker -- a crafted PDF allocated its","  entire `/Contents` gap regardless of the limit. Both the extractor and the PDF","  path now refuse an oversized gap up front, against the same exported ceiling.","  Fixed here in the TypeScript client only, which is what the report named; the","  Python and Rust clients carried the same flaw and are fixed in 3.0.3.","  Reported by peyuaa.","- **Batch signing no longer overwrites files it did not create.** Every","  destination is reserved before the first signing request and written with","  exclusive creation, so a queued input can no longer be replaced by an earlier","  file's output, and same-named inputs from different folders no longer collide.","  Outputs that could not take their derived name are now reported, labelled by","  full input path. Contributed by peyuaa.","","## [3.0.1] - 2026-08-16","","Security release. Fixes one vulnerability introduced in 3.0.0; upgrading is","recommended for everyone running that version.","","### Fixed","","- **A CMS signature without signed attributes no longer vouches for content it","  never covered.** RFC 5652 section 5.4 puts the signature over the content","  itself when `signedAttrs` is absent, and 3.0.0 added support for that shape.","  It resolved the content in the wrong order: an `encapContentInfo.eContent`","  embedded in the blob was preferred over the bytes the caller asked about, so a","  genuine attached signature over attacker-chosen bytes verified successfully","  and the result was then reported as covering an unrelated PDF `ByteRange` or","  detached payload. Because no signed attributes means no `messageDigest` to","  re-check, that signature verdict was the entire integrity argument:","  verification returned `valid`, `hash_ok` and `signature_valid` all true for a","  document the signer never saw. The caller's bytes are now authoritative, and a","  CMS that also embeds its own content is rejected as unverifiable unless the","  two are byte-identical. Verifying an attached CMS on its own terms, by passing","  no detached content, still works. Signatures that carry signed attributes were","  never affected -- their `messageDigest` was always compared against the","  caller's bytes.","","## [3.0.0] - 2026-08-12","","Security release. Every change below is a fix for a reported vulnerability;","upgrading is recommended for all users.","","The major bump is required by one breaking API change in the Rust crates:","`pki::validate_chain` no longer takes a `Transport`, and","`constants::MAX_AIA_FETCHES` is gone. The Python and TypeScript packages keep","their public surface -- `MAX_AIA_FETCHES` was never exported from either","entry point -- but their verification behaviour changes, as described below.","","### Removed","","- **Certificate chains are no longer completed by fetching the Authority","  Information Access URLs printed inside a certificate.** Those URLs travel","  inside the document being verified, so following them let whoever produced a","  file choose hosts for the verifying machine to contact -- opening a document","  became a network callback that reports the reader's address and the moment","  they opened it, and a certificate may carry any number of them. The per-chain","  limit did not restrain this: it counted only fetches that *succeeded*, so","  unreachable URLs were retried without bound, each costing a connection","  attempt with retries. Issuers now come from the CMS blob, which is where","  RFC 5652 has a signer put them, and from the configured trust store. A","  signature whose CMS omits its own intermediates stops short of a trusted","  anchor and reads as `untrusted` -- or `indeterminate`, if an anchor still","  matched by name but the chain could not be verified -- instead of silently","  reaching out for them. No built-in","  profile is affected: EKENG's chain has no intermediates, and its certificates'","  AIA URLs are plain HTTP, which the transport has always refused.","  `MAX_AIA_FETCHES` is gone from all three implementations, and Rust's","  `validate_chain` no longer takes a `Transport` -- chain validation now has no","  way to reach the network at all.","","### Fixed","","- **Python/TypeScript: CMS verification now checks the cryptographic signer","  signature, not only the container structure and signed digest.** Unsupported,","  malformed, or unverifiable signatures fail closed. Signer identity and","  optional trust-chain validation now select the certificate named by","  `SignerInfo.sid` instead of relying on certificate `SET OF` order, and an ESS","  `signingCertificate`/`signingCertificateV2` attribute binds that identity to","  the exact embedded certificate. TypeScript verifies directly with that","  selected certificate instead of allowing PKIjs to resolve `sid` again, and","  both implementations expose signer metadata only when matching ESS or a","  trusted certificate chain authenticates it.","- **Python: the signer public key is read from the certificate's","  `SubjectPublicKeyInfo` instead of parsing the whole certificate.** Production","  signer certificates (EKENG/CoSign among them) encode DN attributes such as","  `emailAddress` as a `PrintableString` containing `@`, which is outside that","  type's character set; a strict X.509 parser rejects the entire certificate","  over a field signature verification never reads, turning genuine signatures","  into false negatives.","- **Python and TypeScript now agree on the CMS signature input.** Python hashed","  the signed attributes re-encoded as canonical DER while TypeScript hashed only","  the bytes as transmitted, so the same document could verify in one and fail in","  the other. Both now try the as-transmitted encoding first and the canonical","  DER re-encoding second. The two encodings carry the same parsed attributes,","  which are validated independently before any signature is checked, so nothing","  is loosened. Rust remains canonical-DER only and still rejects a signature","  made over a non-DER transmitted ordering.","- Failures that cannot be attributed to a specific check now name the","  underlying error instead of reporting an opaque \"CMS signature check failed\".","- **Signing workflows no longer return a signature nobody verified.** The","  CMS/PKCS#7 bytes a signing service sends back are now proven to be a genuine","  signature over exactly the bytes that were submitted, before any workflow","  reports success. A compromised or impersonated service could previously make","  `sign_data` / `sign_pdf_detached` succeed while returning something no","  conforming verifier would accept, and the embedded flow accepted a response","  that merely hashed correctly. Failures raise a distinct","  `SigningResponseError` (`RevenantError::SigningResponse` in Rust): the request","  succeeded at the protocol level, so retrying is pointless and nothing is","  saved. The raw `SigningTransport` methods remain unverified by design -- they","  are the low-level primitive, and calling them directly is an explicit opt-out.","- **Rust: knowing the hash that was submitted no longer stands in for checking","  what came back.** Post-sign verification treated a matching `expected_hash` as","  proof and skipped the CMS `messageDigest` comparison entirely, so a response","  carrying no digest at all could be reported as intact. Both are now required,","  matching Python and TypeScript.","- **Rust: `has_signed_attributes` distinguishes \"the CMS says it has none\" from","  \"the CMS did not parse\".** It now returns `Option\u003cbool\u003e`; previously a","  corrupt blob borrowed the diagnostic wording of a legitimate","  no-`signedAttrs` signature.","- **`sign_hash` now says what it actually does.** It signs the hash bytes","  themselves. What a service does with a submitted digest is service-defined and","  observed to vary: some sign it as a pre-computed digest, others hash it again","  and sign it as ordinary content -- and only the first kind yields a signature","  that can be attached to the document the hash came from. The response is","  checked to be a genuine signature, and a warning is logged naming both digests","  when it does not bind the one submitted. The operation is not failed over","  this: the caller asked for those bytes to be signed, and they were. To sign a","  document, pass the document to `sign_data`.","- **TypeScript: the signature image pixel budget is enforced before decoding,","  not after.** `loadSignatureImage` decoded the whole file and only then","  compared the pixel count against its limit, by which point the decoder had","  already allocated the full output: a 547 KB PNG declaring 12000x12000 drove","  resident memory to 1.7 GB before the check ran, and the 5 MB file cap allows","  an order of magnitude more. Dimensions are now read from the PNG `IHDR` chunk","  and the JPEG frame header, and refused there; `jpeg-js` is additionally given","  the same limit so that a frame the header scan did not reach cannot exceed it","  either. Python and Rust already checked dimensions before decoding.","- **Legacy TLS now authenticates the server, and is never selected on its","  own.** The TLS 1.0 + RC4 transport validated no certificate at all, and any","  host could be pushed onto it: a standard-HTTPS failure -- including a failed","  certificate check, the one signal that something is wrong -- made the client","  retry over a transport that performs no certificate check. Two changes close","  that. A profile now carries `tls_pins`, the SHA-256 of the server","  certificate's `SubjectPublicKeyInfo`, checked the moment the certificate","  arrives and before any request byte or premaster secret is sent; the `ekeng`","  profile ships the key its appliance has presented since 2006. And the","  transport no longer probes: a host reaches the legacy path only because a","  profile declared it, and every other host is standard HTTPS or nothing.","  Chain validation was never possible for these appliances -- they present a","  self-signed factory certificate naming neither the host nor a checkable","  authority -- so a pinned key is a stronger guarantee here than the public","  PKI could have given.","","### Changed","","- **Breaking (Python/TypeScript API):** `VerificationResult` gains","  `signature_valid` / `signatureValid` (`true`, `false`, or `null` when","  verification could not be performed), and `valid` now additionally requires","  that the signer signature verifies. Documents that earlier releases reported","  as valid on digest agreement alone are now reported invalid.","- **Breaking (Python/TypeScript API):** `signer` is `null` unless a matching ESS","  attribute or a trusted certificate chain authenticates the certificate.","  Callers that displayed signer identity unconditionally must handle absence.","- Only RSA PKCS#1 v1.5 signers are verified. ECDSA and RSASSA-PSS signers are","  reported as unverifiable, and therefore invalid, rather than accepted.","- **Consequence, stated deliberately:** because signing workflows now refuse a","  response they cannot verify, a signing service that switched to ECDSA or","  RSASSA-PSS would fail signing outright rather than return an unverifiable","  signature. That is the intended direction -- an unverifiable signature","  returned under a success message is the defect being fixed -- but it means","  algorithm support is now on the signing path, not only the verification path.","### Added","","- **Signature coverage is now measured and reported.** A signature covers only","  the bytes named by its `/ByteRange`; anything after it belongs to a later","  incremental revision that the signature does not protect. Results gain","  `covers_whole_file`, `covered_bytes` and `total_bytes`, and each signature","  reports whether it covers the whole file or only part of it. When *no*","  signature in a document reaches the end of the file, the trailing region is","  signed by nobody and is called out explicitly. This is decided by arithmetic","  over the ByteRange alone -- the content of the unsigned region is never","  inspected or guessed at. Partial coverage does not by itself invalidate a","  signature: in a sequentially signed document every signature but the last","  legitimately covers an earlier revision.","- **CMS without signed attributes is now verified.** RFC 5652 section 5.4 makes","  `signedAttrs` optional and puts the signature over the content itself when it","  is absent. EKENG issues its credential documents in exactly that shape, so all","  three implementations previously reported genuine EKENG-issued signatures as","  invalid. The signature is now verified against the signed bytes, and because","  no `messageDigest` attribute exists in that form, integrity follows from the","  signature verdict alone. Such signatures can carry no ESS binding, so signer","  identity is exposed only when a trusted certificate chain authenticates it.","","## [2.1.2] - 2026-08-03","","### Fixed","","- **macOS: sandbox write failure on auto-substituted output paths.** The GUI now resolves the output file/folder via native macOS Save/Open dialogs (Powerbox) on every sign operation, granting the app sandbox write permission. Previously, auto-derived paths like `Downloads/file_signed.pdf` triggered `Operation not permitted (os error 1)`.","","### Changed","","- **GUI: removed the separate output path text field and \"Browse...\" button.** The destination is now chosen directly from the native dialog when clicking \"Sign PDF\" (single file) or selecting a folder (batch mode).","","## [2.1.1] - 2026-07-20","","### Fixed","","- **Python/TypeScript: signing a PDF that already has a form no longer","  destroys it.** The incremental update now merges the new signature field","  into the existing `/AcroForm` (preserving `/Fields`, `/DR`, `/DA`,","  `/NeedAppearances` and OR-ing `/SigFlags`) instead of replacing it","  wholesale -- the same fix the Rust port received in 2.1.0. Previously,","  signing a form-bearing PDF orphaned its fields, and counter-signing a","  server-pre-signed document (e.g. a tax-portal registration agreement)","  dropped the server's signature field from the form, so validators reported","  the document as tampered.","","- **macOS: the Dock icon is no longer overridden by a flat square at runtime.**","  The desktop GUI forwarded its embedded window icon to","  `NSApplication.setApplicationIconImage`, which replaced the app bundle's","  rounded `.icns` with a square, full-bleed PNG for as long as the app ran. The","  runtime window icon is now set only on Windows (taskbar) and Linux (window","  manager); on macOS the Dock tile comes from the bundle icon.","","## [2.1.0] - 2026-07-20","","### Added","","- **Rust port: a library plus a `revenant` CLI, published as three crates.**","  A new idiomatic Rust implementation targeting the same surface as the Python","  and TypeScript ports (no GUI):","  - `revenant-sign-tls` — from-scratch TLS 1.0 + RC4-MD5 client for the","    legacy CoSign appliances (EKENG's `ca.gov.am`) that no maintained Rust TLS","    library can reach, mirroring `tlslite-ng` / `node-forge`.","  - `revenant-sign-core` — the client library: config and credential storage, the","    CoSign DSS SOAP client with auto-detecting transport, CMS extraction,","    PDF signature preparation / embedding / verification, and pinned/TSL","    certificate-chain validation.","  - `revenant-sign` — the `revenant` binary (`sign`, `verify`, `check`, `info`,","    `cert`, `setup`, `logout`, `reset`), a thin front-end over `revenant-sign-core`.","- **Three new GUI languages: Turkish (tr), Georgian (ka), and Persian (fa).**","  The desktop app now ships in six languages. Persian is right-to-left;","  text in labels, messages, and input fields is right-aligned automatically","  when a right-to-left locale is active.","","### Changed","","- **The Rust workspace now versions as one unit on the product line (2.1.0),","  continuing from the Python client rather than restarting at 0.1.x.** The","  library crates (`revenant-sign-core`, `revenant-sign-tls`) and the shipped","  binaries (`revenant-sign`, `revenant-sign-gui`) share a single version, so the","  CLI `--version`, the GUI About box, the store listings, and the signer version","  recorded in each signed PDF (`/Prop_Build /REx`) all report the same number --","  matching the single-version model the Python and TypeScript packages use.","","### Fixed","","- **Rust: signing a PDF that already has a form no longer destroys it.** The","  incremental update now merges the new signature field into the existing","  `/AcroForm` (preserving `/Fields`, `/DR`, `/DA`, `/NeedAppearances` and","  OR-ing `/SigFlags`) instead of replacing it wholesale. Previously, signing a","  form-bearing PDF orphaned its fields, and re-signing dropped the earlier","  signature from the form.","- **Rust: `revenant verify` now recognises genuine signatures as trusted.** It","  validates detached CMS signatures fully in-crate against the active profile's","  pinned trust anchors (the same path `check` uses), instead of shelling out to","  `openssl` with the system trust store — which lacks the EKENG root and","  reported every valid signature as `INVALID`. The external `openssl`","  dependency is gone.","- **Rust: the CMS verifier no longer reports a signature `Valid` when the","  mandatory `contentType` signed attribute is missing or disagrees with","  `eContentType`** (RFC 5652 §11.1). It also now verifies the ESS","  `signingCertificate` / `signingCertificateV2` binding when present (RFC 5035):","  a signature whose ESSCertID hash names a different certificate than the one","  that verified is no longer accepted. EKENG CoSign signatures carry the v1","  form and continue to verify.","- **Rust: a CMS whose signature could not be *checked* (unusable key,","  unsupported algorithm OID, DER decode failure) is now reported `Unverifiable`","  rather than `Invalid`** — the latter wrongly implied the signature was forged.","- **Rust: the legacy TLS handshake is now bounded by an overall deadline,** so a","  server that dribbles records (or empty handshake records) can no longer pin a","  client thread indefinitely past the per-read socket timeout.","- **Rust: smaller correctness and robustness fixes.**","  - LTV status no longer claims an OCSP response merely because an Adobe","    `RevocationInfoArchival` container is present (it may hold only CRLs).","  - `/Contents` hex extraction now ignores insignificant white space inside the","    hex string, per ISO 32000-1 §7.3.4.3, so signatures laid out by other tools","    are read correctly.","  - The trailer `/ID` is carried into the incremental update as a hex string,","    preserving binary `/ID` values byte-for-byte instead of mangling them","    through a lossy UTF-8 conversion.","  - Signing a document whose catalog is at a non-zero generation now fails with","    a clear message instead of producing a structurally inconsistent file.","  - Signing a document large enough that a cross-reference-stream byte offset","    would exceed the 4-byte xref field now fails loudly instead of silently","    truncating the offset into a corrupt xref stream.","  - The legacy TLS client rejects a ServerHello that selects a version other","    than TLS 1.0, parses IPv6 URL literals correctly, and fails loudly if the","    socket read/write timeout cannot be set (rather than silently blocking).","","## [2.0.0] - 2026-05-16","","### TypeScript SDK only — Python client is unchanged","","### Breaking","","- Low-level signing functions in `revenant-sign` are renamed with a","  `WithTransport` suffix to free their unsuffixed names for new","  high-level wrappers:","  - `signPdfEmbedded` → `signPdfEmbeddedWithTransport`","  - `signPdfDetached` → `signPdfDetachedWithTransport`","  - `signHash` → `signHashWithTransport`","  - `signData` → `signDataWithTransport`","","  The `SigningTransport` interface method names (`transport.signHash`,","  `transport.signData`, `transport.signPdfDetached`) are unchanged.","  Migration: rename your imports; transport-handling code is untouched.","","### Added","","- **High-level `signHash(hashBytes, username, password, options?)`** —","  detached CMS over a precomputed 20-byte SHA-1 hash, with profile","  resolution, transport setup, and TLS registration handled internally.","  Production callers no longer need to construct a `SoapSigningTransport`","  to sign a hash.","- **High-level `signData(dataBytes, username, password, options?)`** —","  same shape, detached CMS over arbitrary bytes (server computes the","  hash).","- **High-level `getCertInfo(username, password, options?)`** — returns","  the signer's identity (`CertInfo`: CN, email, organization, DN,","  `notBefore`, `notAfter`) by trying the `enum-certificates` SAPI","  operation first, falling back to dummy-hash signing.","- **High-level `verifyCredentials(username, password, options?)`** —","  thin wrapper over `getCertInfo` that discards the result and surfaces","  `AuthError` on bad credentials. One round-trip against the appliance's","  5-attempt lockout counter; suitable for \"test before persist\"","  credential-entry flows.","- `CertInfo` type is re-exported from the top-level `revenant-sign`","  entry.","","## [1.2.1] - 2026-04-16","","### Fixed","","- Completed Armenian (hy) locale -- 21 strings were showing English fallback","","## [1.2.0] - 2026-04-14","","### Added","","- **PKI chain validation** against ETSI Trust Service Lists (TSL) -- verifies signer certificate chains to trusted CAs (Python and TypeScript)","- TSL parser and cache with configurable TTL (24h default)","- Per-profile `tsl_url` field; EKENG profile uses the Armenian TSL","- Trust status display in GUI verify panel (trusted / not publicly trusted / not checked)","- Improved verify result formatting -- human-readable summary above technical details","","### Improved","","- Test coverage expanded to 95%+ (740 tests)","- New test modules: chain validation, LTV, ASN.1 BER edge cases, i18n","","### Fixed","","- CVE-2026-40192 (pillow), CVE-2025-71176 (pytest)","- CI: added `cryptography` and Python 3.10 backports to dev requirements","","## [1.1.1] - 2026-04-10","","### Improved","","- Updated Russian and Armenian translations","- Added English locale for i18n completeness","- Refined translatable strings across GUI modules","","## [1.1.0] - 2026-04-09","","### Added","","- **Localization**: Russian and Armenian language support for the GUI (gettext-based i18n)","- **Signature position preview**: mini page diagram in the sign form showing stamp placement","- **Password visibility toggle** in the login dialog","- **Friendly error messages**: raw HTTP/TLS errors mapped to user-readable messages in the GUI","- **ASCII credential warning**: warn when credentials contain non-Latin characters","- **Universal macOS binaries** (arm64 + x86_64) -- Intel Mac users can now install from the Mac App Store, Homebrew, and DMG","- Mac App Store badge in README","- `merge_universal.py` script for lipo-merging .app bundles and CLI binaries","- Translation consistency checker (`check_translations.py`) and .po compiler (`compile_translations.py`)","","### Changed","","- Homebrew formula no longer requires `arch: :arm64`","- Release pipeline builds on both `macos-latest` (arm64) and `macos-15-intel` (x86_64), merges with `lipo`","- Release artifacts renamed from `*-macos-arm64` to `*-macos-universal`","- Credential resolution moved off main thread to prevent UI freeze on macOS keychain prompts","- Platform-specific helpers (macOS menu bar, Windows icon) extracted to dedicated module","","### Fixed","","- Support for original EKENG cosign BER-encoded PDF signatures (indefinite-length CMS blobs) in both Python and TypeScript","- BER parser guards against deeply nested structures (depth limit) and oversized length fields","- npm publish step in release pipeline no longer silently swallows errors","- macOS notarization step properly fails on command errors instead of silently continuing","","## [1.0.0] - 2026-02-26","","### Added","","- **TypeScript/Node.js client** (`revenant-sign` on npm) -- full-featured port of the Python client with CLI, library API, and 662 tests at 97%+ coverage","  - Dual ESM + CJS build via tsup","  - Embedded and detached PDF signing","  - Signature verification (embedded and detached CMS/PKCS#7)","  - CMS blob inspection and certificate info extraction","  - Multi-profile configuration with keytar credential storage","  - Legacy TLS 1.0 + RC4 transport for EKENG (pure JS via node-forge)","  - Armenian font support (GHEA Grapalat, GHEA Mariam, Noto Sans)","  - Signature image embedding (PNG/JPEG) with alpha channel support","- Microsoft Store distribution (Revenant Sign) with Partner Center identity","- Release pipeline now includes MAS .pkg in GitHub Releases","","### Changed","","- MSIX package renamed to RevenantSign.msix, display name updated to \"Revenant Sign\"","","### Fixed","","- PDF incremental updates now use cross-reference streams for PDFs that use XRef streams (PDF 1.5+), per ISO 32000-1 S7.5.8.4 -- fixes compatibility with macOS Preview and strict PDF readers when signing pdf-lib or modern-tool-generated PDFs (both Python and TypeScript)","","## [0.2.6] - 2026-02-26","","### Changed","","- GUI window title simplified to \"Revenant\" (version removed to avoid screenshot staleness)","- MAS minimum deployment target bumped from macOS 11.0 to 12.0 (required for arm64-only builds)","","## [0.2.5] - 2026-02-26","","### Added","","- macOS menu bar with File (Close Window) and Edit (Undo, Cut, Copy, Paste, Select All) menus","- Cmd+W keyboard shortcut to close windows and dialogs on macOS","- Privacy policy URL in Info.plist (MAS Guideline 5.1.1)","","### Changed","","- PyPI classifier updated from Beta to Production/Stable (MAS Guideline 2.2)","","### Fixed","","- MAS build: switch from system Tk 8.6 to Homebrew Tk 9.0 to resolve private API rejection (Guideline 2.5.1 -- `_NSWindowDidOrderOnScreenNotification`)","- CI: extract Python+Tk setup and private API verification into reusable composite actions","- macOS Preferences menu item no longer shows default Tk behavior","","## [0.2.4] - 2026-02-21","","### Fixed","","- MAS build: use separate entitlements for nested executables to avoid ITMS-90885","","## [0.2.3] - 2026-02-21","","### Fixed","","- MAS build: add application identifier to sandbox entitlements (ITMS-90886)","","## [0.2.2] - 2026-02-21","","### Fixed","","- MAS build: embed provisioning profile for TestFlight and App Store submission (ITMS-90889)","","## [0.2.1] - 2026-02-20","","### Added","","- Linux ARM64 build support","- Automatic Homebrew tap update on release","","## [0.2.0] - 2026-02-16","","### Changed","","- **BREAKING:** Position preset names renamed to Y-first (CSS/UI) convention: `right-bottom` -\u003e `bottom-right`, `right-top` -\u003e `top-right`, `left-bottom` -\u003e `bottom-left`, `left-top` -\u003e `top-left`, `center-bottom` -\u003e `bottom-center`. Short aliases updated accordingly (`rb` -\u003e `br`, `rt` -\u003e `tr`, etc.)","- Windows release artifact renamed from `revenant-cli-windows-x64.zip` to `revenant-windows-x64.zip` (contains both CLI and GUI)","","### Fixed","","- MAS build: include sandbox entitlements when signing outer .app bundle","- Snap: use `adopt-info` for version instead of `git describe`","- Integration tests TLS setup for env-var-only credentials","","## [0.1.0] - 2026-02-14","","### Added","","- PDF digital signing via CoSign SAPI Web Service (SOAP/DSS)","- Embedded visible and invisible signature support with customizable appearance","- Armenian font support (GHEA Grapalat, GHEA Mariam, Noto Sans)","- Signature image embedding (PNG/JPEG) with alpha channel support","- PDF signature verification (embedded and detached CMS/PKCS#7)","- CMS blob inspection: digest algorithm, signer info, certificate chain","- Certificate information extraction from X.509 and CMS blobs","- Multi-profile configuration with per-profile credentials (keyring or config file)","- Automatic CoSign server discovery via DNS SRV records","- Legacy TLS 1.0 + RC4-MD5 transport for EKENG (ca.gov.am)","- Cross-platform GUI (tkinter) with sign, verify, and setup dialogs","- CLI with `sign`, `verify`, and `setup` commands","- Incremental PDF updates preserving existing signatures (re-signing support)","- Cross-reference stream PDF support (modern PDF 1.5+ format)","- Page size detection with CropBox/MediaBox/Rotate handling","- CI/CD workflows for linting, testing, and release builds","- Multi-platform release builds: macOS (.dmg), Windows (.msix, .zip), Linux (AppImage)","- Pyright strict mode type checking with 0 errors","- 96%+ test coverage (600+ tests)","","[Unreleased]: https://github.com/lobotomoe/revenant/compare/v4.0.0...HEAD","[4.0.0]: https://github.com/lobotomoe/revenant/compare/v3.0.5...v4.0.0","[3.0.5]: https://github.com/lobotomoe/revenant/compare/v3.0.4...v3.0.5","[3.0.4]: https://github.com/lobotomoe/revenant/compare/v3.0.3...v3.0.4","[3.0.3]: https://github.com/lobotomoe/revenant/compare/v3.0.2...v3.0.3","[3.0.2]: https://github.com/lobotomoe/revenant/compare/v3.0.1...v3.0.2","[3.0.1]: https://github.com/lobotomoe/revenant/compare/v3.0.0...v3.0.1","[3.0.0]: https://github.com/lobotomoe/revenant/compare/v2.1.2...v3.0.0","[2.1.2]: https://github.com/lobotomoe/revenant/compare/v2.1.1...v2.1.2","[2.1.1]: https://github.com/lobotomoe/revenant/compare/v2.1.0...v2.1.1","[2.1.0]: https://github.com/lobotomoe/revenant/compare/v2.0.0...v2.1.0","[2.0.0]: https://github.com/lobotomoe/revenant/compare/v1.2.1...v2.0.0","[1.2.1]: https://github.com/lobotomoe/revenant/compare/v1.2.0...v1.2.1","[1.2.0]: https://github.com/lobotomoe/revenant/compare/v1.1.1...v1.2.0","[1.1.1]: https://github.com/lobotomoe/revenant/compare/v1.1.0...v1.1.1","[1.1.0]: https://github.com/lobotomoe/revenant/compare/v1.0.0...v1.1.0","[1.0.0]: https://github.com/lobotomoe/revenant/compare/v0.2.6...v1.0.0","[0.2.6]: https://github.com/lobotomoe/revenant/compare/v0.2.5...v0.2.6","[0.2.5]: https://github.com/lobotomoe/revenant/compare/v0.2.4...v0.2.5","[0.2.4]: https://github.com/lobotomoe/revenant/compare/v0.2.3...v0.2.4","[0.2.3]: https://github.com/lobotomoe/revenant/compare/v0.2.2...v0.2.3","[0.2.2]: https://github.com/lobotomoe/revenant/compare/v0.2.1...v0.2.2","[0.2.1]: https://github.com/lobotomoe/revenant/compare/v0.2.0...v0.2.1","[0.2.0]: https://github.com/lobotomoe/revenant/compare/v0.1.0...v0.2.0","[0.1.0]: https://github.com/lobotomoe/revenant/releases/tag/v0.1.0"],"stylingDirectives":[[[0,11,"pl-mh"],[2,11,"pl-en"]],[],[],[],[[23,24,"pl-s"],[40,41,"pl-s"],[41,42,"pl-s"],[42,78,"pl-corl"],[78,79,"pl-s"]],[[28,29,"pl-s"],[48,49,"pl-s"],[49,50,"pl-s"],[50,85,"pl-corl"],[85,86,"pl-s"]],[],[[0,15,"pl-mh"],[3,15,"pl-en"],[3,4,"pl-s"],[14,15,"pl-s"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[],[],[],[],[[0,1,"pl-s"],[1,14,"pl-c1"],[14,15,"pl-s"],[19,20,"pl-s"],[20,38,"pl-c1"],[38,39,"pl-s"]],[[4,5,"pl-s"],[5,22,"pl-c1"],[22,23,"pl-s"]],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[52,54,"pl-s"]],[],[],[],[],[],[],[],[[22,25,"pl-s"],[23,25,"pl-corl"]],[[0,1,"pl-v"],[2,4,"pl-s"],[78,80,"pl-s"]],[],[[41,42,"pl-s"],[42,47,"pl-c1"],[47,48,"pl-s"]],[],[[10,11,"pl-s"],[11,34,"pl-c1"],[34,35,"pl-s"]],[[31,32,"pl-s"],[32,40,"pl-c1"],[40,41,"pl-s"]],[],[],[[0,12,"pl-mh"],[4,12,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[61,63,"pl-s"],[64,65,"pl-s"],[65,75,"pl-c1"],[75,76,"pl-s"]],[[60,61,"pl-s"],[61,68,"pl-c1"],[68,69,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[44,46,"pl-s"],[47,48,"pl-s"],[48,59,"pl-c1"],[59,60,"pl-s"]],[[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"]],[],[[33,34,"pl-s"],[34,44,"pl-c1"],[44,45,"pl-s"]],[],[[2,3,"pl-s"],[3,6,"pl-c1"],[6,7,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[69,71,"pl-s"]],[[8,9,"pl-s"],[9,17,"pl-c1"],[17,18,"pl-s"],[40,41,"pl-s"],[41,55,"pl-c1"],[55,56,"pl-s"]],[[27,28,"pl-s"],[28,43,"pl-c1"],[43,44,"pl-s"],[52,53,"pl-s"],[53,62,"pl-c1"],[62,63,"pl-s"]],[],[[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"]],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[4,5,"pl-s"],[5,18,"pl-c1"],[18,19,"pl-s"],[23,24,"pl-s"],[24,42,"pl-c1"],[42,43,"pl-s"],[51,52,"pl-s"],[52,69,"pl-c1"],[69,70,"pl-s"],[70,72,"pl-s"]],[[17,18,"pl-s"],[18,40,"pl-c1"],[40,41,"pl-s"]],[[2,3,"pl-s"],[3,25,"pl-c1"],[25,26,"pl-s"],[29,30,"pl-s"],[30,46,"pl-c1"],[46,47,"pl-s"]],[],[],[],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[4,5,"pl-s"],[5,38,"pl-c1"],[38,39,"pl-s"],[40,42,"pl-s"]],[],[],[[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"]],[],[],[[49,52,"pl-s"],[50,52,"pl-corl"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[],[],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[66,68,"pl-s"]],[],[],[],[],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[72,74,"pl-s"]],[],[],[],[],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[66,68,"pl-s"]],[],[],[[37,38,"pl-s"],[38,55,"pl-c1"],[55,56,"pl-s"]],[],[],[],[],[],[[0,12,"pl-mh"],[4,12,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[59,61,"pl-s"]],[[10,11,"pl-s"],[11,23,"pl-c1"],[23,24,"pl-s"]],[],[],[],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[],[],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[59,61,"pl-s"]],[],[],[[31,32,"pl-s"],[32,47,"pl-c1"],[47,48,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[58,60,"pl-s"]],[[10,11,"pl-s"],[11,44,"pl-c1"],[44,45,"pl-s"]],[[2,3,"pl-s"],[3,22,"pl-c1"],[22,23,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[70,72,"pl-s"]],[[40,41,"pl-s"],[41,49,"pl-c1"],[49,50,"pl-s"]],[],[],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[58,60,"pl-s"]],[],[],[],[[19,20,"pl-s"],[20,29,"pl-c1"],[29,30,"pl-s"]],[[12,13,"pl-s"],[13,23,"pl-c1"],[23,24,"pl-s"]],[],[],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[55,57,"pl-s"]],[],[],[[2,3,"pl-s"],[3,17,"pl-c1"],[17,18,"pl-s"],[47,48,"pl-s"],[48,54,"pl-c1"],[54,55,"pl-s"]],[],[],[[61,62,"pl-s"],[62,79,"pl-c1"],[79,80,"pl-s"]],[],[[6,7,"pl-s"],[7,17,"pl-c1"],[17,18,"pl-s"]],[[50,51,"pl-s"],[51,62,"pl-c1"],[62,63,"pl-s"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[61,63,"pl-s"]],[],[[2,3,"pl-s"],[11,12,"pl-s"]],[[40,41,"pl-s"],[41,63,"pl-c1"],[63,64,"pl-s"]],[[21,22,"pl-s"],[22,43,"pl-c1"],[43,44,"pl-s"]],[[44,45,"pl-s"],[45,53,"pl-c1"],[53,54,"pl-s"],[64,65,"pl-s"],[65,80,"pl-c1"],[80,81,"pl-s"]],[],[],[],[],[],[[2,3,"pl-s"],[3,31,"pl-c1"],[31,32,"pl-s"]],[[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[64,66,"pl-s"]],[],[],[],[[9,10,"pl-s"],[10,19,"pl-c1"],[19,20,"pl-s"]],[],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[63,65,"pl-s"]],[],[],[],[],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[16,18,"pl-s"]],[[14,15,"pl-s"],[15,26,"pl-c1"],[26,27,"pl-s"]],[[49,50,"pl-s"],[50,75,"pl-c1"],[75,76,"pl-s"]],[],[],[[64,65,"pl-s"],[65,74,"pl-c1"],[74,75,"pl-s"]],[[58,59,"pl-s"],[59,72,"pl-c1"],[72,73,"pl-s"]],[],[[24,25,"pl-s"],[25,30,"pl-c1"],[30,31,"pl-s"],[33,34,"pl-s"],[34,41,"pl-c1"],[41,42,"pl-s"],[47,48,"pl-s"],[48,63,"pl-c1"],[63,64,"pl-s"]],[],[],[],[],[[26,27,"pl-s"],[27,40,"pl-c1"],[40,41,"pl-s"]],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[],[],[],[],[[0,1,"pl-s"],[1,20,"pl-c1"],[20,21,"pl-s"],[40,41,"pl-s"],[41,50,"pl-c1"],[50,51,"pl-s"]],[[0,1,"pl-s"],[1,27,"pl-c1"],[27,28,"pl-s"]],[[24,25,"pl-s"],[25,40,"pl-c1"],[40,41,"pl-s"]],[],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[55,57,"pl-s"]],[],[],[],[],[[60,61,"pl-s"],[70,71,"pl-s"]],[],[],[],[],[[22,23,"pl-s"],[23,32,"pl-c1"],[32,33,"pl-s"],[40,41,"pl-s"],[41,54,"pl-c1"],[54,55,"pl-s"]],[],[],[],[],[[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"]],[[2,3,"pl-s"],[3,17,"pl-c1"],[17,18,"pl-s"],[37,38,"pl-s"],[38,47,"pl-c1"],[47,48,"pl-s"]],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[64,66,"pl-s"]],[],[],[[2,3,"pl-s"],[3,17,"pl-c1"],[17,18,"pl-s"],[53,54,"pl-s"],[54,60,"pl-c1"],[60,61,"pl-s"]],[[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[23,24,"pl-s"],[24,44,"pl-c1"],[44,45,"pl-s"]],[],[[60,61,"pl-s"],[61,64,"pl-c1"],[64,65,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[2,3,"pl-s"],[3,23,"pl-c1"],[23,24,"pl-s"],[66,68,"pl-s"]],[],[[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"],[22,23,"pl-s"],[23,38,"pl-c1"],[38,39,"pl-s"],[51,52,"pl-s"],[52,53,"pl-c1"],[53,54,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[63,65,"pl-s"]],[],[],[],[],[],[],[],[[0,1,"pl-v"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[67,69,"pl-s"]],[[2,12,"pl-s"],[2,10,"pl-corl"],[11,12,"pl-corl"]],[],[],[[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"],[16,17,"pl-s"],[17,34,"pl-c1"],[34,35,"pl-s"]],[],[],[[2,3,"pl-s"],[3,23,"pl-c1"],[23,24,"pl-s"],[26,27,"pl-s"],[27,57,"pl-c1"],[57,58,"pl-s"]],[],[[17,18,"pl-s"],[18,34,"pl-c1"],[34,35,"pl-s"]],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[17,19,"pl-s"],[62,63,"pl-s"],[63,76,"pl-c1"],[76,77,"pl-s"]],[[28,29,"pl-s"],[29,42,"pl-c1"],[42,43,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[10,11,"pl-s"],[11,32,"pl-c1"],[32,33,"pl-s"]],[[26,28,"pl-s"],[44,45,"pl-s"],[45,57,"pl-c1"],[57,58,"pl-s"]],[],[[5,6,"pl-s"],[6,17,"pl-c1"],[17,18,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[4,5,"pl-s"],[5,14,"pl-c1"],[14,15,"pl-s"],[47,49,"pl-s"]],[],[],[],[],[],[],[],[[33,34,"pl-s"],[34,43,"pl-c1"],[43,44,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"]],[[12,14,"pl-s"],[15,16,"pl-s"],[16,34,"pl-c1"],[34,35,"pl-s"]],[],[],[],[[67,68,"pl-s"],[68,72,"pl-c1"],[72,73,"pl-s"]],[[48,49,"pl-s"],[49,56,"pl-c1"],[56,57,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[6,8,"pl-s"]],[],[],[],[[30,31,"pl-s"],[31,39,"pl-c1"],[39,40,"pl-s"]],[[16,17,"pl-s"],[17,37,"pl-c1"],[37,38,"pl-s"]],[[71,72,"pl-s"],[72,77,"pl-c1"],[77,78,"pl-s"]],[],[],[],[],[],[],[],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[37,39,"pl-s"],[40,41,"pl-s"],[41,59,"pl-c1"],[59,60,"pl-s"]],[[2,3,"pl-s"],[3,18,"pl-c1"],[18,19,"pl-s"],[22,23,"pl-s"],[23,37,"pl-c1"],[37,38,"pl-s"],[40,41,"pl-s"],[41,45,"pl-c1"],[45,46,"pl-s"],[48,49,"pl-s"],[49,54,"pl-c1"],[54,55,"pl-s"],[60,61,"pl-s"],[61,65,"pl-c1"],[65,66,"pl-s"]],[[44,45,"pl-s"],[45,50,"pl-c1"],[50,51,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[37,39,"pl-s"],[40,41,"pl-s"],[41,47,"pl-c1"],[47,48,"pl-s"],[52,53,"pl-s"],[53,57,"pl-c1"],[57,58,"pl-s"]],[],[],[[0,1,"pl-v"],[11,17,"pl-s"],[11,15,"pl-corl"],[16,17,"pl-corl"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[37,39,"pl-s"]],[],[],[],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[52,54,"pl-s"]],[[25,26,"pl-s"],[26,36,"pl-c1"],[36,37,"pl-s"]],[],[[2,3,"pl-s"],[3,20,"pl-c1"],[20,21,"pl-s"],[23,24,"pl-s"],[24,37,"pl-c1"],[37,38,"pl-s"],[43,44,"pl-s"],[44,55,"pl-c1"],[55,56,"pl-s"]],[[68,69,"pl-s"],[71,72,"pl-s"]],[],[],[],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[50,52,"pl-s"]],[[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"]],[],[],[],[[5,6,"pl-s"],[6,19,"pl-c1"],[19,20,"pl-s"]],[],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[66,68,"pl-s"],[263,264,"pl-s"],[264,289,"pl-c1"],[289,290,"pl-s"],[301,302,"pl-s"],[302,338,"pl-c1"],[338,339,"pl-s"]],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[76,78,"pl-s"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[14,16,"pl-s"]],[[20,21,"pl-s"],[21,30,"pl-c1"],[30,31,"pl-s"],[44,45,"pl-s"],[45,52,"pl-c1"],[52,53,"pl-s"],[55,56,"pl-s"],[56,59,"pl-c1"],[59,60,"pl-s"],[62,63,"pl-s"],[63,66,"pl-c1"],[66,67,"pl-s"]],[[2,3,"pl-s"],[3,19,"pl-c1"],[19,20,"pl-s"],[32,33,"pl-s"],[33,42,"pl-c1"],[42,43,"pl-s"]],[],[],[],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[77,79,"pl-s"]],[],[[2,3,"pl-s"],[3,40,"pl-c1"],[40,41,"pl-s"]],[[10,11,"pl-s"],[11,16,"pl-c1"],[16,17,"pl-s"]],[],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[32,33,"pl-s"],[33,41,"pl-c1"],[41,42,"pl-s"],[74,76,"pl-s"]],[],[],[[2,3,"pl-v"],[4,5,"pl-s"],[5,22,"pl-c1"],[22,23,"pl-s"]],[[38,39,"pl-s"],[39,48,"pl-c1"],[48,49,"pl-s"]],[[33,34,"pl-s"],[34,44,"pl-c1"],[44,45,"pl-s"],[48,49,"pl-s"],[49,59,"pl-c1"],[59,60,"pl-s"]],[[2,3,"pl-v"],[4,5,"pl-s"],[5,23,"pl-c1"],[23,24,"pl-s"]],[],[],[],[[2,3,"pl-v"],[4,5,"pl-s"],[5,18,"pl-c1"],[18,19,"pl-s"],[26,27,"pl-s"],[27,35,"pl-c1"],[35,36,"pl-s"],[45,46,"pl-s"],[46,50,"pl-c1"],[50,51,"pl-s"],[53,54,"pl-s"],[54,60,"pl-c1"],[60,61,"pl-s"],[63,64,"pl-s"],[64,69,"pl-c1"],[69,70,"pl-s"],[72,73,"pl-s"],[73,77,"pl-c1"],[77,78,"pl-s"]],[[4,5,"pl-s"],[5,9,"pl-c1"],[9,10,"pl-s"],[12,13,"pl-s"],[13,18,"pl-c1"],[18,19,"pl-s"],[21,22,"pl-s"],[22,28,"pl-c1"],[28,29,"pl-s"],[31,32,"pl-s"],[32,37,"pl-c1"],[37,38,"pl-s"],[63,64,"pl-s"],[64,82,"pl-c1"],[82,83,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[75,77,"pl-s"]],[],[],[],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"]],[[68,70,"pl-s"]],[[18,19,"pl-s"],[19,37,"pl-c1"],[37,38,"pl-s"],[40,41,"pl-s"],[41,58,"pl-c1"],[58,59,"pl-s"]],[[12,13,"pl-s"],[13,26,"pl-c1"],[26,27,"pl-s"],[29,30,"pl-s"],[30,47,"pl-c1"],[47,48,"pl-s"]],[[6,7,"pl-s"],[7,16,"pl-c1"],[16,17,"pl-s"]],[[31,32,"pl-s"],[32,48,"pl-c1"],[48,49,"pl-s"]],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[70,72,"pl-s"]],[],[[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"],[26,27,"pl-s"],[27,34,"pl-c1"],[34,35,"pl-s"],[37,38,"pl-s"],[38,41,"pl-c1"],[41,42,"pl-s"],[44,45,"pl-s"],[45,48,"pl-c1"],[48,49,"pl-s"],[51,52,"pl-s"],[52,68,"pl-c1"],[68,69,"pl-s"]],[[9,10,"pl-s"],[10,19,"pl-c1"],[19,20,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[10,11,"pl-s"],[11,26,"pl-c1"],[26,27,"pl-s"],[73,75,"pl-s"]],[],[[38,39,"pl-s"],[39,44,"pl-c1"],[44,45,"pl-s"]],[[2,3,"pl-s"],[3,10,"pl-c1"],[10,11,"pl-s"]],[[36,37,"pl-s"],[37,44,"pl-c1"],[44,45,"pl-s"],[60,61,"pl-s"],[61,68,"pl-c1"],[68,69,"pl-s"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[57,58,"pl-s"],[58,63,"pl-c1"],[63,64,"pl-s"]],[[12,13,"pl-s"],[13,24,"pl-c1"],[24,25,"pl-s"]],[[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"],[16,18,"pl-s"]],[[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[25,26,"pl-s"],[26,46,"pl-c1"],[46,47,"pl-s"]],[],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[45,46,"pl-s"],[53,54,"pl-s"]],[[65,66,"pl-s"],[66,78,"pl-c1"],[78,79,"pl-s"]],[[14,15,"pl-s"],[15,22,"pl-c1"],[22,23,"pl-s"],[23,25,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[73,75,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[51,53,"pl-s"]],[[2,3,"pl-v"]],[[4,5,"pl-s"],[5,27,"pl-c1"],[27,28,"pl-s"]],[[2,3,"pl-v"],[4,5,"pl-s"],[5,14,"pl-c1"],[14,15,"pl-s"]],[],[],[[2,3,"pl-v"],[16,17,"pl-s"],[17,20,"pl-c1"],[20,21,"pl-s"]],[[22,23,"pl-s"],[23,26,"pl-c1"],[26,27,"pl-s"]],[],[[2,3,"pl-v"]],[],[[2,3,"pl-v"]],[],[],[[2,3,"pl-v"]],[],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,52,"pl-mh"],[4,52,"pl-en"]],[],[[0,12,"pl-mh"],[4,12,"pl-en"]],[],[[0,1,"pl-v"],[33,34,"pl-s"],[34,47,"pl-c1"],[47,48,"pl-s"]],[[2,3,"pl-s"],[3,16,"pl-c1"],[16,17,"pl-s"]],[],[[2,3,"pl-v"],[4,5,"pl-s"],[5,20,"pl-c1"],[20,21,"pl-s"],[24,25,"pl-s"],[25,53,"pl-c1"],[53,54,"pl-s"]],[[2,3,"pl-v"],[4,5,"pl-s"],[5,20,"pl-c1"],[20,21,"pl-s"],[24,25,"pl-s"],[25,53,"pl-c1"],[53,54,"pl-s"]],[[2,3,"pl-v"],[4,5,"pl-s"],[5,13,"pl-c1"],[13,14,"pl-s"],[17,18,"pl-s"],[18,39,"pl-c1"],[39,40,"pl-s"]],[[2,3,"pl-v"],[4,5,"pl-s"],[5,13,"pl-c1"],[13,14,"pl-s"],[17,18,"pl-s"],[18,39,"pl-c1"],[39,40,"pl-s"]],[],[[6,7,"pl-s"],[7,23,"pl-c1"],[23,24,"pl-s"],[49,50,"pl-s"],[50,68,"pl-c1"],[68,69,"pl-s"]],[[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"],[24,25,"pl-s"],[25,50,"pl-c1"],[50,51,"pl-s"]],[],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[15,16,"pl-s"],[16,65,"pl-c1"],[65,66,"pl-s"],[66,68,"pl-s"]],[],[],[[51,52,"pl-s"],[52,72,"pl-c1"],[72,73,"pl-s"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[15,16,"pl-s"],[16,65,"pl-c1"],[65,66,"pl-s"],[66,68,"pl-s"]],[],[],[[0,1,"pl-v"],[2,4,"pl-s"],[15,16,"pl-s"],[16,57,"pl-c1"],[57,58,"pl-s"],[58,60,"pl-s"]],[[25,26,"pl-s"],[26,34,"pl-c1"],[34,35,"pl-s"]],[[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"],[15,16,"pl-s"],[16,24,"pl-c1"],[24,25,"pl-s"],[41,42,"pl-s"],[42,59,"pl-c1"],[59,60,"pl-s"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[15,16,"pl-s"],[16,63,"pl-c1"],[63,64,"pl-s"],[64,66,"pl-s"]],[[20,21,"pl-s"],[21,32,"pl-c1"],[32,33,"pl-s"]],[[2,3,"pl-s"],[3,12,"pl-c1"],[12,13,"pl-s"]],[],[],[[0,1,"pl-v"],[2,3,"pl-s"],[3,11,"pl-c1"],[11,12,"pl-s"],[52,53,"pl-s"],[53,66,"pl-c1"],[66,67,"pl-s"]],[],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[24,26,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"],[14,15,"pl-s"],[15,22,"pl-c1"],[22,23,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,12,"pl-mh"],[4,12,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,10,"pl-s"],[6,10,"pl-corl"],[27,35,"pl-s"],[31,35,"pl-corl"]],[[0,1,"pl-v"],[12,13,"pl-s"],[13,25,"pl-c1"],[25,26,"pl-s"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,12,"pl-mh"],[4,12,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[16,18,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[30,32,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[30,32,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[27,29,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[28,30,"pl-s"]],[[0,1,"pl-v"],[2,4,"pl-s"],[28,30,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"],[2,3,"pl-s"],[3,21,"pl-c1"],[21,22,"pl-s"]],[[0,1,"pl-v"],[35,36,"pl-s"],[36,57,"pl-c1"],[57,58,"pl-s"],[78,79,"pl-s"],[79,102,"pl-c1"],[102,103,"pl-s"]],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[38,39,"pl-s"],[39,51,"pl-c1"],[51,52,"pl-s"]],[[0,1,"pl-v"],[34,35,"pl-s"],[35,47,"pl-c1"],[47,48,"pl-s"],[61,62,"pl-s"],[62,76,"pl-c1"],[76,77,"pl-s"],[100,101,"pl-s"],[101,105,"pl-c1"],[105,106,"pl-s"]],[[0,1,"pl-v"],[33,34,"pl-s"],[34,47,"pl-c1"],[47,48,"pl-s"],[52,53,"pl-s"],[53,70,"pl-c1"],[70,71,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[29,31,"pl-s"],[33,34,"pl-s"],[34,47,"pl-c1"],[47,48,"pl-s"]],[[2,3,"pl-v"]],[[2,3,"pl-v"]],[[2,3,"pl-v"],[50,60,"pl-s"],[50,58,"pl-corl"],[59,60,"pl-corl"]],[[2,3,"pl-v"]],[[2,3,"pl-v"]],[[2,3,"pl-v"]],[[2,3,"pl-v"]],[[2,3,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"],[111,112,"pl-s"],[112,149,"pl-c1"],[149,150,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,11,"pl-mh"],[4,11,"pl-en"]],[],[[0,1,"pl-v"],[2,4,"pl-s"],[13,15,"pl-s"],[78,79,"pl-s"],[79,91,"pl-c1"],[91,92,"pl-s"],[96,97,"pl-s"],[97,109,"pl-c1"],[109,110,"pl-s"],[112,113,"pl-s"],[113,122,"pl-c1"],[122,123,"pl-s"],[127,128,"pl-s"],[128,137,"pl-c1"],[137,138,"pl-s"],[140,141,"pl-s"],[141,152,"pl-c1"],[152,153,"pl-s"],[157,158,"pl-s"],[158,169,"pl-c1"],[169,170,"pl-s"],[172,173,"pl-s"],[173,181,"pl-c1"],[181,182,"pl-s"],[186,187,"pl-s"],[187,195,"pl-c1"],[195,196,"pl-s"],[198,199,"pl-s"],[199,212,"pl-c1"],[212,213,"pl-s"],[217,218,"pl-s"],[218,231,"pl-c1"],[231,232,"pl-s"],[269,270,"pl-s"],[270,272,"pl-c1"],[272,273,"pl-s"],[277,278,"pl-s"],[278,280,"pl-c1"],[280,281,"pl-s"],[283,284,"pl-s"],[284,286,"pl-c1"],[286,287,"pl-s"],[291,292,"pl-s"],[292,294,"pl-c1"],[294,295,"pl-s"]],[[0,1,"pl-v"],[40,41,"pl-s"],[41,69,"pl-c1"],[69,70,"pl-s"],[74,75,"pl-s"],[75,99,"pl-c1"],[99,100,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"],[12,13,"pl-s"],[13,23,"pl-c1"],[23,24,"pl-s"],[48,49,"pl-s"],[49,61,"pl-c1"],[61,62,"pl-s"]],[[0,1,"pl-v"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"],[3,4,"pl-s"],[9,10,"pl-s"]],[],[[0,9,"pl-mh"],[4,9,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"],[52,62,"pl-s"],[52,60,"pl-corl"],[61,62,"pl-corl"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"],[11,12,"pl-s"],[12,16,"pl-c1"],[16,17,"pl-s"],[19,20,"pl-s"],[20,26,"pl-c1"],[26,27,"pl-s"],[33,34,"pl-s"],[34,39,"pl-c1"],[39,40,"pl-s"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,1,"pl-s"],[1,11,"pl-en"],[11,12,"pl-s"],[14,73,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,70,"pl-corl"]],[[0,1,"pl-s"],[1,6,"pl-en"],[6,7,"pl-s"],[9,66,"pl-corl"]]],"colorizedLines":null}},"title":"revenant/CHANGELOG.md at main · lobotomoe/revenant","appPayload":{},"meta":{"title":"revenant/CHANGELOG.md at main · lobotomoe/revenant"}}</script>
  <div data-target="react-app.reactRoot"><meta name="github-code-view-meta-stats" id="github-code-view-meta-stats" data-hydrostats="publish"/> <!-- --> <a hidden="" id="code-view-repo-link" href="/lobotomoe/revenant" data-discover="true"></a> <div class="d-none"></div><div><div style="--spacing:var(--spacing-none)" class="prc-PageLayout-PageLayoutRoot--KH-d" data-component="SplitPageLayout" data-has-sidebar="true"><div class="prc-PageLayout-SidebarWrapper-kLG4B CopilotSidePanelSidebar-module__SidePanel__L3O0C CopilotSidePanelSidebar-module__HiddenSidePanel__TBRGn" style="--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="end" data-sticky="true" data-responsive-variant="fullscreen"><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-SidebarVerticalDivider-0Rl0V" data-component="PageLayout.VerticalDivider" data-variant="line" data-position="end" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="450" aria-valuemax="768" aria-valuenow="544" aria-valuetext="Pane width 544 pixels" tabindex="0"></div></div><div class="prc-PageLayout-Sidebar-iciWg" data-component="SplitPageLayout.Sidebar" data-resizable="true" style="--spacing:var(--spacing-normal);--pane-min-width:450px;--pane-max-width:768px;--pane-width-custom:544px;--pane-width-size:var(--pane-width-custom);--pane-width:544px"><div class="height-full" data-testid="copilot-code-view-side-panel"><div id="copilot-side-panel-content" class="height-full"></div></div></div></div><div class="prc-PageLayout-PageLayoutWrapper-2BhU2" data-width="full"><div class="prc-PageLayout-PageLayoutContent-BneH9"><div id="repos-file-tree-sidebar" class="CodeViewFileTreeLayout-module__sidebar__n_Aau" tabindex="0"><div class="prc-PageLayout-PaneWrapper-pHPop ReposFileTreePane-module__Pane__rBZpI ReposFileTreePane-module__HideTree__AYZnm ReposFileTreePane-module__HidePane__VHAVt" style="--offset-header:0px;--spacing-row:var(--spacing-none);--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="start" data-sticky="true"><div class="prc-PageLayout-HorizontalDivider-JLVqp prc-PageLayout-PaneHorizontalDivider-9tbnE" data-component="PageLayout.HorizontalDivider" data-variant-regular="none" data-variant-narrow="none" data-position="start" style="--spacing-divider:var(--spacing-none);--spacing:var(--spacing-none)"></div><div class="prc-PageLayout-Pane-AyzHK" data-component="SplitPageLayout.Pane" data-resizable="true" style="--spacing:var(--spacing-none);--pane-min-width:256px;--pane-max-width:calc(100vw - var(--pane-max-width-diff));--pane-width-size:var(--pane-width-large);--pane-width:320px"></div><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-PaneVerticalDivider-le57g" data-component="PageLayout.VerticalDivider" data-variant-narrow="none" data-variant-regular="line" data-variant-wide="line" data-position="start" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="256" aria-valuemax="600" aria-valuenow="320" aria-valuetext="Pane width 320 pixels" tabindex="0"></div></div></div></div><div data-component="SplitPageLayout.Content" class="prc-PageLayout-ContentWrapper-gR9eG"><div class="prc-PageLayout-Content-xWL-A" data-width="full" style="--spacing:var(--spacing-none)"><div class="SharedPageLayout-module__content__IwGAp" data-selector="repos-split-pane-content" id="repos-split-pane-content" tabindex="0"> <!-- --> <div class="container CodeViewHeader-module__Box__JkPOb"><div class="CodeViewHeader-module__StickyHeader__Qn7UN" id="StickyHeader"><div class="CodeViewHeader-module__Box_1__SbNDV"><div class="CodeViewHeader-module__Box_2__TB46f"><div class="react-code-view-header-wrap--narrow CodeViewHeader-module__Box_3__q1zUL"><div class="CodeViewHeader-module__treeToggleWrapper__RQ__9"><h2 class="use-tree-pane-module__Heading__s4QbZ prc-Heading-Heading-MtWFE" data-component="Heading"><button data-component="Button" type="button" aria-label="Expand file tree" data-testid="expand-file-tree-button-mobile" class="prc-Button-ButtonBase-9n-Xk ExpandFileTreeButton-module__Button_1__Svs95" data-loading="false" data-size="medium" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-left" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.78 12.53a.75.75 0 0 1-1.06 0L2.47 8.28a.75.75 0 0 1 0-1.06l4.25-4.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L4.81 7h7.44a.75.75 0 0 1 0 1.5H4.81l2.97 2.97a.75.75 0 0 1 0 1.06Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Files</span></span></button><button data-component="IconButton" type="button" data-testid="expand-file-tree-button" aria-controls="repos-file-tree" class="prc-Button-ButtonBase-9n-Xk position-relative ExpandFileTreeButton-module__expandButton__hDOcv ExpandFileTreeButton-module__filesButtonBreakpoint__zEvz3 fgColor-muted prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="invisible" aria-labelledby="_R_4lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sidebar-collapse" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.823 7.823a.25.25 0 0 1 0 .354l-2.396 2.396A.25.25 0 0 1 4 10.396V5.604a.25.25 0 0 1 .427-.177Z"></path><path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25H9.5v-13H1.75a.25.25 0 0 0-.25.25ZM11 14.5h3.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H11Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="se" data-component="Tooltip" aria-hidden="true" id="_R_4lla9lik5_">Expand file tree</span><div class="d-none"></div></h2></div><div class="react-code-view-header-mb--narrow mr-2"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector-wide"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="react-code-view-header-mb--narrow CodeViewHeader-module__Box_5__MQ0hL"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__lg__Rjz0A"><nav data-testid="breadcrumbs" aria-labelledby="repos-header-breadcrumb-heading" id="repos-header-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="repos-header-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/lobotomoe/revenant/tree/main" data-discover="true">revenant</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__lg__Rjz0A" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__lg__Rjz0A prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="file-name-id">CHANGELOG.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lla9lik5_">Copy path</span></div></div></div><div class="react-code-view-header-element--wide"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><div><div class="CodeViewHeader-module__FileResultsList__JDzUy"><span class="d-flex FileResultsList-module__FilesSearchBox__ivVkc TextInput-wrapper prc-components-TextInputWrapper-Hpdqi prc-components-TextInputBaseWrapper-wY-n0" data-no-trailing-action="true" data-component="TextInput" data-leading-visual="true" data-trailing-visual="true" aria-busy="false"><span class="TextInput-icon" id="_R_1cpla9lik5_" aria-hidden="true" data-component="TextInput.LeadingVisual"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg></span><input type="text" aria-label="Go to file" role="combobox" aria-controls="file-results-list" aria-expanded="false" aria-haspopup="dialog" autoCorrect="off" spellCheck="false" placeholder="Go to file" aria-describedby="_R_1cpla9lik5_ _R_1cpla9lik5H1_" data-component="input" class="prc-components-Input-IwWrt" value=""/><span class="TextInput-icon" id="_R_1cpla9lik5H1_" aria-hidden="true" data-component="TextInput.TrailingVisual"></span></span></div><div class="d-none"></div></div><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-wide" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7p9la9lik5_" id="_R_99la9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7p9la9lik5_">More file actions</span></div></div></div><div class="react-code-view-header-element--narrow"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-narrow" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7pdla9lik5_" id="_R_9dla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7pdla9lik5_">More file actions</span></div></div></div></div></div></div></div><div class="CodeView-module__contentWrapper__cG2JH"><div class="react-code-view-bottom-padding"><div class="BlobTopBanners-module__Box__v_nvx"></div></div> <div class="d-none"></div><div class="d-flex flex-column border rounded-2 tmp-mb-3 pl-1"><div class="LatestCommit-module__Box__B25ZT"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">Latest commit</h2><div style="width:120px" class="Skeleton Skeleton--text" data-testid="loading"> </div><div class="d-flex flex-shrink-0 gap-2"><div data-testid="latest-commit-details" class="d-none d-sm-flex flex-items-center"></div><div class="d-flex gap-2"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">History</h2><a data-component="LinkButton" href="/lobotomoe/revenant/commits/main/CHANGELOG.md" class="prc-Button-ButtonBase-9n-Xk d-none d-lg-flex LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><span class="fgColor-default">History</span></span></span></a><div class="d-sm-none"></div><div class="d-flex d-lg-none"><a data-component="LinkButton" aria-label="View commit history for this file." href="/lobotomoe/revenant/commits/main/CHANGELOG.md" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible" aria-describedby="_R_4mlala9lik5_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span></span></a><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" role="tooltip" aria-hidden="true" id="_R_4mlala9lik5_">History</span></div></div></div></div></div><div class="d-flex flex-row"><div class="container BlobViewContent-module__blobContainer__DtH2d"><div class="react-code-size-details-banner BlobViewContent-module__codeSizeDetails__e5sUw"><div class="react-code-size-details-banner CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="39.9 KB" style="--truncate-max-width:100%"><span>737 lines (610 loc) · 39.9 KB</span></div></div></div></div><div class="react-blob-view-header-sticky BlobViewContent-module__stickyHeader__VwxB5" id="repos-sticky-header"><div class="BlobViewHeader-module__Box__yhm9u"><div class="react-blob-sticky-header"><div class="FileNameStickyHeader-module__outerWrapper__ZL4Xc FileNameStickyHeader-module__outerWrapperHidden__Zpynk"><div class="FileNameStickyHeader-module__Box_1__Hazu5"><div class="FileNameStickyHeader-module__Box_2__hoolP"><div class="FileNameStickyHeader-module__Box_3__MVKsk"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="FileNameStickyHeader-module__Box_4__FLhtt"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__md__Wb1Gs"><nav data-testid="breadcrumbs" aria-labelledby="sticky-breadcrumb-heading" id="sticky-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="sticky-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/lobotomoe/revenant/tree/main" data-discover="true">revenant</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__md__Wb1Gs" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__md__Wb1Gs prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="sticky-file-name-id">CHANGELOG.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lcpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lcpala9lik5_">Copy path</span></div></div></div><button data-component="Button" type="button" class="prc-Button-ButtonBase-9n-Xk FileNameStickyHeader-module__Button__LSEU_ FileNameStickyHeader-module__GoToTopButton__nxAFn" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-up" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.47 7.78a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018L9 4.81v7.44a.75.75 0 0 1-1.5 0V4.81L4.53 7.78a.75.75 0 0 1-1.06 0Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Top</span></span></button></div></div></div><div class="BlobViewHeader-module__Box_1__VEmuQ"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">File metadata and controls</h2><div class="BlobViewHeader-module__Box_2__icUs2"><ul aria-label="File view" class="prc-SegmentedControl-SegmentedControl-lqIXp BlobTabButtons-module__SegmentedControl__jen2u" data-variant="default" data-size="small" data-component="SegmentedControl"><li class="prc-SegmentedControl-Item-tSCQh" data-selected="" data-component="SegmentedControl.Button"><button aria-pressed="true" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:transparent"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Preview">Preview</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Code">Code</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Blame">Blame</div></span></button></li></ul><div class="d-none"></div><div class="react-code-size-details-in-header CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="39.9 KB" style="--truncate-max-width:100%"><span>737 lines (610 loc) · 39.9 KB</span></div></div></div></div><div class="BlobViewHeader-module__Box_3__ng6v2"><div class="d-none"></div><div class="react-blob-header-edit-and-raw-actions BlobViewHeader-module__Box_4__J4Y4W"><div class="d-none"></div><div class="prc-ButtonGroup-ButtonGroup-vFUrY" data-component="ButtonGroup"><div class="prc-ButtonGroup-Item-PqvDl"><a data-component="LinkButton" href="https://github.com/lobotomoe/revenant/raw/refs/heads/main/CHANGELOG.md" data-testid="raw-button" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov BlobViewHeader-module__LinkButton__X9kx2" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Raw</span></span></a></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="copy-raw-button" class="prc-Button-ButtonBase-9n-Xk prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_qaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_qaucpala9lik5_">Copy raw file</span></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="download-raw-button" class="prc-Button-ButtonBase-9n-Xk BlobViewHeader-module__downloadButton__ef459 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_eaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-download" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"></path><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_eaucpala9lik5_">Download raw file</span></div></div></div><button data-component="IconButton" type="button" aria-pressed="false" class="prc-Button-ButtonBase-9n-Xk tmp-mr-2 TableOfContents-module__IconButton__jrlNM prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_3ucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-list-unordered" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.75 2.5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5ZM2 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-6a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM2 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_3ucpala9lik5_">Outline</span><div class="react-blob-header-edit-and-raw-actions-combined"><button data-component="IconButton" type="button" title="More file actions" data-testid="more-file-actions-button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click BlobViewHeader-module__IconButton__XrMQY prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_fkecpala9lik5_" id="_R_kecpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_fkecpala9lik5_">Edit and raw actions</span></div></div></div></div><div></div></div><div class="BlobViewContent-module__blobContentWrapper__JS0W6"><section aria-labelledby="file-name-id-wide file-name-id-mobile" class="BlobContent-module__blobContentSection__VOgZq BlobContent-module__blobContentSectionMarkdown__mPLOK" style="margin-top:46px"><div class="js-snippet-clipboard-copy-unpositioned BlobContent-module__markdownBlob__T8jpG" data-hpc="true" containertiming="hpc"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 tabindex="-1" class="heading-element" dir="auto">Changelog</h1><a id="user-content-changelog" class="anchor" aria-label="Permalink: Changelog" href="#changelog"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">All notable changes to this project will be documented in this file.</p>
<p dir="auto">The format is based on <a href="https://keepachangelog.com/en/1.1.0/" rel="nofollow">Keep a Changelog</a>,
and this project adheres to <a href="https://semver.org/spec/v2.0.0.html" rel="nofollow">Semantic Versioning</a>.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v4.0.0...HEAD">Unreleased</a></h2><a id="user-content-unreleased" class="anchor" aria-label="Permalink: Unreleased" href="#unreleased"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v3.0.5...v4.0.0">4.0.0</a> - 2026-09-03</h2><a id="user-content-400---2026-09-03" class="anchor" aria-label="Permalink: 4.0.0 - 2026-09-03" href="#400---2026-09-03"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Two fixes -- logging in to a non-EKENG server, and the app icon on macOS -- plus
a dependency refresh that clears a withdrawn package and two advisories.</p>
<p dir="auto">The major bump is required by one breaking change in the Rust crates:
<code>ServerProfile</code> in <code>revenant-sign-core</code> gains a public field, and the struct is
now <code>#[non_exhaustive]</code>. The Python and TypeScript packages keep their public
surface, and nothing changes for users of the desktop app or the CLIs.</p>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed" class="anchor" aria-label="Permalink: Fixed" href="#fixed"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Custom servers now accept non-ASCII credentials.</strong> Both GUIs refused a
username or password containing any non-Latin character, on every profile.
The restriction describes EKENG, whose logins are Latin letters and digits,
but it was applied to custom CoSign deployments too -- blocking credentials
the SOAP envelope escapes and carries as UTF-8 without trouble, and that both
CLIs already accepted. It is now a profile capability, off for custom servers
and on for EKENG, where a non-ASCII entry is a keyboard-layout slip and
refusing it spends none of that profile's five-attempt lockout budget.
Reported by peyuaa (#76).</li>
<li><strong>The macOS Dock no longer shows a black egui logo in place of the app icon.</strong>
The desktop app deliberately left its runtime icon unset on macOS, so that the
Dock tile would stay with the bundle's <code>.icns</code>. eframe reads an unset icon the
other way round: it substitutes its own default egui artwork and pushes that
through <code>setApplicationIconImage</code>, taking over the tile for as long as the app
runs. It now passes an empty <code>IconData</code> -- egui's documented opt-out -- so the
installed icon survives launch.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Security</h3><a id="user-content-security" class="anchor" aria-label="Permalink: Security" href="#security"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>The desktop app no longer ships a browser-launching flaw.</strong> <code>webbrowser</code>,
which the GUI stack uses to open links, honoured the Unix <code>BROWSER</code>
environment variable in a way that allowed argument injection
(RUSTSEC-2026-0257). Updated to 1.2.4.</li>
<li><strong>Rust dependencies are now audited in CI.</strong> <code>cargo audit</code> runs beside
<code>pip-audit</code> in the required Security Audit job, so a known-vulnerable crate
fails the build instead of waiting for a weekly Scorecard alert nobody is
blocked on -- which is how the <code>webbrowser</code> advisory above went unnoticed.
One finding is ignored with a recorded reason and a re-evaluation trigger: the
<code>rsa</code> Marvin timing sidechannel, which has no released fix.</li>
<li><strong>The Rust lockfile is refreshed to the latest compatible releases.</strong> This
takes <code>chacha20</code> off a yanked 0.10.1, <code>event-listener</code> off the unsound 5.4.1
(RUSTSEC-2026-0221), and <code>wayland-scanner</code> onto a <code>quick-xml</code> 0.41 -- which
removes the last vulnerable copy of that crate from the tree and lets the two
<code>quick-xml</code> audit ignores go, arming that guard again.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed" class="anchor" aria-label="Permalink: Changed" href="#changed"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong><code>ServerProfile</code> in <code>revenant-sign-core</code> is now <code>#[non_exhaustive]</code></strong> and
gains a public <code>ascii_credentials_only</code> field. Profiles are meant to come from
<code>ServerProfile::builtin</code> / <code>::custom_default</code>, which fill in every capability
the deployment implies; code that constructed the struct literally must move
to those constructors. Marking it non-exhaustive is itself the breaking part,
and it is what keeps the next field addition off the major-version budget.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Removed</h3><a id="user-content-removed" class="anchor" aria-label="Permalink: Removed" href="#removed"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong><code>python/scripts/merge_universal.py</code>.</strong> The release workflow has lipo'd the
macOS universal binaries directly since the Rust pipeline landed, leaving the
script unreachable from every workflow and build script. Its fallback read a
<code>lipo -create</code> "same architectures" failure as proof that both inputs held
the same architecture set and copied the arm64 side, which for partially
overlapping inputs would have silently dropped the x86_64 slice. The live
path has no such fallback. Reported by peyuaa (#75).</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v3.0.4...v3.0.5">3.0.5</a> - 2026-08-18</h2><a id="user-content-305---2026-08-18" class="anchor" aria-label="Permalink: 3.0.5 - 2026-08-18" href="#305---2026-08-18"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Security release. Three fixes for background results being applied to state
that did not ask for them, one for the output path a signed file is written to,
and a hardened release pipeline.</p>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-1" class="anchor" aria-label="Permalink: Fixed" href="#fixed-1"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>A cancelled connection can no longer become the active server.</strong> Dismissing
the Connect dialog did not stop the server test already running, and neither
GUI checked, when the answer arrived, whether it was still wanted. A slow
endpoint could return success after the user backed out and be saved as the
active profile, so later credentials and signing requests went somewhere the
user had declined. Both GUIs now tag each attempt and drop a result that no
longer belongs to the dialog on screen. Reported by peyuaa; found during that
fix to affect the Python GUI as well.</li>
<li><strong>A saved password can no longer be pre-filled into a different login.</strong> The
Rust GUI read the saved password in the background and applied whatever came
back to whichever login was open, so cancelling one login and starting another
could put the first account's password into the second. The read is now bound
to the login and username that asked for it, and a password box the user has
typed in and cleared is never silently re-filled. The same fix covers identity
discovery, which could otherwise save one login's signer certificate under
another's. Reported by peyuaa.</li>
<li><strong>Signing no longer writes through a symlink at the output path.</strong> When a
sandbox forbids creating a temporary file, both the Python and Rust clients
fall back to writing the output directly. That fallback followed a symlink
left at a predictable output name (<code>&lt;stem&gt;_signed.pdf</code>) and truncated whatever
it pointed at, where the normal path replaces the link instead. The fallback
now refuses to follow links, refuses anything that is not a regular file, and
fails outright on platforms that cannot express either. Reported by peyuaa
against the Python client; the Rust CLI carried the same flaw.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Security</h3><a id="user-content-security-1" class="anchor" aria-label="Permalink: Security" href="#security-1"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>The AppImage packaging tool is now pinned and verified.</strong> The release job
fetched <code>appimagetool</code> from a mutable tag over plain HTTP(S) and marked it
executable without checking it, then used it to pack both published Linux
AppImages -- so whatever that URL served on release day ran inside the release
job and shipped to users. It is now pinned to a tagged release and checked
against a known digest before it is made executable. Reported by peyuaa.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v3.0.3...v3.0.4">3.0.4</a> - 2026-08-18</h2><a id="user-content-304---2026-08-18" class="anchor" aria-label="Permalink: 3.0.4 - 2026-08-18" href="#304---2026-08-18"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Security release. Three fixes in what the desktop apps report about a
signature. The command-line tools were not affected and their verdicts are
unchanged.</p>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-2" class="anchor" aria-label="Permalink: Fixed" href="#fixed-2"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>A tampered PDF is no longer shown as a valid signature.</strong> The Rust GUI took
its green verdict from the CMS signature alone. Altering bytes the ByteRange
covers while leaving the CMS object intact keeps that signature verifiable, so
a modified document rendered <code>Signature VALID</code> in green with a red integrity
failure beneath it, and named the embedded certificate as its signer. The
verdict is now the full result -- integrity and signature -- which is what the
core library already meant by valid, and what the CLI has always reported.
Reported by peyuaa.</li>
<li><strong>A failed signature no longer carries a trusted signer.</strong> The Python GUI
printed <code>Signature: Integrity check FAILED</code> directly above a green
<code>Trust: Trusted (CA)</code>, because the trust line read only the chain result. The
chain may genuinely be trusted; that is a fact about the certificate and not
about a document the signature does not cover. Signer, organization and trust
are now reported only for a signature that verified, on both the embedded and
detached paths. Reported by peyuaa.</li>
<li><strong>A certificate that is not yet valid is no longer styled as normal.</strong> The
Python GUI classified certificates by <code>notAfter</code> only, so one whose validity
period had not started showed the ordinary presentation and a reassuring
count of days remaining. It is now flagged like an expired certificate, since
neither can be used. Reported by peyuaa.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v3.0.2...v3.0.3">3.0.3</a> - 2026-08-17</h2><a id="user-content-303---2026-08-17" class="anchor" aria-label="Permalink: 3.0.3 - 2026-08-17" href="#303---2026-08-17"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Security release completing a fix that 3.0.2 landed in only one of the three
clients. Users of the Python package or the Rust crates should upgrade.</p>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-3" class="anchor" aria-label="Permalink: Fixed" href="#fixed-3"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>The BER size bound now covers Python and Rust as well.</strong> 3.0.2 fixed this
in the TypeScript client because the report named only the npm package, but
the flaw was identical in the other two: the 16 MiB ceiling sat in the
definite-length branch, past the point where an indefinite-length header
returns, and the <code>/Contents</code> gap was decoded to a string with no bound at all.
A crafted <code>/ByteRange</code> therefore allocated the whole gap before anything
rejected it. Both clients now refuse an oversized gap and an oversized input
up front, against the same shared ceiling, matching TypeScript. Originally
reported by peyuaa.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v3.0.1...v3.0.2">3.0.2</a> - 2026-08-16</h2><a id="user-content-302---2026-08-16" class="anchor" aria-label="Permalink: 3.0.2 - 2026-08-16" href="#302---2026-08-16"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Security release. Three reported vulnerabilities in signature verification,
plus a batch-signing fix in the desktop app. Upgrading is recommended.</p>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-4" class="anchor" aria-label="Permalink: Fixed" href="#fixed-4"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Certificate order in a CMS no longer confers trust.</strong> Chain validation began
from the first embedded certificate and signer identity was read from the same
one, while the signature itself was verified against the certificate named by
<code>SignerInfo.sid</code>. A CMS certificate set is a <code>SET OF</code>, so those need not be the
same certificate: signing detached content with an untrusted self-signed
certificate and listing a trusted root ahead of it made Revenant verify the
attacker's signature, walk the chain from the root, report <code>VALID and trusted</code>,
and display the root as the signer. All three now resolve the signer through
the <code>SignerInfo</code>, and only when exactly one embedded certificate matches --
an ambiguous set has not named a signer. Rust's <code>build_chain</code> takes the leaf as
a parameter instead of reading pool index 0. Reported by peyuaa.</li>
<li><strong>LTV status is no longer raised by data anyone can append.</strong> The status came
from the presence of a revocation-related OID and counted the signer's
<em>unsigned</em> attributes alongside the signed ones. Unsigned attributes are not
covered by the signature, so an Adobe <code>RevocationInfoArchival</code> attribute
holding the string <code>bogus-not-ocsp-or-crl</code> could be stapled onto a finished
document and reported as LTV enabled with <code>has_ocsp</code> set. The <code>SignedData.crls</code>
field is outside the signature for the same reason. Both are still reported,
now marked as present and not counted, and attribute values are decoded rather
than taken on the OID's word. This reports what a signature carries, not that
the revocation data was validated -- that check does not exist yet, and the
documentation says so. Python and TypeScript gain
<code>has_unauthenticated_material</code>; Rust reports the same distinction as a
<code>RevocationMaterial</code> enum. Rust was affected too, though the report named only
the Python and npm packages. Reported by peyuaa.</li>
<li><strong>A BER indefinite-length CMS is bounded before it is decoded.</strong> The 16 MiB
ceiling was checked against the length a definite-length header claims. An
indefinite-length blob claims nothing, so it was decoded whole and its extent
discovered only on reaching the end marker -- a crafted PDF allocated its
entire <code>/Contents</code> gap regardless of the limit. Both the extractor and the PDF
path now refuse an oversized gap up front, against the same exported ceiling.
Fixed here in the TypeScript client only, which is what the report named; the
Python and Rust clients carried the same flaw and are fixed in 3.0.3.
Reported by peyuaa.</li>
<li><strong>Batch signing no longer overwrites files it did not create.</strong> Every
destination is reserved before the first signing request and written with
exclusive creation, so a queued input can no longer be replaced by an earlier
file's output, and same-named inputs from different folders no longer collide.
Outputs that could not take their derived name are now reported, labelled by
full input path. Contributed by peyuaa.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v3.0.0...v3.0.1">3.0.1</a> - 2026-08-16</h2><a id="user-content-301---2026-08-16" class="anchor" aria-label="Permalink: 3.0.1 - 2026-08-16" href="#301---2026-08-16"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Security release. Fixes one vulnerability introduced in 3.0.0; upgrading is
recommended for everyone running that version.</p>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-5" class="anchor" aria-label="Permalink: Fixed" href="#fixed-5"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>A CMS signature without signed attributes no longer vouches for content it
never covered.</strong> RFC 5652 section 5.4 puts the signature over the content
itself when <code>signedAttrs</code> is absent, and 3.0.0 added support for that shape.
It resolved the content in the wrong order: an <code>encapContentInfo.eContent</code>
embedded in the blob was preferred over the bytes the caller asked about, so a
genuine attached signature over attacker-chosen bytes verified successfully
and the result was then reported as covering an unrelated PDF <code>ByteRange</code> or
detached payload. Because no signed attributes means no <code>messageDigest</code> to
re-check, that signature verdict was the entire integrity argument:
verification returned <code>valid</code>, <code>hash_ok</code> and <code>signature_valid</code> all true for a
document the signer never saw. The caller's bytes are now authoritative, and a
CMS that also embeds its own content is rejected as unverifiable unless the
two are byte-identical. Verifying an attached CMS on its own terms, by passing
no detached content, still works. Signatures that carry signed attributes were
never affected -- their <code>messageDigest</code> was always compared against the
caller's bytes.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v2.1.2...v3.0.0">3.0.0</a> - 2026-08-12</h2><a id="user-content-300---2026-08-12" class="anchor" aria-label="Permalink: 3.0.0 - 2026-08-12" href="#300---2026-08-12"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Security release. Every change below is a fix for a reported vulnerability;
upgrading is recommended for all users.</p>
<p dir="auto">The major bump is required by one breaking API change in the Rust crates:
<code>pki::validate_chain</code> no longer takes a <code>Transport</code>, and
<code>constants::MAX_AIA_FETCHES</code> is gone. The Python and TypeScript packages keep
their public surface -- <code>MAX_AIA_FETCHES</code> was never exported from either
entry point -- but their verification behaviour changes, as described below.</p>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Removed</h3><a id="user-content-removed-1" class="anchor" aria-label="Permalink: Removed" href="#removed-1"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Certificate chains are no longer completed by fetching the Authority
Information Access URLs printed inside a certificate.</strong> Those URLs travel
inside the document being verified, so following them let whoever produced a
file choose hosts for the verifying machine to contact -- opening a document
became a network callback that reports the reader's address and the moment
they opened it, and a certificate may carry any number of them. The per-chain
limit did not restrain this: it counted only fetches that <em>succeeded</em>, so
unreachable URLs were retried without bound, each costing a connection
attempt with retries. Issuers now come from the CMS blob, which is where
RFC 5652 has a signer put them, and from the configured trust store. A
signature whose CMS omits its own intermediates stops short of a trusted
anchor and reads as <code>untrusted</code> -- or <code>indeterminate</code>, if an anchor still
matched by name but the chain could not be verified -- instead of silently
reaching out for them. No built-in
profile is affected: EKENG's chain has no intermediates, and its certificates'
AIA URLs are plain HTTP, which the transport has always refused.
<code>MAX_AIA_FETCHES</code> is gone from all three implementations, and Rust's
<code>validate_chain</code> no longer takes a <code>Transport</code> -- chain validation now has no
way to reach the network at all.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-6" class="anchor" aria-label="Permalink: Fixed" href="#fixed-6"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Python/TypeScript: CMS verification now checks the cryptographic signer
signature, not only the container structure and signed digest.</strong> Unsupported,
malformed, or unverifiable signatures fail closed. Signer identity and
optional trust-chain validation now select the certificate named by
<code>SignerInfo.sid</code> instead of relying on certificate <code>SET OF</code> order, and an ESS
<code>signingCertificate</code>/<code>signingCertificateV2</code> attribute binds that identity to
the exact embedded certificate. TypeScript verifies directly with that
selected certificate instead of allowing PKIjs to resolve <code>sid</code> again, and
both implementations expose signer metadata only when matching ESS or a
trusted certificate chain authenticates it.</li>
<li><strong>Python: the signer public key is read from the certificate's
<code>SubjectPublicKeyInfo</code> instead of parsing the whole certificate.</strong> Production
signer certificates (EKENG/CoSign among them) encode DN attributes such as
<code>emailAddress</code> as a <code>PrintableString</code> containing <code>@</code>, which is outside that
type's character set; a strict X.509 parser rejects the entire certificate
over a field signature verification never reads, turning genuine signatures
into false negatives.</li>
<li><strong>Python and TypeScript now agree on the CMS signature input.</strong> Python hashed
the signed attributes re-encoded as canonical DER while TypeScript hashed only
the bytes as transmitted, so the same document could verify in one and fail in
the other. Both now try the as-transmitted encoding first and the canonical
DER re-encoding second. The two encodings carry the same parsed attributes,
which are validated independently before any signature is checked, so nothing
is loosened. Rust remains canonical-DER only and still rejects a signature
made over a non-DER transmitted ordering.</li>
<li>Failures that cannot be attributed to a specific check now name the
underlying error instead of reporting an opaque "CMS signature check failed".</li>
<li><strong>Signing workflows no longer return a signature nobody verified.</strong> The
CMS/PKCS#7 bytes a signing service sends back are now proven to be a genuine
signature over exactly the bytes that were submitted, before any workflow
reports success. A compromised or impersonated service could previously make
<code>sign_data</code> / <code>sign_pdf_detached</code> succeed while returning something no
conforming verifier would accept, and the embedded flow accepted a response
that merely hashed correctly. Failures raise a distinct
<code>SigningResponseError</code> (<code>RevenantError::SigningResponse</code> in Rust): the request
succeeded at the protocol level, so retrying is pointless and nothing is
saved. The raw <code>SigningTransport</code> methods remain unverified by design -- they
are the low-level primitive, and calling them directly is an explicit opt-out.</li>
<li><strong>Rust: knowing the hash that was submitted no longer stands in for checking
what came back.</strong> Post-sign verification treated a matching <code>expected_hash</code> as
proof and skipped the CMS <code>messageDigest</code> comparison entirely, so a response
carrying no digest at all could be reported as intact. Both are now required,
matching Python and TypeScript.</li>
<li><strong>Rust: <code>has_signed_attributes</code> distinguishes "the CMS says it has none" from
"the CMS did not parse".</strong> It now returns <code>Option&lt;bool&gt;</code>; previously a
corrupt blob borrowed the diagnostic wording of a legitimate
no-<code>signedAttrs</code> signature.</li>
<li><strong><code>sign_hash</code> now says what it actually does.</strong> It signs the hash bytes
themselves. What a service does with a submitted digest is service-defined and
observed to vary: some sign it as a pre-computed digest, others hash it again
and sign it as ordinary content -- and only the first kind yields a signature
that can be attached to the document the hash came from. The response is
checked to be a genuine signature, and a warning is logged naming both digests
when it does not bind the one submitted. The operation is not failed over
this: the caller asked for those bytes to be signed, and they were. To sign a
document, pass the document to <code>sign_data</code>.</li>
<li><strong>TypeScript: the signature image pixel budget is enforced before decoding,
not after.</strong> <code>loadSignatureImage</code> decoded the whole file and only then
compared the pixel count against its limit, by which point the decoder had
already allocated the full output: a 547 KB PNG declaring 12000x12000 drove
resident memory to 1.7 GB before the check ran, and the 5 MB file cap allows
an order of magnitude more. Dimensions are now read from the PNG <code>IHDR</code> chunk
and the JPEG frame header, and refused there; <code>jpeg-js</code> is additionally given
the same limit so that a frame the header scan did not reach cannot exceed it
either. Python and Rust already checked dimensions before decoding.</li>
<li><strong>Legacy TLS now authenticates the server, and is never selected on its
own.</strong> The TLS 1.0 + RC4 transport validated no certificate at all, and any
host could be pushed onto it: a standard-HTTPS failure -- including a failed
certificate check, the one signal that something is wrong -- made the client
retry over a transport that performs no certificate check. Two changes close
that. A profile now carries <code>tls_pins</code>, the SHA-256 of the server
certificate's <code>SubjectPublicKeyInfo</code>, checked the moment the certificate
arrives and before any request byte or premaster secret is sent; the <code>ekeng</code>
profile ships the key its appliance has presented since 2006. And the
transport no longer probes: a host reaches the legacy path only because a
profile declared it, and every other host is standard HTTPS or nothing.
Chain validation was never possible for these appliances -- they present a
self-signed factory certificate naming neither the host nor a checkable
authority -- so a pinned key is a stronger guarantee here than the public
PKI could have given.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-1" class="anchor" aria-label="Permalink: Changed" href="#changed-1"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Breaking (Python/TypeScript API):</strong> <code>VerificationResult</code> gains
<code>signature_valid</code> / <code>signatureValid</code> (<code>true</code>, <code>false</code>, or <code>null</code> when
verification could not be performed), and <code>valid</code> now additionally requires
that the signer signature verifies. Documents that earlier releases reported
as valid on digest agreement alone are now reported invalid.</li>
<li><strong>Breaking (Python/TypeScript API):</strong> <code>signer</code> is <code>null</code> unless a matching ESS
attribute or a trusted certificate chain authenticates the certificate.
Callers that displayed signer identity unconditionally must handle absence.</li>
<li>Only RSA PKCS#1 v1.5 signers are verified. ECDSA and RSASSA-PSS signers are
reported as unverifiable, and therefore invalid, rather than accepted.</li>
<li><strong>Consequence, stated deliberately:</strong> because signing workflows now refuse a
response they cannot verify, a signing service that switched to ECDSA or
RSASSA-PSS would fail signing outright rather than return an unverifiable
signature. That is the intended direction -- an unverifiable signature
returned under a success message is the defect being fixed -- but it means
algorithm support is now on the signing path, not only the verification path.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added" class="anchor" aria-label="Permalink: Added" href="#added"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Signature coverage is now measured and reported.</strong> A signature covers only
the bytes named by its <code>/ByteRange</code>; anything after it belongs to a later
incremental revision that the signature does not protect. Results gain
<code>covers_whole_file</code>, <code>covered_bytes</code> and <code>total_bytes</code>, and each signature
reports whether it covers the whole file or only part of it. When <em>no</em>
signature in a document reaches the end of the file, the trailing region is
signed by nobody and is called out explicitly. This is decided by arithmetic
over the ByteRange alone -- the content of the unsigned region is never
inspected or guessed at. Partial coverage does not by itself invalidate a
signature: in a sequentially signed document every signature but the last
legitimately covers an earlier revision.</li>
<li><strong>CMS without signed attributes is now verified.</strong> RFC 5652 section 5.4 makes
<code>signedAttrs</code> optional and puts the signature over the content itself when it
is absent. EKENG issues its credential documents in exactly that shape, so all
three implementations previously reported genuine EKENG-issued signatures as
invalid. The signature is now verified against the signed bytes, and because
no <code>messageDigest</code> attribute exists in that form, integrity follows from the
signature verdict alone. Such signatures can carry no ESS binding, so signer
identity is exposed only when a trusted certificate chain authenticates it.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v2.1.1...v2.1.2">2.1.2</a> - 2026-08-03</h2><a id="user-content-212---2026-08-03" class="anchor" aria-label="Permalink: 2.1.2 - 2026-08-03" href="#212---2026-08-03"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-7" class="anchor" aria-label="Permalink: Fixed" href="#fixed-7"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>macOS: sandbox write failure on auto-substituted output paths.</strong> The GUI now resolves the output file/folder via native macOS Save/Open dialogs (Powerbox) on every sign operation, granting the app sandbox write permission. Previously, auto-derived paths like <code>Downloads/file_signed.pdf</code> triggered <code>Operation not permitted (os error 1)</code>.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-2" class="anchor" aria-label="Permalink: Changed" href="#changed-2"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>GUI: removed the separate output path text field and "Browse..." button.</strong> The destination is now chosen directly from the native dialog when clicking "Sign PDF" (single file) or selecting a folder (batch mode).</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v2.1.0...v2.1.1">2.1.1</a> - 2026-07-20</h2><a id="user-content-211---2026-07-20" class="anchor" aria-label="Permalink: 2.1.1 - 2026-07-20" href="#211---2026-07-20"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-8" class="anchor" aria-label="Permalink: Fixed" href="#fixed-8"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>
<p dir="auto"><strong>Python/TypeScript: signing a PDF that already has a form no longer
destroys it.</strong> The incremental update now merges the new signature field
into the existing <code>/AcroForm</code> (preserving <code>/Fields</code>, <code>/DR</code>, <code>/DA</code>,
<code>/NeedAppearances</code> and OR-ing <code>/SigFlags</code>) instead of replacing it
wholesale -- the same fix the Rust port received in 2.1.0. Previously,
signing a form-bearing PDF orphaned its fields, and counter-signing a
server-pre-signed document (e.g. a tax-portal registration agreement)
dropped the server's signature field from the form, so validators reported
the document as tampered.</p>
</li>
<li>
<p dir="auto"><strong>macOS: the Dock icon is no longer overridden by a flat square at runtime.</strong>
The desktop GUI forwarded its embedded window icon to
<code>NSApplication.setApplicationIconImage</code>, which replaced the app bundle's
rounded <code>.icns</code> with a square, full-bleed PNG for as long as the app ran. The
runtime window icon is now set only on Windows (taskbar) and Linux (window
manager); on macOS the Dock tile comes from the bundle icon.</p>
</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v2.0.0...v2.1.0">2.1.0</a> - 2026-07-20</h2><a id="user-content-210---2026-07-20" class="anchor" aria-label="Permalink: 2.1.0 - 2026-07-20" href="#210---2026-07-20"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-1" class="anchor" aria-label="Permalink: Added" href="#added-1"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Rust port: a library plus a <code>revenant</code> CLI, published as three crates.</strong>
A new idiomatic Rust implementation targeting the same surface as the Python
and TypeScript ports (no GUI):
<ul dir="auto">
<li><code>revenant-sign-tls</code> — from-scratch TLS 1.0 + RC4-MD5 client for the
legacy CoSign appliances (EKENG's <code>ca.gov.am</code>) that no maintained Rust TLS
library can reach, mirroring <code>tlslite-ng</code> / <code>node-forge</code>.</li>
<li><code>revenant-sign-core</code> — the client library: config and credential storage, the
CoSign DSS SOAP client with auto-detecting transport, CMS extraction,
PDF signature preparation / embedding / verification, and pinned/TSL
certificate-chain validation.</li>
<li><code>revenant-sign</code> — the <code>revenant</code> binary (<code>sign</code>, <code>verify</code>, <code>check</code>, <code>info</code>,
<code>cert</code>, <code>setup</code>, <code>logout</code>, <code>reset</code>), a thin front-end over <code>revenant-sign-core</code>.</li>
</ul>
</li>
<li><strong>Three new GUI languages: Turkish (tr), Georgian (ka), and Persian (fa).</strong>
The desktop app now ships in six languages. Persian is right-to-left;
text in labels, messages, and input fields is right-aligned automatically
when a right-to-left locale is active.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-3" class="anchor" aria-label="Permalink: Changed" href="#changed-3"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>The Rust workspace now versions as one unit on the product line (2.1.0),
continuing from the Python client rather than restarting at 0.1.x.</strong> The
library crates (<code>revenant-sign-core</code>, <code>revenant-sign-tls</code>) and the shipped
binaries (<code>revenant-sign</code>, <code>revenant-sign-gui</code>) share a single version, so the
CLI <code>--version</code>, the GUI About box, the store listings, and the signer version
recorded in each signed PDF (<code>/Prop_Build /REx</code>) all report the same number --
matching the single-version model the Python and TypeScript packages use.</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-9" class="anchor" aria-label="Permalink: Fixed" href="#fixed-9"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Rust: signing a PDF that already has a form no longer destroys it.</strong> The
incremental update now merges the new signature field into the existing
<code>/AcroForm</code> (preserving <code>/Fields</code>, <code>/DR</code>, <code>/DA</code>, <code>/NeedAppearances</code> and
OR-ing <code>/SigFlags</code>) instead of replacing it wholesale. Previously, signing a
form-bearing PDF orphaned its fields, and re-signing dropped the earlier
signature from the form.</li>
<li><strong>Rust: <code>revenant verify</code> now recognises genuine signatures as trusted.</strong> It
validates detached CMS signatures fully in-crate against the active profile's
pinned trust anchors (the same path <code>check</code> uses), instead of shelling out to
<code>openssl</code> with the system trust store — which lacks the EKENG root and
reported every valid signature as <code>INVALID</code>. The external <code>openssl</code>
dependency is gone.</li>
<li><strong>Rust: the CMS verifier no longer reports a signature <code>Valid</code> when the
mandatory <code>contentType</code> signed attribute is missing or disagrees with
<code>eContentType</code></strong> (RFC 5652 §11.1). It also now verifies the ESS
<code>signingCertificate</code> / <code>signingCertificateV2</code> binding when present (RFC 5035):
a signature whose ESSCertID hash names a different certificate than the one
that verified is no longer accepted. EKENG CoSign signatures carry the v1
form and continue to verify.</li>
<li><strong>Rust: a CMS whose signature could not be <em>checked</em> (unusable key,
unsupported algorithm OID, DER decode failure) is now reported <code>Unverifiable</code>
rather than <code>Invalid</code></strong> — the latter wrongly implied the signature was forged.</li>
<li><strong>Rust: the legacy TLS handshake is now bounded by an overall deadline,</strong> so a
server that dribbles records (or empty handshake records) can no longer pin a
client thread indefinitely past the per-read socket timeout.</li>
<li><strong>Rust: smaller correctness and robustness fixes.</strong>
<ul dir="auto">
<li>LTV status no longer claims an OCSP response merely because an Adobe
<code>RevocationInfoArchival</code> container is present (it may hold only CRLs).</li>
<li><code>/Contents</code> hex extraction now ignores insignificant white space inside the
hex string, per ISO 32000-1 §7.3.4.3, so signatures laid out by other tools
are read correctly.</li>
<li>The trailer <code>/ID</code> is carried into the incremental update as a hex string,
preserving binary <code>/ID</code> values byte-for-byte instead of mangling them
through a lossy UTF-8 conversion.</li>
<li>Signing a document whose catalog is at a non-zero generation now fails with
a clear message instead of producing a structurally inconsistent file.</li>
<li>Signing a document large enough that a cross-reference-stream byte offset
would exceed the 4-byte xref field now fails loudly instead of silently
truncating the offset into a corrupt xref stream.</li>
<li>The legacy TLS client rejects a ServerHello that selects a version other
than TLS 1.0, parses IPv6 URL literals correctly, and fails loudly if the
socket read/write timeout cannot be set (rather than silently blocking).</li>
</ul>
</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v1.2.1...v2.0.0">2.0.0</a> - 2026-05-16</h2><a id="user-content-200---2026-05-16" class="anchor" aria-label="Permalink: 2.0.0 - 2026-05-16" href="#200---2026-05-16"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">TypeScript SDK only — Python client is unchanged</h3><a id="user-content-typescript-sdk-only--python-client-is-unchanged" class="anchor" aria-label="Permalink: TypeScript SDK only — Python client is unchanged" href="#typescript-sdk-only--python-client-is-unchanged"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Breaking</h3><a id="user-content-breaking" class="anchor" aria-label="Permalink: Breaking" href="#breaking"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>
<p dir="auto">Low-level signing functions in <code>revenant-sign</code> are renamed with a
<code>WithTransport</code> suffix to free their unsuffixed names for new
high-level wrappers:</p>
<ul dir="auto">
<li><code>signPdfEmbedded</code> → <code>signPdfEmbeddedWithTransport</code></li>
<li><code>signPdfDetached</code> → <code>signPdfDetachedWithTransport</code></li>
<li><code>signHash</code> → <code>signHashWithTransport</code></li>
<li><code>signData</code> → <code>signDataWithTransport</code></li>
</ul>
<p dir="auto">The <code>SigningTransport</code> interface method names (<code>transport.signHash</code>,
<code>transport.signData</code>, <code>transport.signPdfDetached</code>) are unchanged.
Migration: rename your imports; transport-handling code is untouched.</p>
</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-2" class="anchor" aria-label="Permalink: Added" href="#added-2"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>High-level <code>signHash(hashBytes, username, password, options?)</code></strong> —
detached CMS over a precomputed 20-byte SHA-1 hash, with profile
resolution, transport setup, and TLS registration handled internally.
Production callers no longer need to construct a <code>SoapSigningTransport</code>
to sign a hash.</li>
<li><strong>High-level <code>signData(dataBytes, username, password, options?)</code></strong> —
same shape, detached CMS over arbitrary bytes (server computes the
hash).</li>
<li><strong>High-level <code>getCertInfo(username, password, options?)</code></strong> — returns
the signer's identity (<code>CertInfo</code>: CN, email, organization, DN,
<code>notBefore</code>, <code>notAfter</code>) by trying the <code>enum-certificates</code> SAPI
operation first, falling back to dummy-hash signing.</li>
<li><strong>High-level <code>verifyCredentials(username, password, options?)</code></strong> —
thin wrapper over <code>getCertInfo</code> that discards the result and surfaces
<code>AuthError</code> on bad credentials. One round-trip against the appliance's
5-attempt lockout counter; suitable for "test before persist"
credential-entry flows.</li>
<li><code>CertInfo</code> type is re-exported from the top-level <code>revenant-sign</code>
entry.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v1.2.0...v1.2.1">1.2.1</a> - 2026-04-16</h2><a id="user-content-121---2026-04-16" class="anchor" aria-label="Permalink: 1.2.1 - 2026-04-16" href="#121---2026-04-16"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-10" class="anchor" aria-label="Permalink: Fixed" href="#fixed-10"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>Completed Armenian (hy) locale -- 21 strings were showing English fallback</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v1.1.1...v1.2.0">1.2.0</a> - 2026-04-14</h2><a id="user-content-120---2026-04-14" class="anchor" aria-label="Permalink: 1.2.0 - 2026-04-14" href="#120---2026-04-14"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-3" class="anchor" aria-label="Permalink: Added" href="#added-3"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>PKI chain validation</strong> against ETSI Trust Service Lists (TSL) -- verifies signer certificate chains to trusted CAs (Python and TypeScript)</li>
<li>TSL parser and cache with configurable TTL (24h default)</li>
<li>Per-profile <code>tsl_url</code> field; EKENG profile uses the Armenian TSL</li>
<li>Trust status display in GUI verify panel (trusted / not publicly trusted / not checked)</li>
<li>Improved verify result formatting -- human-readable summary above technical details</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Improved</h3><a id="user-content-improved" class="anchor" aria-label="Permalink: Improved" href="#improved"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>Test coverage expanded to 95%+ (740 tests)</li>
<li>New test modules: chain validation, LTV, ASN.1 BER edge cases, i18n</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-11" class="anchor" aria-label="Permalink: Fixed" href="#fixed-11"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>CVE-2026-40192 (pillow), CVE-2025-71176 (pytest)</li>
<li>CI: added <code>cryptography</code> and Python 3.10 backports to dev requirements</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v1.1.0...v1.1.1">1.1.1</a> - 2026-04-10</h2><a id="user-content-111---2026-04-10" class="anchor" aria-label="Permalink: 1.1.1 - 2026-04-10" href="#111---2026-04-10"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Improved</h3><a id="user-content-improved-1" class="anchor" aria-label="Permalink: Improved" href="#improved-1"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>Updated Russian and Armenian translations</li>
<li>Added English locale for i18n completeness</li>
<li>Refined translatable strings across GUI modules</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v1.0.0...v1.1.0">1.1.0</a> - 2026-04-09</h2><a id="user-content-110---2026-04-09" class="anchor" aria-label="Permalink: 1.1.0 - 2026-04-09" href="#110---2026-04-09"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-4" class="anchor" aria-label="Permalink: Added" href="#added-4"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>Localization</strong>: Russian and Armenian language support for the GUI (gettext-based i18n)</li>
<li><strong>Signature position preview</strong>: mini page diagram in the sign form showing stamp placement</li>
<li><strong>Password visibility toggle</strong> in the login dialog</li>
<li><strong>Friendly error messages</strong>: raw HTTP/TLS errors mapped to user-readable messages in the GUI</li>
<li><strong>ASCII credential warning</strong>: warn when credentials contain non-Latin characters</li>
<li><strong>Universal macOS binaries</strong> (arm64 + x86_64) -- Intel Mac users can now install from the Mac App Store, Homebrew, and DMG</li>
<li>Mac App Store badge in README</li>
<li><code>merge_universal.py</code> script for lipo-merging .app bundles and CLI binaries</li>
<li>Translation consistency checker (<code>check_translations.py</code>) and .po compiler (<code>compile_translations.py</code>)</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-4" class="anchor" aria-label="Permalink: Changed" href="#changed-4"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>Homebrew formula no longer requires <code>arch: :arm64</code></li>
<li>Release pipeline builds on both <code>macos-latest</code> (arm64) and <code>macos-15-intel</code> (x86_64), merges with <code>lipo</code></li>
<li>Release artifacts renamed from <code>*-macos-arm64</code> to <code>*-macos-universal</code></li>
<li>Credential resolution moved off main thread to prevent UI freeze on macOS keychain prompts</li>
<li>Platform-specific helpers (macOS menu bar, Windows icon) extracted to dedicated module</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-12" class="anchor" aria-label="Permalink: Fixed" href="#fixed-12"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>Support for original EKENG cosign BER-encoded PDF signatures (indefinite-length CMS blobs) in both Python and TypeScript</li>
<li>BER parser guards against deeply nested structures (depth limit) and oversized length fields</li>
<li>npm publish step in release pipeline no longer silently swallows errors</li>
<li>macOS notarization step properly fails on command errors instead of silently continuing</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.2.6...v1.0.0">1.0.0</a> - 2026-02-26</h2><a id="user-content-100---2026-02-26" class="anchor" aria-label="Permalink: 1.0.0 - 2026-02-26" href="#100---2026-02-26"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-5" class="anchor" aria-label="Permalink: Added" href="#added-5"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>TypeScript/Node.js client</strong> (<code>revenant-sign</code> on npm) -- full-featured port of the Python client with CLI, library API, and 662 tests at 97%+ coverage
<ul dir="auto">
<li>Dual ESM + CJS build via tsup</li>
<li>Embedded and detached PDF signing</li>
<li>Signature verification (embedded and detached CMS/PKCS#7)</li>
<li>CMS blob inspection and certificate info extraction</li>
<li>Multi-profile configuration with keytar credential storage</li>
<li>Legacy TLS 1.0 + RC4 transport for EKENG (pure JS via node-forge)</li>
<li>Armenian font support (GHEA Grapalat, GHEA Mariam, Noto Sans)</li>
<li>Signature image embedding (PNG/JPEG) with alpha channel support</li>
</ul>
</li>
<li>Microsoft Store distribution (Revenant Sign) with Partner Center identity</li>
<li>Release pipeline now includes MAS .pkg in GitHub Releases</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-5" class="anchor" aria-label="Permalink: Changed" href="#changed-5"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>MSIX package renamed to RevenantSign.msix, display name updated to "Revenant Sign"</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-13" class="anchor" aria-label="Permalink: Fixed" href="#fixed-13"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>PDF incremental updates now use cross-reference streams for PDFs that use XRef streams (PDF 1.5+), per ISO 32000-1 S7.5.8.4 -- fixes compatibility with macOS Preview and strict PDF readers when signing pdf-lib or modern-tool-generated PDFs (both Python and TypeScript)</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.2.5...v0.2.6">0.2.6</a> - 2026-02-26</h2><a id="user-content-026---2026-02-26" class="anchor" aria-label="Permalink: 0.2.6 - 2026-02-26" href="#026---2026-02-26"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-6" class="anchor" aria-label="Permalink: Changed" href="#changed-6"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>GUI window title simplified to "Revenant" (version removed to avoid screenshot staleness)</li>
<li>MAS minimum deployment target bumped from macOS 11.0 to 12.0 (required for arm64-only builds)</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.2.4...v0.2.5">0.2.5</a> - 2026-02-26</h2><a id="user-content-025---2026-02-26" class="anchor" aria-label="Permalink: 0.2.5 - 2026-02-26" href="#025---2026-02-26"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-6" class="anchor" aria-label="Permalink: Added" href="#added-6"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>macOS menu bar with File (Close Window) and Edit (Undo, Cut, Copy, Paste, Select All) menus</li>
<li>Cmd+W keyboard shortcut to close windows and dialogs on macOS</li>
<li>Privacy policy URL in Info.plist (MAS Guideline 5.1.1)</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-7" class="anchor" aria-label="Permalink: Changed" href="#changed-7"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>PyPI classifier updated from Beta to Production/Stable (MAS Guideline 2.2)</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-14" class="anchor" aria-label="Permalink: Fixed" href="#fixed-14"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>MAS build: switch from system Tk 8.6 to Homebrew Tk 9.0 to resolve private API rejection (Guideline 2.5.1 -- <code>_NSWindowDidOrderOnScreenNotification</code>)</li>
<li>CI: extract Python+Tk setup and private API verification into reusable composite actions</li>
<li>macOS Preferences menu item no longer shows default Tk behavior</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.2.3...v0.2.4">0.2.4</a> - 2026-02-21</h2><a id="user-content-024---2026-02-21" class="anchor" aria-label="Permalink: 0.2.4 - 2026-02-21" href="#024---2026-02-21"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-15" class="anchor" aria-label="Permalink: Fixed" href="#fixed-15"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>MAS build: use separate entitlements for nested executables to avoid ITMS-90885</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.2.2...v0.2.3">0.2.3</a> - 2026-02-21</h2><a id="user-content-023---2026-02-21" class="anchor" aria-label="Permalink: 0.2.3 - 2026-02-21" href="#023---2026-02-21"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-16" class="anchor" aria-label="Permalink: Fixed" href="#fixed-16"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>MAS build: add application identifier to sandbox entitlements (ITMS-90886)</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.2.1...v0.2.2">0.2.2</a> - 2026-02-21</h2><a id="user-content-022---2026-02-21" class="anchor" aria-label="Permalink: 0.2.2 - 2026-02-21" href="#022---2026-02-21"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-17" class="anchor" aria-label="Permalink: Fixed" href="#fixed-17"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>MAS build: embed provisioning profile for TestFlight and App Store submission (ITMS-90889)</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.2.0...v0.2.1">0.2.1</a> - 2026-02-20</h2><a id="user-content-021---2026-02-20" class="anchor" aria-label="Permalink: 0.2.1 - 2026-02-20" href="#021---2026-02-20"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-7" class="anchor" aria-label="Permalink: Added" href="#added-7"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>Linux ARM64 build support</li>
<li>Automatic Homebrew tap update on release</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/compare/v0.1.0...v0.2.0">0.2.0</a> - 2026-02-16</h2><a id="user-content-020---2026-02-16" class="anchor" aria-label="Permalink: 0.2.0 - 2026-02-16" href="#020---2026-02-16"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Changed</h3><a id="user-content-changed-8" class="anchor" aria-label="Permalink: Changed" href="#changed-8"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><strong>BREAKING:</strong> Position preset names renamed to Y-first (CSS/UI) convention: <code>right-bottom</code> -&gt; <code>bottom-right</code>, <code>right-top</code> -&gt; <code>top-right</code>, <code>left-bottom</code> -&gt; <code>bottom-left</code>, <code>left-top</code> -&gt; <code>top-left</code>, <code>center-bottom</code> -&gt; <code>bottom-center</code>. Short aliases updated accordingly (<code>rb</code> -&gt; <code>br</code>, <code>rt</code> -&gt; <code>tr</code>, etc.)</li>
<li>Windows release artifact renamed from <code>revenant-cli-windows-x64.zip</code> to <code>revenant-windows-x64.zip</code> (contains both CLI and GUI)</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Fixed</h3><a id="user-content-fixed-18" class="anchor" aria-label="Permalink: Fixed" href="#fixed-18"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>MAS build: include sandbox entitlements when signing outer .app bundle</li>
<li>Snap: use <code>adopt-info</code> for version instead of <code>git describe</code></li>
<li>Integration tests TLS setup for env-var-only credentials</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto"><a href="https://github.com/lobotomoe/revenant/releases/tag/v0.1.0">0.1.0</a> - 2026-02-14</h2><a id="user-content-010---2026-02-14" class="anchor" aria-label="Permalink: 0.1.0 - 2026-02-14" href="#010---2026-02-14"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Added</h3><a id="user-content-added-8" class="anchor" aria-label="Permalink: Added" href="#added-8"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>PDF digital signing via CoSign SAPI Web Service (SOAP/DSS)</li>
<li>Embedded visible and invisible signature support with customizable appearance</li>
<li>Armenian font support (GHEA Grapalat, GHEA Mariam, Noto Sans)</li>
<li>Signature image embedding (PNG/JPEG) with alpha channel support</li>
<li>PDF signature verification (embedded and detached CMS/PKCS#7)</li>
<li>CMS blob inspection: digest algorithm, signer info, certificate chain</li>
<li>Certificate information extraction from X.509 and CMS blobs</li>
<li>Multi-profile configuration with per-profile credentials (keyring or config file)</li>
<li>Automatic CoSign server discovery via DNS SRV records</li>
<li>Legacy TLS 1.0 + RC4-MD5 transport for EKENG (ca.gov.am)</li>
<li>Cross-platform GUI (tkinter) with sign, verify, and setup dialogs</li>
<li>CLI with <code>sign</code>, <code>verify</code>, and <code>setup</code> commands</li>
<li>Incremental PDF updates preserving existing signatures (re-signing support)</li>
<li>Cross-reference stream PDF support (modern PDF 1.5+ format)</li>
<li>Page size detection with CropBox/MediaBox/Rotate handling</li>
<li>CI/CD workflows for linting, testing, and release builds</li>
<li>Multi-platform release builds: macOS (.dmg), Windows (.msix, .zip), Linux (AppImage)</li>
<li>Pyright strict mode type checking with 0 errors</li>
<li>96%+ test coverage (600+ tests)</li>
</ul>
</article></div><div class="d-none"></div></section></div></div></div> </div> <!-- --> </div></div></div></div></div></div><div class="ScrollMarksContainer-module__scrollMarksContainer__Eu7uU" id="find-result-marks-container"></div><div class="d-none"></div><div class="d-none"></div></div> <!-- --> <!-- --> </div>
</react-app>




  </div>

</turbo-frame>

    </main>
  </div>

  </div>

          <footer class="footer f6 color-fg-muted color-border-subtle tmp-pt-7 tmp-pb-6 p-responsive" role="contentinfo"  >
  <h2 class='sr-only'>Footer</h2>

  


  <div class="d-flex flex-justify-center flex-items-center flex-column-reverse flex-lg-row flex-wrap flex-lg-nowrap">
    <div class="d-flex flex-items-center flex-shrink-0 mx-2">
      <a aria-label="GitHub Homepage" class="footer-octicon mr-2" href="https://github.com">
        <svg aria-hidden="true" data-component="Octicon" height="24" viewBox="0 0 24 24" version="1.1" width="24" data-view-component="true" class="octicon octicon-mark-github">
    <path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path>
</svg>
</a>
      <span>
        &copy; 2026 GitHub,&nbsp;Inc.
      </span>
    </div>

    <nav aria-label="Footer">
      <h3 class="sr-only" id="sr-footer-heading">Footer navigation</h3>

      <ul class="list-style-none d-flex flex-justify-center flex-wrap mb-2 mb-lg-0" aria-labelledby="sr-footer-heading">


          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to Terms&quot;,&quot;label&quot;:&quot;text:terms&quot;}" href="https://docs.github.com/site-policy/github-terms/github-terms-of-service" data-view-component="true" class="Link--secondary Link">Terms</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to privacy&quot;,&quot;label&quot;:&quot;text:privacy&quot;}" href="https://docs.github.com/site-policy/privacy-policies/github-privacy-statement" data-view-component="true" class="Link--secondary Link">Privacy</a>
          </li>


            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to security&quot;,&quot;label&quot;:&quot;text:security&quot;}" href="https://github.com/security" data-view-component="true" class="Link--secondary Link">Security</a>
            </li>

            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to status&quot;,&quot;label&quot;:&quot;text:status&quot;}" href="https://www.githubstatus.com/" data-view-component="true" class="Link--secondary Link">Status</a>
            </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to community&quot;,&quot;label&quot;:&quot;text:community&quot;}" href="https://github.community/" data-view-component="true" class="Link--secondary Link">Community</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to docs&quot;,&quot;label&quot;:&quot;text:docs&quot;}" href="https://docs.github.com/" data-view-component="true" class="Link--secondary Link">Docs</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to contact&quot;,&quot;label&quot;:&quot;text:contact&quot;}" href="https://support.github.com?tags=dotcom-footer" data-view-component="true" class="Link--secondary Link">Contact</a>
          </li>

          
<li class="mx-2" >
  <cookie-consent-link>
    <button
      type="button"
      class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent"
      data-action="click:cookie-consent-link#showConsentManagement"
      data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;cookies&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;cookies_link_subfooter_footer&quot;}"
    >
      Manage cookies
    </button>
  </cookie-consent-link>
</li>

  <li class="mx-2">
    <cookie-consent-link>
      <button
        type="button"
        class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent text-left"
        data-action="click:cookie-consent-link#showConsentManagement"
        data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;dont_share_info&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;dont_share_info_link_subfooter_footer&quot;}"
      >
        Do not share my personal information
      </button>
    </cookie-consent-link>
  </li>

      </ul>
    </nav>
  </div>
</footer>



    <ghcc-consent id="ghcc" class="position-fixed bottom-0 left-0" style="z-index: 999999"
      data-locale="en"
      data-initial-cookie-consent-allowed=""
      data-cookie-consent-required="true"
    ></ghcc-consent>




  <div id="ajax-error-message" class="ajax-error-message flash flash-error" hidden>
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    You can’t perform that action at this time.
  </div>

    <template id="site-details-dialog">
  <details class="details-reset details-overlay details-overlay-dark lh-default color-fg-default hx_rsm" open>
    <summary role="button" aria-label="Close dialog"></summary>
    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
      <button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
      </button>
      <div class="octocat-spinner tmp-my-6 js-details-dialog-spinner"></div>
    </details-dialog>
  </details>
</template>

    <div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;">
  <div class="Popover-message Popover-message--bottom-left Popover-message--large Box color-shadow-large" style="width:360px;">
  </div>
</div>

    <template id="snippet-clipboard-copy-button">
  <div class="zeroclipboard-container position-absolute right-0 top-0">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn js-clipboard-copy m-2 p-0" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon m-2 tmp-m-2">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none m-2 tmp-m-2">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>
<template id="snippet-clipboard-copy-button-unpositioned">
  <div class="zeroclipboard-container">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>




    </div>
    <div id="js-global-screen-reader-notice" class="sr-only mt-n1" aria-live="polite" aria-atomic="true" ></div>
    <div id="js-global-screen-reader-notice-assertive" class="sr-only mt-n1" aria-live="assertive" aria-atomic="true"></div>
  </body>
</html>

