Duplicate Code Opportunity
Summary
- Pattern: The logic to list, parse, and delete rules from a DOCKER-USER chain — then flush and delete the chain itself — is implemented twice in
host-iptables-cleanup.ts: once for IPv4 (iptables) and again for IPv6 (ip6tables). A near-identical chain-teardown block also appears in host-iptables-rules.ts during setup.
- Locations:
src/host-iptables-cleanup.ts lines 21–85, src/host-iptables-rules.ts lines 110–145
- Impact: ~35 duplicate lines in cleanup alone; security-critical path where a bug fixed in the IPv4 branch may silently remain in the IPv6 branch
Evidence
src/host-iptables-cleanup.ts — IPv4 block (lines 21–52):
if (bridgeName) {
const { stdout } = await execa('iptables', [
'-t', 'filter', '-L', 'DOCKER-USER', '-n', '--line-numbers',
], { reject: false });
const lines = stdout.split('\n');
const lineNumbers: number[] = [];
for (const line of lines) {
if ((line.includes(`-i ${bridgeName}`) || line.includes(`-o ${bridgeName}`)) && line.includes(CHAIN_NAME)) {
const match = line.match(/^(\d+)/);
if (match) lineNumbers.push(parseInt(match[1], 10));
}
}
for (const lineNum of lineNumbers.reverse()) {
await execa('iptables', ['-t', 'filter', '-D', 'DOCKER-USER', lineNum.toString()], { reject: false });
}
}
await execa('iptables', ['-t', 'filter', '-F', CHAIN_NAME], { reject: false });
await execa('iptables', ['-t', 'filter', '-X', CHAIN_NAME], { reject: false });
src/host-iptables-cleanup.ts — IPv6 block (lines 58–85) — near-identical:
if (bridgeName) {
const { stdout: stdout6 } = await execa('ip6tables', [
'-t', 'filter', '-L', 'DOCKER-USER', '-n', '--line-numbers',
], { reject: false });
const lines6 = stdout6.split('\n');
const lineNumbers6: number[] = [];
for (const line of lines6) {
if (line.includes(CHAIN_NAME_V6)) { ... }
}
for (const lineNum of lineNumbers6.reverse()) {
await execa('ip6tables', ['-t', 'filter', '-D', 'DOCKER-USER', lineNum.toString()], { reject: false });
}
}
await execa('ip6tables', ['-t', 'filter', '-F', CHAIN_NAME_V6], { reject: false });
await execa('ip6tables', ['-t', 'filter', '-X', CHAIN_NAME_V6], { reject: false });
The only differences are: iptables vs ip6tables, CHAIN_NAME vs CHAIN_NAME_V6, variable name suffixes, and the bridge-match predicate.
A third copy of the chain-teardown pattern (list → parse → delete in reverse → flush → delete) appears in src/host-iptables-rules.ts lines 113–145 during pre-setup cleanup.
Suggested Refactoring
Extract a shared helper into src/host-iptables-shared.ts:
async function cleanupChain(
cmd: 'iptables' | 'ip6tables',
chainName: string,
bridgeName: string | null,
matchPredicate?: (line: string) => boolean,
): Promise<void> {
if (bridgeName) {
const { stdout } = await execa(cmd, ['-t', 'filter', '-L', 'DOCKER-USER', '-n', '--line-numbers'], { reject: false });
const lineNumbers: number[] = [];
for (const line of stdout.split('\n')) {
if ((matchPredicate ? matchPredicate(line) : line.includes(chainName))) {
const match = line.match(/^(\d+)/);
if (match) lineNumbers.push(parseInt(match[1], 10));
}
}
for (const lineNum of lineNumbers.reverse()) {
await execa(cmd, ['-t', 'filter', '-D', 'DOCKER-USER', lineNum.toString()], { reject: false });
}
}
await execa(cmd, ['-t', 'filter', '-F', chainName], { reject: false });
await execa(cmd, ['-t', 'filter', '-X', chainName], { reject: false });
}
Both cleanup and setup callers can then delegate to this helper, keeping IPv4/IPv6 parity guaranteed by construction.
Affected Files
src/host-iptables-cleanup.ts — lines 21–85
src/host-iptables-rules.ts — lines 110–145
Effort Estimate
Low — pure refactor, no behavior change; existing tests cover both paths.
Detected by Duplicate Code Detector workflow. Run date: 2026-05-18
Generated by Duplicate Code Detector · ● 8.6M · ◷
Duplicate Code Opportunity
Summary
host-iptables-cleanup.ts: once for IPv4 (iptables) and again for IPv6 (ip6tables). A near-identical chain-teardown block also appears inhost-iptables-rules.tsduring setup.src/host-iptables-cleanup.tslines 21–85,src/host-iptables-rules.tslines 110–145Evidence
src/host-iptables-cleanup.ts— IPv4 block (lines 21–52):src/host-iptables-cleanup.ts— IPv6 block (lines 58–85) — near-identical:The only differences are:
iptablesvsip6tables,CHAIN_NAMEvsCHAIN_NAME_V6, variable name suffixes, and the bridge-match predicate.A third copy of the chain-teardown pattern (list → parse → delete in reverse → flush → delete) appears in
src/host-iptables-rules.tslines 113–145 during pre-setup cleanup.Suggested Refactoring
Extract a shared helper into
src/host-iptables-shared.ts:Both cleanup and setup callers can then delegate to this helper, keeping IPv4/IPv6 parity guaranteed by construction.
Affected Files
src/host-iptables-cleanup.ts— lines 21–85src/host-iptables-rules.ts— lines 110–145Effort Estimate
Low — pure refactor, no behavior change; existing tests cover both paths.
Detected by Duplicate Code Detector workflow. Run date: 2026-05-18