11import { execSync , spawnSync } from 'node:child_process' ;
2+ import { isIP } from 'node:net' ;
23
34export interface FirewallAdapter {
45 name : string ;
@@ -9,6 +10,12 @@ export interface FirewallAdapter {
910 listBlocked ( ) : Promise < string [ ] > ;
1011}
1112
13+ function assertValidFirewallIp ( ip : string ) : void {
14+ if ( isIP ( ip ) !== 4 ) {
15+ throw new Error ( `Invalid IPv4 address: ${ ip } ` ) ;
16+ }
17+ }
18+
1219export class NftablesAdapter implements FirewallAdapter {
1320 name = 'nftables' ;
1421 private table = 'threatcrush' ;
@@ -31,17 +38,20 @@ export class NftablesAdapter implements FirewallAdapter {
3138 }
3239
3340 async block ( ip : string ) : Promise < void > {
41+ assertValidFirewallIp ( ip ) ;
3442 this . ensureSetup ( ) ;
3543 execSync ( `nft add element inet ${ this . table } ${ this . set } '{ ${ ip } }'` ) ;
3644 }
3745
3846 async unblock ( ip : string ) : Promise < void > {
47+ assertValidFirewallIp ( ip ) ;
3948 try {
4049 execSync ( `nft delete element inet ${ this . table } ${ this . set } '{ ${ ip } }'` ) ;
4150 } catch { /* element may not exist */ }
4251 }
4352
4453 async isBlocked ( ip : string ) : Promise < boolean > {
54+ assertValidFirewallIp ( ip ) ;
4555 try {
4656 const output = execSync ( `nft list set inet ${ this . table } ${ this . set } ` , { encoding : 'utf-8' } ) ;
4757 return output . includes ( ip ) ;
@@ -77,17 +87,20 @@ export class IptablesAdapter implements FirewallAdapter {
7787 }
7888
7989 async block ( ip : string ) : Promise < void > {
90+ assertValidFirewallIp ( ip ) ;
8091 this . ensureChain ( ) ;
8192 if ( await this . isBlocked ( ip ) ) return ;
8293 execSync ( `iptables -A ${ this . chain } -s ${ ip } -j DROP` ) ;
8394 }
8495
8596 async unblock ( ip : string ) : Promise < void > {
97+ assertValidFirewallIp ( ip ) ;
8698 try { execSync ( `iptables -D ${ this . chain } -s ${ ip } -j DROP` ) ; }
8799 catch { /* rule may not exist */ }
88100 }
89101
90102 async isBlocked ( ip : string ) : Promise < boolean > {
103+ assertValidFirewallIp ( ip ) ;
91104 try {
92105 const output = execSync ( `iptables -n -L ${ this . chain } ` , { encoding : 'utf-8' } ) ;
93106 return output . includes ( ip ) ;
@@ -112,9 +125,9 @@ export class DryRunAdapter implements FirewallAdapter {
112125 private blocked = new Set < string > ( ) ;
113126
114127 isAvailable ( ) : boolean { return true ; }
115- async block ( ip : string ) : Promise < void > { this . blocked . add ( ip ) ; }
116- async unblock ( ip : string ) : Promise < void > { this . blocked . delete ( ip ) ; }
117- async isBlocked ( ip : string ) : Promise < boolean > { return this . blocked . has ( ip ) ; }
128+ async block ( ip : string ) : Promise < void > { assertValidFirewallIp ( ip ) ; this . blocked . add ( ip ) ; }
129+ async unblock ( ip : string ) : Promise < void > { assertValidFirewallIp ( ip ) ; this . blocked . delete ( ip ) ; }
130+ async isBlocked ( ip : string ) : Promise < boolean > { assertValidFirewallIp ( ip ) ; return this . blocked . has ( ip ) ; }
118131 async listBlocked ( ) : Promise < string [ ] > { return [ ...this . blocked ] ; }
119132}
120133
0 commit comments