Skip to content

Commit 9090ae1

Browse files
Merge pull request #11 from hasip-timurtas/feat/9-add-report-command-to-scan-generate-json-and-html-reports
feat: Implement open htlm report in browser by defaul
2 parents dd1e549 + 28c4399 commit 9090ae1

File tree

6 files changed

+205
-34
lines changed

6 files changed

+205
-34
lines changed

Cargo.lock

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ reqwest = { version = "0.12", features = ["json"] }
4949
toml = "0.8"
5050
# Directory utilities
5151
dirs = "6.0"
52+
# Cross-platform browser opening
53+
opener = "0.7"
5254

5355
[dev-dependencies]
5456
tempfile = "3.8"

README.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ A comprehensive security analysis tool for Solana smart contracts that helps dev
1414
- **Multiple Report Formats**: JSON, HTML, Markdown, and CSV outputs
1515
- **Plugin System**: Extensible architecture for custom security rules
1616
- **CI/CD Integration**: GitHub Actions support with automated security checks
17-
- **Professional Reports**: Beautiful HTML reports with severity rankings and actionable recommendations
17+
- **Professional Reports**: HTML reports with severity rankings and actionable recommendations
1818
- **Smart Error Handling**: Clear, colored error messages with proper path validation
1919
- **Comprehensive Examples**: 8 educational examples demonstrating vulnerabilities and secure patterns
2020

@@ -52,6 +52,9 @@ solsec scan ./my-program --html-only --output results.html
5252
# Generate multiple formats at once
5353
solsec scan ./my-program --format json,html,markdown,csv
5454

55+
# Don't open browser automatically
56+
solsec scan ./my-program --no-open
57+
5558
# Run fuzz testing
5659
solsec fuzz ./my-solana-program --timeout 300
5760
```
@@ -60,7 +63,9 @@ solsec fuzz ./my-solana-program --timeout 300
6063

6164
### `solsec scan`
6265

63-
Run static analysis on your Solana smart contracts. Generates both JSON and HTML If no path is provided, it recursively scans the current directory for all `.rs` files, automatically ignoring `target/` and `.git/` folders.
66+
Run static analysis on your Solana smart contracts. Generates both JSON and HTML by default. If no path is provided, it recursively scans the current directory for all `.rs` files, automatically ignoring `target/` and `.git/` folders.
67+
68+
HTML reports automatically open in the default browser when running interactively, but remain closed in CI/automation environments.
6469

6570
```bash
6671
solsec scan [PATH] [OPTIONS]
@@ -69,27 +74,31 @@ OPTIONS:
6974
-c, --config <FILE> Configuration file path
7075
-o, --output <DIR> Output directory [default: ./solsec-results]
7176
-f, --format <FORMATS> Output formats (comma-separated) [default: json,html] [possible values: json, html, markdown, csv]
72-
--json-only Only generate JSON (perfect for CI/CD)
73-
--html-only Only generate HTML (perfect for humans)
77+
--json-only Only generate JSON
78+
--html-only Only generate HTML
79+
--no-open Don't automatically open HTML report in browser
7480
--fail-on-critical Exit with non-zero code on critical issues [default: true]
7581
7682
EXAMPLES:
77-
# Scan the entire project (generates both JSON and HTML!)
83+
# Scan the entire project (generates both JSON and HTML)
7884
solsec scan
7985
8086
# Scan a specific directory with default formats
8187
solsec scan ./programs/my-program
8288
83-
# Generate only JSON for CI/CD integration
89+
# Generate only JSON for CI/CD integration
8490
solsec scan ./programs --json-only --output results.json
8591
8692
# Generate only HTML for manual review
8793
solsec scan ./programs --html-only --output results.html
8894
95+
# Generate HTML but don't open browser
96+
solsec scan ./programs --html-only --no-open --output results.html
97+
8998
# Generate all available formats
9099
solsec scan ./programs --format json,html,markdown,csv
91100

92-
# Legacy: Scan with configuration file
101+
# Scan with configuration file
93102
solsec scan ./programs --config solsec.toml --output ./security-results
94103
```
95104

@@ -299,17 +308,32 @@ rm -rf ./tmp-security-results
299308
echo "✅ Security scan passed!"
300309
```
301310

