Chronos-DFIR provides forensic-grade exports in multiple formats while preserving data integrity. All exports respect active filters (time range, global search, column filters, row selection) and maintain original values without auto-conversion.
Chronos preserves hex values like 0x00000030 that Excel would normally convert to decimal:
# From app.py:1150-1210 - CSV Export with Hex Protectiondef _export_csv_with_hex_protection(lf: pl.LazyFrame, out_path: str): """ Export CSV with BOM + formula wrapping for hex columns to prevent Excel auto-conversion (0x00000030 → 30). """ # Detect hex-prone columns schema = lf.collect_schema() hex_candidates = [] for col in schema.names(): col_lower = col.lower() if any(kw in col_lower for kw in ['hash', 'guid', 'sid', 'address', 'offset', 'mask']): hex_candidates.append(col) df = lf.collect() # Wrap hex values in Excel formula: ="0x..." for col in hex_candidates: if col in df.columns: df = df.with_columns( pl.when(pl.col(col).cast(pl.Utf8).str.starts_with("0x")) .then(pl.concat_str([pl.lit('="'), pl.col(col), pl.lit('"')])) .otherwise(pl.col(col)) .alias(col) ) # Write with UTF-8 BOM for Excel recognition with open(out_path, 'wb') as f: f.write('\ufeff'.encode('utf-8')) # BOM df.write_csv(out_path, quote_style="always", mode='a')
Critical for Forensics: Without hex protection, values like memory addresses (0x00401000), registry SIDs, and hash prefixes get corrupted when opened in Excel. This violates chain of custody.
All XLSX exports use xlsxwriter with explicit string formatting:
# From app.py:1212-1280 - XLSX Exportimport xlsxwriterworkbook = xlsxwriter.Workbook(out_path, { 'strings_to_numbers': False, # Never auto-convert 'nan_inf_to_errors': True})worksheet = workbook.add_worksheet()# Define text format (prevents conversion)text_format = workbook.add_format({ 'num_format': '@', # Text format 'valign': 'top'})# Write headersheader_format = workbook.add_format({ 'bold': True, 'bg_color': '#f1f5f9', 'border': 1})for col_idx, col_name in enumerate(df.columns): worksheet.write_string(0, col_idx, col_name, header_format)# Write ALL cells as strings (not worksheet.write() which auto-converts)for row_idx, row in enumerate(df.iter_rows(), start=1): for col_idx, val in enumerate(row): worksheet.write_string(row_idx, col_idx, str(val) if val else "", text_format)workbook.close()
Performance Impact: Writing every cell as a string is slower than generic write() but is the only way to guarantee forensic integrity. For 100K rows × 50 columns, export takes ~15 seconds.
All exports respect the composition of all active filters:
1
Global Search
Text search across all columns (debounced 500ms)
if query and query.strip(): search_exprs = [ pl.col(c).cast(pl.Utf8, strict=False) .str.contains(query, literal=True) for c in lf.collect_schema().names() ] lf = lf.filter(pl.any_horizontal(search_exprs))
2
Column Filters
Header-level filters (exact match, range, regex)
if col_filters: for field, flt in col_filters.items(): if flt['type'] == 'like': lf = lf.filter( pl.col(field).cast(pl.Utf8) .str.contains(flt['value'], literal=True) ) elif flt['type'] == '>': lf = lf.filter(pl.col(field).cast(pl.Float64) > flt['value'])
3
Time Range
Start/end time boundaries
if start_time and start_time != "null": lf = lf.filter( pl.col(time_col) >= pl.lit(start_time).str.to_datetime(strict=False) )if end_time and end_time != "null": lf = lf.filter( pl.col(time_col) <= pl.lit(end_time).str.to_datetime(strict=False) )
4
Row Selection
Manual checkbox selection (persistent across pagination)
if selected_ids and len(selected_ids) > 0: lf = lf.filter(pl.col("_id").is_in(selected_ids)) # Renumber to sequential 1,2,3... for export lf = lf.drop("_id").with_row_index(name="_id", offset=1)
5
Visible Columns
Only export columns shown in grid
if visible_columns: target_cols = [pl.col(c) for c in visible_columns if c in lf.collect_schema().names()] lf = lf.select(target_cols)
Single Query Execution: All filters are composed into a single Polars LazyFrame query and executed once during export. No intermediate materializations.
PDF Generation: The PDF export uses the same HTML template with server-side rendering via WeasyPrint. Ensure weasyprint>=68.0 is installed: pip install weasyprint