-
Notifications
You must be signed in to change notification settings - Fork 0
Security Scan — 2026-05-09 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| [ARGON2ID_PARAMS] - Insufficient Argon2id memory cost | ||
| Severity: Critical | ||
| Location: neuron-encrypt/src/crypto.rs:114 | ||
| Description: The current Argon2id memory cost is set to 64MB (m_cost=65536). Security guidelines recommend a minimum of 256MB (m_cost=262144) for hardened file encryption to resist specialized hardware-based brute-force attacks. | ||
| Why not fixed: no-touch zone | ||
| Suggested fix: Update Argon2id Params in derive_key and derive_key_v3 to use m_cost=262144. | ||
| First detected: 2026-05-09 | ||
| Last attempted: 2026-05-09 | ||
| Attempt count: 1 | ||
| --- | ||
| [UNEXPECTED_PANICS] - Unhandled unwrap() calls in crypto-adjacent paths | ||
| Severity: Medium | ||
| Location: neuron-encrypt/src/crypto.rs:116 | ||
| Description: Multiple uses of .unwrap() or .unwrap_or_else() on locks/IO results within src/crypto.rs could lead to panics in production if environment conditions change (e.g., mutex poisoning or unexpected IO failure during file processing). | ||
| Why not fixed: no-touch zone | ||
| Suggested fix: Replace .unwrap() with proper error handling using CryptoError or .expect() with descriptive panic messages. | ||
| First detected: 2026-05-09 | ||
| Last attempted: 2026-05-09 | ||
| Attempt count: 1 | ||
| --- | ||
| [RUSTSEC-2024-0436] - Unmaintained dependency: paste | ||
| Severity: Medium | ||
| Location: Cargo.lock | ||
| Description: The `paste` crate is no longer maintained. While it's a macro-utility crate, unmaintained dependencies increase the long-term risk of unpatched vulnerabilities or incompatibility. | ||
| Why not fixed: no-touch zone (Cargo.lock modification restricted) | ||
| Suggested fix: Replace `paste` with `pastey` or `with_builtin_macros`. | ||
| First detected: 2026-05-09 | ||
| Last attempted: 2026-05-09 | ||
| Attempt count: 1 | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"database":{"advisory-count":1068,"last-commit":"881a159d8d70075fe79eb23605e7127a9c2a738a","last-updated":"2026-05-07T10:56:41+02:00"},"lockfile":{"dependency-count":537},"settings":{"target_arch":[],"target_os":[],"severity":null,"ignore":[],"informational_warnings":["unmaintained","unsound","notice"]},"vulnerabilities":{"found":false,"count":0,"list":[]},"warnings":{"unmaintained":[{"kind":"unmaintained","package":{"name":"paste","version":"1.0.15","source":"registry+https://github.com/rust-lang/crates.io-index","checksum":"57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a","replace":null},"advisory":{"id":"RUSTSEC-2024-0436","package":"paste","title":"paste - no longer maintained","description":"The creator of the crate `paste` has stated in the [`README.md`](https://github.com/dtolnay/paste/blob/master/README.md) \nthat this project is not longer maintained as well as archived the repository\n\n## Possible Alternative(s)\n\n- [`pastey`]: a fork of paste and is aimed to be a drop-in replacement with additional features for paste crate\n- [`with_builtin_macros`]: crate providing a [superset of `paste`'s functionality including general `macro_rules!` eager expansions](https://docs.rs/with_builtin_macros/0.1.0/with_builtin_macros/macro.with_eager_expansions.html) and `concat!`/`concat_idents!` macros\n\n[`pastey`]: https://crates.io/crates/pastey\n[`with_builtin_macros`]: https://crates.io/crates/with_builtin_macros","date":"2024-10-07","aliases":[],"related":[],"collection":"crates","categories":[],"keywords":[],"cvss":null,"informational":"unmaintained","references":[],"source":null,"url":"https://github.com/dtolnay/paste","withdrawn":null,"license":"CC0-1.0","expect-deleted":false},"affected":null,"versions":{"patched":[],"unaffected":[]}}]}} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -239,10 +239,10 @@ fn eval_strength(password: &str) -> (Strength, f32, &'static str) { | |
| let len = password.chars().count(); | ||
|
|
||
| for c in password.chars() { | ||
| if c.is_ascii_uppercase() { | ||
| if c.is_uppercase() { | ||
| has_upper = true; | ||
| } | ||
| if c.is_ascii_digit() { | ||
| if c.is_numeric() { | ||
| has_digit = true; | ||
| } | ||
| if !c.is_alphanumeric() { | ||
|
|
@@ -289,14 +289,9 @@ fn preview_output_name(mode: Mode, path: &Path) -> String { | |
| match mode { | ||
| Mode::Encrypt => format!("{}{}", name, crypto::EXTENSION), | ||
| Mode::Decrypt => { | ||
| let lower = name.to_lowercase(); | ||
| if lower.ends_with(crypto::EXTENSION) { | ||
| // Use char-boundary-safe slicing | ||
| let strip_len = name.len() - crypto::EXTENSION.len(); | ||
| name[..strip_len].to_owned() | ||
| } else { | ||
| name | ||
| } | ||
| name.strip_suffix(crypto::EXTENSION) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| .map(|s| s.to_owned()) | ||
|
Comment on lines
291
to
+293
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: Decrypt output name no longer strips extension case-insensitively. The previous logic handled the extension case-insensitively (e.g. |
||
| .unwrap_or(name) | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -760,7 +755,12 @@ impl NeuronEncryptApp { | |
| }; | ||
|
|
||
| painter.rect_filled(button_rect, 7.0, fill); | ||
| painter.rect_stroke(button_rect, 7.0, Stroke::new(1.0, stroke_color)); | ||
| painter.rect_stroke( | ||
| button_rect, | ||
| 7.0, | ||
| Stroke::new(1.0, stroke_color), | ||
| egui::StrokeKind::Middle, | ||
| ); | ||
| painter.text( | ||
| button_rect.center(), | ||
| Align2::CENTER_CENTER, | ||
|
|
@@ -784,10 +784,10 @@ impl NeuronEncryptApp { | |
| fn draw_screen_header(&self, ui: &mut egui::Ui) { | ||
| let rounding = 11.0; | ||
| let (badge, fill, text_color) = self.screen_badge(); | ||
| egui::Frame::none() | ||
| egui::Frame::new() | ||
| .fill(fill) | ||
| .stroke(Stroke::new(1.0, Palette::BORDER_MED)) | ||
| .rounding(rounding) | ||
| .corner_radius(rounding) | ||
| .inner_margin(6.0) | ||
| .show(ui, |ui| { | ||
| ui.label( | ||
|
|
@@ -845,7 +845,7 @@ impl NeuronEncryptApp { | |
| }; | ||
|
|
||
| painter.rect_filled(rect, 8.0, fill); | ||
| painter.rect_stroke(rect, 8.0, Stroke::new(1.0, stroke_color)); | ||
| painter.rect_stroke(rect, 8.0, Stroke::new(1.0, stroke_color), egui::StrokeKind::Middle); | ||
| painter.text( | ||
| rect.center(), | ||
| Align2::CENTER_CENTER, | ||
|
|
@@ -889,6 +889,7 @@ impl NeuronEncryptApp { | |
| Palette::BORDER_MED | ||
| }, | ||
| ), | ||
| egui::StrokeKind::Middle, | ||
| ); | ||
| painter.text( | ||
| rect.center_top() + vec2(0.0, 42.0), | ||
|
|
@@ -969,10 +970,10 @@ impl NeuronEncryptApp { | |
| .unwrap_or_else(|_| String::from("Unknown size")); | ||
| let output = preview_output_name(self.mode, &path); | ||
|
|
||
| egui::Frame::none() | ||
| egui::Frame::new() | ||
| .fill(Palette::SURFACE_1) | ||
| .stroke(Stroke::new(1.0, Palette::BORDER)) | ||
| .rounding(10.0) | ||
| .corner_radius(10.0) | ||
| .inner_margin(14.0) | ||
| .show(ui, |ui| { | ||
| ui.horizontal(|ui| { | ||
|
|
@@ -1083,11 +1084,12 @@ impl NeuronEncryptApp { | |
| Palette::BORDER | ||
| }, | ||
| ), | ||
| egui::StrokeKind::Middle, | ||
| ); | ||
|
|
||
| let input_rect = | ||
| Rect::from_min_max(rect.min + vec2(12.0, 11.0), rect.max - vec2(12.0, 11.0)); | ||
| ui.allocate_ui_at_rect(input_rect, |ui| { | ||
| ui.scope_builder(egui::UiBuilder::new().max_rect(input_rect), |ui| { | ||
| let edit = if primary { | ||
| egui::TextEdit::singleline(&mut *self.password).id(id) | ||
| } else { | ||
|
|
@@ -1154,10 +1156,10 @@ impl NeuronEncryptApp { | |
| border: Color32, | ||
| text: Color32, | ||
| ) { | ||
| egui::Frame::none() | ||
| egui::Frame::new() | ||
| .fill(fill) | ||
| .stroke(Stroke::new(1.0, border)) | ||
| .rounding(8.0) | ||
| .corner_radius(8.0) | ||
| .inner_margin(12.0) | ||
| .show(ui, |ui| { | ||
| ui.label( | ||
|
|
@@ -1221,10 +1223,10 @@ impl NeuronEncryptApp { | |
| .is_some_and(|path| is_vx2_file(path)) | ||
| { | ||
| ui.add_space(14.0); | ||
| egui::Frame::none() | ||
| egui::Frame::new() | ||
| .fill(Palette::warning_muted()) | ||
| .stroke(Stroke::new(1.0, Palette::WARNING)) | ||
| .rounding(8.0) | ||
| .corner_radius(8.0) | ||
| .inner_margin(12.0) | ||
| .show(ui, |ui| { | ||
| ui.label( | ||
|
|
@@ -1376,10 +1378,10 @@ impl NeuronEncryptApp { | |
| ); | ||
| ui.add_space(12.0); | ||
|
|
||
| egui::Frame::none() | ||
| egui::Frame::new() | ||
| .fill(Palette::SURFACE_1) | ||
| .stroke(Stroke::new(1.0, Palette::BORDER)) | ||
| .rounding(8.0) | ||
| .corner_radius(8.0) | ||
| .inner_margin(12.0) | ||
| .show(ui, |ui| { | ||
| ui.label( | ||
|
|
@@ -1899,17 +1901,17 @@ impl eframe::App for NeuronEncryptApp { | |
| } | ||
|
|
||
| egui::CentralPanel::default() | ||
| .frame(egui::Frame::none().fill(Palette::BG)) | ||
| .frame(egui::Frame::new().fill(Palette::BG)) | ||
| .show(ctx, |ui| { | ||
| self.draw_title_bar(ui); | ||
| ui.add_space(22.0); | ||
|
|
||
| egui::ScrollArea::vertical().show(ui, |ui| { | ||
| ui.vertical_centered(|ui| { | ||
| egui::Frame::none() | ||
| egui::Frame::new() | ||
| .fill(Palette::SURFACE) | ||
| .stroke(Stroke::new(1.0, Palette::BORDER)) | ||
| .rounding(12.0) | ||
| .corner_radius(12.0) | ||
| .inner_margin(24.0) | ||
| .show(ui, |ui| { | ||
| ui.set_width(520.0); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: darkmaster0345/Neuron-Encrypt
Length of output: 273
🌐 Web query:
Does the subtle crate's ConstantTimeEq::ct_eq handle slices of differing lengths in constant time, and what is its current stable version?💡 Result:
No. The subtle crate’s ConstantTimeEq::ct_eq for slices explicitly short-circuits (and therefore does not run in time independent of slice lengths) when slice lengths differ: it checks the lengths and returns Choice::from(0) immediately if they are not equal.[1][2] Current stable version (crates.io): subtle 2.6.1.[3]
Citations:
🏁 Script executed:
Repository: darkmaster0345/Neuron-Encrypt
Length of output: 94
Hand-rolled constant-time compare still leaks the longer length, and bit tricks are fragile under LLVM.
The original early-exit timing leak is gone — good. But two critical concerns remain:
Residual timing leak on input length. The loop runs for
max_len = max(len_a, len_b)iterations, so runtime is proportional to the longer input's length. An observer learnsmax(|a|, |b|), which inread_password_confirmed(line 182) is the length of the longer of the two typed passphrases. A genuinely constant-time compare requires fixed iteration count or accepts that input length is public.Compiler may defeat the masking.
final_result == 0and the|=loop are not guaranteed to compile to branchless code. LLVM is free to introduce branches or short-circuit if it can prove the result. Constant-time properties at source level are not preserved by the optimizer without explicit barriers likecore::hint::black_box.subtle::ConstantTimeEqfix is inadequate: Thesubtlecrate'sct_eqfor slices also short-circuits when input lengths differ—it checks lengths and returns early if unequal. This preserves the exact timing vulnerability identified in concern#1. Usingsubtlerequires padding both inputs to equal length before comparison.Correct approaches:
subtle::ct_eq(padded_a, padded_b), orcore::hint::black_box()or volatile reads around the comparison result to prevent LLVM from introducing branches.🤖 Prompt for AI Agents