Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ rtk telemetry forget # Withdraw consent + delete all local data + request se
**Override via environment:**
```bash
export RTK_TELEMETRY_DISABLED=1 # Blocks telemetry regardless of consent
export DO_NOT_TRACK=true # Standard opt-out convention; 1 also works
```

## Star History
Expand Down
4 changes: 3 additions & 1 deletion docs/TELEMETRY.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ rtk telemetry forget # Withdraw consent + delete local data + request server
Environment variable override (blocks telemetry regardless of consent):
```bash
export RTK_TELEMETRY_DISABLED=1
# Standard "do not track" convention is also respected:
export DO_NOT_TRACK=1 # accepts 1 or true (case-insensitive)
```

## Retention Policy
Expand All @@ -147,7 +149,7 @@ Under the EU General Data Protection Regulation, you have the right to:
- **Erasure** (Art. 17): run `rtk telemetry forget` to delete local data and send an erasure request to the server. Alternatively, email contact@rtk-ai.app with your device hash.
- **Restriction of processing**: `rtk telemetry disable` stops all data collection immediately.
- **Portability**: the local SQLite database at `~/.local/share/rtk/tracking.db` contains all locally stored data.
- **Objection**: `rtk telemetry disable` or `export RTK_TELEMETRY_DISABLED=1`.
- **Objection**: `rtk telemetry disable`, `export RTK_TELEMETRY_DISABLED=1`, or `export DO_NOT_TRACK=true`.

## Erasure Procedure

Expand Down
1 change: 1 addition & 0 deletions docs/guide/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ For full details on what is collected, opt-out options, and GDPR rights, see [Te
| `RTK_DISABLED=1` | Disable RTK for a single command (`RTK_DISABLED=1 git status`) |
| `RTK_TEE_DIR` | Override the tee directory |
| `RTK_TELEMETRY_DISABLED=1` | Disable telemetry |
| `DO_NOT_TRACK=true` | Disable telemetry using the standard opt-out convention (`1` also works) |
| `RTK_HOOK_AUDIT=1` | Enable hook audit logging |
| `SKIP_ENV_VALIDATION=1` | Skip env validation (useful with Next.js) |

Expand Down
4 changes: 3 additions & 1 deletion docs/guide/resources/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ rtk telemetry forget # Withdraw consent + delete local data + request server
Environment variable override (blocks telemetry regardless of consent):
```bash
export RTK_TELEMETRY_DISABLED=1
# Standard "do not track" convention is also respected:
export DO_NOT_TRACK=1 # accepts 1 or true (case-insensitive)
```

## Retention Policy
Expand All @@ -152,7 +154,7 @@ Under the EU General Data Protection Regulation, you have the right to:
- **Erasure** (Art. 17): run `rtk telemetry forget` to delete local data and send an erasure request to the server. Alternatively, email contact@rtk-ai.app with your device hash.
- **Restriction of processing**: `rtk telemetry disable` stops all data collection immediately.
- **Portability**: the local SQLite database at `~/.local/share/rtk/history.db` contains all locally stored data.
- **Objection**: `rtk telemetry disable` or `export RTK_TELEMETRY_DISABLED=1`.
- **Objection**: `rtk telemetry disable`, `export RTK_TELEMETRY_DISABLED=1`, or `export DO_NOT_TRACK=true`.

## Erasure Procedure

Expand Down
2 changes: 2 additions & 0 deletions docs/usage/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,7 @@ exclude_commands = [] # Commandes a exclure de la recriture automatique
|----------|-------------|
| `RTK_TEE_DIR` | Surcharge le repertoire tee |
| `RTK_TELEMETRY_DISABLED=1` | Desactiver la telemetrie |
| `DO_NOT_TRACK=true` | Desactiver la telemetrie via la convention standard (`1` fonctionne aussi) |
| `RTK_HOOK_AUDIT=1` | Activer l'audit du hook |
| `SKIP_ENV_VALIDATION=1` | Desactiver la validation d'env (Next.js, etc.) |

Expand Down Expand Up @@ -1392,6 +1393,7 @@ rtk telemetry forget # Retirer + supprimer donnees locales + demande d'effac
**Desactiver via variable d'environnement :**
```bash
export RTK_TELEMETRY_DISABLED=1
export DO_NOT_TRACK=true # ou DO_NOT_TRACK=1
```

Aucune donnee personnelle, aucun contenu de commande, aucun chemin de fichier n'est transmis. Conservation serveur : 12 mois max. Details : [docs/TELEMETRY.md](../TELEMETRY.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/TRACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ let _ = conn.execute(
## Security & Privacy

- **Local storage only**: Tracking database never leaves the machine
- **Telemetry requires consent**: RTK can send a daily anonymous usage ping (version, OS, command counts, token savings). Disabled by default, requires explicit consent via `rtk init` or `rtk telemetry enable`. Manage with `rtk telemetry status/disable/forget`. Override: `RTK_TELEMETRY_DISABLED=1`
- **Telemetry requires consent**: RTK can send a daily anonymous usage ping (version, OS, command counts, token savings). Disabled by default, requires explicit consent via `rtk init` or `rtk telemetry enable`. Manage with `rtk telemetry status/disable/forget`. Override: `RTK_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=true`/`1`
- **User control**: Users can delete `~/.local/share/rtk/tracking.db` anytime
- **90-day retention**: Old data automatically purged

Expand Down
93 changes: 92 additions & 1 deletion src/core/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,48 @@ static CACHED_SALT: OnceLock<String> = OnceLock::new();
const TELEMETRY_URL: Option<&str> = option_env!("RTK_TELEMETRY_URL");
const TELEMETRY_TOKEN: Option<&str> = option_env!("RTK_TELEMETRY_TOKEN");
const PING_INTERVAL_SECS: u64 = 23 * 3600; // 23 hours
const RTK_TELEMETRY_DISABLED_ENV: &str = "RTK_TELEMETRY_DISABLED";
const DO_NOT_TRACK_ENV: &str = "DO_NOT_TRACK";

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TelemetryEnvOverride {
RtkTelemetryDisabled,
DoNotTrack,
}

impl TelemetryEnvOverride {
pub(crate) fn status_message(self) -> &'static str {
match self {
Self::RtkTelemetryDisabled => "RTK_TELEMETRY_DISABLED (blocked)",
Self::DoNotTrack => "DO_NOT_TRACK (blocked)",
}
}
}