311+
## Browser Opening Behavior
312+
313+
HTML reports automatically open in the default browser under the following conditions:
314+
315+
**Opens automatically when:**
316+
- Running in an interactive terminal (not redirected)
317+
- Generating HTML reports (`--html-only` or default formats)
318+
- Not in CI/automation environments
319+
320+
**Remains closed when:**
321+
- Running in CI environments (GitHub Actions, GitLab CI, etc.)
322+
- Output is redirected or piped
323+
- Using `--no-open` flag
324+
- Only generating non-visual formats (JSON, CSV)
325+
302326
## 📊 Report Examples
303327

304328
### HTML Report
305-
Beautiful, interactive HTML reports with:
329+
Interactive HTML reports with:
306330
- Executive summary with issue counts by severity
307331
- Detailed findings with code snippets
308332
- Actionable recommendations
309333
- Responsive design for all devices
310334

311335
### JSON Report
312-
Machine-readable format perfect for:
336+
Machine-readable format for:
313337
- CI/CD pipeline integration
314338
- Custom tooling and analysis
315339
- Data processing and metrics
@@ -368,7 +392,7 @@ solsec scan examples/unchecked_account/vulnerable.rs # 6 issues found
368392
solsec scan examples/reentrancy/vulnerable.rs # 2 issues found
369393

370394
# Test secure examples (should find 0 issues)
371-
solsec scan examples/*/secure.rs # All pass!
395+
solsec scan examples/*/secure.rs # No issues found
372396

373397
# Comprehensive analysis
374398
solsec scan examples/ # 26 total issues across all vulnerable examples

src/cli.rs

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ use crate::fuzz::FuzzEngine;
88
use crate::plugin::{PluginAction, PluginManager};
99
use crate::report::{ReportFormat, ReportGenerator};
1010

11+
#[derive(Debug)]
12+
pub struct ScanConfig {
13+
pub path: PathBuf,
14+
pub config: Option<PathBuf>,
15+
pub output: PathBuf,
16+
pub formats: Vec<ReportFormat>,
17+
pub json_only: bool,
18+
pub html_only: bool,
19+
pub no_open: bool,
20+
pub fail_on_critical: bool,
21+
}
22+
1123
#[derive(Parser)]
1224
#[command(name = "solsec")]
1325
#[command(about = "Solana Smart Contract Security Toolkit")]
@@ -46,6 +58,10 @@ pub enum Commands {
4658
#[arg(long, conflicts_with = "format")]
4759
html_only: bool,
4860

61+
/// Don't automatically open HTML report in browser (opens by default in interactive mode)
62+
#[arg(long)]
63+
no_open: bool,
64+
4965
/// Fail with non-zero exit code on critical issues
5066
#[arg(long, default_value = "true")]
5167
fail_on_critical: bool,
@@ -81,29 +97,28 @@ pub enum Commands {
8197
},
8298
}
8399

