Skip to content

Commit 1fc2d78

Browse files
authored
Подготовить безопасный выпуск 1.8.1 (#1)
* Усилить проверку курсов и выпуск 1.8.1 * Сохранить диагностику тестов Android 15 * Включить ускорение KVM в Android CI * Исправить запуск диагностики Android CI * Исправить отказоустойчивость выпуска 1.8.1 * Учесть замечания ревью к интерфейсу * Исправить замечания независимого ревью --------- Co-authored-by: German Abrikosov <224131170+Etogerman@users.noreply.github.com>
1 parent 9311b7b commit 1fc2d78

49 files changed

Lines changed: 1623 additions & 275 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/dependabot.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: gradle
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
day: monday
8+
open-pull-requests-limit: 5
9+
groups:
10+
android-dependencies:
11+
patterns:
12+
- "androidx.*"
13+
14+
- package-ecosystem: github-actions
15+
directory: /
16+
schedule:
17+
interval: weekly
18+
day: monday
19+
open-pull-requests-limit: 5

.github/workflows/android.yml

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name: Проверка Android
33
on:
44
push:
55
branches: [main]
6+
tags: ["v*"]
67
pull_request:
78

89
permissions:
@@ -16,19 +17,127 @@ jobs:
1617

1718
steps:
1819
- name: Получить исходный код
19-
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
20+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21+
with:
22+
fetch-depth: 0
2023

2124
- name: Настроить JDK 17
22-
uses: actions/setup-java@d7793b545071e98d581d3bf084a51c3213318a07 # v4
25+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
2326
with:
2427
distribution: temurin
2528
java-version: "17"
2629

2730
- name: Проверить Gradle Wrapper
28-
uses: gradle/actions/wrapper-validation@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
31+
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
2932

3033
- name: Настроить Gradle
31-
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
34+
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
35+
36+
- name: Проверить пробелы в изменениях
37+
env:
38+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
39+
run: |
40+
if [[ -n "$BASE_SHA" ]]; then
41+
git diff --check "$BASE_SHA"...HEAD
42+
else
43+
git diff --check HEAD^ HEAD
44+
fi
3245
3346
- name: Проверить проект
34-
run: ./gradlew --no-daemon test lint assembleDebug
47+
run: ./gradlew --no-daemon test lint assembleDebug assembleRelease
48+
49+
- name: Сохранить неподписанную релизную сборку
50+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
51+
with:
52+
name: CleanRate-unsigned-${{ github.sha }}
53+
path: app/build/outputs/apk/release/app-release-unsigned.apk
54+
if-no-files-found: error
55+
retention-days: 14
56+
57+
- name: Сохранить таблицу деобфускации
58+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
59+
with:
60+
name: CleanRate-mapping-${{ github.sha }}
61+
path: app/build/outputs/mapping/release/mapping.txt
62+
if-no-files-found: error
63+
retention-days: 14
64+
65+
provenance:
66+
name: Происхождение сборки тега
67+
if: startsWith(github.ref, 'refs/tags/v')
68+
needs: [test, android-runtime]
69+
runs-on: ubuntu-latest
70+
permissions:
71+
contents: read
72+
id-token: write
73+
attestations: write
74+
75+
steps:
76+
- name: Получить неподписанную сборку
77+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
78+
with:
79+
name: CleanRate-unsigned-${{ github.sha }}
80+
path: release
81+
82+
- name: Проверить состав артефакта
83+
run: test -f release/app-release-unsigned.apk
84+
85+
- name: Подтвердить происхождение APK
86+
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
87+
with:
88+
subject-path: release/app-release-unsigned.apk
89+
90+
android-runtime:
91+
name: Проверка на Android 15
92+
runs-on: ubuntu-latest
93+
timeout-minutes: 30
94+
permissions:
95+
contents: read
96+
97+
steps:
98+
- name: Получить исходный код
99+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
100+
101+
- name: Разрешить аппаратное ускорение KVM
102+
run: |
103+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
104+
sudo udevadm control --reload-rules
105+
sudo udevadm trigger --name-match=kvm
106+
107+
- name: Настроить JDK 17
108+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
109+
with:
110+
distribution: temurin
111+
java-version: "17"
112+
113+
- name: Настроить Gradle
114+
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
115+
116+
- name: Запустить инструментальные тесты
117+
id: android-tests
118+
continue-on-error: true
119+
uses: ReactiveCircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
120+
with:
121+
api-level: 35
122+
arch: x86_64
123+
target: default
124+
disable-animations: true
125+
script: >-
126+
./gradlew --no-daemon connectedDebugAndroidTest ||
127+
{ android_test_status=$?; echo "Инструментальные тесты завершились ошибкой. Последние строки logcat:";
128+
adb logcat -d -v threadtime | tail -n 4000; exit "$android_test_status"; }
129+
130+
- name: Сохранить отчёт инструментальных тестов
131+
if: always()
132+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
133+
with:
134+
name: Android-15-test-report-${{ github.sha }}
135+
path: |
136+
app/build/outputs/androidTest-results/connected/
137+
app/build/reports/androidTests/connected/
138+
if-no-files-found: warn
139+
retention-days: 7
140+
141+
- name: Завершить проверку при сбое тестов
142+
if: steps.android-tests.outcome == 'failure'
143+
run: exit 1

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
Все заметные изменения проекта перечисляются в этом файле.
44

5+
## 1.8.1 — 2026-08-02
6+
7+
- данные с устаревшей или неправдоподобно будущей датой больше не считаются актуальными;
8+
- сетевые ответы ограничены по размеру, разрешены только HTTPS-переходы, XML ЦБ разбирается с защитой от внешних сущностей;
9+
- точки графика размещаются по реальным календарным датам, а пропуски данных явно обозначаются;
10+
- кэш истории ограничен, загрузка имеет общий отменяемый тайм-аут, а при ошибке используется сохранённый график;
11+
- резервные адреса текущих и исторических курсов проверяются не только по HTTP-ответу, но и по содержимому данных;
12+
- статус курса учитывает только реально используемые автоматические источники, включая смешанные наборы ЦБ и ручных курсов;
13+
- после перезапуска восстанавливаются последний открытый экран, валютная пара, период и сумма графика;
14+
- смена источника во время обновления не теряет последующий запрос;
15+
- график получил действия для TalkBack;
16+
- проект переведён на Android API 36, Gradle 8.14.5, Android Gradle Plugin 8.11.1 и согласованный Compose BOM;
17+
- CI проверяет релизную сборку и запуск на Android 15, раздельно сохраняет APK и таблицу деобфускации и подтверждает происхождение APK из тегов;
18+
- добавлены Dependabot, инструкция проверяемого выпуска и сведения о происхождении звука.
19+
520
## 1.8.0 — 2026-08-02
621

722
- мировой курс переведён на открытый источник `exchange-api` с лицензией CC0 1.0;

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
1. Создайте ответвление репозитория и отдельную ветку.
66
2. Не добавляйте ключи подписи, пароли, APK, AAB, `local.properties` и локальные пути.
7-
3. Выполните `./gradlew test lint assembleDebug`.
7+
3. Выполните `./gradlew test lint assembleDebug assembleRelease`.
88
4. Заголовок и описание запроса на слияние, а также комментарии об изменениях пишите на русском языке.
99
5. Опишите изменение и результат проверки в запросе на слияние.
1010

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Конвертер валют для Android
1+
# Конвертер валют для Android
22

33
Персональный Android-конвертер валют без рекламы, регистрации и аналитики.
44

@@ -23,7 +23,7 @@
2323
## Установка APK
2424

2525
1. Откройте раздел **Releases** справа на странице репозитория.
26-
2. Выберите последний выпуск и скачайте файл `CleanRate-1.8.0.apk`.
26+
2. Выберите последний выпуск и скачайте файл `CleanRate-1.8.1.apk`.
2727
3. Если Android спросит разрешение, временно разрешите установку из браузера или файлового менеджера.
2828
4. Откройте APK и подтвердите установку.
2929
5. После установки верните запрет на установку неизвестных приложений, если он больше не нужен.
@@ -41,6 +41,9 @@
4141
- введённые суммы и собственные курсы банка остаются на устройстве;
4242
- ключ подписи не хранится в репозитории.
4343

44+
Официальный сертификат подписи имеет SHA-256-отпечаток
45+
`1798311602fa64b2f43832bcd16252d6ae338654653f9b149f4b33261b83ded0`.
46+
4447
Полный текст: [политика конфиденциальности](PRIVACY.md). Инструкции по сообщениям об уязвимостях: [SECURITY.md](SECURITY.md).
4548

4649
## Источники курсов
@@ -52,15 +55,15 @@
5255

5356
## Сборка
5457

55-
Нужны JDK 17 и Android SDK 35.
58+
Нужны JDK 17 и Android SDK 36.
5659

5760
```shell
58-
./gradlew test lint assembleDebug
61+
./gradlew test lint assembleDebug assembleRelease
5962
```
6063

6164
Отладочный APK появится в `app/build/outputs/apk/debug/app-debug.apk`.
6265

63-
Релизные APK подписываются владельцем проекта локально. Ключ подписи и его пароль нельзя добавлять в Git, задачи CI или публичные архивы.
66+
Релизные APK подписываются владельцем проекта локально. Ключ подписи и его пароль нельзя добавлять в Git, задачи CI или публичные архивы. Полная проверяемая процедура описана в [инструкции по выпуску](RELEASING.md).
6467

6568
## Участие в разработке
6669

RELEASE_NOTES_1.8.1.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Чистый курс 1.8.1
2+
3+
Технический выпуск, который делает обновление курсов и публикацию APK проверяемыми.
4+
5+
## Что изменилось
6+
7+
- приложение отличает свежую дату курса от времени последней проверки и предупреждает об устаревших данных;
8+
- ответы сервисов ограничены по размеру, перенаправления остаются только на HTTPS, XML Банка России защищён от внешних сущностей;
9+
- график сохраняет календарное расстояние между датами и сообщает о пропущенных точках;
10+
- выбор дат графика доступен через действия TalkBack;
11+
- кэш истории ограничен 24 наборами, а одна загрузка — 60 секундами; при тайм-ауте доступен ранее сохранённый график;
12+
- резервные источники используются и тогда, когда основной адрес ответил некорректными или устаревшими данными;
13+
- предупреждение учитывает только источники, которые действительно используются выбранными валютами ЦБ или ручными курсами;
14+
- последний открытый экран, валютная пара, период и сумма графика восстанавливаются после перезапуска;
15+
- смена источника во время загрузки безопасно запускает необходимое повторное обновление;
16+
- целевая версия Android обновлена до API 36;
17+
- CI собирает debug и release, запускает инструментальные тесты на Android 15, раздельно сохраняет APK и таблицу деобфускации, а для тега создаёт аттестацию происхождения неподписанного APK.
18+
19+
Данные, настройки и последний экран сохраняются. Обновление устанавливается поверх 1.8.0 без удаления пользовательских данных.

RELEASING.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Проверяемый выпуск Android-приложения
2+
3+
Эта процедура связывает исходный коммит, проверку CI, неподписанную сборку, локальную подпись и опубликованный APK. Секретный ключ остаётся только у владельца.
4+
5+
## Один раз для репозитория
6+
7+
1. Включить **Settings → General → Releases → Release immutability**. Правило действует только на будущие выпуски.
8+
2. Включить **Settings → Security → Advanced Security → Private vulnerability reporting**, Dependabot alerts и Dependabot security updates.
9+
3. Защитить `main`: изменения через pull request и обязательная успешная проверка **Тесты, анализ и сборка**.
10+
4. Настроить подпись тегов Git и проверить, что GitHub показывает метку **Verified**.
11+
12+
## Проверка исходного кода
13+
14+
1. Убедиться, что версия в `app/build.gradle.kts`, `CHANGELOG.md` и заметках выпуска совпадает.
15+
2. Выполнить на чистом рабочем дереве:
16+
17+
```shell
18+
./gradlew --no-daemon test lint assembleDebug assembleRelease
19+
git diff --check
20+
```
21+
22+
3. Слить прошедший ревью pull request в `main`.
23+
4. Создать подписанный тег на точном коммите выпуска и отправить его:
24+
25+
```shell
26+
git tag -s v1.8.1 -m "Чистый курс 1.8.1"
27+
git push origin v1.8.1
28+
```
29+
30+
## Получение и подпись APK
31+
32+
1. Дождаться успешной задачи GitHub Actions для тега.
33+
2. Скачать артефакт `CleanRate-unsigned-COMMIT_SHA` из этой задачи.
34+
3. Проверить его происхождение:
35+
36+
```shell
37+
gh attestation verify app-release-unsigned.apk --repo Etogerman/clean-rate-android
38+
```
39+
40+
4. Подписать APK локально штатным `apksigner`, не копируя ключ в репозиторий или CI:
41+
42+
```shell
43+
apksigner sign --ks /БЕЗОПАСНЫЙ/ПУТЬ/release.jks --out CleanRate-1.8.1.apk app-release-unsigned.apk
44+
apksigner verify --verbose --print-certs CleanRate-1.8.1.apk
45+
```
46+
47+
Ожидаемый SHA-256-отпечаток сертификата:
48+
49+
```text
50+
1798311602fa64b2f43832bcd16252d6ae338654653f9b149f4b33261b83ded0
51+
```
52+
53+
5. Создать контрольную сумму:
54+
55+
```shell
56+
shasum -a 256 CleanRate-1.8.1.apk > CleanRate-1.8.1-SHA256SUMS.txt
57+
```
58+
59+
## Публикация
60+
61+
1. Создать черновик GitHub Release на уже проверенном теге `v1.8.1`.
62+
2. Приложить `CleanRate-1.8.1.apk`, файл контрольной суммы и текст `RELEASE_NOTES_1.8.1.md`.
63+
3. Ещё раз скачать файлы из черновика, сверить SHA-256, имя пакета, `versionCode`, `versionName` и сертификат.
64+
4. Опубликовать выпуск. После публикации не заменять файлы; исправления выпускать новой версией.
65+
66+
Подписанный APK закономерно отличается по SHA-256 от неподписанного артефакта CI: блок подписи добавляется после сборки. Проверка сертификата подтверждает владельца, аттестация — происхождение исходной неподписанной сборки, а неизменяемый Release защищает опубликованные файлы от последующей замены.

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
Не публикуйте ключи, пароли, персональные данные или подробности реально эксплуатируемой уязвимости в открытом Issue.
1010

11-
После публикации репозитория используйте **Security → Advisories → New draft security advisory**. Если функция недоступна, создайте краткий Issue без технических подробностей и попросите владельца проекта открыть приватный канал.
11+
Используйте **Security → Advisories → Report a vulnerability**. Эта форма передаёт отчёт владельцу приватно. Не создавайте публичный Issue с техническими подробностями уязвимости.
1212

1313
## Проверка APK
1414

15-
Скачивайте APK только из раздела Releases этого репозитория и сверяйте SHA-256 с файлом `SHA256SUMS.txt`, приложенным к тому же выпуску.
15+
Скачивайте APK только из раздела Releases этого репозитория и сверяйте SHA-256 с файлом `SHA256SUMS.txt`, приложенным к тому же выпуску. Дополнительно проверьте сертификат командой `apksigner verify --print-certs CleanRate-VERSION.apk`: SHA-256 должен совпасть с отпечатком из README.

0 commit comments

Comments
 (0)