# NetScaler Triage Offline forensic triage for Citrix NetScaler ADC and Gateway logs. Point it at a folder or archive to find suspicious requests, public indicators, encoded payloads, update records and related events across logs. Original inputs are not modified. Payloads are decoded as data, never executed or downloaded. A finding is an investigation lead, **not proof of compromise**. ## Quick start Requires **Python 3.14 or newer**. ```sh git clone https://tea.karlcom.de/karl/NetScaler_forensik.git cd NetScaler_forensik python3 -m venv .venv source .venv/bin/activate python -m pip install . netscaler-triage ./logs.7z ``` On Windows PowerShell, create and activate the environment with: ```powershell py -3.14 -m venv .venv .venv\Scripts\Activate.ps1 python -m pip install . netscaler-triage .\logs.7z ``` Installation may require internet access. Analysis runs offline. ### Update an existing installation Activate the same virtual environment used to run the tool, then run from the repository directory: ```sh git pull python -m pip install --upgrade . netscaler-triage rules ``` For an environment managed by uv, use `uv sync --locked` after `git pull`. Version 1.0.1 fixes installation failures reporting `invalid perl operator: (?=` or `SigmaRegularExpressionError` when loading the bundled rules. pySigma 2 changed its default validation engine to RE2, which rejects lookaheads. The package now requires compatible pySigma 1.x; detection patterns and timed matching are unchanged. Reinstalling with the command above also downgrades an incompatible pySigma installation. Rerun any scan that stopped with this error. ## Everyday use ```sh # Unpacked logs, an archive, or multiple appliance exports netscaler-triage ./logs netscaler-triage export.tar.gz netscaler-triage ./appliance-a ./appliance-b export.zip # Discover inputs in the current directory netscaler-triage # Encrypted archive: prompt for a password without displaying it netscaler-triage export.7z --password ``` ZIP (including ZipCrypto/AES), TAR, TAR.GZ/TGZ, GZIP and 7z inputs are supported, including nested archives within preparation limits. Unsupported formats are reported. `--password VALUE` is also supported, but can expose the password in shell history or process listings. Passwords are not stored in results. The tool prints the result directory. Use `--output PATH` to choose a new one; existing results are not overwritten. Appliance grouping is inferred from directories; review the preparation manifest for unusual export layouts. ## Read the results Start with these three files: ```text triage-results-.../ SUMMARY.txt Start here FINDINGS.csv Review matched events IOCS.csv Search candidates for your SIEM details/ Full evidence and machine-readable exports inputs/ Prepared input copies, when preparation was needed ``` | File | Purpose | | --- | --- | | `SUMMARY.txt` | Readable overview: coverage, counts, limitations and what to open next | | `FINDINGS.csv` | Only rule/indicator matches, with explanations and original source references | | `IOCS.csv` | SIEM search candidates; not an automatic blocklist | All technical exports are under `details/`: full events in `findings.jsonl`, URL/Base64 decoding, updates, correlations, checksums, coverage and the SQLite index. Prepared input copies, if needed, remain in `inputs/`. `details/findings.jsonl` includes context and processing errors as well as matches; use `FINDINGS.csv` to browse matched events. A match is not proof of execution. Original-file SHA-256 matches are counted separately in `SUMMARY.txt`; their evidence is in `details/file_hash_hits.json`, even when no log event matches. The CSV keeps authorized-test and reference statuses visible. Spreadsheet-sensitive cells are prefixed with an apostrophe; exact original values remain in JSON. This layout applies to CLI scans. Direct Python calls to `engine.scan` retain flat machine-readable exports. Failed or interrupted scans can also retain the flat layout; inspect their `summary.json` before using partial results. ## Add indicators and Sigma rules Put your packs in a directory and validate them before scanning: ```sh netscaler-triage rules --config ./my-rules netscaler-triage ./logs --config ./my-rules ``` Directories load Sigma YAML, JSON, CSV and TXT recursively. Repeat `--config` to combine packs. No Python changes are needed to add an IP, path or rule. For example, an IOC CSV can contain: ```csv type,value,status,source,description ipv4,192.0.2.42,reference_only,local-review,Investigate matching events path,/example/suspicious-file,reference_only,local-review,Check file context ``` These are synthetic examples. Use original, non-defanged values for matching. TXT accepts one value per line; unambiguous IP addresses, SHA-256 hashes, URLs and Unix paths are inferred. Other values remain literal strings. Sigma is parsed with pySigma and evaluated locally using the subset documented in [RULES.txt](RULES.txt). Unsupported features fail explicitly. Sigma metadata does not bypass detection conditions. Rule packs are data, not executable plugins. Defaults include 47 Sigma rules and 246 public reference indicators. Attribution and limitations are recorded in [THREAT_INTELLIGENCE.txt](THREAT_INTELLIGENCE.txt) and [config/threat_intel.json](config/threat_intel.json). This is a dated reference collection, not a promise of exhaustive intelligence or zero false positives. Keep confidential packs in `private/`, excluded from Git and release archives. Load them explicitly. Never put customer logs or generated findings in a public repository. Examples under [examples/](examples/) use synthetic data. ## Limits and interpretation - Exit `0`: configured processing completed; **not** a clean-system verdict. - Exit `2`: an error or incomplete processing; inspect `SUMMARY.txt` and `details/summary.json`. If the scan failed before presentation, inspect `summary.json` at the result root instead. - Missing years/timezones are not guessed. See time configuration below. - Binary appliance logs may need an external decoder. Decode failures can also come from ordinary text. - Damaged inputs and regex timeouts are reported. Archive path traversal, unsafe links and exhausted quotas stop preparation. - Processing uses one worker, incremental reads and disk-backed SQLite. There is no hard process-wide RAM/CPU quota or automatic resume. - Default limits: 2 GiB preparation budget, 100,000 archive entries, depth 3, 1 MiB per line and 512 MiB expanded per log file. Advanced options can raise limits. Tests were executed on macOS/ARM64. Windows and Linux instructions are provided, but those platforms have not yet been validated in the recorded test environment. ## Time configuration Use known device settings when logs omit their year or timezone: ```sh netscaler-triage scan --input ADC-A=./logs --year 2026 --timezone ADC-A=UTC ``` The appliance name, year and timezone above are examples. Explicit timestamps are retained; ambiguous daylight-saving times remain unresolved. Use `--time-config` for per-device settings and per-file year overrides; see [examples/time.json](examples/time.json). The effective assumptions are recorded in `details/configuration.json`. ## Help and development ```sh netscaler-triage --help netscaler-triage scan --help-all netscaler-triage decode payload.txt netscaler-triage inventory ./logs uv sync --locked uv run python -W error -m unittest discover -s tests uv run mypy uv run ruff check src tests typing_stubs uv run ruff format --check src tests typing_stubs uv build ``` See [RULES.txt](RULES.txt) for rule-pack semantics and [VALIDATION.txt](VALIDATION.txt) for test scope and known coverage limits. ## License Project code is licensed under the [MIT License](LICENSE.txt). Third-party dependencies and referenced external materials retain their own licenses. Source attribution does not imply affiliation with Citrix or the referenced researchers, and does not relicense their original publications.