pub(crate) fn telemetry_env_override() -> Option<TelemetryEnvOverride> {
telemetry_env_override_from_values(
std::env::var(RTK_TELEMETRY_DISABLED_ENV).ok().as_deref(),
std::env::var(DO_NOT_TRACK_ENV).ok().as_deref(),
)
}

fn telemetry_env_override_from_values(
rtk_disabled: Option<&str>,
do_not_track: Option<&str>,
) -> Option<TelemetryEnvOverride> {
if rtk_disabled == Some("1") {
return Some(TelemetryEnvOverride::RtkTelemetryDisabled);
}
if do_not_track.is_some_and(is_truthy_env) {
return Some(TelemetryEnvOverride::DoNotTrack);
}
None
}

fn is_truthy_env(value: &str) -> bool {
let v = value.trim();
v == "1" || v.eq_ignore_ascii_case("true")
}

/// Send a telemetry ping if enabled and not already sent today.
/// Fire-and-forget: errors are silently ignored.
Expand All @@ -24,7 +66,7 @@ pub fn maybe_ping() {
}

// Check opt-out: env var
if std::env::var("RTK_TELEMETRY_DISABLED").unwrap_or_default() == "1" {
if telemetry_env_override().is_some() {
return;
}

Expand Down Expand Up @@ -492,6 +534,55 @@ mod tests {
assert!(path.to_string_lossy().contains("rtk"));
}

#[test]
fn test_telemetry_env_override_baseline() {
assert_eq!(telemetry_env_override_from_values(None, None), None);
assert_eq!(telemetry_env_override_from_values(Some(""), Some("")), None);
}

#[test]
fn test_telemetry_env_override_honors_rtk_specific_opt_out() {
assert_eq!(
telemetry_env_override_from_values(Some("1"), None),
Some(TelemetryEnvOverride::RtkTelemetryDisabled)
);

for value in ["0", "true", "TRUE", "false", "yes", "no", "", " 1 "] {
assert_eq!(
telemetry_env_override_from_values(Some(value), None),
None,
"RTK_TELEMETRY_DISABLED={value:?} should not block telemetry"
);
}
}

#[test]
fn test_telemetry_env_override_honors_do_not_track() {
for value in ["true", "TRUE", "TrUe", "1", " true "] {
assert_eq!(
telemetry_env_override_from_values(None, Some(value)),
Some(TelemetryEnvOverride::DoNotTrack),
"DO_NOT_TRACK={value:?} should block telemetry"
);
}

for value in ["false", "0", "yes", "no", ""] {
assert_eq!(
telemetry_env_override_from_values(None, Some(value)),
None,
"DO_NOT_TRACK={value:?} should not block telemetry"
);
}
}

#[test]
fn test_telemetry_env_override_prefers_rtk_specific_opt_out() {
assert_eq!(
telemetry_env_override_from_values(Some("1"), Some("true")),
Some(TelemetryEnvOverride::RtkTelemetryDisabled)
);
}

#[test]
fn test_install_method_unix_paths() {
assert_eq!(
Expand Down
6 changes: 3 additions & 3 deletions src/core/telemetry_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ fn run_status() -> Result<()> {
"no"
};

let env_override = std::env::var("RTK_TELEMETRY_DISABLED").unwrap_or_default() == "1";
let env_override = super::telemetry::telemetry_env_override();

println!("Telemetry status:");
println!(" consent: {}", consent_str);
if let Some(date) = &config.telemetry.consent_date {
println!(" consent date: {}", date);
}
println!(" enabled: {}", enabled_str);
if env_override {
println!(" env override: RTK_TELEMETRY_DISABLED=1 (blocked)");
if let Some(env_override) = env_override {
println!(" env override: {}", env_override.status_message());
}

let salt_path = super::telemetry::salt_file_path();
Expand Down