DEFENSIVE SECURITY / DETECTION ENGINEERING / OPEN SOURCE
I investigate attacker behavior, build detections, and improve the tools that help defenders make better decisions.
Cybersecurity Engineer · Threat Hunting · DFIR · Security Engineering
| 01 / Detection | 02 / Investigation | 03 / Engineering |
|---|---|---|
| Detection logic, Sigma rules, telemetry quality and ATT&CK mapping. | Threat hunting, endpoint evidence, incident investigation and forensic workflows. | Reliable security tooling, tests, safer defaults and maintainable automation. |
I care about the distance between an event being collected and an analyst being able to act on it. Most of my work sits in that gap.
A few shipped fixes that illustrate the kinds of problems I work on. Each link goes to the actual accepted pull request.
| Focus | Contribution and why it matters |
|---|---|
| Detection fidelity | pySigma #585 — respect the rule's regex policy instead of silently forcing RE2. |
| Query correctness | Splunk Sigma backend #77 — correct Boolean precedence that could broaden generated SPL/SPL2 searches. |
| Threat-hunting coverage | Sysmon Modular #227 — correctly map WMI permanent subscriptions to MITRE ATT&CK T1546.003. |
| Forensic tooling | Volatility 3 #2035 — replace misleading Windows plugin crashes with actionable errors. |
| Security control lifecycle | Firewalla #9954 — ensure domain rules with IP TTL settings are properly removed. |
| Operational reliability | Yeti #1408 — fix dependency installation that prevented scheduled feed tests from running. |
| Endpoint workflow | Velociraptor #5086 — repair the VQL shell's Repeat action. |
Also contributing across Firewalla, DefectDojo, Nuxt UI, pySigma, and other maintained upstream projects.
62 accepted pull requests across 51 independent upstream repositories.
Every PR below is merged into the upstream project, not merely submitted from a fork.
Explore the complete contribution log — 62 merged PRs / 51 repositories
| Upstream | Latest merged change | Accepted PRs |
|---|---|---|
| SigmaHQ/pySigma | fix: compile |re values with the rule's policy | #585 · #545 · #544 · #542 · #541 |
| firewalla/firewalla | Unenforce the per-rule sets of a global domain rule with ipttl | #9954 · #9953 · #9910 · #9909 |
| Velocidex/velociraptor | Fix the repeat button in the VQL shell | #5086 · #5047 · #5016 |
| dgunning/edgartools | fix(search): drop an amendment form code whose root form is also listed | #1545 · #1533 |
| posit-dev/pointblank | fix: serialize non-string values in the duckdb get_dataframe_report() | #445 · #444 |
| DefectDojo/django-DefectDojo | fix(govulncheck): don't crash on OSV entries without aliases | #16014 |
| Chocobozzz/PeerTube | Constrain PyAV when installing whisper-ctranslate2 | #7815 |
| nuxt/ui | perf(useComponentProps): skip prop resolution for Vue internal flags | #7186 |
| HKUDS/nanobot | fix(cli): set SPT_NOENV only on Linux | #6232 |
| boa-dev/boa | fix(engine): accept lone surrogate escapes in JSON.parse | #5578 |
| Flexget/Flexget | Read the transmission-rpc version once at import | #5263 |
| career-ops-hq/career-ops | fix(batch): give node a Windows path for PROJECT_DIR under Git Bash | #5051 |
| atuinsh/atuin | fix(config): let ATUIN_* environment variables override config.toml | #4438 |
| orval-labs/orval | fix(core): do not alias self-referencing schemas in single schemas mode | #4298 |
| Kometa-Team/Kometa | Report failed Apprise notifications with Apprise 2 | #3690 |
| eternnoir/pyTelegramBotAPI | Treat RichText strings as JSON only when they parse to an object | #2635 |
| sgl-project/sglang-omni | [ASR] Probe audio duration with PyAV 19 | #2557 |
| yomidevs/yomitan | Add CJK Unified Ideographs Extension J to the CJK ranges | #2504 |
| volatilityfoundation/volatility3 | Windows: Raise TypeError instead of a bare raise in virtmap and crashinfo | #2035 |
| Kometa-Team/Quickstart | Skip no-commit-to-branch in the Lint workflow | #1878 |
| harry0703/MoneyPrinterTurbo | Constrain PyAV below 19 in requirements.txt | #1582 |
| ipython/ipykernel | Update README install command for the test dependency group | #1573 |
| yeti-platform/yeti | Install the plugins dependency group in the feed tests workflow | #1408 |
| kucherenko/jscpd | fix(semantic): count a JSX element of a component as a call | #1211 |
| nornir-automation/nornir | Deprecate legacy Jinja2 rendering helpers | #1103 |
| open-jarvis/OpenJarvis | fix(speech): constrain PyAV below 19 for faster-whisper | #1094 |
| datajuicer/data-juicer | fix(mapper): use the Lexbor parser in clean_html_mapper | #1085 |
| marcosschroh/dataclasses-avroschema | fix: compare union defaults with dataclasses.MISSING (Python 3.15) | #984 |
| turbot/steampipe-mod-aws-compliance | Fix port ranges in vpc_security_group_restricted_common_ports query | #978 |
| florianfesti/boxes | TrayLayout: accept the escaped layout from the SVG command line | #977 |
| coin-or/pulp | Make pulptest work from an installed package | #953 |
| huggingface/speech-to-speech | Add an opt-in --torch_num_threads setting for PyTorch CPU threads | #707 |
| pyxem/orix | Support Matplotlib 3.12 ArtistList location | #688 |
| callowayproject/bump-my-version | Keep the repository root when HEAD is detached | #439 |
| kaixxx/noScribe | Pin PyAV below 19 until faster-whisper supports it | #367 |
| lingui/swc-plugin | fix: transform a useLingui t nested in a placeholder of another t |
#285 |
| olafhartong/sysmon-modular | Map WMI event subscription events (EID 19/20/21) to T1546.003 | #227 |
| Softcatala/whisper-ctranslate2 | Constrain PyAV below 19 in the package requirements | #154 |
| unitaryai/detoxify | Drop pytest-flake8 from test requirements to fix CI on pytest 9 | #137 |
| OHF-Voice/wyoming-faster-whisper | Constrain PyAV below 19 until faster-whisper supports it | #130 |
| benmoran56/esper | Fix _get_components() dropping a filter type when the min-size set changes twice | #124 |
| NetroScript/Graveyard-Keeper-Savefile-Editor | Read the Eel version with importlib.metadata (fixes #106) | #107 |
| oreilles/polars-st | Fix import on Polars 2 (GeoSeries metaclass conflict) | #85 |
| SigmaHQ/pySigma-backend-splunk | fix: OR/AND precedence inversion widens emitted queries in SPL and SPL2 backends | #77 |
| JFryy/qq | Stringify non-string YAML mapping keys (fixes #73) | #74 |
| logpoint/pySigma-backend-logpoint | Fix AttributeError for numeric pipeline pre-filter values | #38 |
| SigmaHQ/pySigma-backend-crowdstrike | fix: OR nested inside AND was not parenthesized, widening the emitted query | #25 |
| arxhr007/Aliens_eye | Use the Lexbor parser of selectolax (fix ImportError with selectolax 1.0) | #23 |
| Oldcircle/geo-sleuth | fonts: find CJK fonts on Windows, allow a GEO_FONT override | #13 |
| wayne-csrs/pySigma-backend-signals | fix: keep correlation event_type unquoted with pySigma >= 1.5.1 | #9 |
| bacescuandrei/macos-inspector | Remove unused imports and variables | #1 |
Maintained automatically from verified GitHub merges. Open and closed-unmerged PRs are excluded.
View the complete upstream history ↗
Yahoo IMAP Scanner / Local Mail Guardian ↗
A local-first phishing and spam analysis tool with explainable detection signals, a CLI and local web interface. Built around read-only scanning by default, explicit approval for mailbox changes, and privacy-conscious reporting.
- Reproduce before patching. Start with the observed failure, then write a focused test that would fail without the fix.
- Prefer precise changes. Small patches are useful when they resolve a real failure; complexity is not a measure of impact.
- Preserve security boundaries. Favor understandable defaults, testable behavior, and clear failure handling.
- Leave evidence. Link issues, tests, review discussions, and upstream acceptance rather than relying on self-reported metrics.
Defensive security, grounded in evidence.
