Configuration reference
This page describes current defaults, loading semantics, and limits. See the complete example, operations guide, and command reference.
Loading and applying configuration
- Configuration is JSON: no comments, trailing commas, or environment-variable expansion. Durations are strings such as
"30s","10m", and"24h";"1d"is invalid. - The program loads defaults, then overlays the file. Omitted top-level fields retain defaults. An explicit
rulesarray replaces the default rules; individual array entries do not inherit the default SSH template. - A custom rule without
count_by_usertherefore usesfalse; withoutreset_patterns, it has no successful-login reset. A rule fragment is not a complete rule. - Missing files, unknown fields, malformed regexes, invalid ports and duplicate rule names are errors. Run
config-checkbefore restarting. config-checkvalidates without scanning, changing the firewall or sending messages.- The daemon reads configuration at startup, with no hot reload. Restart it after changes and use the same config path for administrative commands.
- Installers call the platform binary’s
init-config; existing files remain unchanged.
Top-level fields
| Field | Default | Behavior |
|---|---|---|
interval |
"30s" |
Polling interval; one cycle runs immediately at startup. Nonpositive values restore the default |
audit_interval |
"1h" |
Audit eligibility checked during scan cycles, not by an independent timer |
state_path |
Platform state directory + state.json |
Log offsets, hit timestamps, bans, backends, cooldowns, and last audit |
dry_run |
true |
Neither applies nor releases real firewall bans; still writes state/logs and sends notifications. Not a global read-only switch |
start_at_end |
true |
First text read starts at EOF; first Windows query records the newest event ID without replay |
ignore_ips |
["127.0.0.1", "::1"] |
IPv4, IPv6, or CIDR, not hostnames. An explicit array replaces this list |
rules |
One SSH authentication rule | [] stops rule scanning, but does not disable audits or immediately remove existing firewall entries |
A process lock rejects concurrent daemons or test using the same state path. Keep independent state and queue paths for replay tests.
Rule fields
| Field | Default SSH rule / normalized fallback | Behavior |
|---|---|---|
name |
ssh-auth-failure; empty becomes rule |
Part of state identity; use unique names without ` |
log_paths |
Platform authentication logs | Linux chooses auth.log if present, otherwise secure; the example lists both. Missing files are skipped |
patterns |
One explicit password-failure expression | Go regexp; one <HOST> or named IP group; optional username group |
reset_patterns |
Accepted ... from <HOST> |
Processed first; clears all username hit counters for that IP within this rule, without removing existing bans |
max_attempts |
5 |
Triggers at the threshold; counts matching log events, not exactly typed passwords |
find_time |
"10m" |
Window uses observation time, not historical timestamps parsed from log text |
ban_time |
"1h" |
Ban expiry, or notification cooldown per counting key in dry-run / notify mode |
action |
"auto" |
Automatic backend selection; "notify" only notifies; other strings are executable names invoked with the IP |
count_by_user |
true in the default rule |
Rule+IP+username if captured, otherwise rule+IP. Enforcement remains IP-based |
region_rules.max_attempts |
None | Optional regional overrides; new installs use the same threshold worldwide. Existing explicit overrides are retained |
Nonpositive attempts or durations restore defaults; they do not disable bans. Empty region names and nonpositive regional thresholds are removed.
Custom action values are executable names invoked with one IP argument, not shell commands or backend names. Use auto for managed rules. Top-level ssh_ports defaults to [22]; set [22,2222] explicitly when both are SSH ports. It does not change sshd ports.
Region matching is case-insensitive and supports partial country/region/city names. Multiple matches deterministically choose the highest threshold to reduce false bans. The database is IPv4-only; IPv6 detection works without GeoIP. Prefer explicit office whitelists.
Password-only detection
Replace the existing SSH rule's patterns, retaining all its other fields, especially reset_patterns, thresholds, and count_by_user:
{
"patterns": ["Failed password for(?: invalid user)? (?P<user>\\S+) from <HOST>"]
}
This is a rule fragment, not a full configuration. It does not count new connections per minute. sshd's MaxAuthTries limits authentication attempts within one connection. Shared office addresses still need ignore_ips; see operations.
SSH baseline
Fields below belong to hardening.ssh. They affect secure-ssh; starting the daemon does not automatically rewrite sshd configuration.
| Field | Default | Behavior |
|---|---|---|
enabled |
true |
Enables baseline preview/application |
password_authentication |
true |
Password authentication supported |
permit_root_login |
"yes" |
Root login supported; an empty string renders as no |
allowed_users |
[] |
Nonempty lists generate AllowUsers; writing requires a list or explicit -force |
max_auth_tries |
3 |
Per-connection authentication attempts |
login_grace_time |
"20s" |
Authentication grace period |
client_alive_interval |
300 |
Server probe interval in seconds |
client_alive_count_max |
2 |
Unanswered probes |
disable_empty_passwords |
true |
Writes PermitEmptyPasswords no |
disable_challenge_response |
true |
Disables keyboard-interactive / challenge-response; adjust if those methods are required |
keepalive is independent: it does not read this JSON and has no -config option. Its preview includes password/root-login settings and keepalive values, written to a different location from secure-ssh. Compare previews and verify effective sshd configuration after applying either command.
Logging, GeoIP, and malware scanning
| Field | Default | Behavior |
|---|---|---|
logging.enabled |
true |
Application log output |
logging.path |
State directory + banhack233.log |
Bans, audits, errors; not an SSH authentication-log input |
logging.max_size_mb |
10 |
Rotation size per file |
logging.max_age_days |
30 |
Retention age for rotated logs |
geoip.enabled |
true |
Regional thresholds and notification enrichment |
geoip.db_path |
State directory + ip2region_v4.xdb |
Missing database removes location detail, not base-threshold detection |
malware.enabled |
true |
Includes scanning in Linux audits; manual malware-scan still scans |
malware.direct_kill |
false |
Only cleanup switch; manual scans only, restricted intrusion executable names; audits always read-only |
malware.report_dir |
State directory + reports |
Manual reports; do not mix unrelated .txt files here |
malware.report_keep |
50 |
After writing a report, retain the newest N .txt files by modification time; nonpositive values restore 50 |
This is a lightweight Linux host antivirus / intrusion-triage helper, not a full commercial antivirus or EDR. Manual reports use {name}.yyyy-MM-dd_HH-mm-ss.txt. The LRU-like count policy keeps recently modified files; reading does not refresh retention. Automatic audits write application logs, not a separate manual report archive.
Notifications
All fields below belong to notifications. Channel-specific examples remain in the README.
| Field | Default | Behavior |
|---|---|---|
language |
"zh-CN" |
Built-in text: en or zh-CN; applies to all channels and retries |
timezone |
"Local" |
Local, UTC or an IANA zone; does not change update scheduling or the host clock |
Channel language |
Empty | Telegram/webhook/email targets inherit global language; en or zh-CN overrides it |
console |
true |
Notification output to stdout |
audit |
false |
Sends audit findings; does not enable/disable audit execution |
batch.enabled |
true |
Batches ban events; notify/audit events enter the durable queue directly |
batch.interval |
"60s" |
Measured from the first pending event; an independent worker checks once per second |
batch.max_items |
20 |
Sends when pending count reaches the limit |
feishu / discord / slack |
enabled=false |
enabled, url, location_language; Feishu/Lark can use a signing secret |
webhooks[] |
Empty | name, enabled, url, format, headers, secret, location_language |
webhooks[].format |
Empty means plain text | text, json, feishu, lark, discord, slack |
email.enabled |
false |
SMTP delivery |
email.from / to |
Empty | Sender; comma-separated recipients |
email.password |
Empty | SMTP authorization code or applicable credential |
email.username |
Empty | SMTP login; empty uses from |
email.password_env |
Empty | Named environment variable; takes precedence and must exist in the sending process |
email.smtp_host / smtp_port |
Empty / 0 |
Infers only missing values; TLS 465 or mandatory STARTTLS for other ports |
email.location_language |
Empty | Location display language, not full-message translation |
Notifications use a bounded durable queue and independent channels with 10-second deadlines. Telegram/Feishu business errors are checked; failed channels retry without resending confirmed channels. See delivery guarantees and setup.
Locale and separate SMTP-credential fields require v0.2.3+. Invalid languages/time zones fail validation. Omitted fields keep zh-CN / Local. For new files use init-config -language en -timezone UTC; see the English Linux example. SMTP PLAIN/TLS is supported; OAuth2 is not. See provider limits.
Production shortcut changes
enable-production changes only dry_run=false; all other preferences remain. Restart the service afterward.
Existing rules are not silently migrated by upgrades. Use safe-ssh to preview and safe-ssh -write to migrate the named default rule.
Source: configuration, scanner, CLI.
New safety and delivery fields
ssh_ports: destination TCP ports, default[22](Linux/Windows); match actual SSH listeners. PF remains administrator-managed.- Rule
success_grace: default template"10m"; omitted in a custom rule means"0s". A successful login protects the source IP across usernames in that rule. The current batch is protected even with zero grace. It does not release existing bans. notifications.queue_path: defaults tostate_path + ".notifications.json"; private writable file, distinct from state.notifications.telegram:enabled,bot_tokenorbot_token_env,chat_id, optionalmessage_thread_idanddisable_notification.bot_token_envand emailpassword_envread named environment variables; JSON is not generally expanded.