/** * Audit Tool * * Specialized read-only audit primitives that collect evidence for * server-auditor / server-deployer agents in one round-trip each. * * - ssh_audit_baseline system/disk/mem/net/ssh/services/docker/firewall/updates * - ssh_tls_check TLS expiry + SAN match + chain + renew_hook for a domain * - ssh_disk_breakdown top consumers (du, docker, journald, caches) * - ssh_service_status systemctl + journalctl tail in one call * * Output is structured (section headers + JSON in the text, and the same JSON * as structuredContent where an output schema is declared), so agents can * consume it without N follow-up ssh_exec calls. */ import { CallToolRequest, Tool } from '@modelcontextprotocol/sdk/types.js'; import { type ToolResult } from '../utils/tool-result.js'; export declare class AuditTool { /** Report section names — also the list of values `include` accepts */ private static readonly BASELINE_SECTIONS; /** Sections whose commands answer nothing to anyone but root */ private static readonly SUDO_SECTIONS; private executor; constructor(); getTools(): Tool[]; handleCall(request: CallToolRequest, signal?: AbortSignal): Promise; private handleBaseline; private splitSections; private buildBaselineResult; private parseDf; /** * Firewall state from the section command's markers. * * An empty `iptables` rule list also means "not checked": even on a machine * without a single rule, the command prints three chain headers. */ private parseFirewall; /** * Memory figures by the header's column names. * * `free` from procps older than 2014 has no `available` column at all, and * `cached` comes last instead: taken by position, it would pass cache off * as free memory — twice as much as there actually is. */ private parseFree; /** * Parsing the list of listening sockets. * * There are two sources, and their columns differ: for `ss` the local * address is fifth, for `netstat` — fourth, with two more header lines * before it. So instead of a column number, we look for the first address * shaped like `host:port` with a numeric port: for both commands that is * exactly the socket being listened on. This also filters out netstat's * headers — left unfiltered, they would show up in the report as entries * like `{ proto: "Proto", address: "Address" }`. */ private parseListeners; /** * Ports sshd itself listens on. * * The process name sits at the tail of the line for both `ss` and * `netstat`; the port is taken after the last colon, otherwise `[::]:2222` * would read differently than `0.0.0.0:2222`. */ private static sshdListenerPorts; /** * Ports published by containers in the nat table. * * An empty chain answers with a single `-N DOCKER` line; publish rules * arrive as `-A DOCKER … --dport N -j DNAT` lines. */ private static dockerPublishedPorts; private static formatUfw; private static formatIptables; private formatBaseline; private handleTlsCheck; private handleDiskBreakdown; /** * The same sections as fields. * * A section with nothing in it is named in `unavailable` rather than left * empty: `du` says nothing both about a directory that is empty and about * one it was not allowed to read, and the difference is not ours to guess. */ private buildDiskBreakdownResult; private handleServiceStatus; /** * What exactly the services section had nothing to run with — or nothing, * if it ran fine. * * Two different "no"s do not merge: the binary is missing entirely, versus * the binary is there but systemd does not answer. An empty answer does not * count as a reason — it is the legitimate "no service is running". */ private static servicesUnavailable; /** Answers by which systemd reports that it is not there at all */ private static readonly NO_SYSTEMD; /** The answer by which systemd reports that no such service exists */ private static readonly NO_UNIT; private parseShowProps; }