Skip to content

Commit fe31316

Browse files
committed
Redact PII from production log statements
- Also committing task list update after all the fixes
1 parent 8ee2dca commit fe31316

5 files changed

Lines changed: 41 additions & 21 deletions

File tree

apps/desktop/src-tauri/src/licensing/app_status.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! - Caching for offline use (30-day grace period)
77
//! - Mock mode for local testing
88
9+
use crate::licensing::redact_email;
910
use crate::licensing::verification::{LicenseInfo, get_license_info};
1011
use serde::{Deserialize, Serialize};
1112
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -267,7 +268,7 @@ fn get_cached_or_validate(app: &tauri::AppHandle, license_info: &LicenseInfo) ->
267268
// We'll return Personal until async validation completes
268269
log::info!(
269270
"License key found for {} but no cached status, returning Personal until validation",
270-
license_info.email
271+
redact_email(&license_info.email)
271272
);
272273
AppStatus::Personal {
273274
show_commercial_reminder: should_show_commercial_reminder(app),

apps/desktop/src-tauri/src/licensing/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ pub use verification::{LicenseInfo, activate_license, activate_license_async, ge
1515

1616
use serde::{Deserialize, Serialize};
1717

18+
/// Redact an email for logging: "john@example.com" -> "j***@example.com"
19+
pub(crate) fn redact_email(email: &str) -> String {
20+
match email.find('@') {
21+
Some(0) | None => "***".to_string(),
22+
Some(at) => format!("{}***{}", &email[..1], &email[at..]),
23+
}
24+
}
25+
1826
/// License data encoded in the license key.
1927
#[derive(Debug, Clone, Serialize, Deserialize)]
2028
pub struct LicenseData {

apps/desktop/src-tauri/src/licensing/verification.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! License key verification using Ed25519 signatures.
22
33
use crate::licensing::LicenseData;
4+
use crate::licensing::redact_email;
45
use crate::licensing::validation_client::{activate_short_code, is_short_code};
56
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
67
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
@@ -137,15 +138,11 @@ fn validate_license_key_with_public_key(license_key: &str, public_key_hex: &str)
137138

138139
// Parse payload
139140
let data: LicenseData = serde_json::from_slice(&payload_bytes).map_err(|e| {
140-
log::info!(
141-
"License payload parse error: {}. Raw payload: {}",
142-
e,
143-
String::from_utf8_lossy(&payload_bytes)
144-
);
141+
log::info!("License payload parse error: {}", e);
145142
"Invalid license key: bad payload data"
146143
})?;
147144

148-
log::info!("License validated successfully for: {}", data.email);
145+
log::info!("License validated successfully for: {}", redact_email(&data.email));
149146

150147
Ok(data)
151148
}

apps/license-server/src/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ function isValidLicenseType(type: string): type is LicenseType {
6262
return (licenseTypes as readonly string[]).includes(type)
6363
}
6464

65+
/** Redact an email for logging: "john@example.com" -> "j***@example.com" */
66+
function redactEmail(email: string): string {
67+
const atIndex = email.indexOf('@')
68+
if (atIndex <= 0) return '***'
69+
return email[0] + '***' + email.slice(atIndex)
70+
}
71+
6572
const app = new Hono<{ Bindings: Bindings }>()
6673

6774
// Health check
@@ -237,7 +244,7 @@ async function processCompletedTransaction(payload: PaddleWebhookPayload, env: B
237244
return Response.json({ error: 'Failed to fetch customer details' }, { status: 500 })
238245
}
239246

240-
console.log('Customer email:', customer.email, 'business:', customer.businessName)
247+
console.log('Customer:', redactEmail(customer.email))
241248

242249
// Determine license type from price ID
243250
const priceIds: PriceIdMapping = {
@@ -274,7 +281,14 @@ async function processCompletedTransaction(payload: PaddleWebhookPayload, env: B
274281
const sevenDaysInSeconds = 604_800
275282
await env.LICENSE_CODES.put(idempotencyKey, 'processed', { expirationTtl: sevenDaysInSeconds })
276283

277-
console.log('Licenses sent to:', customer.email, 'type:', result.licenseType, 'quantity:', result.quantity)
284+
console.log(
285+
'Licenses sent to:',
286+
redactEmail(customer.email),
287+
'type:',
288+
result.licenseType,
289+
'quantity:',
290+
result.quantity,
291+
)
278292
return Response.json({
279293
status: 'ok',
280294
email: customer.email,

docs/notes/2026-02-09-codebase-review.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -158,24 +158,24 @@ locations. The staging pattern, atomic rename failure handling, and rollback-on-
158158

159159
### Immediate (critical, affects real money)
160160

161-
- [ ] Add webhook idempotency — check KV for existing transaction before processing (#1) `[S]`
162-
- [ ] Wrap webhook handler in try-catch, handle email/KV failures gracefully (#2) `[S]`
161+
- [x] Add webhook idempotency — check KV for existing transaction before processing (#1) `[S]`
162+
- [x] Wrap webhook handler in try-catch, handle email/KV failures gracefully (#2) `[S]`
163163

164164
### Urgent (high severity)
165165

166-
- [ ] Fix admin auth timing attack + audit for other constant-time comparison gaps (#3) `[S]`
167-
- [ ] Add `--color-allow` to dark mode CSS block (#4) `[XS]`
168-
- [ ] Add Rust tests for `delete.rs` — success, cancellation, permission errors, partial failure (#11) `[M]`
166+
- [x] Fix admin auth timing attack + audit for other constant-time comparison gaps (#3) `[S]`
167+
- [x] Add `--color-allow` to dark mode CSS block (#4) `[XS]`
168+
- [x] Add Rust tests for `delete.rs` — success, cancellation, permission errors, partial failure (#11) `[M]`
169169

170170
### Soon (medium severity)
171171

172-
- [ ] Fix AppleScript injection + audit codebase for other shell/script interpolation gaps (#5) `[M]`
173-
- [ ] Replace hardcoded viewer highlight colors with CSS variables (#6) `[XS]`
174-
- [ ] HTML-escape user inputs in license email templates (#7) `[XS]`
175-
- [ ] Add input validation to admin endpoint + audit other endpoints (#8) `[S]`
176-
- [ ] Add tests for cross-filesystem move staging pattern (#12) `[M]`
172+
- [x] Fix AppleScript injection + audit codebase for other shell/script interpolation gaps (#5) `[M]`
173+
- [x] Replace hardcoded viewer highlight colors with CSS variables (#6) `[XS]`
174+
- [x] HTML-escape user inputs in license email templates (#7) `[XS]`
175+
- [x] Add input validation to admin endpoint + audit other endpoints (#8) `[S]`
176+
- [x] Add tests for cross-filesystem move staging pattern (#12) `[M]`
177177

178178
### When convenient (low severity)
179179

180-
- [ ] Delete completed specs from `docs/specs/` (#9) `[XS]`
181-
- [ ] Remove/redact PII from production logs + audit for more (#10) `[S]`
180+
- [x] Delete completed specs from `docs/specs/` (#9) `[XS]`
181+
- [x] Remove/redact PII from production logs + audit for more (#10) `[S]`

0 commit comments

Comments
 (0)