<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Navalny Team</title>
    <description>The latest articles on DEV Community by Navalny Team (@navalnyteam).</description>
    <link>https://dev.to/navalnyteam</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1160299%2F86499c01-f483-4948-a510-a9a1624738df.jpeg</url>
      <title>DEV Community: Navalny Team</title>
      <link>https://dev.to/navalnyteam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/navalnyteam"/>
    <language>en</language>
    <item>
      <title>Alexei Navalny: Preserving His Legacy in the Age of AI</title>
      <dc:creator>Navalny Team</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:43:52 +0000</pubDate>
      <link>https://dev.to/navalnyteam/alexei-navalny-preserving-his-legacy-in-the-age-of-ai-4lf8</link>
      <guid>https://dev.to/navalnyteam/alexei-navalny-preserving-his-legacy-in-the-age-of-ai-4lf8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tk5q1cmkogan6r7jf04.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tk5q1cmkogan6r7jf04.jpg" alt=" " width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One day, there will be a museum dedicated to Alexei Navalny in Russia. People will come to learn about his life, his work and the extraordinary legacy he left behind. Some will have followed him for years; others may know little more than his name. They will be able to watch his speeches, read his writings, explore photographs from different periods of his life and discover the Alexei that those who knew him remember: what made him laugh, what he loved and what he dreamed of.&lt;/p&gt;

&lt;p&gt;We don't know when that museum will become a reality. In today's Russia, even supporting Alexei Navalny can lead to persecution. Many of those who worked with him or supported his cause have been forced to leave the country under threat of criminal prosecution.&lt;/p&gt;

&lt;p&gt;But we cannot wait for Russia to change before we begin preserving his legacy.&lt;/p&gt;

&lt;p&gt;There is an extraordinary wealth of material documenting Alexei's life and work. His speeches, interviews, blog posts and social media tell part of the story. So do the memories of those who knew him, the photographs taken over the years and the countless newspaper articles and television reports in which he appeared. Some of these materials are still available online. Others are scattered across private collections, hidden away in old newspapers, photo albums and videotapes. Some have already disappeared from public view.&lt;/p&gt;

&lt;p&gt;The challenge is to find them, bring them together and preserve them before they are lost.&lt;/p&gt;

&lt;p&gt;That is why Yulia Navalnaya, who is continuing her husband's work, decided to create a digital archive of his life and legacy at &lt;a href="http://navalny.com/en" rel="noopener noreferrer"&gt;navalny.com&lt;/a&gt;. The Anti-Corruption Foundation (ACF) took on the task of building it, with an ambitious goal: to bring together as much of this material as possible and make it freely accessible to people around the world, in both Russian and English.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl3kwg9hzm9vcuthuu0r9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl3kwg9hzm9vcuthuu0r9.jpg" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We launched the Archive on 4 June 2026, the day Alexei would have turned 50. By then, the collection contained more than 14,000 items, ranging from short social media posts and campaign leaflets to speeches lasting several hours.&lt;/p&gt;

&lt;p&gt;We tracked down interviews that had seemed lost, scanned old newspapers and photographs, and translated thousands of texts, most of which had never been available in English. Some of the most exciting discoveries were materials that even those of us who had worked alongside Alexei for years had never seen before.&lt;/p&gt;

&lt;p&gt;In this article, we'll explain how a small team managed such an enormous undertaking, where artificial intelligence helped us and what still had to be done by hand.&lt;/p&gt;

&lt;p&gt;If you believe in preserving Alexei's legacy and making it accessible to people around the world, please &lt;a href="https://navalny.com/donate/?utm_source=devto&amp;amp;utm_medium=email&amp;amp;utm_campaign=navalny_archive_article&amp;amp;utm_content=donate_ru" rel="noopener noreferrer"&gt;consider supporting our work with a donation&lt;/a&gt;. Your contribution will help us expand the collection, cover the costs of storing and preserving the materials, and continue developing the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Found and Preserved the Materials
&lt;/h2&gt;

&lt;p&gt;We began collecting materials more than a year before the Archive launched. At first, we worked from memory, compiling a Google spreadsheet of the best-known interviews, videos and other recordings featuring Alexei Navalny. Gradually, the collection began to take shape, with sections devoted to his speeches at rallies, his final statements in court, his interviews and much more.&lt;/p&gt;

&lt;p&gt;Then came hundreds of hours of searching.&lt;/p&gt;

&lt;p&gt;From the outside, this might sound like monotonous work. In reality, it was incredibly absorbing, especially when we managed to uncover something rare that everyone had assumed was lost forever.&lt;/p&gt;

&lt;p&gt;There were considerable obstacles. Many Russian media outlets that operated in the 2000s and 2010s have since closed down, changed ownership or adopted a different editorial line. The Russian authorities persecute people for their connections to the Anti-Corruption Foundation, which they have designated an "extremist organisation" for its 15-year history of challenging Putin's regime. Faced with the threat of criminal prosecution, many publications have removed Alexei's interviews and comments from their websites.&lt;/p&gt;

&lt;p&gt;Sometimes we would find a media outlet's website with a seemingly complete archive of its past publications, only to discover that the one article featuring Alexei had mysteriously vanished. This is what censorship looks like in practice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrhcbz4uhdokjy7ognas.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrhcbz4uhdokjy7ognas.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, dozens of Alexei's interviews were missing from the archives of Echo of Moscow, the independent radio station. We searched YouTube, VKontakte, Odnoklassniki and other platforms, eventually recovering the vast majority of them.&lt;/p&gt;

&lt;p&gt;We are particularly proud of these discoveries, as well as materials that can now be found nowhere else: recordings of the court hearings in the first Kirovles case, which captivated the country at the time, and previously unpublished archival photographs of Alexei.&lt;/p&gt;

&lt;p&gt;Some of the materials we collected had never been available online at all. People shared magazines, newspapers and brochures from their personal collections. One of our colleagues spent days at a scanner, turning pages one by one so that visitors to the Archive could read them. Among the results were &lt;a href="https://navalny.com/en/search?type=outreach&amp;amp;format=document&amp;amp;year_from=2013&amp;amp;year_to=2013" rel="noopener noreferrer"&gt;rare local campaign newspapers&lt;/a&gt; from Alexei's 2013 run for mayor of Moscow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8rbhh9vcafv5hsxu6sc5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8rbhh9vcafv5hsxu6sc5.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While our editors were tracking down and digitising materials, the development team was building the content management system and processing tools, and the designers were working out how best to present such a varied collection.&lt;/p&gt;

&lt;p&gt;We chose to house the Archive on Alexei's own website, navalny.com. Unfortunately, the site is blocked in Russia, meaning people there need a VPN to access it. It was a limitation we had to accept: the Russian authorities would have immediately blocked any new website featuring material critical of the regime anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audience and Algorithms: Designing an Archive for Everyone
&lt;/h2&gt;

&lt;p&gt;From the outset, Yulia Navalnaya wanted the Archive to serve two audiences: people who had followed Alexei for years and those discovering his work for the first time. We also knew it could become a valuable resource for historians, journalists and other researchers.&lt;/p&gt;

&lt;p&gt;These different audiences shaped both the content of the Archive and the way we designed the website.&lt;/p&gt;

&lt;p&gt;A researcher might need to find a particular speech, check its date, read a transcript or search for references to a specific person or event. Someone browsing out of curiosity, on the other hand, might not know what they're looking for until something catches their eye.&lt;/p&gt;

&lt;p&gt;We wanted the Archive to work equally well for both.&lt;/p&gt;

&lt;p&gt;That's why, alongside a comprehensive search function, we created themed collections and recommendations that encourage visitors to explore. Someone might start by exploring &lt;a href="https://navalny.com/en/editorials/umnoe-golosovanie" rel="noopener noreferrer"&gt;Smart Voting&lt;/a&gt;, the strategy Alexei developed to challenge Putin's ruling party after being barred from running for office himself, and end up discovering &lt;a href="https://navalny.com/en/search?tag=knigi" rel="noopener noreferrer"&gt;the books he read&lt;/a&gt; or &lt;a href="https://navalny.com/en/search?tag=muzyka" rel="noopener noreferrer"&gt;the music he loved&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our recommendation system takes into account both the subject and the date of each publication, helping visitors move naturally from one item to another.&lt;br&gt;
And every day, the homepage features something Alexei said or wrote on that date in a previous year. It's a way of bringing his words back into the present, and giving people a reason to return, explore and discover something new.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl918ivncl3mhpf37w6ob.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl918ivncl3mhpf37w6ob.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Bringing It All Together: The Design Challenge
&lt;/h2&gt;

&lt;p&gt;Over the years, Alexei Navalny's various projects had developed their own visual identities, created by different teams for different purposes. Before we could bring them together in a single Archive, our designers needed to establish a consistent visual language.&lt;/p&gt;

&lt;p&gt;They went back to the foundations of Alexei's visual identity, restoring the original Swiss-style sans-serif typeface and signature colour, and resolving inconsistencies that had emerged over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm32shfa81wtnxunt8s5f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm32shfa81wtnxunt8s5f.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also had to think about the future. Once the Archive was launched, a small team would be responsible for maintaining and expanding it. The design therefore needed to be flexible enough to accommodate new materials and features without requiring a complete overhaul each time.&lt;/p&gt;

&lt;p&gt;The design went through three major iterations. By the end, we had created 31 distinct screens, each adapted to at least four different screen sizes, using a shared library of components and design variables.&lt;/p&gt;

&lt;p&gt;For inspiration, we looked to YouTube, Netflix and Apple TV. We loved the way these platforms make it easy to navigate enormous libraries of content and find something worth watching.&lt;/p&gt;

&lt;p&gt;But our collection presented a rather different challenge. A scanned newspaper from twenty years ago might sit alongside a court document, a photo gallery or a three-hour interview. Each format needed to be presented in a way that suited the material itself.&lt;/p&gt;

&lt;p&gt;Rather than force everything into a single template, we opted for a consistent design on the homepage while allowing different types of content to have their own layouts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxqj5f3ougayxxc90mm0o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxqj5f3ougayxxc90mm0o.jpg" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We developed standard previews for each type of content, while our designers created almost a thousand individual cover images for the most important publications. Our editors also selected 250 items and tagged them as “Top Content”. A random selection of these is displayed each time someone visits the homepage, keeping it fresh and varied, while all 250 receive higher priority in search results, helping visitors discover important materials regardless of when they were published.&lt;/p&gt;
&lt;h2&gt;
  
  
  What We Entrusted to AI
&lt;/h2&gt;

&lt;p&gt;Every item in the Archive needed a title, a date and a link to its original source. We also had to identify the people, places and events mentioned, assign relevant tags and prepare an English translation. To make long speeches and interviews searchable, we needed transcripts, which meant extracting the automatically generated subtitles available on YouTube.&lt;/p&gt;

&lt;p&gt;Of the roughly 60 people working at the Anti-Corruption Foundation, only about ten were dedicated to the Archive full-time. Processing thousands of items manually would have taken such a small team years.&lt;/p&gt;

&lt;p&gt;Our developers therefore built an automated system to handle the initial processing. In the final two months before launch, we also brought in around 50 volunteers to help review the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4827454l40irya6f67b7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4827454l40irya6f67b7.jpg" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Automation transformed the editorial workflow. Instead of spending hours on repetitive tasks, our editors and volunteers could concentrate on checking the AI-processed results, correcting mistakes and adding historical context.&lt;/p&gt;

&lt;p&gt;We built a collaborative content management system that allowed us to assign materials to individual team members, track their progress and review their work. New volunteers initially received closer supervision, but as they became familiar with our editorial guidelines, they were given greater independence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwh7nd57nrhmc5sklysf1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwh7nd57nrhmc5sklysf1.jpg" alt=" " width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI made it possible for a small nonprofit with a limited budget to process thousands of materials in time for launch.&lt;/p&gt;

&lt;p&gt;For the initial classification, we used large language models. Gemini Flash handled metadata extraction, identifying topics, people and places, while GPT-5.4 was responsible for translation.&lt;/p&gt;

&lt;p&gt;This freed our team to focus on tasks that required human knowledge and editorial judgement: recognising someone in an old photograph, for example, or understanding the historical context of a text Alexei had written from prison.&lt;/p&gt;

&lt;p&gt;We decided against training our own classification model. At the start of the project, we simply didn't have enough labelled data, and creating a sufficiently large training dataset would have taken considerable time.&lt;/p&gt;

&lt;p&gt;Instead, we developed detailed instructions and a set of around 10–20 carefully selected examples. We included several of these examples in each prompt to guide the model towards the results we needed.&lt;/p&gt;
&lt;h2&gt;
  
  
  How We Process Each Material
&lt;/h2&gt;

&lt;p&gt;We needed a system that could take each material through a series of processing stages, running independent tasks in parallel wherever possible.&lt;/p&gt;

&lt;p&gt;We chose a technology stack our developers already knew well: Python 3.13, Django, &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; for data transfer objects (DTOs), Django Q2 (a maintained fork of Django Q) for asynchronous tasks, PostgreSQL, Redis and Elasticsearch for data storage and search, Google Cloud Storage for files, OpenTelemetry for observability and GitLab for continuous integration and deployment (CI/CD).&lt;/p&gt;

&lt;p&gt;Django allowed us to build a working prototype quickly, with a ready-made administration interface. But as the project grew, our processing pipeline became increasingly complex, and we began separating business logic from data access, following the principles of Clean Architecture.&lt;/p&gt;

&lt;p&gt;Combining this approach with Django's Active Record pattern proved more time-consuming than expected. We had to move logic into repository classes, duplicate models in Pydantic and eventually rebuild the administration interface from scratch.&lt;/p&gt;

&lt;p&gt;In other words, the time we saved at the beginning came at a cost later, in additional development work and substantial amounts of boilerplate code. Based on this experience, we will probably choose FastAPI and SQLAlchemy for our next project.&lt;/p&gt;

&lt;p&gt;For every material, the system produces several outputs: a plain text representation of the content for searching and processing, a rich text version to be displayed on the website, and metadata, such as links to related topics, people and places. Some of this information comes directly from the original source; the rest is inferred during processing.&lt;/p&gt;

&lt;p&gt;Take a video, for example. The system first extracts its subtitles and divides the resulting transcript into sections. It then launches four tasks simultaneously: generate a short description, assign tags, identify places and identify people mentioned in the recording.&lt;/p&gt;

&lt;p&gt;A custom-built task runner coordinates the entire process, determining which operations need to happen in sequence and which can run in parallel.&lt;/p&gt;

&lt;p&gt;By running independent tasks simultaneously, we significantly reduce the processing time of each material, reducing the time from initial upload to human review.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkd29p9f90fg6xniomxaw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkd29p9f90fg6xniomxaw.jpg" alt=" " width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Where LLMs Got Things Wrong — and How We Fixed It
&lt;/h2&gt;

&lt;p&gt;We quickly discovered that getting an LLM to produce technically correct results wasn't enough. The results also had to make sense to the people using the Archive.&lt;/p&gt;

&lt;p&gt;Take tags, for example. Initially, we allowed the model to generate them freely. But it kept inventing slightly different labels for essentially the same subject: prison, penal colony, imprisonment. Before long, the collection was cluttered with overlapping tags.&lt;/p&gt;

&lt;p&gt;We changed our approach, creating a fixed list of approved tags from which the model could choose. Only a human editor could add new ones, which could then be applied when existing materials were reprocessed.&lt;/p&gt;

&lt;p&gt;The next challenge was determining how important a particular subject was to a material. A passing reference to a government official, for example, shouldn't carry the same weight as an entire investigation devoted to that person.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbhgsezfpl5tiissnns8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbhgsezfpl5tiissnns8.jpg" alt=" " width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We then prompted the model to apply this scale to find the most relevant tags in each material’s transcript, obtaining a result similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The investigation is entirely centered around Dmitry Medvedev ('Dimon') and his hidden wealth, luxury real estate, and assets, making him and this theme the core subject of the material."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dont-call-him-dimon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"As the foundational focus of the film and Navalny's anti-corruption foundation, corruption is deeply analyzed throughout the entire investigation with extensive documentation."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"corruption"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The investigation was produced, published, and driven by the Anti-Corruption Foundation (FBK), which conducted the research and released the film."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fbk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"One of the most famous specific properties exposed in the investigation is the elaborate estate colloquially referred to as the 'duck house' on the Milovka estate grounds."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"duck-house"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The entire documentary is structured as a detailed, multi-part investigative report uncovering hidden ownership structures and slush funds."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"investigation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step was to decide which tags to keep. We used a simple 1D clustering algorithm to identify sharp drops in relevance scores and discard less relevant tags. If several tags scored highly and the next group received significantly lower scores, the algorithm placed the cutoff between them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2ucd330exrbmbfazvnm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2ucd330exrbmbfazvnm.jpg" alt=" " width="799" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also prompted the model to provide a short explanation for each score in a reasoning field. These explanations were never shown to visitors and were discarded during processing. But we found that forcing the model to explain its decisions made its assessments more consistent, a variation of &lt;a href="https://research.google/blog/language-models-perform-reasoning-via-chain-of-thought/" rel="noopener noreferrer"&gt;Chain-of-Thought prompting&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We reinforced this approach via &lt;a href="https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/prompts/few-shot-examples" rel="noopener noreferrer"&gt;few-shot prompting&lt;/a&gt;—by including examples of correctly tagged texts in the beginning of the prompt.&lt;/p&gt;

&lt;p&gt;Even with these improvements, mistakes remained. One of Alexei's posts, My Fear and Loathing, about the betrayal of democratic ideals by post-Soviet reformers, was classified as "Lifestyle". His prison writings were frequently tagged as "Humour". Alexei often joked, even in the bleakest circumstances, but behind the jokes were sometimes deeply painful stories that visitors would hardly expect to encounter in a collection of humorous posts. It also turned out to be surprisingly tricky to explain to an LLM that what it categorized as "Torture" was in fact just typical police behavior during a peaceful protest in Russia.&lt;/p&gt;

&lt;p&gt;Our editors reviewed these cases with the project manager, and the developers used their feedback to refine the system instructions for the LLM.&lt;/p&gt;

&lt;p&gt;Instagram posts proved particularly challenging. To keep costs down, we had decided not to use image recognition. But a caption alone often provided too little information to establish what was happening in a photograph, so editors had to check the assigned tags particularly carefully. For posts from 2011 and 2012, colleagues who remembered the events firsthand were invaluable. Younger staff members and volunteers often had no way of recognising the people or situations depicted in the photographs.&lt;/p&gt;

&lt;p&gt;AI-generated descriptions presented a different problem. The model repeatedly fell back on formulaic phrases such as "The author argues that..." or "This article examines the issue of...".&lt;/p&gt;

&lt;p&gt;No amount of system instructions and few-shot prompts entirely solved the problem. In the end, we treated AI-generated descriptions as first drafts, which editors would then revise to remove awkward, bureaucratic language and make the wording more precise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translations: Bringing Alexei's Words to the World
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakkl7aorramxvgvs3xwn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakkl7aorramxvgvs3xwn.jpg" alt=" " width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Translations were another enormous part of the project. We wanted people who don't speak Russian to be able to read Alexei's speeches, interviews and writings from different periods of his life, and form their own understanding of who he was and what he stood for.&lt;/p&gt;

&lt;p&gt;By the time we launched, almost the entire Archive was available in English, with the vast majority of these materials translated for the first time. For us, this was one of the project's most important achievements, and we were genuinely impressed by the quality of the translations LLMs could produce.&lt;/p&gt;

&lt;p&gt;To make this possible, we built our own translation service on top of GPT-5.4. It gave us control over how texts were processed and how translations were returned, and also allowed us to translate texts from other languages into Russian when needed.&lt;/p&gt;

&lt;p&gt;The biggest challenge was dealing with long documents. When given too much text at once, the model would sometimes introduce details that weren't present in the original.&lt;/p&gt;

&lt;p&gt;To minimise this problem, we divided texts into smaller sections, keeping paragraphs intact wherever possible. When that wasn't feasible, the system used punctuation and word boundaries to determine where to split the text, falling back on character limits only when necessary.&lt;/p&gt;

&lt;p&gt;For each section, the model returned two fields: &lt;strong&gt;original&lt;/strong&gt;, containing a copy of the source text, and &lt;strong&gt;translation&lt;/strong&gt;, containing the English version.&lt;/p&gt;

&lt;p&gt;We found that requiring the model to reproduce the original piece of text alongside its translation helped it stay more faithful to the source, a technique known as &lt;a href="https://getminds.ai/glossary/what-is-a-context-anchored-llm" rel="noopener noreferrer"&gt;Context Anchoring&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding What You Need Among Thousands of Materials
&lt;/h2&gt;

&lt;p&gt;Once the materials had been classified and translated, we needed to make them searchable, both by exact wording and by meaning.&lt;/p&gt;

&lt;p&gt;Again, we had two types of visitors in mind. Someone might remember a particular phrase Alexei had used and want to find the original speech or article. Someone else might be interested in a broader subject but have no specific document in mind.&lt;/p&gt;

&lt;p&gt;Our search system needed to work for both.&lt;/p&gt;

&lt;p&gt;We used Elasticsearch to combine full-text search, filters and semantic search within a single engine.&lt;/p&gt;

&lt;p&gt;For full-text search, we use &lt;a href="https://en.wikipedia.org/wiki/Okapi_BM25" rel="noopener noreferrer"&gt;BM25&lt;/a&gt;, an algorithm that ranks results based on how closely their wording matches the query. Alongside it, we use k-nearest neighbours (kNN) search with embeddings generated by OpenAI's &lt;strong&gt;text-embedding-3-small model&lt;/strong&gt;. This allows the system to find materials that are similar in meaning, even when they don't contain the exact words entered by the user.&lt;/p&gt;

&lt;p&gt;For example, someone searching for "corruption in government procurement" should be able to find investigations by RosPil, Alexei's project dedicated to exposing corruption in public procurement, even if the original text uses different expressions, such as "embezzlement of public funds through government tenders".&lt;/p&gt;

&lt;p&gt;We chose OpenAI's embedding model because it supports multiple languages and was easy to integrate into our existing processing pipeline, which already used OpenAI.&lt;/p&gt;

&lt;p&gt;The model represents each piece of text as a vector of 1,536 numbers, allowing the search engine to calculate how closely different texts and queries are related in meaning.&lt;/p&gt;

