Conversation
📝 WalkthroughWalkthroughThe PR rewrites cookie extraction from a ChangesCookie Extraction Rewrite
nosleep Removal and Sleep Inhibit Consolidation
log → tracing Migration
Sequence Diagram(s)sequenceDiagram
participant Renderer
participant import_browser_cookies
participant cookies_for_url
participant platform_decrypt
participant elevate_module
Renderer->>import_browser_cookies: invoke("import_browser_cookies", {browser, url})
import_browser_cookies->>cookies_for_url: cookies_for_url(browser, url)
cookies_for_url->>platform_decrypt: extract_master_key + decrypt_value
alt v20 encrypted (admin Chrome)
platform_decrypt-->>cookies_for_url: Err(ELEVATION_REQUIRED)
cookies_for_url-->>import_browser_cookies: Err("risuko:elevation-required")
import_browser_cookies-->>Renderer: Err("ELEVATION_REQUIRED")
Renderer->>import_browser_cookies: invoke("import_browser_cookies_elevated")
import_browser_cookies->>elevate_module: elevate::import_elevated(browser, url)
elevate_module->>elevate_module: ShellExecuteExW runas extract-cookies --out tmpfile
elevate_module-->>import_browser_cookies: HostCookies
import_browser_cookies-->>Renderer: ImportedCookies
else normal decryption succeeds
platform_decrypt-->>cookies_for_url: Vec<Cookie>
cookies_for_url-->>import_browser_cookies: HostCookies
import_browser_cookies-->>Renderer: ImportedCookies
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/risuko-cookies/Cargo.toml`:
- Line 36: The aes-gcm dependency in the Cargo.toml file is pinned to a release
candidate version (0.11.0-rc.4) which should be avoided in production. Update
the aes-gcm dependency version from "0.11.0-rc.4" to "0.10.3" (the latest stable
release) while maintaining the existing features configuration ["aes", "alloc"].
In `@src-tauri/risuko-cookies/src/browser/chromium.rs`:
- Around line 217-233: The SELECT statement at line 217 is missing the `value`
column, and the row parsing logic skips any cookies where `encrypted_value` is
empty, discarding valid plaintext cookies. To fix this, add the `value` column
to the SELECT query, then update the `parse_row` function to capture both
`encrypted_value` and `value` columns. Modify the condition that checks
`raw.raw_value.is_empty()` to implement fallback logic: use the decrypted
`encrypted_value` when available, otherwise fall back to the plaintext `value`
column, and only skip the row when both columns are empty. Apply the same
changes to the similar code block mentioned in the "Also applies to" range.
In `@src-tauri/risuko-cookies/src/browser/firefox.rs`:
- Around line 110-123: The else branch in the cookie_covers_host function
incorrectly treats non-dot-prefixed cookie hosts as domain cookies that cover
subdomains. Host-only cookies without a leading dot should only match the exact
host. Remove the subdomain matching logic from the else branch by replacing the
condition `r == c || r.ends_with(&format!(".{c}"))` with just `r == c` to ensure
host-only cookies match only the exact request host.
In `@src-tauri/risuko-cookies/src/browser/safari.rs`:
- Around line 24-41: The current approach incorrectly uses defaults export which
outputs plist format, but then attempts to open the result as a SQLite database
with Connection::open(), which will fail since Safari's Cookies.binarycookies is
a proprietary binary format. Replace the defaults export command and
NamedTempFile approach with a dedicated binary cookie parser library that can
directly parse the Cookies.binarycookies format. Also ensure the command output
status is checked before proceeding to detect failures earlier. Consider using a
crate designed specifically for parsing Safari's binary cookie format rather
than attempting format conversion through defaults export.
In `@src-tauri/risuko-cookies/src/platform/linux.rs`:
- Around line 74-75: The OpenSession method is being called on the Collection
proxy at lines 74-75, but OpenSession is a method on the
org.freedesktop.Secret.Service interface located at /org/freedesktop/secrets,
not on Collection. Create a separate Service proxy using the zbus connection and
the /org/freedesktop/secrets path, then call the OpenSession method with
parameters ("plain", empty zvariant Value) on this Service proxy instead of on
the current Collection proxy to fix the D-Bus method-not-found error.
In `@src-tauri/risuko-cookies/src/utils/time.rs`:
- Around line 14-19: The safari_to_unix function has a unit mismatch issue where
it adds a seconds offset (978_307_200) directly to the timestamp value before
dividing by 1_000_000_000, treating the timestamp as if it's in nanoseconds. To
fix this in the function body, reorder the operations so that the timestamp is
first converted from nanoseconds to seconds by dividing by 1_000_000_000, and
then the Safari epoch offset (978_307_200 seconds) is added to produce the
correct UNIX timestamp in seconds.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 615a002b-c20d-407f-801e-1b9669d34189
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (65)
package.jsonsrc-tauri/Cargo.tomlsrc-tauri/capabilities/default.jsonsrc-tauri/capabilities/desktop.jsonsrc-tauri/risuko-bt/Cargo.tomlsrc-tauri/risuko-bt/src/dht.rssrc-tauri/risuko-bt/src/lsd.rssrc-tauri/risuko-bt/src/magnet.rssrc-tauri/risuko-bt/src/peer/connection.rssrc-tauri/risuko-bt/src/session.rssrc-tauri/risuko-bt/src/torrent.rssrc-tauri/risuko-bt/src/upnp.rssrc-tauri/risuko-cli/Cargo.tomlsrc-tauri/risuko-cookies/Cargo.tomlsrc-tauri/risuko-cookies/src/browser/chromium.rssrc-tauri/risuko-cookies/src/browser/firefox.rssrc-tauri/risuko-cookies/src/browser/mod.rssrc-tauri/risuko-cookies/src/browser/safari.rssrc-tauri/risuko-cookies/src/lib.rssrc-tauri/risuko-cookies/src/platform/linux.rssrc-tauri/risuko-cookies/src/platform/macos.rssrc-tauri/risuko-cookies/src/platform/mod.rssrc-tauri/risuko-cookies/src/platform/windows.rssrc-tauri/risuko-cookies/src/utils/mod.rssrc-tauri/risuko-cookies/src/utils/paths.rssrc-tauri/risuko-cookies/src/utils/time.rssrc-tauri/risuko-engine/Cargo.tomlsrc-tauri/risuko-engine/src/config/mod.rssrc-tauri/risuko-engine/src/engine/cookie_store.rssrc-tauri/risuko-engine/src/engine/dns.rssrc-tauri/risuko-engine/src/engine/ed2k/download.rssrc-tauri/risuko-engine/src/engine/ed2k/server.rssrc-tauri/risuko-engine/src/engine/manager.rssrc-tauri/risuko-engine/src/engine/mod.rssrc-tauri/risuko-engine/src/engine/options.rssrc-tauri/risuko-engine/src/engine/rpc.rssrc-tauri/risuko-engine/src/engine/rss/mod.rssrc-tauri/risuko-engine/src/engine/session.rssrc-tauri/risuko-engine/src/engine/ssh_known_hosts.rssrc-tauri/risuko-engine/src/engine/torrent.rssrc-tauri/risuko-engine/src/engine/upload/manager.rssrc-tauri/risuko-engine/src/engine/upload/s3.rssrc-tauri/risuko-engine/src/engine/upload/sftp.rssrc-tauri/risuko-engine/src/engine/upload/webdav.rssrc-tauri/risuko-engine/src/traits.rssrc-tauri/risuko-napi/Cargo.tomlsrc-tauri/risuko-napi/src/lib.rssrc-tauri/src/bridge.rssrc-tauri/src/cli/commands.rssrc-tauri/src/cli/headless.rssrc-tauri/src/cli/mod.rssrc-tauri/src/commands/android_intent.rssrc-tauri/src/commands/app_cmds.rssrc-tauri/src/commands/completion_script_cmds.rssrc-tauri/src/commands/cookie_cmds.rssrc-tauri/src/commands/engine_cmds.rssrc-tauri/src/commands/event_cmds.rssrc-tauri/src/commands/file_cmds.rssrc-tauri/src/commands/rss_cmds.rssrc-tauri/src/commands/upload_cmds.rssrc-tauri/src/lib.rssrc-tauri/src/managers/flyout.rssrc-tauri/src/managers/vault.rssrc-tauri/src/state.rssrc/renderer/components/Native/EngineClient.vue
💤 Files with no reviewable changes (2)
- src-tauri/capabilities/default.json
- src-tauri/risuko-cli/Cargo.toml
| hmac = "0.13" | ||
|
|
||
| [target.'cfg(target_os = "windows")'.dependencies] | ||
| aes-gcm = { version = "0.11.0-rc.4", features = ["aes", "alloc"] } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the latest stable version of the aes-gcm Rust crate?
💡 Result:
As of June 21, 2026, the latest stable version of the aes-gcm Rust crate is 0.10.3 [1][2][3]. While newer release candidate versions (such as 0.11.0-rc.4) are available on crates.io, they are not considered stable releases [1][3].
Citations:
- 1: https://crates.io/crates/aes-gcm
- 2: https://crates.io/crates/aes-gcm/0.10.3
- 3: https://crates.io/crates/aes_gcm
Consider using stable aes-gcm 0.10.3 instead of release candidate 0.11.0-rc.4.
The dependency pins aes-gcm = "0.11.0-rc.4", a pre-release version. The latest stable release is 0.10.3, which is suitable for AES-256-GCM decryption. Avoid RC versions in production dependencies unless the RC-specific features are essential, as they may introduce breaking changes before final release.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src-tauri/risuko-cookies/Cargo.toml` at line 36, The aes-gcm dependency in
the Cargo.toml file is pinned to a release candidate version (0.11.0-rc.4) which
should be avoided in production. Update the aes-gcm dependency version from
"0.11.0-rc.4" to "0.10.3" (the latest stable release) while maintaining the
existing features configuration ["aes", "alloc"].
| // Modern Firefox stores domain cookies without a leading dot | ||
| fn cookie_covers_host(request_host: &str, cookie_host: &str) -> bool { | ||
| let r = request_host.to_lowercase(); | ||
| let c = cookie_host.to_lowercase(); | ||
|
|
||
| if c.starts_with('.') { | ||
| // Older Firefox: domain cookie with explicit leading dot | ||
| let domain = &c[1..]; | ||
| r == domain || r.ends_with(&format!(".{domain}")) | ||
| } else { | ||
| // Modern Firefox: bare domain is a domain cookie (covers subdomains) | ||
| r == c || r.ends_with(&format!(".{c}")) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In current Firefox moz_cookies, how are host-only cookies represented in the host field, and is a non-leading-dot host always a domain cookie that should match subdomains?
💡 Result:
In the Firefox moz_cookies table, the host field stores the hostname for which a cookie is set [1][2]. Host-only cookies—those restricted to the exact host that set them—are represented in the host field as the full hostname without a leading dot [3][4][5]. Conversely, domain cookies (which are allowed to be sent to subdomains) are typically represented by including a leading dot in the host string (e.g., .example.com) [4][5][6]. Regarding your second question, a non-leading-dot host is not necessarily a domain cookie [4][5]. In Firefox, the presence of a leading dot is the primary indicator used to explicitly mark a cookie as a domain cookie [4][6]. A hostname without a leading dot is treated as a host-only cookie, meaning it should only be sent to the exact host that set it, not its subdomains [3][7][4]. Firefox uses this distinction to enforce scoping rules, where cookies lacking the leading dot are restricted to the specific host, while those with the leading dot (provided they meet valid domain requirements) are scoped to the domain and its subdomains [7][6][8].
Citations:
- 1: http://fileformats.archiveteam.org/wiki/Firefox_cookie_database
- 2: http://retinal.dehy.de/docs/doku.php?id=technotes%3Acookies
- 3: https://searchfox.org/mozilla-central/source/netwerk/cookie/nsICookie.idl
- 4: https://gist.github.com/pyokagan/4257326
- 5: https://gist.github.com/bmarcondes/500634cb87c9ed95944d
- 6: https://github.com/mozilla-releng/git-backing/blob/main/netwerk/cookie/nsICookieManager.idl
- 7: https://searchfox.org/mozilla-central/rev/765cc1b925c5d32d05111c364257a0b79bf2952e/toolkit/components/extensions/ext-cookies.js
- 8: https://raw.githubusercontent.com/mozilla-firefox/firefox/main/netwerk/cookie/CookieCommons.cpp
Fix host-only cookie matching logic.
The code incorrectly treats non-dot-prefixed hosts as domain cookies. In Firefox, host-only cookies (without leading dot) should match only the exact host, not subdomains. Update the else branch to use only r == c instead of r == c || r.ends_with(&format!(".{c}")).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src-tauri/risuko-cookies/src/browser/firefox.rs` around lines 110 - 123, The
else branch in the cookie_covers_host function incorrectly treats
non-dot-prefixed cookie hosts as domain cookies that cover subdomains. Host-only
cookies without a leading dot should only match the exact host. Remove the
subdomain matching logic from the else branch by replacing the condition `r == c
|| r.ends_with(&format!(".{c}"))` with just `r == c` to ensure host-only cookies
match only the exact request host.
| pub fn safari_to_unix(timestamp: u64) -> Option<u64> { | ||
| if timestamp == 0 { | ||
| return None; | ||
| } | ||
| Some((timestamp + 978_307_200) / 1_000_000_000) | ||
| } |
There was a problem hiding this comment.
Safari timestamp conversion is using mixed units.
Line 18 adds a seconds offset to a nanoseconds value before dividing, which produces incorrect UNIX expiry values.
Proposed fix
#[cfg(target_os = "macos")]
pub fn safari_to_unix(timestamp: u64) -> Option<u64> {
if timestamp == 0 {
return None;
}
- Some((timestamp + 978_307_200) / 1_000_000_000)
+ let secs_since_2001 = timestamp / 1_000_000_000;
+ secs_since_2001.checked_add(978_307_200)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn safari_to_unix(timestamp: u64) -> Option<u64> { | |
| if timestamp == 0 { | |
| return None; | |
| } | |
| Some((timestamp + 978_307_200) / 1_000_000_000) | |
| } | |
| pub fn safari_to_unix(timestamp: u64) -> Option<u64> { | |
| if timestamp == 0 { | |
| return None; | |
| } | |
| let secs_since_2001 = timestamp / 1_000_000_000; | |
| secs_since_2001.checked_add(978_307_200) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src-tauri/risuko-cookies/src/utils/time.rs` around lines 14 - 19, The
safari_to_unix function has a unit mismatch issue where it adds a seconds offset
(978_307_200) directly to the timestamp value before dividing by 1_000_000_000,
treating the timestamp as if it's in nanoseconds. To fix this in the function
body, reorder the operations so that the timestamp is first converted from
nanoseconds to seconds by dividing by 1_000_000_000, and then the Safari epoch
offset (978_307_200 seconds) is added to produce the correct UNIX timestamp in
seconds.
There was a problem hiding this comment.
5 issues found across 67 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src-tauri/risuko-cookies/src/platform/macos.rs">
<violation number="1" location="src-tauri/risuko-cookies/src/platform/macos.rs:29">
P1: macOS v10 cookie format parsed incorrectly: IV is not embedded after `v10`. This will fail/decode garbage for valid Chromium cookies.</violation>
</file>
<file name="src-tauri/risuko-cookies/src/browser/safari.rs">
<violation number="1" location="src-tauri/risuko-cookies/src/browser/safari.rs:69">
P3: Domain matching logic is duplicated instead of shared. Future fixes may diverge across browsers and produce inconsistent filtering.</violation>
</file>
<file name="src-tauri/risuko-cookies/Cargo.toml">
<violation number="1" location="src-tauri/risuko-cookies/Cargo.toml:31">
P1: New crypto dependency versions require a higher Rust MSRV than the project’s documented minimum. This can break builds for users/CI pinned to Rust 1.77.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| let iv: [u8; 16] = data[3..19].try_into()?; | ||
| let ciphertext = &data[19..]; |
There was a problem hiding this comment.
P1: macOS v10 cookie format parsed incorrectly: IV is not embedded after v10. This will fail/decode garbage for valid Chromium cookies.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-cookies/src/platform/macos.rs, line 29:
<comment>macOS v10 cookie format parsed incorrectly: IV is not embedded after `v10`. This will fail/decode garbage for valid Chromium cookies.</comment>
<file context>
@@ -0,0 +1,48 @@
+ }
+
+ // v10 format: "v10" + 16-byte IV + ciphertext
+ let iv: [u8; 16] = data[3..19].try_into()?;
+ let ciphertext = &data[19..];
+
</file context>
| let iv: [u8; 16] = data[3..19].try_into()?; | |
| let ciphertext = &data[19..]; | |
| let iv = [b' '; 16]; | |
| let ciphertext = &data[3..]; |
| aes = "0.9" | ||
| cbc = { version = "0.2", features = ["alloc"] } | ||
| cipher = { version = "0.5", features = ["alloc", "block-padding"] } | ||
| pbkdf2 = "0.13" |
There was a problem hiding this comment.
P1: New crypto dependency versions require a higher Rust MSRV than the project’s documented minimum. This can break builds for users/CI pinned to Rust 1.77.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-cookies/Cargo.toml, line 31:
<comment>New crypto dependency versions require a higher Rust MSRV than the project’s documented minimum. This can break builds for users/CI pinned to Rust 1.77.</comment>
<file context>
@@ -13,8 +13,34 @@ name = "risuko_cookies"
+aes = "0.9"
+cbc = { version = "0.2", features = ["alloc"] }
+cipher = { version = "0.5", features = ["alloc", "block-padding"] }
+pbkdf2 = "0.13"
+sha1 = "0.11"
+hmac = "0.13"
</file context>
| } | ||
|
|
||
| #[cfg(target_os = "macos")] | ||
| fn cookie_covers_host(request_host: &str, cookie_domain: &str) -> bool { |
There was a problem hiding this comment.
P3: Domain matching logic is duplicated instead of shared. Future fixes may diverge across browsers and produce inconsistent filtering.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-cookies/src/browser/safari.rs, line 69:
<comment>Domain matching logic is duplicated instead of shared. Future fixes may diverge across browsers and produce inconsistent filtering.</comment>
<file context>
@@ -0,0 +1,126 @@
+}
+
+#[cfg(target_os = "macos")]
+fn cookie_covers_host(request_host: &str, cookie_domain: &str) -> bool {
+ let r = request_host.to_lowercase();
+ let c = cookie_domain.to_lowercase();
</file context>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/risuko-cookies/src/browser/chromium.rs`:
- Line 325: The `expires` field assignment with the `webkit_to_unix` function is
casting a signed i64 value directly to u64 without first validating that it is
non-negative. When `expires_utc` is negative (e.g., -1), the cast wraps to a
very large u64 value, bypassing the zero check inside `webkit_to_unix` and
producing an incorrect far-future timestamp instead of returning None. Add a
check before the `as u64` cast on the value from `row.get::<_, i64>(7)` to
validate that it is positive, and return None or handle the invalid case
appropriately rather than allowing the cast to silently wrap negative values to
large positive numbers.
In `@src-tauri/risuko-cookies/src/browser/safari.rs`:
- Around line 102-161: The Safari binarycookies format uses mixed endianness
where page content and cookie records must be parsed as little-endian instead of
big-endian. In the parse_page function, change from_be_bytes to from_le_bytes
for the num_cookies field read from page[4..8] and for the cookie_offset field
read in the loop at page[off..off + 4]. In the parse_cookie function, change
from_be_bytes to from_le_bytes for all the field reads: flags at cookie[4..8],
url_offset at cookie[12..16], name_offset at cookie[16..20], path_offset at
cookie[20..24], value_offset at cookie[24..28], and the expiry f64 bytes at
cookie[28..36]. This ensures all page-level and cookie record data is correctly
interpreted using little-endian byte ordering.
In `@src-tauri/risuko-cookies/src/platform/linux.rs`:
- Around line 81-82: The OpenSession method call at line 81-82 in the linux.rs
platform module is attempting to deserialize the return value directly to
OwnedObjectPath, but according to the freedesktop.org Secret Service
specification, OpenSession returns a tuple of (Variant output, ObjectPath
result). Change the type annotation for session_path to deserialize the full
tuple type instead of just the ObjectPath component, then destructure the
returned tuple to extract only the ObjectPath element (the second part of the
tuple) that is needed for subsequent operations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f4731895-d8dd-4618-b74b-35672df4a4d5
⛔ Files ignored due to path filters (1)
src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
src-tauri/Cargo.tomlsrc-tauri/risuko-cookies/src/browser/chromium.rssrc-tauri/risuko-cookies/src/browser/safari.rssrc-tauri/risuko-cookies/src/platform/linux.rssrc-tauri/risuko-cookies/src/utils/time.rs
| #[cfg(target_os = "macos")] | ||
| fn parse_page(page: &[u8], cookies: &mut Vec<RawSafariCookie>) -> Result<()> { | ||
| if page.len() < 8 { | ||
| bail!("page too short"); | ||
| } | ||
|
|
||
| let num_cookies = u32::from_be_bytes(page[4..8].try_into().unwrap()) as usize; | ||
| if num_cookies == 0 { | ||
| return Ok(()); | ||
| } | ||
|
|
||
| let header_size = 8 + num_cookies * 4; | ||
| if page.len() < header_size { | ||
| bail!("page header truncated"); | ||
| } | ||
|
|
||
| for i in 0..num_cookies { | ||
| let off = 8 + i * 4; | ||
| let cookie_offset = u32::from_be_bytes(page[off..off + 4].try_into().unwrap()) as usize; | ||
| if cookie_offset >= page.len() { | ||
| bail!("cookie offset out of bounds"); | ||
| } | ||
| parse_cookie(&page[cookie_offset..], cookies)?; | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(target_os = "macos")] | ||
| fn parse_cookie(cookie: &[u8], cookies: &mut Vec<RawSafariCookie>) -> Result<()> { | ||
| if cookie.len() < 44 { | ||
| bail!("cookie record too short"); | ||
| } | ||
|
|
||
| let flags = u32::from_be_bytes(cookie[4..8].try_into().unwrap()); | ||
| let url_offset = u32::from_be_bytes(cookie[12..16].try_into().unwrap()) as usize; | ||
| let name_offset = u32::from_be_bytes(cookie[16..20].try_into().unwrap()) as usize; | ||
| let path_offset = u32::from_be_bytes(cookie[20..24].try_into().unwrap()) as usize; | ||
| let value_offset = u32::from_be_bytes(cookie[24..28].try_into().unwrap()) as usize; | ||
|
|
||
| let expiry_bytes: [u8; 8] = cookie[28..36].try_into().unwrap(); | ||
| let expiry = f64::from_be_bytes(expiry_bytes); | ||
|
|
||
| let domain = read_cstr(cookie, url_offset)?; | ||
| let name = read_cstr(cookie, name_offset)?; | ||
| let path = read_cstr(cookie, path_offset)?; | ||
| let value = read_cstr(cookie, value_offset)?; | ||
|
|
||
| cookies.push(RawSafariCookie { | ||
| name, | ||
| value, | ||
| domain, | ||
| path, | ||
| secure: (flags & 0x01) != 0, | ||
| http_only: (flags & 0x04) != 0, | ||
| expires: time::safari_to_unix(expiry), | ||
| }); | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Safari Cookies.binarycookies binary format endianness specification
💡 Result:
The Safari Cookies.binarycookies file format does not use a single endianness; it is a mixed-endian format [1]. The file utilizes both big-endian and little-endian byte ordering depending on the specific section and data field [1][2]. General Endianness Structure: 1. File Header and Page Indexing (Big-Endian): The top-level file structure, including the file signature ("cook"), the page count, and the array of page sizes, uses big-endian byte order [1][3][4][2]. 2. Page Content and Cookie Records (Little-Endian): Once inside individual pages, the data structures switch to little-endian [1][3][4][2]. This includes the page header signature (which is a constant 0x00000100), the number of cookies within the page, the cookie offsets, and all fields within the individual cookie records (such as cookie size, flags, string offsets, and expiration/creation timestamps) [1][5][2]. Key Technical Specifications: - File Signature: The 4-byte magic number is 0x636f6f6b ("cook") [1][4]. - Page Header Signature: The page start is marked by 0x00000100 [1][5][6]. - Integer Types: Integers in the file header are 32-bit big-endian [1][2], while integers within the cookie records are 32-bit little-endian [1][3][2]. - Timestamps: Date and time values are represented as 64-bit little-endian floats (Cocoa timestamps), counting seconds since 00:00:00 UTC on January 1, 2001 [1][3][2]. - Checksum: A checksum, often calculated by summing 32-bit integers at 4-byte intervals across pages, is typically stored in big-endian format in the file header section [3][4][2]. This mixed-endian architecture is consistent across authoritative technical documentation and community-maintained specifications for the format [1][3][2].
Citations:
- 1: https://github.com/libyal/dtformats/blob/main/documentation/Safari%20Cookies.asciidoc
- 2: https://gist.github.com/creachadair/ba843bd92c2cfc78dc5e1a53b44775a3
- 3: https://github.com/Lessica/CookiesTool
- 4: https://github.com/NotsoanoNimus/NETBinaryCookie
- 5: https://github.com/cixtor/binarycookies
- 6: http://fileformats.archiveteam.org/wiki/Safari_cookies
🏁 Script executed:
# Verify the parse_binary_cookies function to confirm file-level endianness handling
cd src-tauri/risuko-cookies && head -100 src/browser/safari.rs | tail -40Repository: YueMiyuki/Risuko
Length of output: 1088
Safari binarycookies format uses mixed endianness—page headers and cookie records must be parsed as little-endian, not big-endian.
Apple's Cookies.binarycookies format uses mixed byte ordering: the file header (magic "cook", page count, page offsets) is big-endian, but all page content and cookie records are little-endian. The current code reads all fields as big-endian, causing incorrect parsing of cookie count, field offsets, flags, and expiry timestamps.
The parse_binary_cookies function correctly handles the file-level big-endian format, but parse_page and parse_cookie must use from_le_bytes() instead of from_be_bytes() for their respective fields.
Proposed fix
fn parse_page(page: &[u8], cookies: &mut Vec<RawSafariCookie>) -> Result<()> {
if page.len() < 8 {
bail!("page too short");
}
- let num_cookies = u32::from_be_bytes(page[4..8].try_into().unwrap()) as usize;
+ let num_cookies = u32::from_le_bytes(page[4..8].try_into().unwrap()) as usize;
if num_cookies == 0 {
return Ok(());
}
let header_size = 8 + num_cookies * 4;
if page.len() < header_size {
bail!("page header truncated");
}
for i in 0..num_cookies {
let off = 8 + i * 4;
- let cookie_offset = u32::from_be_bytes(page[off..off + 4].try_into().unwrap()) as usize;
+ let cookie_offset = u32::from_le_bytes(page[off..off + 4].try_into().unwrap()) as usize;
if cookie_offset >= page.len() {
bail!("cookie offset out of bounds");
}
parse_cookie(&page[cookie_offset..], cookies)?;
}
Ok(())
}
fn parse_cookie(cookie: &[u8], cookies: &mut Vec<RawSafariCookie>) -> Result<()> {
if cookie.len() < 44 {
bail!("cookie record too short");
}
- let flags = u32::from_be_bytes(cookie[4..8].try_into().unwrap());
- let url_offset = u32::from_be_bytes(cookie[12..16].try_into().unwrap()) as usize;
- let name_offset = u32::from_be_bytes(cookie[16..20].try_into().unwrap()) as usize;
- let path_offset = u32::from_be_bytes(cookie[20..24].try_into().unwrap()) as usize;
- let value_offset = u32::from_be_bytes(cookie[24..28].try_into().unwrap()) as usize;
+ let flags = u32::from_le_bytes(cookie[4..8].try_into().unwrap());
+ let url_offset = u32::from_le_bytes(cookie[12..16].try_into().unwrap()) as usize;
+ let name_offset = u32::from_le_bytes(cookie[16..20].try_into().unwrap()) as usize;
+ let path_offset = u32::from_le_bytes(cookie[20..24].try_into().unwrap()) as usize;
+ let value_offset = u32::from_le_bytes(cookie[24..28].try_into().unwrap()) as usize;
let expiry_bytes: [u8; 8] = cookie[28..36].try_into().unwrap();
- let expiry = f64::from_be_bytes(expiry_bytes);
+ let expiry = f64::from_le_bytes(expiry_bytes);
let domain = read_cstr(cookie, url_offset)?;
let name = read_cstr(cookie, name_offset)?;
let path = read_cstr(cookie, path_offset)?;
let value = read_cstr(cookie, value_offset)?;
cookies.push(RawSafariCookie {
name,
value,
domain,
path,
secure: (flags & 0x01) != 0,
http_only: (flags & 0x04) != 0,
expires: time::safari_to_unix(expiry),
});
Ok(())
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src-tauri/risuko-cookies/src/browser/safari.rs` around lines 102 - 161, The
Safari binarycookies format uses mixed endianness where page content and cookie
records must be parsed as little-endian instead of big-endian. In the parse_page
function, change from_be_bytes to from_le_bytes for the num_cookies field read
from page[4..8] and for the cookie_offset field read in the loop at
page[off..off + 4]. In the parse_cookie function, change from_be_bytes to
from_le_bytes for all the field reads: flags at cookie[4..8], url_offset at
cookie[12..16], name_offset at cookie[16..20], path_offset at cookie[20..24],
value_offset at cookie[24..28], and the expiry f64 bytes at cookie[28..36]. This
ensures all page-level and cookie record data is correctly interpreted using
little-endian byte ordering.
There was a problem hiding this comment.
5 issues found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src-tauri/risuko-cookies/src/browser/safari.rs">
<violation number="1" location="src-tauri/risuko-cookies/src/browser/safari.rs:69">
P3: Domain matching logic is duplicated instead of shared. Future fixes may diverge across browsers and produce inconsistent filtering.</violation>
<violation number="2" location="src-tauri/risuko-cookies/src/browser/safari.rs:92">
P1: Binarycookies header entries are page sizes, not absolute offsets. Treating them as offsets makes the parser jump to wrong positions and miss/fail cookie extraction.</violation>
<violation number="3" location="src-tauri/risuko-cookies/src/browser/safari.rs:108">
P1: The parser uses wrong endianness/layout for Safari page and cookie records. Decoded counts/offsets/expiry become garbage, causing out-of-bounds errors or incorrect cookie data.</violation>
</file>
<file name="src-tauri/risuko-cookies/src/platform/macos.rs">
<violation number="1" location="src-tauri/risuko-cookies/src/platform/macos.rs:29">
P1: macOS v10 cookie format parsed incorrectly: IV is not embedded after `v10`. This will fail/decode garbage for valid Chromium cookies.</violation>
</file>
<file name="src-tauri/risuko-cookies/Cargo.toml">
<violation number="1" location="src-tauri/risuko-cookies/Cargo.toml:31">
P1: New crypto dependency versions require a higher Rust MSRV than the project’s documented minimum. This can break builds for users/CI pinned to Rust 1.77.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| bail!("page too short"); | ||
| } | ||
|
|
||
| let num_cookies = u32::from_be_bytes(page[4..8].try_into().unwrap()) as usize; |
There was a problem hiding this comment.
P1: The parser uses wrong endianness/layout for Safari page and cookie records. Decoded counts/offsets/expiry become garbage, causing out-of-bounds errors or incorrect cookie data.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-cookies/src/browser/safari.rs, line 108:
<comment>The parser uses wrong endianness/layout for Safari page and cookie records. Decoded counts/offsets/expiry become garbage, causing out-of-bounds errors or incorrect cookie data.</comment>
<file context>
@@ -65,6 +55,123 @@ pub fn extract_cookies(host: Option<&str>) -> Result<Vec<Cookie>> {
+ bail!("page too short");
+ }
+
+ let num_cookies = u32::from_be_bytes(page[4..8].try_into().unwrap()) as usize;
+ if num_cookies == 0 {
+ return Ok(());
</file context>
| let mut cookies = Vec::new(); | ||
| for i in 0..num_pages { | ||
| let off = 8 + i * 4; | ||
| let page_offset = u32::from_be_bytes(data[off..off + 4].try_into().unwrap()) as usize; |
There was a problem hiding this comment.
P1: Binarycookies header entries are page sizes, not absolute offsets. Treating them as offsets makes the parser jump to wrong positions and miss/fail cookie extraction.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-cookies/src/browser/safari.rs, line 92:
<comment>Binarycookies header entries are page sizes, not absolute offsets. Treating them as offsets makes the parser jump to wrong positions and miss/fail cookie extraction.</comment>
<file context>
@@ -65,6 +55,123 @@ pub fn extract_cookies(host: Option<&str>) -> Result<Vec<Cookie>> {
+ let mut cookies = Vec::new();
+ for i in 0..num_pages {
+ let off = 8 + i * 4;
+ let page_offset = u32::from_be_bytes(data[off..off + 4].try_into().unwrap()) as usize;
+ if page_offset >= data.len() {
+ bail!("page offset out of bounds");
</file context>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/risuko-cookies/src/browser/chromium.rs`:
- Around line 253-269: The error handling for decrypt failures in the Err(e)
branch and the no_key branch are incorrectly falling back to plaintext or raw
encrypted bytes as cookie values, which can corrupt cookies and break elevation
retry logic. Instead of always using plaintext_value or raw_value as fallback,
only use plaintext_value when the cookie has no encrypted_value in the first
place. When decryption fails for an encrypted cookie, do not import that cookie
value at all to preserve the elevation retry contract; the fallback logic should
only apply when encrypted_value is empty, not when decryption errors occur.
In `@src-tauri/risuko-cookies/src/utils/time.rs`:
- Around line 18-19: The cast to u64 on the result of the timestamp calculation
does not validate that the value is finite before conversion, which allows NaN
and infinity values to be silently converted to 0 or u64::MAX, causing incorrect
cookie expiry handling. Before casting the unix timestamp value to u64, add a
check to ensure the floating point value is finite (not NaN, not positive
infinity, not negative infinity) and return None if the value is not finite,
similar to how the existing code validates timestamp <= 0.0.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f20e5025-6bd1-4e08-95ac-c76f4974e393
⛔ Files ignored due to path filters (1)
src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
src-tauri/risuko-cookies/src/browser/chromium.rssrc-tauri/risuko-cookies/src/browser/safari.rssrc-tauri/risuko-cookies/src/platform/linux.rssrc-tauri/risuko-cookies/src/utils/time.rs
💤 Files with no reviewable changes (1)
- src-tauri/risuko-cookies/src/browser/safari.rs
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src-tauri/risuko-cookies/src/browser/safari.rs">
<violation number="1" location="src-tauri/risuko-cookies/src/browser/safari.rs:69">
P3: Domain matching logic is duplicated instead of shared. Future fixes may diverge across browsers and produce inconsistent filtering.</violation>
<violation number="2" location="src-tauri/risuko-cookies/src/browser/safari.rs:92">
P1: Binarycookies header entries are page sizes, not absolute offsets. Treating them as offsets makes the parser jump to wrong positions and miss/fail cookie extraction.</violation>
<violation number="3" location="src-tauri/risuko-cookies/src/browser/safari.rs:108">
P1: The parser uses wrong endianness/layout for Safari page and cookie records. Decoded counts/offsets/expiry become garbage, causing out-of-bounds errors or incorrect cookie data.</violation>
</file>
<file name="src-tauri/risuko-cookies/src/platform/macos.rs">
<violation number="1" location="src-tauri/risuko-cookies/src/platform/macos.rs:29">
P1: macOS v10 cookie format parsed incorrectly: IV is not embedded after `v10`. This will fail/decode garbage for valid Chromium cookies.</violation>
</file>
<file name="src-tauri/risuko-cookies/Cargo.toml">
<violation number="1" location="src-tauri/risuko-cookies/Cargo.toml:31">
P1: New crypto dependency versions require a higher Rust MSRV than the project’s documented minimum. This can break builds for users/CI pinned to Rust 1.77.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Summary by cubic
Rebuilt browser cookie import with native, cross‑browser decryptors and migrated Rust logging to
tracing. Adds a Windows elevation flow for Chrome v20 app‑bound cookies, removes the nosleep plugin and unused Tauri capabilities, bumps Rust MSRV to 1.85, and updates deps.New Features
extract-cookiesfor elevated runs on Windows.import_browser_cookiesreturns "ELEVATION_REQUIRED" to trigger UAC retry on Windows.Refactors
logtotracingacross engine/BT/CLI/NAPI; removedtauri-plugin-nosleep-apiand related UI/command code; trimmed Tauri capabilities (dropped fs/process/os/notification).@tauri-apps/api2.11.1,@tauri-apps/cli2.11.3,@lucide/vue1.21.0,reka-ui2.10.0,@types/node26,suppaftp9; Rust MSRV -> 1.85.Written for commit ad9d09b. Summary will update on new commits.
Summary by CodeRabbit
extract-cookiessubcommand to output cookies by browser and URL, with Windows elevation flow when required.logtotracing.