Skip to content

Commit 8eff049

Browse files
committed
Update Paser Log
1 parent d3b6b57 commit 8eff049

2 files changed

Lines changed: 62 additions & 38 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ yarn-error.log*
3333
# typescript
3434
*.tsbuildinfo
3535
next-env.d.ts
36+
log.txt
37+
lib/__tests__/*
38+
*.md
39+
*.mdx
40+
test-*

lib/rule-generator.ts

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
import { ParsedLog } from './log-parser';
1+
import { ParsedLog } from "./log-parser";
22

3-
export type WhitelistLevel = 'global' | 'hostname' | 'uri' | 'method' | 'ip' | 'combined';
3+
export type WhitelistLevel =
4+
| "global"
5+
| "hostname"
6+
| "uri"
7+
| "method"
8+
| "ip"
9+
| "combined";
410

511
export interface WhitelistRule {
612
level: WhitelistLevel;
@@ -15,127 +21,134 @@ export interface WhitelistRule {
1521

1622
export function generateWhitelistRules(parsedLog: ParsedLog): WhitelistRule[] {
1723
const rules: WhitelistRule[] = [];
18-
const ruleId = parsedLog.ruleId || '0';
24+
const ruleId = parsedLog.ruleId || "0";
1925

2026
// 1. Global whitelist - Disable rule completely (most permissive)
2127
rules.push({
22-
level: 'global',
23-
description: 'Disable rule globally (most permissive)',
24-
descriptionKey: 'descGlobal',
25-
rule: `SecRuleRemoveById ${ruleId}`
28+
level: "global",
29+
description: "Disable rule globally (most permissive)",
30+
descriptionKey: "descGlobal",
31+
rule: `SecRuleRemoveById ${ruleId}`,
2632
});
2733

2834
// 2. Hostname-based whitelist
2935
if (parsedLog.hostname) {
36+
const escapedHostname = escapeModSecString(parsedLog.hostname);
3037
rules.push({
31-
level: 'hostname',
38+
level: "hostname",
3239
description: `Disable rule for hostname: ${parsedLog.hostname}`,
33-
descriptionKey: 'descHostname',
40+
descriptionKey: "descHostname",
3441
hostname: parsedLog.hostname,
35-
rule: `SecRule REQUEST_HEADERS:Host "@streq ${parsedLog.hostname}" \\
42+
rule: `SecRule REQUEST_HEADERS:Host "@rx ^${escapedHostname}$" \\
3643
"id:${generateCustomId()},\\
3744
phase:1,\\
3845
pass,\\
3946
nolog,\\
40-
ctl:ruleRemoveById=${ruleId}"`
47+
ctl:ruleRemoveById=${ruleId}"`,
4148
});
4249
}
4350

4451
// 3. URI-based whitelist
4552
if (parsedLog.uri) {
53+
const escapedUri = escapeModSecString(parsedLog.uri);
4654
rules.push({
47-
level: 'uri',
55+
level: "uri",
4856
description: `Disable rule for URI: ${parsedLog.uri}`,
49-
descriptionKey: 'descUri',
57+
descriptionKey: "descUri",
5058
uri: parsedLog.uri,
51-
rule: `SecRule REQUEST_URI "@streq ${parsedLog.uri}" \\
59+
rule: `SecRule REQUEST_URI "@rx ^${escapedUri}" \\
5260
"id:${generateCustomId()},\\
5361
phase:1,\\
5462
pass,\\
5563
nolog,\\
56-
ctl:ruleRemoveById=${ruleId}"`
64+
ctl:ruleRemoveById=${ruleId}"`,
5765
});
5866
}
5967

6068
// 4. Request Method-based whitelist
6169
if (parsedLog.requestMethod) {
6270
rules.push({
63-
level: 'method',
71+
level: "method",
6472
description: `Disable rule for method: ${parsedLog.requestMethod}`,
65-
descriptionKey: 'descMethod',
73+
descriptionKey: "descMethod",
6674
method: parsedLog.requestMethod,
67-
rule: `SecRule REQUEST_METHOD "@streq ${parsedLog.requestMethod}" \\
75+
rule: `SecRule REQUEST_METHOD "@rx ^${parsedLog.requestMethod}$" \\
6876
"id:${generateCustomId()},\\
6977
phase:1,\\
7078
pass,\\
7179
nolog,\\
72-
ctl:ruleRemoveById=${ruleId}"`
80+
ctl:ruleRemoveById=${ruleId}"`,
7381
});
7482
}
7583

7684
// 5. IP-based whitelist
7785
if (parsedLog.clientIp) {
7886
rules.push({
79-
level: 'ip',
87+
level: "ip",
8088
description: `Disable rule for IP: ${parsedLog.clientIp}`,
81-
descriptionKey: 'descIp',
89+
descriptionKey: "descIp",
8290
ip: parsedLog.clientIp,
8391
rule: `SecRule REMOTE_ADDR "@ipMatch ${parsedLog.clientIp}" \\
8492
"id:${generateCustomId()},\\
8593
phase:1,\\
8694
pass,\\
8795
nolog,\\
88-
ctl:ruleRemoveById=${ruleId}"`
96+
ctl:ruleRemoveById=${ruleId}"`,
8997
});
9098
}
9199

92100
// 6. Combined whitelist - Hostname + URI (recommended)
93101
if (parsedLog.hostname && parsedLog.uri) {
102+
const escapedHostname = escapeModSecString(parsedLog.hostname);
103+
const escapedUri = escapeModSecString(parsedLog.uri);
94104
rules.push({
95-
level: 'combined',
105+
level: "combined",
96106
description: `Disable rule for hostname + URI (recommended)`,
97-
descriptionKey: 'descCombinedHostUri',
107+
descriptionKey: "descCombinedHostUri",
98108
hostname: parsedLog.hostname,
99109
uri: parsedLog.uri,
100-
rule: `SecRule REQUEST_HEADERS:Host "@streq ${parsedLog.hostname}" \\
110+
rule: `SecRule REQUEST_HEADERS:Host "@rx ^${escapedHostname}$" \\
101111
"id:${generateCustomId()},\\
102112
phase:1,\\
103113
pass,\\
104114
nolog,\\
105115
chain"
106-
SecRule REQUEST_URI "@streq ${parsedLog.uri}" \\
107-
"ctl:ruleRemoveById=${ruleId}"`
116+
SecRule REQUEST_URI "@rx ^${escapedUri}" \\
117+
"ctl:ruleRemoveById=${ruleId}"`,
108118
});
109119
}
110120

111121
// 7. Combined whitelist - Hostname + URI + Method (most restrictive)
112122
if (parsedLog.hostname && parsedLog.uri && parsedLog.requestMethod) {
123+
const escapedHostname = escapeModSecString(parsedLog.hostname);
124+
const escapedUri = escapeModSecString(parsedLog.uri);
113125
rules.push({
114-
level: 'combined',
126+
level: "combined",
115127
description: `Disable rule for hostname + URI + method (most restrictive)`,
116-
descriptionKey: 'descCombinedHostUriMethod',
128+
descriptionKey: "descCombinedHostUriMethod",
117129
hostname: parsedLog.hostname,
118130
uri: parsedLog.uri,
119131
method: parsedLog.requestMethod,
120-
rule: `SecRule REQUEST_HEADERS:Host "@streq ${parsedLog.hostname}" \\
132+
rule: `SecRule REQUEST_HEADERS:Host "@rx ^${escapedHostname}$" \\
121133
"id:${generateCustomId()},\\
122134
phase:1,\\
123135
pass,\\
124136
nolog,\\
125137
chain"
126-
SecRule REQUEST_URI "@streq ${parsedLog.uri}" \\
127-
"chain"
128-
SecRule REQUEST_METHOD "@streq ${parsedLog.requestMethod}" \\
129-
"ctl:ruleRemoveById=${ruleId}"`
138+
SecRule REQUEST_URI "@rx ^${escapedUri}" \\
139+
"chain"
140+
SecRule REQUEST_METHOD "@rx ^${parsedLog.requestMethod}$" \\
141+
"ctl:ruleRemoveById=${ruleId}"`,
130142
});
131143
}
132144

133145
// 8. Combined whitelist - IP + URI
134146
if (parsedLog.clientIp && parsedLog.uri) {
147+
const escapedUri = escapeModSecString(parsedLog.uri);
135148
rules.push({
136-
level: 'combined',
149+
level: "combined",
137150
description: `Disable rule for IP + URI`,
138-
descriptionKey: 'descCombinedIpUri',
151+
descriptionKey: "descCombinedIpUri",
139152
ip: parsedLog.clientIp,
140153
uri: parsedLog.uri,
141154
rule: `SecRule REMOTE_ADDR "@ipMatch ${parsedLog.clientIp}" \\
@@ -144,14 +157,20 @@ SecRule REQUEST_METHOD "@streq ${parsedLog.requestMethod}" \\
144157
pass,\\
145158
nolog,\\
146159
chain"
147-
SecRule REQUEST_URI "@streq ${parsedLog.uri}" \\
148-
"ctl:ruleRemoveById=${ruleId}"`
160+
SecRule REQUEST_URI "@rx ^${escapedUri}" \\
161+
"ctl:ruleRemoveById=${ruleId}"`,
149162
});
150163
}
151164

152165
return rules;
153166
}
154167

168+
// Escape special regex characters for ModSecurity
169+
function escapeModSecString(str: string): string {
170+
// Escape special regex characters
171+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
172+
}
173+
155174
// Generate a custom rule ID in range 88,000,000 - 89,999,999
156175
// This range ensures no conflicts with existing ModSecurity rules
157176
const MIN_CUSTOM_ID = 88000000;

0 commit comments

Comments
 (0)