Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,9 @@ Scans Rust source for ambient authority (filesystem, network, env, process, FFI)

```bash
cargo install cargo-capsec

# Or install from source
cargo install --path crates/cargo-capsec
```

### Adopt in 30 seconds
Expand Down
26 changes: 16 additions & 10 deletions crates/capsec-deep/src/main.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -346,17 +346,23 @@ impl rustc_driver::Callbacks for CapsecCallbacks {
eprintln!("[capsec-deep] Found {} findings in {crate_name}", findings.len());
}

// Write findings as JSONL
// Write findings as JSONL — buffer all lines and write in a single batch
// to avoid interleaving when cargo compiles multiple crates in parallel.
if let Ok(output_path) = std::env::var("CAPSEC_DEEP_OUTPUT") {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
let _ = writeln!(file, "{json}");
}
let mut batch = String::new();
for finding in &findings {
if let Ok(json) = serde_json::to_string(finding) {
batch.push_str(&json);
batch.push('\n');
}
}
if !batch.is_empty() {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&output_path)
{
let _ = file.write_all(batch.as_bytes());
}
}
} else if debug {
Expand Down
80 changes: 66 additions & 14 deletions crates/cargo-capsec/src/diff.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
let fs_read = cap_root.grant::<capsec_core::permission::FsRead>();
let spawn_cap = cap_root.grant::<capsec_core::permission::Spawn>();

let left = parse_crate_spec_or_latest(&opts.left);
let right = parse_crate_spec_or_latest(&opts.right);
let mut left = parse_crate_spec_or_latest(&opts.left);
let mut right = parse_crate_spec_or_latest(&opts.right);

eprintln!("Fetching {} v{}...", left.name, left.version);
eprintln!("Fetching {}...", left.name);
let left_dir = fetch_crate_source(&left.name, &left.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut left, &left_dir);

eprintln!("Fetching {} v{}...", right.name, right.version);
eprintln!("Fetching {}...", right.name);
let right_dir = fetch_crate_source(&right.name, &right.version, &spawn_cap, &fs_read)
.unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
resolve_version_from_path(&mut right, &right_dir);

eprintln!("Scanning...\n");
let config = Config::default();
Expand DownExpand Up@@ -146,8 +148,13 @@ fn fetch_crate_source(
let temp_dir = std::env::temp_dir().join(format!("capsec-fetch-{crate_name}-{version}"));
let _ = std::fs::create_dir_all(&temp_dir);

let version_spec = if version == "*" {
format!("\"{version}\"")
} else {
format!("\"={version}\"")
};
let cargo_toml = format!(
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = \"={version}\"\n"
"[package]\nname = \"capsec-fetch-temp\"\nversion = \"0.0.1\"\nedition = \"2021\"\n\n[dependencies]\n{crate_name} = {version_spec}\n"
);
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| format!("Failed to write temp Cargo.toml: {e}"))?;
Expand DownExpand Up@@ -180,6 +187,7 @@ fn fetch_crate_source(
}

/// Looks for a crate's source in ~/.cargo/registry/src/.
/// When version is "*", finds the latest version available in the cache.
fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
let home = std::env::var("CARGO_HOME").unwrap_or_else(|_| {
std::env::var("HOME")
Expand All@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
return None;
}

// Iterate index directories (index.crates.io-HASH/)
let entries = std::fs::read_dir(&registry_src).ok()?;
for entry in entries.flatten() {
let crate_dir = entry.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
// Some crates have src/ subdir, some don't
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
for index_dir in entries.flatten() {
if version == "*" {
// Find any version of this crate — pick the latest by name sort
let prefix = format!("{crate_name}-");
if let Ok(crate_dirs) = std::fs::read_dir(index_dir.path()) {
let mut matches: Vec<_> = crate_dirs
.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
matches.sort_by_key(|b| std::cmp::Reverse(b.file_name()));
if let Some(best) = matches.first() {
let src_dir = best.path().join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(best.path());
}
}
} else {
let crate_dir = index_dir.path().join(format!("{crate_name}-{version}"));
if crate_dir.exists() {
let src_dir = crate_dir.join("src");
if src_dir.exists() {
return Some(src_dir);
}
return Some(crate_dir);
}
return Some(crate_dir);
}
}

Expand DownExpand Up@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {

// ── Parsers ──

/// If version is "*", resolves it from the fetched directory path.
/// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
fn resolve_version_from_path(spec: &mut CrateSpec, dir: &Path) {
if spec.version != "*" {
return;
}
// Walk up from src/ to the crate dir: ureq-2.12.1
let crate_dir = if dir.ends_with("src") {
dir.parent()
} else {
Some(dir)
};
if let Some(dir_name) = crate_dir
.and_then(|d| d.file_name())
.and_then(|n| n.to_str())
{
let prefix = format!("{}-", spec.name);
if let Some(ver) = dir_name.strip_prefix(&prefix) {
spec.version = ver.to_string();
}
}
}

/// Parses "serde_json@1.0.133" into CrateSpec.
fn parse_crate_spec(spec: &str) -> Result<CrateSpec, String> {
let parts: Vec<&str> = spec.splitn(2, '@').collect();
Expand Down
Loading