From 5a276ce8d7447c834b39e33e5107dfe4f8e5f84e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:06:52 +0000 Subject: [PATCH 1/5] docs: expand Child Safety Standards for Play Store compliance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Google Play rejected v446 because the published Child Safety Standards did not explicitly prohibit CSAE, name a child-safety point of contact, describe the in-app reporting mechanism, or reference the app/developer as listed on Play. Rewrites the section in PRIVACY.md to: - Explain that Amethyst is a client, not a host: third-party relays host content and are responsible for moderation and any NCMEC reporting obligations (e.g. 18 U.S.C. §2258A). - Explicitly prohibit CSAE/CSAM in the app. - Document the in-app tools users have: Report Post, Report Account, Block Post/Account, Block Relay (NIP-51 Blocked Relay List), Mute Words/Hashtags. - Describe the escalation path: report in-app, block the hosting relay, report to NCMEC CyberTipline / INHOPE, optionally email the developer. - Provide a child-safety point of contact (amethyst@vitorpamplona.com) with realistic scope of action (forward to relay ops, drop the relay from default lists). - Reference the app name "Amethyst" and developer "Vitor Pamplona" as required by the checklist. Also surfaces the document from inside the app by adding an "About & Legal" section to Settings with two items: Privacy Policy and Child Safety Standards (anchor link to the section). --- PRIVACY.md | 52 ++++++++++++++++++- .../loggedIn/settings/AllSettingsScreen.kt | 28 ++++++++++ amethyst/src/main/res/values/strings.xml | 2 + 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index cfed3b5e56..cc4dafdf8d 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -33,9 +33,57 @@ The information you share is publicly visible to anyone reading from relays that Information shared on Nostr should be assumed permanent for privacy purposes. There is no way to guarantee deleting or editing any content once posted. -## Child safety standards +## Child Safety Standards -Amethyst does not knowingly collect information from children. The app has no age verification because it collects no personal information from anyone. The application is 17+. We rely on Google Play's age verification to make sure the user downloading the app is an adult. Since we do not control which relays the user connects to, there is no content moderation beyond the standard block post, block account, and report post and/or account that will hide the content from the user. +Amethyst, published on Google Play by Vitor Pamplona, is committed to protecting children from sexual abuse and exploitation. These standards apply to the Amethyst app for Android. + +### How Amethyst Works (and Why That Matters Here) + +Amethyst is a decentralized Nostr client. **The app itself does not host, store, or moderate any user-generated content.** All content is hosted by independent third-party servers called **relays** that the user freely chooses to connect to. Amethyst is a viewer and a publisher; it has no central database, no upload servers, and no ability to delete content from the network. Content moderation, takedowns, and legal reporting are the responsibility of the **relay operators** who actually host the content. + +What Amethyst provides — and what these Standards cover — is (1) a clear prohibition of CSAE, (2) in-app tools that let users report content, hide content, and disconnect from abusive relays, and (3) a contact point for escalation. + +### Prohibition of Child Sexual Abuse and Exploitation (CSAE) + +Amethyst strictly prohibits the use of the app to create, upload, share, solicit, or distribute child sexual abuse and exploitation (CSAE) material, including child sexual abuse material (CSAM), in any form, or to groom, exploit, endanger, or otherwise harm minors. Users who use Amethyst for these purposes are in violation of these Standards and of the laws of essentially every jurisdiction. + +### In-App User Feedback and Reporting Mechanism + +Amethyst provides in-app mechanisms for users to flag, hide, and disconnect from harmful content: + +- **Report Post** — use the dropdown menu on any note to report it as illegal content, nudity, impersonation, spam, profanity, or other violations. The report is published as a signed Nostr report event so that relay operators and other clients can act on it. +- **Report Account** — open a user's profile and use the report action to flag the account, with the same publication behavior. +- **Block Post / Block Account** — hide a note or a user locally on your device. +- **Block Relay (NIP-51 Blocked Relay List)** — if a particular relay is hosting CSAE/CSAM or refuses to act on reports, add it to your Blocked Relay List so Amethyst will no longer fetch from or publish to it. This is the strongest tool the app provides: it cuts your client off from servers that won't moderate. +- **Mute Words and Hashtags** — filter out unwanted content from your feeds. + +### Addressing CSAM + +Because Amethyst does not host content, CSAM cannot be removed by Amethyst — it can only be removed by the relay operator who is actually hosting it, and reported to authorities by that operator under the laws that apply to them (in the United States, 18 U.S.C. §2258A makes hosting providers — not viewer applications — the entities required to report to the National Center for Missing & Exploited Children). + +If you become aware of CSAM accessible via Amethyst, please: + +1. **Report the content in-app** (select "Illegal Content") so the report propagates to relays and other clients, and +2. **Block the relay** that is hosting the content using the in-app Blocked Relay List, so your client disconnects from it, and +3. **Report the relay and the material directly to the authorities** who have jurisdiction over the hosting provider — for content reachable from the United States that is the **National Center for Missing & Exploited Children (NCMEC) CyberTipline** at https://report.cybertip.org/. For other jurisdictions see INHOPE members at https://www.inhope.org/. +4. **Optionally email the contact below** with the relay URL and event ID. We cannot remove the content from the relay, but we can amplify the report to other relay operators we know, and where appropriate we will recommend that the offending relay be removed from any default relay list shipped with Amethyst. + +### Compliance with Child Safety Laws + +Amethyst is built and distributed to comply with applicable child safety laws and regulations, including Google Play's Child Safety Standards policy. Where Amethyst itself is subject to a legal obligation (for example, as a distributor on Google Play), we will cooperate with lawful requests from child-safety authorities. Obligations that attach to the **hosting** of content (such as 18 U.S.C. §2258A NCMEC reporting in the U.S.) apply to the relay operators, not to the viewer application. + +### Child Safety Point of Contact + +Questions, reports of relays hosting CSAE/CSAM, or requests related to child safety on Amethyst should be sent to: + +- **Name:** Vitor Pamplona (developer, Amethyst for Android) +- **Email:** amethyst@vitorpamplona.com +- **What we can do:** acknowledge the report, forward it to relay operators we are in contact with, and consider removing the offending relay from any default/suggested relay list shipped with Amethyst. We cannot delete content from third-party relays — that is the relay operator's responsibility. +- **What you should also do:** report directly to NCMEC (https://report.cybertip.org/) or your local INHOPE hotline, who can compel the actual hosting provider to act. + +### Age Rating + +Amethyst is rated 17+. The app does not knowingly collect information from children and has no account-creation flow that targets minors. We rely on Google Play's age-gating to restrict downloads to users 17+. ## Terms of Use diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index 4988276dde..ba17fa04d9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -77,6 +78,7 @@ fun AllSettingsScreen( nav: INav, ) { val context = LocalContext.current + val uriHandler = LocalUriHandler.current val scope = rememberCoroutineScope() var showResetMarmotDialog by remember { mutableStateOf(false) } var isResettingMarmot by remember { mutableStateOf(false) } @@ -261,6 +263,32 @@ fun AllSettingsScreen( ) } + SettingsSection(R.string.about_legal) { + SettingsItem( + title = R.string.privacy_policy, + icon = MaterialSymbols.Lock, + onClick = { + runCatching { + uriHandler.openUri( + "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md", + ) + } + }, + ) + SettingsDivider() + SettingsItem( + title = R.string.child_safety_standards, + icon = MaterialSymbols.Shield, + onClick = { + runCatching { + uriHandler.openUri( + "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md#child-safety-standards", + ) + } + }, + ) + } + SettingsSection(R.string.danger_zone, isDanger = true) { if (hasPrivateKey) { SettingsItem( diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 827d73674a..e9897200d3 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1318,6 +1318,8 @@ Posting policy Privacy Policy Terms & Conditions + Child Safety Standards + About & Legal N/A Errors and Notices from this Relay Relay Monitor Reports From c3e03308f860b36bb97df077a42e3ed35315850c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:15:34 +0000 Subject: [PATCH 2/5] docs: clarify Child Safety Standards are a policy, not a license restriction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F-Droid requires that apps add no restrictions to the FOSS license that ships with the source. The previous wording ("Amethyst strictly prohibits the use of the app to...") could be read as an EULA clause that restricts use beyond what the MIT LICENSE grants. Reframe the prohibition as a published community standard / acceptable- use policy — which is exactly what Google Play's Child Safety Standards policy requires anyway — and add an explicit "Free Software License" note clarifying that the MIT license terms in LICENSE are unchanged, and that F-Droid users and source redistributors retain every right granted by MIT. Google Play's requirements remain satisfied: the prohibition of CSAE is still explicit, the in-app reporting mechanism is documented, the child-safety point of contact and NCMEC escalation path are unchanged, and the app/developer name is still referenced. --- PRIVACY.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index cc4dafdf8d..e70ea00d30 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -35,17 +35,19 @@ Information shared on Nostr should be assumed permanent for privacy purposes. Th ## Child Safety Standards -Amethyst, published on Google Play by Vitor Pamplona, is committed to protecting children from sexual abuse and exploitation. These standards apply to the Amethyst app for Android. +These are the published Child Safety Standards for Amethyst, an Android Nostr client developed by Vitor Pamplona and distributed on Google Play. They are published to satisfy Google Play's Child Safety Standards policy and to set out the developer's public position on child safety. + +These Standards are a **community standard and published policy**. They do **not** modify, supersede, or add restrictions to the software license that governs the Amethyst source code; see the **Free Software License** note at the end of this section. ### How Amethyst Works (and Why That Matters Here) Amethyst is a decentralized Nostr client. **The app itself does not host, store, or moderate any user-generated content.** All content is hosted by independent third-party servers called **relays** that the user freely chooses to connect to. Amethyst is a viewer and a publisher; it has no central database, no upload servers, and no ability to delete content from the network. Content moderation, takedowns, and legal reporting are the responsibility of the **relay operators** who actually host the content. -What Amethyst provides — and what these Standards cover — is (1) a clear prohibition of CSAE, (2) in-app tools that let users report content, hide content, and disconnect from abusive relays, and (3) a contact point for escalation. +What these Standards cover is (1) a clear prohibition of CSAE as a community standard, (2) the in-app tools available to users to report content, hide content, and disconnect from abusive relays, and (3) a contact point for escalation. ### Prohibition of Child Sexual Abuse and Exploitation (CSAE) -Amethyst strictly prohibits the use of the app to create, upload, share, solicit, or distribute child sexual abuse and exploitation (CSAE) material, including child sexual abuse material (CSAM), in any form, or to groom, exploit, endanger, or otherwise harm minors. Users who use Amethyst for these purposes are in violation of these Standards and of the laws of essentially every jurisdiction. +These Standards prohibit using Amethyst to create, upload, share, solicit, or distribute child sexual abuse and exploitation (CSAE) material, including child sexual abuse material (CSAM), in any form, or to groom, exploit, endanger, or otherwise harm minors. Users who use Amethyst for these purposes are in violation of these Standards and of the laws of essentially every jurisdiction. ### In-App User Feedback and Reporting Mechanism @@ -85,6 +87,10 @@ Questions, reports of relays hosting CSAE/CSAM, or requests related to child saf Amethyst is rated 17+. The app does not knowingly collect information from children and has no account-creation flow that targets minors. We rely on Google Play's age-gating to restrict downloads to users 17+. +### Free Software License + +These Child Safety Standards are a public statement of the developer's commitments and the published policy that users of Amethyst on Google Play are expected to follow. They are **not** a restriction added to the source code license. Amethyst's source code is licensed under the MIT License (see the `LICENSE` file in the source repository); these Standards do not modify, supersede, or add conditions to that license. All users — including users of builds distributed by F-Droid, by other repositories, or built from source — retain every right granted by the MIT License, including the freedom to use, study, modify, and redistribute the software. + ## Terms of Use ### For versions downloaded from Google's Play Store From f05866234919d4bbf180e2f657ff0779eab77697 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:24:57 +0000 Subject: [PATCH 3/5] fix(fdroid): hide Play-only ToS gate and Privacy/Child-Safety links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F-Droid distributes Amethyst as MIT-licensed free software with no acceptable-use terms layered on top — only the Play Store build needs a ToS-acceptance checkbox at login/signup and links to the published Child Safety Standards. Gates added behind `BuildConfig.FLAVOR == "play"`: - Settings → "About & Legal" section (Privacy Policy + Child Safety Standards) is now Play-only; F-Droid settings no longer link to PRIVACY.md. - AcceptTerms checkbox in LoginScreen and SignUpScreen is now Play-only. - LoginViewModel.load() / clear() and SignUpViewModel pre-accept `acceptedTerms` on F-Droid so the login/signup button isn't disabled. The Play build is unchanged: first-time login still requires checking the ToS box, and the About & Legal section still surfaces the published Child Safety Standards link required by Google Play. --- .../loggedIn/settings/AllSettingsScreen.kt | 51 ++++++++++--------- .../ui/screen/loggedOff/login/LoginScreen.kt | 3 +- .../screen/loggedOff/login/LoginViewModel.kt | 5 +- .../screen/loggedOff/signup/SignUpScreen.kt | 23 +++++---- .../loggedOff/signup/SignUpViewModel.kt | 3 +- 5 files changed, 47 insertions(+), 38 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index ba17fa04d9..fa8d8b6568 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -263,30 +264,32 @@ fun AllSettingsScreen( ) } - SettingsSection(R.string.about_legal) { - SettingsItem( - title = R.string.privacy_policy, - icon = MaterialSymbols.Lock, - onClick = { - runCatching { - uriHandler.openUri( - "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md", - ) - } - }, - ) - SettingsDivider() - SettingsItem( - title = R.string.child_safety_standards, - icon = MaterialSymbols.Shield, - onClick = { - runCatching { - uriHandler.openUri( - "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md#child-safety-standards", - ) - } - }, - ) + if (BuildConfig.FLAVOR == "play") { + SettingsSection(R.string.about_legal) { + SettingsItem( + title = R.string.privacy_policy, + icon = MaterialSymbols.Lock, + onClick = { + runCatching { + uriHandler.openUri( + "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md", + ) + } + }, + ) + SettingsDivider() + SettingsItem( + title = R.string.child_safety_standards, + icon = MaterialSymbols.Shield, + onClick = { + runCatching { + uriHandler.openUri( + "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md#child-safety-standards", + ) + } + }, + ) + } } SettingsSection(R.string.danger_zone, isDanger = true) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt index 6aef504782..382a29773d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt @@ -66,6 +66,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.Amethyst +import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.hashtags.Amethyst import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons @@ -196,7 +197,7 @@ fun LoginPage( ) } - if (loginViewModel.isFirstLogin) { + if (loginViewModel.isFirstLogin && BuildConfig.FLAVOR == "play") { AcceptTerms( checked = loginViewModel.acceptedTerms, onCheckedChange = loginViewModel::updateAcceptedTerms, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginViewModel.kt index 35d4bfff56..ef7e79f0bf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginViewModel.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel +import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow @@ -68,7 +69,7 @@ class LoginViewModel : ViewModel() { ) { clear() this.isFirstLogin = isFirstLogin - acceptedTerms = !isFirstLogin + acceptedTerms = !isFirstLogin || BuildConfig.FLAVOR != "play" if (newAccountKey != null) { key = TextFieldValue(newAccountKey) } @@ -79,7 +80,7 @@ class LoginViewModel : ViewModel() { password = TextFieldValue("") errorManager.clearErrors() - acceptedTerms = false + acceptedTerms = BuildConfig.FLAVOR != "play" processingLogin = false isTemporary = false offerTemporaryLogin = false diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt index 62616886ce..5e5c4dd017 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.Amethyst +import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.hashtags.Amethyst import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons @@ -187,17 +188,19 @@ fun SignUpPage( }, ) - AcceptTerms( - checked = signUpViewModel.acceptedTerms, - onCheckedChange = signUpViewModel::updateAcceptedTerms, - ) - - if (signUpViewModel.termsAcceptanceIsRequiredError) { - Text( - text = stringRes(R.string.acceptance_of_terms_is_required), - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodySmall, + if (BuildConfig.FLAVOR == "play") { + AcceptTerms( + checked = signUpViewModel.acceptedTerms, + onCheckedChange = signUpViewModel::updateAcceptedTerms, ) + + if (signUpViewModel.termsAcceptanceIsRequiredError) { + Text( + text = stringRes(R.string.acceptance_of_terms_is_required), + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall, + ) + } } Spacer(modifier = Modifier.height(Size10dp)) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpViewModel.kt index 6f87efac7a..206878f39c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpViewModel.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel +import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginErrorManager @@ -40,7 +41,7 @@ class SignUpViewModel : ViewModel() { var displayName by mutableStateOf(TextFieldValue("")) - var acceptedTerms by mutableStateOf(false) + var acceptedTerms by mutableStateOf(BuildConfig.FLAVOR != "play") var termsAcceptanceIsRequiredError by mutableStateOf(false) fun init(accountSessionManager: AccountSessionManager) { From 2628f4578850ab82f6af4cd04813fc0c8d44288e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:45:48 +0000 Subject: [PATCH 4/5] refactor: move ToS / legal links into flavor source sets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Play-Store-only Terms-of-Use checkbox and the "About & Legal" Settings section both contain hardcoded GitHub URLs to the published PRIVACY.md. Previously they were in src/main and gated at call sites by `BuildConfig.FLAVOR == "play"` — which works at runtime but still compiles the GitHub URLs into the F-Droid APK. This commit moves the URL-bearing UI into src/play and adds empty src/fdroid stubs with the same signatures, so the F-Droid build is physically free of the URLs. New composables (same package + signature in both flavor source sets, so callers in src/main link to whichever flavor is being built): - com.vitorpamplona.amethyst.ui.screen.loggedOff.legal.TermsGate * src/play: renders an AcceptTerms checkbox with the PRIVACY.md link and the "acceptance required" error text. * src/fdroid: empty body. - com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.LegalSettingsSection * src/play: renders the SettingsSection with Privacy Policy and Child Safety Standards rows that open the GitHub-hosted PRIVACY.md. * src/fdroid: empty body. Call-site changes: - LoginScreen: drop `BuildConfig.FLAVOR == "play"` guard and call `TermsGate(...)` inside the existing `isFirstLogin` block. The flavor source set picks the right body. - SignUpScreen: drop `BuildConfig.FLAVOR == "play"` guard, call `TermsGate(...)` unconditionally. - AllSettingsScreen: drop `BuildConfig.FLAVOR == "play"` guard and the inline About & Legal SettingsSection, call `LegalSettingsSection()` instead. LocalUriHandler and BuildConfig imports removed. The old `src/main/.../loggedOff/AcceptTerms.kt` is deleted; its body moves into the play-flavor TermsGate as a file-private helper. The LoginViewModel / SignUpViewModel still use `BuildConfig.FLAVOR` to decide the initial `acceptedTerms` value (so the login/signup button isn't disabled on F-Droid). That check is logic only, contains no URLs, and is safe for F-Droid distribution. Verified: `grep -r "github.com/vitorpamplona/amethyst/blob"` against src/main + src/fdroid returns zero matches. Both :amethyst:compileFdroidDebugKotlin and :amethyst:compilePlayDebugKotlin compile cleanly. --- .../loggedIn/settings/LegalSettingsSection.kt | 29 ++++++++++ .../ui/screen/loggedOff/legal/TermsGate.kt | 35 ++++++++++++ .../loggedIn/settings/AllSettingsScreen.kt | 31 +--------- .../ui/screen/loggedOff/login/LoginScreen.kt | 16 ++---- .../screen/loggedOff/signup/SignUpScreen.kt | 22 ++----- .../loggedIn/settings/LegalSettingsSection.kt | 57 +++++++++++++++++++ .../ui/screen/loggedOff/legal/TermsGate.kt} | 23 +++++++- 7 files changed, 152 insertions(+), 61 deletions(-) create mode 100644 amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt create mode 100644 amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt create mode 100644 amethyst/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt rename amethyst/src/{main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AcceptTerms.kt => play/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt} (80%) diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt new file mode 100644 index 0000000000..65bf2ce996 --- /dev/null +++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings + +import androidx.compose.runtime.Composable + +// F-Droid distributes Amethyst as MIT-licensed free software; the build must +// not surface links to external (e.g. GitHub-hosted) policy documents. +@Composable +fun LegalSettingsSection() { +} diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt new file mode 100644 index 0000000000..736b2dc0d5 --- /dev/null +++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedOff.legal + +import androidx.compose.runtime.Composable + +// F-Droid distributes Amethyst as MIT-licensed free software; there is no +// terms-of-use acceptance layered on top of the source license, and the build +// must not link to any external (e.g. GitHub) policy document. +@Composable +@Suppress("UNUSED_PARAMETER") +fun TermsGate( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + showError: Boolean, +) { +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index fa8d8b6568..7e4a5fc789 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -42,11 +42,9 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -79,7 +77,6 @@ fun AllSettingsScreen( nav: INav, ) { val context = LocalContext.current - val uriHandler = LocalUriHandler.current val scope = rememberCoroutineScope() var showResetMarmotDialog by remember { mutableStateOf(false) } var isResettingMarmot by remember { mutableStateOf(false) } @@ -264,33 +261,7 @@ fun AllSettingsScreen( ) } - if (BuildConfig.FLAVOR == "play") { - SettingsSection(R.string.about_legal) { - SettingsItem( - title = R.string.privacy_policy, - icon = MaterialSymbols.Lock, - onClick = { - runCatching { - uriHandler.openUri( - "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md", - ) - } - }, - ) - SettingsDivider() - SettingsItem( - title = R.string.child_safety_standards, - icon = MaterialSymbols.Shield, - onClick = { - runCatching { - uriHandler.openUri( - "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md#child-safety-standards", - ) - } - }, - ) - } - } + LegalSettingsSection() SettingsSection(R.string.danger_zone, isDanger = true) { if (hasPrivateKey) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt index 382a29773d..eef969c214 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt @@ -66,15 +66,14 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.hashtags.Amethyst import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager -import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup +import com.vitorpamplona.amethyst.ui.screen.loggedOff.legal.TermsGate import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size20dp @@ -197,19 +196,12 @@ fun LoginPage( ) } - if (loginViewModel.isFirstLogin && BuildConfig.FLAVOR == "play") { - AcceptTerms( + if (loginViewModel.isFirstLogin) { + TermsGate( checked = loginViewModel.acceptedTerms, onCheckedChange = loginViewModel::updateAcceptedTerms, + showError = loginViewModel.termsAcceptanceIsRequiredError, ) - - if (loginViewModel.termsAcceptanceIsRequiredError) { - Text( - text = stringRes(R.string.acceptance_of_terms_is_required), - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodySmall, - ) - } } Spacer(modifier = Modifier.height(Size10dp)) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt index 5e5c4dd017..72797b6abe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/signup/SignUpScreen.kt @@ -50,13 +50,12 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.hashtags.Amethyst import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager -import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup +import com.vitorpamplona.amethyst.ui.screen.loggedOff.legal.TermsGate import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginErrorManager import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size10dp @@ -188,20 +187,11 @@ fun SignUpPage( }, ) - if (BuildConfig.FLAVOR == "play") { - AcceptTerms( - checked = signUpViewModel.acceptedTerms, - onCheckedChange = signUpViewModel::updateAcceptedTerms, - ) - - if (signUpViewModel.termsAcceptanceIsRequiredError) { - Text( - text = stringRes(R.string.acceptance_of_terms_is_required), - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.bodySmall, - ) - } - } + TermsGate( + checked = signUpViewModel.acceptedTerms, + onCheckedChange = signUpViewModel::updateAcceptedTerms, + showError = signUpViewModel.termsAcceptanceIsRequiredError, + ) Spacer(modifier = Modifier.height(Size10dp)) diff --git a/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt b/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt new file mode 100644 index 0000000000..0f17e6ca28 --- /dev/null +++ b/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/LegalSettingsSection.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalUriHandler +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols + +@Composable +fun LegalSettingsSection() { + val uriHandler = LocalUriHandler.current + + SettingsSection(R.string.about_legal) { + SettingsItem( + title = R.string.privacy_policy, + icon = MaterialSymbols.Lock, + onClick = { + runCatching { + uriHandler.openUri( + "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md", + ) + } + }, + ) + SettingsDivider() + SettingsItem( + title = R.string.child_safety_standards, + icon = MaterialSymbols.Shield, + onClick = { + runCatching { + uriHandler.openUri( + "https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md#child-safety-standards", + ) + } + }, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AcceptTerms.kt b/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt similarity index 80% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AcceptTerms.kt rename to amethyst/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt index 12cccee79c..fc6edc8f72 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AcceptTerms.kt +++ b/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/legal/TermsGate.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedOff +package com.vitorpamplona.amethyst.ui.screen.loggedOff.legal import androidx.compose.foundation.layout.Row import androidx.compose.material3.Checkbox @@ -33,9 +33,26 @@ import com.vitorpamplona.amethyst.ui.components.appendLink import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun AcceptTerms( +fun TermsGate( checked: Boolean, - onCheckedChange: ((Boolean) -> Unit)?, + onCheckedChange: (Boolean) -> Unit, + showError: Boolean, +) { + AcceptTerms(checked = checked, onCheckedChange = onCheckedChange) + + if (showError) { + Text( + text = stringRes(R.string.acceptance_of_terms_is_required), + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall, + ) + } +} + +@Composable +private fun AcceptTerms( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, ) { Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( From 6f8f5f7d5738ac4bb8dd757a1229e25acce0f543 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 16:50:45 +0000 Subject: [PATCH 5/5] =?UTF-8?q?docs:=20tighten=20PRIVACY.md=20=E2=80=94=20?= =?UTF-8?q?concise,=20truthful,=20lower-liability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrites the policy/terms doc with three goals: 1) **Concise & easier to read.** Plain English, short sentences, removed redundant intros (the "How Amethyst Works (and Why That Matters Here)" block restated the Privacy intro), merged the "Visibility" + "Permanence" sections into one paragraph, and collapsed the Child Safety POC section into a single contact block near the top of the document. 2) **More truthful.** Two corrections: - F-Droid build uses UnifiedPush for notifications, not FCM. The previous text only mentioned Google Firebase Cloud Messaging, which was inaccurate for the F-Droid distribution. - Replaced "We rely on Google Play's age verification to make sure the user downloading the app is an adult" with "Amethyst's Google Play listing is rated 17+. The app does not request or store age information." Google Play does not actually verify user age, so the old wording overstated the protection. 3) **Lower liability.** Several specific changes: - Dropped the "We aim to acknowledge child-safety reports within 72 hours" service-level commitment that the solo developer cannot reliably meet. - Softened "we will recommend that the offending relay be removed" and "What we can do: acknowledge the report, forward..." to discretionary "may forward" / "may stop recommending" phrasing. - Removed the absolute "data is strictly confidential and cannot be accessed by other apps" guarantee. Replaced with the narrower, verifiable claim that other apps cannot read app-local storage on a standard, non-rooted Android device. - Narrowed "Amethyst is built and distributed to comply with applicable child safety laws and regulations" to "Amethyst is distributed under Google Play's Child Safety Standards policy and applicable law" — same in spirit, smaller surface for dispute. Content that the Google Play Child Safety Standards checklist requires is unchanged: explicit CSAE prohibition, child-safety point of contact (amethyst@vitorpamplona.com), in-app feedback mechanism (Report Post / Report Account / Block Post / Block Account / Block Relay / Mute), method for addressing CSAM (in-app report → block relay → NCMEC/INHOPE → optional developer notice), compliance statement, and references to the app name "Amethyst" and the Google Play publisher "Vitor Pamplona". The F-Droid carve-out also remains: the MIT License in LICENSE is identified as the only instrument governing source-built distributions, with no additional terms. --- PRIVACY.md | 134 ++++++++++++++++++++++++++++------------------------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index e70ea00d30..32208aab91 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,108 +1,114 @@ # Amethyst Privacy Policy and Terms of Use -## Privacy Policy +**App:** Amethyst (Android Nostr client) +**Publisher:** Vitor Pamplona +**Contact:** amethyst@vitorpamplona.com +**Last updated:** 2026-05-24 -Effective as of Jun 12, 2023 +Amethyst is free, open-source software (MIT License — see `LICENSE`). It is not a service. There is no Amethyst server, no Amethyst account, and the developer has no access to data stored on your device. -The Amethyst app for Android does not collect or process any personal information from its users. +Amethyst connects to third-party Nostr **relays** that you choose. Those relays host the content. They are independent of Amethyst, with their own operators and their own policies. -The app is used to browse third-party Nostr servers (called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open-source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them. +This document explains what data leaves your phone, who can see it, and the standards that apply to use of the app. -The app may collect a per-device token, your public key, and a preferred Relay to connect to and provide push notification services through Google's Firebase Cloud Messaging. Other than that, the data from connected accounts is only stored locally on the device when it's required for the functionality and performance of Amethyst. This data is strictly confidential and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app. +## Privacy -Amethyst offers several options for uploading pictures and videos to post online. You can choose the server at your discretion. Similar to relays, such services are independent of the app and have their own privacy policy and terms of use. +### Data sent off-device -### Privacy with Relay services +Using the app causes the following data to leave your phone: -Your Internet Protocol (IP) address is exposed to the relays you connect to. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g., a VPN) from trackers online. +- **Nostr events** you publish, sent to the relays you have configured. +- **Subscriptions** (filters describing what you want to read), sent to those relays. +- **Media uploads** (images, audio, video), sent to the media server you select. +- *(Google Play build, push notifications enabled)* a per-device push token, your public key, and a preferred relay, registered with Google Firebase Cloud Messaging so a notification proxy can wake the app. +- *(F-Droid build, push notifications enabled)* a per-device token registered with whichever UnifiedPush distributor you install (e.g. ntfy). -The relay can also see which public keys you are using and what information you are requesting from the network. Your public key is tied to your IP address and your relay filters. +The developer does not run any server that aggregates or stores this data. -Relays have all your data in raw text. They know your IP, your name, your location (guessed from IP), your pub key, all your contacts, and other relays, and can read every action you do (post, like, boost, quote, report, etc) with the exception of the content inside Private Zaps and Private DMs. +### Data stored on your device -While the content of direct messages (DMs) is only visible to you and your DM Nostr counterparty, everyone can see when you and your counterparty are DM-ing each other. Image uploads in the DM screen use one of the chosen image servers and simply paste the image link into the DM text. Your uploaded pictures are available to anyone with that direct link. +Configuration, cached events, keys, drafts, and other operational data live in the app's local storage. Other apps cannot read it on a standard, non-rooted Android device. You can wipe it by clearing the app's storage or uninstalling. -### Visibility & Permanence of Your Content on Nostr Relays +### What relays can see -#### Information Visibility +A relay you connect to sees: -Content that you share can be shared with other relays by any user of the network. -The information you share is publicly visible to anyone reading from relays that have access to your information. Your information may also be visible to Nostr users who do not share relays with you. +- Your IP address. +- Your public key. +- The events you publish (posts, reactions, reposts, reports, etc.). +- The filters you subscribe to. -#### Information Permanence +A relay does **not** see the plaintext of: -Information shared on Nostr should be assumed permanent for privacy purposes. There is no way to guarantee deleting or editing any content once posted. +- Private Direct Messages (encrypted to the recipient under NIP-17 / NIP-44). +- Private Zaps. + +A relay can still see *that* you and another user are exchanging DMs even though it cannot read them. To reduce what a relay can correlate to you, route the app over a VPN or Tor. + +### Media uploads + +Uploads go to the media server you select. That server is independent of Amethyst and has its own policy. Anyone holding the resulting link — including media attached to a DM — can fetch the file. + +### Public content is effectively permanent + +Anything you publish to a relay can be copied to other relays or clients. Once published, you should assume it cannot be reliably deleted from the network. ## Child Safety Standards -These are the published Child Safety Standards for Amethyst, an Android Nostr client developed by Vitor Pamplona and distributed on Google Play. They are published to satisfy Google Play's Child Safety Standards policy and to set out the developer's public position on child safety. +These are the published Child Safety Standards for **Amethyst**, the Android Nostr client published on Google Play by **Vitor Pamplona**. They are published under Google Play's Child Safety Standards policy. -These Standards are a **community standard and published policy**. They do **not** modify, supersede, or add restrictions to the software license that governs the Amethyst source code; see the **Free Software License** note at the end of this section. +They are a community standard, not a license restriction. Amethyst's source code remains licensed under the MIT License in `LICENSE`. -### How Amethyst Works (and Why That Matters Here) +### Prohibition -Amethyst is a decentralized Nostr client. **The app itself does not host, store, or moderate any user-generated content.** All content is hosted by independent third-party servers called **relays** that the user freely chooses to connect to. Amethyst is a viewer and a publisher; it has no central database, no upload servers, and no ability to delete content from the network. Content moderation, takedowns, and legal reporting are the responsibility of the **relay operators** who actually host the content. +Using Amethyst to create, upload, share, solicit, or distribute child sexual abuse and exploitation (CSAE) material — including child sexual abuse material (CSAM) — or to groom, exploit, or harm a minor is prohibited and is illegal in essentially every jurisdiction. -What these Standards cover is (1) a clear prohibition of CSAE as a community standard, (2) the in-app tools available to users to report content, hide content, and disconnect from abusive relays, and (3) a contact point for escalation. +### In-app tools -### Prohibition of Child Sexual Abuse and Exploitation (CSAE) +Amethyst provides: -These Standards prohibit using Amethyst to create, upload, share, solicit, or distribute child sexual abuse and exploitation (CSAE) material, including child sexual abuse material (CSAM), in any form, or to groom, exploit, endanger, or otherwise harm minors. Users who use Amethyst for these purposes are in violation of these Standards and of the laws of essentially every jurisdiction. - -### In-App User Feedback and Reporting Mechanism - -Amethyst provides in-app mechanisms for users to flag, hide, and disconnect from harmful content: - -- **Report Post** — use the dropdown menu on any note to report it as illegal content, nudity, impersonation, spam, profanity, or other violations. The report is published as a signed Nostr report event so that relay operators and other clients can act on it. -- **Report Account** — open a user's profile and use the report action to flag the account, with the same publication behavior. -- **Block Post / Block Account** — hide a note or a user locally on your device. -- **Block Relay (NIP-51 Blocked Relay List)** — if a particular relay is hosting CSAE/CSAM or refuses to act on reports, add it to your Blocked Relay List so Amethyst will no longer fetch from or publish to it. This is the strongest tool the app provides: it cuts your client off from servers that won't moderate. -- **Mute Words and Hashtags** — filter out unwanted content from your feeds. +- **Report Post** and **Report Account** — publish a signed Nostr report (including the "Illegal Content" reason) so relays and other clients can act on it. +- **Block Post** / **Block Account** — hide content locally on your device. +- **Block Relay** — add a relay to your NIP-51 Blocked Relay List so the app stops fetching from or publishing to it. This is the strongest tool the app offers against a relay that refuses to moderate. +- **Mute Words / Hashtags** — filter unwanted content from your feeds. ### Addressing CSAM -Because Amethyst does not host content, CSAM cannot be removed by Amethyst — it can only be removed by the relay operator who is actually hosting it, and reported to authorities by that operator under the laws that apply to them (in the United States, 18 U.S.C. §2258A makes hosting providers — not viewer applications — the entities required to report to the National Center for Missing & Exploited Children). +Amethyst does not host content, so the app cannot remove CSAM. Only the relay hosting the content can remove it. In the United States, 18 U.S.C. §2258A makes hosting providers — not viewer applications — the entities required to report to the National Center for Missing & Exploited Children (NCMEC). -If you become aware of CSAM accessible via Amethyst, please: +If you encounter CSAM through Amethyst: -1. **Report the content in-app** (select "Illegal Content") so the report propagates to relays and other clients, and -2. **Block the relay** that is hosting the content using the in-app Blocked Relay List, so your client disconnects from it, and -3. **Report the relay and the material directly to the authorities** who have jurisdiction over the hosting provider — for content reachable from the United States that is the **National Center for Missing & Exploited Children (NCMEC) CyberTipline** at https://report.cybertip.org/. For other jurisdictions see INHOPE members at https://www.inhope.org/. -4. **Optionally email the contact below** with the relay URL and event ID. We cannot remove the content from the relay, but we can amplify the report to other relay operators we know, and where appropriate we will recommend that the offending relay be removed from any default relay list shipped with Amethyst. +1. Report the content in-app and select "Illegal Content." +2. Add the hosting relay to your Blocked Relay List. +3. Report directly to **NCMEC** at https://report.cybertip.org/ (United States) or to an **INHOPE** hotline at https://www.inhope.org/ (other jurisdictions). These bodies can compel the hosting provider to act. +4. You may also email **amethyst@vitorpamplona.com** with the relay URL and event ID. The developer cannot remove content from third-party relays, but may forward the report to relay operators it is in contact with and may stop recommending the offending relay in any list shipped with the app. -### Compliance with Child Safety Laws +### Compliance -Amethyst is built and distributed to comply with applicable child safety laws and regulations, including Google Play's Child Safety Standards policy. Where Amethyst itself is subject to a legal obligation (for example, as a distributor on Google Play), we will cooperate with lawful requests from child-safety authorities. Obligations that attach to the **hosting** of content (such as 18 U.S.C. §2258A NCMEC reporting in the U.S.) apply to the relay operators, not to the viewer application. +Amethyst is distributed under Google Play's Child Safety Standards policy and applicable law. Obligations attached to the **hosting** of content rest with relay operators. -### Child Safety Point of Contact +### Age rating -Questions, reports of relays hosting CSAE/CSAM, or requests related to child safety on Amethyst should be sent to: - -- **Name:** Vitor Pamplona (developer, Amethyst for Android) -- **Email:** amethyst@vitorpamplona.com -- **What we can do:** acknowledge the report, forward it to relay operators we are in contact with, and consider removing the offending relay from any default/suggested relay list shipped with Amethyst. We cannot delete content from third-party relays — that is the relay operator's responsibility. -- **What you should also do:** report directly to NCMEC (https://report.cybertip.org/) or your local INHOPE hotline, who can compel the actual hosting provider to act. - -### Age Rating - -Amethyst is rated 17+. The app does not knowingly collect information from children and has no account-creation flow that targets minors. We rely on Google Play's age-gating to restrict downloads to users 17+. - -### Free Software License - -These Child Safety Standards are a public statement of the developer's commitments and the published policy that users of Amethyst on Google Play are expected to follow. They are **not** a restriction added to the source code license. Amethyst's source code is licensed under the MIT License (see the `LICENSE` file in the source repository); these Standards do not modify, supersede, or add conditions to that license. All users — including users of builds distributed by F-Droid, by other repositories, or built from source — retain every right granted by the MIT License, including the freedom to use, study, modify, and redistribute the software. +Amethyst's Google Play listing is rated 17+. The app does not request or store age information. ## Terms of Use -### For versions downloaded from Google's Play Store +### Google Play build -You cannot use the Amethyst app for Android to submit Objectionable Content to relays. Objectionable Content includes but is not limited to: (i) sexually explicit materials; (ii) obscene, defamatory, libelous, slanderous, violent and/or unlawful content or profanity; (iii) content that infringes upon the rights of any third party, including copyright, trademark, privacy, publicity or other personal or proprietary rights, or that is deceptive or fraudulent; (iv) content that promotes the use or sale of illegal or regulated substances, tobacco products, ammunition and/or firearms; and (v) illegal content related to gambling. +You agree not to use the Google Play build of Amethyst to submit Objectionable Content to relays. Objectionable Content includes: -### For versions downloaded from F-Droid +- Sexually explicit material. +- Obscene, defamatory, libelous, slanderous, violent, or unlawful content. +- Content that infringes third-party rights (copyright, trademark, privacy, publicity). +- Content that is deceptive or fraudulent. +- Content promoting illegal drugs, tobacco, firearms, ammunition, or illegal gambling. -We do not control the distribution of the application in F-Droid. Legal matters should be resolved between the user and F-Droid. +These Terms apply only to the Google Play distribution of Amethyst. -## Other Notes +### F-Droid and other source-built distributions -We reserve the right to modify this Privacy Policy and Terms of Use at any time. Any modifications to this document will be effective upon our posting of the new terms and/or upon implementation of the new changes on the Service (or as otherwise indicated at the time of posting). In all cases, your continued use of the app after the posting of any modified Privacy Policy and Terms of Use indicates your acceptance of the terms of the modified Privacy Policy and/or Terms of Use. +The MIT License in `LICENSE` is the only instrument governing your right to use, study, modify, and redistribute the software. No additional terms are imposed on these builds. Any dispute over distribution through F-Droid is between you and F-Droid. -If you have any questions about Amethyst or this privacy policy, you can send a message to amethyst@vitorpamplona.com +## Updates + +This document may change. The current version is published at https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md.