&lt;p&gt;The cost is remarkably low. According to our developers' calculations, generating embeddings for the entire collection costs &lt;strong&gt;just a few cents&lt;/strong&gt;, while processing a single search query costs approximately &lt;strong&gt;$0.0000005&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We also cache query embeddings in Redis for a week, so repeated searches don't require another request to the model.&lt;/p&gt;

&lt;p&gt;Visitors can narrow their results using filters for source, content type, format and date. Anyone looking for an exact phrase can put it in quotation marks.&lt;/p&gt;

&lt;p&gt;The search engine also accounts for the grammatical features of Russian and English, so different forms of the same word don't prevent relevant results from appearing.&lt;/p&gt;

&lt;p&gt;There was, however, one technical compromise.&lt;/p&gt;

&lt;p&gt;In our Elasticsearch configuration, Reciprocal Rank Fusion (RRF), a method for combining results from different search algorithms based on their rankings, was unavailable without a paid licence.&lt;/p&gt;

&lt;p&gt;Instead, we simply add together the scores produced by full-text and semantic search, without normalising them to a common scale.&lt;/p&gt;

&lt;p&gt;This makes the final ranking somewhat approximate. Nevertheless, in our testing, it has worked reliably for our collection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jndybu3a8065tb2haj6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jndybu3a8065tb2haj6.jpg" alt=" " width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Had to Make Search Faster
&lt;/h2&gt;

&lt;p&gt;Finding relevant items was only half the challenge. Visitors also needed to get their results quickly and see exactly where their search terms appeared in each document.&lt;/p&gt;

&lt;p&gt;When we began load testing with k6, we discovered that Elasticsearch itself was remarkably fast, returning search results in milliseconds. The delays came later, during the fetch phase, when the system retrieved the matching documents and highlighted the relevant passages.&lt;/p&gt;

&lt;p&gt;Each document's &lt;strong&gt;_source&lt;/strong&gt; contained around 49 KB of vector data that wasn't needed to generate search results, since kNN search used a separate index. By removing this unnecessary data, we reduced the average document size from approximately 75 KB to 26 KB.&lt;/p&gt;

&lt;p&gt;Highlighting proved more difficult to optimise.&lt;/p&gt;

&lt;p&gt;With our texts, Elasticsearch took around 400 milliseconds to highlight matches on a single page of search results. We experimented with different highlighting engines, but none delivered a significant improvement. Under load, the Fast Vector Highlighter (fvh) consumed substantial CPU resources building term structures, even when there were no matches to highlight.&lt;/p&gt;

&lt;p&gt;In our configuration, each Elasticsearch node had seven search threads. Under load, fetching and highlighting results took around a second, and our two-node setup reached its limit at approximately 13 requests per second.&lt;/p&gt;

&lt;p&gt;We decided to move highlighting out of Elasticsearch and into our backend, which could be scaled horizontally by adding more instances. This gave us greater flexibility, but initially did little to reduce the time required for highlighting itself.&lt;/p&gt;

