Collectors

Modular Evidence Gatherers

Collectors are pluggable modules that gather specific evidence from remote systems. Each collector implements a common interface with get_command(), parse_output(), and compare() methods.

PortCollector

Scans for listening TCP/UDP ports using ss.

Baseline Configuration

collectors:
  ports:
    allowed: [22, 443, 80]
    forbidden: [21, 23, 3389, 5900]

Evidence Collected

  • List of all listening ports
  • Protocol (TCP/UDP)
  • Binding address

Comparison Logic

  • Pass: Only allowed ports are open
  • Fail: Forbidden port detected or unexpected port open

UserCollector

Enumerates system users from /etc/passwd.

Baseline Configuration

collectors:
  users:
    allowed:
      - root
      - www-data
      - sysadmin
    forbidden:
      - guest
      - test
      - temp

Evidence Collected

  • Username
  • UID/GID
  • Shell
  • Home directory

FileCollector

Checks file existence and integrity via SHA-256 hashes.

Baseline Configuration

collectors:
  files:
    - path: "/etc/ssh/sshd_config"
      description: "SSH daemon config"
      sha256: "a1b2c3..."  # Expected hash
    - path: "/etc/sudoers"
      sha256: null  # Just check existence

Evidence Collected

  • File existence
  • SHA-256 hash
  • Permission mode
  • Owner/group

Comparison Logic

  • Pass: File exists and hash matches (if specified)
  • Fail: File missing, hash mismatch, or permission drift

Use --show-diffs to see actual file changes when hashes don't match.

ProcessCollector

Lists running processes using ps.

Baseline Configuration

collectors:
  processes:
    allowed:
      - sshd
      - systemd
      - cron
    forbidden:
      - telnetd
      - rshd
      - fingerd

Evidence Collected

  • Process name
  • PID
  • Running user

Collector Interface

All collectors implement this abstract interface:

class Collector(ABC):
    name: str = "base"
    description: str = "Base collector"

    @abstractmethod
    def get_command(self) -> str:
        """Return shell command to execute."""
        pass

    @abstractmethod
    def parse_output(self, raw_output: str) -> dict:
        """Parse raw command output into structured data."""
        pass

    @abstractmethod
    def compare(self, actual: dict, expected: dict) -> dict:
        """Compare actual vs expected, return findings."""
        pass