84-
pub async fn handle_scan_command(
85-
path: PathBuf,
86-
config: Option<PathBuf>,
87-
output: PathBuf,
88-
formats: Vec<ReportFormat>,
89-
json_only: bool,
90-
html_only: bool,
91-
fail_on_critical: bool,
92-
) -> Result<()> {
93-
info!("Starting static analysis scan on: {}", path.display());
100+
pub async fn handle_scan_command(config: ScanConfig) -> Result<()> {
101+
info!(
102+
"Starting static analysis scan on: {}",
103+
config.path.display()
104+
);
94105

95-
let mut analyzer = StaticAnalyzer::new(config)?;
96-
let results = analyzer.analyze_path(&path).await?;
106+
let mut analyzer = StaticAnalyzer::new(config.config)?;
107+
let results = analyzer.analyze_path(&config.path).await?;
97108

98109
// Determine which formats to generate
99-
let formats_to_generate = if json_only {
110+
let formats_to_generate = if config.json_only {
100111
vec![ReportFormat::Json]
101-
} else if html_only {
112+
} else if config.html_only {
102113
vec![ReportFormat::Html]
103114
} else {
104-
formats
115+
config.formats
105116
};
106117

118+
// Check if we should open HTML before generating reports
119+
let should_open = should_open_html(&formats_to_generate, config.no_open);
120+
let mut html_file_path: Option<PathBuf> = None;
121+
107122
// Generate reports in all requested formats
108123
let report_gen = ReportGenerator::new();
109124
for format in formats_to_generate {
@@ -114,14 +129,19 @@ pub async fn handle_scan_command(
114129
ReportFormat::Csv => "csv",
115130
};
116131

117-
let output_file = if output.extension().is_some() {
132+
let output_file = if config.output.extension().is_some() {
118133
// If user provided a specific filename, respect it for the first format
119-
output.clone()
134+
config.output.clone()
120135
} else {
121136
// Generate appropriate filename based on format
122-
output.join(format!("security-report.{}", extension))
137+
config.output.join(format!("security-report.{}", extension))
123138
};
124139

140+
// Track HTML file path for opening later
141+
if matches!(format, ReportFormat::Html) {
142+
html_file_path = Some(output_file.clone());
143+
}
144+
125145
report_gen
126146
.generate_report(&results, &output_file, format.clone())
127147
.await?;
@@ -135,11 +155,18 @@ pub async fn handle_scan_command(
135155
critical_count, high_count
136156
);
137157

138-
if fail_on_critical && critical_count > 0 {
158+
if config.fail_on_critical && critical_count > 0 {
139159
error!("Critical issues found. Failing as requested.");
140160
std::process::exit(1);
141161
}
142162

163+
// Open HTML report in browser if appropriate
164+
if should_open {
165+
if let Some(html_path) = html_file_path {
166+
open_html_file(&html_path)?;
167+
}
168+
}
169+
143170
Ok(())
144171
}
145172

@@ -199,3 +226,65 @@ pub async fn handle_plugin_command(action: PluginAction, path: Option<PathBuf>)
199226

200227
Ok(())
201228
}
229+
230+
/// Detects if we're running in a CI environment
231+
fn is_ci_environment() -> bool {
232+
// Check common CI environment variables
233+
std::env::var("CI").is_ok()
234+
|| std::env::var("GITHUB_ACTIONS").is_ok()
235+
|| std::env::var("GITLAB_CI").is_ok()
236+
|| std::env::var("JENKINS_URL").is_ok()
237+
|| std::env::var("TRAVIS").is_ok()
238+
|| std::env::var("CIRCLECI").is_ok()
239+
|| std::env::var("BUILDKITE").is_ok()
240+
|| std::env::var("TF_BUILD").is_ok() // Azure DevOps
241+
}
242+
243+
/// Detects if we're in an interactive terminal session
244+
fn is_interactive() -> bool {
245+
// Check if stdout is a terminal and not redirected
246+
use std::io::IsTerminal;
247+
std::io::stdout().is_terminal()
248+
}
249+
250+
/// Determines if we should automatically open the HTML report
251+
fn should_open_html(formats: &[ReportFormat], no_open: bool) -> bool {
252+
// Don't open if user explicitly disabled it
253+
if no_open {
254+
return false;
255+
}
256+
257+
// Don't open in CI environments
258+
if is_ci_environment() {
259+
return false;
260+
}
261+
262+
// Don't open if not in interactive terminal
263+
if !is_interactive() {
264+
return false;
265+
}
266+
267+
// Only open if HTML is being generated
268+
formats.contains(&ReportFormat::Html)
269+
}
270+
271+
/// Opens the HTML file in the default browser
272+
fn open_html_file(file_path: &PathBuf) -> Result<()> {
273+
match opener::open(file_path) {
274+
Ok(()) => {
275+
info!(
276+
"📖 Opening security report in browser: {}",
277+
file_path.display()
278+
);
279+
Ok(())
280+
}
281+
Err(e) => {
282+
warn!(
283+
"Could not open HTML report in browser: {}. You can manually open: {}",
284+
e,
285+
file_path.display()
286+
);
287+
Ok(()) // Don't fail the entire command if browser opening fails
288+
}
289+
}
290+
}

0 commit comments

Comments
 (0)