&lt;p&gt;Our first implementation examined every word in a document, reduced it to its stem and compared it with the search query. The problem was that finding just a handful of matches could require processing thousands of words.&lt;br&gt;
`&lt;br&gt;
Replacing the Python stemmer with a C implementation made little difference.&lt;/p&gt;

&lt;p&gt;So we changed the order of operations. Instead of stemming every word, we introduced an initial filtering step. A compiled regular expression searches for words with matching prefixes, and only those candidates are passed to the stemmer for verification.&lt;/p&gt;

&lt;p&gt;The system also builds the snippets displayed in search results as it finds matches. Once it has collected enough relevant passages, it stops processing the document.&lt;/p&gt;

&lt;p&gt;We retained stemming because matching prefixes alone can produce false positives. In Russian, for example, a search for words beginning with коррупц would match both коррупция (corruption) and коррупционеры (corrupt officials), even though they are different words. Stemming provides an additional check.&lt;/p&gt;

&lt;p&gt;We also had to account for language-specific behaviour. The English stemmer, for instance, converts &lt;strong&gt;country&lt;/strong&gt; to &lt;strong&gt;countri&lt;/strong&gt;, so candidate selection must use a prefix shared by both forms. In Russian, the letters е and ё also need to be treated as equivalent for search purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  From 400 ms to 30 ms
&lt;/h2&gt;

&lt;p&gt;The results were substantial. In our tests, highlighting time fell from approximately 400 milliseconds to just 30 milliseconds.&lt;/p&gt;

&lt;p&gt;We also tested the new implementation against Elasticsearch's native highlighting on a corpus containing more than a million characters and found no discrepancies.&lt;/p&gt;

&lt;p&gt;These figures refer specifically to highlighting performance. We measured the response time of the search service as a whole separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Manage Traffic
&lt;/h2&gt;

&lt;p&gt;Once we had made the fetch phase more efficient, the next bottleneck was our backend.&lt;/p&gt;

&lt;p&gt;In a separate test with highlighting disabled, the system handled approximately 107 requests per second, with a p95 response time of 1.8 seconds. In other words, 95% of requests were completed within that time.&lt;/p&gt;

&lt;p&gt;A typical response containing around 20 search results was approximately 66 KB. This included full descriptions and lists of tags, people and places associated with each item. Reducing the amount of data returned became our next priority.&lt;/p&gt;

&lt;p&gt;To make performance tuning easier, we moved the settings we needed to adjust regularly into the administration interface.&lt;/p&gt;

&lt;p&gt;From there, we can enable or disable semantic search, adjust its weighting and similarity threshold, choose the highlighting engine and control how search snippets are generated: not at all, from descriptions or from the full text.&lt;/p&gt;

&lt;p&gt;We can also configure response caching and cache expiration times.&lt;/p&gt;

&lt;p&gt;These settings are stored in the database and accessed through a 30-second cache, allowing us to make changes without deploying new code. We later extended the same approach to adaptive throttling.&lt;/p&gt;

&lt;p&gt;Other parameters, such as the number of Elasticsearch index shards, still have to be configured when an index is created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7oo2m9l1dy0n2qfuhzb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7oo2m9l1dy0n2qfuhzb.jpg" alt=" " width="799" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To handle spikes in traffic, we introduced response caching in Redis and adaptive throttling. The system monitors p95 response times and, when it detects an overload, temporarily rejects some requests with an HTTP 429 ("Too Many Requests") response.&lt;/p&gt;

&lt;p&gt;We also designed the system so that rebuilding the search index doesn't require taking the website offline. A new index is created alongside the existing one, and once it has been checked, we switch the alias to point to the new version.&lt;/p&gt;

&lt;p&gt;This allows us to update the search infrastructure while keeping the Archive accessible to visitors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Archive Continues to Grow
&lt;/h2&gt;

&lt;p&gt;From the beginning, we designed the Archive to keep growing long after its launch.&lt;/p&gt;

&lt;p&gt;Every month, we add around 100–200 new items: videos, interviews, legal documents and other discoveries. Each goes through the same process as the original collection, from tracking down a digital file or paper document to processing, editorial review and, finally, publication on the website, where anyone can read, watch or search for it.&lt;/p&gt;

&lt;p&gt;Some of our most valuable contributions come from Alexei's supporters. People discover old recordings and photographs in their personal collections and share them with us, helping us uncover parts of his life and work that even we knew little about.&lt;/p&gt;

&lt;p&gt;There is still so much to find and preserve, and these contributions are invaluable. If you have any materials that might belong in the Archive, please write to us at &lt;u&gt;&lt;a href="mailto:archive@navalny.com"&gt;archive@navalny.com&lt;/a&gt;&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;We also invite you to explore the Archive for yourself. Browse a themed collection, revisit an old interview or discover what's featured on the homepage today. Even if you've followed Alexei for years, we hope you'll find something you've never seen before.&lt;/p&gt;

&lt;p&gt;And if you believe this work matters, &lt;a href="https://navalny.com/donate/?utm_source=devto&amp;amp;utm_medium=email&amp;amp;utm_campaign=navalny_archive_article&amp;amp;utm_content=donate_en" rel="noopener noreferrer"&gt;please consider supporting it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your donations help us preserve the collection, maintain the website and prepare new materials for publication. With your support, the Anti-Corruption Foundation can continue collecting and safeguarding the record of Alexei's life and work, making it accessible to people around the world — both those who remember him and those who are only just discovering his story.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznx6slcb5qzpo7jic6i2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznx6slcb5qzpo7jic6i2.jpg" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>ai</category>
      <category>digitalarchiving</category>
    </item>
    <item>
      <title>Алексей Навальный: сохраняем наследие в эпоху ИИ</title>
      <dc:creator>Navalny Team</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:43:48 +0000</pubDate>
      <link>https://dev.to/navalnyteam/alieksiei-navalnyi-sokhraniaiem-nasliediie-v-epokhu-ii-52ki</link>
      <guid>https://dev.to/navalnyteam/alieksiei-navalnyi-sokhraniaiem-nasliediie-v-epokhu-ii-52ki</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp4zlzkhmvq66dk005ub.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjp4zlzkhmvq66dk005ub.jpg" alt=" " width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Когда-нибудь в России появится музей Алексея Навального. Нам хочется, чтобы туда приходили и те, кто много лет поддерживал Алексея, и те, кто знает о нём совсем немного. Чтобы можно было послушать его выступления, почитать записи в блоге, посмотреть фотографии и узнать, над чем он смеялся, что любил и о чём мечтал.&lt;/p&gt;

&lt;p&gt;Трудно сказать, когда это станет возможно, ведь сейчас сторонники Алексея подвергаются преследованию и многим из них пришлось эмигрировать из России под угрозой уголовных дел. Но собирать будущую коллекцию важно уже сейчас. О жизни и работе Алексея сохранилось множество свидетельств: его собственные тексты и записи, воспоминания людей, которые его знали, публикации в прессе, фотографии и видео. Что-то можно найти в интернете, а что-то хранится у людей дома — в старых газетах, фотоальбомах и на кассетах. Нам хочется найти и сберечь как можно больше, пока эти материалы ещё доступны.&lt;/p&gt;

&lt;p&gt;Поэтому Юлия Навальная, которая продолжает дело своего мужа, решила создать цифровой архив его памяти на navalny.com. За создание Архива взялась команда Фонда борьбы с коррупцией. Нам хотелось сберечь как можно больше материалов и сделать их доступными на русском и английском языках.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdx7hj4xp8uason3cz38.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgdx7hj4xp8uason3cz38.jpg" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Мы запустили Архив 4 июня 2026 года — в день, когда Алексею исполнилось бы 50 лет. К этому моменту в коллекции было больше 14 тысяч материалов: от коротких постов и агитационных листовок до многочасовых выступлений. Мы находили интервью, которые казались потерянными, сканировали старые газеты и фотографии, переводили тысячи текстов, большинство из которых прежде нельзя было прочитать по-английски. Особенно радостно было находить что-то незнакомое даже тем из нас, кто много лет работал с Алексеем.&lt;/p&gt;

&lt;p&gt;Ниже расскажем, как небольшая команда справилась с таким объёмом работы, где нам помог ИИ и что всё равно пришлось делать вручную. Если вам важно сохранить наследие Алексея и сделать его доступным для людей во всём мире, &lt;a href="https://navalny.com/donate/?utm_source=devto&amp;amp;utm_medium=email&amp;amp;utm_campaign=navalny_archive_article&amp;amp;utm_content=donate_ru" rel="noopener noreferrer"&gt;поддержите нашу работу пожертвованием&lt;/a&gt;. Это поможет пополнять коллекцию, оплачивать хранение материалов и развивать сайт.&lt;/p&gt;

&lt;h2&gt;
  
  
  Как мы искали и сохраняли материалы
&lt;/h2&gt;

&lt;p&gt;К сбору материалов мы приступили больше чем за год до запуска. Начали с того, что помнили сами: собрали в гугл-таблицу самые известные текстовые интервью, видеозаписи и другие материалы с участием Алексей Навального. Постепенно из них складывались разделы — выступления на митингах, последние слова в судах, интервью, — а дальше начались сотни часов поиска. Со стороны эта работа могла выглядеть однообразно. Но когда в неё погружаешься, оторваться трудно, особенно если удаётся найти уникальный материал, который уже считался потерянным.&lt;/p&gt;

&lt;p&gt;У сбора материалов были объективные трудности. Многие СМИ, работавшие в нулевые и десятые, закрылись или сменили владельцев и редакционную политику. Российские власти преследуют людей за связь с ФБК, признанным «экстремистской организацией», и под угрозой уголовных дел издания удаляют интервью и комментарии Алексея. Бывает, что заходишь на сайт СМИ, и там есть полный архив всех публикаций, но только статья с участием Алексея таинственно исчезает — так работает цензура. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrc7vpdqp5s3xj1xn50g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrc7vpdqp5s3xj1xn50g.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Например, в архиве радиостанции «Эхо Москвы» не сохранились десятки интервью с Алексеем. Мы искали их на YouTube, во «ВКонтакте», в «Одноклассниках» и других хранилищах и в итоге нашли подавляющее большинство. Этим особенно гордимся, как и материалами, которые теперь можно найти только у нас: видеозаписи судебных заседаний по «Кировлесу-1», за которыми тогда следила вся страна, и архивными фотографиями Алексея.&lt;/p&gt;

&lt;p&gt;Часть коллекции вообще раньше не попадала в интернет. Журналы, газеты и брошюры нам передавали из личных архивов. Один из коллег несколько дней провёл у сканера, переворачивая страницы, чтобы их смогли прочитать посетители сайта. Так у нас появились в том числе &lt;a href="https://navalny.com/search?type=outreach&amp;amp;year_from=2013&amp;amp;year_to=2013&amp;amp;format=document" rel="noopener noreferrer"&gt;редкие районные предвыборные газеты кампании&lt;/a&gt; Алексея на выборах мэра Москвы. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7vlym0pz1t2go1ss6wil.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7vlym0pz1t2go1ss6wil.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Пока редакторы искали и оцифровывали материалы, разработчики делали админку и систему обработки, а дизайнеры работали над тем, как показать материалы посетителям. Для размещения архива мы выбрали собственный сайт Алексея, navalny.com. К сожалению, сайт заблокирован в России, поэтому посетителям необходим VPN, но с этим ограничением пришлось мириться.&lt;/p&gt;

&lt;h2&gt;
  
  
  Аудитория и алгоритмы
&lt;/h2&gt;

&lt;p&gt;Юлия Навальная хотела, чтобы Архивом могли пользоваться как давние сторонники Алексея, так и те, кто хочет познакомиться с его работой. Также мы считали вероятным, что сайт станет полезным инструментом для тех, кто занимается научными исследованиями. &lt;/p&gt;

&lt;p&gt;Эти задачи определяли и содержание, и устройство сайта. Исследователю нужны точная дата, теги, расшифровка и удобный поиск. Посетителю без определённого запроса — возможность сразу открыть что-нибудь интересное. Поэтому мы сделали тематические подборки и блоки с похожими публикациями. Можно начать с Умного голосования, а можно почитать, какие книги и музыку любил Алексей. Рекомендации учитывают тему и дату публикации и помогают переходить от одного материала к другому. На главной ежедневно появляются цитаты Алексея «в этот день», — нам хотелось дать людям повод постоянно возвращаться, чтобы найти для себя что-то новое.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmjhetcbl0xtxg5bdqsw6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmjhetcbl0xtxg5bdqsw6.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Как показать настолько разные материалы?
&lt;/h2&gt;

&lt;p&gt;За годы работы у проектов Навального накопилось множество визуальных решений, созданных разными командами под разные задачи. Прежде чем собирать из них интерфейс Архива, дизайнеры вернулись к общим правилам: восстановили швейцарский гротеск и фирменный цвет, убрали наиболее заметные противоречия. Компоненты разрабатывали с расчётом на то, что после запуска сайт будет поддерживать небольшая команда. Ей нужно добавлять новые материалы и функции, не пересматривая каждый раз оформление всего проекта.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffcc1txin6ty06zpqhkjy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffcc1txin6ty06zpqhkjy.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;За время работы дизайн прошёл три большие итерации. Получился 31 экран, каждый минимум в четырёх адаптивных размерах, с общими компонентами и Variables.&lt;/p&gt;

&lt;p&gt;В качестве ориентира дизайна мы выбрали YouTube, Netflix и Apple TV: нам нравилось, как эти интерфейсы помогают выбрать нужное из большого каталога. Проблема была в том, что у нас рядом оказывались старый газетный скан, судебный документ, фотогалерея и многочасовое интервью. Для каждого формата требовались свои специфические визуальные решения. Поэтому единообразие мы оставили только на главной, а внутри каждый тип материала выглядел по-своему.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvrqkccwokrpmdfiv0sb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvrqkccwokrpmdfiv0sb.jpg" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Для каждого типа материала мы подготовили универсальные превью, а почти тысячу обложек к наиболее важным публикациям дизайнеры сделали вручную. Редакторы выбрали 250 экспонатов с тегом «Топ-контент». Они случайным образом появляются на главной и получают повышенный приоритет в поиске, чтобы посетители замечали важные материалы независимо от их давности.&lt;/p&gt;

&lt;h2&gt;
  
  
  Что мы поручили ИИ
&lt;/h2&gt;

&lt;p&gt;Для каждого материала требовались заголовок, дата и ссылка на источник. Нужно было определить, о каких людях, местах и событиях в нём говорится, подобрать теги и подготовить английскую версию. Для поиска внутри длинных выступлений требовался текст — значит нужно использовать имеющиеся автоматические субтитры YouTube. &lt;/p&gt;

&lt;p&gt;Из примерно 60 сотрудников ФБК только около десяти занимались Архивом как основной работой. Для такой команды ручная обработка всей коллекции могла растянуться на годы. Поэтому разработчики построили систему первичной обработки, а в последние два месяца перед запуском к проверке материалов подключились примерно 50 волонтёров. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7va29inmszvnvmucysf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7va29inmszvnvmucysf.jpg" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Автоматизация упростила работу редакторам и волонтёрам: можно было меньше заниматься ручной работой и сфокусироваться на проверке результата работы ИИ: добавлять исторический контекст и исправлять ошибки. В админке появились функции для совместной работы: назначить материал конкретному человеку, увидеть его статус и проверить результат. Работу новых участников сначала просматривали внимательнее, а когда человек осваивал правила, давали больше самостоятельности. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiizgffeliiz7xt37p36d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiizgffeliiz7xt37p36d.jpg" alt=" " width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Внедрение искусственного интеллекта позволило небольшой НКО с ограниченным бюджетом обработать тысячи материалов к запуску. Для первичной разметки мы использовали большие языковые модели: Gemini Flash определяла техническую информацию о материале: темы, людей и места, а перевод поручили GPT-5.4. Люди могли сосредоточиться на том, что требовало знания событий и редакторского решения, — например, узнать человека на старой фотографии или разобраться в контексте поста.&lt;/p&gt;

&lt;p&gt;Собственную модель для классификации мы не обучали: в начале проекта не было достаточного набора размеченных данных. Подготовить его означало потратить много времени. Вместо этого мы составили подробные инструкции и около десяти-двадцати эталонных примеров; несколько из них включали в запрос к готовой модели.&lt;/p&gt;

&lt;h2&gt;
  
  
  Как обрабатывается материал
&lt;/h2&gt;

&lt;p&gt;Все эти операции нужно было связать в систему, которая готовит публикацию поэтапно и позволяет выполнять независимые задачи одновременно. Стек выбирали из того, что команда уже хорошо знала: Python 3.13, Django, &lt;a href="https://pydantic.dev/docs/validation/latest/get-started/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; для DTO, Django Q2 для асинхронных задач, Postgres, Redis и Elasticsearch для хранения и поиска, GCS для файлов, OpenTelemetry для наблюдаемости и GitLab для CI/CD. Django Q2 — форк Django Q.&lt;/p&gt;

&lt;p&gt;Django помог быстро собрать прототип и дал готовую админку. По мере роста проекта обработка становилась многоэтапной, а мы стремились отделять бизнес-логику от работы с данными, следуя Clean Architecture. Совместить этот подход с Active Record в Django оказалось трудоёмко: пришлось выносить логику в репозитории, дублировать модели в Pydantic и в итоге переписать встроенную админку с нуля. За быстрый старт мы позже заплатили временем и большим количеством шаблонного кода. С учётом этого опыта следующий проект, скорее всего, будем писать на FastAPI + SQLAlchemy.&lt;/p&gt;

&lt;p&gt;Для каждого материала система готовит текст для поиска и обработки, версию с разметкой для показа на сайте, метаданные и связи с темами, людьми и местами. Часть сведений приходит вместе с источником, остальные извлекаются при обработке.&lt;/p&gt;

&lt;p&gt;Например, у видеоролика сначала извлекаются субтитры. Полученный текст делится на секции, после чего одновременно запускаются четыре задачи: подготовить краткое описание, подобрать теги, определить упомянутые места и людей. Собственный раннер управляет последовательностью операций и максимально распараллеливает те, которые не зависят друг от друга. Это сокращает время подготовки материала к проверке редактором.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr9alsy485ppxxwgn51wj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr9alsy485ppxxwgn51wj.jpg" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Где ИИ ошибался и что мы меняли
&lt;/h2&gt;

&lt;p&gt;На практике быстро выяснилось, что формально правильного ответа модели недостаточно, чтобы сайт был удобным для использования. Например, сначала мы позволили модели самой создавать теги. Но она предлагала для одной темы разные формулировки, и коллекция материалов быстро обрастала почти одинаковыми тегами: тюрьма, колония, заключение. &lt;/p&gt;

&lt;p&gt;Мы отказались от этого подхода и составили фиксированный список, из которого модель выбирала теги. Пополнять сам список мог только человек, после чего новые теги можно было применять и при повторной обработке материалов.&lt;/p&gt;

&lt;p&gt;Следующая трудность — определить, насколько тема важна для конкретного текста. Упоминание чиновника в одном предложении и расследование о нём не должны давать одинаковый результат в виде тега. Поэтому мы составили шкалу, по которой модель оценивает кандидатов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0l5pk100t0im0170199.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0l5pk100t0im0170199.jpg" alt=" " width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;И попросили модель применить её для оценки наиболее подходящих сущностей на основе текстовой транскрипции материала:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The investigation is entirely centered around Dmitry Medvedev ('Dimon') and his hidden wealth, luxury real estate, and assets, making him and this theme the core subject of the material."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dont-call-him-dimon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"As the foundational focus of the film and Navalny's anti-corruption foundation, corruption is deeply analyzed throughout the entire investigation with extensive documentation."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"corruption"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The investigation was produced, published, and driven by the Anti-Corruption Foundation (FBK), which conducted the research and released the film."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fbk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"One of the most famous specific properties exposed in the investigation is the elaborate estate colloquially referred to as the 'duck house' on the Milovka estate grounds."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"duck-house"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The entire documentary is structured as a detailed, multi-part investigative report uncovering hidden ownership structures and slush funds."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"investigation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Дальше алгоритм одномерной кластеризации ищет резкий перепад оценок и отсекает менее релевантные теги. Например, если несколько кандидатов получили высокие баллы, а следующие — заметно более низкие, граница проходит между этими группами.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffgj3ewydoo9zpw2spk8p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffgj3ewydoo9zpw2spk8p.jpg" alt=" " width="799" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;В структурированный ответ к каждой оценке добавили поле reasoning с коротким объяснением. Пользователю оно не показывается и после обработки удаляется. По наблюдениям команды, с этим полем модель оценивала сущности последовательнее. Примеры разметки в запросе показывали, как применять шкалу к реальным текстам — подход multi-shot prompting. &lt;/p&gt;

&lt;p&gt;Даже после этих изменений оставались ошибки. Например, пост Алексея «Мои страх и ненависть» о предательстве со стороны постсоветских реформаторов получил тег «Лайфстайл». Тюремные записи часто попадали в юмор: Алексей действительно шутил, но за шуткой мог стоять очень грустный рассказ, который посетитель вряд ли ожидал найти в такой подборке. А под тегом «пытки» оказывались тексты о задержании на митинге или полицейской слежке. Редакторы разбирали эти случаи с менеджером, после чего разработчики уточняли инструкции для ИИ.&lt;/p&gt;

&lt;p&gt;Особенно много внимания требовали посты в Instagram. Чтобы ограничить расходы, мы не использовали распознавание изображений, а лишь по одной подписи часто невозможно понять, что происходит на фотографии. Редактору приходилось тщательно перепроверять присвоенные теги. С публикациями 2011–2012 годов особенно помогали коллеги, которые помнили события: молодые сотрудники и волонтёры могли просто не узнать человека или ситуацию на снимке.&lt;/p&gt;

&lt;p&gt;С описаниями трудность была другой: модель регулярно возвращалась к оборотам вроде «Автор считает…» и «В материале рассматривается вопрос…». Исправить тон одними инструкциями не удалось. Поэтому сгенерированные описания стали черновиками, которые редакторы правят перед публикацией, убирая канцелярские обороты и уточняя формулировки.&lt;/p&gt;

&lt;h2&gt;
  
  
  Перевод: как мы показали миру слова Алексея
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjo4nkwdw4wnfzk8oo2cj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjo4nkwdw4wnfzk8oo2cj.jpg" alt=" " width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Перевод был ещё одной большой частью подготовки. Нам хотелось, чтобы человек без знания русского мог читать выступления, интервью и записи Алексея за разные годы и составлять собственное представление о нём. В итоге почти все материалы на сайте стали доступны на английском, большинство — впервые. Для нас это один из важнейших результатов проекта, и качество переводов действительно нас порадовало.&lt;/p&gt;

&lt;p&gt;Чтобы получить этот результат, мы сделали собственный сервис на базе GPT-5.4. Он позволяет управлять обработкой текста и форматом ответа, а при необходимости переводить и с английского на русский. Главная сложность возникала с длинными документами: модель могла добавлять детали, которых не было в оригинале.&lt;/p&gt;

&lt;p&gt;Поэтому мы делили тексты на небольшие фрагменты, по возможности по границам абзацев. Если этого недостаточно, система использовала пунктуацию и границы слов, а в крайнем случае — ограничение по числу символов. Для каждого фрагмента модель возвращает два поля: &lt;strong&gt;original&lt;/strong&gt; с копией исходного текста и &lt;strong&gt;translation&lt;/strong&gt; с переводом. По наблюдениям команды, такой формат помог ей точнее придерживаться оригинала.&lt;/p&gt;

&lt;h2&gt;
  
  
  Как найти нужное среди тысяч материалов
&lt;/h2&gt;

&lt;p&gt;После разметки и перевода материалы можно было искать по тексту и смыслу. Здесь мы снова вернулись к двум сценариям: кто-то помнит точную фразу Алексея, а кто-то интересуется какой-то темой, но точного запроса у него нет. Поиск должен был помогать в обоих случаях.&lt;/p&gt;

&lt;p&gt;Elasticsearch позволил собрать полнотекстовый поиск по материалам, фильтры и векторный поиск в одном движке. &lt;a href="https://en.wikipedia.org/wiki/Okapi_BM25" rel="noopener noreferrer"&gt;BM25&lt;/a&gt; учитывает совпадения слов, а kNN по эмбеддингам OpenAI &lt;strong&gt;text-embedding-3-small&lt;/strong&gt; добавляет близкие по смыслу результаты. Это нужно, например, чтобы по запросу «коррупция на госзакупках» находились материалы антикоррупционного проекта РосПил, даже если в таком материале нет такой формулировки, но есть близкие, например «распил бюджета на тендерах».&lt;/p&gt;

&lt;p&gt;Модель эмбеддингов выбрали за поддержку разных языков и удобство интеграции: OpenAI уже использовался в конвейере. Она представляет текст вектором из 1536 чисел, по которому можно сравнивать близость материалов и запросов. По оценке разработчиков, расходы на их вычисление невелики. По их расчётам, создание эмбеддингов для всего корпуса стоит &lt;strong&gt;центы&lt;/strong&gt;, а для одного поискового запроса — порядка &lt;strong&gt;5·10⁻⁷ доллара&lt;/strong&gt;. Эмбеддинги запросов кешируются в Redis на неделю, поэтому повторный запрос не требует нового обращения к модели.&lt;/p&gt;

&lt;p&gt;У выдачи есть фильтры по источнику, типу, формату и датам. Для буквального совпадения можно ввести фразу в кавычках. Слова обрабатываются с учётом русского и английского языка, чтобы изменение формы слова не мешало поиску.&lt;/p&gt;

&lt;p&gt;В используемой нами конфигурации Elasticsearch без платной лицензии был недоступен RRF — механизм объединения результатов по их позициям в разных выдачах. Поэтому оценки полнотекстового и векторного поиска складываются без согласования шкал. Ранжирование получается приблизительным, но на нашей коллекции, по наблюдениям команды, работает уверенно.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6xcqp3hc8hpf3wippmxp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6xcqp3hc8hpf3wippmxp.jpg" alt=" " width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Почему поиск пришлось ускорять
&lt;/h2&gt;

&lt;p&gt;Найти подходящие публикации через поиск было половиной задачи: посетитель должен быстро получить выдачу и увидеть, где именно в найденном документе встречается его запрос. Когда мы начали нагрузочные тесты с k6, выяснилось, что сам поиск в Elasticsearch занимает миллисекунды, а задержки возникают на этапе fetch — получения найденных документов и подсветки совпадений. &lt;/p&gt;

&lt;p&gt;В &lt;strong&gt;_source&lt;/strong&gt; каждого документа лежало около 49 КБ векторов, ненужных при формировании ответа: kNN использовал отдельный индекс. Удалив эти данные из &lt;strong&gt;_source&lt;/strong&gt;, мы уменьшили размер документа примерно с 75 до 26 КБ.&lt;/p&gt;

&lt;p&gt;С подсветкой пришлось разбираться дольше. На наших текстах она занимала около 400 мс на страницу выдачи. Перебор движков заметного выигрыша не дал, а &lt;strong&gt;fvh&lt;/strong&gt; под нагрузкой тратил много CPU на построение структуры термов даже без совпадений. В нашей конфигурации на каждую ноду приходилось семь поисковых потоков; под нагрузкой fetch с подсветкой занимал около секунды, и две ноды упирались примерно в 13 запросов в секунду.&lt;/p&gt;

&lt;p&gt;Мы перенесли подсветку на бэкенд, который можно масштабировать горизонтально. Это позволило добавлять мощности, но время самой операции почти не изменилось.&lt;/p&gt;

&lt;p&gt;Первая версия проходила по всем словам документа, приводила их к основе с помощью стеммера и сравнивала с запросом. Ради нескольких совпадений приходилось обрабатывать тысячи слов. Замена Python-реализации стеммера на реализацию на C заметного выигрыша не дала.&lt;/p&gt;

&lt;p&gt;Тогда перед стеммером добавили отбор кандидатов: скомпилированное регулярное выражение ищет слова по префиксу, а стеммер проверяет только найденные. Фрагменты для показа строятся по мере обнаружения совпадений; когда их набралось достаточно, обработка останавливается.&lt;/p&gt;

&lt;p&gt;Стеммер сохранили, потому что один префикс захватывает лишние слова: например, «коррупц…» найдёт и «коррупцию», и «коррупционеров». Дополнительная проверка отсекает неподходящие совпадения. Пришлось учесть и языковые особенности: английский стеммер превращает &lt;strong&gt;country&lt;/strong&gt; в &lt;strong&gt;countri&lt;/strong&gt;, поэтому для отбора нужен общий префикс, а русские «е» и «ё» следует обрабатывать одинаково.&lt;/p&gt;

&lt;p&gt;В тестах время подсветки сократилось примерно с 400 до 30 мс. На проверочном корпусе объёмом больше миллиона символов команда не обнаружила расхождений с нативной версией. Эти измерения относятся к самой подсветке; время ответа всего поискового сервиса мы оценивали отдельно.&lt;/p&gt;

&lt;h2&gt;
  
  
  Как мы управляем нагрузкой
&lt;/h2&gt;

&lt;p&gt;После облегчения fetch следующим ограничением стал бэкенд. В отдельном тесте без подсветки система обрабатывала около 107 запросов в секунду при p95 в 1,8 секунды — 95% запросов укладывались в это время. Ответ с примерно двадцатью карточками весил около 66 КБ: в него входили полные описания и списки тегов, людей и мест. Сокращение этого объёма стало следующей задачей.&lt;/p&gt;

&lt;p&gt;Настройки, с которыми приходилось экспериментировать, вынесли в админку. Там можно включить семантический поиск, изменить его вес и порог близости, выбрать движок подсветки и режим сниппетов: без них, по описанию или по полному тексту. Там же задаются кеширование ответов и срок жизни кеша. Настройки хранятся в БД и читаются через 30-секундный кеш, поэтому для изменений не нужен деплой. Позже этот подход применили и к адаптивному троттлингу. Параметры устройства индекса, например число шардов, по-прежнему задаются при его создании.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ebn9qhp5ahtowut7ptk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2ebn9qhp5ahtowut7ptk.jpg" alt=" " width="799" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Для пиков нагрузки добавили кеш ответов в Redis и адаптивный троттлинг. Он следит за p95 времени ответа и при перегрузке отклоняет часть запросов с HTTP 429. Переиндексация тоже не требует останавливать сайт: новый индекс собирается рядом с действующим, после проверки на него переключается алиас.&lt;/p&gt;

&lt;h2&gt;
  
  
  Архив продолжает расти
&lt;/h2&gt;

&lt;p&gt;Сайт Архива мы делали с расчётом на то, что коллекция будет расти и после запуска.&lt;/p&gt;

&lt;p&gt;Каждый месяц мы добавляем примерно 100–200 материалов: видео, интервью, юридические документы и другие находки. Они проходят тот же путь, что и первые публикации: от найденного файла или бумажного оригинала через обработку и редакторскую проверку — к материалу на сайте, который можно открыть, прочитать и найти в поиске.&lt;/p&gt;

&lt;p&gt;Часть материалов присылают сторонники. Люди находят у себя записи и фотографии, делятся ими с нами, и мы продолжаем узнавать что-то новое об Алексее. Нам ещё многое предстоит найти и оцифровать, поэтому такая помощь очень важна. Если у вас есть подходящие материалы, напишите нам на &lt;a href="mailto:archive@navalny.com"&gt;archive@navalny.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://navalny.com/" rel="noopener noreferrer"&gt;Загляните в Архив&lt;/a&gt;: выберите подборку, откройте старое интервью или посмотрите, что сегодня появилось на главной. Мы надеемся, что вы найдёте там что-то, чего раньше не видели, даже если много лет следили за Алексеем.&lt;/p&gt;

&lt;p&gt;Продолжать эту работу нам помогает ваша поддержка. Пожертвования оплачивают хранение коллекции, работу сайта и подготовку новых публикаций. &lt;a href="https://navalny.com/donate/?utm_source=devto&amp;amp;utm_medium=email&amp;amp;utm_campaign=navalny_archive_article&amp;amp;utm_content=donate_ru" rel="noopener noreferrer"&gt;Поддержите команду ФБК&lt;/a&gt;, чтобы мы могли сохранять больше свидетельств жизни Алексея и открывать их людям по всему миру — тем, кто его помнит, и тем, кто только знакомится с ним.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtwdryptd6qe7k7v4f4d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtwdryptd6qe7k7v4f4d.jpg" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>ai</category>
      <category>digitalarchiving</category>
    </item>
    <item>
      <title>Как мы делали приложение «Фотон-2024»</title>
      <dc:creator>Navalny Team</dc:creator>
      <pubDate>Tue, 09 Apr 2024 12:58:49 +0000</pubDate>
      <link>https://dev.to/navalnyteam/kak-my-dielali-prilozhieniie--2ba0</link>
      <guid>https://dev.to/navalnyteam/kak-my-dielali-prilozhieniie--2ba0</guid>
      <description>&lt;p&gt;В рамках кампании против Путина на президентских выборах в России в 2024 году IT-отдел команды Навального разработал приложение под Android и iOS, которое помогало  случайным образом выбирать любого другого кандидата. Для этого приложение использовало аппаратный генератор случайных чисел, основанный на квантовом эффекте. Эту идею предложил Алексей Навальный. &lt;/p&gt;

&lt;p&gt;Задача разработки приложения включала в себя требования непосредственного подключения к аппаратному генератору в офисе ФБК, устойчивости приложения к нагрузкам и блокировкам и обеспечения приватности пользователя. В этой статье мы раскрываем некоторые технические подробности реализации.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s2rt76g17dwypz0e30r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s2rt76g17dwypz0e30r.png" alt="Основные экраны приложения" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Выбор устройства и покупка квантового генератора
&lt;/h2&gt;

&lt;p&gt;Алексей темой случайности озаботился еще во время изучения языка Python. Когда он столкнулся с функцией random, то заинтересовался, как работает случайность в скриптах, которые он писал во время обучения. Откуда конкретно берется случайное число? &lt;/p&gt;

&lt;p&gt;В 2019 году он узнал о том, что компьютерная случайность не есть истинная случайность, что это псевдослучайность. Когда выполняется random.random() в скрипте, случайное число не рождается во Вселенной из ничего, а основано на простой математике — на так называемых &lt;a href="https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%85%D1%80%D1%8C_%D0%9C%D0%B5%D1%80%D1%81%D0%B5%D0%BD%D0%BD%D0%B0" rel="noopener noreferrer"&gt;вихрях Мерсенна&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Изучение природы случайности Алексей продолжил и в заключении. Чтение биографии Эйнштейна и подробное знакомство с квантовой физикой дало ему идею, откуда можно брать истинную случайность — из фотонов, частиц света.&lt;/p&gt;

&lt;p&gt;В начале XX века Эйнштейн и Бор начали знаменитый спор о том, являются ли случайные процессы в квантовом мире лишь кажущимися, случайными, или же они истинно случайные. Этот спор с Бором многие знают по фразе Эйнштейна «Бог не играет в кости!». Так Эйнштейн выразил позицию, что истинной случайности нет, а есть просто скрытые параметры, которые и создают кажущуюся случайность в квантовых процессах. Через полвека после спора наука смогла проверить экспериментально через &lt;a href="https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D1%80%D0%B0%D0%B2%D0%B5%D0%BD%D1%81%D1%82%D0%B2%D0%B0_%D0%91%D0%B5%D0%BB%D0%BB%D0%B0" rel="noopener noreferrer"&gt;неравенства Белла&lt;/a&gt;, кто же прав, — и прав оказался Бор. Его интерпретация квантовой механики, называемая копенгагенской, до сих пор &lt;a href="https://arxiv.org/pdf/1301.1069.pdf" rel="noopener noreferrer"&gt;главенствует&lt;/a&gt; в науке.&lt;/p&gt;

&lt;p&gt;Когда в конце сентября 2023 возник &lt;a href="https://meduza.io/feature/2023/10/02/kats-prizval-hodorkovskogo-i-komandu-navalnogo-ob-edinitsya-protiv-putina-pered-vyborami-2024-goda-i-tut-nachalos" rel="noopener noreferrer"&gt;спор о стратегии голосования&lt;/a&gt; на президентских выборах, Алексей вспомнил идею о случайности, создаваемой фотонами, и предложил переложить моральную ответственность выбора «любого другого кандидата», за которого можно было бы проголосовать в рамках кампании против Путина, на генератор истинно случайных чисел. Так родилась идея приложения «Фотон», где реальный фотон будет определять случайного кандидата.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y3o3hwqy5hj9ewt8vfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0y3o3hwqy5hj9ewt8vfg.png" alt="Письмо Алексея с идеей приложения" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Что важно: Алексея не устраивал простой псевдорандом, ему нужен был реальный фотон, летящий и делающий выбор для каждого конкретного человека.&lt;/p&gt;

&lt;p&gt;От идеи получать истинную случайность по &lt;a href="https://aws.amazon.com/marketplace/pp/prodview-246kyrfjo3bag" rel="noopener noreferrer"&gt;API в AWS&lt;/a&gt;, где случайность брали бы у австралийских физиков, сделавших лазерную установку, и получали бы энтропию из квантовых флуктуаций вакуума, он отказался.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74pdwrqhqwfsy3fflu2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74pdwrqhqwfsy3fflu2a.png" alt="Письмо Навального о персональном фотоне" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Для надежности мы решили использовать две установки, основную и резервную. Обе от ID Quantique, обе на квантовых эффектах, но на разных. Одна использует &lt;a href="https://ru.wikipedia.org/wiki/%D0%94%D1%80%D0%BE%D0%B1%D0%BE%D0%B2%D0%BE%D0%B9_%D1%88%D1%83%D0%BC" rel="noopener noreferrer"&gt;дробовой шум&lt;/a&gt; при облучении слабым источником света CMOS-матрицы, что вызывает флуктуации количества детектируемых квантов света каждой из ячеек матрицы. Эта установка, &lt;a href="https://marketing.idquantique.com/acton/attachment/11868/f-9a58efcf-5169-45ba-8a1c-ceea3761581f/1/-/-/-/-/Quantis%20QRNG%20PCIe%20New%20Generation_Brochure.pdf" rel="noopener noreferrer"&gt;Quantis QRNG PCIe&lt;/a&gt;, дает порядка 40 Мбит/с энтропии. Вторая, &lt;a href="https://marketing.idquantique.com/acton/attachment/11868/f-021f/1/-/-/-/-/Quantis%20QRNG_Brochure.pdf" rel="noopener noreferrer"&gt;Quantis QRNG USB&lt;/a&gt;, подключаемая по USB и содержащая полупрозрачное зеркало, на которое направляется поток одиночных фотонов, выдает около 4 Мбит/с энтропии. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpw39gjb4fneb2nsyq4ae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpw39gjb4fneb2nsyq4ae.png" alt="Изобаржение квантового генератора" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Квантовый генератор случайных чисел с полупрозрачным зеркалом работает следующим образом. Источник раз за разом испускает одиночный квант света. Тот налетает на полупрозрачное зеркало, через которое проходит 50% света, а 50% отражается. И, поскольку фотон неделим (слава Планку и Эйнштейну), он либо пролетает через зеркало, либо отражается. Детекторы определяют, куда попал фотон, и так генерируется случайный выбор, 0 или 1. Для компенсации неравнозначности нуля и единицы в случае, если зеркало имеет не идеально точную 50-процентную пропускную способность, используется постобработка по &lt;a href="https://babel.hathitrust.org/cgi/pt?id=osu.32435030295547&amp;amp;view=image&amp;amp;seq=48" rel="noopener noreferrer"&gt;алгоритму Джона фон Неймана&lt;/a&gt;. С ее помощью определенные выше биты группируются по два, что в четыре раза уменьшает получаемую энтропию, но делает ее unbiased.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfnyypsppzxb6fx4y0q0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfnyypsppzxb6fx4y0q0.png" alt="Схема работы квантового генератора случайных чисел" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Схема работы квантового генератора случайных чисел&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Физическое подключение и первоначальная настройка
&lt;/h2&gt;

&lt;p&gt;Вместе с устройством, помимо руководств по эксплуатации, поставляются исходники драйверов, системные требования (по большей части с упором в программную часть) и библиотека на C.&lt;/p&gt;

&lt;p&gt;Так как драйвер генератора требовал конкретные версии ядра Linux и системных пакетов, мы развернули виртуальную машину с CentOS 7 как самой стабильной из перечисленных в документации поддерживаемых операционных систем.&lt;/p&gt;

&lt;p&gt;Сам генератор был установлен в офисный сервер. Доступ к нему пробросили в виртуальную машину через гипервизор — систему управления виртуальными машинами. Предварительно установили на хост-систему драйвер для корректного отображения устройства и выполнения инструкций проброса. &lt;/p&gt;

&lt;p&gt;После определения генератора в системе скомпилировали драйвер. Заморозили обновления для ядра и используемых драйвером системных пакетов, чтобы случайно не сбить работу системы обновлениями. Только после этого можно было передать разработчикам доступ к виртуальной машине.&lt;/p&gt;

&lt;p&gt;Ввиду того, что документация, технические характеристики, исходный код драйвера — защищенная NDA собственность компании-производителя, в сети отсутствует подробная информация о генераторе, а также темы с решением проблем на форумах. Любые вопросы, касающиеся генератора и его ПО, можно задать только официальным представителям техподдержки.&lt;/p&gt;

&lt;p&gt;В комплекте, помимо самого устройства, находился сертификат, подтверждающий подлинность генератора и результаты проверки работоспособности конкретного устройства.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbju8o3xqx91bmk3l17l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbju8o3xqx91bmk3l17l9.png" alt="Сертификат квантового генератора" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Архитектура приложения и алгоритм работы
&lt;/h2&gt;

&lt;p&gt;В связи с тем, что пользователю приложения необходимо было подключаться к квантовому генератору, размещенному в офисе ФБК, без сетевого соединения на стороне приложения было никак не обойтись. А значит, Роскомнадзор имел бы все шансы идентифицировать пользователя и попытаться заблокировать адреса, по которым он обращался к генератору при помощи СОРМ и ТСПУ. Мы решили построить архитектуру приложения, исходя из устойчивости к потенциальным блокировкам, нагрузкам от большого количества запросов и сохранения приватности пользователя. &lt;/p&gt;

&lt;p&gt;Приложение должно было быть реализовано на двух платформах (Android и iOS), а также работать на планшетах — выбор пал на Flutter. В будущем плюсами данного выбора станут скорость и гибкость в разработке, но из-за отсутствия достаточного количества библиотек некоторые решения пришлось писать вручную. В нашем случае это была кастомная библиотека DNS over HTTPS (DoH) для работы по протоколу &lt;a href="https://datatracker.ietf.org/doc/html/rfc8484" rel="noopener noreferrer"&gt;RFC 8484&lt;/a&gt;. Вместо стандартного сетевого соединения клиент генерировал случайную строку из 20 символов a-z0-9 и через DoH запрашивал один поддомен дискавери-домена (скажем, &lt;code&gt;w4a1s27c81syputinvor.photon.navalny-team.org&lt;/code&gt;) через случайный публичный резолвер, например 1.1.1.1 от Cloudflare или 8.8.8.8 от Google. В качестве фолбэка было добавлено еще несколько альтернативных, чтобы Роскомнадзор не решался блокировать любой из них под угрозой сломать множество приложений российского сегмента интернета.&lt;/p&gt;

&lt;p&gt;Приложение проверяло, чтобы ответ сервера состоял ровно из двух записей TXT: с префиксами «N» для имени кандидата и «D» — для его описания в utf-8. Необходимо было, чтобы записи были минимально возможного размера, чтобы ответ влез в ограничения ответа DNS — 512 байт. Разгоняться с контентом было нельзя, и поэтому приложение не выдавало длинных биографий кандидатов или их фотографий, тем более что особого значения это не имело. Если вы читаете эту статью вскоре после ее публикации, возможно, сервер еще работает и вы можете сгенерировать себе еще кандидатов командой &lt;code&gt;host -t txt w4a1s27c81syputinvor.photon.navalny-team.org&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zlrcq441nel2ngrjeic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zlrcq441nel2ngrjeic.png" alt="Схема архитектуры приложения ФОТОН-2024" width="800" height="636"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Схема архитектуры приложения «Фотон-2024»&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Клиентская библиотека генератора случайных чисел написана на C и представляет собой довольно простой интерфейс: перечисление подключенных устройств, получение версий, открытие-закрытие сессии и, собственно, запрос на получение случайных чисел.&lt;/p&gt;

&lt;p&gt;Сервер HW RNG, к которому был подключен генератор, при запуске устанавливал TCP-соединение с DNS-сервером (по отдельному внутреннему порту) и без остановки лил туда случайные числа. Поскольку сервер считывал данные из сокета только по мере поступления запросов от пользователей, рано или поздно backpressure от сервера заставлял клиент заблокироваться. Энтропию из буфера брали в порядке &lt;a href="https://ru.wikipedia.org/wiki/LIFO" rel="noopener noreferrer"&gt;LIFO&lt;/a&gt;, чтобы для пользователей брались «свежие» фотоны.&lt;/p&gt;

&lt;p&gt;DNS-сервер реализовывал основную логику бэкенда. При запуске он считывал JSON-файл с кандидатами с бакета GCS и обновлял его в фоне каждые 10 секунд на случай изменений. Сервер считывал данные от HW RNG и писал их в буфер фиксированного размера на случай поступления спайков клиентских запросов. Размер буфера можно сделать таким, сколько памяти не жалко — хоть гигабайт.&lt;/p&gt;

&lt;p&gt;Сервер получал запрос с утвержденным адресом нашего дискавери, брал нужное количество байт энтропии и делал выбор кандидата в соответствии с текущим конфигом, полученным с GCS. Ответ упаковывался в TXT-записи DNS.&lt;/p&gt;

&lt;p&gt;Чтобы сделать работу системы максимально надежной, было реализовано несколько аварийных режимов:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Если по каким-то причинам энтропии в буфере было бы недостаточно (например, сломался сервер HW RNG или пропал интернет в офисе ФБК), DNS-сервер сгенерировал бы случайное число из /dev/urandom. Пользователь бы об этом не узнал, но администраторы получили бы оповещение. К слову сказать, этого так ни разу в продакшене и не случилось — все случайные числа были сгенерированы настоящими фотонами.&lt;/li&gt;
&lt;li&gt;Если бы у пользователя оборвалось соединение, отключился интернет или приложение было бы заблокировано Роскомнадзором, на такой случай в него была заложена функция локального рандома для выбора кандидата из списка, которое приложение в фоновом режиме получало с GCS.&lt;/li&gt;
&lt;li&gt;На случай, если бы свежеустановленное приложение так ни разу и не смогло обновить список кандидатов и DNS-сервер не отвечал, в нем еще был захардкожен список с кандидатами на дату сборки приложения.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Если приложению не удавалось подключиться к квантовому генератору в офисе ФБК и оно было вынуждено делать локальную генерацию, то оно честно в этом признавалось: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjclhmbk7bd398wox3c2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjclhmbk7bd398wox3c2.png" alt="Экран сбой подключения к Фотону" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Для хранения данных в приложении использовались защищенные хранилища — Keychain (iOS) и KeyStore (Android).&lt;/p&gt;

&lt;p&gt;Для отправки запроса в «фотонную пушку» по нажатию кнопки в приложении и получения ответа нужны были считанные миллисекунды: пользователь дольше любовался красивой анимацией, реализованной отделом дизайна. &lt;/p&gt;

&lt;p&gt;Бот «Умного голосования» для мессенджеров аналогично обращался к DNS серверу, получал «квантового кандидата», и ответ в нем был мгновенным.&lt;/p&gt;

&lt;h2&gt;
  
  
  Заключение
&lt;/h2&gt;

&lt;p&gt;Для контроля работы и сбора статистики были организованы дашборды в Grafana. Записывались запросы к квантовому генератору и состояние буфера энтропии. Был подключен Firebase c инструментарием Google Analytics на стороне приложения.&lt;/p&gt;

&lt;p&gt;«Фотонная пушка» сработала более 650 тысяч раз.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07lhj3064cnwrptq6wzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07lhj3064cnwrptq6wzj.png" alt="График распределения запросов к квантовому генератору из приложения и телеграм-бота&lt;br&gt;
" width="800" height="356"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;График распределения запросов к квантовому генератору из приложения и телеграм-бота&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Приложением воспользовались более 70 тысяч человек. Оно попало в топы рейтингов сторов в своей категории. Роскомнадзор даже &lt;a href="https://t.me/news_sirena/25646" rel="noopener noreferrer"&gt;ограничил доступ к сайту Google Play Market&lt;/a&gt; в день выпуска.&lt;/p&gt;

&lt;p&gt;Задача сделать приложение, которое подключалось бы к квантовому генератору и выдавало бы один результат, звучит просто. Но в условиях ограничений интернета в России она вынуждает разработчиков продумывать в своей архитектуре дополнительные меры безопасности и дополнительный функционал для борьбы с репрессивными органами, которые не только ограничивают свободу, но и ломают российский интернет. Нашим долгом было воплотить идею Алексея Навального и продолжить борьбу с Путиным.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Все это стало возможным только благодаря вашей помощи. Поддержать нашу работу можно по ссылке: &lt;a href="https://donate.fbk.info/it" rel="noopener noreferrer"&gt;https://donate.fbk.info/it&lt;/a&gt;. Чтобы сделать пожертвования еще безопаснее, мы даже добавили на сайт &lt;a href="https://donate.fbk.info/#crypto" rel="noopener noreferrer"&gt;функцию генерации одноразового биткоин-адреса&lt;/a&gt; и обязательное смешивание всех полученных средств через миксер.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>flutter</category>
      <category>devops</category>
      <category>digitalresistance</category>
    </item>
    <item>
      <title>Security of Navalny's underground resistance on the Dark Web</title>
      <dc:creator>Navalny Team</dc:creator>
      <pubDate>Wed, 25 Oct 2023 10:53:25 +0000</pubDate>
      <link>https://dev.to/navalnyteam/security-of-navalnys-underground-resistance-on-the-dark-web-1lga</link>
      <guid>https://dev.to/navalnyteam/security-of-navalnys-underground-resistance-on-the-dark-web-1lga</guid>
      <description>&lt;p&gt;In 2017, Navalny's team started an underground resistance in Russia. Initially serving as pre-election hubs, they later evolved into regional political centers. This network of these centers posed a significant threat to Putin, leading to a relentless escalation of repression tactics, including raids, account freezes, employee arrests, physical assaults, and menacing threats. In 2021, the authorities falsely concocted an extremism case, ultimately forcing the shutdown of those centers, all purportedly in the name of safeguarding those involved.&lt;/p&gt;

&lt;p&gt;In December 2022, our team relaunched Navalny's resistance movement, but this time in a form of an underground organization. Our primary objective was to create a secure platform for online communication and collaboration among coordinators and activists.&lt;/p&gt;

&lt;p&gt;In developing our security system, we avoided relying on security through obscurity. By sharing the information in this article, we are not increasing the risks but, instead, providing experts with an opportunity to evaluate our security measures and assess their relevance and depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the system
&lt;/h2&gt;

&lt;p&gt;The website of Navalny's underground resistance provides each user with a personal account. Activists sign up based on their professional and geographical affiliations. The platform offers participants assignments tailored to their specialties and interests, providing them a possibility for direct communication with coordinators. Users earn points for completing assignments, with that achieving specified levels, and then can purchase avatars as rewards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6hi9nm57ur4fcs2k0mq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6hi9nm57ur4fcs2k0mq.jpeg" alt="Personal account interface screen" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In August 2023, the platform underwent a significant update, introducing a messenger feature that facilitates anonymous communication among participants while they are working on assignments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Threat and attacker modeling
&lt;/h2&gt;

&lt;p&gt;Our system development commenced with threat modeling, where we outlined the objectives that potential attackers might pursue. We were acutely aware of the risks faced by those in Russia who support our cause, and we prioritized their safety above everything else. We identified the primary threat as the potential exposure of the activists' identities. Additionally, we acknowledged secondary threats, which encompass denial of user access to the service, jeopardizing confidential projects and plans, compromising the integrity of our service's software, and moving laterally to other services after a potential compromise.&lt;/p&gt;

&lt;p&gt;During the second phase, we profiled potential intruders, identifying who might act as our adversaries — primarily, corrupt law enforcement agencies or special services, and Internet censorship enforcement entities. We then assessed their capabilities for attaining their objectives. We considered four primary attack vectors: targeting our systems through hacking, conducting searches on activists, compromising our employees through bribery, infiltration, or threats, and the most straightforward approach: encountering a "Comrade Major" in an online chat room.&lt;/p&gt;

&lt;p&gt;Threat and attacker modeling serve as the bedrock of information security. Our priorities often meant we had to make product decisions that could negatively impact user experience and retention. For instance, we couldn't create a user-friendly mobile app with a clever workaround for evading censorship, equipped with notifications to improve user retention, like the ones in the “Smart Voting” app. That limitation arose from the concern that such an app would become easy to discover if someone were to search the phone of an activist.&lt;/p&gt;

&lt;p&gt;Once we pinpointed the threats that demanded our attention, we initiated planning of a layered security system comprising multiple security perimeters. With each additional barrier we erected — such as authentication or a firewall — we asked ourselves, "Alright, but what if these defenses are bypassed? What potential actions could an attacker take afterwards?" Our goal was to ensure that any breach would fall short of facilitating a successful attack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tor
&lt;/h2&gt;

&lt;p&gt;One pivotal decision we made was hosting the website on the dark web. Standard web browsers not only expose a wealth of user information, including device metadata, IP addresses, but also leave behind extensive traces on the user's device, like browsing history and caches. We made the deliberate choice to exclusively employ the Tor browser to access our system, as it minimizes these risks and takes numerous other measures to safeguard users.&lt;/p&gt;

&lt;p&gt;This approach complicates the process of implicating a user in the organization's activities in the event of device seizure. While it might be evident that the Tor application is installed on a device, it would remain unclear what purpose it served. It's worth noting an interesting tidbit: Edward Snowden used Tor to communicate with journalists when disclosing classified information about the NSA. As a side note, Tor is being developed by a nonprofit organization, so if you can, consider supporting our fellow colleagues financially — &lt;a href="https://donate.torproject.org/" rel="noopener noreferrer"&gt;donate.torproject.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tor browser can also serve as a means to evade Roskomnadzor's (the Russia’s Media Supervision Agency, or simply saying the main censorship body of the country) surveillance. With the widespread deployment of the DPI (Deep Packet Inspection) equipment by most ISPs in Russia, concealing access to HTTPS websites has become considerably more challenging. Mainstream browsers still lack the ECH (Encrypted Client Hello) protocol support, and while Roskomnadzor may not discern the data exchanged between the user and the site, it can track plenty of metadata, e.g. which user has accessed which domains. Consequently, we opted to host the site on the .onion domain, rendering it inaccessible through regular browsers. This ensures that users cannot inadvertently stumble upon the website and register that fact in DPI logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal data
&lt;/h2&gt;

&lt;p&gt;To sign up within the system, an activist is not required to disclose any personally identifying information. We only identify activists through their chosen pseudonyms, without any attempts to uncover their true identities. Every activist is assigned a reputation, quantified in points. These points accrue as supporters successfully complete tasks, raising their trustworthiness. As a result, more confidential and significant assignments can be delegated to them. So if a "Comrade Major" seeks access to classified projects, they must first undertake simpler tasks, such as distributing flyers or creating protest graffiti. Let’s give them a chance to finally do something useful!&lt;/p&gt;

&lt;p&gt;To sign up for the system, a participant is required to create a pseudonym (username), a suitably complex password, and provide a brief description of their professional skills in an open format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvnodaoksahsbs4zmr5r.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvnodaoksahsbs4zmr5r.jpeg" alt="Sign up interface screen" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Actual names, email addresses, and phone numbers are not collected (except for an email address for the first contact, which is then erased from the database). All registrations undergo an extra level of review, following which users gain access to the system's complete functionality. The moderation process serves as a safeguard against excessive registration attempts. Given that Tor users remain anonymous, Tor shields malicious users who could inundate the system with automated bot submissions and disruptive messages from us. To ensure minimal disruption to the site's operation, our moderators scrutinize and filter out irrelevant or spam registrations. They also categorize supporters based on their professional interests (such as software engineers, designers, attorneys, etc.) before granting them access to the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69xei5k4g8xp31rzmcgg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69xei5k4g8xp31rzmcgg.jpeg" alt="Moderation awaiting interface screen" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Insider risks
&lt;/h2&gt;

&lt;p&gt;Insiders refer to our staff members, including coordinators, moderators, software engineers, system administrators, and so on. They possess additional access to the system, which could potentially be exploited by attackers. While we have confidence in our employees, we still implement precautions in case they are compromised. This is because an employee might not only willingly become an attacker (due to ideological reasons or bribery) but could also fall victim to hacking (remember that phishing remains a threat) or be coerced into cooperation, possibly through pressure on their family members in Russia.&lt;/p&gt;

&lt;p&gt;To safeguard against insider threats, we take a few measures. Firstly, we adhere to the principle of least privilege, ensuring that employees only have access essential for their immediate responsibilities. For instance, software engineers can access the code repository but not the servers, while testers can access the testing environment but not the production environment. Secondly, we employ multiple security layers to shield against insider threats. For example, even if system administrators were to dump the database or exfiltrate files from the downloaded file storage, they would be unable to decrypt them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption
&lt;/h2&gt;

&lt;p&gt;We apply encryption to protect all sensitive database fields and uploaded files. Our solution involves using symmetric encryption with authentication (AEAD), which serves as a protection against tampering with the encrypted content. Importantly, the encryption keys are not retained within the application itself but are securely stored in Google's Cloud KMS (Key Management System). Additionally, we employ envelope encryption, that entails encrypting each file with its unique ephemeral key, that is in turn wrapped by KMS.&lt;/p&gt;

&lt;p&gt;Cloud KMS keys are not exportable and are stored within a separate Google Cloud project. Access to this project is restricted to a select group of highly trusted administrators. Permission for encryption and decryption using these keys is exclusively granted to service accounts linked to workload identities of services that need to interact with the database.&lt;/p&gt;

&lt;p&gt;This encryption approach minimizes the security perimeter of the data processed by the service. For instance, even if an administrator accesses the database, they won't be able to see the plaintext. The same holds true for backups. Furthermore, even systems responsible for creating backups do not need to decrypt the data. Everything is securely backed up directly in the encrypted state, and in order to compromise the data, one would also need to acquire access to the KMS keys, significantly raising the difficulty level of any potential data compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attacks on the application
&lt;/h2&gt;

&lt;p&gt;If the application itself is compromised due to a technical vulnerability, an attacker can potentially access everything within the application's purview. It is the system's most valuable asset, and a successful attack could yield significant damage. To mitigate this risk, we implement two key strategies. Firstly, we refrain from storing any personal data of our users, ensuring that even in the event of data exfiltration, it would be exceptionally challenging to identify supporters. Secondly, we make concerted efforts to minimize the attacker's ability to establish a foothold within the system.&lt;/p&gt;

&lt;p&gt;As our servers run on Google Kubernetes Engine (GKE), they consist of virtual machines that undergo regular updates and are frequently replaced by fresh ones. We employ Google's Container-optimized OS (COS) as the base image, known for its security posture. When it comes to service authentication, we avoid using exported JSON keys of service accounts, which can be susceptible to theft and long-term misuse. Instead, we rely on GKE Workload Identity, which furnishes applications with temporary tokens that have a brief lifespan, typically lasting only a few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;The entire infrastructure is segregated into two distinct environments: trusted and untrusted. The untrusted environment, devoid of sensitive data, is primarily dedicated to development and testing purposes. Conversely, the trusted environment encompasses all components with access to protected data.&lt;/p&gt;

&lt;p&gt;The application code and containers are built by specialized secure virtual machines known as "runners." The runners operate in an isolated environment separate from other projects and engineers. Access to the project where the runners operate is restricted to a limited number of trusted engineers, and even then, direct access is only permitted in the event of runner failures. Service accounts associated with these machines hold administrative access to the production environment. This enables adoption of the gitops approach, resulting in meticulously documenting a comprehensive history of all production changes in both git logs and Google Cloud audit logs. Such dual audit setup allows for retrospective tracing of potential attacks.&lt;/p&gt;

&lt;p&gt;We use the untrusted environment for building code from unprotected GitLab branches that are not considered secure. These branches are open for any developer to commit changes for review or deployment to test servers. Even if a developer were to introduce something malicious that compromises the build server, it would not grant them access to the production environment. Following a review by another team member and the subsequent merging of the branch into the primary production branch, the code undergoes build and deployment within the trusted environment, dedicated to production.&lt;/p&gt;

&lt;p&gt;To improve cost-efficiency and achieve better server utilization, we use Kubernetes. For our build system, we've developed an in-house solution, allowing virtual machines with GitLab runners to run only when they are needed. The runner management system itself runs in the trusted environment, bolstering our overall security measures.&lt;/p&gt;

&lt;h2&gt;
  
  
  DoS attacks
&lt;/h2&gt;

&lt;p&gt;When the system was launched, the team anticipated that, unlike in the open Internet, attackers would find it costly to carry out DoS attacks within the Tor network. However, attackers wasted no time, and our monitoring systems quickly detected a significant surge in server load. In the beginning, we were prepared to filter traffic only at the HTTP level, and initially it was sufficient. However, attackers switched tactics to more sophisticated ones, exploiting numerous ways to overwhelm our Tor client.&lt;/p&gt;

&lt;p&gt;To provide some context, it's essential to understand how onion sites work. Unlike in regular Internet, where clients directly connect to servers, in Tor, both clients and servers establish connections with specialized introduction nodes. Those nodes act as intermediaries to introduce clients and servers to each other. For data transfer, after being introduced, both parties connect to rendezvous nodes through secure tunnels. The rendezvous servers then consolidate both tunnels into a single circuit, facilitating transmission of regular HTTP traffic between the clients and the servers.&lt;/p&gt;

&lt;p&gt;In our case, the attackers flooded us with a massive influx of connection requests, effectively forcing our server to initiate numerous connections with rendezvous servers, which involved computationally intensive asymmetric cryptography. Notably, they never even attempted to transmit any data through these established tunnels, and the attack never reached the HTTP servers.&lt;/p&gt;

&lt;p&gt;Standard defense mechanisms against DoS attacks, such as identifying the sources and temporarily blocking them, are unfeasible within the Tor network because we lack any means to identify the clients. Consequently, we developed a specialized solution. The key challenge was that a typical onion server could not scale efficiently. When an onion server is launched, it connects to the Tor network and publishes a service descriptor that points exclusively to itself. If one were to initiate a second instance of the same server, it would overwrite the descriptor of the first instance, rendering them unable to function at the same time.&lt;/p&gt;

&lt;p&gt;To tackle this issue, we employed onionbalance, a third-party tool that enabled us to deploy multiple onion servers at once, each operating on its unique, randomly generated onion address. Subsequently, onionbalance retrieved their descriptors from the network, consolidated them into a unified descriptor, and then published that combined descriptor as the primary onion site's descriptor. This approach effectively distributed the traffic load across the servers, enabling them to handle traffic surges.&lt;/p&gt;

&lt;p&gt;Here's an overview of our solution's architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fts5v3q9y8qw29om8gv2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fts5v3q9y8qw29om8gv2g.png" alt="Architecture screen" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tor-balancer serves as a wrapper around onionbalance, interacting with our cluster to obtain a list of healthy and active Tor server instances. It then creates a configuration for onionbalance and runs the balancer. If the collection of active instances change, for instance, if some instances go offline or new ones come online, the configuration is automatically regenerated, and onionbalance is restarted.&lt;/p&gt;

&lt;p&gt;Meanwhile, Tor-frontend wraps a standard onion server. It generates a random onion address, starts an onion server at that address, waits until it successfully registers itslef and becomes accessible on the Tor network, and subsequently starts responding to HTTP requests with its designated onion address. This HTTP interface is what allows tor-balancer to discover which individual onion addresses should be merged to construct the final descriptor. This way, we evenly distribute the load across multiple onion addresses using "discovery" descriptors, preventing any single address from being overloaded by an attacker and causing denial of service.&lt;/p&gt;

&lt;p&gt;The client initiates a connection with the server once, which is typically a slow and resource-intensive operation. Afterward, within that same connection or circuit, it can establish numerous logical connections, which are faster and more resource-efficient. To counteract potential attacks, we enforce a rule: if a client establishes an excessive number of connections within its circuit, we either terminate the circuit entirely or impose rate limits using ingress-nginx. This strategy allows our system to effectively counteract such attacks.&lt;/p&gt;

&lt;p&gt;The second layer of defense involves relinquishing server privacy. Since we are not a dark market and take pride in openly declaring ownership of our service, there's no point for the server to establish a triple-wrapped connection to a rendezvous node. Instead, we utilize a Tor option that enables our server to form a direct connection. This significant adjustment has led to a substantial reduction in the amount of asymmetric cryptography performed on the server, significantly reducing its workload. Client privacy remains unaffected, and the server's operations become more efficient. As a result, we have successfully thwarted any further DoS attacks on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinators
&lt;/h2&gt;

&lt;p&gt;Within this system, any participant has the capability to send a message to a coordinator and receive a response. For instance, they can report the completion of an assignment or ask a question.&lt;/p&gt;

&lt;p&gt;To facilitate the coordinators' responsibilities, we employ a dedicated administrative panel built on the Django framework. Its functionality proves more than sufficient for efficiently managing sign-up requests, assigning tasks, assessing their completion, and engaging in private chats with activists. Moderators are not privy to users' real usernames; instead, they are assigned impersonal sequential numbers to enable another level of pseudonymization. All administrators are mandated to employ two-factor authentication, and their access is restricted by job role, with access exclusively allowed through a VPN. We also maintain audit logs to enable tracking of which staff member utilized which access.&lt;/p&gt;

&lt;p&gt;Team coordinators were tasked with distributing and grading assigned tasks, supervising execution and maintaining communication with the activists through private chats. This indirect process necessitated human resources and raised the issue of delayed feedback. To address it, we decided to launch public group chats based on the platform, allowing supporters to interact directly with each other within a task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chats
&lt;/h2&gt;

&lt;p&gt;In terms of both functionality and design, the chat interface closely resembles the interface of a messenger app like Telegram. Users can seamlessly switch between chats, read, compose, and delete messages, respond to messages from others, and clear message histories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiqegh5mcv6cb3fee0s8l.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiqegh5mcv6cb3fee0s8l.jpeg" alt="Chat interface screen" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our chats run on top of websockets over the Tor network. Every chat message is encrypted at rest with Cloud KMS. To prevent an excessive number of requests to KMS, the system incorporates a local cache in the application's memory for plaintext messages.&lt;/p&gt;

&lt;p&gt;Users have the option to manually delete chat messages, and messages are also automatically deleted after a certain period of time.&lt;/p&gt;

&lt;p&gt;To safeguard chat rooms against message spam, the system enforces a limit on the number of messages a user can send within a given time frame. Once this limit is reached, further messages will not be accepted. Additionally, we conducted comprehensive load testing of the chats with various scenarios to ensure that the server can withstand the anticipated load.&lt;/p&gt;

&lt;p&gt;We've implemented measures to safeguard chat users from potential metadata-based identification. The concern here is that attackers with access to SORM (the police system for traffic supervision) can detect when users access Tor. While they may not discern the specific activities users engage in, concealing their presence becomes quite challenging. Attackers could enter a popular chat room within the system, send bulky messages of a certain size, and exploit SORM data to identify users across Russia who received messages of the same size at the same time. Although this identification process may yield noisy results and somewhat fuzzy identification, repeated attempts could eventually allow them to reliably ascertain which Russian users were present in the chat room during that time. To substantially complicate such an attack, we introduced randomness in both the message sizes delivered to users and the time intervals between these messages. This deliberate variation obscures the signal, reducing it to a level beneath the noise, thereby making identification through metadata exceedingly difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anonymity
&lt;/h2&gt;

&lt;p&gt;The deepest level of our defense is anonymity. Even if all the abovementioned security measures were to fail—servers get compromised, coordinators are compromised, and administrators face threats—the only thing an attacker would obtain is solely a record of anonymous-to-anonymous communication that doesn't disclose any personally identifying information.&lt;/p&gt;

&lt;p&gt;However, even without any security breaches, publicly accessible chat rooms are susceptible to spammers, provocateurs, and other malicious individuals. To mitigate those risks, we implement some protective measures.&lt;/p&gt;

&lt;p&gt;To begin with, each chat room is allocated a moderator who can see slightly more than a regular user. The moderator has access to both the merged feed, where all messages subject to moderation are routed to, and a violations feed, which contains reports from other users regarding rule violations. Within these administrative chats, the moderator holds the ability to delete messages and impose temporary or permanent bans on users. Importantly, the moderator need not be an administrator with access to the admin panel over VPN; they could be an ordinary user signed in within their personal account, albeit endowed with extra privileges.&lt;/p&gt;

&lt;p&gt;Furthermore, to thwart any attempts by an individual like "Comrade Major" to amass enough data to indirectly identify a user, we've instituted an additional mandatory layer of pseudonymization. Within a chat room, each user is allocated a randomly generated alias distinct from the one they use for their primary sign-in—this is the alias visible to other participants. The usernames of the activists that they use when logging into their accounts remain secret and are not displayed anywhere. We've prearranged a selection of adjective-noun combinations to ensure that all participants sport amusing and unique aliases. Consequently, users encounter each other as "Spotted Hippogryph" or "Cute Cat." Users also have the option to change their alias to another randomly generated one if desired.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx675o0n84a6vlga6eut.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx675o0n84a6vlga6eut.jpeg" alt="Username random generator interface screen" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Fedor bot, being constant present in the chat room, serves as a reminder to participants about safety protocols. For instance, it advises them not to disclose any personal information about their real lives and encourages them to regularly rotate their nicknames to sever ties with their past online identities. Functioning much like a regular chat participant, the bot selects a random message from its knowledge base and shares it within the chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  File uploads
&lt;/h2&gt;

&lt;p&gt;The messenger doesn't just facilitate exchange of short messages; it also allows image uploading. We've taken measures to ensure that the metadata associated with the images uploaded to a shared chat room is automatically stripped off, preventing anyone from ascertaining details such as the creator, creation date, and location.&lt;/p&gt;

&lt;p&gt;To accomplish this, we opted for exiftool as our solution for metadata removal. It supports the broadest range of formats and is easy to use. However, it's worth noting that exiftool relies on numerous codecs written in C, a language that makes it easy to introduce vulnerabilities enabling remote code execution.&lt;/p&gt;

&lt;p&gt;To mitigate the risk of a compromised exiftool affecting the entire application, we've isolated it as a separate microservice within a sandboxed environment. This setup ensures that if a malicious file is encountered, hackers won’t be able to breach the primary service or establish a foothold on the server, thereby preventing interception of other users' files. This microservice runs within Google's serverless Cloud Functions execution environment, which limits the lifespan of an instance and restricts its access to the database and other microservices at the network level.&lt;/p&gt;

&lt;p&gt;During the initial day of the chat rooms’ launch, our security measures were put to the test when malicious testers attempted to shell our system with a genuine zip bomb disguised as an image containing millions of empty pixels. A zip bomb, in this context, refers to an archive filled with billions of zeroes that have been compressed to an extraordinarily compact size by a compression algorithm. If a recipient attempts to decompress it, they would rapidly deplete their available memory or storage, or even both.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_decompression_bomb_check&lt;br&gt;
    raise DecompressionBombError(&lt;br&gt;
PIL.Image.DecompressionBombError: Image size (50625000000 pixels) exceeds limit of *N* pixels, could be decompression bomb DOS attack.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, the bomb was defused as our library's security measures kicked in. However, even if it had exploded, it would not have caused any harm to the primary service thanks to the isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We've developed a unique tool that empowers supporters and activists in Russia to challenge the regime more efficiently while maintaining secure communication and minimizing the risk of being exposed. This endeavor was made possible through generous donations from our supporters, and we express our heartfelt gratitude to them! You can also become a part of the underground resistance and lend your support right away:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patreon of the underground resistance:&lt;/strong&gt; &lt;a href="https://www.patreon.com/shtab_navalny" rel="noopener noreferrer"&gt;https://www.patreon.com/shtab_navalny&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can donate company shares to us via the DonateStock website. It's quick and convenient, and the site sometimes doubles the sum of donation:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://donatestock.com/rikolto-ltd" rel="noopener noreferrer"&gt;https://donatestock.com/rikolto-ltd&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ACF crypto wallets:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitcoin&lt;/strong&gt;&lt;br&gt;
3QzYvaRFY6bakFBW4YBRrzmwzTnfZcaA6E&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ethereum&lt;/strong&gt;&lt;br&gt;
0x314aC71aEB2feC4D60Cc50Eb46e64980a27F2680&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monero&lt;/strong&gt;&lt;br&gt;
42e1hkdsTHUUWSM24jVgLTH4MDJPMbNS1XDt7rK36dTBSKweohz9FSWKAAoqHLN9nSVgocnSnkR2AaLMWtsrmAoQGPdWSE1&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zcash (Z-address)&lt;/strong&gt;&lt;br&gt;
zs1l8ztrqpk0qyn2hyte3x9m568taz64jyc90ppfskylqgawxw7r3gq453yn6hk9swq2l0dq9yal0a&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USDT (ВЕР-20)&lt;/strong&gt;&lt;br&gt;
0x9A4B20a7909Af03dd8B4f28A419840F9715E1F73&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benevity is a corporate donation software used by over 900 companies: Apple, Google, Twitter, Facebook and others. If you work for a large company, your corporate website probably has an Employee Giving section. Type Anti-Corruption Foundation in the search box there and enter the donation amount. The advantage of Benevity is that sometimes corporations will multiply the amount of donations made through it by several times.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>darkweb</category>
      <category>tor</category>
      <category>security</category>
    </item>
    <item>
      <title>Безопасность Штабов Навального в Dark Web</title>
      <dc:creator>Navalny Team</dc:creator>
      <pubDate>Wed, 25 Oct 2023 08:50:00 +0000</pubDate>
      <link>https://dev.to/navalnyteam/biezopasnost-shtabov-navalnogho-v-dark-web-3739</link>
      <guid>https://dev.to/navalnyteam/biezopasnost-shtabov-navalnogho-v-dark-web-3739</guid>
      <description>&lt;p&gt;В 2017 году команда Навального открыла штабы по всей России. Сначала они были предвыборными, а затем стали региональными политическими центрами. Опасная для Путина, сеть штабов подвергалась непрерывным и усиливающимся репрессиям: обыски, аресты счетов и сотрудников, физическое насилие, угрозы. В 2021 году власти выдумали дело об экстремизме, и штабы пришлось закрыть — для защиты всех участников.&lt;/p&gt;

&lt;p&gt;В декабре 2022 года наша команда возобновила работу штабов Навального — теперь в виде подпольной организации. Нужно было разработать средства для коммуникации и коллаборации координаторов и активистов в онлайне. Осознавая риски для сторонников и активистов, находящихся в России, мы подошли к вопросу обеспечения их безопасности максимально серьёзно. Мы строим нашу систему безопасности, не полагаясь на принцип security via obscurity, поэтому, рассказывая о ней, мы не подвергаем ее значительным дополнительным рискам, а напротив — даем возможность специалистам понять уровень ее безопасности и дать свою оценку качества нашей работы.&lt;/p&gt;

&lt;h2&gt;
  
  
  Описание системы
&lt;/h2&gt;

&lt;p&gt;Сайт Штабов Навального представляет из себя личный кабинет. Активисты могут зарегистрироваться в нем по профессиональному и территориальному признаку. На базе платформы публикуются задачи, которые назначаются в соответствии со специальностью и интересами участников, имеется возможность связи с координатором. Пользователь может заработать баллы за выполненные задачи, достигнуть определенных уровней и, например, приобрести за это аватары. В августе 2023 года на базе платформы вышло большое обновление: запущен мессенджер, обеспечивающий анонимную коммуникацию участников в рамках задачи. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknvwmfctszds4o7rq83n.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknvwmfctszds4o7rq83n.jpeg" alt="Интерфейс главное страницы с заданиями сайта Штабов" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Моделирование угроз и нарушителей
&lt;/h2&gt;

&lt;p&gt;Разработку системы мы начали с моделирования угроз — определения целей, которые могут ставить перед собой злоумышленники. Как главную угрозу мы определили идентификацию участников штабов. Вторичные угрозы — блокировка доступа пользователей к сервису, компрометация секретных проектов и планов, компрометация программного обеспечения нашего сервиса и получение через него доступа к другим сервисам.&lt;/p&gt;

&lt;p&gt;Вторым этапом мы произвели моделирование нарушителя — прикинули, кто может быть нашим злоумышленником (очевидно, это сотрудники коррумпированных правоохранительных органов и спецслужб, органов обеспечения цензуры в интернете), и какие у них есть возможности по достижению их целей. Мы рассматривали четыре основных вектора атак: на серверы (хакерство), на активистов (обыски), на сотрудников (внедрение, подкуп, угрозы) и самое простое: «товарищ майор» в чате.&lt;/p&gt;

&lt;p&gt;Моделирование угроз и нарушителей — это фундамент обеспечения информационной безопасности. Поставив безопасность активистов во главу угла, мы были вынуждены часто принимать продуктовые решения в ущерб юзабилити, удержанию пользователей и т.д. Например, мы не могли позволить себе сделать удобное приложение для телефона с крутым обходом блокировок, &lt;a href="https://dev.to/navalnyteam/rkn-protiv-prilozhieniia-navalnyi-borba-za-dostupnost-2gg6"&gt;как в Умном голосовании&lt;/a&gt;, с уведомлениями, возвращающими неактивных пользователей, — ведь при досмотре телефона это приложение будет легко найти.&lt;/p&gt;

&lt;p&gt;Когда мы определили, с какими угрозами надо бороться, мы начали планировать эшелонированную систему безопасности с несколькими периметрами защиты. Каждый раз, когда мы возводили очередную стену: «тут у нас аутентификация», «тут у нас файрвол», — мы думали: «Хорошо, а что будет, если эта защита будет взломана? Что сможет сделать злоумышленник?» — и придумывали, как сделать, чтобы этого было недостаточно для успешной атаки.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tor
&lt;/h2&gt;

&lt;p&gt;Одним из ключевых решений стало размещение сайта в dark web. Обычные браузеры не только раскрывают много информации о пользователе, например метаданные с типом и именем устройства, IP-адрес пользователя, но и оставляют много следов на устройстве — историю посещений, кэши. Нами было принято решение для подключения к штабам использовать исключительно браузер Tor, который ничего из этого не делает и предпринимает еще много других мер для защиты пользователей. Доказать причастность пользователя к деятельности организации, в случае изъятия устройства, таким образом проблематично — видно, что приложение Tor установлено, но для чего оно использовалось — непонятно. И тут на ум приходит интересный факт: именно Tor использовал Эдвард Сноуден для коммуникации с журналистами при сливе секретных данных АНБ. Кстати говоря, Tor разрабатывает некоммерческая организация, и если у вас есть возможность, поддержите коллег финансово — &lt;a href="https://donate.torproject.org/" rel="noopener noreferrer"&gt;donate.torproject.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;С помощью данного браузера можно спрятаться и от Роскомнадзора. С момента введения повсеместного DPI на большинстве провайдеров в России сохранить в тайне доступ к сайтам на HTTPS стало гораздо сложнее. В популярных браузерах до сих пор отсутствует возможность использования протокола ECH, и, хотя Роскомнадзор не знает, какая именно информация передавалась между пользователем и сайтом, он видит, какой пользователь на какие домены обращался. Поэтому нами было принято решение разместить сайт на onion-сервере, обратиться к которому из обычного браузера нельзя. Таким образом пользователи даже случайно не смогут зайти на сайт и «засветиться» в логах DPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Персональные данные
&lt;/h2&gt;

&lt;p&gt;Для того чтобы зарегистрироваться в системе, активисту не нужно предоставлять свои личные данные. Мы видим активистов только под их псевдонимами, не зная и не пытаясь узнать их реальные личности. У каждого активиста есть репутация, которая измеряется в баллах. Баллы выдаются по мере выполнения заданий и делают активиста более доверенным — ему можно поручать более секретные и серьёзные задания. Таким образом, если «товарищ майор» захочет узнать больше о секретных проектах, ему придется выполнять больше простых заданий — расклеивать листовки и рисовать протестные граффити. Пусть наконец сделает что-нибудь полезное!&lt;/p&gt;

&lt;p&gt;Для регистрации в системе штабов участнику необходимо придумать свой псевдоним (логин), достаточный по сложности пароль и коротко описать свои профессиональные навыки в свободной форме.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d2xaismkw2cjj0gvseu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9d2xaismkw2cjj0gvseu.jpeg" alt="Страница регистрации сайта Штабов" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Реальных имён, электронных почт и телефонов не требуется. Все регистрации проходят дополнительную модерацию, после которой пользователю открывается доступ к полному функционалу системы. Модерация также защищает от флуда регистрациями. Поскольку в Tor клиенты анонимны, это ровно так же защищает от нас и самих злоумышленников, которые могут ботами заполнить миллионы анкет, а потом заспамить систему накрученными сообщениями. Чтобы это не повлияло на работу сайта, модераторы проверяют анкеты, отсеивая мусорные, размечают активистов по их профессиональным интересам (программисты, дизайнеры, юристы и т.д.) и только после этого пропускают их на сайт.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxpjx61vp3cysc1yzam2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxpjx61vp3cysc1yzam2.jpeg" alt="Страница экрана ожидания модерации сайта Штабов" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Поэтому, пожалуйста, отнеситесь с пониманием к тому, что нам нужно время на проверку ваших анкет. Мы ставим цель — 1-3 суток, но в случае атак оно может и затянуться.&lt;/p&gt;

&lt;h2&gt;
  
  
  Риски от инсайдеров
&lt;/h2&gt;

&lt;p&gt;Инсайдеры — это сотрудники штабов: координаторы, модераторы, программисты, системные администраторы и т.д. У них по роду деятельности есть дополнительный доступ к системе, которым могут воспользоваться злоумышленники. Мы доверяем нашим сотрудникам, но всё равно предпринимаем меры на случай их компрометации — ведь сотрудник может не только стать злоумышленником по своей воле (по идейным соображениям или быть подкупленным), но и быть взломанным (фишинг никто не отменял) или принужденным к сотрудничеству,  например, через давление на родственников.&lt;/p&gt;

&lt;p&gt;Для того чтобы защититься от угроз со стороны инсайдеров, мы предпринимаем специальные меры. Во-первых, мы всегда руководствуемся принципом минимальных привилегий — у наших сотрудников нет лишнего доступа, который не нужен им для выполнения прямых обязанностей. Например, у программистов есть доступ к репозиторию с кодом, но нет доступа к серверам. У тестеров есть доступ к тестовому окружению, но нет доступа к продакшну. Во-вторых, мы используем несколько периметров и для защиты от угроз инсайдеров. Скажем, даже если системные администраторы сделают дамп базы данных или скопируют все файлы из хранилища загруженных файлов, они не смогут их расшифровать.&lt;/p&gt;

&lt;h2&gt;
  
  
  Шифрование
&lt;/h2&gt;

&lt;p&gt;Все чувствительные поля базы данных и все загружаемые на сервер файлы зашифрованы. Мы используем симметричное шифрование с аутентификацией (AEAD), защищающее также от подмены зашифрованного содержимого. Ключи шифрования хранятся не в приложении, а в облачной системе управления ключами Cloud KMS от Google. Файлы шифруются при помощи envelope encryption — каждый файл шифруется своим эфемерным ключом, который уже шифруется KMS.&lt;/p&gt;

&lt;p&gt;У ключей Cloud KMS запрещён экспорт. Они хранятся в отдельном проекте Google Cloud, доступ к которому есть только у самых доверенных администраторов. Доступ к шифрованию и расшифрованию этими ключами выдан только сервис-аккаунтам, связанным с workload identity сервисов, которые должны работать с базой данных.&lt;/p&gt;

&lt;p&gt;Такой подход к шифрованию минимизирует периметр защиты данных. Например, даже если администраторы будут делать запросы к базе данных, они не смогут увидеть содержимое. То же самое касается и бэкапов. Более того, даже системы, делающие бэкапы, не должны расшифровывать данные. Всё бэкапится в зашифрованном виде, и для того, чтобы украсть данные, надо ещё и получить доступ к ключам KMS, что сильно усложняет задачу компрометации данных.&lt;/p&gt;

&lt;h2&gt;
  
  
  Атаки на приложение
&lt;/h2&gt;

&lt;p&gt;Если будет скомпрометировано само приложение через какую-нибудь техническую уязвимость, злоумышленник может получить доступ ко всему, к чему имеет доступ приложение. Это самый ценный актив системы, и успешная атака способна нанести очень большой ущерб. Чтобы его минимизировать, мы во-первых, не храним никаких персональных данных пользователей, чтобы даже в случае перехвата всех данных было максимально сложно идентифицировать активистов, а во-вторых мы стараемся минимизировать возможности злоумышленника закрепиться в системе.&lt;/p&gt;

&lt;p&gt;Поскольку серверы работают на GKE, они являются виртуальными машинами, которые периодически обновляются и заменяются новыми. В качестве базового образа используется COS (Container-optimized OS) от Google, представляющий из себя защищенную Linux-систему. Для аутентификации сервисов мы не используем экспортируемые JSON-ключи сервис-аккаунтов, которые можно украсть и использовать длительное время. Вместо этого применяется GKE Workload Identity, которая дает приложениям только временные токены со сроком жизни несколько минут.&lt;/p&gt;

&lt;h2&gt;
  
  
  Деплой
&lt;/h2&gt;

&lt;p&gt;Вся инфраструктура разделена на две части — доверенную и недоверенную. В недоверенной работает тестовое окружение, в котором нет никаких секретных данных. Оно используется для разработки и тестирования. В доверенной напротив — работает всё, у чего есть доступ к защищаемым данным.&lt;/p&gt;

&lt;p&gt;Сборкой кода приложения и контейнеров, занимаются специальные защищенные виртуальные машины (раннеры), работающие в изолированной от других проектов и инженеров среде. Доступ к проекту, в котором работают эти раннеры, есть у ограниченного количества доверенных инженеров, и то только на случай их поломки. У сервис-аккаунтов этих машин есть административный доступ к продакшну, что позволяет использовать подход gitops, в котором история всех изменений продакшна ведется в двух местах — в логах git и в аудит-логах Google Cloud, что позволит если не предотвратить атаки полностью, то хотя бы проследить их ретроактивно.&lt;/p&gt;

&lt;p&gt;Мы используем недоверенную среду для сборки кода из незащищенных веток GitLab. Это ветки, куда любой разработчик может делать свои коммиты для ревью или выкатки на тестовые серверы. Даже если разработчик закоммитит что-то вредоносное, что скомпрометирует сервер сборки, это не даст ему никакого доступа к продакшну. После ревью другим сотрудником и мержа ветки в основную продакшн-ветку, код собирается уже в доверенной среде и деплоится в прод.&lt;/p&gt;

&lt;p&gt;Для экономного расходования средств и достижения плотной загрузки серверов мы используем Kubernetes, а для системы сборки — собственное решение включения виртуальных машин раннеров только в тот момент, когда они нужны. Система управления раннерами также работает в защищенной среде.&lt;/p&gt;

&lt;h2&gt;
  
  
  DoS-атаки
&lt;/h2&gt;

&lt;p&gt;При запуске системы штабов команда рассчитывала, что, в отличие от открытого интернета, в Tor злоумышленникам будет накладно устраивать DoS-атаки. Но атакующие не заставили себя долго ждать, и наш мониторинг вскоре зафиксировал резкое увеличение нагрузки на серверы. Мы были готовы к фильтрации трафика на уровне HTTP, и поначалу этого было достаточно, но злоумышленники стали поступать хитрее — они создавали множество маршрутов и нагружали наш клиент Tor.&lt;/p&gt;

&lt;p&gt;Здесь надо немного объяснить, как работают onion-сайты. В отличие от обычного интернета, где клиенты подключаются к серверам напрямую, в Tor и клиенты, и серверы, подключаются к специальным узлам дискавери, которые "знакомят" их друг с другом, а после этого для передачи данных и те, и другие подключаются к узлам рандеву, устанавливают туннели с обеих сторон, и сервер рандеву замыкает их в единую цепь, по которой потом уже будет ходить обычный HTTP.&lt;/p&gt;

&lt;p&gt;В нашем случае злоумышленники отправляли огромное количество запросов на установку соединений, вынуждая наш сервер заниматься установкой соединений с серверами рандеву и сопутствующей дорогостоящей асимметричной криптографией. Далее они даже и не пытались ничего отправлять по этим туннелям, и атака даже не доходила до HTTP.&lt;/p&gt;

&lt;p&gt;Обычные средства защиты от DoS-атак типа определения источников и их временной блокировки в Tor невозможны, поскольку мы никак не можем идентифицировать клиентов. С этой целью было разработано специальное решение. Дело в том, что обычный onion-сервер не умеет масштабироваться. Когда запускается Tor, он подключается к сети и публикует дескриптор сервиса, указывающий на него единственного. Если запустить второй инстанс того же сервера, он затрет дескрипторы первого, и они вместе работать не смогут.&lt;/p&gt;

&lt;p&gt;В качестве решения проблемы мы используем onion balance - сторонний инструмент, который позволяет запустить несколько onion-серверов (каждый из которых работает на своём случайном onion-адресе), а затем загрузить из сети их дескрипторы, объединить в единый дескриптор и опубликовать уже в качестве дескриптора основного onion-сайта. Это позволяет распределять нагрузку на серверы и выдерживать пропорционально больший трафик.&lt;/p&gt;

&lt;p&gt;Так выглядит архитектура нашего решения:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnwk4oh63z604l6prcak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnwk4oh63z604l6prcak.png" alt="Архитектура сайта Штабов" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tor-balancer - это обёртка над onionbalance, которая обращается в наш кластер, получает список здоровых и активных инстансов тор-сервера, генерирует конфиг для onionbalance и запускает его. Если набор активных инстансов меняется (например, какой-то отключился, или новый включился), то конфиг регенерируется, и onionbalance перезапускается.&lt;/p&gt;

&lt;p&gt;Tor-frontend управляет обычным onion-сервером. Он генерирует случайный onion-адрес, запускает onion-сервер на этом адресе, ждёт, пока он успешно зарегистрируется и станет доступным в сети тор, а потом начинает отвечать на HTTP-запросы собственным onion-адресом. Именно обращаясь к этому HTTP, tor-balancer может узнать, какие индивидуальные onion-адреса надо использовать для компиляции общего большого дескриптора.&lt;/p&gt;

&lt;p&gt;Клиент устанавливает маршрут (circuit) с сервером один раз. Обычно это медленная и «дорогая» операция с точки зрения ресурсозатратности, а дальше может устанавливать любое количество логических соединений в рамках одного circuit — это уже быстро и дешево. Для борьбы с атаками настроено правило: если какой-то клиент внутри своего circuit установил большое количество соединений, такой circuit либо разрывается совсем или замедляется rate limit’ом на ingress-nginx. Это позволяет эффективно бороться с атаками. &lt;/p&gt;

&lt;p&gt;Таким образом мы распределяем нагрузку между onion-адресами с помощью «дискавери» дескрипторов и не даем злоумышленнику нагрузить один конкретный адрес и завалить систему.&lt;/p&gt;

&lt;p&gt;Вторым уровнем защиты от атак был отказ от конфиденциальности сервера. Поскольку у нас не дарк маркет и нам не надо прятать сервер (мы и так с гордостью говорим, чей он), серверу нет никакой нужды устанавливать тройное соединение с точкой рандеву. Вместо этого мы используем опцию Tor, которая позволяет нашему серверу устанавливать прямое соединение. Это позволило нам резко сократить объем асимметричной криптографии, который надо делать на сервере, и в несколько раз сократить нагрузку на сервер. Конфиденциальность клиентов при этом никак не страдает, а серверу — легче.&lt;/p&gt;

&lt;p&gt;Больше успешных DoS-атак на сервер не было.&lt;/p&gt;

&lt;h2&gt;
  
  
  Координаторы
&lt;/h2&gt;

&lt;p&gt;В системе штабов любой участник может написать сообщение координатору и получить ответ. Например, он может сообщить о выполненном задании или задать вопрос.&lt;/p&gt;

&lt;p&gt;Для работы координаторов мы используем специальную административную панель на базе фреймворка Django. Его функционала более чем достаточно для удобного разбора заявок на регистрацию, постановки задач и оценки их выполнения и общения с активистами в личном чате. Модераторы не видят реальных логинов пользователей, но, чтобы они могли как-то идентифицировать их, модераторы видят пользователей под еще одним уровнем псевдонимов — для них пользователи обозначены обезличенными порядковыми номерами. Для всех администраторов включено принудительное правило двухфакторной аутентификации, действует разграничение доступа по направлениям работы, и доступ включен только из-под служебного VPN. Ведется журнал действий, чтобы всегда можно было установить, какой сотрудник каким доступом пользовался.  &lt;/p&gt;

&lt;p&gt;Координаторы команды ставят задачи, следят за их исполнением, оценивают, поддерживают связь в личном чате с пользователем. Это требует человеческих ресурсов и создает проблему отсутствия быстрой обратной связи. Поэтому на базе платформы было решено запустить публичные групповые чаты для взаимодействия активистов друг с другом напрямую в рамках задачи. &lt;/p&gt;

&lt;h2&gt;
  
  
  Чаты
&lt;/h2&gt;

&lt;p&gt;По функционалу и дизайну, интерфейс чатов приближен к ленте мессенджера типа Telegram. В нем можно переключаться между чатами, читать, писать и удалять сообщения, отвечать на чужие сообщения и чистить историю.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlljhrbpfqfq1akhevop.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlljhrbpfqfq1akhevop.jpeg" alt="Интерфейс страницы чатов сайта Штабов" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Чаты работают поверх вебсокетов внутри Tor. Каждое сообщение чата зашифровано при помощи Cloud KMS, и чтобы избежать избыточного количества запросов к KMS, в системе реализован локальный кеш расшифрованных сообщений в памяти приложения.&lt;/p&gt;

&lt;p&gt;Сообщения в чате можно удалять вручную, а по прошествии времени они удаляются автоматически.&lt;/p&gt;

&lt;p&gt;Для защиты чатов от спама сообщениями, в системе настроено ограничение на количество отправляемых сообщений в единицу времени, после которого сообщения перестанут приниматься. Мы также проводили нагрузочное тестирование чатов различными сценариями, чтобы убедиться, что сервер выдерживает расчетную нагрузку.&lt;/p&gt;

&lt;p&gt;Мы предприняли меры по защите пользователей чата от их идентификации по метаданным. Дело в том, что злоумышленники, имеющие доступ к СОРМ, видят, когда пользователи пользуются тором. Они не знают, что именно пользователи там делают, но сам факт скрыть довольно сложно. Они могут подключиться к популярному чату в системе штабов, отправить какое-то большое сообщение заведомо известной длины и по данным СОРМ посмотреть, какие из пользователей по всей России в тот же самый момент получили сообщение той же самой длины. Сигнал зашумлен, идентификация будет нечеткой, но после нескольких таких сообщений они смогут достоверно выяснить, кто из россиян сидел в это время в чате. Чтобы сильно затруднить возможность такой атаки, мы рандомизируем размер сообщений, отправляемых пользователям, и задержки времени между ними, чтобы размыть сигнал и сделать его ниже уровня шума.&lt;/p&gt;

&lt;h2&gt;
  
  
  Анонимность
&lt;/h2&gt;

&lt;p&gt;Самый глубокий уровень нашей обороны — это анонимность. Даже если все предыдущие уровни падут — серверы будут взломаны, координаторы куплены, а админы запытаны, — все, что получит злоумышленник, — это историю общения анонимов с анонимами, прямо не раскрывающую никаких идентифицирующих данных.&lt;/p&gt;

&lt;p&gt;Но даже без всякого взлома, общедоступные чаты также подразумевают наличие в них спамеров, провокаторов и прочих злоумышленников. Поэтому мы предпринимаем соответствующие меры защиты.&lt;/p&gt;

&lt;p&gt;Во-первых, за каждым чатом закрепляется модератор. Он видит чуть больше, чем обычный пользователь. Общую ленту, в которую падает поток всех сообщений, которые он модерирует, и ленту нарушений — жалобы от других пользователей. В этих административных чатах модератор имеет функционал удаления сообщений и блокировки пользователей - на определенное время или насовсем. При этом модератором не обязательно должен быть администратор с доступом в отдельную панель. Это обычный пользователь, зарегистрированный в личном кабинете, только с повышенными привилегиями.&lt;/p&gt;

&lt;p&gt;А во-вторых, чтобы «товарищ майор» не мог, долгое время наблюдая за чатом, собрать достаточно информации на пользователя, чтобы идентифицировать его по косвенным данным, мы ввели еще один принудительный уровень псевдонимизации. Каждому пользователю в чате назначается случайное имя, отличное от того, которое он использует для основного входа в систему — именно его и видят другие участники. Имена пользователей, с помощью которых участники штабов входят в личный кабинет, нигде не фигурируют. Мы заранее подготовили комбинации из прилагательных и существительных, чтобы покрыть всех участников забавными и уникальными именами. «Пятнистый Гиппогриф» или «Милый Кот» — такими видят пользователи друг друга. Доступна функция смены имени на другое, но тоже случайное. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes03y77hgufh4z0e4h6h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes03y77hgufh4z0e4h6h.jpeg" alt="Страница смены именип ользователя сайта Штабов" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Бот Федор, постоянно присутствующий в чате, напоминает участникам о правилах безопасности - например не рассказывать ничего про свою реальную жизнь или регулярно менять свой ник, чтобы разорвать связь с собой прошлым. Он выглядит, как обычный участник чата, берет случайное сообщение из своей базы знаний и отправляет в чат.&lt;/p&gt;

&lt;h2&gt;
  
  
  Загрузка файлов
&lt;/h2&gt;

&lt;p&gt;Мессенджер — это не только обмен короткими сообщениями, но и загрузка изображений. Мы позаботились о том, чтобы их метаданные автоматически очищались при загрузке в общий чат, и чтобы никто не мог узнать, кем, когда и где они были созданы.&lt;/p&gt;

&lt;p&gt;В качестве решения для очистки метаданных был выбран exiftool. Он поддерживает наибольшее количество форматов и прост в использовании. Однако важно обратить внимание на то, что exiftool использует множество кодеков, написанных на С — языке, на котором очень легко написать уязвимый код, дающий возможность удаленного исполнения кода злоумышленником.&lt;/p&gt;

&lt;p&gt;Чтобы скомпрометированный exiftool не привел к компрометации всего приложения, мы запускаем его отдельным микросервисом в изолированной песочнице, чтобы при загрузке вредоносного файла, хакер не мог бы получить доступ к основному сервису либо закрепиться на сервере и перехватывать файлы других пользователей. Мы запускаем этот микросервис в бессерверной среде исполнения Cloud Functions от Google, благодаря которой он живет короткое время и на уровне сети не имеет доступа к базе данных и другим микросервисам.&lt;/p&gt;

&lt;p&gt;В первый день работы чатов террористы тестеры подбросили нам настоящую zip-бомбу в виде картинки из миллионов пустых пикселей. Zip-бомба — это архив на миллиарды нулей которые сжали компрессором до очень компактного размера. Если получатель пытается его распаковать, у него просто напросто кончится или память, или диск, или и то, и другое. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;_decompression_bomb_check&lt;br&gt;
    raise DecompressionBombError(&lt;br&gt;
PIL.Image.DecompressionBombError: Image size (50625000000 pixels) exceeds limit of *N* pixels, could be decompression bomb DOS attack.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;К счастью, бомба так и не разорвалась - сработала защита в библиотеке. Но даже если бы она сработала, основному сервису это бы никак не повредило.&lt;/p&gt;

&lt;h2&gt;
  
  
  Заключение
&lt;/h2&gt;

&lt;p&gt;Мы создали уникальный инструмент, дающий возможность сторонникам и активистам в России более эффективно бороться с режимом, поддерживая связь между собой и не боясь быть раскрытыми. А оплачено все это за счет пожертвований наших активистов, за что им огромное спасибо! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Поддержать работу IT-отдела Команды Навального:&lt;/strong&gt; &lt;a href="https://donate.fbk.info/it" rel="noopener noreferrer"&gt;https://donate.fbk.info/it&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Вы тоже можете присоединиться и поддержать Штабы:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Патреон Штабов:&lt;/strong&gt; &lt;a href="https://www.patreon.com/shtab_navalny" rel="noopener noreferrer"&gt;https://www.patreon.com/shtab_navalny&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Вы можете пожертвовать нам акции компаний через сайт DonateStock. Это быстро и удобно, к тому же площадка иногда удваивает сумму.&lt;/strong&gt; &lt;a href="https://donatestock.com/rikolto-ltd" rel="noopener noreferrer"&gt;https://donatestock.com/rikolto-ltd&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Криптокошельки ФБК (ACF):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitcoin&lt;/strong&gt;&lt;br&gt;
3QzYvaRFY6bakFBW4YBRrzmwzTnfZcaA6E&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ethereum&lt;/strong&gt;&lt;br&gt;
0x314aC71aEB2feC4D60Cc50Eb46e64980a27F2680&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monero&lt;/strong&gt;&lt;br&gt;
42e1hkdsTHUUWSM24jVgLTH4MDJPMbNS1XDt7rK36dTBSKweohz9FSWKAAoqHLN9nSVgocnSnkR2AaLMWtsrmAoQGPdWSE1&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zcash (Z-адрес)&lt;/strong&gt;&lt;br&gt;
zs1l8ztrqpk0qyn2hyte3x9m568taz64jyc90ppfskylqgawxw7r3gq453yn6hk9swq2l0dq9yal0a&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USDT (ВЕР-20)&lt;/strong&gt;&lt;br&gt;
0x9A4B20a7909Af03dd8B4f28A419840F9715E1F73&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Вы можете оформить корпоративное пожертвование, чтобы помочь Штабам Навального работать. Это можно сделать через платформу Benevity.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Benevity — программа для корпоративных пожертвований, которую используют более 900 компаний: Apple, Google, Twitter, Facebook и другие. Если вы работаете в крупной компании, скорее всего, на вашем корпоративном сайте есть раздел Employee Giving. Там в поисковой строке вбейте Anti-Corruption Foundation и введите сумму. Плюс системы Benevity в том, что иногда корпорации в несколько раз увеличивают размер сделанных через нее пожертвований.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>История защиты Умного голосования от DDoS-атак</title>
      <dc:creator>Navalny Team</dc:creator>
      <pubDate>Thu, 14 Sep 2023 12:49:41 +0000</pubDate>
      <link>https://dev.to/navalnyteam/istoriia-zashchity-umnogho-gholosovaniia-ot-ddos-atak-118h</link>
      <guid>https://dev.to/navalnyteam/istoriia-zashchity-umnogho-gholosovaniia-ot-ddos-atak-118h</guid>
      <description>&lt;p&gt;С самого начала своего существования Умное голосование каждый год подвергалось DDoS-атакам — в период со дня публикации рекомендаций до дня выборов. Первую атаку мы зафиксировали в 2019 году, аккурат за неделю до голосования. Разумеется, мы ее ждали. К тому времени на сайте уже была развернута защита от атак, и она с честью приняла на себя удар и так же с честью упала. Пожалуй, самая масштабная атака произошла в этом году, 2022-м, и с ней мы успешно справились. Миллиарды запросов к сайту и тысячи заблокированных адресов за сутки — такова была цена доступности Умного голосования на московских муниципальных выборах. В посте мы рассказываем об истории Умного голосования и опыте нашей команды по борьбе с DDoS: о методах атак, архитектуре и технологиях по защите.&lt;/p&gt;

&lt;p&gt;Чтобы понять некоторые из принятых технических решений, необходимо ненадолго заглянуть в прошлое. История Умного голосования восходит к 2018 году, когда Алексей Навальный впервые выдвинул идею консолидированного протестного голосования. К тому времени в России уже несколько лет работала система интернет-цензуры, осуществляемая Роскомнадзором. РКН рассылал списки заблокированных сайтов провайдерам, и те были обязаны блокировать к ним доступ.&lt;/p&gt;

&lt;p&gt;Разумеется, команда Навального понимала, что попытки заблокировать сайт будут предприняты примерно сразу после его открытия, а значит, нужны были такие решения по хостингу, какие РКН на том этапе технически не мог заблокировать. В то время на провайдерах еще не было массово установлено оборудование DPI, и блокировки осуществлялись в лучшем случае по IP-адресу. Соответственно, сайт нужно было размещать на IP-адресах, общих с другими важными сайтами, чтобы РКН не смог его заблокировать, не сломав пол-интернета. В ретроспективе это решение оказалось совершенно правильным, и сайт так никогда и не был заблокирован по IP.&lt;/p&gt;

&lt;p&gt;Итак, в начале 2019 года был запущен сайт на домене vote2019.appspot.com. Домен &lt;a href="http://appspot.com/" rel="noopener noreferrer"&gt;appspot.com&lt;/a&gt; принадлежит Google, и в нем автоматически создаются поддомены для всех проектов, использующих App Engine. Все сайты в &lt;a href="http://appspot.com/" rel="noopener noreferrer"&gt;appspot.com&lt;/a&gt; доступны по одним и тем же IP-адресам. Мы не рискнули тогда запускаться на отдельном домене для сайта, чтобы имя сайта прямо кричало, что оно работает на общей площадке и блокировать его нельзя.&lt;/p&gt;

&lt;p&gt;В то время уже существовали коммерческие системы защиты от DDoS, например, в России был популярен Qrator. Но именно тот факт, что фронтенд приложения должен был работать на Google App Engine, не позволял нам использовать готовые решения. Если бы мы спрятали сайт за одним из них, то сервис защиты от DDoS выделил бы сайту отдельный IP-адрес, и он был бы немедленно заблокирован Роскомнадзором.&lt;/p&gt;

&lt;p&gt;Достоинством App Engine является отличная масштабируемость — пока на сайт нагрузки нет, будет запущено всего 1-2 инстанса приложения. А если приедет большая нагрузка, то в течение нескольких секунд App Engine сможет поднять тысячи инстансов нашего приложения и переварить нагрузку. Учитывая все это, мы приняли решение строить собственную систему защиты от DDoS на базе App Engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Архитектура
&lt;/h2&gt;

&lt;p&gt;Чтобы не разориться на App Engine, мы решили не запускать на нем тяжеловесное Умное голосование целиком. Вместо этого мы стали гонять там легковесный кеширующий прокси-сервер на Go, который проверяет трафик на признаки атаки и либо отбивает запросы, либо отдает ответы из кеша, либо проксирует их в основное приложение. Go в первую очередь был выбран из-за того, что он компилируемый — в результате приложение и запускается быстро, и ресурсов тратит мало, что очень важно для быстрого масштабирования при росте нагрузки и для экономии денег соответственно.&lt;/p&gt;

&lt;p&gt;Поскольку во время атаки каждый инстанс прокси-сервера видит только малую долю всего трафика, принимать локальные решения о том, кого блокировать, а кого нет, качественно не получится — без полноты информации хороших решений, как известно, не принять. Поэтому был придуман отдельный бэкенд-сервис — «координатор», который собирает статистику со всех проксей, находит паттерны в трафике и посылает обратно нашу «политику партии» — кого мы баним, а кого нет.&lt;/p&gt;

&lt;p&gt;Координатор также предоставляет базовые административные функции — показ статистики, мониторинг, ручное управление политикой банов и т. д. Координатор записывает свое состояние и всю телеметрию в базу данных для того, чтобы ему не были страшны перезапуски.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmna6jr60hze99jo9c7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmna6jr60hze99jo9c7x.png" alt="Жирным выделен data plane — путь запросов пользователей (ну и, разумеется, DDoS). Нежирным — все остальное, то есть control и management plane.&lt;br&gt;
" width="800" height="637"&gt;&lt;/a&gt;&lt;em&gt;Жирным выделен data plane — путь запросов пользователей (ну и, разумеется, DDoS). Нежирным — все остальное, то есть control и management plane.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Анализ трафика
&lt;/h2&gt;

&lt;p&gt;Все прокси Front Shield ведут локальный реестр обслуженных запросов и раз в 2 секунды отправляют его координатору в HTTP-запросе. В ответ координатор возвращает текущий бан-лист — список правил, по которым должны баниться запросы.&lt;/p&gt;

&lt;p&gt;На стороне координатора все запросы анализируются, и если по каким-то IP-адресам обнаруживается подозрительная активность, то они попадают в бан на сутки. Анализ трафика делается асинхронно с приемом данных от инстансов прокси, поэтому решения могут чуть-чуть отставать от реального времени. На практике, если какой-то IP-адрес начинает резко слать много запросов, в бан он попадает в среднем через 5 секунд (2 секунды на доставку отчетов о трафике координатору, 1 секунда на анализ, 2 секунды на доставку бан-листов обратно на фронтенд).&lt;/p&gt;

&lt;h2&gt;
  
  
  Крещение огнем
&lt;/h2&gt;

&lt;p&gt;Итак, остается около недели до голосования 2019 года. Приходит алерт от мониторинга, что сайт Умного голосования недоступен. Мы смотрим в консоль Front Shield и видим небольшой и очень короткий всплеск трафика. Было даже странно, что система не справилась даже с такой скромной нагрузкой. Начинаем разбираться, и все быстро встает на свои места — консоль самого App Engine показывает 20 тысяч запросов в секунду. Просто инстансы Front Shield приняли все эти запросы и массово упали от нехватки памяти. App Engine увидел нагрузку и быстро добавил инстансов, но атакующие, видимо, убедились, что в силах положить сайт, и быстро остановили атаку, рассчитывая устроить то же самое в день голосования.&lt;/p&gt;

&lt;p&gt;Так началась борьба меча и щита. На каждую проблему, обнаруженную нападающими, мы придумывали решения. В данном случае была реализована техника load shedding, которая анализирует загрузку процесса и, если она приближается к своему пределу, начинает возвращать ошибки на раннем этапе обработки запроса, не дожидаясь падения. Это позволяет избежать каскадных аварий, когда падения одних инстансов увеличивают нагрузку на оставшиеся и дальше все разваливается, как домино.&lt;/p&gt;

&lt;p&gt;В день голосования 2019 года все прошло довольно спокойно — было несколько атак без всяких последствий. Леонид Волков тогда постил графики Front Shield у себя в соцсетях.&lt;/p&gt;

&lt;h2&gt;
  
  
  Выборы 2020
&lt;/h2&gt;

&lt;p&gt;После повсеместного внедрения ТСПУ на провайдерах особого смысла держать сайт Умного голосования живым не было. Именно поэтому наша команда выпустила дополнительные инструменты, такие как приложение под Android и iOS и &lt;a href="https://t.me/smartvotebot" rel="noopener noreferrer"&gt;телеграм-бот&lt;/a&gt;, в которых можно было узнать рекомендуемых кандидатов. Подробнее о технологиях для обхода блокировок, примененных в нашем приложении, можно почитать в нашем &lt;a href="https://dev.to/navalnyteam/rkn-protiv-prilozhieniia-navalnyi-borba-za-dostupnost-2gg6"&gt;другом нашем посте&lt;/a&gt;. Поскольку в мобильном приложении пользователям не надо запоминать доменное имя и оно не является брендом, мы могли позволить себе менять домены раз в минуту, и DDoS-атаки просто не успевали сфокусироваться на сервере, как он уже менялся.&lt;/p&gt;

&lt;p&gt;Веб-сайт же, разумеется, мы могли бы перенести на другой домен (не в &lt;a href="http://appspot.com/" rel="noopener noreferrer"&gt;appspot.com&lt;/a&gt;), но, во-первых, особого смысла с точки зрения блокировок в этом не было — любой сайт был бы во внесудебном порядке успешно заблокирован РКН, как только стал бы хоть немного заметен, во-вторых, скорее всего, если бы мы даже и закрылись за CloudFlare или Google Cloud Armor, как показывает практика, во время больших атак процентов 10 трафика все равно пролезает в бэкенд, и нам бы все равно приходилось тратить ресурсы на его обработку и загрузку CPU. Система, которая «знает» трафик, «знает» паттерны поведения пользователей, может быть гораздо более точной в обнаружении ботов и может защитить бэкенд гораздо эффективнее.&lt;/p&gt;

&lt;p&gt;Так и вышло: DDoS-атак на бэкенд приложения практически не было, а атаки на сайт мы успешно отбили — ничего особенно выдающегося в тот год не было.&lt;/p&gt;

&lt;h2&gt;
  
  
  Выборы 2021
&lt;/h2&gt;

&lt;p&gt;В этот год были очень крупные и важные выборы — избирались депутаты Госдумы и мы понимали, что атаки на Умное голосование достигнут беспрецедентных масштабов. Поэтому мы сделали еще одно крупное обновление — добавили еще один слой блокировок перед прокси Front Shield. У App Engine есть встроенный файрвол, который можно настраивать через API. Он позволяет вообще не платить за отбивание запросов с забаненных адресов — они даже не доходят до приложения. У этого решения есть один минус — жесткое ограничение на число правил файрвола: их всего 1000. Нам надо было придумать, как использовать их максимально эффективно. Мы заметили, что не все боты одинаково производительны — некоторые могут слать во много раз больше трафика, чем большинство других. Поэтому мы научили координатор собирать статистику: с каких IP-адресов идет больше всего трафика, который мы определяем как DDoS, те и заносить в файрвол. Но как только адрес попадал в файрвол, мы тут же «теряли» его из мониторинга — узнать, продолжает ли он отправлять запросы, а если да, то в каком количестве, становилось невозможно. Поэтому был реализован алгоритм, который со временем «забывает» старые грехи IP-адресов и замещает их в таблице новыми, самыми «злыми» на данный момент. Если разбаненный адрес продолжит слать трафик, то он быстро вернется в бан.&lt;/p&gt;

&lt;p&gt;В неделю перед голосованием, когда мы опубликовали рекомендации, злоумышленники не просто начали заливать Front Shield огромным трафиком, но и стали действовать креативно. Они искали изъяны в системе и били так, чтобы пробить все слои кеширования при помощи рандомизации и достать до бэкенда. У нас во время голосования (а оно в этот раз было трехдневным) дежурили команды разработчиков и бэкенда, и Front Shield. Какие-то атаки мы отражали, дорабатывая код Front Shield прямо на лету, — скажем, делали нормализацию запросов или регулировали параметры анализа статистики. Например, одним из паттернов атаки было использование какого-то гигантского ботнета, где каждый из IP-адресов слал относительно мало трафика, но суммарно выходило очень много, и нам пришлось увеличить интервал сбора статистики в памяти координатора, чтобы обнаруживать этих ботов на основании более длительного времени. Какие-то ошибки приходилось исправлять прямо на бэкенде. Например, мы обнаружили, что поиск адреса «а а а а а а а а а а а а а а а а а а а а а а» был очень дорогим, и при помощи небольшого исправления алгоритма быстро справились с проблемой.&lt;/p&gt;

&lt;p&gt;Надо отметить, что в основном атаки шли на бэкенд приложения, потому что сайт был к тому времени уже заблокирован, и его важность была сильно ниже, чем у приложения.&lt;/p&gt;

&lt;h2&gt;
  
  
  Выборы 2022
&lt;/h2&gt;

&lt;p&gt;Все началось с осени 2021 года. Кремль начал под ноль зачищать информационное пространство, и под нож попали социальные сети. Пожалуй, Instagram стал той их роковой ошибкой, в результате которой клиенты VPN прописались в телефонах у огромного количества даже технически не подкованных россиян, и внезапно оказалось, что важность сайта Умного голосования снова возросла.&lt;/p&gt;

&lt;p&gt;Вечером 10 сентября 2022 года, аккурат перед ключевым днем голосования, на сайт была запущена огромная DDoS-атака, которая продлилась более суток. Она продолжилась и после выборов, поэтому мы были вынуждены после окончания голосования совсем отключить Front Shield, чтобы не расходовать ресурсы зря. Миллиарды запросов к сайту и тысячи заблокированных адресов за сутки — такова была цена доступности Умного голосования на московских муниципальных выборах.&lt;/p&gt;

&lt;p&gt;К сожалению, из-за принципа работы файрвола App Engine мы так никогда и не узнаем, сколько трафика пришло на сайт, но во время кратковременных сбоев, о которых мы напишем ниже, на какое-то время мы переставали обновлять файрвол, и в пиках прокси обрабатывала до 250 тысяч запросов в секунду, а отдаваемый сервисом трафик достигал 100 гигабит в секунду. Скорее всего, реальный масштаб атаки был в несколько раз больше.&lt;/p&gt;

&lt;p&gt;Один из обычных методов борьбы с DDoS — территориальные ограничения. Например, если нас не интересует трафик из Южной Америки, то можно его сразу блокировать, а не пытаться даже обслуживать. В случае же Умного голосования так делать было нельзя, потому что в условиях блокировок всем пользователям из России приходилось пользоваться VPN, и страна, которую мы видели на фронтенде, была какой угодно, но только не Россией.&lt;/p&gt;

&lt;p&gt;Одной ошибкой, которую мы совершили, была отдача неверных заголовков кеширования. Дело в том, что перед тем, как запросы достигают приложения на App Engine, они обрабатываются Google Frontend, который имеет свой собственный слой кеширования. Если приложение своими заголовками Cache-Control позволяет кешировать ответы, то Google Frontend это запомнит и будет в будущем отдавать их из кэша. Приложение даже не догадывалось, что у нас есть такие гигантские источники запросов и не могло занести их в файрвол.&lt;/p&gt;

&lt;p&gt;Это продолжалось в течение нескольких часов. Поменяв заголовок Cache-Control с «public, max-age=120» на «private, max-age=120», мы решили проблему, и боты быстро улетели в бан. &lt;/p&gt;

&lt;p&gt;Был еще ночной «тест» со стороны нападающих накануне основной атаки в день голосования. Количество забаненных запросов держалось на уровне 20-30k rps, но иногда пробы достигали и 250 тысяч. Напомним, что мы считаем трафик уже после файрвола, то есть сколько там было до него — мы не знаем, но, скорее всего, в несколько раз больше.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpxjmotfaeegenaciu4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpxjmotfaeegenaciu4p.png" alt="Requests graph" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Анализируя логи, мы поняли, что они отыскали один из эндпоинтов, на который у нас не была включена нормализация, и рандомизированными запросами смогли на несколько минут уложить бэкенд. Мы поспешно добавили в правила нормализацию для этого эндпоинта.&lt;/p&gt;

&lt;p&gt;В день голосования атака усилилась. Front Shield без остановки блокировал не менее 40 тысяч запросов в секунду (опять же, это то, что прошло через файрвол), а ко второй половине дня голосования трафик дорос до 100 тысяч запросов в секунду.&lt;/p&gt;

&lt;p&gt;Нам пришлось несколько раз добавлять памяти координатору из-за огромного количества данных, которые он должен был держать и обрабатывать. В мирное время eму нужно около 50 мегабайт, во время прошлых DDoS-атак он мог вырасти до гигабайта, а в этот раз, особенно под конец дня, ему понадобилось больше 20 гигов.&lt;/p&gt;

&lt;p&gt;В какой-то момент мы заметили, что координатор сломался. Оказалось, что он уже 45 минут не отвечает ни на какие запросы и находится в жестком дауне. Мы быстро обнаружили причину — из-за большого числа операций записи binlog базы данных переполнил диск, и все записи остановились. Как только мы решили эту проблему, координатор «проснулся» и принял первые за 45 минут отчеты от проксей — 200 тысяч запросов в секунду. Скорее всего, на протяжении этих 45 минут трафик на прокси плавно нарастал, поскольку никто не обновлял файрвол, и дорос до 200 тысяч.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8a4sd06cyr4uair0tdt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8a4sd06cyr4uair0tdt1.png" alt="Requests graph - 2" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Причудливый узор во время пика трафика — это артефакт мониторинга, возникший из-за билинейной интерполяции точек и большого пропуска. Хотя уж больно он похож на кривую сложности &lt;a href="https://dfwk.ru/Dwarf_Fortress_Wiki:%D0%9E%D0%B1_%D0%B8%D0%B3%D1%80%D0%B5" rel="noopener noreferrer"&gt;Dwarf Fortress&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Когда голосование близилось к концу, App Engine показывал, что за последние сутки было обслужено более 2,4 миллиарда запросов, и это отражало только то, что прошло через файрвол и смогло добраться до прокси. Оценочно можно предположить, что трафика было в несколько раз больше. Скорее всего, около 10 миллиардов запросов за сутки.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vpuhn3d0yk2wyos36dj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vpuhn3d0yk2wyos36dj.png" alt="Requests graph - 3" width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Таким образом, нас защитило несколько слоев: файрвол отбивал запросы от самых крупных ботов, прокси Front Shield отбивала «длинный хвост» с более мелких, далее прокси из локального кэша отдавала часть ответов на популярные запросы, и все остальное уже шло в бэкенд. На бэкенде запросы встречала «батарея» nginx с дисковым кешем, ну и дальше уже само приложение. Такая многослойная котлета позволила нам сохранять устойчивость сервиса на протяжении всего времени работы сайта.&lt;/p&gt;

&lt;p&gt;За один только день голосования в бан-лист Front Shield было внесено более 30 тысяч IP-адресов. Злоумышленникам не удалось достичь своей цели от слова совсем. Все системы функционировали штатно — мы держали нагрузку, и сайт отлично работал, быстро и надежно. Также работали телеграм-бот и приложение, которые были полностью изолированы от сайта.&lt;/p&gt;

&lt;p&gt;А у нас в трекере висят тикеты — вынести все захардкоженные настройки в файлы конфигурации, написать документацию и выложить исходники Front Shield в общий доступ. Надеемся, у нас до них дойдут руки :)&lt;/p&gt;

&lt;p&gt;Вся эта работа была бы невозможна без вашей поддержки. Огромное спасибо!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Поддержать работу IT-отдела можно здесь:&lt;/strong&gt; &lt;a href="https://donate.fbk.info/it" rel="noopener noreferrer"&gt;https://donate.fbk.info/it&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Этот пост был опубликован на портале Habr 14 сентября 2022 года, а на следующий день скрыт администрацией сайта. В поддержке сослались на особый порядок блокировки «в отношении материалов от организаций, признанных экстремистскими» и на «инициирование политических дискуссий». Мы выступаем против ограничения свободы распространения информации. Habr мы рассматривали как площадку для обмена опытом и для отчета о работе нашей технической команды.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ddos</category>
      <category>network</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>РКН против приложения «Навальный»: борьба за доступность</title>
      <dc:creator>Navalny Team</dc:creator>
      <pubDate>Thu, 14 Sep 2023 10:53:24 +0000</pubDate>
      <link>https://dev.to/navalnyteam/rkn-protiv-prilozhieniia-navalnyi-borba-za-dostupnost-2gg6</link>
      <guid>https://dev.to/navalnyteam/rkn-protiv-prilozhieniia-navalnyi-borba-za-dostupnost-2gg6</guid>
      <description>&lt;p&gt;2021 год в России запомнится беспрецедентной волной судебных и внесудебных блокировок ресурсов средствами DPI. 26 июля Роскомнадзор &lt;a href="https://t.me/leonid_volkov/2684" rel="noopener noreferrer"&gt;заблокировал&lt;/a&gt; множество наших ресурсов: блог Навального &lt;a href="//navalny.com"&gt;navalny.com&lt;/a&gt;, сайт сети региональных штабов &lt;a href="//shtab.navalny.com"&gt;shtab.navalny.com&lt;/a&gt;, сайт Фонда борьбы с коррупцией &lt;a href="//fbk.info"&gt;fbk.info&lt;/a&gt; и другие. Под блокировки попали даже сайты проектов «РосЯма» и «РосЖКХ». Все перечисленные ресурсы были заблокированы на основании требования Генеральной прокуратуры РФ как содержащие призывы к массовым беспорядкам, осуществлению экстремистской деятельности, участию в массовых (публичных) мероприятиях, проводимых с нарушением установленного порядка, и информационные материалы организаций, деятельность которых признана нежелательной. Сайт проекта «&lt;a href="https://votesmart.appspot.com/" rel="noopener noreferrer"&gt;Умное голосование&lt;/a&gt;» заблокировали за неделю до выборов — 6 сентября. С конца августа РКН развернул настоящую войну с приложением «Навальный» и чуть не сломал весь российский интернет.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;В этом посте мы расскажем о противостоянии, которое развернулось между нашей командой и РКН летом — осенью 2021 года: о технологиях по обходу блокировок, о собственной системе мониторинга, о том, как нам удалось «обучить» РКН регулярным выражениям. Расскажем, как РКН был вынужден пойти на крайние меры, попытавшись заблокировать публичные DNS в России, и каких результатов нам удалось достичь.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Введение
&lt;/h2&gt;

&lt;p&gt;Наша команда рассматривала сценарий блокировок заранее. Еще в 2020 году было решено использовать мобильное приложение как основной инструмент доставки до пользователей постов в блоге и рекомендаций «Умного голосования».&lt;/p&gt;

&lt;p&gt;С момента введения повсеместного DPI в России за доступность сайтов стало бороться все сложнее. Дело в том, что протокол TLS не предусматривает шифрование изначального «рукопожатия» между клиентом и сервером. Когда во всем мире обнаружился дефицит IPv4-адресов, виртуальный хостинг (когда несколько разных доменов работают на одном IPv4-адресе) пришлось широко использовать и в TLS. Клиент во время рукопожатия передает доменное имя сайта (SNI — Server Name Indication), а сервер в ответ отправляет сертификат для этого домена. Этим и пользуются разработчики DPI, позволяя различать трафик, идущий на один и тот же адрес, по SNI и блокируя только тот, что им нужно. Во время разработки стандарта ESNI (Encrypted SNI, позволяющего шифровать заголовок SNI) обнаружились неустранимые проблемы, и &lt;a href="https://blog.cloudflare.com/encrypted-client-hello/" rel="noopener noreferrer"&gt;на замену ESNI пришел протокол ECH&lt;/a&gt; (Encrypted Client Hello), в котором шифруется все рукопожатие со стороны клиента. &lt;/p&gt;

&lt;p&gt;Стандарт ECH еще не стал всеобщим. В &lt;a href="https://www.ietf.org/" rel="noopener noreferrer"&gt;IETF&lt;/a&gt; он находится в фазе активной разработки в статусе draft. Последнее обновление &lt;a href="https://dev.to%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%20%D1%81%20%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC%20%D1%81%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%B0"&gt;документа с описанием стандарта&lt;/a&gt; было как раз в августе 2021 года. Следовательно, поддержки этого протокола на конечных клиентах (браузерах) не предусматривалось. Мы понимали, что успешно скрыть домен в HTTPS-трафике от РКН нам не удастся. Поддержка доступности web-сайта стала весьма проблематичной задачей. &lt;/p&gt;

&lt;p&gt;Когда требование Генпрокуратуры о блокировке было исполнено, сетевое сообщество начало организовывать прокси до заблокированных ресурсов команды Навального, однако на практике это оказалось неэффективным. Любой адрес, замеченный РКН, рано или поздно попадал под внесудебную блокировку и моментально становился недоступным. Было понятно, что любые публично распространенные прокси, вне зависимости от доменного имени, будут успешно заблокированы. &lt;/p&gt;

&lt;p&gt;После вступления блокировок в силу мы первым делом провели простой эксперимент: переключили домены наших ресурсов на IP-адреса AppEngine. Были возможны три варианта: либо блокировка полностью отключилась бы (если айпишники AppEngine находились в белом списке у РКН), либо весь AppEngine в российском сегменте интернета оказался бы заблокированным, либо осталась бы только блокировка по DPI. Результаты теста подтвердили наши ожидания — доступность не возросла. Надо отметить, что РКН хорошо подготовился и начиная еще с 2016 года явно обеспечил покрытие DPI на большинстве провайдеров России, в том числе DPI «по сигнатурам» (он же ТСПУ).&lt;/p&gt;

&lt;h2&gt;
  
  
  Система мониторинга
&lt;/h2&gt;

&lt;p&gt;Здесь стоит рассказать о средствах мониторинга, с помощью которых мы проверяли доступность наши ресурсов.&lt;/p&gt;

&lt;p&gt;Блокировки через «технические средства противодействия угрозам» (ТСПУ) нельзя отследить традиционными методами, с помощью реестра РКН или так называемых «&lt;a href="https://vigruzki.rkn.gov.ru/auto-delta/" rel="noopener noreferrer"&gt;Дельт&lt;/a&gt;». Нет никакого подлинного списка ресурсов, которые блокировались на уровне ТСПУ. Блокировки приобрели не подотчетный и непрозрачный характер. Поэтому, чтобы отслеживать работоспособность ресурсов, пришлось разработать систему отслеживания на уровне провайдеров РФ. &lt;/p&gt;

&lt;p&gt;Для реализации такой системы мы приобрели комплекты Raspberry Pi 3B. Наши читатели наверняка знают, что мы фанаты «Малинок», и в этот раз выбор снова пал на них. Эти микрокомпьютеры обладают полноценной Linux-системой на борту, а также оборудованы сетевой картой с портом RJ45, дешевы и просты в обслуживании.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0lbyxxykctwt97rga5j.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0lbyxxykctwt97rga5j.jpeg" alt="Модель RPi" width="800" height="600"&gt;&lt;/a&gt;&lt;em&gt;Модель RPi&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Мы подготовили комплекты Pi для волонтеров, которые согласились установить у себя эти импровизированные «зонды». В каждом из них была скомпилирована ОС на базе Ubuntu 20.04 — так, чтобы волонтеру было достаточно просто подключить Raspberry Pi в сеть. Малинки совершали попытки обращения к нужным ресурсам и отслеживали статус, отсылая его нам.&lt;/p&gt;

&lt;p&gt;Для безопасности мы скрыли весь служебный трафик Pi в VPN-сеть. Оставили только GET-запросы и скачивание пакетов при обновлении.&lt;/p&gt;

&lt;p&gt;Мы постарались охватить своей сетью провайдеров по всей стране. В первую очередь, конечно, нас интересовали провайдеры с ТСПУ, но в связи с тем, что нет никаких официальных публичных списков провайдеров с этим «черным ящиком» на борту, мы пытались придерживаться самых крупных — большой тройки, Ростелекома, Дом.ру и подобных. Сеть составляли где-то 50% провайдеров с ТСПУ и 50% без. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6tu5uyg69ck7hrnsqss.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6tu5uyg69ck7hrnsqss.jpg" alt="Пример download показателей обращения к ресурсу системы мониторинга" width="800" height="496"&gt;&lt;/a&gt;&lt;em&gt;Пример download показателей обращения к ресурсу системы мониторинга&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Для отправки запросов, хранения полученных данных и алертов мы воспользовались Zabbix. Чтобы отслеживать блокировки сайтов, использовалась встроенная функция «веб-агента» и кастомные скрипты, исполняемые на зондах.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1stzc5b1pzs4n04kz63n.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1stzc5b1pzs4n04kz63n.jpg" alt="Дашборд со сканерами системы мониторинга доступности" width="800" height="177"&gt;&lt;/a&gt;&lt;em&gt;Дашборд со сканерами системы мониторинга доступности&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Для обработки и вывода данных использовалась Grafana. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq2kdlz5199perm73ja5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq2kdlz5199perm73ja5.jpg" alt="Дашборд с гистограммой системы мониторинга доступности | Grafana" width="800" height="400"&gt;&lt;/a&gt;&lt;em&gt;Дашборд с гистограммой системы мониторинга доступности | Grafana&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;В дополнение был написан API-микросервис, которому мы дали имя «Navalny Hello». Его основной задачей было выдавать по любому домену информацию о блокировке. Сервис работал на основе Pi-сканеров и выдавал скоринг для каждого запрашиваемого адреса. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawt2z1lubm89xx0be6yh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawt2z1lubm89xx0be6yh.jpeg" alt="_Пример ответа сервиса «Navalny Hello» по запросу доступности ya.ru_" width="800" height="123"&gt;&lt;/a&gt;&lt;em&gt;Пример ответа сервиса «Navalny Hello» по запросу доступности ya.ru&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Требования к приложению
&lt;/h2&gt;

&lt;p&gt;В приложении «Навальный» важнее всего было обеспечить доступность блога и проекта «Умное голосование». Мы публикуем свои рекомендации по голосованию за три дня до выборов, чтобы власти не могли снять кандидатов, за которых мы советуем отдать голос. При этом основная функция приложения — поиск кандидата по адресу (пользователь вбивает свой адрес и получает конкретную рекомендацию). Таким образом, залить список кандидатов прямо в приложение через стандартное обновление было проблематично, — тем более что обновление в Google Play и Apple Store могут рассматривать от нескольких суток до нескольких недель, и такого запаса времени у нас просто не было. &lt;/p&gt;

&lt;p&gt;Учитывая необходимость постоянной синхронизации приложения с адресами нашего бэкенда, мы выделили в основных требованиях несколько пунктов:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;защита от блокировки IP-адресов;&lt;/li&gt;
&lt;li&gt;защита от блокировки доменных имен;&lt;/li&gt;
&lt;li&gt;обход DPI-средств провайдеров.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Был подготовлен специальный протокол, который поддерживал технологии DoH-резолвинга и SNI-фейкинга. С помощью закодированного дискавери, представляющего собой JSON-структуру с электронной подписью для защиты от replay-атак, мы смогли «научить» приложение обнаруживать через публичные DNS актуальные адреса бэкенда для обновления контента. Когда РКН банил наш адрес, мы создавали новый, обновляли дискавери, контролировали значение TTL записи, чтобы избежать ненужного кэширования, и таким образом перед очередным запросом пользователя сообщали для него новый маршрут обращения. К моменту блокировки сайтов приложение работало в Google Play и Apple Store с данной версией протокола.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ротация «на опережение»
&lt;/h2&gt;

&lt;p&gt;В «мирное время» секретные механизмы предусмотрительно не были включены. Специалисты РКН не совершали никаких дополнительных действий после блокировок сайтов и только 20 августа &lt;a href="https://t.me/teamnavalny/7957" rel="noopener noreferrer"&gt;перешли в наступление&lt;/a&gt; и предприняли первые попытки заблокировать адреса приложения. Чтобы не раскрывать все наши возможности до дня выборов, мы решили поиграть в «догонялки» с РКН — обновляя дискавери и доставляя пользователю необходимые адреса. &lt;/p&gt;

&lt;p&gt;По первым наблюдениям, РКН осуществлял мониторинг чуть ли не вручную. Мы запустили скрипт, который генерил доменные адреса третьего уровня, например 1np13q836n.rkngov.com, готовил для них необходимый бекенд и отдавал в дискавери. Согласно нашему мониторингу, каждый новый адрес третьего уровня попадал в реестр и разливался в качестве правила по провайдерам в течение 13 минут. Когда в РКН удостоверились, что мы осуществляем ротацию доменов исключительно третьего уровня, они наконец решились блокировать всю вторую зону целиком. &lt;/p&gt;

&lt;p&gt;Мы вернулись к варианту хостинга на AppEngine. Каждый проект в AppEngine автоматически получает служебное доменное имя, соответствующее идентификатору проекта. Скажем, проект navalnyapp был доступен на домене navalnyapp.appspot.com. Однако в документации на AppEngine мы обнаружили, что сайты получают и другие служебные домены, например &amp;lt;версия&amp;gt;-dot-&amp;lt;проект&amp;gt;.appspot.com. Если в качестве версии указать любую строку, для которой даже нет версии приложения, то осуществляется fallback на основную версию. В итоге адрес для ротации в дискавери был заменен на *-dot-navalnyapp.appspot.com. Основная задумка заключалась в том, что в данном случае нам не обязательно было «заранее готовить» новый адрес. Достаточно было дописать перед «dot» любое слово, и такие адреса моментально мапились на наш проект. РКН стал получать на своих сканерах «адреса-приветы» от нашей команды — &lt;code&gt;putinprivet-dot-navalnyapp.appspot.com&lt;/code&gt;, &lt;code&gt;putinvor-dot-navalnyapp.appspot.com&lt;/code&gt;, &lt;code&gt;123putinuhodi-dot-navalnyapp.appspot.com&lt;/code&gt;, 321putin… и т.д.&lt;/p&gt;

&lt;p&gt;Вы можете проверить это сами: придумайте любой аналогичный адрес, вбейте его в браузер — и обнаружите на корневом адресе блог Навального. Впрочем, сейчас все эти адреса заблокированы. &lt;/p&gt;

&lt;p&gt;Подобным образом мы могли осуществлять ротацию в любом объеме и в любое время. На сканерах своего мониторинга мы мгновенно увидели 100% доступность, и РКН встал перед выбором: либо обучить свой блокирующий софт регуляркам, либо заблокировать всю зону &lt;a href="http://appspot.com/" rel="noopener noreferrer"&gt;appspot.com&lt;/a&gt; целиком.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft18gmn4t8yhyeuck3nat.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft18gmn4t8yhyeuck3nat.jpg" alt="Дашборд с показателями доступности приложения при ротации служебных доменов -dot-navalnyapp.appspot.com&lt;br&gt;
" width="800" height="415"&gt;&lt;/a&gt;&lt;em&gt;Дашборд с показателями доступности приложения при ротации служебных доменов -dot-navalnyapp.appspot.com&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Интересно заметить, что на любой «нестандартный» ход РКН реагировал с предсказуемой задержкой. Если мы что-то меняли в будни, условно говоря, с 9 до 17, то реакция следовала в течение часа. А если дожидались вечера (лучше, конечно, вечера пятницы), то 100% доступность сохранялась до 9 утра следующего рабочего дня. Видимо, у РКН была дежурная смена юных падаванов, которые делали какие-то совсем простые вещи, и настоящие специалисты, доступные только в рабочее время.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Пока РКН «учился в регулярки», мы смогли выиграть время и апгрейднуть скрипт ротации наших адресов. В связи с тем, что любые из них блокировались сразу по второй зоне целиком, мы написали обновление. Скрипт заблаговременно и массово регистрировал домены второго уровня у одного из хостеров. Затем регистрировал их в CloudFlare, добавлял в качестве кастомного домена к нашему проекту в AppEngine (это было сделано для того, чтобы исключить блокировки на тех провайдерах, которые не умели в DPI), ждал выпуска сертификата гуглом и каждые n минут подсовывал эти домены приложению, предварительно проверяя через наш вышеупомянутый сервис «Navalny Hello», не заблокированы ли они. Таким образом мы могли переиспользовать адрес, если РКН со временем внезапно решил бы его «разблокировать». Благодаря непрерывному мониторингу, ускоряя или замедляя скрипт, мы могли определить, за какое время РКН замечал новые адреса второго уровня. В очередной раз на сканеры нашего противника посыпались «адреса-приветы» самых дешевых доменных зон, например: &lt;code&gt;www.roskomnadzoruhodi.website&lt;/code&gt;, &lt;code&gt;www.svobodypoltzakluchennym.xyz&lt;/code&gt;, &lt;code&gt;www.rknidiotdohni.fun&lt;/code&gt;, &lt;code&gt;www.privetput1nu.online&lt;/code&gt;, &lt;code&gt;www.rknprivet.club&lt;/code&gt; и т.д.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Вы можете проверить — эти адреса и сейчас заблокированы у провайдеров с ТСПУ.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CDN вступают в борьбу
&lt;/h2&gt;

&lt;p&gt;Мы в очередной раз выиграли время. Но предварительная покупка доменов, пускай даже в самых дешевых доменных зонах, в долгосрочной перспективе и в больших объемах требует затрат, а от идентификации адреса до распространения блокировки на провайдерах, согласно нашим сканерам, стало проходить не больше минуты. Так что мы решили обратиться за адресами к известным CDN-провайдерам, таким как Bunny, Fastly, Amazon.&lt;/p&gt;

&lt;p&gt;Идея заключалась в том, что РКН не решится целиком блокировать доменную зону второго уровня, предоставляемую публично известными CDN-провайдерами, и мы сможем относительно дешево проксировать трафик на бэкенд с их помощью через дискавери приложения. Пошаманив с настройками кэширования, мы запустили скрипт ротации доменов с CDN от Bunny.&lt;/p&gt;

&lt;p&gt;К моменту, когда мы израсходовали более 6000 подобных адресов, РКН также провел несколько тестов и все-таки решился на полную блокировку b-cdn.net на ТСПУ. Практически сразу мы получили письмо от основателя Bunny о том, что они вынуждены приостановить действие нашей учетной записи, поскольку это негативно сказывается на доступности их сети. Вероятнее всего, блокировкой РКН был задеты крупные пользователи Bunny в России. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsc5i6iqov3l7hta28ik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmsc5i6iqov3l7hta28ik.png" alt="Письмо от основателя Bunny c объяснением причин блокировки аккаунта" width="800" height="300"&gt;&lt;/a&gt;&lt;em&gt;Письмо от основателя Bunny c объяснением причин блокировки аккаунта&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;В качестве резервного варианта мы переключили ротацию на Fastly CDN. Это работало нормально, и на этот раз РКН уже не решался блокировать весь второй уровень более известного CDN. Но из-за ограничений по количеству создаваемых зон на аккаунт (мы не всегда могли освобождать старые зоны, и из-за багов в приложении в некоторых случаях приходилось их «копить», чтобы устройства, получившие старую запись дискавери, могли их использовать неограниченно долго) в конечном счете мы перешли на адреса Amazon. Довольно легко получилось арендовать на старте квоту со значением около 10 тысяч адресов в доменной зоне cloudfront.net. &lt;/p&gt;

&lt;p&gt;На этот раз мы наконец создали очевидную проблему для РКН. Адреса пачками и довольно быстро готовились по ранее опробованному скрипту. Наши сканеры зафиксировали рост доступности.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j5f77b8kae3qgfq4rpo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j5f77b8kae3qgfq4rpo.jpg" alt="Доступность адресов CloudFront при использовании в ротации приложения «Навальный»&lt;br&gt;
" width="800" height="403"&gt;&lt;/a&gt;&lt;em&gt;Доступность адресов CloudFront при использовании в ротации приложения «Навальный»&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS под угрозой. Domain Fronting, DOH-резолвинг и NewNode
&lt;/h2&gt;

&lt;p&gt;РКН оставалось разве что блокировать Amazon целиком. И в этом случае у нашего приложения в запасе еще оставался секретный механизм SNI-фейкинга. Именно в этот момент в РКН впервые задумались о запрете на использование публичных систем доменных имен и начали готовить российских провайдеров к отключению Google и CloudFlare DNS, рассылая соответствующие письма с требованиями.&lt;/p&gt;

&lt;p&gt;В РКН даже провели тесты кратковременной блокировки DNS, что и зафиксировал наш мониторинг и пользователи российского сегмента интернета:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9cqtx83e0w0t80rnwin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9cqtx83e0w0t80rnwin.png" alt="https://twitter.com/ShieldNav/status/1435692644676603906?" width="800" height="922"&gt;&lt;/a&gt;&lt;em&gt;&lt;a href="https://twitter.com/ShieldNav/status/1435692644676603906?" rel="noopener noreferrer"&gt;https://twitter.com/ShieldNav/status/1435692644676603906?&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Мы были готовы и к такому развитию событий. Все это время параллельно работали над дополнительными обновлениями по обходу блокировок для нашего приложения. Мы полагали, что  на один день забанить Tor ради большой цели для РКН, в принципе, не будет проблемой. Конечно, у этого был бы медийный эффект, но, вероятнее всего, кроме гиков, этого бы почти никто не заметил. Так что идею использовать Tor мы отложили. Была подготовлена имплементация технологии Domain Fronting в качестве дополнительного инструментария, которым мы могли аналогичным образом управлять на стороне клиента через дискавери. А также DOH-резолвинга на случай полной блокировки DNS в России и решения проблемы обновления самого дискавери. &lt;/p&gt;

&lt;p&gt;Идея была проста. Вышеупомянутый &lt;a href="http://navalnyapp.appspot.com/" rel="noopener noreferrer"&gt;navalnyapp.appspot.com&lt;/a&gt;, (а вообще говоря, любой &lt;code&gt;*.appspot.com&lt;/code&gt;) можно фронтить в любой &lt;code&gt;${garbage}.appspot.com&lt;/code&gt;. Мы также нашли возможность фронтинга еще через пару популярных ресурсов, которые РКН вряд ли решился бы полностью отключить в России (мы намеренно не хотели бы раскрывать их в этом посте). Добавили этот функционал в приложение. А в качестве DoH-резолвинга расширили параметры нашего дискавери до получения записей с нескольких storage-провайдеров, таких как Google Cloud Storage и Amazon S3, на случай блокировки Google и Cloudflare DNS.&lt;/p&gt;

&lt;p&gt;Помимо всего прочего, в приложении закладывались и альтернативные, весьма экспериментальные способы обхода блокировок, включая использование механизмов P2P-обмена данными. В конце августа  нам удалось сделать экспериментальную сборку приложения с внедренным &lt;a href="https://github.com/clostra/newnode" rel="noopener noreferrer"&gt;SDK под названием NewNode&lt;/a&gt;. NewNode, а также экспериментальный мессенджер FireSide — продолжение исследовательских работ, которую ведет команда, известная другим инновационным мессенджером FireChat. Они же разработчики известного протокола BitTorrent. По задумке, эта штука должна обеспечивать связность даже при полном отключении интернета —  просто передавая данные от клиента к клиенту через встроенные в мобильные телефоны модули беспроводной связи Bluetooth и Wi-Fi. SDK NewNode специально создан, чтоб научить мобильные приложения использовать для передачи данных встроенные механизмы BitTorrent, &lt;a href="https://en.wikipedia.org/wiki/LEDBAT" rel="noopener noreferrer"&gt;LEDBAT&lt;/a&gt; и передачу данных от устройства к устройству (Device-to-Device Communications —  D2D). Эта тема, пожалуй, заслуживает отдельной статьи, но если коротко объяснять принцип работы, то для разработчика SDK выглядит как прокси, который использует собственный транспорт для передачи данных: сильно переделанный DHT в качестве транспорта, D2D для организации каналов в случае недоступности интернет-ресурса и LEDBAT, который оркеструет все доступные способы коммуникации, выбирая самый эффективный в данный момент времени.&lt;/p&gt;

&lt;p&gt;В постановлении РКН о блокировке публичных DNS для провайдеров предполагалась также блокировка DNS-over-UDP. Именно этот сервис использует библиотека NewNode, что только укрепляет нас в мысли о направленности данных действий исключительно против нашего приложения. Примерно в то же время &lt;a href="https://github.com/TeamNewPipe/NewPipe/issues/7114" rel="noopener noreferrer"&gt;была замечена&lt;/a&gt; и блокировка приложения &lt;a href="https://newpipe.net/" rel="noopener noreferrer"&gt;NewPipe&lt;/a&gt; в части YouTube-клиента по TLS-отпечаткам. РКН, по всей видимости, проводил предварительные тесты (или перепутал NewNode или NewPipe? :))&lt;/p&gt;

&lt;p&gt;Последняя сборка приложения «Навальный» содержит NewNode и потенциально будет работать (в крупных городах с большой плотностью населения) даже при полном отключении интернета.&lt;/p&gt;

&lt;h2&gt;
  
  
  Заключение
&lt;/h2&gt;

&lt;p&gt;Паралельно с ежеминутными блокировками, РКН продолжал отправлять запросы в Apple и Google с требованиями удалить приложение «Навальный». Письмами дело не ограничилось — представителей Apple и Google &lt;a href="https://meduza.io/news/2021/09/15/k-nim-ochen-mnogo-voprosov-sovet-federatsii-vyzval-predstaviteley-google-i-apple-na-zasedanie-komissii-po-zaschite-suvereniteta" rel="noopener noreferrer"&gt;вызвали&lt;/a&gt; в Совет Федерации на заседание комиссии по защите суверенитета: неисполнение требований РКН было воспринято как иностранное вмешательство в выборы. В московский офис Google незадолго до выборов &lt;a href="https://www.bbc.com/russian/news-58547764" rel="noopener noreferrer"&gt;приходили силовики&lt;/a&gt;. Дошло и до прямого шантажа — по данным издания Bloomberg, российским сотрудникам Google &lt;a href="https://www.bloomberg.com/news/articles/2021-09-16/russia-targets-google-apple-in-crackdown-before-elections" rel="noopener noreferrer"&gt;угрожали тюрьмой&lt;/a&gt;, если компания не исполнит требования властей. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0btziqg2urz63bpkkfve.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0btziqg2urz63bpkkfve.jpeg" alt="Письмо от РКН с запросом удаления приложения «Навальный» в App Store service&lt;br&gt;
" width="800" height="444"&gt;&lt;/a&gt;&lt;em&gt;Письмо от РКН с запросом удаления приложения «Навальный» в App Store service&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Само собой, все эти процессы усложняли ревью любых наших обновлений. Рассмотрение зависало на несколько дней. Последние обновления, c domain-fronting и DoH-резолвингом, мы отправили 10 сентября. Очевидно, РКН рассматривал два сценария: либо приложение удастся удалить, либо придется ломать половину российского интернета, отключив публичные DNS. К счастью для РКН, им удалось добиться первого сценария. В конце концов, после почти недельного ожидания, наши обновления были подтверждены магазинами приложений, но одновременно с этим их моментально &lt;a href="https://t.me/leonid_volkov/2819" rel="noopener noreferrer"&gt;удалили из магазинов&lt;/a&gt;. Это случилось утром 17 сентября, в первый день выборов. А в ночь на 18 сентября Павел Дуров, сославшись на действия Apple и Google, &lt;a href="https://t.me/durov_russia/32" rel="noopener noreferrer"&gt;заблокировал&lt;/a&gt; &lt;a href="https://t.me/smartvotebot" rel="noopener noreferrer"&gt;телеграм-бота&lt;/a&gt; «Умного голосования». &lt;/p&gt;

&lt;p&gt;Наша команда распространяла списки с рекомендациями кандидатов через &lt;a href="https://github.com/smartvotedocs/votesmart2021" rel="noopener noreferrer"&gt;репозиторий в Github&lt;/a&gt;, &lt;a href="https://docs.google.com/document/u/2/d/e/2PACX-1vSe2exjjq0o246AaIiUGPN48SAevbeOke09ZvMrwqcfVDk5Lz-MnHy3temxMSOBJY3Kfo4pjvgNbPIS/pub" rel="noopener noreferrer"&gt;Google Docs&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=OVPorDqxE28" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt; и &lt;a href="https://github.com/smartvotedocs/votesmart2021/releases/tag/android-app" rel="noopener noreferrer"&gt;APK-сборку Android&lt;/a&gt; c последними алгоритмами обхода блокировок. Аккурат перед днем голосования мы успели закинуть в магазины приложений и последнее обновление с обычным PDF-файлом, где содержался список рекомендованных кандидатов. Так что те немногие пользователи, которые все-таки успели обновить или загрузить сборку, могли потрясти телефон и посмотреть в PDF список кандидатов даже без активного подключения к интернету.&lt;/p&gt;

&lt;p&gt;В РКН понимают, что, как только будет принят стандарт ECH и его реализация доедет до серверов крупных сервисов и в популярные браузеры, бороться с непокорными сайтами станет намного сложнее. DPI придется полагаться исключительно на фингерпринтинг со всеми вытекающими ложноположительными и ложноотрицательными проблемами. Иными словами, им придется или поломать весь интернет, или сдаться. Поэтому на следующий день после выборов Министерство цифрового развития, связи и массовых коммуникаций РФ разместило для общественного обсуждения &lt;a href="https://regulation.gov.ru/projects#npa=108513" rel="noopener noreferrer"&gt;проект&lt;/a&gt; федерального закона, устанавливающего запрет на использование на территории РФ протоколов шифрования, которые позволяют скрыть имя (идентификатор) интернет-страницы или сайта. Нет никаких сомнений, что это происходит для «легализации» борьбы с новыми методами обхода блокировок, подобными тем, над которыми работала наша команда в этом году. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Такие законы с каждым днем делают российский сегмент интернета все хуже — менее безопасным и менее приватным. Наша задача — отстоять его.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Вся эта работа была бы невозможна без вашей поддержки. Огромное спасибо!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Поддержать работу IT-отдела можно здесь:&lt;/strong&gt; &lt;a href="https://donate.fbk.info/it" rel="noopener noreferrer"&gt;https://donate.fbk.info/it&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Этот пост был опубликован на портале Habr и в 2022 году скрыт администрацией сайта. В поддержке сослались на особый порядок блокировки «в отношении материалов от организаций, признанных экстремистскими» и на «инициирование политических дискуссий». Мы выступаем против ограничения свободы распространения информации. Habr мы рассматривали как площадку для обмена опытом и для отчета о работе нашей технической команды.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ios</category>
      <category>android</category>
      <category>devops</category>
      <category>network</category>
    </item>
  </channel>
</rss>
