@chain305/x-security 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/xsecurity.mjs CHANGED
@@ -978,7 +978,7 @@ function buildAuthzLua(authz, ctx) {
978
978
  ` if ${condition} then`,
979
979
  ` kong.log.warn("[${SS_BOLA_TAG}] rule ${i5 + 1} (${rule.field} ${rule.operator}) denied")`,
980
980
  ` return kong.response.exit(403, {`,
981
- ` message = "Writ: BOLA denied",`,
981
+ ` message = "XSecurity: BOLA denied",`,
982
982
  ` tag = "${SS_BOLA_TAG}",`,
983
983
  ` rule = ${i5 + 1}`,
984
984
  ` })`,
@@ -997,12 +997,12 @@ function buildAuthzLua(authz, ctx) {
997
997
  });
998
998
  }
999
999
  const head = [
1000
- `-- Writ K-1 BOLA pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1000
+ `-- XSecurity K-1 BOLA pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1001
1001
  `local cjson = require("cjson.safe")`,
1002
1002
  `local principal = ${refToLuaExpr("jwt.sub").expr}`,
1003
1003
  `if not principal then`,
1004
1004
  ` kong.log.warn("[${SS_BOLA_TAG}] no authenticated principal; denying")`,
1005
- ` return kong.response.exit(401, {message = "Writ: no authenticated principal", tag = "${SS_BOLA_TAG}"})`,
1005
+ ` return kong.response.exit(401, {message = "XSecurity: no authenticated principal", tag = "${SS_BOLA_TAG}"})`,
1006
1006
  `end`,
1007
1007
  ``
1008
1008
  ];
@@ -1011,21 +1011,21 @@ function buildAuthzLua(authz, ctx) {
1011
1011
  const idExpr = refToLuaExpr(lookup.identifierFrom).expr;
1012
1012
  head.push(
1013
1013
  `-- W10-11: shared_dict cache (key=principal:resource_id, TTL=${SS_BOLA_CACHE_TTL_SECONDS}s)`,
1014
- `local ss_cache = ngx.shared.writ_bola_cache`,
1014
+ `local ss_cache = ngx.shared.x_security_bola_cache`,
1015
1015
  `local ss_resource_id = tostring(${idExpr} or "")`,
1016
1016
  `local ss_cache_key = tostring(principal) .. ":" .. ss_resource_id`,
1017
1017
  `local ss_cached_owner = ss_cache and ss_cache:get(ss_cache_key) or nil`,
1018
1018
  `local ss_resource = nil`,
1019
1019
  `if ss_cached_owner ~= nil then`,
1020
- ` kong.log.warn("[writ-bola] cache_hit key=" .. ss_cache_key .. " owner=" .. tostring(ss_cached_owner))`,
1020
+ ` kong.log.warn("[x-security-bola] cache_hit key=" .. ss_cache_key .. " owner=" .. tostring(ss_cached_owner))`,
1021
1021
  ` ss_resource = { ownerId = ss_cached_owner, _ss_cached = true }`,
1022
1022
  `else`,
1023
1023
  ` -- resource lookup: ${lookup.endpoint} (identifier from ${lookup.identifierFrom})`,
1024
- ` kong.log.warn("[writ-bola] cache_miss key=" .. ss_cache_key)`,
1024
+ ` kong.log.warn("[x-security-bola] cache_miss key=" .. ss_cache_key)`,
1025
1025
  ` local ok_req, http = pcall(require, "resty.http")`,
1026
1026
  ` if not ok_req then`,
1027
1027
  ` kong.log.warn("[${SS_BOLA_TAG}] resty.http not available; failing closed")`,
1028
- ` return kong.response.exit(403, {message = "Writ: resource access denied", tag = "${SS_BOLA_TAG}", reason = "resty_http_missing"})`,
1028
+ ` return kong.response.exit(403, {message = "XSecurity: resource access denied", tag = "${SS_BOLA_TAG}", reason = "resty_http_missing"})`,
1029
1029
  ` end`,
1030
1030
  ` local httpc = http.new()`,
1031
1031
  ` httpc:set_timeout(2000)`,
@@ -1035,19 +1035,19 @@ function buildAuthzLua(authz, ctx) {
1035
1035
  ` method = "GET",`,
1036
1036
  ` headers = {`,
1037
1037
  ` ["Authorization"] = kong.request.get_header("Authorization"),`,
1038
- ` ["X-Writ-Internal"] = "rule-lookup"`,
1038
+ ` ["X-XSecurity-Internal"] = "rule-lookup"`,
1039
1039
  ` },`,
1040
1040
  ` ssl_verify = false`,
1041
1041
  ` })`,
1042
1042
  ` end)`,
1043
1043
  ` if not ok_call or not res or res.status ~= 200 then`,
1044
- ` kong.log.warn("[writ-bola] resource lookup failed status=" .. tostring(res and res.status or "no_response"))`,
1045
- ` return kong.response.exit(403, {message = "Writ: resource access denied", tag = "${SS_BOLA_TAG}", reason = "lookup_failed"})`,
1044
+ ` kong.log.warn("[x-security-bola] resource lookup failed status=" .. tostring(res and res.status or "no_response"))`,
1045
+ ` return kong.response.exit(403, {message = "XSecurity: resource access denied", tag = "${SS_BOLA_TAG}", reason = "lookup_failed"})`,
1046
1046
  ` end`,
1047
1047
  ` local ok_decode, body = pcall(cjson.decode, res.body or "{}")`,
1048
1048
  ` if not ok_decode or type(body) ~= "table" then`,
1049
- ` kong.log.warn("[writ-bola] decode failed for " .. lookup_url)`,
1050
- ` return kong.response.exit(403, {message = "Writ: invalid resource response", tag = "${SS_BOLA_TAG}", reason = "decode_failed"})`,
1049
+ ` kong.log.warn("[x-security-bola] decode failed for " .. lookup_url)`,
1050
+ ` return kong.response.exit(403, {message = "XSecurity: invalid resource response", tag = "${SS_BOLA_TAG}", reason = "decode_failed"})`,
1051
1051
  ` end`,
1052
1052
  ` ss_resource = body`,
1053
1053
  ` if ss_cache and body.ownerId ~= nil then`,
@@ -1112,7 +1112,7 @@ function buildSsrfPreFunctionPlugins(request9, ctx = {}) {
1112
1112
  }
1113
1113
  if (items.length === 0) return [];
1114
1114
  const lines = [
1115
- `-- Writ W19-A SSRF url-allowlist pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1115
+ `-- XSecurity W19-A SSRF url-allowlist pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1116
1116
  `-- W21-C: pure Kong PDK + Lua stdlib (no external module loads). Hardened`,
1117
1117
  `-- OSS deployments restrict the untrusted_lua sandbox; this snippet runs there.`,
1118
1118
  ``,
@@ -1142,13 +1142,13 @@ function buildSsrfPreFunctionPlugins(request9, ctx = {}) {
1142
1142
  lines.push(` local ss_allow = ${luaHostSet(it.allow)}`);
1143
1143
  lines.push(` if not host or not ss_allow[host] then`);
1144
1144
  lines.push(` kong.log.warn("[${SS_SSRF_TAG}] host '" .. tostring(host) .. "' not in domainAllowlist for ${it.field}")`);
1145
- lines.push(` return kong.response.exit(403, {message = "Writ: SSRF url not in domainAllowlist", tag = "${SS_SSRF_TAG}", field = ${luaStr(it.field)}})`);
1145
+ lines.push(` return kong.response.exit(403, {message = "XSecurity: SSRF url not in domainAllowlist", tag = "${SS_SSRF_TAG}", field = ${luaStr(it.field)}})`);
1146
1146
  lines.push(` end`);
1147
1147
  }
1148
1148
  if (it.block) {
1149
1149
  lines.push(` if ss_is_private(host) then`);
1150
1150
  lines.push(` kong.log.warn("[${SS_SSRF_PRIVATE_TAG}] private-range host '" .. tostring(host) .. "' on ${it.field}")`);
1151
- lines.push(` return kong.response.exit(403, {message = "Writ: SSRF blocked private/loopback host", tag = "${SS_SSRF_PRIVATE_TAG}", field = ${luaStr(it.field)}})`);
1151
+ lines.push(` return kong.response.exit(403, {message = "XSecurity: SSRF blocked private/loopback host", tag = "${SS_SSRF_PRIVATE_TAG}", field = ${luaStr(it.field)}})`);
1152
1152
  lines.push(` end`);
1153
1153
  }
1154
1154
  lines.push(` end`);
@@ -1185,7 +1185,7 @@ function buildMassAssignPreFunctionPlugins(request9, ctx = {}) {
1185
1185
  if (!allow) return [];
1186
1186
  const luaAllowTable = "{" + allow.map((k5) => `[${luaStr(k5)}]=true`).join(", ") + "}";
1187
1187
  const lua = [
1188
- `-- Writ K-3 mass-assignment pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1188
+ `-- XSecurity K-3 mass-assignment pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1189
1189
  `-- Allowlist sourced from ${explicitAllow ? "request.allowedFields" : "request.schema top-level keys"}.`,
1190
1190
  `local ss_allow = ${luaAllowTable}`,
1191
1191
  `local body = kong.request.get_body()`,
@@ -1194,7 +1194,7 @@ function buildMassAssignPreFunctionPlugins(request9, ctx = {}) {
1194
1194
  ` if not ss_allow[k] then`,
1195
1195
  ` kong.log.warn("[${SS_MASS_ASSIGN_TAG}] unknown field '" .. tostring(k) .. "' rejected")`,
1196
1196
  ` return kong.response.exit(403, {`,
1197
- ` message = "Writ: unknown field rejected",`,
1197
+ ` message = "XSecurity: unknown field rejected",`,
1198
1198
  ` tag = "${SS_MASS_ASSIGN_TAG}",`,
1199
1199
  ` field = tostring(k)`,
1200
1200
  ` })`,
@@ -1219,7 +1219,7 @@ function buildSqliPreFunctionPlugins(request9, ctx = {}) {
1219
1219
  checks.push(` if vl:match(${luaStr(SQLI_LUA_PATTERNS[i5])}) then return k, ${i5 + 1} end`);
1220
1220
  }
1221
1221
  const lua = [
1222
- `-- Writ K-4 SQLi heuristic pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1222
+ `-- XSecurity K-4 SQLi heuristic pre-function for endpoint=${ctx.endpoint ?? "?"}`,
1223
1223
  `-- Scans JSON body string values for classic SQLi patterns; rejects on hit.`,
1224
1224
  `local body = kong.request.get_body()`,
1225
1225
  `if type(body) == "table" then`,
@@ -1235,7 +1235,7 @@ function buildSqliPreFunctionPlugins(request9, ctx = {}) {
1235
1235
  ` if hit_field ~= nil then`,
1236
1236
  ` kong.log.warn("[${SS_SQLI_TAG}] sqli pattern " .. tostring(hit_rule) .. " in field '" .. tostring(hit_field) .. "'")`,
1237
1237
  ` return kong.response.exit(403, {`,
1238
- ` message = "Writ: SQLi pattern rejected",`,
1238
+ ` message = "XSecurity: SQLi pattern rejected",`,
1239
1239
  ` tag = "${SS_SQLI_TAG}",`,
1240
1240
  ` field = tostring(hit_field),`,
1241
1241
  ` rule = hit_rule`,
@@ -1252,7 +1252,7 @@ function buildSqliPreFunctionPlugins(request9, ctx = {}) {
1252
1252
  function buildDeprecatedEndpointPlugins(policy, ctx = {}) {
1253
1253
  if (policy.deprecated !== true) return [];
1254
1254
  const bodyFields = [
1255
- `message = "Writ: endpoint is deprecated"`,
1255
+ `message = "XSecurity: endpoint is deprecated"`,
1256
1256
  `tag = "${SS_DEPRECATED_TAG}"`
1257
1257
  ];
1258
1258
  if (policy.sunsetDate) bodyFields.push(`sunset = ${luaStr(policy.sunsetDate)}`);
@@ -1271,7 +1271,7 @@ function buildDeprecatedEndpointPlugins(policy, ctx = {}) {
1271
1271
  }`);
1272
1272
  }
1273
1273
  const lua = [
1274
- `-- Writ K-5 deprecated-endpoint block for endpoint=${ctx.endpoint ?? "?"}`,
1274
+ `-- XSecurity K-5 deprecated-endpoint block for endpoint=${ctx.endpoint ?? "?"}`,
1275
1275
  `kong.log.warn("[${SS_DEPRECATED_TAG}] request to deprecated endpoint blocked")`,
1276
1276
  `return kong.response.exit(${exitArgs.join(", ")})`
1277
1277
  ].join("\n");
@@ -1517,12 +1517,12 @@ var init_plugins = __esm({
1517
1517
  "hmac-sha384",
1518
1518
  "hmac-sha512"
1519
1519
  ]);
1520
- SS_BOLA_TAG = "writ-rule-bola-403";
1520
+ SS_BOLA_TAG = "x-security-rule-bola-403";
1521
1521
  SS_BOLA_CACHE_TTL_SECONDS = 60;
1522
- SS_BOLA_CACHE_DICT = "writ_bola_cache";
1522
+ SS_BOLA_CACHE_DICT = "x_security_bola_cache";
1523
1523
  SS_BOLA_CACHE_DICT_SIZE = "10m";
1524
- SS_SSRF_TAG = "writ-rule-ssrf-403";
1525
- SS_SSRF_PRIVATE_TAG = "writ-rule-ssrf-private-403";
1524
+ SS_SSRF_TAG = "x-security-rule-ssrf-403";
1525
+ SS_SSRF_PRIVATE_TAG = "x-security-rule-ssrf-private-403";
1526
1526
  SSRF_PRIVATE_LUA_PATTERNS = [
1527
1527
  "^10%.",
1528
1528
  "^127%.",
@@ -1540,8 +1540,8 @@ var init_plugins = __esm({
1540
1540
  "^%[fd",
1541
1541
  "^%[fe80"
1542
1542
  ];
1543
- SS_MASS_ASSIGN_TAG = "writ-mass-assign-403";
1544
- SS_SQLI_TAG = "writ-sqli-403";
1543
+ SS_MASS_ASSIGN_TAG = "x-security-mass-assign-403";
1544
+ SS_SQLI_TAG = "x-security-sqli-403";
1545
1545
  SQLI_LUA_PATTERNS = [
1546
1546
  `[' "]%s*or%s+%d+%s*=%s*%d+`,
1547
1547
  // ' OR 1=1
@@ -1562,10 +1562,10 @@ var init_plugins = __esm({
1562
1562
  "0x%x+"
1563
1563
  // hex literal (loose; matched only when combined w/ above in practice)
1564
1564
  ];
1565
- SS_DEPRECATED_TAG = "writ-deprecated-endpoint-block";
1565
+ SS_DEPRECATED_TAG = "x-security-deprecated-endpoint-block";
1566
1566
  DEFAULT_BURST_MULTIPLIER = 3;
1567
1567
  DEFAULT_BURST_MINIMUM_HEADROOM = 20;
1568
- SS_PER_ROUTE_RATELIMIT_TAG_PREFIX = "writ-per-route-ratelimit";
1568
+ SS_PER_ROUTE_RATELIMIT_TAG_PREFIX = "x-security-per-route-ratelimit";
1569
1569
  SEMANTIC_TO_JSON = {
1570
1570
  string: { type: "string" },
1571
1571
  integer: { type: "integer" },
@@ -1709,7 +1709,7 @@ function buildBolaRule(endpoint, param, method, ruleId, kind, profile) {
1709
1709
  return m3[1] === param ? "([^/?]+)" : "[^/]+";
1710
1710
  }).join("/");
1711
1711
  const captureRx = `^(?:/[^/]+)?/${rebuilt}(?:[/?]|$)`;
1712
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
1712
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
1713
1713
  const term = profile.legalCollections.has("user") ? "" : ' "t:none"';
1714
1714
  return [
1715
1715
  header2(
@@ -1717,13 +1717,13 @@ function buildBolaRule(endpoint, param, method, ruleId, kind, profile) {
1717
1717
  lifted from W13-C identity-conf; emitted because authorization.rules
1718
1718
  declares request.params.${param} equals principal.id (BOLA defense).`
1719
1719
  ),
1720
- `SecRule REQUEST_METHOD "@streq ${method}" "id:${ruleId},phase:1,deny,status:403,log,auditlog,msg:'Writ B1: BOLA-${kind} denied (path-${param} != ${TRUSTED_PRINCIPAL_HEADER})',tag:'${esc(tag2)}',tag:'writ/b1/bola-${kind}',chain"`,
1720
+ `SecRule REQUEST_METHOD "@streq ${method}" "id:${ruleId},phase:1,deny,status:403,log,auditlog,msg:'x-security B1: BOLA-${kind} denied (path-${param} != ${TRUSTED_PRINCIPAL_HEADER})',tag:'${esc(tag2)}',tag:'x-security/b1/bola-${kind}',chain"`,
1721
1721
  ` SecRule REQUEST_URI "@rx ${escRx(captureRx)}" "capture,chain"`,
1722
1722
  ` SecRule TX:1 "!@streq %{REQUEST_HEADERS.${TRUSTED_PRINCIPAL_HEADER}}"${term}`
1723
1723
  ].join("\n");
1724
1724
  }
1725
1725
  function buildBflaRules(endpoint, role, baseId, profile) {
1726
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
1726
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
1727
1727
  const term = profile.legalCollections.has("user") ? "" : ' "t:none"';
1728
1728
  const escapedPath = endpoint.path.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1729
1729
  const withParams = escapedPath.replace(/\\\{[^/]+?\\\}/g, "[^/]+");
@@ -1737,7 +1737,7 @@ function buildBflaRules(endpoint, role, baseId, profile) {
1737
1737
  case (a): no authenticated principal. Upstream gate should have
1738
1738
  returned 401, but we deny defensively in case the gate is bypassed.`
1739
1739
  ),
1740
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idMissing},phase:1,deny,status:403,log,auditlog,msg:'Writ B1: BFLA denied (${role}-only route, no authenticated principal)',tag:'${esc(tag2)}',tag:'writ/b1/bfla',chain"`,
1740
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idMissing},phase:1,deny,status:403,log,auditlog,msg:'x-security B1: BFLA denied (${role}-only route, no authenticated principal)',tag:'${esc(tag2)}',tag:'x-security/b1/bfla',chain"`,
1741
1741
  ` SecRule REQUEST_URI "@rx ${escRx(pathRx)}" "chain"`,
1742
1742
  ` SecRule &REQUEST_HEADERS:${TRUSTED_PRINCIPAL_HEADER} "@eq 0"${term}`
1743
1743
  ].join("\n"),
@@ -1746,7 +1746,7 @@ returned 401, but we deny defensively in case the gate is bypassed.`
1746
1746
  `B1: BFLA \u2014 ${endpoint.method} ${endpoint.path}
1747
1747
  case (b): authenticated but ${TRUSTED_PRINCIPAL_HEADER} != '${role}'.`
1748
1748
  ),
1749
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idNonRole},phase:1,deny,status:403,log,auditlog,msg:'Writ B1: BFLA denied (${role}-only route, non-${role} principal)',tag:'${esc(tag2)}',tag:'writ/b1/bfla',chain"`,
1749
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idNonRole},phase:1,deny,status:403,log,auditlog,msg:'x-security B1: BFLA denied (${role}-only route, non-${role} principal)',tag:'${esc(tag2)}',tag:'x-security/b1/bfla',chain"`,
1750
1750
  ` SecRule REQUEST_URI "@rx ${escRx(pathRx)}" "chain"`,
1751
1751
  ` SecRule REQUEST_HEADERS:${TRUSTED_PRINCIPAL_HEADER} "!@streq ${role}" "t:none"`
1752
1752
  ].join("\n")
@@ -1823,7 +1823,7 @@ function buildCorsRules(endpoint, profile = CORAZA_GO_PROFILE) {
1823
1823
  if (!cors || !Array.isArray(cors.allowedOrigins) || cors.allowedOrigins.length === 0) {
1824
1824
  return [];
1825
1825
  }
1826
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
1826
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
1827
1827
  const pathRx = pathRegex(endpoint.path);
1828
1828
  const term = chainTerm(profile);
1829
1829
  const slot = endpointHash3(endpoint.method, endpoint.path) % 1e3;
@@ -1839,7 +1839,7 @@ msg carries the literal substring 'id:339' so scorer attribution
1839
1839
  (scoring_lib/attribution.py) maps the firing to cors-policy regardless
1840
1840
  of audit-log format (libmodsec3 writes [id "339NNN"], not id:339NNN).`
1841
1841
  ),
1842
- `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${originId},phase:1,deny,status:403,msg:'Writ id:339 CORS origin not allowed',tag:'${esc2(tag2)}',tag:'writ-cors-policy',chain"`,
1842
+ `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${originId},phase:1,deny,status:403,msg:'x-security id:339 CORS origin not allowed',tag:'${esc2(tag2)}',tag:'x-security-cors-policy',chain"`,
1843
1843
  ` SecRule &REQUEST_HEADERS:Origin "@gt 0" "chain"`,
1844
1844
  ` SecRule REQUEST_HEADERS:Origin "!@rx ${esc2(originRx)}"${term}`
1845
1845
  ].join("\n")
@@ -1854,7 +1854,7 @@ of audit-log format (libmodsec3 writes [id "339NNN"], not id:339NNN).`
1854
1854
  phase:1 \u2014 on OPTIONS, deny when Access-Control-Request-Method is set
1855
1855
  and not in allowedMethods. msg carries 'id:332' for attribution.`
1856
1856
  ),
1857
- `SecRule REQUEST_METHOD "@streq OPTIONS" "id:${preflightMethodId},phase:1,deny,status:403,msg:'Writ id:332 CORS preflight method not allowed',tag:'${esc2(tag2)}',tag:'writ-cors-policy',chain"`,
1857
+ `SecRule REQUEST_METHOD "@streq OPTIONS" "id:${preflightMethodId},phase:1,deny,status:403,msg:'x-security id:332 CORS preflight method not allowed',tag:'${esc2(tag2)}',tag:'x-security-cors-policy',chain"`,
1858
1858
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
1859
1859
  ` SecRule REQUEST_HEADERS:Access-Control-Request-Method "!@rx ^(${methodAlt})$"${term}`
1860
1860
  ].join("\n")
@@ -1871,7 +1871,7 @@ phase:1 \u2014 on OPTIONS, deny when Access-Control-Request-Headers is
1871
1871
  anything other than a comma-list of allowedHeaders (case-insensitive).
1872
1872
  RE2-safe: no negative lookahead \u2014 match the allow form and negate.`
1873
1873
  ),
1874
- `SecRule REQUEST_METHOD "@streq OPTIONS" "id:${preflightHeadersId},phase:1,deny,status:403,msg:'Writ id:332 CORS preflight header not allowed',tag:'${esc2(tag2)}',tag:'writ-cors-policy',chain"`,
1874
+ `SecRule REQUEST_METHOD "@streq OPTIONS" "id:${preflightHeadersId},phase:1,deny,status:403,msg:'x-security id:332 CORS preflight header not allowed',tag:'${esc2(tag2)}',tag:'x-security-cors-policy',chain"`,
1875
1875
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
1876
1876
  ` SecRule REQUEST_HEADERS:Access-Control-Request-Headers "!@rx ${esc2(allowedListRx)}" "t:lowercase${term ? ",t:none" : ""}"`
1877
1877
  ].join("\n")
@@ -1885,7 +1885,7 @@ RE2-safe: no negative lookahead \u2014 match the allow form and negate.`
1885
1885
  `CORS credentials advertisement for ${endpoint.method} ${endpoint.path}
1886
1886
  phase:3 \u2014 setenv Access-Control-Allow-Credentials:true for upstream add_header.`
1887
1887
  ),
1888
- `SecAction "id:${id},phase:3,pass,nolog,msg:'Writ id:333 CORS credentials true',tag:'${esc2(tag2)}',tag:'writ-cors-policy',setenv:Access-Control-Allow-Credentials=true"`
1888
+ `SecAction "id:${id},phase:3,pass,nolog,msg:'x-security id:333 CORS credentials true',tag:'${esc2(tag2)}',tag:'x-security-cors-policy',setenv:Access-Control-Allow-Credentials=true"`
1889
1889
  ].join("\n")
1890
1890
  );
1891
1891
  }
@@ -1898,7 +1898,7 @@ phase:3 \u2014 setenv Access-Control-Allow-Credentials:true for upstream add_hea
1898
1898
  `CORS expose-headers advertisement for ${endpoint.method} ${endpoint.path}
1899
1899
  phase:3 \u2014 setenv Access-Control-Expose-Headers:<list> for upstream add_header.`
1900
1900
  ),
1901
- `SecAction "id:${id},phase:3,pass,nolog,msg:'Writ id:334 CORS expose-headers',tag:'${esc2(tag2)}',tag:'writ-cors-policy',setenv:Access-Control-Expose-Headers=${esc2(list2)}"`
1901
+ `SecAction "id:${id},phase:3,pass,nolog,msg:'x-security id:334 CORS expose-headers',tag:'${esc2(tag2)}',tag:'x-security-cors-policy',setenv:Access-Control-Expose-Headers=${esc2(list2)}"`
1902
1902
  ].join("\n")
1903
1903
  );
1904
1904
  }
@@ -1910,7 +1910,7 @@ phase:3 \u2014 setenv Access-Control-Expose-Headers:<list> for upstream add_head
1910
1910
  `CORS max-age advertisement for ${endpoint.method} ${endpoint.path}
1911
1911
  phase:3 \u2014 setenv Access-Control-Max-Age:<seconds> for upstream add_header.`
1912
1912
  ),
1913
- `SecAction "id:${id},phase:3,pass,nolog,msg:'Writ id:335 CORS max-age',tag:'${esc2(tag2)}',tag:'writ-cors-policy',setenv:Access-Control-Max-Age=${cors.maxAge}"`
1913
+ `SecAction "id:${id},phase:3,pass,nolog,msg:'x-security id:335 CORS max-age',tag:'${esc2(tag2)}',tag:'x-security-cors-policy',setenv:Access-Control-Max-Age=${cors.maxAge}"`
1914
1914
  ].join("\n")
1915
1915
  );
1916
1916
  }
@@ -1965,7 +1965,7 @@ function buildOutputSanitizationRules(endpoint, profile = CORAZA_GO_PROFILE, war
1965
1965
  });
1966
1966
  return [];
1967
1967
  }
1968
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
1968
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
1969
1969
  const pathRx = pathRegex(endpoint.path);
1970
1970
  const term = chainTerm2(profile);
1971
1971
  const rules = [];
@@ -1980,7 +1980,7 @@ phase:4 \u2014 deny when RESPONSE_BODY contains a stack-frame shape
1980
1980
  (common across Python / Node / Java / Go runtimes). msg carries 'id:268'
1981
1981
  for scorer attribution.`
1982
1982
  ),
1983
- `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'Writ id:268 output sanitization (stack trace leak)',tag:'${esc3(tag2)}',tag:'writ-output-sanitization',chain"`,
1983
+ `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'x-security id:268 output sanitization (stack trace leak)',tag:'${esc3(tag2)}',tag:'x-security-output-sanitization',chain"`,
1984
1984
  // Match the most common stack-frame markers: `at File.method`,
1985
1985
  // `Traceback (most recent call last):`, `\tat com.example.Foo`,
1986
1986
  // `Exception in thread`, `goroutine \d+ [running]:`.
@@ -1998,7 +1998,7 @@ phase:3 \u2014 strip Server / X-Powered-By response headers. ModSec has no
1998
1998
  native header-rewrite primitive on Coraza-Go (action 'setenv'/'setvar'
1999
1999
  do not touch response headers); we deny the response instead, msg carries 'id:268'.`
2000
2000
  ),
2001
- `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:3,deny,status:500,msg:'Writ id:268 output sanitization (server-version leak)',tag:'${esc3(tag2)}',tag:'writ-output-sanitization',chain"`,
2001
+ `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:3,deny,status:500,msg:'x-security id:268 output sanitization (server-version leak)',tag:'${esc3(tag2)}',tag:'x-security-output-sanitization',chain"`,
2002
2002
  ` SecRule RESPONSE_HEADERS:Server|RESPONSE_HEADERS:X-Powered-By "@rx ^.+$"${term}`
2003
2003
  ].join("\n")
2004
2004
  );
@@ -2012,7 +2012,7 @@ do not touch response headers); we deny the response instead, msg carries 'id:26
2012
2012
  phase:4 \u2014 deny when RESPONSE_BODY exposes raw DB/runtime error keywords
2013
2013
  (SQL state, file paths, "syntax error near"). msg carries 'id:268'.`
2014
2014
  ),
2015
- `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'Writ id:268 output sanitization (raw error leak)',tag:'${esc3(tag2)}',tag:'writ-output-sanitization',chain"`,
2015
+ `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'x-security id:268 output sanitization (raw error leak)',tag:'${esc3(tag2)}',tag:'x-security-output-sanitization',chain"`,
2016
2016
  ` SecRule RESPONSE_BODY "@rx (?i)(?:syntax error near|ORA-\\d+|ER_\\w+|psycopg2\\.|SQLSTATE|\\bENOENT\\b|undefined method|NullPointerException|panic:\\s)"${term}`
2017
2017
  ].join("\n")
2018
2018
  );
@@ -2035,7 +2035,7 @@ function buildDataExposurePiiRules(endpoint, profile = CORAZA_GO_PROFILE, warnin
2035
2035
  ([name, spec]) => spec?.pii === true || isSensitiveFieldName(name)
2036
2036
  );
2037
2037
  if (sensitiveFields.length === 0) return [];
2038
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2038
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2039
2039
  const pathRx = pathRegex(endpoint.path);
2040
2040
  const term = chainTerm2(profile);
2041
2041
  const rules = [];
@@ -2049,8 +2049,8 @@ function buildDataExposurePiiRules(endpoint, profile = CORAZA_GO_PROFILE, warnin
2049
2049
  phase:4 \u2014 deny when sensitive-named field appears in RESPONSE_BODY with
2050
2050
  a non-empty string value. msg carries 'id:428' for scorer attribution.`
2051
2051
  ),
2052
- `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'Writ id:428 data-exposure: response leaked sensitive field ${esc3(field)}',tag:'${esc3(tag2)}',tag:'writ-data-exposure',chain"`,
2053
- ` SecRule RESPONSE_BODY "@rx \\"${esc3(field)}\\"\\s*:\\s*\\"[^\\"]+\\""${term}`
2052
+ `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'x-security id:428 data-exposure: response leaked sensitive field ${esc3(field)}',tag:'${esc3(tag2)}',tag:'x-security-data-exposure',chain"`,
2053
+ ` SecRule RESPONSE_BODY "@rx \\x22${esc3(field)}\\x22\\s*:\\s*\\x22[^\\x22]+\\x22"${term}`
2054
2054
  ].join("\n")
2055
2055
  );
2056
2056
  }
@@ -2100,7 +2100,7 @@ function header5(comment) {
2100
2100
  function buildLifecycleRules(endpoint, _profile = CORAZA_GO_PROFILE) {
2101
2101
  const policy = endpoint.policy;
2102
2102
  const rules = [];
2103
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2103
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2104
2104
  const slot = endpointHash3(endpoint.method, endpoint.path) % 1e3;
2105
2105
  if (policy.deprecated === true) {
2106
2106
  const id = DEPRECATED_BASE_ID + slot;
@@ -2109,9 +2109,9 @@ function buildLifecycleRules(endpoint, _profile = CORAZA_GO_PROFILE) {
2109
2109
  header5(
2110
2110
  `lifecycle: endpoint deprecated for ${endpoint.method} ${endpoint.path}
2111
2111
  phase:1 \u2014 deny with HTTP 410 Gone per RFC 8594 deprecation disposition.
2112
- msg carries 'id:269' substring for scorer attribution (writ-lifecycle-410).`
2112
+ msg carries 'id:269' substring for scorer attribution (x-security-lifecycle-410).`
2113
2113
  ),
2114
- `SecAction "id:${id},phase:1,deny,status:410,msg:'Writ id:269 endpoint deprecated',tag:'${esc4(tag2)}',tag:'writ-lifecycle-410'"`
2114
+ `SecAction "id:${id},phase:1,deny,status:410,msg:'x-security id:269 endpoint deprecated',tag:'${esc4(tag2)}',tag:'x-security-lifecycle-410'"`
2115
2115
  ].join("\n")
2116
2116
  );
2117
2117
  }
@@ -2122,9 +2122,9 @@ msg carries 'id:269' substring for scorer attribution (writ-lifecycle-410).`
2122
2122
  header5(
2123
2123
  `lifecycle: Sunset header for ${endpoint.method} ${endpoint.path}
2124
2124
  phase:3 \u2014 setenv:Sunset=<iso> so the upstream proxy can add_header it.
2125
- msg carries 'id:270' substring (writ-lifecycle-sunset).`
2125
+ msg carries 'id:270' substring (x-security-lifecycle-sunset).`
2126
2126
  ),
2127
- `SecAction "id:${id},phase:3,pass,nolog,msg:'Writ id:270 sunset header',tag:'${esc4(tag2)}',tag:'writ-lifecycle-sunset',setenv:Sunset=${esc4(policy.sunsetDate)}"`
2127
+ `SecAction "id:${id},phase:3,pass,nolog,msg:'x-security id:270 sunset header',tag:'${esc4(tag2)}',tag:'x-security-lifecycle-sunset',setenv:Sunset=${esc4(policy.sunsetDate)}"`
2128
2128
  ].join("\n")
2129
2129
  );
2130
2130
  }
@@ -2136,9 +2136,9 @@ msg carries 'id:270' substring (writ-lifecycle-sunset).`
2136
2136
  header5(
2137
2137
  `lifecycle: Link successor-version for ${endpoint.method} ${endpoint.path}
2138
2138
  phase:3 \u2014 setenv:Link=<replacement>; rel="successor-version" per RFC 8594.
2139
- msg carries 'id:271' substring (writ-lifecycle-replacement).`
2139
+ msg carries 'id:271' substring (x-security-lifecycle-replacement).`
2140
2140
  ),
2141
- `SecAction "id:${id},phase:3,pass,nolog,msg:'Writ id:271 replacement endpoint',tag:'${esc4(tag2)}',tag:'writ-lifecycle-replacement',setenv:Link=${esc4(linkValue)}"`
2141
+ `SecAction "id:${id},phase:3,pass,nolog,msg:'x-security id:271 replacement endpoint',tag:'${esc4(tag2)}',tag:'x-security-lifecycle-replacement',setenv:Link=${esc4(linkValue)}"`
2142
2142
  ].join("\n")
2143
2143
  );
2144
2144
  }
@@ -2174,7 +2174,7 @@ function buildCsrfRules(endpoint, profile = CORAZA_GO_PROFILE) {
2174
2174
  const csrf = endpoint.policy.csrf;
2175
2175
  if (!csrf) return [];
2176
2176
  if (!STATE_CHANGING.has(endpoint.method.toUpperCase())) return [];
2177
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2177
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2178
2178
  const pathRx = pathRegex(endpoint.path);
2179
2179
  const term = chainTerm3(profile);
2180
2180
  const slot = endpointHash3(endpoint.method, endpoint.path) % 1e3;
@@ -2189,9 +2189,9 @@ function buildCsrfRules(endpoint, profile = CORAZA_GO_PROFILE) {
2189
2189
  header6(
2190
2190
  `csrf: origin-check for ${endpoint.method} ${endpoint.path}
2191
2191
  phase:1 \u2014 deny when Origin missing or not in allowedOrigins.
2192
- msg carries 'id:272' substring (writ-csrf).`
2192
+ msg carries 'id:272' substring (x-security-csrf).`
2193
2193
  ),
2194
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'Writ id:272 CSRF origin not allowed',tag:'${esc5(tag2)}',tag:'writ-csrf',chain"`,
2194
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'x-security id:272 CSRF origin not allowed',tag:'${esc5(tag2)}',tag:'x-security-csrf',chain"`,
2195
2195
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
2196
2196
  ` SecRule REQUEST_HEADERS:Origin "!@rx ${esc5(originRx)}"${term}`
2197
2197
  ].join("\n")
@@ -2202,14 +2202,14 @@ msg carries 'id:272' substring (writ-csrf).`
2202
2202
  if (!cookieName || !headerName) return [];
2203
2203
  const captureId = CSRF_BASE_ID + 1e3 + slot;
2204
2204
  const denyId = CSRF_BASE_ID + 2e3 + slot;
2205
- const txVar = `writ_csrf_${slot}`;
2205
+ const txVar = `x_security_csrf_${slot}`;
2206
2206
  rules.push(
2207
2207
  [
2208
2208
  header6(
2209
2209
  `csrf: double-submit capture for ${endpoint.method} ${endpoint.path}
2210
2210
  phase:1 \u2014 capture cookie '${esc5(cookieName)}' into TX:${txVar} for the equality check.`
2211
2211
  ),
2212
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${captureId},phase:1,pass,nolog,tag:'${esc5(tag2)}',tag:'writ-csrf',chain"`,
2212
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${captureId},phase:1,pass,nolog,tag:'${esc5(tag2)}',tag:'x-security-csrf',chain"`,
2213
2213
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
2214
2214
  ` SecRule REQUEST_COOKIES:${cookieName} "@rx .+" "capture,setvar:tx.${txVar}=%{MATCHED_VAR}${term ? ",t:none" : ""}"`
2215
2215
  ].join("\n")
@@ -2219,9 +2219,9 @@ phase:1 \u2014 capture cookie '${esc5(cookieName)}' into TX:${txVar} for the equ
2219
2219
  header6(
2220
2220
  `csrf: double-submit verify for ${endpoint.method} ${endpoint.path}
2221
2221
  phase:1 \u2014 deny when header '${esc5(headerName)}' missing OR != TX:${txVar}.
2222
- msg carries 'id:272' substring (writ-csrf).`
2222
+ msg carries 'id:272' substring (x-security-csrf).`
2223
2223
  ),
2224
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${denyId},phase:1,deny,status:403,msg:'Writ id:272 CSRF token mismatch',tag:'${esc5(tag2)}',tag:'writ-csrf',chain"`,
2224
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${denyId},phase:1,deny,status:403,msg:'x-security id:272 CSRF token mismatch',tag:'${esc5(tag2)}',tag:'x-security-csrf',chain"`,
2225
2225
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
2226
2226
  ` SecRule REQUEST_HEADERS:${headerName} "!@streq %{TX.${txVar}}"${term}`
2227
2227
  ].join("\n")
@@ -2235,9 +2235,9 @@ msg carries 'id:272' substring (writ-csrf).`
2235
2235
  header6(
2236
2236
  `csrf: custom-header for ${endpoint.method} ${endpoint.path}
2237
2237
  phase:1 \u2014 deny when '${esc5(headerName)}' header missing/empty.
2238
- msg carries 'id:272' substring (writ-csrf).`
2238
+ msg carries 'id:272' substring (x-security-csrf).`
2239
2239
  ),
2240
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'Writ id:272 CSRF custom header missing',tag:'${esc5(tag2)}',tag:'writ-csrf',chain"`,
2240
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'x-security id:272 CSRF custom header missing',tag:'${esc5(tag2)}',tag:'x-security-csrf',chain"`,
2241
2241
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
2242
2242
  ` SecRule &REQUEST_HEADERS:${headerName} "@eq 0"${term}`
2243
2243
  ].join("\n")
@@ -2270,7 +2270,7 @@ function buildDuplicateParamRules(endpoint, profile = CORAZA_GO_PROFILE) {
2270
2270
  const req = endpoint.policy.request;
2271
2271
  if (!req || req.duplicateParamPolicy !== "reject") return [];
2272
2272
  if (!req.schema) return [];
2273
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2273
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2274
2274
  const pathRx = pathRegex(endpoint.path);
2275
2275
  const term = chainTerm4(profile);
2276
2276
  const rules = [];
@@ -2283,9 +2283,9 @@ function buildDuplicateParamRules(endpoint, profile = CORAZA_GO_PROFILE) {
2283
2283
  header7(
2284
2284
  `request.duplicateParamPolicy=reject for ${endpoint.method} ${endpoint.path} field=${field}
2285
2285
  phase:2 \u2014 deny when ARGS:${field} appears more than once (HPP defense).
2286
- msg carries 'id:275' substring (writ-hpp-reject).`
2286
+ msg carries 'id:275' substring (x-security-hpp-reject).`
2287
2287
  ),
2288
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ id:275 duplicate parameter ${esc6(field)}',tag:'${esc6(tag2)}',tag:'writ-hpp-reject',chain"`,
2288
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security id:275 duplicate parameter ${esc6(field)}',tag:'${esc6(tag2)}',tag:'x-security-hpp-reject',chain"`,
2289
2289
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
2290
2290
  ` SecRule &ARGS:${field} "@gt 1"${term}`
2291
2291
  ].join("\n")
@@ -2316,7 +2316,7 @@ function chainTerm5(profile) {
2316
2316
  function buildResponseContentTypeRules(endpoint, profile = CORAZA_GO_PROFILE) {
2317
2317
  const resp = endpoint.policy.response;
2318
2318
  if (!resp || !Array.isArray(resp.contentType) || resp.contentType.length === 0) return [];
2319
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2319
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2320
2320
  const pathRx = pathRegex(endpoint.path);
2321
2321
  const term = chainTerm5(profile);
2322
2322
  const slot = endpointHash3(endpoint.method, endpoint.path) % 1e3;
@@ -2328,9 +2328,9 @@ function buildResponseContentTypeRules(endpoint, profile = CORAZA_GO_PROFILE) {
2328
2328
  header8(
2329
2329
  `response.contentType allowlist for ${endpoint.method} ${endpoint.path}
2330
2330
  phase:3 \u2014 deny when RESPONSE_HEADERS:Content-Type not in allowlist.
2331
- msg carries 'id:276' substring (writ-response-ct).`
2331
+ msg carries 'id:276' substring (x-security-response-ct).`
2332
2332
  ),
2333
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:3,deny,status:500,msg:'Writ id:276 response content-type not allowed',tag:'${esc7(tag2)}',tag:'writ-response-ct',chain"`,
2333
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:3,deny,status:500,msg:'x-security id:276 response content-type not allowed',tag:'${esc7(tag2)}',tag:'x-security-response-ct',chain"`,
2334
2334
  ` SecRule REQUEST_FILENAME "@rx ${pathRx}" "chain"`,
2335
2335
  ` SecRule RESPONSE_HEADERS:Content-Type "!@rx ${esc7(allowRx)}"${term}`
2336
2336
  ].join("\n")
@@ -2422,10 +2422,10 @@ PARTIAL \u2014 edge serialization only: a crude same-key cap in a 1s window,
2422
2422
  NOT an in-handler mutex. Does NOT provide transaction atomicity.`
2423
2423
  ),
2424
2424
  // 1. open + increment the same-key counter for a 1s edge window.
2425
- `SecRule REQUEST_URI "@rx ${pathRx}" "id:${initId},phase:1,pass,nolog,tag:'writ-serialize',initcol:${initcolArg},setvar:${col}.${varName}=+1,expirevar:${col}.${varName}=1,chain"`,
2425
+ `SecRule REQUEST_URI "@rx ${pathRx}" "id:${initId},phase:1,pass,nolog,tag:'x-security-serialize',initcol:${initcolArg},setvar:${col}.${varName}=+1,expirevar:${col}.${varName}=1,chain"`,
2426
2426
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${term}`,
2427
2427
  // 2. deny when the same-key in-window count exceeds the concurrency cap.
2428
- `SecRule REQUEST_URI "@rx ${pathRx}" "id:${checkId},phase:1,deny,status:429,msg:'Writ: serializeBy concurrency cap (${limit}) exceeded (edge serialization, not in-handler atomicity)',tag:'writ-serialize',log,chain"`,
2428
+ `SecRule REQUEST_URI "@rx ${pathRx}" "id:${checkId},phase:1,deny,status:429,msg:'x-security: serializeBy concurrency cap (${limit}) exceeded (edge serialization, not in-handler atomicity)',tag:'x-security-serialize',log,chain"`,
2429
2429
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
2430
2430
  ` SecRule ${col.toUpperCase()}:${varName} "@gt ${limit}"${term}`
2431
2431
  ].join("\n")
@@ -2452,7 +2452,7 @@ introspection meta-fields __schema / __type. A WAF cannot fully
2452
2452
  validate GraphQL semantics, but the introspection token check is
2453
2453
  genuinely enforcing at the byte level.`
2454
2454
  ),
2455
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'Writ: GraphQL introspection disabled',tag:'writ-graphql',chain"`,
2455
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'x-security: GraphQL introspection disabled',tag:'x-security-graphql',chain"`,
2456
2456
  ` SecRule REQUEST_URI "@rx ${pathRx}" "chain"`,
2457
2457
  ` SecRule REQUEST_BODY|ARGS "@rx (?i)__(?:schema|type)\\b"${term}`
2458
2458
  ].join("\n")
@@ -2468,7 +2468,7 @@ genuinely enforcing at the byte level.`
2468
2468
  PARTIAL \u2014 crude non-parsing alias-token count (heuristic): deny when
2469
2469
  >${g5.maxAliases} alias-shaped '<ident>:' tokens appear in the body.`
2470
2470
  ),
2471
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'Writ: GraphQL alias cap (${g5.maxAliases}) exceeded',tag:'writ-graphql',chain"`,
2471
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'x-security: GraphQL alias cap (${g5.maxAliases}) exceeded',tag:'x-security-graphql',chain"`,
2472
2472
  ` SecRule REQUEST_URI "@rx ${pathRx}" "chain"`,
2473
2473
  ` SecRule REQUEST_BODY "@rx (?s)(?:[A-Za-z_][A-Za-z0-9_]*\\s*:\\s*[A-Za-z_][A-Za-z0-9_]*[^:]*){${min},}"${term}`
2474
2474
  ].join("\n")
@@ -2491,9 +2491,9 @@ PARTIAL \u2014 crude non-parsing alias-token count (heuristic): deny when
2491
2491
  PARTIAL \u2014 crude non-parsing batch count: deny when >${g5.batchLimit}
2492
2492
  '"query"' members appear (batched array of operations).`
2493
2493
  ),
2494
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'Writ: GraphQL batch cap (${g5.batchLimit}) exceeded',tag:'writ-graphql',chain"`,
2494
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'x-security: GraphQL batch cap (${g5.batchLimit}) exceeded',tag:'x-security-graphql',chain"`,
2495
2495
  ` SecRule REQUEST_URI "@rx ${pathRx}" "chain"`,
2496
- ` SecRule REQUEST_BODY "@rx (?s)(?:\\"query\\"\\s*:[^:]*){${min},}"${term}`
2496
+ ` SecRule REQUEST_BODY "@rx (?s)(?:\\x22query\\x22\\s*:[^:]*){${min},}"${term}`
2497
2497
  ].join("\n")
2498
2498
  );
2499
2499
  warnings?.push({
@@ -2536,7 +2536,7 @@ function buildResidualScaffolding(endpoint) {
2536
2536
  OVERRIDE-ONLY \u2014 SCAFFOLDING, NOT ENFORCED.
2537
2537
  Per-resolver authorization requires an operator-supplied GraphQL-aware
2538
2538
  processor: a WAF cannot parse the query, bind the resolved object to an
2539
- identity claim, and evaluate ownership/role per operation. Writ
2539
+ identity claim, and evaluate ownership/role per operation. x-security
2540
2540
  emits NO enforcing SecRule for these. Operator contract \u2014 wire each
2541
2541
  operation's authz in your GraphQL gateway / resolver middleware:
2542
2542
  ` + named
@@ -2588,7 +2588,7 @@ function supportsStatefulNamedCollection(profile) {
2588
2588
  function buildPasswordPolicyRules(endpoint, profile = CORAZA_GO_PROFILE) {
2589
2589
  const pol = endpoint.policy.authentication?.passwordPolicy;
2590
2590
  if (!pol) return [];
2591
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2591
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2592
2592
  const rx = pathRegex(endpoint.path);
2593
2593
  const seedBase = endpointHash3(endpoint.method, `${endpoint.path}|pwpolicy`);
2594
2594
  const term = chainTerm7(profile);
@@ -2616,7 +2616,7 @@ function buildPasswordPolicyRules(endpoint, profile = CORAZA_GO_PROFILE) {
2616
2616
  Body-carried password strength (API2:2023). PCRE !@rx on the password
2617
2617
  field \u2014 rejects a present-but-weak password. FULL (plain @rx).`
2618
2618
  ),
2619
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'Writ: ${esc9(c5.reason)}',tag:'${esc9(tag2)}',tag:'writ-rule-password-policy',chain"`,
2619
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'x-security: ${esc9(c5.reason)}',tag:'${esc9(tag2)}',tag:'x-security-rule-password-policy',chain"`,
2620
2620
  ` SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "chain"`,
2621
2621
  ` SecRule ${selector} "!@rx ${escRx2(c5.rx)}"${term}`
2622
2622
  ].join("\n")
@@ -2628,7 +2628,7 @@ field \u2014 rejects a present-but-weak password. FULL (plain @rx).`
2628
2628
  out.push(
2629
2629
  [
2630
2630
  header10(`v0.7 passwordPolicy: blocklisted password for ${endpoint.method} ${endpoint.path}`),
2631
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'Writ: password is on the blocklist',tag:'${esc9(tag2)}',tag:'writ-rule-password-policy',chain"`,
2631
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'x-security: password is on the blocklist',tag:'${esc9(tag2)}',tag:'x-security-rule-password-policy',chain"`,
2632
2632
  ` SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "chain"`,
2633
2633
  ` SecRule ${selector} "@rx ${escRx2(`(?i)^(?:${alt})$`)}"${term}`
2634
2634
  ].join("\n")
@@ -2658,7 +2658,7 @@ function buildAccountLockoutRules(endpoint, profile = CORAZA_GO_PROFILE, warning
2658
2658
  });
2659
2659
  return [];
2660
2660
  }
2661
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2661
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2662
2662
  const rx = pathRegex(endpoint.path);
2663
2663
  const window2 = parseDurationSec(lock.window) || 900;
2664
2664
  const { expansion, keyPhase } = lockoutVar(lock.identifier);
@@ -2679,7 +2679,7 @@ populated); increment at phase:5 on a >=400 auth response. FULL on ${profile.nam
2679
2679
  `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base},phase:${keyPhase},pass,nolog,tag:'${esc9(tag2)}',${initcol},chain"`,
2680
2680
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${term}`,
2681
2681
  // 2. Deny while locked out — counter already over budget.
2682
- `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base + 1},phase:${keyPhase},deny,status:429,log,msg:'Writ: account locked (>${lock.attempts} failed logins / ${esc9(lock.window)})',tag:'${esc9(tag2)}',tag:'writ-rule-account-lockout',chain"`,
2682
+ `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base + 1},phase:${keyPhase},deny,status:429,log,msg:'x-security: account locked (>${lock.attempts} failed logins / ${esc9(lock.window)})',tag:'${esc9(tag2)}',tag:'x-security-rule-account-lockout',chain"`,
2683
2683
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
2684
2684
  ` SecRule GLOBAL:${counterKey} "@gt ${lock.attempts}"${term}`,
2685
2685
  // 3. On a failed-auth response (>=400), increment + refresh the TTL (phase:5).
@@ -2702,7 +2702,7 @@ function buildForbidArrayRootRules(endpoint, profile = CORAZA_GO_PROFILE, warnin
2702
2702
  });
2703
2703
  return [];
2704
2704
  }
2705
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2705
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2706
2706
  const rx = pathRegex(endpoint.path);
2707
2707
  const seed = endpointHash3(endpoint.method, `${endpoint.path}|arrayroot`);
2708
2708
  const id = FORBID_ARRAY_ROOT_BASE_ID + seed % 999;
@@ -2715,7 +2715,7 @@ JSON-hijacking defense (API3:2023). phase:4 RESPONSE_BODY @rx on the first
2715
2715
  non-whitespace byte; an array-rooted body is denied (wrap in an object instead).
2716
2716
  FULL on ${profile.name} (supportsResponseBodyAccess).`
2717
2717
  ),
2718
- `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'Writ: bare top-level JSON array response (forbidArrayRoot)',tag:'${esc9(tag2)}',tag:'writ-rule-forbid-array-root',chain"`,
2718
+ `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'x-security: bare top-level JSON array response (forbidArrayRoot)',tag:'${esc9(tag2)}',tag:'x-security-rule-forbid-array-root',chain"`,
2719
2719
  ` SecRule RESPONSE_BODY "@rx ^[\\s\\xef\\xbb\\xbf]*\\["${term}`
2720
2720
  ].join("\n")
2721
2721
  ];
@@ -2724,7 +2724,7 @@ function buildIdempotencyKeyRules(endpoint, profile = CORAZA_GO_PROFILE, warning
2724
2724
  const idem = endpoint.policy.request?.idempotencyKey;
2725
2725
  if (!idem) return [];
2726
2726
  const epId = `${endpoint.method} ${endpoint.path}`;
2727
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2727
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2728
2728
  const rx = pathRegex(endpoint.path);
2729
2729
  const ttl = parseDurationSec(idem.ttl) || 300;
2730
2730
  const seed = endpointHash3(endpoint.method, `${endpoint.path}|idempotency`);
@@ -2739,7 +2739,7 @@ function buildIdempotencyKeyRules(endpoint, profile = CORAZA_GO_PROFILE, warning
2739
2739
  `v0.7 idempotencyKey: require header ${idem.header} for ${endpoint.method} ${endpoint.path}
2740
2740
  (API6:2023). Header-presence check \u2014 FULL on ${profile.name}.`
2741
2741
  ),
2742
- `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base},phase:1,deny,status:400,log,msg:'Writ: missing ${esc9(idem.header)} header',tag:'${esc9(tag2)}',tag:'writ-rule-idempotency-key',chain"`,
2742
+ `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base},phase:1,deny,status:400,log,msg:'x-security: missing ${esc9(idem.header)} header',tag:'${esc9(tag2)}',tag:'x-security-rule-idempotency-key',chain"`,
2743
2743
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
2744
2744
  ` SecRule &REQUEST_HEADERS:${esc9(idem.header)} "@eq 0"${term}`
2745
2745
  ].join("\n")
@@ -2771,7 +2771,7 @@ flight races (no atomic check-and-set at the WAF; handle that in the store).`
2771
2771
  `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base + 2},phase:1,pass,nolog,tag:'${esc9(tag2)}',setvar:global.${counterKey}=+1,expirevar:global.${counterKey}=${ttl},chain"`,
2772
2772
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${term}`,
2773
2773
  // Deny the replay — second+ request with this key inside the ttl window.
2774
- `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base + 3},phase:1,deny,status:409,log,msg:'Writ: replayed idempotency key (${esc9(idem.header)})',tag:'${esc9(tag2)}',tag:'writ-rule-idempotency-key',chain"`,
2774
+ `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${base + 3},phase:1,deny,status:409,log,msg:'x-security: replayed idempotency key (${esc9(idem.header)})',tag:'${esc9(tag2)}',tag:'x-security-rule-idempotency-key',chain"`,
2775
2775
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
2776
2776
  ` SecRule GLOBAL:${counterKey} "@gt 1"${term}`
2777
2777
  ].join("\n")
@@ -2781,7 +2781,7 @@ flight races (no atomic check-and-set at the WAF; handle that in the store).`
2781
2781
  function buildLoggingRules(endpoint, profile = CORAZA_GO_PROFILE) {
2782
2782
  const log = endpoint.policy.logging;
2783
2783
  if (!log || !Array.isArray(log.events) || log.events.length === 0) return [];
2784
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
2784
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
2785
2785
  const rx = pathRegex(endpoint.path);
2786
2786
  const seed = endpointHash3(endpoint.method, `${endpoint.path}|logging`);
2787
2787
  const id = LOGGING_BASE_ID + seed % 999;
@@ -2791,7 +2791,7 @@ function buildLoggingRules(endpoint, profile = CORAZA_GO_PROFILE) {
2791
2791
  const notes = [];
2792
2792
  notes.push(`events declared: ${log.events.join(", ")}`);
2793
2793
  notes.push(`auth-failure / authz-deny / injection-block / rate-limit-trip are`);
2794
- notes.push(`already audit-logged by the corresponding Writ deny rules.`);
2794
+ notes.push(`already audit-logged by the corresponding x-security deny rules.`);
2795
2795
  if (log.sink && log.sink !== "stdout") {
2796
2796
  notes.push(`sink='${log.sink}' is NOT enforced at the WAF \u2014 configure it at the`);
2797
2797
  notes.push(`log-shipping layer (SecAuditLogStorageDir + syslog/fluent-bit sidecar).`);
@@ -2813,7 +2813,7 @@ function buildLoggingRules(endpoint, profile = CORAZA_GO_PROFILE) {
2813
2813
  `v0.7 logging (SSEC-AUDIT): force audit log for ${endpoint.method} ${endpoint.path}
2814
2814
  ` + notes.join("\n")
2815
2815
  ),
2816
- `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${id},phase:5,pass,log,auditlog,msg:'Writ: audit (request/response) for ${esc9(endpoint.method)} ${esc9(endpoint.path)}',tag:'${esc9(tag2)}',tag:'writ-audit'${partsCtl},chain"`,
2816
+ `SecRule REQUEST_FILENAME "@rx ${escRx2(rx)}" "id:${id},phase:5,pass,log,auditlog,msg:'x-security: audit (request/response) for ${esc9(endpoint.method)} ${esc9(endpoint.path)}',tag:'${esc9(tag2)}',tag:'x-security-audit'${partsCtl},chain"`,
2817
2817
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${term}`
2818
2818
  ].join("\n")
2819
2819
  );
@@ -2907,7 +2907,7 @@ function buildContentType(ctx, allowed, profile = CORAZA_GO_PROFILE) {
2907
2907
  const term = chainTerm8(profile);
2908
2908
  return [
2909
2909
  header11(`request.contentType allowlist for ${endpoint.method} ${endpoint.path}`),
2910
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:415,msg:'Writ: unsupported Content-Type',tag:'${esc10(tag2)}',chain"`,
2910
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:415,msg:'x-security: unsupported Content-Type',tag:'${esc10(tag2)}',chain"`,
2911
2911
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
2912
2912
  ` SecRule REQUEST_HEADERS:Content-Type "!@rx ^(${alt})(;.*)?$"${term}`
2913
2913
  ].join("\n");
@@ -2918,7 +2918,7 @@ function buildBodySize(ctx, bytes, profile = CORAZA_GO_PROFILE) {
2918
2918
  const term = chainTerm8(profile);
2919
2919
  return [
2920
2920
  header11(`request.maxBodySize=${bytes} bytes for ${endpoint.method} ${endpoint.path}`),
2921
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:413,msg:'Writ: request body exceeds endpoint limit',tag:'${esc10(tag2)}',chain"`,
2921
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:413,msg:'x-security: request body exceeds endpoint limit',tag:'${esc10(tag2)}',chain"`,
2922
2922
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
2923
2923
  ` SecRule REQUEST_HEADERS:Content-Length "@gt ${bytes}"${term}`
2924
2924
  ].join("\n");
@@ -2930,7 +2930,7 @@ function buildAuth(ctx, headerName, profile = CORAZA_GO_PROFILE) {
2930
2930
  const term = chainTerm8(profile);
2931
2931
  return [
2932
2932
  header11(`authentication required (${hdr}) for ${endpoint.method} ${endpoint.path}`),
2933
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:401,msg:'Writ: missing ${esc10(hdr)} header',tag:'${esc10(tag2)}',chain"`,
2933
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:401,msg:'x-security: missing ${esc10(hdr)} header',tag:'${esc10(tag2)}',chain"`,
2934
2934
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
2935
2935
  ` SecRule &REQUEST_HEADERS:${hdr} "@eq 0"${term}`
2936
2936
  ].join("\n");
@@ -2946,7 +2946,7 @@ function buildIpPolicy(ctx, ipPolicy, profile = CORAZA_GO_PROFILE) {
2946
2946
  out.push(
2947
2947
  [
2948
2948
  header11(`ipPolicy.allow for ${endpoint.method} ${endpoint.path}`),
2949
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'Writ: source IP not in allowlist',tag:'${esc10(tag2)}',chain"`,
2949
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'x-security: source IP not in allowlist',tag:'${esc10(tag2)}',chain"`,
2950
2950
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
2951
2951
  ` SecRule REMOTE_ADDR "!@ipMatch ${allow.join(",")}"${term}`
2952
2952
  ].join("\n")
@@ -2957,7 +2957,7 @@ function buildIpPolicy(ctx, ipPolicy, profile = CORAZA_GO_PROFILE) {
2957
2957
  out.push(
2958
2958
  [
2959
2959
  header11(`ipPolicy.deny for ${endpoint.method} ${endpoint.path}`),
2960
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'Writ: source IP in denylist',tag:'${esc10(tag2)}',chain"`,
2960
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'x-security: source IP in denylist',tag:'${esc10(tag2)}',chain"`,
2961
2961
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
2962
2962
  ` SecRule REMOTE_ADDR "@ipMatch ${deny.join(",")}"${term}`
2963
2963
  ].join("\n")
@@ -3085,7 +3085,7 @@ pattern: initcol \u2192 setvar/expirevar \u2192 @gt check (Coraza v3 collection
3085
3085
  `SecRule REQUEST_URI "@rx ${pathRx}" "id:${counterId},phase:1,pass,nolog,tag:'${esc10(tag2)}',setvar:${col}.${counterKey}=+1,expirevar:${col}.${counterKey}=${window2},chain"`,
3086
3086
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${chainTerm9}`,
3087
3087
  // 3. check — deny when counter exceeds the configured limit.
3088
- `SecRule REQUEST_URI "@rx ${pathRx}" "id:${checkId},phase:1,deny,status:429,msg:'Writ: rate limit exceeded (${rl.requests}/${esc10(rl.window)})',tag:'${esc10(tag2)}',log,chain"`,
3088
+ `SecRule REQUEST_URI "@rx ${pathRx}" "id:${checkId},phase:1,deny,status:429,msg:'x-security: rate limit exceeded (${rl.requests}/${esc10(rl.window)})',tag:'${esc10(tag2)}',log,chain"`,
3089
3089
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
3090
3090
  ` SecRule ${col.toUpperCase()}:${counterKey} "@gt ${rl.requests}"${chainTerm9}`
3091
3091
  ];
@@ -3102,7 +3102,7 @@ pattern: initcol \u2192 setvar/expirevar \u2192 @gt check (Coraza v3 collection
3102
3102
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${chainTerm9}`,
3103
3103
  `SecRule REQUEST_URI "@rx ${pathRx}" "id:${burstCounterId},phase:1,pass,nolog,tag:'${esc10(tag2)}',setvar:${col}.${burstCounterKey}=+1,expirevar:${col}.${burstCounterKey}=1,chain"`,
3104
3104
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${chainTerm9}`,
3105
- `SecRule REQUEST_URI "@rx ${pathRx}" "id:${burstCheckId},phase:1,deny,status:429,msg:'Writ: rate limit burst exceeded (${rl.burst}/1s)',tag:'${esc10(tag2)}',log,chain"`,
3105
+ `SecRule REQUEST_URI "@rx ${pathRx}" "id:${burstCheckId},phase:1,deny,status:429,msg:'x-security: rate limit burst exceeded (${rl.burst}/1s)',tag:'${esc10(tag2)}',log,chain"`,
3106
3106
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
3107
3107
  ` SecRule ${col.toUpperCase()}:${burstCounterKey} "@gt ${rl.burst}"${chainTerm9}`
3108
3108
  );
@@ -3120,7 +3120,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3120
3120
  rules.push(
3121
3121
  [
3122
3122
  header11(`request.schema.${field}.minLength=${ps.minLength} for ${endpoint.method} ${endpoint.path}`),
3123
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ: field ${esc10(field)} too short',tag:'${esc10(tag2)}',chain"`,
3123
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security: field ${esc10(field)} too short',tag:'${esc10(tag2)}',chain"`,
3124
3124
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3125
3125
  ` SecRule ARGS:${field} "@lt ${ps.minLength}" "t:length"`
3126
3126
  ].join("\n")
@@ -3131,7 +3131,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3131
3131
  rules.push(
3132
3132
  [
3133
3133
  header11(`request.schema.${field}.maxLength=${ps.maxLength} for ${endpoint.method} ${endpoint.path}`),
3134
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ: field ${esc10(field)} too long',tag:'${esc10(tag2)}',chain"`,
3134
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security: field ${esc10(field)} too long',tag:'${esc10(tag2)}',chain"`,
3135
3135
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3136
3136
  ` SecRule ARGS:${field} "@gt ${ps.maxLength}" "t:length"`
3137
3137
  ].join("\n")
@@ -3142,7 +3142,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3142
3142
  rules.push(
3143
3143
  [
3144
3144
  header11(`request.schema.${field}.fixedLength=${ps.fixedLength} for ${endpoint.method} ${endpoint.path}`),
3145
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ: field ${esc10(field)} wrong length',tag:'${esc10(tag2)}',chain"`,
3145
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security: field ${esc10(field)} wrong length',tag:'${esc10(tag2)}',chain"`,
3146
3146
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3147
3147
  ` SecRule ARGS:${field} "!@eq ${ps.fixedLength}" "t:length"`
3148
3148
  ].join("\n")
@@ -3153,7 +3153,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3153
3153
  rules.push(
3154
3154
  [
3155
3155
  header11(`request.schema.${field}.min=${ps.min} for ${endpoint.method} ${endpoint.path}`),
3156
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ: field ${esc10(field)} below min',tag:'${esc10(tag2)}',chain"`,
3156
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security: field ${esc10(field)} below min',tag:'${esc10(tag2)}',chain"`,
3157
3157
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3158
3158
  ` SecRule ARGS:${field} "@lt ${ps.min}"${term}`
3159
3159
  ].join("\n")
@@ -3164,7 +3164,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3164
3164
  rules.push(
3165
3165
  [
3166
3166
  header11(`request.schema.${field}.max=${ps.max} for ${endpoint.method} ${endpoint.path}`),
3167
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ: field ${esc10(field)} above max',tag:'${esc10(tag2)}',chain"`,
3167
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security: field ${esc10(field)} above max',tag:'${esc10(tag2)}',chain"`,
3168
3168
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3169
3169
  ` SecRule ARGS:${field} "@gt ${ps.max}"${term}`
3170
3170
  ].join("\n")
@@ -3175,7 +3175,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3175
3175
  rules.push(
3176
3176
  [
3177
3177
  header11(`request.schema.${field}.pattern for ${endpoint.method} ${endpoint.path}`),
3178
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ: field ${esc10(field)} pattern mismatch',tag:'${esc10(tag2)}',chain"`,
3178
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security: field ${esc10(field)} pattern mismatch',tag:'${esc10(tag2)}',chain"`,
3179
3179
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3180
3180
  ` SecRule ARGS:${field} "!@rx ${esc10(ps.pattern)}"${term}`
3181
3181
  ].join("\n")
@@ -3188,7 +3188,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3188
3188
  rules.push(
3189
3189
  [
3190
3190
  header11(`request.schema.${field}.type=${ps.type} for ${endpoint.method} ${endpoint.path}`),
3191
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'Writ: field ${esc10(field)} not ${spec.what}',tag:'${esc10(tag2)}',chain"`,
3191
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:400,msg:'x-security: field ${esc10(field)} not ${spec.what}',tag:'${esc10(tag2)}',chain"`,
3192
3192
  // REQUEST_FILENAME (path-only), not REQUEST_URI (path+query): an
3193
3193
  // anchored `^/path$` against REQUEST_URI never matches once the
3194
3194
  // request carries a query string, so the type check would silently
@@ -3206,7 +3206,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3206
3206
  rules.push(
3207
3207
  [
3208
3208
  header11(`request.schema.${field}.allowedMimeTypes (request Content-Type) for ${endpoint.method} ${endpoint.path}`),
3209
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${ctId},phase:1,deny,status:415,msg:'Writ: field ${esc10(field)} Content-Type not in allowedMimeTypes',tag:'${esc10(tag2)}',chain"`,
3209
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${ctId},phase:1,deny,status:415,msg:'x-security: field ${esc10(field)} Content-Type not in allowedMimeTypes',tag:'${esc10(tag2)}',chain"`,
3210
3210
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3211
3211
  ` SecRule REQUEST_HEADERS:Content-Type "!@rx ^(${alt})(;.*)?$"${term}`
3212
3212
  ].join("\n")
@@ -3215,7 +3215,7 @@ function buildSchemaRules(ctx, schema, profile = CORAZA_GO_PROFILE) {
3215
3215
  rules.push(
3216
3216
  [
3217
3217
  header11(`request.schema.${field}.allowedMimeTypes (multipart part MIME) for ${endpoint.method} ${endpoint.path}`),
3218
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${fileId},phase:2,deny,status:415,msg:'Writ: field ${esc10(field)} upload MIME not allowed',tag:'${esc10(tag2)}',chain"`,
3218
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${fileId},phase:2,deny,status:415,msg:'x-security: field ${esc10(field)} upload MIME not allowed',tag:'${esc10(tag2)}',chain"`,
3219
3219
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3220
3220
  ` SecRule FILES_TMP_CONTENT:${field}|FILES:${field} "!@rx ^(${alt})$"${term}`
3221
3221
  ].join("\n")
@@ -3228,7 +3228,7 @@ function buildSsrfRules(endpoint, profile = CORAZA_GO_PROFILE, phase = 2) {
3228
3228
  const schema = endpoint.policy.request?.schema;
3229
3229
  if (!schema) return [];
3230
3230
  const term = chainTerm8(profile);
3231
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
3231
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
3232
3232
  const rules = [];
3233
3233
  const slot = endpointHash3(endpoint.method, endpoint.path) % 4500;
3234
3234
  let offset = 0;
@@ -3244,7 +3244,7 @@ function buildSsrfRules(endpoint, profile = CORAZA_GO_PROFILE, phase = 2) {
3244
3244
  rules.push(
3245
3245
  [
3246
3246
  header11(`W19-A SSRF: ${field} must match domainAllowlist for ${endpoint.method} ${endpoint.path}`),
3247
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:${phase},deny,status:403,msg:'Writ: ${esc10(field)} host not in domainAllowlist',tag:'${esc10(tag2)}',tag:'writ-rule-ssrf-403',chain"`,
3247
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:${phase},deny,status:403,msg:'x-security: ${esc10(field)} host not in domainAllowlist',tag:'${esc10(tag2)}',tag:'x-security-rule-ssrf-403',chain"`,
3248
3248
  // W22-B: REQUEST_FILENAME (path-only), not REQUEST_URI. libmodsec3's
3249
3249
  // REQUEST_URI is path *+ query string* — an anchored `^/path$` rx
3250
3250
  // never matches once the endpoint receives the very ?url= it is
@@ -3263,7 +3263,7 @@ function buildSsrfRules(endpoint, profile = CORAZA_GO_PROFILE, phase = 2) {
3263
3263
  rules.push(
3264
3264
  [
3265
3265
  header11(`W19-A SSRF: ${field} blockPrivateRanges for ${endpoint.method} ${endpoint.path}`),
3266
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:${phase},deny,status:403,msg:'Writ: ${esc10(field)} resolves to private/loopback host',tag:'${esc10(tag2)}',tag:'writ-rule-ssrf-private-403',chain"`,
3266
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:${phase},deny,status:403,msg:'x-security: ${esc10(field)} resolves to private/loopback host',tag:'${esc10(tag2)}',tag:'x-security-rule-ssrf-private-403',chain"`,
3267
3267
  // W22-B: REQUEST_FILENAME — see comment on the domainAllowlist rule above.
3268
3268
  ` SecRule REQUEST_FILENAME "@rx ${pathRegex(endpoint.path)}" "chain"`,
3269
3269
  // W22-B: escRx — see comment on escRx. Same reasoning as the allowlist rule.
@@ -3281,7 +3281,7 @@ function hasJsonContentType(contentTypes) {
3281
3281
  function buildJsonBodyProcessor(endpoint, profile = CORAZA_GO_PROFILE) {
3282
3282
  const policy = endpoint.policy;
3283
3283
  if (!hasJsonContentType(policy.request?.contentType)) return null;
3284
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
3284
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
3285
3285
  const ctlId = BODY_ALLOWLIST_BASE_ID + 1e4 + endpointHash3(endpoint.method, endpoint.path) % 9e3;
3286
3286
  const term = chainTerm8(profile);
3287
3287
  return [
@@ -3320,7 +3320,7 @@ function buildBodyFieldAllowlistRules(endpoint, profile = CORAZA_GO_PROFILE) {
3320
3320
  }
3321
3321
  }
3322
3322
  if (safe.length === 0) return [];
3323
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
3323
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
3324
3324
  const id = BODY_ALLOWLIST_BASE_ID + endpointHash3(endpoint.method, endpoint.path) % 9e3;
3325
3325
  const alt = safe.join("|");
3326
3326
  const argsRegex = `^json\\.(${alt})$`;
@@ -3344,7 +3344,7 @@ function buildBodyFieldAllowlistRules(endpoint, profile = CORAZA_GO_PROFILE) {
3344
3344
  out.push(
3345
3345
  [
3346
3346
  header11(headerLines.join("\n")),
3347
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'Writ: request body contains field outside allowlist (mass-assignment)',tag:'${esc10(tag2)}',tag:'writ-api6-mass-assignment',chain"`,
3347
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'x-security: request body contains field outside allowlist (mass-assignment)',tag:'${esc10(tag2)}',tag:'x-security-api6-mass-assignment',chain"`,
3348
3348
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3349
3349
  // Content-Type guard: only enforce the json.<key> allowlist when the body
3350
3350
  // is actually JSON. Without this, a POST carrying query-string/form args
@@ -3380,7 +3380,7 @@ function buildResponseInspectionRules(endpoint, profile = CORAZA_GO_PROFILE, war
3380
3380
  reason: `response inspection enabled (phase 4); expect ~10-15% throughput cost on libmodsecurity3 and an extra SPOE round-trip on coraza-spoa. Acceptable for high-value endpoints; disable response.schema on hot paths if latency budget is tight.`,
3381
3381
  detail: { mechanism: "SecResponseBodyAccess", phase: 4 }
3382
3382
  });
3383
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
3383
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
3384
3384
  const base = RESPONSE_INSPECT_BASE_ID + endpointHash3(endpoint.method, endpoint.path) % 9e3 * 1;
3385
3385
  const pathRx = pathRegex(endpoint.path);
3386
3386
  const term = chainTerm8(profile);
@@ -3399,16 +3399,16 @@ function buildResponseInspectionRules(endpoint, profile = CORAZA_GO_PROFILE, war
3399
3399
  `C-1 response.schema.${field}.maxLength=${ps.maxLength} for ${endpoint.method} ${endpoint.path}
3400
3400
  phase:4 inspects RESPONSE_BODY for "${field}":"<value of length > ${cap2}>". Heuristic regex-over-JSON; nested / escaped values may not match.`
3401
3401
  ),
3402
- `SecRule REQUEST_URI "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'Writ: response.${esc10(field)} exceeds maxLength=${ps.maxLength} (data exposure)',tag:'${esc10(tag2)}',tag:'writ-api3-bopla',chain"`,
3402
+ `SecRule REQUEST_URI "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'x-security: response.${esc10(field)} exceeds maxLength=${ps.maxLength} (data exposure)',tag:'${esc10(tag2)}',tag:'x-security-api3-bopla',chain"`,
3403
3403
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
3404
- ` SecRule RESPONSE_BODY "@rx \\"${esc10(field)}\\"\\s*:\\s*\\"[^\\"]{${cap2 + 1},}\\""${term}`
3404
+ ` SecRule RESPONSE_BODY "@rx \\x22${esc10(field)}\\x22\\s*:\\s*\\x22[^\\x22]{${cap2 + 1},}\\x22"${term}`
3405
3405
  ].join("\n")
3406
3406
  );
3407
3407
  }
3408
3408
  if (ps.pattern) {
3409
3409
  const captureId = nextId++;
3410
3410
  const denyId = nextId++;
3411
- const txVar = `writ_${safeVarName2(field)}`;
3411
+ const txVar = `x_security_${safeVarName2(field)}`;
3412
3412
  rules.push(
3413
3413
  [
3414
3414
  header11(
@@ -3417,7 +3417,7 @@ phase:4 rule A: extract response.${field} value into TX:${txVar} for the inverse
3417
3417
  ),
3418
3418
  `SecRule REQUEST_URI "@rx ${pathRx}" "id:${captureId},phase:4,pass,nolog,chain"`,
3419
3419
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
3420
- ` SecRule RESPONSE_BODY "@rx \\"${esc10(field)}\\"\\s*:\\s*\\"([^\\"]*)\\"" "capture,setvar:tx.${txVar}=%{TX.1}${term ? ",t:none" : ""}"`
3420
+ ` SecRule RESPONSE_BODY "@rx \\x22${esc10(field)}\\x22\\s*:\\s*\\x22([^\\x22]*)\\x22" "capture,setvar:tx.${txVar}=%{TX.1}${term ? ",t:none" : ""}"`
3421
3421
  ].join("\n")
3422
3422
  );
3423
3423
  rules.push(
@@ -3426,7 +3426,7 @@ phase:4 rule A: extract response.${field} value into TX:${txVar} for the inverse
3426
3426
  `C-1 response.schema.${field}.pattern (deny) for ${endpoint.method} ${endpoint.path}
3427
3427
  phase:4 rule B: deny when TX:${txVar} does not match the required pattern (RE2-safe; no lookahead)`
3428
3428
  ),
3429
- `SecRule TX:${txVar} "!@rx ${esc10(ps.pattern)}" "id:${denyId},phase:4,deny,status:500,msg:'Writ: response.${esc10(field)} pattern mismatch (data exposure)',tag:'${esc10(tag2)}',tag:'writ-api3-bopla'"`
3429
+ `SecRule TX:${txVar} "!@rx ${esc10(ps.pattern)}" "id:${denyId},phase:4,deny,status:500,msg:'x-security: response.${esc10(field)} pattern mismatch (data exposure)',tag:'${esc10(tag2)}',tag:'x-security-api3-bopla'"`
3430
3430
  ].join("\n")
3431
3431
  );
3432
3432
  }
@@ -3440,14 +3440,14 @@ phase:4 rule B: deny when TX:${txVar} does not match the required pattern (RE2-s
3440
3440
  `C-1 stored-XSS guard for ${endpoint.method} ${endpoint.path}
3441
3441
  phase:4 \u2014 response.contentType is application/json, so raw <script> or
3442
3442
  javascript: URIs in the body indicate a stored-XSS echo. msg carries
3443
- 'writ-xss-stored' for scorer attribution.`
3443
+ 'x-security-xss-stored' for scorer attribution.`
3444
3444
  ),
3445
3445
  // REQUEST_FILENAME (path-only) — REQUEST_URI on Coraza/libmodsec3
3446
3446
  // includes the query string, so an anchored `^/path$` rx fails for
3447
3447
  // any request carrying `?foo=bar`. The vAPI XSS attack hits
3448
3448
  // /vapi/stickynotes?format=html; REQUEST_FILENAME strips the query
3449
3449
  // and matches cleanly. (Same fix W22-B applied to the SSRF rules.)
3450
- `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'Writ: stored-XSS payload in JSON response body',tag:'${esc10(tag2)}',tag:'writ-xss-stored',chain"`,
3450
+ `SecRule REQUEST_FILENAME "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'x-security: stored-XSS payload in JSON response body',tag:'${esc10(tag2)}',tag:'x-security-xss-stored',chain"`,
3451
3451
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
3452
3452
  ` SecRule RESPONSE_BODY "@rx (?i)(?:<script\\b|javascript:)"${term}`
3453
3453
  ].join("\n")
@@ -3486,9 +3486,9 @@ phase:4 denies when the response body contains a top-level JSON key NOT in the d
3486
3486
  Note: this is a deny-on-unknown rule, NOT a strip \u2014 true stripping requires a Lua plugin.
3487
3487
  PCRE-only (uses negative lookahead); RE2-backed engines (coraza-go/spoa) skip this rule.`
3488
3488
  ),
3489
- `SecRule REQUEST_URI "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'Writ: response contains undeclared field (stripUnknownFields)',tag:'${esc10(tag2)}',tag:'writ-api3-bopla',chain"`,
3489
+ `SecRule REQUEST_URI "@rx ${pathRx}" "id:${id},phase:4,deny,status:500,msg:'x-security: response contains undeclared field (stripUnknownFields)',tag:'${esc10(tag2)}',tag:'x-security-api3-bopla',chain"`,
3490
3490
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
3491
- ` SecRule RESPONSE_BODY "@rx \\"(?!(?:${allowAlt})\\")[A-Za-z_][A-Za-z0-9_]*\\"\\s*:"${term}`
3491
+ ` SecRule RESPONSE_BODY "@rx \\x22(?!(?:${allowAlt})\\x22)[A-Za-z_][A-Za-z0-9_]*\\x22\\s*:"${term}`
3492
3492
  ].join("\n")
3493
3493
  );
3494
3494
  }
@@ -3503,7 +3503,7 @@ function buildSqliHeuristics(endpoint, profile = CORAZA_GO_PROFILE) {
3503
3503
  const policy = endpoint.policy;
3504
3504
  const req = policy.request;
3505
3505
  if (!req || !req.schema) return [];
3506
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
3506
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
3507
3507
  const term = chainTerm8(profile);
3508
3508
  const out = [];
3509
3509
  for (const [field, ps] of Object.entries(req.schema)) {
@@ -3516,7 +3516,7 @@ function buildSqliHeuristics(endpoint, profile = CORAZA_GO_PROFILE) {
3516
3516
  const m3 = INJECTION_SINK_MATCHERS[sink];
3517
3517
  const idSeed = endpointHash3(`${endpoint.method}|${endpoint.path}|${field}|${sink}`, "");
3518
3518
  const id = INJECTION_GUARD_BASE_ID + idSeed % 9e3;
3519
- const classTag = `writ-${m3.category}`;
3519
+ const classTag = `x-security-${m3.category}`;
3520
3520
  const ssecId = m3.category === "prompt" ? "SSEC-PROMPT" : "SSEC-INJECTION";
3521
3521
  out.push(
3522
3522
  [
@@ -3525,7 +3525,7 @@ function buildSqliHeuristics(endpoint, profile = CORAZA_GO_PROFILE) {
3525
3525
  Author-declared injectionGuard sink; phase:2 denies the payload at write
3526
3526
  time over the body/query field. Attributed to ${ssecId} by the reporter.`
3527
3527
  ),
3528
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'Writ: ${m3.msg} detected in ${esc10(field)}',tag:'${esc10(tag2)}',tag:'${classTag}',tag:'${classTag}-${m3.tagSuffix}',chain"`,
3528
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'x-security: ${m3.msg} detected in ${esc10(field)}',tag:'${esc10(tag2)}',tag:'${classTag}',tag:'${classTag}-${m3.tagSuffix}',chain"`,
3529
3529
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3530
3530
  ` SecRule ARGS:${field}|ARGS:json.${field} "${m3.operator}"${term}`
3531
3531
  ].join("\n")
@@ -3539,7 +3539,7 @@ function buildXssHeuristics(endpoint, profile = CORAZA_GO_PROFILE) {
3539
3539
  const req = policy.request;
3540
3540
  if (!req || !req.schema) return [];
3541
3541
  if (!hasJsonContentType(req.contentType)) return [];
3542
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
3542
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
3543
3543
  const term = chainTerm8(profile);
3544
3544
  const out = [];
3545
3545
  for (const [field, ps] of Object.entries(req.schema)) {
@@ -3554,7 +3554,7 @@ phase:2 \u2014 denies when the free-text field carries a literal <script tag
3554
3554
  or javascript: URI. Stops the payload at write time so the GET-side echo
3555
3555
  (which coraza-spoa cannot inspect \u2014 no response body via SPOE) is moot.`
3556
3556
  ),
3557
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'Writ: stored-XSS payload in ${esc10(field)}',tag:'${esc10(tag2)}',tag:'writ-xss-stored',chain"`,
3557
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'x-security: stored-XSS payload in ${esc10(field)}',tag:'${esc10(tag2)}',tag:'x-security-xss-stored',chain"`,
3558
3558
  ` SecRule REQUEST_URI "@rx ${pathRegex(endpoint.path)}" "chain"`,
3559
3559
  ` SecRule ARGS:json.${field} "@rx (?i)(?:<script\\b|javascript:)"${term}`
3560
3560
  ].join("\n")
@@ -3565,7 +3565,7 @@ or javascript: URI. Stops the payload at write time so the GET-side echo
3565
3565
  function buildPolicyRules(endpoint, profile = CORAZA_GO_PROFILE, warnings) {
3566
3566
  const policy = endpoint.policy;
3567
3567
  const base = ruleBase(endpoint);
3568
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
3568
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
3569
3569
  const ctx = { endpoint, base, tag: tag2 };
3570
3570
  const out = [];
3571
3571
  out.push(buildScopeMarker(ctx));
@@ -3637,7 +3637,7 @@ var init_rules = __esm({
3637
3637
  RESPONSE_INSPECT_BASE_ID = 42e4;
3638
3638
  SLOT = {
3639
3639
  scope: 0,
3640
- // path/method match flag (tx.writ_match)
3640
+ // path/method match flag (tx.x_security_match)
3641
3641
  ctype: 1,
3642
3642
  // content-type allowlist
3643
3643
  bodySize: 2,
@@ -3942,7 +3942,7 @@ function buildHaproxyStickTables(spec, engine, warnings, peers = []) {
3942
3942
  const key = resolveKey(rl, engine, epId);
3943
3943
  if (key.warning) warnings.push(key.warning);
3944
3944
  const slug = slugify(ep.method, ep.path) + (rls.length > 1 ? `_${idx}` : "");
3945
- const backendName = `st_writ_${slug}`;
3945
+ const backendName = `st_x_security_${slug}`;
3946
3946
  if (seenBackends.has(backendName)) return;
3947
3947
  seenBackends.add(backendName);
3948
3948
  const peersClause = peers.length > 0 ? ` peers ${PEER_GROUP}` : "";
@@ -4000,7 +4000,7 @@ ${burstMethodLine}`,
4000
4000
  }
4001
4001
  if (emissions.length === 0) return null;
4002
4002
  const out = [];
4003
- out.push("# Writ \u2192 HAProxy stick-tables \u2014 auto-generated. DO NOT EDIT BY HAND.");
4003
+ out.push("# x-security \u2192 HAProxy stick-tables \u2014 auto-generated. DO NOT EDIT BY HAND.");
4004
4004
  out.push(`# engine: ${engine}`);
4005
4005
  out.push(`# source: ${spec.info.title} ${spec.info.version}`);
4006
4006
  out.push("#");
@@ -4011,7 +4011,7 @@ ${burstMethodLine}`,
4011
4011
  out.push("#");
4012
4012
  out.push("# How to integrate into an EXISTING haproxy.cfg:");
4013
4013
  out.push("#");
4014
- out.push("# 1. Append every `backend st_writ_*` block below to your");
4014
+ out.push("# 1. Append every `backend st_x_security_*` block below to your");
4015
4015
  out.push("# haproxy.cfg (they are self-contained \u2014 no listener needed).");
4016
4016
  out.push("# 2. Inside your existing `frontend` block, paste the ACL/track/deny");
4017
4017
  out.push('# snippet from the "WRIT FRONTEND SNIPPET" section.');
@@ -4021,7 +4021,7 @@ ${burstMethodLine}`,
4021
4021
  if (peers.length > 0) {
4022
4022
  out.push("# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
4023
4023
  out.push(`# Peer replication (group: ${PEER_GROUP}) \u2014 ${peers.length} instance(s)`);
4024
- out.push("# Each stick-table below references `peers writ` so HAProxy");
4024
+ out.push("# Each stick-table below references `peers x-security` so HAProxy");
4025
4025
  out.push("# replicates counters between the listed nodes in near-real-time.");
4026
4026
  out.push("# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
4027
4027
  out.push(`peers ${PEER_GROUP}`);
@@ -4058,7 +4058,7 @@ var init_haproxy_stick_tables = __esm({
4058
4058
  "src/generators/coraza/templates/haproxy-stick-tables.ts"() {
4059
4059
  "use strict";
4060
4060
  init_rules();
4061
- PEER_GROUP = "writ";
4061
+ PEER_GROUP = "x-security";
4062
4062
  }
4063
4063
  });
4064
4064
 
@@ -4130,7 +4130,7 @@ function buildModsecNginxServerConf(spec) {
4130
4130
  }
4131
4131
  if (!tlsFloor && !ciphers && endpointBlocks.length === 0) return null;
4132
4132
  const lines = [
4133
- "# Writ \u2192 modsec-nginx server-side directives \u2014 auto-generated.",
4133
+ "# x-security \u2192 modsec-nginx server-side directives \u2014 auto-generated.",
4134
4134
  "# Merge inside your `server { ... }` block (not at http {} scope).",
4135
4135
  `# Source: ${spec.info.title} ${spec.info.version}`,
4136
4136
  ""
@@ -4195,12 +4195,12 @@ function smallestBodyLimit(endpoints) {
4195
4195
  function buildDirectives(spec, profile, warnings) {
4196
4196
  const lines = [];
4197
4197
  if (profile.name === "coraza-go") {
4198
- lines.push("# Writ \u2192 Coraza v3.x \u2014 auto-generated. DO NOT EDIT BY HAND.");
4199
- lines.push(`# generator: writ-coraza v${VERSION}`);
4198
+ lines.push("# x-security \u2192 Coraza v3.x \u2014 auto-generated. DO NOT EDIT BY HAND.");
4199
+ lines.push(`# generator: x-security-coraza v${VERSION}`);
4200
4200
  lines.push(`# source: ${spec.info.title} ${spec.info.version}`);
4201
4201
  } else {
4202
- lines.push("# Writ \u2192 Coraza/ModSecurity \u2014 auto-generated. DO NOT EDIT BY HAND.");
4203
- lines.push(`# generator: writ-coraza v${VERSION}`);
4202
+ lines.push("# x-security \u2192 Coraza/ModSecurity \u2014 auto-generated. DO NOT EDIT BY HAND.");
4203
+ lines.push(`# generator: x-security-coraza v${VERSION}`);
4204
4204
  lines.push(`# engine: ${profile.name}`);
4205
4205
  lines.push(`# source: ${spec.info.title} ${spec.info.version}`);
4206
4206
  }
@@ -4286,20 +4286,20 @@ function buildDirectives(spec, profile, warnings) {
4286
4286
  function buildIncludeSnippet(profile) {
4287
4287
  if (profile.name === "modsec-nginx") {
4288
4288
  return [
4289
- "# === Writ include for owasp/modsecurity-crs:nginx ===",
4289
+ "# === x-security include for owasp/modsecurity-crs:nginx ===",
4290
4290
  "#",
4291
- "# Mount writ.conf to /etc/modsecurity.d/writ.conf and use",
4291
+ "# Mount x-security.conf to /etc/modsecurity.d/x-security.conf and use",
4292
4292
  "# the entrypoint wrapper documented in deployment-recipes/modsec-nginx.md",
4293
4293
  "# to append this Include line after the image regenerates setup.conf:",
4294
4294
  "#",
4295
- "Include /etc/modsecurity.d/writ.conf",
4295
+ "Include /etc/modsecurity.d/x-security.conf",
4296
4296
  ""
4297
4297
  ].join("\n");
4298
4298
  }
4299
4299
  if (profile.name === "modsec-apache") {
4300
4300
  return [
4301
4301
  "# Drop into Apache config (e.g. /etc/apache2/mods-enabled/security2.conf):",
4302
- "Include /etc/modsecurity/writ.conf",
4302
+ "Include /etc/modsecurity/x-security.conf",
4303
4303
  ""
4304
4304
  ].join("\n");
4305
4305
  }
@@ -4307,7 +4307,7 @@ function buildIncludeSnippet(profile) {
4307
4307
  }
4308
4308
  function buildWarningsDoc(profile, warnings) {
4309
4309
  const lines = [
4310
- `# Writ \u2192 ${profile.name} \u2014 emission warnings`,
4310
+ `# x-security \u2192 ${profile.name} \u2014 emission warnings`,
4311
4311
  "",
4312
4312
  `Generator version: ${VERSION}`,
4313
4313
  `Engine profile: ${profile.name}`,
@@ -4368,21 +4368,21 @@ function createCorazaGenerator(opts = {}) {
4368
4368
  const artifacts = [];
4369
4369
  if (profile.fileExt === "conf") {
4370
4370
  artifacts.push({
4371
- path: "writ.conf",
4371
+ path: "x-security.conf",
4372
4372
  content: directives + "\n",
4373
4373
  format: "conf"
4374
4374
  });
4375
4375
  const snippet = buildIncludeSnippet(profile);
4376
4376
  if (snippet) {
4377
4377
  artifacts.push({
4378
- path: "writ-include.conf",
4378
+ path: "x-security-include.conf",
4379
4379
  content: snippet,
4380
4380
  format: "conf"
4381
4381
  });
4382
4382
  }
4383
4383
  } else {
4384
4384
  const meta = {
4385
- generator: "writ-coraza",
4385
+ generator: "x-security-coraza",
4386
4386
  version: VERSION
4387
4387
  };
4388
4388
  if (profile.name !== "coraza-go") meta["engine"] = profile.name;
@@ -4490,7 +4490,7 @@ function createCorazaGenerator(opts = {}) {
4490
4490
  // python pickle \x80 opcode frame) [v0.7]
4491
4491
  // ai-prompt → @rx LLM prompt-injection marker denylist (jailbreak /
4492
4492
  // system-prompt-leak / role-override). Distinct tag
4493
- // writ-prompt → SSEC-PROMPT (NOT SSEC-INJECTION) [v0.7]
4493
+ // x-security-prompt → SSEC-PROMPT (NOT SSEC-INJECTION) [v0.7]
4494
4494
  // Every sink is a real enforcing matcher on all four profiles (sql/xss
4495
4495
  // need the dedicated operator, present on all shipping engines; the rest
4496
4496
  // are plain RE2-safe @rx). → full.
@@ -4716,10 +4716,10 @@ function buildRateLimitSettings(rl, url, index) {
4716
4716
  if (primaryIdentifier(r5) === "user-id") {
4717
4717
  const zoneName = `lazy_user_${n2}`;
4718
4718
  const rate = rateLimitToBunkerRate(r5);
4719
- out[`CUSTOM_CONF_HTTP_LIMIT_REQ_USER_${n2}`] = `# Writ: per-user-id rate limit for ${url} (drift closure)
4719
+ out[`CUSTOM_CONF_HTTP_LIMIT_REQ_USER_${n2}`] = `# x-security: per-user-id rate limit for ${url} (drift closure)
4720
4720
  limit_req_zone $http_x_forwarded_user zone=${zoneName}:10m rate=${rate};
4721
4721
  `;
4722
- out[`WRIT_USER_RL_ZONE_${n2}`] = `${zoneName}@${url}`;
4722
+ out[`X_SECURITY_USER_RL_ZONE_${n2}`] = `${zoneName}@${url}`;
4723
4723
  }
4724
4724
  });
4725
4725
  return out;
@@ -4773,7 +4773,7 @@ function escMsec(s) {
4773
4773
  }
4774
4774
  function buildAuthModSecRules(auth) {
4775
4775
  const lines = [
4776
- `# Writ-generated authentication rules (${auth.type})`,
4776
+ `# x-security-generated authentication rules (${auth.type})`,
4777
4777
  // initcol is for ip/global/resource collections; tx is request-scoped and
4778
4778
  // does not need initcol. We just zero the relevant TX var directly.
4779
4779
  `SecAction "id:990000,phase:1,nolog,pass,setvar:tx.jwt_invalid=0"`
@@ -4784,7 +4784,7 @@ function buildAuthModSecRules(auth) {
4784
4784
  case "oauth2": {
4785
4785
  lines.push(
4786
4786
  `SecRule REQUEST_HEADERS:${escMsec(header15)} "@rx ^Bearer (.+)$" "id:990010,phase:1,nolog,pass,capture,setvar:tx.bearer_token=%{TX.1}"`,
4787
- `SecRule &TX:bearer_token "@eq 0" "id:990011,phase:1,deny,status:401,log,msg:'Writ: missing bearer token (signature NOT verified \u2014 external auth layer required)'"`
4787
+ `SecRule &TX:bearer_token "@eq 0" "id:990011,phase:1,deny,status:401,log,msg:'x-security: missing bearer token (signature NOT verified \u2014 external auth layer required)'"`
4788
4788
  );
4789
4789
  if (auth.type === "oauth2" && auth.scopes?.length) {
4790
4790
  lines.push(`# oauth2 required scopes (NOT enforced here \u2014 needs external auth): ${auth.scopes.join(" ")}`);
@@ -4794,13 +4794,13 @@ function buildAuthModSecRules(auth) {
4794
4794
  case "api-key": {
4795
4795
  lines.push(
4796
4796
  `SecRule REQUEST_HEADERS:${escMsec(header15)} "@rx ^.+$" "id:990020,phase:1,nolog,pass,setvar:tx.api_key_present=1"`,
4797
- `SecRule &TX:api_key_present "@eq 0" "id:990021,phase:1,deny,status:401,log,msg:'Writ: missing API key (${escMsec(header15)})'"`
4797
+ `SecRule &TX:api_key_present "@eq 0" "id:990021,phase:1,deny,status:401,log,msg:'x-security: missing API key (${escMsec(header15)})'"`
4798
4798
  );
4799
4799
  break;
4800
4800
  }
4801
4801
  case "basic": {
4802
4802
  lines.push(
4803
- `SecRule REQUEST_HEADERS:Authorization "!@rx ^Basic [A-Za-z0-9+/=]+$" "id:990030,phase:1,deny,status:401,log,msg:'Writ: missing/invalid Basic credentials'"`
4803
+ `SecRule REQUEST_HEADERS:Authorization "!@rx ^Basic [A-Za-z0-9+/=]+$" "id:990030,phase:1,deny,status:401,log,msg:'x-security: missing/invalid Basic credentials'"`
4804
4804
  );
4805
4805
  break;
4806
4806
  }
@@ -4826,13 +4826,13 @@ function buildAuthSettings(auth) {
4826
4826
  const header15 = auth.headerName ?? (auth.type === "api-key" ? "X-API-Key" : "Authorization");
4827
4827
  out.USE_MODSECURITY = "yes";
4828
4828
  out.CUSTOM_CONF_MODSEC_1 = buildAuthModSecRules(auth);
4829
- out.WRIT_AUTH_HEADER = header15;
4830
- out.WRIT_AUTH_TYPE = auth.type;
4831
- if (auth.jwksUri) out.WRIT_JWKS_URI = String(auth.jwksUri);
4832
- if (auth.issuer) out.WRIT_AUTH_ISSUER = String(auth.issuer);
4833
- if (auth.audience) out.WRIT_AUTH_AUDIENCE = String(auth.audience);
4829
+ out.X_SECURITY_AUTH_HEADER = header15;
4830
+ out.X_SECURITY_AUTH_TYPE = auth.type;
4831
+ if (auth.jwksUri) out.X_SECURITY_JWKS_URI = String(auth.jwksUri);
4832
+ if (auth.issuer) out.X_SECURITY_AUTH_ISSUER = String(auth.issuer);
4833
+ if (auth.audience) out.X_SECURITY_AUTH_AUDIENCE = String(auth.audience);
4834
4834
  if (auth.type === "oauth2" && auth.scopes?.length) {
4835
- out.WRIT_AUTH_SCOPES = auth.scopes.join(" ");
4835
+ out.X_SECURITY_AUTH_SCOPES = auth.scopes.join(" ");
4836
4836
  }
4837
4837
  const jwtSettings = buildJwtNativeSettings(auth);
4838
4838
  for (const [k5, v] of Object.entries(jwtSettings)) out[k5] = v;
@@ -4905,9 +4905,9 @@ function buildLifecycleRules2(endpoint) {
4905
4905
  const pathRx = pathRegex2(endpoint.path);
4906
4906
  const sunset = endpoint.policy.sunsetDate ? ` (sunset:${endpoint.policy.sunsetDate})` : "";
4907
4907
  const replacement = endpoint.policy.replacementEndpoint ? ` use ${endpoint.policy.replacementEndpoint}` : "";
4908
- const msg = `Writ: ${DEPRECATED_TAG} ${endpoint.method} ${endpoint.path}${sunset}${replacement}`;
4908
+ const msg = `x-security: ${DEPRECATED_TAG} ${endpoint.method} ${endpoint.path}${sunset}${replacement}`;
4909
4909
  const lines = [
4910
- `# Writ-generated lifecycle rule (deprecated endpoint \u2192 410 Gone)`,
4910
+ `# x-security-generated lifecycle rule (deprecated endpoint \u2192 410 Gone)`,
4911
4911
  `# Source: ${endpoint.method} ${endpoint.path}` + (endpoint.policy.sunsetDate ? ` (sunsetDate: ${endpoint.policy.sunsetDate})` : ""),
4912
4912
  `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:410,log,auditlog,msg:'${escMsec2(msg)}',tag:'${DEPRECATED_TAG}',chain"`,
4913
4913
  ` SecRule REQUEST_FILENAME "@rx ${escRx4(pathRx)}" "t:none"`
@@ -4919,7 +4919,7 @@ var init_lifecycle = __esm({
4919
4919
  "src/generators/bunkerweb/lifecycle.ts"() {
4920
4920
  "use strict";
4921
4921
  DEPRECATED_BASE_ID2 = 970500;
4922
- DEPRECATED_TAG = "writ-deprecated-endpoint-block";
4922
+ DEPRECATED_TAG = "x-security-deprecated-endpoint-block";
4923
4923
  }
4924
4924
  });
4925
4925
 
@@ -4959,24 +4959,24 @@ function buildAuthzMultiRoleRules(endpoint) {
4959
4959
  const idMissing = base;
4960
4960
  const idNoMatch = base + 1;
4961
4961
  const altRx = `(?:^|,)(?:${safeRoles.map((r5) => r5.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:,|$)`;
4962
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
4962
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
4963
4963
  const rolesStr = safeRoles.join(",");
4964
4964
  const lines = [];
4965
4965
  lines.push(
4966
4966
  [
4967
- `# Writ-generated authorization rules (rbac multi-role: ${rolesStr})`,
4967
+ `# x-security-generated authorization rules (rbac multi-role: ${rolesStr})`,
4968
4968
  `# Source: ${endpoint.method} ${endpoint.path}`,
4969
4969
  `# Chain on X-Forwarded-Groups (set by upstream OIDC sidecar / Kong+OIDC).`,
4970
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idMissing},phase:1,deny,status:403,log,auditlog,msg:'Writ rbac-multi-role denied (no ${TRUSTED_GROUPS_HEADER} header)',tag:'${escMsec3(tag2)}',tag:'writ-rule-rbac-multi-role',chain"`,
4970
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idMissing},phase:1,deny,status:403,log,auditlog,msg:'x-security rbac-multi-role denied (no ${TRUSTED_GROUPS_HEADER} header)',tag:'${escMsec3(tag2)}',tag:'x-security-rule-rbac-multi-role',chain"`,
4971
4971
  ` SecRule REQUEST_FILENAME "@rx ${escRx5(pathRx)}" "chain"`,
4972
4972
  ` SecRule &REQUEST_HEADERS:${TRUSTED_GROUPS_HEADER} "@eq 0" "t:none"`
4973
4973
  ].join("\n")
4974
4974
  );
4975
4975
  lines.push(
4976
4976
  [
4977
- `# Writ-generated authorization rules (rbac multi-role: ${rolesStr})`,
4977
+ `# x-security-generated authorization rules (rbac multi-role: ${rolesStr})`,
4978
4978
  `# Source: ${endpoint.method} ${endpoint.path}`,
4979
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idNoMatch},phase:1,deny,status:403,log,auditlog,msg:'Writ rbac-multi-role denied (${TRUSTED_GROUPS_HEADER} lacks any of: ${escMsec3(rolesStr)})',tag:'${escMsec3(tag2)}',tag:'writ-rule-rbac-multi-role',chain"`,
4979
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${idNoMatch},phase:1,deny,status:403,log,auditlog,msg:'x-security rbac-multi-role denied (${TRUSTED_GROUPS_HEADER} lacks any of: ${escMsec3(rolesStr)})',tag:'${escMsec3(tag2)}',tag:'x-security-rule-rbac-multi-role',chain"`,
4980
4980
  ` SecRule REQUEST_FILENAME "@rx ${escRx5(pathRx)}" "chain"`,
4981
4981
  ` SecRule REQUEST_HEADERS:${TRUSTED_GROUPS_HEADER} "!@rx ${escRx5(altRx)}" "t:none"`
4982
4982
  ].join("\n")
@@ -5034,16 +5034,16 @@ function buildPiiResponseRules(endpoint) {
5034
5034
  const fields = collectSensitiveFields(endpoint);
5035
5035
  if (fields.length === 0) return [];
5036
5036
  const pathRx = pathRegex4(endpoint.path);
5037
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5037
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5038
5038
  const h5 = stableHash(endpoint.method, endpoint.path);
5039
5039
  const rules = [];
5040
5040
  fields.forEach((field, i5) => {
5041
5041
  const id = DATA_EXPOSURE_PII_BASE + (h5 * 17 + 3 + i5) % 999;
5042
5042
  rules.push(
5043
5043
  [
5044
- `# Writ-generated response PII filter (id:428)`,
5044
+ `# x-security-generated response PII filter (id:428)`,
5045
5045
  `# Source: ${endpoint.method} ${endpoint.path}, field: ${field}`,
5046
- `SecRule REQUEST_FILENAME "@rx ${escRx6(pathRx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'Writ id:428 data-exposure: response leaked sensitive field ${escMsec4(field)}',tag:'${escMsec4(tag2)}',tag:'writ-data-exposure',chain"`,
5046
+ `SecRule REQUEST_FILENAME "@rx ${escRx6(pathRx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'x-security id:428 data-exposure: response leaked sensitive field ${escMsec4(field)}',tag:'${escMsec4(tag2)}',tag:'x-security-data-exposure',chain"`,
5047
5047
  ` SecRule RESPONSE_BODY "@rx \\"${escMsec4(field)}\\"\\s*:\\s*\\"[^\\"]+\\"" "t:none"`
5048
5048
  ].join("\n")
5049
5049
  );
@@ -5054,7 +5054,7 @@ function buildErrorScrubbingRules(endpoint) {
5054
5054
  const scrub = endpoint.policy.response?.errorScrubbing;
5055
5055
  if (!scrub) return [];
5056
5056
  const pathRx = pathRegex4(endpoint.path);
5057
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5057
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5058
5058
  const h5 = stableHash(endpoint.method, endpoint.path);
5059
5059
  const rules = [];
5060
5060
  let slot = 0;
@@ -5062,9 +5062,9 @@ function buildErrorScrubbingRules(endpoint) {
5062
5062
  const id = OUTPUT_SANITIZE_BASE + (h5 * 31 + 7 + slot++) % 999;
5063
5063
  rules.push(
5064
5064
  [
5065
- `# Writ-generated response error-scrubbing (id:268, stack traces)`,
5065
+ `# x-security-generated response error-scrubbing (id:268, stack traces)`,
5066
5066
  `# Source: ${endpoint.method} ${endpoint.path}`,
5067
- `SecRule REQUEST_FILENAME "@rx ${escRx6(pathRx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'Writ id:268 output sanitization (stack trace leak)',tag:'${escMsec4(tag2)}',tag:'writ-output-sanitization',chain"`,
5067
+ `SecRule REQUEST_FILENAME "@rx ${escRx6(pathRx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'x-security id:268 output sanitization (stack trace leak)',tag:'${escMsec4(tag2)}',tag:'x-security-output-sanitization',chain"`,
5068
5068
  // Common stack-frame markers across Python / Node / Java / Go.
5069
5069
  // RE2-safe: only character classes + non-capturing groups + bounded \\d+.
5070
5070
  ` SecRule RESPONSE_BODY "@rx (?:Traceback \\(most recent call last\\)|Exception in thread|\\bat\\s+[\\w.]+\\.[\\w$<>]+\\(|goroutine\\s+\\d+\\s+\\[)" "t:none"`
@@ -5075,9 +5075,9 @@ function buildErrorScrubbingRules(endpoint) {
5075
5075
  const id = OUTPUT_SANITIZE_BASE + (h5 * 31 + 7 + slot++) % 999;
5076
5076
  rules.push(
5077
5077
  [
5078
- `# Writ-generated response error-scrubbing (id:268, generic messages)`,
5078
+ `# x-security-generated response error-scrubbing (id:268, generic messages)`,
5079
5079
  `# Source: ${endpoint.method} ${endpoint.path}`,
5080
- `SecRule REQUEST_FILENAME "@rx ${escRx6(pathRx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'Writ id:268 output sanitization (raw error leak)',tag:'${escMsec4(tag2)}',tag:'writ-output-sanitization',chain"`,
5080
+ `SecRule REQUEST_FILENAME "@rx ${escRx6(pathRx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'x-security id:268 output sanitization (raw error leak)',tag:'${escMsec4(tag2)}',tag:'x-security-output-sanitization',chain"`,
5081
5081
  ` SecRule RESPONSE_BODY "@rx (?i)(?:syntax error near|ORA-\\d+|ER_\\w+|psycopg2\\.|SQLSTATE|\\bENOENT\\b|undefined method|NullPointerException|panic:\\s)" "t:none"`
5082
5082
  ].join("\n")
5083
5083
  );
@@ -5139,7 +5139,7 @@ function buildRequestSchemaRules(endpoint) {
5139
5139
  const ctx = {
5140
5140
  endpoint,
5141
5141
  base: ruleBase(endpoint),
5142
- tag: `writ/${endpoint.method} ${endpoint.path}`
5142
+ tag: `x-security/${endpoint.method} ${endpoint.path}`
5143
5143
  };
5144
5144
  return buildSchemaRules(ctx, schema, MODSEC_NGINX_PROFILE);
5145
5145
  }
@@ -5204,7 +5204,7 @@ function sinkRule(sink) {
5204
5204
  operator: "@rx (?i)(?:ignore\\s+(?:all\\s+)?(?:previous|prior|above)\\s+instructions|disregard\\s+(?:the\\s+)?(?:previous|above|system)|system\\s+prompt|you\\s+are\\s+now\\s+|act\\s+as\\s+(?:a\\s+)?(?:dan|developer\\s+mode)|reveal\\s+(?:your\\s+)?(?:system\\s+)?prompt|new\\s+instructions?\\s*:)",
5205
5205
  msg: "LLM prompt injection",
5206
5206
  slot: 8,
5207
- attrTag: "writ-ssec-prompt"
5207
+ attrTag: "x-security-ssec-prompt"
5208
5208
  };
5209
5209
  default:
5210
5210
  return null;
@@ -5213,7 +5213,7 @@ function sinkRule(sink) {
5213
5213
  function buildInjectionGuardRules(endpoint) {
5214
5214
  const schema = endpoint.policy.request?.schema;
5215
5215
  if (!schema || Object.keys(schema).length === 0) return [];
5216
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5216
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5217
5217
  const rx = pathRegex(endpoint.path);
5218
5218
  const out = [];
5219
5219
  for (const [field, ps] of Object.entries(schema)) {
@@ -5225,16 +5225,16 @@ function buildInjectionGuardRules(endpoint) {
5225
5225
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|${field}`, "");
5226
5226
  const id = INJECTION_GUARD_BASE_ID2 + seed % 1e3 * 9 + def.slot;
5227
5227
  const selector = `ARGS:json.${field}|ARGS:${field}`;
5228
- const attrTag = def.attrTag ?? "writ-ssec-injection";
5229
- const attrName = attrTag === "writ-ssec-prompt" ? "SSEC-PROMPT" : "SSEC-INJECTION";
5228
+ const attrTag = def.attrTag ?? "x-security-ssec-injection";
5229
+ const attrName = attrTag === "x-security-ssec-prompt" ? "SSEC-PROMPT" : "SSEC-INJECTION";
5230
5230
  out.push(
5231
5231
  [
5232
5232
  header12(
5233
5233
  `W19 injectionGuard[${sink}] on request.schema.${field} for ${endpoint.method} ${endpoint.path}
5234
5234
  Native libmodsec3 operator on the declared sink field. Attributed to
5235
- ${attrName} (Writ-native), not an OWASP-API cell.`
5235
+ ${attrName} (x-security-native), not an OWASP-API cell.`
5236
5236
  ),
5237
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'Writ: ${esc11(def.msg)} in ${esc11(field)}',tag:'${esc11(tag2)}',tag:'${esc11(attrTag)}',chain"`,
5237
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:403,msg:'x-security: ${esc11(def.msg)} in ${esc11(field)}',tag:'${esc11(tag2)}',tag:'${esc11(attrTag)}',chain"`,
5238
5238
  ` SecRule REQUEST_FILENAME "@rx ${escRx7(rx)}" "chain"`,
5239
5239
  ` SecRule ${selector} "${escRx7(def.operator)}"${CHAIN_TERM}`
5240
5240
  ].join("\n")
@@ -5254,7 +5254,7 @@ function redirectHostRx(domain) {
5254
5254
  function buildRedirectAllowlistRules(endpoint) {
5255
5255
  const schema = endpoint.policy.request?.schema;
5256
5256
  if (!schema || Object.keys(schema).length === 0) return [];
5257
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5257
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5258
5258
  const rx = pathRegex(endpoint.path);
5259
5259
  const out = [];
5260
5260
  for (const [field, ps] of Object.entries(schema)) {
@@ -5270,7 +5270,7 @@ function buildRedirectAllowlistRules(endpoint) {
5270
5270
  header12(
5271
5271
  `S-15 open-redirect: ${field} must match redirectAllowedDomains for ${endpoint.method} ${endpoint.path}`
5272
5272
  ),
5273
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'Writ: ${esc11(field)} redirect target not in redirectAllowedDomains',tag:'${esc11(tag2)}',tag:'writ-rule-open-redirect-403',chain"`,
5273
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:403,msg:'x-security: ${esc11(field)} redirect target not in redirectAllowedDomains',tag:'${esc11(tag2)}',tag:'x-security-rule-open-redirect-403',chain"`,
5274
5274
  ` SecRule REQUEST_FILENAME "@rx ${escRx7(rx)}" "chain"`,
5275
5275
  ` SecRule ARGS:${field}|ARGS:json.${field} "!@rx ${escRx7(allowRx)}"${CHAIN_TERM}`
5276
5276
  ].join("\n")
@@ -5284,7 +5284,7 @@ function buildXxeRules(endpoint) {
5284
5284
  const disallowXml = req.disallowXml === true;
5285
5285
  const disableEntities = req.disableExternalEntities === true;
5286
5286
  if (!disallowXml && !disableEntities) return [];
5287
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5287
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5288
5288
  const rx = pathRegex(endpoint.path);
5289
5289
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|xxe`, "");
5290
5290
  const id = XXE_BASE_ID + seed % 9e3;
@@ -5295,7 +5295,7 @@ function buildXxeRules(endpoint) {
5295
5295
  `S-5 XXE: reject XML body for ${endpoint.method} ${endpoint.path}
5296
5296
  ${reason}`
5297
5297
  ),
5298
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:415,msg:'Writ: ${esc11(reason)}',tag:'${esc11(tag2)}',tag:'writ-rule-xxe-415',chain"`,
5298
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:415,msg:'x-security: ${esc11(reason)}',tag:'${esc11(tag2)}',tag:'x-security-rule-xxe-415',chain"`,
5299
5299
  ` SecRule REQUEST_FILENAME "@rx ${escRx7(rx)}" "chain"`,
5300
5300
  ` SecRule REQUEST_HEADERS:Content-Type "@rx ${escRx7(XML_CONTENT_TYPE_RX)}"${CHAIN_TERM}`
5301
5301
  ].join("\n")
@@ -5303,7 +5303,7 @@ ${reason}`
5303
5303
  }
5304
5304
  function buildPathCanonicalizationRules(endpoint) {
5305
5305
  if (endpoint.policy.request?.pathCanonicalization !== true) return [];
5306
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5306
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5307
5307
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|pathcanon`, "");
5308
5308
  const id = PATH_CANON_BASE_ID + seed % 9e3;
5309
5309
  return [
@@ -5313,7 +5313,7 @@ function buildPathCanonicalizationRules(endpoint) {
5313
5313
  Denies traversal / double-slash / percent-encoded separators so the
5314
5314
  canonical path every later SecRule matches against can't be bypassed.`
5315
5315
  ),
5316
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:400,msg:'Writ: non-canonical request path',tag:'${esc11(tag2)}',tag:'writ-rule-path-canon-400',chain"`,
5316
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:1,deny,status:400,msg:'x-security: non-canonical request path',tag:'${esc11(tag2)}',tag:'x-security-rule-path-canon-400',chain"`,
5317
5317
  ` SecRule REQUEST_URI "@rx ${escRx7(PATH_TRAVERSAL_RX)}"${CHAIN_TERM}`
5318
5318
  ].join("\n")
5319
5319
  ];
@@ -5347,7 +5347,7 @@ function header13(comment) {
5347
5347
  function buildPasswordPolicyRules2(endpoint) {
5348
5348
  const pol = endpoint.policy.authentication?.passwordPolicy;
5349
5349
  if (!pol) return [];
5350
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5350
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5351
5351
  const rx = pathRegex(endpoint.path);
5352
5352
  const seedBase = endpointHash3(`${endpoint.method}|${endpoint.path}|pwpolicy`, "");
5353
5353
  const checks = [];
@@ -5374,7 +5374,7 @@ function buildPasswordPolicyRules2(endpoint) {
5374
5374
  Body-carried password strength (API2:2023). libmodsec3 PCRE !@rx on the
5375
5375
  password field \u2014 rejects a present-but-weak password.`
5376
5376
  ),
5377
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'Writ: ${esc12(c5.reason)}',tag:'${esc12(tag2)}',tag:'writ-rule-password-policy',chain"`,
5377
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'x-security: ${esc12(c5.reason)}',tag:'${esc12(tag2)}',tag:'x-security-rule-password-policy',chain"`,
5378
5378
  ` SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "chain"`,
5379
5379
  ` SecRule ${selector} "!@rx ${escRx8(c5.rx)}"${CHAIN_TERM2}`
5380
5380
  ].join("\n")
@@ -5388,7 +5388,7 @@ password field \u2014 rejects a present-but-weak password.`
5388
5388
  header13(
5389
5389
  `v0.7 passwordPolicy: blocklisted password for ${endpoint.method} ${endpoint.path}`
5390
5390
  ),
5391
- `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'Writ: password is on the blocklist',tag:'${esc12(tag2)}',tag:'writ-rule-password-policy',chain"`,
5391
+ `SecRule REQUEST_METHOD "@streq ${endpoint.method}" "id:${id},phase:2,deny,status:422,log,msg:'x-security: password is on the blocklist',tag:'${esc12(tag2)}',tag:'x-security-rule-password-policy',chain"`,
5392
5392
  ` SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "chain"`,
5393
5393
  ` SecRule ${selector} "@rx ${escRx8(`(?i)^(?:${alt})$`)}"${CHAIN_TERM2}`
5394
5394
  ].join("\n")
@@ -5407,7 +5407,7 @@ function lockoutVar2(identifier) {
5407
5407
  function buildAccountLockoutRules2(endpoint) {
5408
5408
  const lock = endpoint.policy.authentication?.accountLockout;
5409
5409
  if (!lock) return [];
5410
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5410
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5411
5411
  const rx = pathRegex(endpoint.path);
5412
5412
  const window2 = parseDurationSec(lock.window) || 900;
5413
5413
  const { expansion, collectKeyPhase } = lockoutVar2(lock.identifier);
@@ -5428,7 +5428,7 @@ identifier source is populated); increment at phase:5 on a >=400 auth response.`
5428
5428
  `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base},phase:${keyPhase},pass,nolog,tag:'${esc12(tag2)}',${initcol},chain"`,
5429
5429
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${CHAIN_TERM2}`,
5430
5430
  // 2. Deny while locked out — counter already over budget.
5431
- `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base + 1},phase:${keyPhase},deny,status:429,log,msg:'Writ: account locked (>${lock.attempts} failed logins / ${esc12(lock.window)})',tag:'${esc12(tag2)}',tag:'writ-rule-account-lockout',chain"`,
5431
+ `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base + 1},phase:${keyPhase},deny,status:429,log,msg:'x-security: account locked (>${lock.attempts} failed logins / ${esc12(lock.window)})',tag:'${esc12(tag2)}',tag:'x-security-rule-account-lockout',chain"`,
5432
5432
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
5433
5433
  ` SecRule GLOBAL:${counterKey} "@gt ${lock.attempts}"${CHAIN_TERM2}`,
5434
5434
  // 3. On a failed-auth response (>=400), increment + refresh the TTL (phase:5).
@@ -5440,7 +5440,7 @@ identifier source is populated); increment at phase:5 on a >=400 auth response.`
5440
5440
  }
5441
5441
  function buildForbidArrayRootRules2(endpoint) {
5442
5442
  if (endpoint.policy.response?.forbidArrayRoot !== true) return [];
5443
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5443
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5444
5444
  const rx = pathRegex(endpoint.path);
5445
5445
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|arrayroot`, "");
5446
5446
  const id = FORBID_ARRAY_ROOT_BASE_ID2 + seed % 999;
@@ -5451,7 +5451,7 @@ function buildForbidArrayRootRules2(endpoint) {
5451
5451
  JSON-hijacking defense (API3:2023). phase:4 RESPONSE_BODY @rx on the first
5452
5452
  non-whitespace byte; an array-rooted body is denied (wrap in an object instead).`
5453
5453
  ),
5454
- `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'Writ: bare top-level JSON array response (forbidArrayRoot)',tag:'${esc12(tag2)}',tag:'writ-rule-forbid-array-root',chain"`,
5454
+ `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${id},phase:4,deny,status:500,log,auditlog,msg:'x-security: bare top-level JSON array response (forbidArrayRoot)',tag:'${esc12(tag2)}',tag:'x-security-rule-forbid-array-root',chain"`,
5455
5455
  ` SecRule RESPONSE_BODY "@rx ^[\\s\\xef\\xbb\\xbf]*\\[" "t:none"`
5456
5456
  ].join("\n")
5457
5457
  ];
@@ -5459,7 +5459,7 @@ non-whitespace byte; an array-rooted body is denied (wrap in an object instead).
5459
5459
  function buildIdempotencyKeyRules2(endpoint) {
5460
5460
  const idem = endpoint.policy.request?.idempotencyKey;
5461
5461
  if (!idem) return [];
5462
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5462
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5463
5463
  const rx = pathRegex(endpoint.path);
5464
5464
  const ttl = parseDurationSec(idem.ttl) || 300;
5465
5465
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|idempotency`, "");
@@ -5476,7 +5476,7 @@ request replay via a persistent-collection seen-count, NOT concurrent in-
5476
5476
  flight races (no atomic check-and-set at the WAF; handle that in the store).`
5477
5477
  ),
5478
5478
  // 1. Require the idempotency-key header to be present at all.
5479
- `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base},phase:1,deny,status:400,log,msg:'Writ: missing ${esc12(idem.header)} header',tag:'${esc12(tag2)}',tag:'writ-rule-idempotency-key',chain"`,
5479
+ `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base},phase:1,deny,status:400,log,msg:'x-security: missing ${esc12(idem.header)} header',tag:'${esc12(tag2)}',tag:'x-security-rule-idempotency-key',chain"`,
5480
5480
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
5481
5481
  ` SecRule &REQUEST_HEADERS:${esc12(idem.header)} "@eq 0"${CHAIN_TERM2}`,
5482
5482
  // 2. Open the per-key persistent collection.
@@ -5486,7 +5486,7 @@ flight races (no atomic check-and-set at the WAF; handle that in the store).`
5486
5486
  `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base + 2},phase:1,pass,nolog,tag:'${esc12(tag2)}',setvar:global.${counterKey}=+1,expirevar:global.${counterKey}=${ttl},chain"`,
5487
5487
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${CHAIN_TERM2}`,
5488
5488
  // 4. Deny the replay — second+ request with this key inside the ttl window.
5489
- `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base + 3},phase:1,deny,status:409,log,msg:'Writ: replayed idempotency key (${esc12(idem.header)})',tag:'${esc12(tag2)}',tag:'writ-rule-idempotency-key',chain"`,
5489
+ `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${base + 3},phase:1,deny,status:409,log,msg:'x-security: replayed idempotency key (${esc12(idem.header)})',tag:'${esc12(tag2)}',tag:'x-security-rule-idempotency-key',chain"`,
5490
5490
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
5491
5491
  ` SecRule GLOBAL:${counterKey} "@gt 1"${CHAIN_TERM2}`
5492
5492
  ].join("\n")
@@ -5495,7 +5495,7 @@ flight races (no atomic check-and-set at the WAF; handle that in the store).`
5495
5495
  function buildLoggingRules2(endpoint) {
5496
5496
  const log = endpoint.policy.logging;
5497
5497
  if (!log || !Array.isArray(log.events) || log.events.length === 0) return [];
5498
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5498
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5499
5499
  const rx = pathRegex(endpoint.path);
5500
5500
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|logging`, "");
5501
5501
  const id = LOGGING_BASE_ID2 + seed % 999;
@@ -5504,7 +5504,7 @@ function buildLoggingRules2(endpoint) {
5504
5504
  const notes = [];
5505
5505
  notes.push(`events declared: ${log.events.join(", ")}`);
5506
5506
  notes.push(`auth-failure / authz-deny / injection-block / rate-limit-trip are`);
5507
- notes.push(`already audit-logged by the corresponding Writ deny rules.`);
5507
+ notes.push(`already audit-logged by the corresponding x-security deny rules.`);
5508
5508
  if (log.sink && log.sink !== "stdout") {
5509
5509
  notes.push(`sink='${log.sink}' is NOT enforced at libmodsec3 \u2014 configure it at`);
5510
5510
  notes.push(`the nginx/bw-scheduler log-shipping layer (syslog/fluent-bit sidecar).`);
@@ -5523,7 +5523,7 @@ function buildLoggingRules2(endpoint) {
5523
5523
  `v0.7 logging (SSEC-AUDIT): force audit log for ${endpoint.method} ${endpoint.path}
5524
5524
  ` + notes.join("\n")
5525
5525
  ),
5526
- `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${id},phase:5,pass,log,auditlog,msg:'Writ: audit (request/response) for ${esc12(endpoint.method)} ${esc12(endpoint.path)}',tag:'${esc12(tag2)}',tag:'writ-audit',chain"`,
5526
+ `SecRule REQUEST_FILENAME "@rx ${escRx8(rx)}" "id:${id},phase:5,pass,log,auditlog,msg:'x-security: audit (request/response) for ${esc12(endpoint.method)} ${esc12(endpoint.path)}',tag:'${esc12(tag2)}',tag:'x-security-audit',chain"`,
5527
5527
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${CHAIN_TERM2}`
5528
5528
  ].join("\n")
5529
5529
  );
@@ -5570,7 +5570,7 @@ function operationsWithAuthz(ops) {
5570
5570
  function buildGraphqlOperationAuthzRules(endpoint) {
5571
5571
  const ops = operationsWithAuthz(endpoint.policy.graphql?.operations);
5572
5572
  if (ops.length === 0) return [];
5573
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5573
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5574
5574
  const rx = pathRegex(endpoint.path);
5575
5575
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|gqlops`, "");
5576
5576
  const id = GRAPHQL_OPS_BASE_ID + seed % 999;
@@ -5595,7 +5595,7 @@ Per-operation authz contract the processor MUST enforce:
5595
5595
  // Route marker: pass-through (NOT a deny) so we never claim enforcement
5596
5596
  // we don't have. The tag lets a downstream ext-filter / sidecar pick up
5597
5597
  // the request; on its own it changes nothing about the response.
5598
- `SecRule REQUEST_FILENAME "@rx ${escRx9(rx)}" "id:${id},phase:2,pass,nolog,msg:'Writ: graphql per-operation authz handoff (override-only)',tag:'${esc13(tag2)}',tag:'writ-graphql-ops-authz',tag:'writ-override-only',chain"`,
5598
+ `SecRule REQUEST_FILENAME "@rx ${escRx9(rx)}" "id:${id},phase:2,pass,nolog,msg:'x-security: graphql per-operation authz handoff (override-only)',tag:'${esc13(tag2)}',tag:'x-security-graphql-ops-authz',tag:'x-security-override-only',chain"`,
5599
5599
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}"${CHAIN_TERM3}`
5600
5600
  ].join("\n")
5601
5601
  ];
@@ -5603,7 +5603,7 @@ Per-operation authz contract the processor MUST enforce:
5603
5603
  function buildGraphqlStaticLimitRules2(endpoint) {
5604
5604
  const g5 = endpoint.policy.graphql;
5605
5605
  if (!g5) return [];
5606
- const tag2 = `writ/${endpoint.method} ${endpoint.path}`;
5606
+ const tag2 = `x-security/${endpoint.method} ${endpoint.path}`;
5607
5607
  const rx = pathRegex(endpoint.path);
5608
5608
  const seed = endpointHash3(`${endpoint.method}|${endpoint.path}|gqlstatic`, "");
5609
5609
  const base = GRAPHQL_STATIC_BASE_ID + seed % 498 * 2;
@@ -5621,7 +5621,7 @@ function buildGraphqlStaticLimitRules2(endpoint) {
5621
5621
  an introspection query MUST contain those meta-fields, so this is a real,
5622
5622
  non-parsing block (not a heuristic guess).`
5623
5623
  ),
5624
- `SecRule REQUEST_FILENAME "@rx ${escRx9(rx)}" "id:${base},phase:2,deny,status:403,log,msg:'Writ: GraphQL introspection disabled (graphql.disableIntrospection)',tag:'${esc13(tag2)}',tag:'writ-rule-graphql-introspection',chain"`,
5624
+ `SecRule REQUEST_FILENAME "@rx ${escRx9(rx)}" "id:${base},phase:2,deny,status:403,log,msg:'x-security: GraphQL introspection disabled (graphql.disableIntrospection)',tag:'${esc13(tag2)}',tag:'x-security-rule-graphql-introspection',chain"`,
5625
5625
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
5626
5626
  ` SecRule REQUEST_BODY "@rx (?i)__(?:schema|type)\\b"${CHAIN_TERM3}`
5627
5627
  ].join("\n")
@@ -5637,7 +5637,7 @@ parser, so we reject any top-level JSON-array body (batched GraphQL is
5637
5637
  [{...},{...}]). This caps batching at "none" \u2014 stricter than the numeric
5638
5638
  limit, not the exact semantics, hence partial.`
5639
5639
  ),
5640
- `SecRule REQUEST_FILENAME "@rx ${escRx9(rx)}" "id:${base + 1},phase:2,deny,status:403,log,msg:'Writ: GraphQL request batching rejected (graphql.batchLimit)',tag:'${esc13(tag2)}',tag:'writ-rule-graphql-batch',chain"`,
5640
+ `SecRule REQUEST_FILENAME "@rx ${escRx9(rx)}" "id:${base + 1},phase:2,deny,status:403,log,msg:'x-security: GraphQL request batching rejected (graphql.batchLimit)',tag:'${esc13(tag2)}',tag:'x-security-rule-graphql-batch',chain"`,
5641
5641
  ` SecRule REQUEST_METHOD "@streq ${endpoint.method}" "chain"`,
5642
5642
  ` SecRule REQUEST_BODY "@rx ^[\\s]*\\[" "t:none"`
5643
5643
  ].join("\n")
@@ -5651,7 +5651,7 @@ ${unenforceable.join(", ")} require GraphQL query-depth/complexity/alias
5651
5651
  counting, which is NOT regular-language-expressible \u2014 libmodsec3 cannot
5652
5652
  enforce it without a parser. Wire a GraphQL-aware processor (the same
5653
5653
  sidecar the graphql.operations.authz scaffolding routes to) to enforce
5654
- these. Writ does NOT fake them with regex (Rule D-1).`
5654
+ these. x-security does NOT fake them with regex (Rule D-1).`
5655
5655
  )
5656
5656
  );
5657
5657
  }
@@ -5673,7 +5673,7 @@ function buildSerializeByHttpSnippet(endpoint, index) {
5673
5673
  const limit = endpoint.policy.request?.concurrencyLimit;
5674
5674
  const conc = typeof limit === "number" && limit >= 1 ? limit : 1;
5675
5675
  const scope = ser.scope ?? "per-identifier";
5676
- const keyVar = scope === "global" ? `"writ_serial_global"` : serializeKeyToNginxVar(ser.key);
5676
+ const keyVar = scope === "global" ? `"x_security_serial_global"` : serializeKeyToNginxVar(ser.key);
5677
5677
  const zoneName = `ss_serial_${index}`;
5678
5678
  const snippet = `# v0.8 request.serializeBy (API6:2023) for ${endpoint.method} ${endpoint.path}
5679
5679
  # PARTIAL \u2014 edge serialization only; does NOT provide in-handler transaction
@@ -5693,7 +5693,7 @@ function buildDataAtRestRules(endpoint) {
5693
5693
  ADVISORY-ONLY \u2014 NOT gateway-enforced. fields=[${dar.fields.join(", ")}] must be
5694
5694
  '${dar.protection}' at rest, but the WAF never sees the datastore write, so this
5695
5695
  compiles to NOTHING enforcing. Implement at-rest ${dar.protection} in the app's
5696
- persistence layer. Writ surfaces this as an out-of-band SSEC-STORAGE
5696
+ persistence layer. x-security surfaces this as an out-of-band SSEC-STORAGE
5697
5697
  finding, not a control (Rule D-1: no fake 'full' for an unenforceable field).`
5698
5698
  )
5699
5699
  ];
@@ -5814,7 +5814,7 @@ function bunkerwebCapabilities() {
5814
5814
  // emits a native libmodsec3 operator per declared sink. v0.7 adds two
5815
5815
  // sinks that ride the SAME cell — both are real @rx denylists on the
5816
5816
  // field (deserialization preamble denylist; ai-prompt heuristic denylist,
5817
- // tagged writ-ssec-prompt) → cell stays 'full'.
5817
+ // tagged x-security-ssec-prompt) → cell stays 'full'.
5818
5818
  // v0.7 (API2): authentication.passwordPolicy — phase:2 !@rx strength
5819
5819
  // SecRules (minLength/upper/digit/symbol/blocklist) on the body password
5820
5820
  // field. Real per-rule enforcement on a present password → full.
@@ -5880,7 +5880,7 @@ function bunkerwebCapabilities() {
5880
5880
  "request.dataAtRest": "unsupported",
5881
5881
  "mtls.pinnedCertificates": "unsupported",
5882
5882
  // Promoted: deprecated emits phase:1 SecRule returning 410 with
5883
- // writ-deprecated-endpoint-block tag (attribution.py:35).
5883
+ // x-security-deprecated-endpoint-block tag (attribution.py:35).
5884
5884
  "deprecated": "full",
5885
5885
  "sunsetDate": "partial"
5886
5886
  }
@@ -5943,8 +5943,8 @@ function detectMixedJwtAudienceIssuer(per) {
5943
5943
  const audiences = /* @__PURE__ */ new Set();
5944
5944
  const issuers = /* @__PURE__ */ new Set();
5945
5945
  for (const { settings } of per) {
5946
- if (settings.WRIT_AUTH_AUDIENCE !== void 0) audiences.add(String(settings.WRIT_AUTH_AUDIENCE));
5947
- if (settings.WRIT_AUTH_ISSUER !== void 0) issuers.add(String(settings.WRIT_AUTH_ISSUER));
5946
+ if (settings.X_SECURITY_AUTH_AUDIENCE !== void 0) audiences.add(String(settings.X_SECURITY_AUTH_AUDIENCE));
5947
+ if (settings.X_SECURITY_AUTH_ISSUER !== void 0) issuers.add(String(settings.X_SECURITY_AUTH_ISSUER));
5948
5948
  }
5949
5949
  const errors = [];
5950
5950
  if (audiences.size > 1) errors.push(`mixed JWT audience values across service: ${Array.from(audiences).join(", ")}`);
@@ -6181,8 +6181,8 @@ function groupByService(spec) {
6181
6181
  }
6182
6182
  function serializeModsecConf(spec, service) {
6183
6183
  const lines = [];
6184
- lines.push(`# Generated by Writ from ${spec.info.title} v${spec.info.version}`);
6185
- lines.push("# Phase-1/phase-2 Writ ModSecurity rules.");
6184
+ lines.push(`# Generated by x-security from ${spec.info.title} v${spec.info.version}`);
6185
+ lines.push("# Phase-1/phase-2 x-security ModSecurity rules.");
6186
6186
  lines.push("# Place this file under bw-scheduler's /data/configs/modsec/ volume.");
6187
6187
  lines.push("# Do not edit by hand \u2014 regenerate via `lazy generate --target bunkerweb`.");
6188
6188
  lines.push("");
@@ -6190,10 +6190,10 @@ function serializeModsecConf(spec, service) {
6190
6190
  ([k5]) => /^CUSTOM_CONF_MODSEC_\d+$/.test(k5)
6191
6191
  );
6192
6192
  const ssrfBlocks = modsecEntries.filter(
6193
- ([, v]) => String(v).includes("writ-rule-ssrf")
6193
+ ([, v]) => String(v).includes("x-security-rule-ssrf")
6194
6194
  );
6195
6195
  const otherBlocks = modsecEntries.filter(
6196
- ([, v]) => !String(v).includes("writ-rule-ssrf")
6196
+ ([, v]) => !String(v).includes("x-security-rule-ssrf")
6197
6197
  );
6198
6198
  for (const [key, val] of [...ssrfBlocks, ...otherBlocks]) {
6199
6199
  const origins = service.provenance.get(key) ?? [];
@@ -6231,7 +6231,7 @@ function serializeModsecConf(spec, service) {
6231
6231
  }
6232
6232
  function serializeDeploymentMd(spec, warnings) {
6233
6233
  const lines = [
6234
- "# Writ \u2192 BunkerWeb deployment notes",
6234
+ "# x-security \u2192 BunkerWeb deployment notes",
6235
6235
  "",
6236
6236
  `Generated from ${spec.info.title} v${spec.info.version}.`,
6237
6237
  "",
@@ -6257,11 +6257,11 @@ function serializeDeploymentMd(spec, warnings) {
6257
6257
  " Settings like `USE_MODSECURITY`, `ALLOWED_METHODS`, rate-limit thresholds, etc.",
6258
6258
  " belong in your **docker-compose.yml** as per-service environment variables",
6259
6259
  " (`<SERVICE>_<KEY>=<value>` in multisite mode). See the comment block at the",
6260
- " bottom of `configs/modsec/writ.conf` for the recommended values.",
6260
+ " bottom of `configs/modsec/x-security.conf` for the recommended values.",
6261
6261
  "",
6262
6262
  "## JWT signature validation",
6263
6263
  "",
6264
- "BunkerWeb 1.6+ ships `nginx_jwt_module`; Writ now emits the native",
6264
+ "BunkerWeb 1.6+ ships `nginx_jwt_module`; x-security now emits the native",
6265
6265
  "`USE_AUTH_JWT`, `JWT_JWKS_URI`, and `JWT_ALGORITHMS` settings for every",
6266
6266
  "`bearer-jwt` / `oauth2` endpoint that declares `jwksUri`. The WAF-side",
6267
6267
  "header-presence SecRule chain (id:990010/990011) stays as defense-in-depth.",
@@ -6274,17 +6274,17 @@ function serializeDeploymentMd(spec, warnings) {
6274
6274
  "",
6275
6275
  "## Per-user rate limits (rateLimit.identifier=user-id)",
6276
6276
  "",
6277
- "For endpoints declaring `rateLimit.identifier: user-id`, Writ emits a",
6277
+ "For endpoints declaring `rateLimit.identifier: user-id`, x-security emits a",
6278
6278
  "`CUSTOM_CONF_HTTP_LIMIT_REQ_USER_<n>` snippet that declares a dedicated",
6279
6279
  "`limit_req_zone` keyed on `$http_x_forwarded_user`. Paste the snippet into",
6280
- "`configs/http/writ-limit-user.conf` in your bw-scheduler volume, then",
6280
+ "`configs/http/x-security-limit-user.conf` in your bw-scheduler volume, then",
6281
6281
  "wire the matching `limit_req zone=lazy_user_<n> burst=<b> nodelay;` into the",
6282
6282
  "per-location server block (BunkerWeb's `CUSTOM_CONF_SERVER_*` overrides).",
6283
6283
  "",
6284
6284
  "## Deprecated endpoints",
6285
6285
  "",
6286
6286
  "Endpoints with `deprecated: true` get a phase:1 SecRule returning **410 Gone**",
6287
- "with tag `writ-deprecated-endpoint-block` (consumed by the scorer's",
6287
+ "with tag `x-security-deprecated-endpoint-block` (consumed by the scorer's",
6288
6288
  "attribution table). `sunsetDate` is surfaced in the deny message; emit a",
6289
6289
  "`Sunset:` response header via `CUSTOM_HEADER_*` if the client needs it.",
6290
6290
  "",
@@ -6314,7 +6314,7 @@ var init_bunkerweb = __esm({
6314
6314
  init_ssrf_policy_check();
6315
6315
  init_rules();
6316
6316
  init_profiles();
6317
- MODSEC_BLOCK_MARKER = "# Writ-generated authentication rules";
6317
+ MODSEC_BLOCK_MARKER = "# x-security-generated authentication rules";
6318
6318
  bunkerwebGenerator = {
6319
6319
  name: "bunkerweb",
6320
6320
  targets: ["bunkerweb"],
@@ -6333,7 +6333,7 @@ var init_bunkerweb = __esm({
6333
6333
  const modsecBody = serializeModsecConf(spec, service);
6334
6334
  const artifacts = [
6335
6335
  {
6336
- path: "configs/modsec/writ.conf",
6336
+ path: "configs/modsec/x-security.conf",
6337
6337
  content: modsecBody,
6338
6338
  format: "conf"
6339
6339
  },
@@ -6595,7 +6595,7 @@ function buildDoc(spec, opts = {}) {
6595
6595
  }
6596
6596
  if (hosts.size === 0) hosts.add("*");
6597
6597
  const specificRules = Array.from(hosts).map((host) => ({
6598
- name: `writ-asset-${sanitizeName(host) || "default"}`,
6598
+ name: `x-security-asset-${sanitizeName(host) || "default"}`,
6599
6599
  host,
6600
6600
  triggers: [DEFAULT_TRIGGER],
6601
6601
  mode: "prevent-learn",
@@ -6615,7 +6615,7 @@ function buildDoc(spec, opts = {}) {
6615
6615
  practices,
6616
6616
  "log-triggers": [buildDefaultTrigger()],
6617
6617
  "custom-responses": [buildDefaultResponse()],
6618
- "writ-extended": {
6618
+ "x-security-extended": {
6619
6619
  "schema-validation": schemaValidations
6620
6620
  }
6621
6621
  };
@@ -6642,10 +6642,10 @@ var DEFAULT_PRACTICE, DEFAULT_RATE_LIMIT_PRACTICE, DEFAULT_TRIGGER, DEFAULT_RESP
6642
6642
  var init_policy = __esm({
6643
6643
  "src/generators/openappsec/policy.ts"() {
6644
6644
  "use strict";
6645
- DEFAULT_PRACTICE = "writ-threat-prevention";
6646
- DEFAULT_RATE_LIMIT_PRACTICE = "writ-rate-limit";
6647
- DEFAULT_TRIGGER = "writ-log-trigger";
6648
- DEFAULT_RESPONSE = "writ-blocked-response";
6645
+ DEFAULT_PRACTICE = "x-security-threat-prevention";
6646
+ DEFAULT_RATE_LIMIT_PRACTICE = "x-security-rate-limit";
6647
+ DEFAULT_TRIGGER = "x-security-log-trigger";
6648
+ DEFAULT_RESPONSE = "x-security-blocked-response";
6649
6649
  BYTE_UNITS = {
6650
6650
  B: 1,
6651
6651
  KB: 1024,
@@ -6666,7 +6666,7 @@ var init_openappsec = __esm({
6666
6666
  init_ssrf_policy_check();
6667
6667
  openappsecLastWarnings = [];
6668
6668
  HEADER = [
6669
- "# Generated by Writ \u2014 DO NOT EDIT BY HAND.",
6669
+ "# Generated by x-security \u2014 DO NOT EDIT BY HAND.",
6670
6670
  "# Target: open-appsec.io (flat local_policy.yaml format)",
6671
6671
  "# Source: annotated OpenAPI spec (x-security)",
6672
6672
  "#",
@@ -6676,7 +6676,7 @@ var init_openappsec = __esm({
6676
6676
  "# practice as `openapi-schema-validation.files:`. Mount both files at",
6677
6677
  "# the same path inside the agent container (default: /ext/appsec/).",
6678
6678
  "# - Per-property `mitigates:` hints and the full property tree are also",
6679
- "# preserved under `writ-extended:` for Writ drift detection",
6679
+ "# preserved under `x-security-extended:` for x-security drift detection",
6680
6680
  "# (open-appsec ignores unknown top-level keys).",
6681
6681
  "# - Authentication (JWT/OAuth), authorization (rule-based), and request",
6682
6682
  "# HMAC signatures are NOT enforceable by open-appsec \u2014 it is an L7",
@@ -6688,7 +6688,7 @@ var init_openappsec = __esm({
6688
6688
  "# declares the anti-bot block but does not configure a provider.",
6689
6689
  "# - The ML model may block attacks not explicitly in our policy (its",
6690
6690
  "# default web-attacks heuristics). Such blocks are open-appsec doing",
6691
- "# its own job, NOT Writ enforcement.",
6691
+ "# its own job, NOT x-security enforcement.",
6692
6692
  ""
6693
6693
  ].join("\n");
6694
6694
  openappsecGenerator = {
@@ -6714,7 +6714,7 @@ var init_openappsec = __esm({
6714
6714
  noRefs: true,
6715
6715
  sortKeys: false
6716
6716
  });
6717
- const fragmentContent = "# Generated by Writ \u2014 DO NOT EDIT BY HAND.\n# OpenAPI schema fragment for open-appsec schema validation.\n# Referenced by practices[].openapi-schema-validation.files: in policy.yaml.\n# Mount this file at the path declared there (default: /ext/appsec/openapi-schema.yaml).\n" + fragmentYaml;
6717
+ const fragmentContent = "# Generated by x-security \u2014 DO NOT EDIT BY HAND.\n# OpenAPI schema fragment for open-appsec schema validation.\n# Referenced by practices[].openapi-schema-validation.files: in policy.yaml.\n# Mount this file at the path declared there (default: /ext/appsec/openapi-schema.yaml).\n" + fragmentYaml;
6718
6718
  return [
6719
6719
  { path: "openappsec/policy.yaml", content: policyContent, format: "yaml" },
6720
6720
  { path: "openappsec/openapi-schema.yaml", content: fragmentContent, format: "yaml" }
@@ -6730,7 +6730,7 @@ var init_openappsec = __esm({
6730
6730
  // needs baseline traffic to reach steady-state recall — under
6731
6731
  // prevent-learn with no learned state, it lets some malformed
6732
6732
  // payloads through and (b) per-property `mitigates:` hints are
6733
- // Writ-internal and not consumed by the agent.
6733
+ // x-security-internal and not consumed by the agent.
6734
6734
  "request.schema": "partial",
6735
6735
  "request.contentType": "partial",
6736
6736
  "request.maxBodySize": "partial",
@@ -6790,10 +6790,10 @@ var init_metadata_blocks = __esm({
6790
6790
 
6791
6791
  // src/generators/firewall/iptables.ts
6792
6792
  function provenanceComment(endpoint, field, label) {
6793
- return `# writ: ${endpoint} ${field} -- ${label}`;
6793
+ return `# x-security: ${endpoint} ${field} -- ${label}`;
6794
6794
  }
6795
6795
  function blockRule(entry, uid, endpoint, field) {
6796
- const inlineComment = `writ/${endpoint}/${field}`;
6796
+ const inlineComment = `x-security/${endpoint}/${field}`;
6797
6797
  return {
6798
6798
  comment: provenanceComment(endpoint, field, entry.label),
6799
6799
  line: `-A OUTPUT -d ${entry.cidr} -m owner --uid-owner ${uid} -m comment --comment "${inlineComment}" -j DROP`,
@@ -6801,10 +6801,10 @@ function blockRule(entry, uid, endpoint, field) {
6801
6801
  };
6802
6802
  }
6803
6803
  function allowlistRule(fqdn, uid, endpoint, field) {
6804
- const inlineComment = `writ/${endpoint}/${field}/allow=${fqdn}`;
6804
+ const inlineComment = `x-security/${endpoint}/${field}/allow=${fqdn}`;
6805
6805
  return {
6806
6806
  comment: provenanceComment(endpoint, field, `allow ${fqdn} (resolved at deploy)`),
6807
- line: `-A OUTPUT @@WRIT_RESOLVE:${fqdn}@@ -m owner --uid-owner ${uid} -m comment --comment "${inlineComment}" -j ACCEPT`,
6807
+ line: `-A OUTPUT @@X_SECURITY_RESOLVE:${fqdn}@@ -m owner --uid-owner ${uid} -m comment --comment "${inlineComment}" -j ACCEPT`,
6808
6808
  family: "v4"
6809
6809
  };
6810
6810
  }
@@ -6860,11 +6860,11 @@ function buildIptablesV6(spec, opts = {}) {
6860
6860
  function renderRuleset(rules, family, uid) {
6861
6861
  const filtered = rules.filter((r5) => r5.family === family);
6862
6862
  const header15 = [
6863
- `# Generated by Writ \u2014 DO NOT EDIT.`,
6863
+ `# Generated by XSecurity \u2014 DO NOT EDIT.`,
6864
6864
  `# Family: IP${family === "v4" ? "v4" : "v6"}`,
6865
6865
  `# Apply with: ${family === "v4" ? "iptables-restore" : "ip6tables-restore"} < this-file`,
6866
- `# Deploy wrapper must substitute @@WRIT_RESOLVE:<fqdn>@@ tokens`,
6867
- `# and ${"${WRIT_APP_UID}"} before applying.`,
6866
+ `# Deploy wrapper must substitute @@X_SECURITY_RESOLVE:<fqdn>@@ tokens`,
6867
+ `# and ${"${X_SECURITY_APP_UID}"} before applying.`,
6868
6868
  `*filter`,
6869
6869
  `:INPUT ACCEPT [0:0]`,
6870
6870
  `:FORWARD ACCEPT [0:0]`,
@@ -6875,9 +6875,9 @@ function renderRuleset(rules, family, uid) {
6875
6875
  body.push(rule.comment);
6876
6876
  body.push(rule.line);
6877
6877
  }
6878
- body.push(`# writ: * default-deny -- fail-closed terminator`);
6878
+ body.push(`# x-security: * default-deny -- fail-closed terminator`);
6879
6879
  body.push(
6880
- `-A OUTPUT -m owner --uid-owner ${uid} -m comment --comment "writ/default-deny" -j DROP`
6880
+ `-A OUTPUT -m owner --uid-owner ${uid} -m comment --comment "x-security/default-deny" -j DROP`
6881
6881
  );
6882
6882
  return [...header15, ...body, `COMMIT`, ``].join("\n");
6883
6883
  }
@@ -6886,7 +6886,7 @@ var init_iptables = __esm({
6886
6886
  "src/generators/firewall/iptables.ts"() {
6887
6887
  "use strict";
6888
6888
  init_metadata_blocks();
6889
- DEFAULT_UID = "${WRIT_APP_UID}";
6889
+ DEFAULT_UID = "${X_SECURITY_APP_UID}";
6890
6890
  }
6891
6891
  });
6892
6892
 
@@ -6908,28 +6908,28 @@ function read(name) {
6908
6908
  function loadWrapperScripts() {
6909
6909
  return [
6910
6910
  {
6911
- filename: "writ-resolve.sh",
6912
- content: read("writ-resolve.sh"),
6911
+ filename: "x-security-resolve.sh",
6912
+ content: read("x-security-resolve.sh"),
6913
6913
  format: "text"
6914
6914
  },
6915
6915
  {
6916
- filename: "writ-refresh.sh",
6917
- content: read("writ-refresh.sh"),
6916
+ filename: "x-security-refresh.sh",
6917
+ content: read("x-security-refresh.sh"),
6918
6918
  format: "text"
6919
6919
  },
6920
6920
  {
6921
- filename: "writ-refresh.service",
6922
- content: read("writ-refresh.service"),
6921
+ filename: "x-security-refresh.service",
6922
+ content: read("x-security-refresh.service"),
6923
6923
  format: "conf"
6924
6924
  },
6925
6925
  {
6926
- filename: "writ-refresh.timer",
6927
- content: read("writ-refresh.timer"),
6926
+ filename: "x-security-refresh.timer",
6927
+ content: read("x-security-refresh.timer"),
6928
6928
  format: "conf"
6929
6929
  },
6930
6930
  {
6931
- filename: "writ.logrotate",
6932
- content: read("writ.logrotate"),
6931
+ filename: "x-security.logrotate",
6932
+ content: read("x-security.logrotate"),
6933
6933
  format: "conf"
6934
6934
  },
6935
6935
  {
@@ -7139,7 +7139,7 @@ var init_clusters = __esm({
7139
7139
  "src/generators/envoy/templates/clusters.ts"() {
7140
7140
  "use strict";
7141
7141
  init_yaml_util();
7142
- UPSTREAM_CLUSTER = "writ_upstream";
7142
+ UPSTREAM_CLUSTER = "x_security_upstream";
7143
7143
  JWKS_CLUSTER = "jwks_cluster";
7144
7144
  }
7145
7145
  });
@@ -7184,7 +7184,7 @@ function denyLiteral(ruleClass) {
7184
7184
  "{",
7185
7185
  ` "allowed": false,`,
7186
7186
  ` "http_status": 403,`,
7187
- ` "headers": {"x-writ-rule": ${regoString(marker)}},`,
7187
+ ` "headers": {"x-x-security-rule": ${regoString(marker)}},`,
7188
7188
  ` "body": ${regoString(marker)}`,
7189
7189
  " }"
7190
7190
  ].join("\n ");
@@ -7626,7 +7626,7 @@ function emitExtAuthzFilter(lines) {
7626
7626
  lines.push(" timeout: 0.5s");
7627
7627
  lines.push(" failure_mode_allow: false");
7628
7628
  lines.push(" include_peer_certificate: false");
7629
- lines.push(" # W17-A: forward OPA-emitted x-writ-rule marker downstream.");
7629
+ lines.push(" # W17-A: forward OPA-emitted x-x-security-rule marker downstream.");
7630
7630
  lines.push(" with_request_body:");
7631
7631
  lines.push(" max_request_bytes: 8192");
7632
7632
  lines.push(" allow_partial_message: true");
@@ -7660,11 +7660,11 @@ function buildRegoPolicyInner(cfg) {
7660
7660
  const ssrf = cfg.ssrfPolicy ?? [];
7661
7661
  const abac = cfg.abac ?? [];
7662
7662
  const lines = [];
7663
- lines.push("# Writ \u2192 OPA \u2014 auto-generated. DO NOT EDIT BY HAND.");
7664
- lines.push(`# generator: writ-envoy/extauthz v${VERSION2}`);
7663
+ lines.push("# x-security \u2192 OPA \u2014 auto-generated. DO NOT EDIT BY HAND.");
7664
+ lines.push(`# generator: x-security-envoy/extauthz v${VERSION2}`);
7665
7665
  lines.push(`# source: ${cfg.specTitle} ${cfg.specVersion}`);
7666
7666
  lines.push("# Wave-10 E-3: rule-based authorization \u2192 ext_authz + OPA sidecar.");
7667
- lines.push("# Wave-17 W17-A: structured decision object emits per-class x-writ-rule");
7667
+ lines.push("# Wave-17 W17-A: structured decision object emits per-class x-x-security-rule");
7668
7668
  lines.push("# headers (opa-bola-403, opa-jwt-claim-403, opa-default-403) so the scorer's");
7669
7669
  lines.push("# intent-attribution layer can map a denial back to the right defense-class.");
7670
7670
  lines.push("# Wave-18 W18-A: + opa-bfla-403 (rbac admin-only routes, principal-role check)");
@@ -7787,13 +7787,13 @@ function buildResponseSchemaConfig(items, specTitle, specVersion) {
7787
7787
  };
7788
7788
  });
7789
7789
  const cfg = {
7790
- $comment: "Writ \u2192 Envoy ext_proc response-schema contract. Auto-generated. DO NOT EDIT BY HAND. Consumed by an operator-supplied ext_proc gRPC ExternalProcessor that parses the response body as JSON and enforces these per-field typed constraints on the parsed value. Writ does NOT ship that processor; this file + the envoy.yaml ext_proc filter are scaffolding. No regex over raw bytes \u2014 the processor MUST JSON.parse.",
7791
- generator: "writ-envoy/ext_proc",
7790
+ $comment: "x-security \u2192 Envoy ext_proc response-schema contract. Auto-generated. DO NOT EDIT BY HAND. Consumed by an operator-supplied ext_proc gRPC ExternalProcessor that parses the response body as JSON and enforces these per-field typed constraints on the parsed value. x-security does NOT ship that processor; this file + the envoy.yaml ext_proc filter are scaffolding. No regex over raw bytes \u2014 the processor MUST JSON.parse.",
7791
+ generator: "x-security-envoy/ext_proc",
7792
7792
  source: `${specTitle} ${specVersion}`,
7793
7793
  enforcement: {
7794
7794
  status: "override-only",
7795
7795
  enforcedBy: `operator-supplied ext_proc gRPC ExternalProcessor (cluster ${EXT_PROC_CLUSTER}, host ${EXT_PROC_HOST}:${EXT_PROC_PORT})`,
7796
- operatorMustSupply: "A gRPC service implementing envoy.service.ext_proc.v3.ExternalProcessor that JSON-parses the response body and evaluates the rules below. Writ does not bundle it; the OPA sidecar (opa_grpc) is ext_authz-only.",
7796
+ operatorMustSupply: "A gRPC service implementing envoy.service.ext_proc.v3.ExternalProcessor that JSON-parses the response body and evaluates the rules below. x-security does not bundle it; the OPA sidecar (opa_grpc) is ext_authz-only.",
7797
7797
  dataPath: "Envoy ext_proc filter streams the response body (processing_mode: response_body_mode=BUFFERED) to the processor, which returns an ImmediateResponse(502) to block a schema-violating body or continues otherwise."
7798
7798
  },
7799
7799
  routes
@@ -7803,7 +7803,7 @@ function buildResponseSchemaConfig(items, specTitle, specVersion) {
7803
7803
  function emitExtProcFilter(lines) {
7804
7804
  lines.push(" # response.schema validation (override-only): real JSON parse happens in the");
7805
7805
  lines.push(" # operator-supplied ext_proc processor; this filter only delivers the body.");
7806
- lines.push(" # Writ does NOT ship the processor \u2014 see ext_proc/response-schema.json.");
7806
+ lines.push(" # x-security does NOT ship the processor \u2014 see ext_proc/response-schema.json.");
7807
7807
  lines.push(" - name: envoy.filters.http.ext_proc");
7808
7808
  lines.push(" typed_config:");
7809
7809
  lines.push(' "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor');
@@ -7842,9 +7842,9 @@ var init_ext_proc = __esm({
7842
7842
  "src/generators/envoy/templates/ext_proc.ts"() {
7843
7843
  "use strict";
7844
7844
  init_extauthz_rego_util();
7845
- EXT_PROC_CLUSTER = "writ_ext_proc";
7845
+ EXT_PROC_CLUSTER = "x_security_ext_proc";
7846
7846
  EXT_PROC_PORT = 9292;
7847
- EXT_PROC_HOST = "writ-respval";
7847
+ EXT_PROC_HOST = "x-security-respval";
7848
7848
  }
7849
7849
  });
7850
7850
 
@@ -7903,7 +7903,7 @@ function emitSinkTypedConfig(lines, indent, r5) {
7903
7903
  lines.push(`${indent} typed_config:`);
7904
7904
  lines.push(`${indent} "@type": type.googleapis.com/envoy.extensions.access_loggers.grpc.v3.HttpGrpcAccessLogConfig`);
7905
7905
  lines.push(`${indent} common_config:`);
7906
- lines.push(`${indent} log_name: writ_access`);
7906
+ lines.push(`${indent} log_name: x_security_access`);
7907
7907
  lines.push(`${indent} grpc_service:`);
7908
7908
  lines.push(`${indent} envoy_grpc:`);
7909
7909
  lines.push(`${indent} cluster_name: ${ALS_CLUSTER}`);
@@ -7929,11 +7929,11 @@ function eventFilter(event) {
7929
7929
  ];
7930
7930
  switch (event) {
7931
7931
  case "auth-failure":
7932
- return f5(401, "writ_log_401");
7932
+ return f5(401, "x_security_log_401");
7933
7933
  case "authz-deny":
7934
- return f5(403, "writ_log_403");
7934
+ return f5(403, "x_security_log_403");
7935
7935
  case "rate-limit-trip":
7936
- return f5(429, "writ_log_429");
7936
+ return f5(429, "x_security_log_429");
7937
7937
  // injection-block / request / response have no single-status signature: they
7938
7938
  // ride the always-on base logger (which records every transaction). Faking a
7939
7939
  // narrower filter would mis-route, so we keep them on the base logger.
@@ -7954,7 +7954,7 @@ function emitAccessLog(lines, spec, prefix = " ") {
7954
7954
  lines.push(`${prefix} inline_string: "${DEFAULT_FORMAT}"`);
7955
7955
  return;
7956
7956
  }
7957
- lines.push(`${prefix} # Writ logging (SSEC-AUDIT) \u2014 NATIVE/full.`);
7957
+ lines.push(`${prefix} # x-security logging (SSEC-AUDIT) \u2014 NATIVE/full.`);
7958
7958
  lines.push(`${prefix} # events: ${r5.events.join(", ")}`);
7959
7959
  lines.push(`${prefix} # sink: ${r5.sink}${r5.sinkRef ? ` (sinkRef ${r5.sinkRef})` : ""}; piiRedaction: ${r5.piiRedaction}`);
7960
7960
  if (r5.sink === "syslog") {
@@ -8002,9 +8002,9 @@ var init_access_log = __esm({
8002
8002
  "src/generators/envoy/templates/access_log.ts"() {
8003
8003
  "use strict";
8004
8004
  init_yaml_util();
8005
- ALS_CLUSTER = "writ_access_log";
8005
+ ALS_CLUSTER = "x_security_access_log";
8006
8006
  ALS_PORT = 9001;
8007
- ALS_HOST = "writ-log-collector";
8007
+ ALS_HOST = "x-security-log-collector";
8008
8008
  DEFAULT_FORMAT = '[%START_TIME%] \\"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\\" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% bytes=%BYTES_RECEIVED%/%BYTES_SENT% ua=\\"%REQ(USER-AGENT)%\\"\\n';
8009
8009
  }
8010
8010
  });
@@ -8156,7 +8156,7 @@ function emitRouteIpPolicy(lines, pol) {
8156
8156
  if (pol.deny.length) {
8157
8157
  lines.push(" action: DENY");
8158
8158
  lines.push(" policies:");
8159
- lines.push(" writ-ip-deny:");
8159
+ lines.push(" x-security-ip-deny:");
8160
8160
  lines.push(" permissions: [{ any: true }]");
8161
8161
  lines.push(" principals:");
8162
8162
  for (const c5 of pol.deny) {
@@ -8167,7 +8167,7 @@ function emitRouteIpPolicy(lines, pol) {
8167
8167
  } else {
8168
8168
  lines.push(" action: ALLOW");
8169
8169
  lines.push(" policies:");
8170
- lines.push(" writ-ip-allow:");
8170
+ lines.push(" x-security-ip-allow:");
8171
8171
  lines.push(" permissions: [{ any: true }]");
8172
8172
  lines.push(" principals:");
8173
8173
  for (const c5 of pol.allow) {
@@ -8210,7 +8210,7 @@ function collectJwtEndpoints(spec) {
8210
8210
  }
8211
8211
  if (!jwt.length || !jwksUri) return null;
8212
8212
  return {
8213
- providerName: "writ_jwt",
8213
+ providerName: "x_security_jwt",
8214
8214
  issuer,
8215
8215
  audiences: [...audiences],
8216
8216
  jwksUri,
@@ -8233,7 +8233,7 @@ function emitJwtAuthnFilter(lines, jwt) {
8233
8233
  lines.push(" from_headers:");
8234
8234
  lines.push(` - name: ${yamlString(jwt.headerName)}`);
8235
8235
  lines.push(' value_prefix: "Bearer "');
8236
- lines.push(" forward_payload_header: x-writ-jwt-payload");
8236
+ lines.push(" forward_payload_header: x-x-security-jwt-payload");
8237
8237
  lines.push(` payload_in_metadata: ${jwt.providerName}`);
8238
8238
  lines.push(" remote_jwks:");
8239
8239
  lines.push(" http_uri:");
@@ -8266,7 +8266,7 @@ function emitLuaFilter(lines, luaSource) {
8266
8266
  lines.push(" - name: envoy.filters.http.lua");
8267
8267
  lines.push(" typed_config:");
8268
8268
  lines.push(' "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua');
8269
- lines.push(" # Residual Writ Lua \u2014 handles fields with no native filter equivalent");
8269
+ lines.push(" # Residual x-security Lua \u2014 handles fields with no native filter equivalent");
8270
8270
  lines.push(" # (duplicateParamPolicy, headerInjectionGuard, request.signature, \u2026).");
8271
8271
  lines.push(" inline_code: |");
8272
8272
  for (const ln of luaSource.replace(/\n$/, "").split("\n")) {
@@ -8307,7 +8307,7 @@ function emitRbacFilter(lines, rbac, jwtName) {
8307
8307
  lines.push(" policies:");
8308
8308
  for (const entry of rbac) {
8309
8309
  for (const role of entry.roles) {
8310
- const polName = `writ-rbac-${safeStatId(entry.endpoint)}-${role.replace(/[^a-z0-9]+/gi, "-")}`;
8310
+ const polName = `x-security-rbac-${safeStatId(entry.endpoint)}-${role.replace(/[^a-z0-9]+/gi, "-")}`;
8311
8311
  lines.push(` ${yamlString(polName)}:`);
8312
8312
  lines.push(" permissions:");
8313
8313
  lines.push(" - url_path:");
@@ -8347,7 +8347,7 @@ function collectRateLimits(spec) {
8347
8347
  maxTokens,
8348
8348
  tokensPerFill: primary.requests,
8349
8349
  fillIntervalSec: sec,
8350
- statPrefix: `writ_${safeStatId(ep)}_ratelimit`
8350
+ statPrefix: `x_security_${safeStatId(ep)}_ratelimit`
8351
8351
  });
8352
8352
  }
8353
8353
  return out;
@@ -8356,12 +8356,12 @@ function emitLocalRateLimitChain(lines) {
8356
8356
  lines.push(" - name: envoy.filters.http.local_ratelimit");
8357
8357
  lines.push(" typed_config:");
8358
8358
  lines.push(' "@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit');
8359
- lines.push(" stat_prefix: writ_chain_ratelimit");
8359
+ lines.push(" stat_prefix: x_security_chain_ratelimit");
8360
8360
  lines.push(" filter_enabled:");
8361
- lines.push(" runtime_key: writ.local_ratelimit_enabled");
8361
+ lines.push(" runtime_key: x-security.local_ratelimit_enabled");
8362
8362
  lines.push(" default_value: { numerator: 100, denominator: HUNDRED }");
8363
8363
  lines.push(" filter_enforced:");
8364
- lines.push(" runtime_key: writ.local_ratelimit_enforced");
8364
+ lines.push(" runtime_key: x-security.local_ratelimit_enforced");
8365
8365
  lines.push(" default_value: { numerator: 0, denominator: HUNDRED }");
8366
8366
  }
8367
8367
  var DEFAULT_BURST_MULTIPLIER2, DEFAULT_BURST_MINIMUM_HEADROOM2;
@@ -8505,7 +8505,7 @@ function emitRouteEntry(lines, ep, ctx, upstreamCluster) {
8505
8505
  lines.push(" default_value: { numerator: 100, denominator: HUNDRED }");
8506
8506
  lines.push(" response_headers_to_add:");
8507
8507
  lines.push(" - append: false");
8508
- lines.push(" header: { key: x-writ-ratelimit, value: enforced }");
8508
+ lines.push(" header: { key: x-x-security-ratelimit, value: enforced }");
8509
8509
  }
8510
8510
  if (cors) {
8511
8511
  lines.push(" envoy.filters.http.cors:");
@@ -8557,8 +8557,8 @@ function buildEnvoyYaml(opts) {
8557
8557
  adminPort = 9901
8558
8558
  } = opts;
8559
8559
  const lines = [];
8560
- lines.push("# Writ \u2192 Envoy \u2014 auto-generated. DO NOT EDIT BY HAND.");
8561
- lines.push(`# generator: writ-envoy v${VERSION3}`);
8560
+ lines.push("# x-security \u2192 Envoy \u2014 auto-generated. DO NOT EDIT BY HAND.");
8561
+ lines.push(`# generator: x-security-envoy v${VERSION3}`);
8562
8562
  lines.push(`# source: ${spec.info.title} ${spec.info.version}`);
8563
8563
  lines.push("");
8564
8564
  const ruleBased = collectRuleBased(spec);
@@ -8583,7 +8583,7 @@ function buildEnvoyYaml(opts) {
8583
8583
  const globalBodyCap = smallestBodyLimit2(spec.endpoints);
8584
8584
  lines.push("static_resources:");
8585
8585
  lines.push(" listeners:");
8586
- lines.push(" - name: writ_listener");
8586
+ lines.push(" - name: x_security_listener");
8587
8587
  lines.push(" address:");
8588
8588
  lines.push(` socket_address: { address: 0.0.0.0, port_value: ${listenerPort} }`);
8589
8589
  lines.push(" filter_chains:");
@@ -8591,12 +8591,12 @@ function buildEnvoyYaml(opts) {
8591
8591
  lines.push(" - name: envoy.filters.network.http_connection_manager");
8592
8592
  lines.push(" typed_config:");
8593
8593
  lines.push(' "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager');
8594
- lines.push(" stat_prefix: writ_hcm");
8594
+ lines.push(" stat_prefix: x_security_hcm");
8595
8595
  emitAccessLog(lines, spec);
8596
8596
  lines.push(" route_config:");
8597
- lines.push(" name: writ_routes");
8597
+ lines.push(" name: x_security_routes");
8598
8598
  lines.push(" virtual_hosts:");
8599
- lines.push(" - name: writ_vhost");
8599
+ lines.push(" - name: x_security_vhost");
8600
8600
  lines.push(' domains: ["*"]');
8601
8601
  lines.push(" routes:");
8602
8602
  const sortedEps = [...spec.endpoints].sort(
@@ -8709,7 +8709,7 @@ function buildEndpointBlock2(opts) {
8709
8709
  body.push(` -- request.headerInjectionGuard: reject CR/LF/NUL in header values`);
8710
8710
  body.push(` for k, v in pairs(headers) do`);
8711
8711
  body.push(` if type(v) == "string" and string.match(v, "[\\r\\n\\0]") then`);
8712
- body.push(` request_handle:respond({[":status"] = "400"}, "Writ: invalid header value")`);
8712
+ body.push(` request_handle:respond({[":status"] = "400"}, "x-security: invalid header value")`);
8713
8713
  body.push(` return`);
8714
8714
  body.push(` end`);
8715
8715
  body.push(` end`);
@@ -8723,7 +8723,7 @@ function buildEndpointBlock2(opts) {
8723
8723
  body.push(` for kv in string.gmatch(string.sub(q_string, q_pos + 1), "([^&]+)") do`);
8724
8724
  body.push(` local k = string.match(kv, "^([^=]+)")`);
8725
8725
  body.push(` if k and seen[k] then`);
8726
- body.push(` request_handle:respond({[":status"] = "400"}, "Writ: duplicate query parameter")`);
8726
+ body.push(` request_handle:respond({[":status"] = "400"}, "x-security: duplicate query parameter")`);
8727
8727
  body.push(` return`);
8728
8728
  body.push(` end`);
8729
8729
  body.push(` if k then seen[k] = true end`);
@@ -8740,7 +8740,7 @@ function buildEndpointBlock2(opts) {
8740
8740
  body.push(` if string.match(ct, ${luaStr4(pat)}) then ct_ok = true end`);
8741
8741
  }
8742
8742
  body.push(` if not ct_ok then`);
8743
- body.push(` request_handle:respond({[":status"] = "415"}, "Writ: unsupported Content-Type")`);
8743
+ body.push(` request_handle:respond({[":status"] = "415"}, "x-security: unsupported Content-Type")`);
8744
8744
  body.push(` return`);
8745
8745
  body.push(` end`);
8746
8746
  }
@@ -8749,7 +8749,7 @@ function buildEndpointBlock2(opts) {
8749
8749
  body.push(` -- request.maxBodySize=${bytes} bytes`);
8750
8750
  body.push(` local cl = tonumber(request_handle:headers():get("content-length") or "0") or 0`);
8751
8751
  body.push(` if cl > ${bytes} then`);
8752
- body.push(` request_handle:respond({[":status"] = "413"}, "Writ: request body exceeds endpoint limit")`);
8752
+ body.push(` request_handle:respond({[":status"] = "413"}, "x-security: request body exceeds endpoint limit")`);
8753
8753
  body.push(` return`);
8754
8754
  body.push(` end`);
8755
8755
  }
@@ -8757,20 +8757,20 @@ function buildEndpointBlock2(opts) {
8757
8757
  body.push(` -- request.signature (${policy.request.signature.algorithm}): TODO \u2014 needs body filter callback`);
8758
8758
  }
8759
8759
  body.push(` end`);
8760
- const startMarker = ` -- writ:${endpoint.method}:${endpoint.path}:START`;
8761
- const endMarker = ` -- writ:END`;
8760
+ const startMarker = ` -- xSecurity:${endpoint.method}:${endpoint.path}:START`;
8761
+ const endMarker = ` -- xSecurity:END`;
8762
8762
  return [startMarker, ...body, endMarker].join("\n");
8763
8763
  }
8764
8764
  function buildLuaModule(specTitle, specVersion, blocks, methodMap) {
8765
8765
  const lines = [];
8766
- lines.push(`-- Writ \u2192 Envoy residual Lua filter \u2014 auto-generated. DO NOT EDIT BY HAND.`);
8767
- lines.push(`-- generator: writ-envoy v${VERSION4}`);
8766
+ lines.push(`-- x-security \u2192 Envoy residual Lua filter \u2014 auto-generated. DO NOT EDIT BY HAND.`);
8767
+ lines.push(`-- generator: x-security-envoy v${VERSION4}`);
8768
8768
  lines.push(`-- source: ${specTitle} ${specVersion}`);
8769
8769
  lines.push(`-- This module handles ONLY fields with no native Envoy filter equivalent.`);
8770
8770
  lines.push(`-- JWT auth, RBAC, rate-limit, and CORS are enforced by their native filters`);
8771
8771
  lines.push(`-- earlier in the chain (jwt_authn, rbac, local_ratelimit, cors).`);
8772
8772
  lines.push("");
8773
- lines.push(`-- writ:method-allowlist:START`);
8773
+ lines.push(`-- xSecurity:method-allowlist:START`);
8774
8774
  lines.push(`local method_allowlist = {`);
8775
8775
  const sorted = [...methodMap].sort((a5, b5) => a5.path.localeCompare(b5.path));
8776
8776
  for (const e5 of sorted) {
@@ -8778,7 +8778,7 @@ function buildLuaModule(specTitle, specVersion, blocks, methodMap) {
8778
8778
  lines.push(` { pattern = ${luaStr4(e5.pattern)}, methods = { ${methods.map(luaStr4).join(", ")} } },`);
8779
8779
  }
8780
8780
  lines.push(`}`);
8781
- lines.push(`-- writ:method-allowlist:END`);
8781
+ lines.push(`-- xSecurity:method-allowlist:END`);
8782
8782
  lines.push("");
8783
8783
  lines.push(`function envoy_on_request(request_handle)`);
8784
8784
  lines.push(` local headers = request_handle:headers()`);
@@ -8797,7 +8797,7 @@ function buildLuaModule(specTitle, specVersion, blocks, methodMap) {
8797
8797
  lines.push(` if m == method then method_ok = true; break end`);
8798
8798
  lines.push(` end`);
8799
8799
  lines.push(` if not method_ok then`);
8800
- lines.push(` request_handle:respond({[":status"] = "405"}, "Writ: method not allowed")`);
8800
+ lines.push(` request_handle:respond({[":status"] = "405"}, "x-security: method not allowed")`);
8801
8801
  lines.push(` return`);
8802
8802
  lines.push(` end`);
8803
8803
  lines.push(` break`);
@@ -8875,7 +8875,7 @@ var init_envoy = __esm({
8875
8875
  init_extauthz();
8876
8876
  init_ext_proc();
8877
8877
  ENVOY_PATH = "envoy.yaml";
8878
- LUA_PATH = "writ.lua";
8878
+ LUA_PATH = "x-security.lua";
8879
8879
  OPA_POLICY_PATH = "opa/policy.rego";
8880
8880
  RESPONSE_SCHEMA_PATH = "ext_proc/response-schema.json";
8881
8881
  envoyLastWarnings = [];
@@ -9007,7 +9007,7 @@ var init_envoy = __esm({
9007
9007
  // ext_proc scaffolding: envoy.filters.http.ext_proc + processing
9008
9008
  // cluster + ext_proc/response-schema.json (typed per-field constraints,
9009
9009
  // stripUnknownFields). The body IS delivered to a processor that does a
9010
- // real JSON.parse — NO regex over raw bytes. But Writ does not
9010
+ // real JSON.parse — NO regex over raw bytes. But x-security does not
9011
9011
  // ship the processor (the opa_grpc sidecar is ext_authz-only; ext_proc
9012
9012
  // is an unmerged upstream PR on a different port). Enforcement depends
9013
9013
  // on an operator-supplied gRPC ExternalProcessor, so the honest status
@@ -9022,7 +9022,7 @@ var init_envoy = __esm({
9022
9022
  // carry a per-route `forbidArrayRoot` flag the operator processor reads).
9023
9023
  // Envoy ships no native response-body shape inspector and a Lua regex for
9024
9024
  // a leading '[' over raw bytes is the Rule D-1 masked-quality shortcut.
9025
- // Writ does NOT ship the processor → override-only, never full.
9025
+ // x-security does NOT ship the processor → override-only, never full.
9026
9026
  "response.forbidArrayRoot": "override-only",
9027
9027
  "response.headers.csp": "full",
9028
9028
  // wave-22: per-route response_headers_to_add
@@ -9037,7 +9037,7 @@ var init_envoy = __esm({
9037
9037
  // cannot evaluate per-operation/per-resolver authorization. The only
9038
9038
  // honest path is the same ext_proc handoff as response.schema — stream
9039
9039
  // the GraphQL POST body to an operator-supplied GraphQL-aware processor
9040
- // that parses the query and enforces authz. Writ ships NO such
9040
+ // that parses the query and enforces authz. x-security ships NO such
9041
9041
  // processor, so this is override-only, NEVER full. (Schema pins this
9042
9042
  // override-only on every target.) No scaffolding is emitted this wave —
9043
9043
  // the matrix is honest so --strict-fidelity/--feasible flag the gap.
@@ -58691,7 +58691,7 @@ async function runGenerate(specPath, opts) {
58691
58691
  if (opts.strictFidelity) {
58692
58692
  assertFidelity(spec, generator.capabilities(), { targetName: target });
58693
58693
  }
58694
- const outDir = opts.out ?? path.join(process.cwd(), "writ-out", target);
58694
+ const outDir = opts.out ?? path.join(process.cwd(), "x-security-out", target);
58695
58695
  const artifactPaths = [];
58696
58696
  if (!opts.dryRun) {
58697
58697
  await mkdir(outDir, { recursive: true });
@@ -59024,7 +59024,7 @@ function fieldForSlot(slot) {
59024
59024
  }
59025
59025
  function extractRateLimitThreshold(block) {
59026
59026
  for (const l3 of block.lines) {
59027
- const m3 = /SecRule\s+(?:IP|USER|GLOBAL|TX):(?:RATE_|writ_rl_|rl_)([A-Za-z0-9_]+?)(?:_burst)?\s+"@gt\s+(\d+)"/.exec(l3);
59027
+ const m3 = /SecRule\s+(?:IP|USER|GLOBAL|TX):(?:RATE_|x_security_rl_|rl_)([A-Za-z0-9_]+?)(?:_burst)?\s+"@gt\s+(\d+)"/.exec(l3);
59028
59028
  if (m3 && m3[2] && !l3.includes("_burst")) return Number(m3[2]);
59029
59029
  }
59030
59030
  return null;
@@ -59122,7 +59122,7 @@ function diffEndpoint(ep, expectedBlocks, actualBlocks) {
59122
59122
  severity: "LOW",
59123
59123
  expected: "absent",
59124
59124
  actual: actBlock.ruleId,
59125
- message: `Unknown Writ rule slot ${slot} (id=${actBlock.ruleId}) on deployed config`
59125
+ message: `Unknown x-security rule slot ${slot} (id=${actBlock.ruleId}) on deployed config`
59126
59126
  });
59127
59127
  }
59128
59128
  return issues;
@@ -59223,7 +59223,8 @@ function rateToPerSecond(v) {
59223
59223
  return unit === "h" ? n2 / 3600 : unit === "m" ? n2 / 60 : n2;
59224
59224
  }
59225
59225
  function severityForKey(key, expected, actual) {
59226
- if ((key === "USE_AUTH_BASIC" || key === "USE_MODSECURITY" || key === "WRIT_AUTH_TYPE" || key === "WRIT_JWKS_URI" || key === "USE_CLIENT_SSL" || key === "WRIT_AUTH_HEADER" || key === "MODSECURITY_RULES_FILE") && (actual === void 0 || actual === "no" || actual === "")) {
59226
+ if ((key === "USE_AUTH_BASIC" || key === "USE_MODSECURITY" || // Accept legacy WRIT_* keys on pre-rebrand deployments (back-compat).
59227
+ key === "X_SECURITY_AUTH_TYPE" || key === "WRIT_AUTH_TYPE" || key === "X_SECURITY_JWKS_URI" || key === "WRIT_JWKS_URI" || key === "USE_CLIENT_SSL" || key === "X_SECURITY_AUTH_HEADER" || key === "WRIT_AUTH_HEADER" || key === "MODSECURITY_RULES_FILE") && (actual === void 0 || actual === "no" || actual === "")) {
59227
59228
  return "CRITICAL";
59228
59229
  }
59229
59230
  if (/^LIMIT_REQ_RATE_\d+$/.test(key)) {
@@ -59460,7 +59461,7 @@ function diffRateLimitPractice(expected, actual) {
59460
59461
  if (!actual) {
59461
59462
  issues.push({
59462
59463
  endpoint: "*",
59463
- field: "practices.writ-rate-limit",
59464
+ field: "practices.x-security-rate-limit",
59464
59465
  severity: "CRITICAL",
59465
59466
  expected: "present",
59466
59467
  actual: "missing",
@@ -59517,8 +59518,8 @@ async function detectOpenAppSecDrift(spec, opts) {
59517
59518
  const expectedArtifacts = await Promise.resolve(openappsecGenerator.generate(spec));
59518
59519
  const expectedDoc = asDoc(yaml4.load(expectedArtifacts[0]?.content ?? ""));
59519
59520
  const issues = [];
59520
- const expSV = expectedDoc["writ-extended"]?.["schema-validation"] ?? [];
59521
- const actSV = actualDoc["writ-extended"]?.["schema-validation"] ?? [];
59521
+ const expSV = expectedDoc["x-security-extended"]?.["schema-validation"] ?? [];
59522
+ const actSV = actualDoc["x-security-extended"]?.["schema-validation"] ?? [];
59522
59523
  const actByKey = new Map(actSV.map((s) => [svKey(s), s]));
59523
59524
  for (const e5 of expSV) {
59524
59525
  const key = svKey(e5);
@@ -59548,8 +59549,8 @@ async function detectOpenAppSecDrift(spec, opts) {
59548
59549
  }
59549
59550
  const expPractices = expectedDoc.practices ?? [];
59550
59551
  const actPractices = actualDoc.practices ?? [];
59551
- const expRL = expPractices.find((p2) => p2.name === "writ-rate-limit");
59552
- const actRL = actPractices.find((p2) => p2.name === "writ-rate-limit");
59552
+ const expRL = expPractices.find((p2) => p2.name === "x-security-rate-limit");
59553
+ const actRL = actPractices.find((p2) => p2.name === "x-security-rate-limit");
59553
59554
  issues.push(...diffRateLimitPractice(expRL, actRL));
59554
59555
  return {
59555
59556
  kind: "drift",
@@ -59574,7 +59575,7 @@ function normalizeRuleLine(line) {
59574
59575
  function destOf(rule) {
59575
59576
  const cidr = /-d\s+(\S+)/.exec(rule);
59576
59577
  if (cidr && cidr[1]) return cidr[1];
59577
- const fqdn = /@@WRIT_RESOLVE:([^@]+)@@/.exec(rule);
59578
+ const fqdn = /@@(?:X_SECURITY|WRIT)_RESOLVE:([^@]+)@@/.exec(rule);
59578
59579
  if (fqdn && fqdn[1]) return `fqdn:${fqdn[1]}`;
59579
59580
  return "";
59580
59581
  }
@@ -59584,7 +59585,7 @@ function extractTagged(content) {
59584
59585
  for (let i5 = 0; i5 < lines.length; i5++) {
59585
59586
  const l3 = lines[i5];
59586
59587
  if (!l3) continue;
59587
- if (!l3.startsWith("# writ:")) continue;
59588
+ if (!l3.startsWith("# x-security:")) continue;
59588
59589
  let j5 = i5 + 1;
59589
59590
  while (j5 < lines.length) {
59590
59591
  const next = lines[j5];
@@ -59593,7 +59594,7 @@ function extractTagged(content) {
59593
59594
  }
59594
59595
  const ruleLine = lines[j5];
59595
59596
  if (!ruleLine) continue;
59596
- const body = l3.slice("# writ:".length).trim();
59597
+ const body = l3.slice("# x-security:".length).trim();
59597
59598
  const sepIdx = body.indexOf(" -- ");
59598
59599
  const tag2 = sepIdx >= 0 ? body.slice(0, sepIdx).trim() : body;
59599
59600
  out.push({
@@ -59667,7 +59668,7 @@ function diffRuleset(family, expected, actual) {
59667
59668
  severity: "LOW",
59668
59669
  expected: void 0,
59669
59670
  actual: a5.rule,
59670
- message: `Unknown Writ-tagged firewall (${family}) rule: ${a5.comment}`
59671
+ message: `Unknown XSecurity-tagged firewall (${family}) rule: ${a5.comment}`
59671
59672
  });
59672
59673
  }
59673
59674
  }
@@ -59834,7 +59835,7 @@ function extractRateLimitStatPrefixes(rawYaml) {
59834
59835
  }
59835
59836
  if (node && typeof node === "object") {
59836
59837
  const obj = node;
59837
- if (typeof obj.stat_prefix === "string" && obj.stat_prefix !== "writ_chain_ratelimit" && obj.stat_prefix !== "writ_hcm") {
59838
+ if (typeof obj.stat_prefix === "string" && obj.stat_prefix !== "x_security_chain_ratelimit" && obj.stat_prefix !== "x_security_hcm") {
59838
59839
  if (/_ratelimit$/.test(obj.stat_prefix)) out.add(obj.stat_prefix);
59839
59840
  }
59840
59841
  for (const v of Object.values(obj)) walk(v);
@@ -59934,7 +59935,7 @@ async function readSources(opts) {
59934
59935
  envoyYaml2 = "";
59935
59936
  }
59936
59937
  try {
59937
- lua = await readFile8(path3.join(opts.filePath, "writ.lua"), "utf8");
59938
+ lua = await readFile8(path3.join(opts.filePath, "x-security.lua"), "utf8");
59938
59939
  } catch {
59939
59940
  lua = null;
59940
59941
  }
@@ -59950,7 +59951,7 @@ async function detectEnvoyDrift(spec, opts) {
59950
59951
  const actualLua = actualLuaFile ?? extractInlineLua(actualYaml) ?? "";
59951
59952
  const expectedArtifacts = await Promise.resolve(envoyGenerator.generate(spec));
59952
59953
  const expectedYaml = expectedArtifacts.find((a5) => a5.path === "envoy.yaml")?.content ?? "";
59953
- const expectedLua = expectedArtifacts.find((a5) => a5.path === "writ.lua")?.content ?? "";
59954
+ const expectedLua = expectedArtifacts.find((a5) => a5.path === "x-security.lua")?.content ?? "";
59954
59955
  const expectedBlocks = extractEndpointBlocks(expectedLua);
59955
59956
  const actualBlocks = extractEndpointBlocks(actualLua);
59956
59957
  const issues = [];
@@ -60004,7 +60005,7 @@ async function detectEnvoyDrift(spec, opts) {
60004
60005
  const authz = ep.policy.authorization;
60005
60006
  if (!authz || authz.type !== "rbac" || !authz.roles?.length) continue;
60006
60007
  for (const role of authz.roles) {
60007
- const fragment = `"writ-rbac-`;
60008
+ const fragment = `"x-security-rbac-`;
60008
60009
  const roleFragment = role.replace(/[^a-z0-9]+/gi, "-");
60009
60010
  if (!deployedHasRbacPolicy(actualYaml, fragment) || !actualYaml.includes(`-${roleFragment}":`)) {
60010
60011
  issues.push({
@@ -60026,7 +60027,7 @@ async function detectEnvoyDrift(spec, opts) {
60026
60027
  severity: "LOW",
60027
60028
  expected: "absent",
60028
60029
  actual: "present",
60029
- message: `Unknown Writ-tagged endpoint block in deployed Lua: ${block.method} ${block.pathTemplate}`
60030
+ message: `Unknown x-security-tagged endpoint block in deployed Lua: ${block.method} ${block.pathTemplate}`
60030
60031
  });
60031
60032
  }
60032
60033
  return {
@@ -60042,8 +60043,8 @@ var init_envoy2 = __esm({
60042
60043
  "use strict";
60043
60044
  init_envoy();
60044
60045
  init_kong_shared();
60045
- START_RE = /^\s*--\s*writ:([A-Z]+):(.+):START\s*$/;
60046
- END_RE = /^\s*--\s*writ:END\s*$/;
60046
+ START_RE = /^\s*--\s*xSecurity:([A-Z]+):(.+):START\s*$/;
60047
+ END_RE = /^\s*--\s*xSecurity:END\s*$/;
60047
60048
  }
60048
60049
  });
60049
60050
 
@@ -60242,7 +60243,7 @@ function buildLog(toolName, rules, results) {
60242
60243
  tool: {
60243
60244
  driver: {
60244
60245
  name: toolName,
60245
- informationUri: "https://github.com/writ/writ",
60246
+ informationUri: "https://github.com/ZeeshanSultan/x-security",
60246
60247
  rules
60247
60248
  }
60248
60249
  },
@@ -60279,7 +60280,7 @@ function driftToSarif(r5, specPath) {
60279
60280
  properties: { severity: i5.severity }
60280
60281
  };
60281
60282
  });
60282
- return buildLog("writ-drift", Array.from(ruleSet.values()), results);
60283
+ return buildLog("x-security-drift", Array.from(ruleSet.values()), results);
60283
60284
  }
60284
60285
  function owaspToSarif(r5, specPath) {
60285
60286
  const rules = [
@@ -60301,7 +60302,7 @@ function owaspToSarif(r5, specPath) {
60301
60302
  }
60302
60303
  ]
60303
60304
  }));
60304
- return buildLog("writ-owasp", rules, results);
60305
+ return buildLog("x-security-owasp", rules, results);
60305
60306
  }
60306
60307
  var init_sarif = __esm({
60307
60308
  "src/reporters/sarif.ts"() {
@@ -60473,9 +60474,9 @@ function buildComposePlan(opts) {
60473
60474
  const upstreamPort = opts.upstreamPort ?? 18080;
60474
60475
  const gatewayPort = opts.gatewayPort ?? 18e3;
60475
60476
  const suffix = randomSuffix();
60476
- const network = `writ-${opts.target}-net-${suffix}`;
60477
- const upstreamName = `writ-${opts.target}-upstream-${suffix}`;
60478
- const gatewayName = `writ-${opts.target}-gateway-${suffix}`;
60477
+ const network = `x-security-${opts.target}-net-${suffix}`;
60478
+ const upstreamName = `x-security-${opts.target}-upstream-${suffix}`;
60479
+ const gatewayName = `x-security-${opts.target}-gateway-${suffix}`;
60479
60480
  const image = GATEWAY_IMAGE[opts.target];
60480
60481
  let gatewayEnv = "";
60481
60482
  if (opts.target === "kong") {
@@ -61103,9 +61104,9 @@ function fileHasWritHeader(container, path30) {
61103
61104
  });
61104
61105
  if (r5.error || r5.status !== 0) return false;
61105
61106
  const head = r5.stdout || "";
61106
- return WRIT_HEADER_MARKERS.some((m3) => head.includes(m3));
61107
+ return X_SECURITY_HEADER_MARKERS.some((m3) => head.includes(m3));
61107
61108
  }
61108
- function writRulesAreIncluded(nginxDump, container) {
61109
+ function xSecurityRulesAreIncluded(nginxDump, container) {
61109
61110
  if (!nginxDump) return true;
61110
61111
  const lines = nginxDump.split("\n");
61111
61112
  const includes = [];
@@ -61113,12 +61114,12 @@ function writRulesAreIncluded(nginxDump, container) {
61113
61114
  const m3 = line.match(/^\s*Include\s+(\S+)/i);
61114
61115
  if (m3 && m3[1]) includes.push(m3[1]);
61115
61116
  }
61116
- if (includes.some((i5) => /writ/i.test(i5))) return true;
61117
+ if (includes.some((i5) => /x-security|writ/i.test(i5))) return true;
61117
61118
  if (!container) return false;
61118
61119
  const globs = includes.filter((i5) => /[*?[]/.test(i5));
61119
61120
  for (const glob of globs) {
61120
61121
  const matched = listGlobInContainer(container, glob);
61121
- if (matched.some((p2) => /writ/i.test(p2))) return true;
61122
+ if (matched.some((p2) => /x-security|writ/i.test(p2))) return true;
61122
61123
  for (const p2 of matched) {
61123
61124
  if (fileHasWritHeader(container, p2)) return true;
61124
61125
  }
@@ -61186,7 +61187,7 @@ function scanEmittedRules(directives) {
61186
61187
  }
61187
61188
  return out;
61188
61189
  }
61189
- var SECTION_BANNER, RULE_ID_RE2, SEC_RULE_LINE, RULES_ERROR_RE, GENERIC_PARSE_ERR_RE, RULES_LOADED_SUMMARY, WRIT_HEADER_MARKERS, modsecNginxReader;
61190
+ var SECTION_BANNER, RULE_ID_RE2, SEC_RULE_LINE, RULES_ERROR_RE, GENERIC_PARSE_ERR_RE, RULES_LOADED_SUMMARY, X_SECURITY_HEADER_MARKERS, modsecNginxReader;
61190
61191
  var init_modsec_nginx = __esm({
61191
61192
  "src/verify/readers/modsec-nginx.ts"() {
61192
61193
  "use strict";
@@ -61197,9 +61198,9 @@ var init_modsec_nginx = __esm({
61197
61198
  RULES_ERROR_RE = /ModSecurity:\s*(?:Rules error\.?\s*)?File:\s*([^.]+?)\.\s*Line:\s*(\d+)\.\s*Column:\s*(\d+)\.\s*(.*)$/;
61198
61199
  GENERIC_PARSE_ERR_RE = /ModSecurity:\s*(Something wrong with [^.\n]+|SecDefaultActions[^.\n]+)/;
61199
61200
  RULES_LOADED_SUMMARY = /rules loaded inline\/local\/remote:\s*(\d+)\/(\d+)\/(\d+)/i;
61200
- WRIT_HEADER_MARKERS = [
61201
- "Writ \u2192 Coraza",
61202
- "generator: writ-coraza"
61201
+ X_SECURITY_HEADER_MARKERS = [
61202
+ "x-security \u2192 Coraza",
61203
+ "generator: x-security-coraza"
61203
61204
  ];
61204
61205
  modsecNginxReader = {
61205
61206
  async readEmittedArtifacts(spec) {
@@ -61216,12 +61217,12 @@ var init_modsec_nginx = __esm({
61216
61217
  const out = [];
61217
61218
  const nginxDump = await readNginxConfigDump(gateway);
61218
61219
  const container = gateway.startsWith("docker:") ? gateway.slice("docker:".length) : void 0;
61219
- const included = writRulesAreIncluded(nginxDump, container);
61220
+ const included = xSecurityRulesAreIncluded(nginxDump, container);
61220
61221
  if (nginxDump && !included) {
61221
61222
  out.push({
61222
61223
  id: "__not-included__",
61223
61224
  kind: "coraza-rule",
61224
- rejectionReason: "Writ rules file is not Include'd by the running nginx config (nginx -T) \u2014 every emitted rule is unloaded"
61225
+ rejectionReason: "x-security rules file is not Include'd by the running nginx config (nginx -T) \u2014 every emitted rule is unloaded"
61225
61226
  });
61226
61227
  }
61227
61228
  for (const line of raw.split("\n")) {
@@ -61266,7 +61267,7 @@ var init_modsec_nginx = __esm({
61266
61267
  diagnostics.push(`could not find "rules loaded inline/local/remote" startup line \u2014 coverage inferred from parse-error lines only`);
61267
61268
  }
61268
61269
  if (notIncluded) {
61269
- diagnostics.push(notIncluded.rejectionReason ?? "Writ rules file not included by gateway config");
61270
+ diagnostics.push(notIncluded.rejectionReason ?? "x-security rules file not included by gateway config");
61270
61271
  }
61271
61272
  const rejectionsByLine = /* @__PURE__ */ new Map();
61272
61273
  const genericRejections = [];
@@ -61283,7 +61284,7 @@ var init_modsec_nginx = __esm({
61283
61284
  }
61284
61285
  const rows = [];
61285
61286
  const everythingRejected = !!notIncluded || summaryKnown && totalLoadedFromSummary === 0;
61286
- const genericReason = notIncluded?.rejectionReason ?? genericRejections[0]?.rejectionReason ?? (everythingRejected ? "no Writ rules loaded (file likely not included by nginx, or parse aborted at engine-globals)" : "");
61287
+ const genericReason = notIncluded?.rejectionReason ?? genericRejections[0]?.rejectionReason ?? (everythingRejected ? "no x-security rules loaded (file likely not included by nginx, or parse aborted at engine-globals)" : "");
61287
61288
  for (const [endpoint, arts] of byEndpoint) {
61288
61289
  const rejected = [];
61289
61290
  let loadedCount = 0;
@@ -61983,8 +61984,8 @@ function parseEmittedSnippet(yamlText) {
61983
61984
  collectRbacPolicyNames(doc, rbacPolicies);
61984
61985
  const ratelimitStatPrefixes = /* @__PURE__ */ new Set();
61985
61986
  collectStatPrefixes(doc, ratelimitStatPrefixes);
61986
- ratelimitStatPrefixes.delete("writ_chain_ratelimit");
61987
- ratelimitStatPrefixes.delete("writ_hcm");
61987
+ ratelimitStatPrefixes.delete("x_security_chain_ratelimit");
61988
+ ratelimitStatPrefixes.delete("x_security_hcm");
61988
61989
  const corsRoutes = [];
61989
61990
  const routes = pickRoutes(doc);
61990
61991
  if (routes) {
@@ -62137,7 +62138,7 @@ var init_envoy3 = __esm({
62137
62138
  "src/verify/readers/envoy.ts"() {
62138
62139
  "use strict";
62139
62140
  init_registry();
62140
- SENTINEL_RE = /--\s*writ:([A-Z]+):([^\s:]+):START/g;
62141
+ SENTINEL_RE = /--\s*xSecurity:([A-Z]+):([^\s:]+):START/g;
62141
62142
  envoyReader = {
62142
62143
  async readEmittedArtifacts(spec) {
62143
62144
  const gen = await loadGenerator("envoy");
@@ -62166,8 +62167,8 @@ var init_envoy3 = __esm({
62166
62167
  for (const p2 of rbacPolicies) out.push({ id: p2, kind: "envoy-rbac-policy" });
62167
62168
  const statPrefixes = /* @__PURE__ */ new Set();
62168
62169
  collectStatPrefixes(configDump, statPrefixes);
62169
- statPrefixes.delete("writ_chain_ratelimit");
62170
- statPrefixes.delete("writ_hcm");
62170
+ statPrefixes.delete("x_security_chain_ratelimit");
62171
+ statPrefixes.delete("x_security_hcm");
62171
62172
  for (const s of statPrefixes) out.push({ id: s, kind: "envoy-ratelimit-route" });
62172
62173
  const corsRouteLabels = /* @__PURE__ */ new Set();
62173
62174
  walkCorsRoutes(configDump, corsRouteLabels);
@@ -62203,7 +62204,7 @@ var init_envoy3 = __esm({
62203
62204
  rows.sort((a5, b5) => a5.endpoint.localeCompare(b5.endpoint));
62204
62205
  if (loaded.length === 0) {
62205
62206
  diagnostics.push(
62206
- "envoy /config_dump returned no Writ artifacts \u2014 the bootstrap may not have loaded"
62207
+ "envoy /config_dump returned no x-security artifacts \u2014 the bootstrap may not have loaded"
62207
62208
  );
62208
62209
  } else {
62209
62210
  const haveJwt = loaded.some((l3) => l3.kind === "envoy-http-filter" && l3.id === "envoy.filters.http.jwt_authn");
@@ -62312,7 +62313,7 @@ var init_bunkerweb3 = __esm({
62312
62313
  init_registry();
62313
62314
  RULE_ID_RE5 = /\bid:(\d{4,7})\b/g;
62314
62315
  SOURCE_ENDPOINTS_RE = /^#\s*Source endpoints?:\s*(.+)$/i;
62315
- SECTION_HEADER_RE = /^#\s*Writ-generated [^\n]*$/i;
62316
+ SECTION_HEADER_RE = /^#\s*x-security-generated [^\n]*$/i;
62316
62317
  bunkerwebReader = {
62317
62318
  async readEmittedArtifacts(spec) {
62318
62319
  const gen = await loadGenerator("bunkerweb");
@@ -62341,7 +62342,7 @@ var init_bunkerweb3 = __esm({
62341
62342
  out.push({
62342
62343
  id: "__empty-dump__",
62343
62344
  kind: "coraza-rule",
62344
- rejectionReason: "nginx -T returned no Writ rule ids \u2014 the scheduler may not have synced configs/modsec into the bunkerweb container yet"
62345
+ rejectionReason: "nginx -T returned no x-security rule ids \u2014 the scheduler may not have synced configs/modsec into the bunkerweb container yet"
62345
62346
  });
62346
62347
  }
62347
62348
  if (split2.scheduler) {
@@ -62451,7 +62452,7 @@ function parseOpenappsecPolicy(policyYaml) {
62451
62452
  }
62452
62453
  }
62453
62454
  const schemaEntries = [];
62454
- const ext = doc["writ-extended"];
62455
+ const ext = doc["x-security-extended"];
62455
62456
  const sv = ext?.["schema-validation"];
62456
62457
  if (Array.isArray(sv)) {
62457
62458
  for (const s of sv) {
@@ -62503,7 +62504,7 @@ function countWritVerdicts(container) {
62503
62504
  container,
62504
62505
  "sh",
62505
62506
  "-c",
62506
- 'test -f /var/log/nano_agent/decision.log && grep -c "writ" /var/log/nano_agent/decision.log || echo MISSING'
62507
+ 'test -f /var/log/nano_agent/decision.log && grep -c "x-security" /var/log/nano_agent/decision.log || echo MISSING'
62507
62508
  ],
62508
62509
  { encoding: "utf8", maxBuffer: 1 * 1024 * 1024 }
62509
62510
  );
@@ -62548,12 +62549,12 @@ var init_openappsec3 = __esm({
62548
62549
  out.push({
62549
62550
  id: "__no-policy-loaded__",
62550
62551
  kind: "envoy-endpoint-policy",
62551
- rejectionReason: "/etc/cp/conf/ on the openappsec agent contains no policy/yaml files \u2014 agent did not load any Writ policy"
62552
+ rejectionReason: "/etc/cp/conf/ on the openappsec agent contains no policy/yaml files \u2014 agent did not load any x-security policy"
62552
62553
  });
62553
62554
  return out;
62554
62555
  }
62555
62556
  const presence = /* @__PURE__ */ new Set();
62556
- const reToken = /\b(writ[-A-Za-z0-9_]+|(?:get|post|put|delete|patch|head|options)-[A-Za-z0-9_-]+)\b/gi;
62557
+ const reToken = /\b((?:x-security|writ)[-A-Za-z0-9_]+|(?:get|post|put|delete|patch|head|options)-[A-Za-z0-9_-]+)\b/gi;
62557
62558
  let m3;
62558
62559
  while ((m3 = reToken.exec(confBlob)) !== null) presence.add(m3[1]);
62559
62560
  for (const tok of presence) out.push({ id: tok, kind: "envoy-endpoint-policy" });
@@ -62575,7 +62576,7 @@ var init_openappsec3 = __esm({
62575
62576
  if (verdicts && verdicts.rejectionReason) {
62576
62577
  const n2 = Number(verdicts.rejectionReason.replace(/^verdicts:/, ""));
62577
62578
  if (Number.isFinite(n2)) {
62578
- diagnostics.push(`/var/log/nano_agent/decision.log contains ${n2} Writ-attributed verdict line(s)`);
62579
+ diagnostics.push(`/var/log/nano_agent/decision.log contains ${n2} x-security-attributed verdict line(s)`);
62579
62580
  }
62580
62581
  }
62581
62582
  const loadedIds = new Set(
@@ -62615,7 +62616,7 @@ var init_openappsec3 = __esm({
62615
62616
  function renderTable2(r5) {
62616
62617
  const lines = [];
62617
62618
  const targetLabel = r5.engine ? `${r5.target}/${r5.engine}` : r5.target;
62618
- lines.push(`Writ verify \u2014 target=${targetLabel} \u2014 gateway=${r5.gateway}`);
62619
+ lines.push(`x-security verify \u2014 target=${targetLabel} \u2014 gateway=${r5.gateway}`);
62619
62620
  lines.push("");
62620
62621
  if (r5.diagnostics.length > 0) {
62621
62622
  for (const d5 of r5.diagnostics) lines.push(`! ${d5}`);
@@ -62691,8 +62692,8 @@ function renderSarif(r5) {
62691
62692
  {
62692
62693
  tool: {
62693
62694
  driver: {
62694
- name: "writ-verify",
62695
- informationUri: "https://github.com/writ/writ",
62695
+ name: "x-security-verify",
62696
+ informationUri: "https://github.com/ZeeshanSultan/x-security",
62696
62697
  rules: [
62697
62698
  {
62698
62699
  id: "writ.verify.load-failure",
@@ -62867,7 +62868,7 @@ async function runTest(specPath, opts) {
62867
62868
  }
62868
62869
  spec.servers = [{ url: upstreamForSpec }];
62869
62870
  const artifacts = await gen.generate(spec);
62870
- const tmpDir = path5.join(os.tmpdir(), `writ-${target}-${Date.now()}-${process.pid}`);
62871
+ const tmpDir = path5.join(os.tmpdir(), `x-security-${target}-${Date.now()}-${process.pid}`);
62871
62872
  await mkdir2(tmpDir, { recursive: true });
62872
62873
  for (const a5 of artifacts) {
62873
62874
  const full = path5.join(tmpDir, a5.path);
@@ -63254,7 +63255,7 @@ var init_feasibility = __esm({
63254
63255
  { capKey: "request.allowedHosts", present: (p2) => nonEmpty(p2.request?.allowedHosts) },
63255
63256
  { capKey: "mtls", present: (p2) => nonEmpty(p2.mtls) }
63256
63257
  ],
63257
- // SSEC-INJECTION = Writ-native injection category (W19). Mitigated by
63258
+ // SSEC-INJECTION = x-security-native injection category (W19). Mitigated by
63258
63259
  // per-arg sink hardening: request.schema.<field>.injectionGuard. The probe
63259
63260
  // resolves against the `request.schema.injectionGuard` capability key, which
63260
63261
  // kong + envoy currently advertise `unsupported` (no libinjection/@detectSQLi
@@ -63265,7 +63266,7 @@ var init_feasibility = __esm({
63265
63266
  "SSEC-INJECTION": [
63266
63267
  { capKey: "request.schema.injectionGuard", present: hasInjectionGuard }
63267
63268
  ],
63268
- // SSEC-PROMPT = Writ-native LLM prompt-injection category (v0.7). A
63269
+ // SSEC-PROMPT = x-security-native LLM prompt-injection category (v0.7). A
63269
63270
  // distinct threat class from SSEC-INJECTION (one synthetic id per class), so
63270
63271
  // it gets its own probe keyed on injectionGuard.includes('ai-prompt'). It
63271
63272
  // resolves against the SAME `request.schema.injectionGuard` capability key —
@@ -63274,7 +63275,7 @@ var init_feasibility = __esm({
63274
63275
  "SSEC-PROMPT": [
63275
63276
  { capKey: "request.schema.injectionGuard", present: hasAiPromptGuard }
63276
63277
  ],
63277
- // SSEC-AUDIT = Writ-native audit-logging category (v0.7). Mitigated by
63278
+ // SSEC-AUDIT = x-security-native audit-logging category (v0.7). Mitigated by
63278
63279
  // the declarative `logging` policy (events/sink/sinkRef/piiRedaction). The
63279
63280
  // probe resolves against the flat `logging` capability key; until a generator
63280
63281
  // advertises it the verdict honestly resolves to none/partial rather than an
@@ -63282,7 +63283,7 @@ var init_feasibility = __esm({
63282
63283
  "SSEC-AUDIT": [
63283
63284
  { capKey: "logging", present: (p2) => nonEmpty(p2.logging) }
63284
63285
  ],
63285
- // SSEC-STORAGE = Writ-native at-rest storage posture (v0.8). ADVISORY
63286
+ // SSEC-STORAGE = x-security-native at-rest storage posture (v0.8). ADVISORY
63286
63287
  // ONLY. request.dataAtRest declares the protection the *app* must apply to
63287
63288
  // named body fields; the gateway never sees the DB write, so this compiles to
63288
63289
  // NOTHING enforcing. The capability is hard-pinned override-only/unsupported
@@ -63902,7 +63903,7 @@ var init_propose_annotation = __esm({
63902
63903
  required: ["method", "path"]
63903
63904
  };
63904
63905
  proposeAnnotationTool = {
63905
- name: "writ/propose-annotation",
63906
+ name: "x-security/propose-annotation",
63906
63907
  description: "Propose an x-security block for a route signature. Pure heuristic; no network calls. Returns YAML the agent can paste under the operation in its OpenAPI spec.",
63907
63908
  inputSchema,
63908
63909
  handler: (raw) => {
@@ -63987,8 +63988,8 @@ var init_lint_annotation = __esm({
63987
63988
  required: ["annotation"]
63988
63989
  };
63989
63990
  lintAnnotationTool = {
63990
- name: "writ/lint-annotation",
63991
- description: "Validate an x-security block against the Writ schema and return a LOW/MEDIUM/HIGH confidence verdict plus structural warnings.",
63991
+ name: "x-security/lint-annotation",
63992
+ description: "Validate an x-security block against the x-security schema and return a LOW/MEDIUM/HIGH confidence verdict plus structural warnings.",
63992
63993
  inputSchema: inputSchema2,
63993
63994
  handler: (raw) => {
63994
63995
  const input = raw ?? {};
@@ -64018,8 +64019,8 @@ var init_lint_annotation = __esm({
64018
64019
  // ../cursor-mcp/dist/tools/check-endpoint.js
64019
64020
  import { request as request6 } from "undici";
64020
64021
  async function checkEndpoint(input, fetcher = defaultFetcher) {
64021
- const apiKey = input.apiKey ?? process.env.WRIT_API_KEY;
64022
- const apiUrl = process.env.WRIT_API_URL ?? DEFAULT_API_URL;
64022
+ const apiKey = input.apiKey ?? process.env.X_SECURITY_API_KEY ?? process.env.WRIT_API_KEY;
64023
+ const apiUrl = process.env.X_SECURITY_API_URL ?? process.env.WRIT_API_URL ?? DEFAULT_API_URL;
64023
64024
  if (!apiKey) {
64024
64025
  return {
64025
64026
  configured: false,
@@ -64068,8 +64069,8 @@ var init_check_endpoint = __esm({
64068
64069
  return { statusCode: res.statusCode, body };
64069
64070
  };
64070
64071
  checkEndpointTool = {
64071
- name: "writ/check-endpoint",
64072
- description: "Look up an endpoint in the Writ scan history. Returns existing analysis (annotations, OWASP coverage, prior findings) if WRIT_API_KEY is configured.",
64072
+ name: "x-security/check-endpoint",
64073
+ description: "Look up an endpoint in the x-security scan history. Returns existing analysis (annotations, OWASP coverage, prior findings) if WRIT_API_KEY is configured.",
64073
64074
  inputSchema: inputSchema3,
64074
64075
  handler: async (raw) => {
64075
64076
  const input = raw ?? {};
@@ -64260,7 +64261,7 @@ var init_dist5 = __esm({
64260
64261
  init_server();
64261
64262
  init_transport();
64262
64263
  dist_default = main;
64263
- isDirectRun = import.meta.url === `file://${process.argv[1] ?? ""}` || process.argv[1]?.endsWith("cursor-mcp/dist/index.js") === true || process.argv[1]?.endsWith("writ-cursor-mcp") === true;
64264
+ isDirectRun = import.meta.url === `file://${process.argv[1] ?? ""}` || process.argv[1]?.endsWith("cursor-mcp/dist/index.js") === true || process.argv[1]?.endsWith("x-security-cursor-mcp") === true;
64264
64265
  if (isDirectRun) {
64265
64266
  main().catch((e5) => {
64266
64267
  process.stderr.write(`cursor-mcp: fatal: ${e5.message}
@@ -64333,7 +64334,7 @@ async function runVerifyBundle(tarballPath, opts = {}) {
64333
64334
  } else {
64334
64335
  pubKeyPem = BUNDLE_VERIFY_PUBLIC_KEY;
64335
64336
  }
64336
- const tmpdir5 = fs.mkdtempSync(path6.join(os2.tmpdir(), "writ-verify-"));
64337
+ const tmpdir5 = fs.mkdtempSync(path6.join(os2.tmpdir(), "x-security-verify-"));
64337
64338
  try {
64338
64339
  await extractTarball(tarballPath, tmpdir5);
64339
64340
  const manifestPath = path6.join(tmpdir5, MANIFEST);
@@ -64640,11 +64641,33 @@ function endpointToFileId(method, routePath) {
64640
64641
  const safePath = routePath.replace(/^\//, "").replace(/\//g, "__").replace(/[^A-Za-z0-9_:.\-{}#]/g, "_");
64641
64642
  return `${method.toUpperCase()}__${safePath || "root"}`;
64642
64643
  }
64643
- function writDir(repoDir) {
64644
+ function xSecurityDir(repoDir) {
64645
+ return path7.join(repoDir, ".x-security");
64646
+ }
64647
+ function legacyWritDir(repoDir) {
64644
64648
  return path7.join(repoDir, ".writ");
64645
64649
  }
64650
+ async function resolveArtifactDir(repoDir) {
64651
+ const canonical = xSecurityDir(repoDir);
64652
+ try {
64653
+ await fs2.access(canonical);
64654
+ return canonical;
64655
+ } catch {
64656
+ }
64657
+ const legacy = legacyWritDir(repoDir);
64658
+ try {
64659
+ await fs2.access(legacy);
64660
+ console.warn("[x-security] reading legacy .writ/ artifact dir; run a compile to migrate to .x-security/");
64661
+ return legacy;
64662
+ } catch {
64663
+ }
64664
+ return canonical;
64665
+ }
64646
64666
  function policiesDir(repoDir) {
64647
- return path7.join(writDir(repoDir), POLICIES_DIR);
64667
+ return path7.join(xSecurityDir(repoDir), POLICIES_DIR);
64668
+ }
64669
+ async function resolvePoliciesDir(repoDir) {
64670
+ return path7.join(await resolveArtifactDir(repoDir), POLICIES_DIR);
64648
64671
  }
64649
64672
  async function persistPolicy(repoDir, route, policy, cites) {
64650
64673
  const dir = policiesDir(repoDir);
@@ -70276,7 +70299,7 @@ var init_emit = __esm({
70276
70299
  "../detect-core/dist/agentic/emit.js"() {
70277
70300
  "use strict";
70278
70301
  init_dist3();
70279
- AUTH_JWKS_VAR = "${WRIT_AUTH_JWKS_URI}";
70302
+ AUTH_JWKS_VAR = "${X_SECURITY_AUTH_JWKS_URI}";
70280
70303
  AUTHZ_LOCATION_SEGMENT = {
70281
70304
  path: "request.params",
70282
70305
  query: "request.query",
@@ -72847,7 +72870,7 @@ async function citeMatches(repoDir, cite) {
72847
72870
  return snap !== null;
72848
72871
  }
72849
72872
  async function runAudit(repoDir) {
72850
- const dir = policiesDir(repoDir);
72873
+ const dir = await resolvePoliciesDir(repoDir);
72851
72874
  let entries;
72852
72875
  try {
72853
72876
  entries = (await fs18.readdir(dir)).filter((f5) => f5.endsWith(".yaml") || f5.endsWith(".yml"));
@@ -72956,7 +72979,7 @@ import { execFile } from "node:child_process";
72956
72979
  import { promisify } from "node:util";
72957
72980
  import yaml15 from "js-yaml";
72958
72981
  function resolveApiUrl(env) {
72959
- const override = env.WRIT_API_URL?.trim();
72982
+ const override = (env.X_SECURITY_API_URL ?? env.WRIT_API_URL)?.trim();
72960
72983
  if (!override) return DEFAULT_API_URL2;
72961
72984
  let parsed;
72962
72985
  try {
@@ -72975,16 +72998,16 @@ function resolveApiUrl(env) {
72975
72998
  const allowed = LOCAL_HOSTS.has(host) || HOST_ALLOWLIST_SUFFIXES.some((s) => host === s.slice(1) || host.endsWith(s));
72976
72999
  if (!allowed) {
72977
73000
  throw new PushError(
72978
- `Refusing to send the API token to "${host}". WRIT_API_URL must be the Writ SaaS (host ending in ${HOST_ALLOWLIST_SUFFIXES.join(" or ")}, or localhost for dev). This guard prevents token exfiltration to an attacker-controlled host.`
73001
+ `Refusing to send the API token to "${host}". WRIT_API_URL must be the x-security SaaS (host ending in ${HOST_ALLOWLIST_SUFFIXES.join(" or ")}, or localhost for dev). This guard prevents token exfiltration to an attacker-controlled host.`
72979
73002
  );
72980
73003
  }
72981
73004
  return override.replace(/\/+$/, "");
72982
73005
  }
72983
73006
  function resolveToken(env) {
72984
- const token = env.WRIT_API_TOKEN?.trim();
73007
+ const token = (env.X_SECURITY_API_TOKEN ?? env.WRIT_API_TOKEN)?.trim();
72985
73008
  if (!token) {
72986
73009
  throw new PushError(
72987
- "WRIT_API_TOKEN is not set. Export your Writ API key (WRIT_API_TOKEN=...) \u2014 push reads it from the environment only, never from a flag."
73010
+ "WRIT_API_TOKEN is not set. Export your x-security API key (WRIT_API_TOKEN=...) \u2014 push reads it from the environment only, never from a flag."
72988
73011
  );
72989
73012
  }
72990
73013
  return token;
@@ -73026,7 +73049,7 @@ async function resolveRepoIdentity(repoDir) {
73026
73049
  return { repoUrl: normalizeRemoteUrl(remote), commitSha };
73027
73050
  }
73028
73051
  async function loadPolicies(repoDir) {
73029
- const dir = policiesDir(repoDir);
73052
+ const dir = await resolvePoliciesDir(repoDir);
73030
73053
  let files;
73031
73054
  try {
73032
73055
  files = (await fs19.readdir(dir)).filter((f5) => f5.endsWith(".yaml") || f5.endsWith(".yml"));
@@ -73061,7 +73084,7 @@ async function loadPolicies(repoDir) {
73061
73084
  }
73062
73085
  async function loadReport(repoDir) {
73063
73086
  try {
73064
- return await fs19.readFile(path25.join(writDir(repoDir), REPORT_FILE), "utf8");
73087
+ return await fs19.readFile(path25.join(await resolveArtifactDir(repoDir), REPORT_FILE), "utf8");
73065
73088
  } catch {
73066
73089
  return void 0;
73067
73090
  }
@@ -73391,7 +73414,7 @@ var init_compile3 = __esm({
73391
73414
  function renderReport3(audit, routes) {
73392
73415
  const pct = (audit.coverage * 100).toFixed(1);
73393
73416
  const lines = [];
73394
- lines.push("# Writ report");
73417
+ lines.push("# x-security report");
73395
73418
  lines.push("");
73396
73419
  lines.push(`- Routes with a compiled policy: **${audit.routes}**`);
73397
73420
  lines.push(`- Enforced controls: **${audit.controls}**`);
@@ -73444,16 +73467,16 @@ var init_ci_gate = __esm({
73444
73467
  'echo "$OUT"',
73445
73468
  `CITE_BACKED="$(printf '%s' "$OUT" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(String(JSON.parse(s).citeBacked)))')"`,
73446
73469
  'if [ "$CITE_BACKED" != "true" ]; then',
73447
- ' echo "::error::Writ gate failed \u2014 an emitted control no longer cites your code." >&2',
73470
+ ' echo "::error::x-security gate failed \u2014 an emitted control no longer cites your code." >&2',
73448
73471
  " exit 1",
73449
73472
  "fi",
73450
73473
  ""
73451
73474
  ].join("\n");
73452
73475
  GITHUB_WORKFLOW = [
73453
- "name: Writ gate",
73476
+ "name: x-security gate",
73454
73477
  "on: [pull_request, push]",
73455
73478
  "jobs:",
73456
- " writ-audit:",
73479
+ " x-security-audit:",
73457
73480
  " runs-on: ubuntu-latest",
73458
73481
  " steps:",
73459
73482
  " - uses: actions/checkout@v4",
@@ -73466,7 +73489,7 @@ var init_ci_gate = __esm({
73466
73489
  ].join("\n");
73467
73490
  GITLAB_SNIPPET = [
73468
73491
  "# Append to .gitlab-ci.yml",
73469
- "writ-audit:",
73492
+ "x-security-audit:",
73470
73493
  " image: node:20",
73471
73494
  " script:",
73472
73495
  " - bash .writ/ci/audit-gate.sh",
@@ -73483,7 +73506,7 @@ import { promises as fs20 } from "node:fs";
73483
73506
  import path26 from "node:path";
73484
73507
  import yaml16 from "js-yaml";
73485
73508
  async function loadPolicies2(repoDir) {
73486
- const dir = policiesDir(repoDir);
73509
+ const dir = await resolvePoliciesDir(repoDir);
73487
73510
  let files;
73488
73511
  try {
73489
73512
  files = (await fs20.readdir(dir)).filter((f5) => f5.endsWith(".yaml") || f5.endsWith(".yml"));
@@ -73517,7 +73540,7 @@ function toSpecIR(policies) {
73517
73540
  return {
73518
73541
  openapi: "3.1.0",
73519
73542
  dialect: "3.1",
73520
- info: { title: "Writ BYO-agent policies", version: "0.0.0" },
73543
+ info: { title: "x-security BYO-agent policies", version: "0.0.0" },
73521
73544
  servers: [],
73522
73545
  endpoints,
73523
73546
  unprotectedEndpoints: []
@@ -73527,7 +73550,7 @@ async function emitWaf(repoDir, policies) {
73527
73550
  const gen = await loadGenerator("bunkerweb");
73528
73551
  if (!gen) throw new Error("bunkerweb generator unavailable");
73529
73552
  const artifacts = await gen.generate(toSpecIR(policies));
73530
- const outDir = path26.join(writDir(repoDir), WAF_DIR);
73553
+ const outDir = path26.join(xSecurityDir(repoDir), WAF_DIR);
73531
73554
  await fs20.mkdir(outDir, { recursive: true });
73532
73555
  const written = [];
73533
73556
  for (const a5 of artifacts) {
@@ -73539,13 +73562,13 @@ async function emitWaf(repoDir, policies) {
73539
73562
  return written;
73540
73563
  }
73541
73564
  async function emitReport(repoDir, audit, policies) {
73542
- await fs20.mkdir(writDir(repoDir), { recursive: true });
73543
- const dest = path26.join(writDir(repoDir), REPORT_FILE);
73565
+ await fs20.mkdir(xSecurityDir(repoDir), { recursive: true });
73566
+ const dest = path26.join(xSecurityDir(repoDir), REPORT_FILE);
73544
73567
  await fs20.writeFile(dest, renderReport3(audit, policies.map((p2) => ({ method: p2.method, path: p2.routePath }))), "utf8");
73545
73568
  return [dest];
73546
73569
  }
73547
73570
  async function emitCi(repoDir) {
73548
- const outDir = path26.join(writDir(repoDir), CI_DIR);
73571
+ const outDir = path26.join(xSecurityDir(repoDir), CI_DIR);
73549
73572
  await fs20.mkdir(outDir, { recursive: true });
73550
73573
  const gate = renderCiGate();
73551
73574
  const written = [];
@@ -74873,7 +74896,7 @@ async function resolveSpecArg(arg, stdin = process.stdin) {
74873
74896
  if (!content) {
74874
74897
  throw new Error("expected a spec document on stdin, got empty input");
74875
74898
  }
74876
- const dir = fs22.mkdtempSync(path29.join(os4.tmpdir(), "writ-stdin-"));
74899
+ const dir = fs22.mkdtempSync(path29.join(os4.tmpdir(), "x-security-stdin-"));
74877
74900
  tempSpecDirs.add(dir);
74878
74901
  if (!exitHandlerRegistered) {
74879
74902
  exitHandlerRegistered = true;
@@ -75001,7 +75024,7 @@ var init_lazy = __esm({
75001
75024
  init_dist4();
75002
75025
  program = new Command2();
75003
75026
  program.name(basename2(process.argv[1] ?? "lazy").replace(/\.(mjs|cjs|js)$/, "")).description("Compile, validate, test, and report on x-security policies in OpenAPI specs.").option("--quiet", "Suppress warnings/advisories on stderr (results still print)").option("--verbose", "Print extra progress detail to stderr").version(resolveVersion()).showHelpAfterError("(add --help for usage)");
75004
- program.command("generate <spec>").description("Compile an annotated OpenAPI spec into target-specific gateway config. <spec> may be - to read from stdin.").requiredOption("--target <name>", "Target: kong|coraza|bunkerweb|openappsec|firewall").option("--out <dir>", "Output directory (default: ./writ-out/<target>)").option("--dry-run", "Print planned artifacts without writing to disk").option("--no-strict", "Allow unresolved variables (default: strict)").option("--vault", "Resolve $vault.* refs via HashiCorp Vault (uses VAULT_ADDR + VAULT_TOKEN or AppRole)").option("--aws-secrets", "Resolve $aws.* refs via AWS Secrets Manager (uses default credential chain + AWS_REGION)").option("--vault-kv-version <v>", "Vault KV engine version: 1 or 2 (default 2)", (v) => v === "1" ? 1 : 2).option("--no-with-consumers", "Kong only: emit spec-only output (omit consumers + per-plugin credentials). Consumers are emitted by default so OSS Kong gateways actually authenticate; pass this for the spec-only baseline.").option("--kong-deployment <mode>", "Kong only: standalone|behind-proxy|with-coraza|with-istio. with-coraza rewrites all service URLs to http://coraza:8080.", "standalone").option("--kong-edition <edition>", "Kong only: oss|enterprise. enterprise emits openid-connect (real JWKS+RS256) instead of HS256 jwt_secrets downgrade.", "oss").option("--kong-dbless", "Kong only: deployment runs in DB-less mode (KONG_DATABASE=off). Per-identity rate-limits fall back to policy=local with a structured warning instead of policy=cluster.", false).option("--kong-policy <mode>", "Kong only: rate-limit policy. local|cluster. Default local (safe for OSS DB-less). Pass cluster only when Kong is running in database mode (postgres/cassandra); cluster is a boot error in DB-less Kong.", "local").option("--coraza-engine <name>", "Coraza only: modsec-nginx|modsec-apache|coraza-go|coraza-spoa (default modsec-nginx).", "modsec-nginx").option("--coraza-peers <list>", 'Coraza only (coraza-spoa|coraza-go): HAProxy peer-replication for stick-tables. Format: "name1:host1:port1,name2:host2:port2". When set, emitted haproxy-stick-tables.cfg includes a `peers writ` section so counters replicate across instances.').option("--strict-fidelity", "Exit 4 (S3) if any spec field cannot be enforced by the chosen target+engine. Independent of --strict.").addHelpText("after", generateExamples).action(async (spec, opts) => {
75027
+ program.command("generate <spec>").description("Compile an annotated OpenAPI spec into target-specific gateway config. <spec> may be - to read from stdin.").requiredOption("--target <name>", "Target: kong|coraza|bunkerweb|openappsec|firewall").option("--out <dir>", "Output directory (default: ./x-security-out/<target>)").option("--dry-run", "Print planned artifacts without writing to disk").option("--no-strict", "Allow unresolved variables (default: strict)").option("--vault", "Resolve $vault.* refs via HashiCorp Vault (uses VAULT_ADDR + VAULT_TOKEN or AppRole)").option("--aws-secrets", "Resolve $aws.* refs via AWS Secrets Manager (uses default credential chain + AWS_REGION)").option("--vault-kv-version <v>", "Vault KV engine version: 1 or 2 (default 2)", (v) => v === "1" ? 1 : 2).option("--no-with-consumers", "Kong only: emit spec-only output (omit consumers + per-plugin credentials). Consumers are emitted by default so OSS Kong gateways actually authenticate; pass this for the spec-only baseline.").option("--kong-deployment <mode>", "Kong only: standalone|behind-proxy|with-coraza|with-istio. with-coraza rewrites all service URLs to http://coraza:8080.", "standalone").option("--kong-edition <edition>", "Kong only: oss|enterprise. enterprise emits openid-connect (real JWKS+RS256) instead of HS256 jwt_secrets downgrade.", "oss").option("--kong-dbless", "Kong only: deployment runs in DB-less mode (KONG_DATABASE=off). Per-identity rate-limits fall back to policy=local with a structured warning instead of policy=cluster.", false).option("--kong-policy <mode>", "Kong only: rate-limit policy. local|cluster. Default local (safe for OSS DB-less). Pass cluster only when Kong is running in database mode (postgres/cassandra); cluster is a boot error in DB-less Kong.", "local").option("--coraza-engine <name>", "Coraza only: modsec-nginx|modsec-apache|coraza-go|coraza-spoa (default modsec-nginx).", "modsec-nginx").option("--coraza-peers <list>", 'Coraza only (coraza-spoa|coraza-go): HAProxy peer-replication for stick-tables. Format: "name1:host1:port1,name2:host2:port2". When set, emitted haproxy-stick-tables.cfg includes a `peers x-security` section so counters replicate across instances.').option("--strict-fidelity", "Exit 4 (S3) if any spec field cannot be enforced by the chosen target+engine. Independent of --strict.").addHelpText("after", generateExamples).action(async (spec, opts) => {
75005
75028
  try {
75006
75029
  const diag = makeDiagnostics(verbosity());
75007
75030
  const fromStdin = spec === "-";
@@ -75371,14 +75394,14 @@ import { dump as yamlDump } from "js-yaml";
75371
75394
  function luaStr2(s) {
75372
75395
  return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r") + '"';
75373
75396
  }
75374
- var SS_RESPONSE_STRIP_UNKNOWN_TAG = "writ-response-strip-unknown";
75397
+ var SS_RESPONSE_STRIP_UNKNOWN_TAG = "x-security-response-strip-unknown";
75375
75398
  function buildResponseStripUnknownPlugins(resp, ctx = {}) {
75376
75399
  if (!resp || resp.stripUnknownFields !== true) return [];
75377
75400
  const schemaKeys = resp.schema ? Object.keys(resp.schema) : [];
75378
75401
  if (schemaKeys.length === 0) return [];
75379
75402
  const luaAllowTable = "{" + schemaKeys.map((k5) => `[${luaStr2(k5)}]=true`).join(", ") + "}";
75380
75403
  const lua = [
75381
- `-- Writ W26 response strip-unknown-fields for endpoint=${ctx.endpoint ?? "?"}`,
75404
+ `-- XSecurity W26 response strip-unknown-fields for endpoint=${ctx.endpoint ?? "?"}`,
75382
75405
  `local cjson = require("cjson.safe")`,
75383
75406
  `local ss_allow = ${luaAllowTable}`,
75384
75407
  `local raw = kong.response.get_raw_body()`,
@@ -75403,7 +75426,7 @@ function buildResponseStripUnknownPlugins(resp, ctx = {}) {
75403
75426
  tags: [SS_RESPONSE_STRIP_UNKNOWN_TAG]
75404
75427
  }];
75405
75428
  }
75406
- var SS_RESPONSE_STRIP_TRACES_TAG = "writ-response-strip-traces";
75429
+ var SS_RESPONSE_STRIP_TRACES_TAG = "x-security-response-strip-traces";
75407
75430
  var STACK_TRACE_LUA_PATTERNS = [
75408
75431
  "\n%s*at%s+[%w%._$<>]+%([^%)]*%)",
75409
75432
  // Java/JS: " at com.foo.Bar.baz(File.java:42)"
@@ -75422,7 +75445,7 @@ function buildResponseStripTracesPlugins(resp, ctx = {}) {
75422
75445
  if (!resp?.errorScrubbing?.stripStackTraces) return [];
75423
75446
  const subs = STACK_TRACE_LUA_PATTERNS.map((p2) => ` body = body:gsub(${luaStr2(p2)}, "")`).join("\n");
75424
75447
  const lua = [
75425
- `-- Writ W26 response strip-stack-traces for endpoint=${ctx.endpoint ?? "?"}`,
75448
+ `-- XSecurity W26 response strip-stack-traces for endpoint=${ctx.endpoint ?? "?"}`,
75426
75449
  `local status = kong.response.get_status()`,
75427
75450
  `if status >= 400 then`,
75428
75451
  ` local body = kong.response.get_raw_body()`,
@@ -75442,11 +75465,11 @@ function buildResponseStripTracesPlugins(resp, ctx = {}) {
75442
75465
  tags: [SS_RESPONSE_STRIP_TRACES_TAG]
75443
75466
  }];
75444
75467
  }
75445
- var SS_RESPONSE_GENERIC_ERROR_TAG = "writ-response-generic-error";
75468
+ var SS_RESPONSE_GENERIC_ERROR_TAG = "x-security-response-generic-error";
75446
75469
  function buildResponseGenericErrorPlugins(resp, ctx = {}) {
75447
75470
  if (!resp?.errorScrubbing?.genericMessages) return [];
75448
75471
  const lua = [
75449
- `-- Writ W26 response generic-error-message for endpoint=${ctx.endpoint ?? "?"}`,
75472
+ `-- XSecurity W26 response generic-error-message for endpoint=${ctx.endpoint ?? "?"}`,
75450
75473
  `local status = kong.response.get_status()`,
75451
75474
  `if status >= 500 then`,
75452
75475
  ` kong.log.warn("[${SS_RESPONSE_GENERIC_ERROR_TAG}] rewrote 5xx body to generic message")`,
@@ -75460,13 +75483,13 @@ function buildResponseGenericErrorPlugins(resp, ctx = {}) {
75460
75483
  tags: [SS_RESPONSE_GENERIC_ERROR_TAG]
75461
75484
  }];
75462
75485
  }
75463
- var SS_RESPONSE_CONTENT_TYPE_TAG = "writ-response-contenttype";
75486
+ var SS_RESPONSE_CONTENT_TYPE_TAG = "x-security-response-contenttype";
75464
75487
  function buildResponseContentTypeAssertPlugins(resp, ctx = {}) {
75465
75488
  const allowed = resp?.contentType;
75466
75489
  if (!allowed || allowed.length === 0) return [];
75467
75490
  const luaSet = allowed.map((t) => `["${t.toLowerCase().replace(/["\\]/g, "")}"]=true`).join(", ");
75468
75491
  const headerFilter = [
75469
- `-- Writ response Content-Type assertion endpoint=${ctx.endpoint ?? "?"}`,
75492
+ `-- XSecurity response Content-Type assertion endpoint=${ctx.endpoint ?? "?"}`,
75470
75493
  `local allowed = { ${luaSet} }`,
75471
75494
  `local status = kong.response.get_status()`,
75472
75495
  `if status >= 200 and status < 300 then`,
@@ -75494,7 +75517,7 @@ function buildResponseContentTypeAssertPlugins(resp, ctx = {}) {
75494
75517
  tags: [SS_RESPONSE_CONTENT_TYPE_TAG]
75495
75518
  }];
75496
75519
  }
75497
- var SS_RESPONSE_SCHEMA_TAG = "writ-response-schema";
75520
+ var SS_RESPONSE_SCHEMA_TAG = "x-security-response-schema";
75498
75521
  var FORMAT_LUA = {
75499
75522
  // RFC-ish email: local@domain.tld, no spaces, one @, a dotted domain.
75500
75523
  email: `return type(v)=="string" and v:match("^[^@%s]+@[^@%s]+%.[^@%s]+$") ~= nil`,
@@ -75619,7 +75642,7 @@ function buildResponseMaxLengthPlugins(resp, ctx = {}) {
75619
75642
  if (rules.length === 0) return [];
75620
75643
  const validators = rules.flatMap((r5) => fieldValidatorLua(r5));
75621
75644
  const lua = [
75622
- `-- Writ W26 response typed-schema validation for endpoint=${ctx.endpoint ?? "?"}`,
75645
+ `-- XSecurity W26 response typed-schema validation for endpoint=${ctx.endpoint ?? "?"}`,
75623
75646
  `-- Validates the cjson-DECODED value (canonical: immune to pretty-printing,`,
75624
75647
  `-- escaped quotes, key order, numeric formatting). Never matches raw body bytes.`,
75625
75648
  `local cjson = require("cjson.safe")`,
@@ -75645,7 +75668,7 @@ function buildResponseMaxLengthPlugins(resp, ctx = {}) {
75645
75668
  tags: [SS_RESPONSE_SCHEMA_TAG]
75646
75669
  }];
75647
75670
  }
75648
- var SS_RATE_LIMIT_FINGERPRINT_TAG = "writ-rate-limit-fingerprint";
75671
+ var SS_RATE_LIMIT_FINGERPRINT_TAG = "x-security-rate-limit-fingerprint";
75649
75672
  function rateLimitsUseFingerprint(rl) {
75650
75673
  if (!rl) return false;
75651
75674
  const arr = Array.isArray(rl) ? rl : [rl];
@@ -75663,9 +75686,9 @@ function rateLimitsUseFingerprint(rl) {
75663
75686
  function buildRateLimitFingerprintPlugins(rl, ctx = {}) {
75664
75687
  if (!rateLimitsUseFingerprint(rl)) return [];
75665
75688
  const lua = [
75666
- `-- Writ W26 rate-limit fingerprint pre-function for endpoint=${ctx.endpoint ?? "?"}`,
75689
+ `-- XSecurity W26 rate-limit fingerprint pre-function for endpoint=${ctx.endpoint ?? "?"}`,
75667
75690
  `-- Composite key = client_ip + sha1(user-agent). Stored on ctx.shared and`,
75668
- `-- mirrored to X-Writ-Fingerprint so rate-limiting limit_by=header picks it up.`,
75691
+ `-- mirrored to X-XSecurity-Fingerprint so rate-limiting limit_by=header picks it up.`,
75669
75692
  `local sha1 = require("resty.sha1"):new()`,
75670
75693
  `local ip = kong.client.get_forwarded_ip() or kong.client.get_ip() or "0.0.0.0"`,
75671
75694
  `local ua = kong.request.get_header("user-agent") or ""`,
@@ -75677,8 +75700,8 @@ function buildRateLimitFingerprintPlugins(rl, ctx = {}) {
75677
75700
  ` for i = 1, #digest do hex = hex .. string.format("%02x", string.byte(digest, i)) end`,
75678
75701
  ` fp = ip .. ":" .. hex:sub(1, 16)`,
75679
75702
  `end`,
75680
- `kong.ctx.shared.writ_fp = fp`,
75681
- `kong.service.request.set_header("X-Writ-Fingerprint", fp)`,
75703
+ `kong.ctx.shared.x_security_fp = fp`,
75704
+ `kong.service.request.set_header("X-XSecurity-Fingerprint", fp)`,
75682
75705
  `kong.log.warn("[${SS_RATE_LIMIT_FINGERPRINT_TAG}] fp=" .. fp)`
75683
75706
  ].join("\n");
75684
75707
  return [{
@@ -75687,7 +75710,7 @@ function buildRateLimitFingerprintPlugins(rl, ctx = {}) {
75687
75710
  tags: [SS_RATE_LIMIT_FINGERPRINT_TAG]
75688
75711
  }];
75689
75712
  }
75690
- var SS_BOT_DETECTED_TAG = "writ-bot-detected";
75713
+ var SS_BOT_DETECTED_TAG = "x-security-bot-detected";
75691
75714
  var BOT_UA_LUA_PATTERNS = [
75692
75715
  "[Cc]url/",
75693
75716
  "[Pp]ython%-requests/",
@@ -75711,14 +75734,14 @@ function buildBotProtectionPlugins(bot, ctx = {}) {
75711
75734
  const checks = BOT_UA_LUA_PATTERNS.map((p2, i5) => ` if ua:match(${luaStr2(p2)}) then return ${i5 + 1} end`).join("\n");
75712
75735
  const blockLua = enforce ? [
75713
75736
  ` return kong.response.exit(403, {`,
75714
- ` message = "Writ: bot detected",`,
75737
+ ` message = "XSecurity: bot detected",`,
75715
75738
  ` tag = "${SS_BOT_DETECTED_TAG}",`,
75716
75739
  ` provider = "${provider}",`,
75717
75740
  ` rule = hit`,
75718
75741
  ` })`
75719
75742
  ].join("\n") : ` -- observe mode: log but pass through`;
75720
75743
  const lua = [
75721
- `-- Writ W26 botProtection pre-function for endpoint=${ctx.endpoint ?? "?"}`,
75744
+ `-- XSecurity W26 botProtection pre-function for endpoint=${ctx.endpoint ?? "?"}`,
75722
75745
  `-- provider=${provider} mode=${bot.mode}`,
75723
75746
  `local ua = kong.request.get_header("user-agent") or ""`,
75724
75747
  `local hit = (function()`,
@@ -75735,7 +75758,7 @@ function buildBotProtectionPlugins(bot, ctx = {}) {
75735
75758
  `if not challenge:match("ss_bot_challenge=") then`,
75736
75759
  ` kong.log.warn("[${SS_BOT_DETECTED_TAG}] missing js-challenge cookie")`,
75737
75760
  ` return kong.response.exit(403, {`,
75738
- ` message = "Writ: bot challenge required",`,
75761
+ ` message = "XSecurity: bot challenge required",`,
75739
75762
  ` tag = "${SS_BOT_DETECTED_TAG}",`,
75740
75763
  ` provider = "${provider}"`,
75741
75764
  ` })`,
@@ -75759,7 +75782,7 @@ function durationSeconds(window2) {
75759
75782
  if (!m3) return 60;
75760
75783
  return Number(m3[1]) * (WINDOW_TO_SECONDS2[m3[2].toLowerCase()] ?? 60);
75761
75784
  }
75762
- var SS_AUDIT_TAG = "writ-audit-log";
75785
+ var SS_AUDIT_TAG = "x-security-audit-log";
75763
75786
  var LOGGING_SINK_PLUGIN = {
75764
75787
  stdout: "file-log",
75765
75788
  file: "file-log",
@@ -75867,7 +75890,7 @@ function buildLoggingPlugins(policy, ctx = {}) {
75867
75890
  tags: [SS_AUDIT_TAG]
75868
75891
  }];
75869
75892
  }
75870
- var SS_PASSWORD_POLICY_TAG = "writ-password-policy-422";
75893
+ var SS_PASSWORD_POLICY_TAG = "x-security-password-policy-422";
75871
75894
  function buildPasswordPolicyPlugins(auth, ctx = {}) {
75872
75895
  const pp = auth?.passwordPolicy;
75873
75896
  if (!pp) return [];
@@ -75915,7 +75938,7 @@ function buildPasswordPolicyPlugins(auth, ctx = {}) {
75915
75938
  );
75916
75939
  }
75917
75940
  const lua = [
75918
- `-- Writ v0.7 password-policy pre-function for endpoint=${ctx.endpoint ?? "?"}`,
75941
+ `-- XSecurity v0.7 password-policy pre-function for endpoint=${ctx.endpoint ?? "?"}`,
75919
75942
  `local body = kong.request.get_body()`,
75920
75943
  `if type(body) == "table" and type(body.password) == "string" then`,
75921
75944
  ` local pw = body.password`,
@@ -75926,7 +75949,7 @@ function buildPasswordPolicyPlugins(auth, ctx = {}) {
75926
75949
  ` if violation ~= nil then`,
75927
75950
  ` kong.log.warn("[${SS_PASSWORD_POLICY_TAG}] password rejected: " .. violation)`,
75928
75951
  ` return kong.response.exit(422, {`,
75929
- ` message = "Writ: password does not meet policy",`,
75952
+ ` message = "XSecurity: password does not meet policy",`,
75930
75953
  ` tag = "${SS_PASSWORD_POLICY_TAG}",`,
75931
75954
  ` violation = violation`,
75932
75955
  ` })`,
@@ -75939,11 +75962,11 @@ function buildPasswordPolicyPlugins(auth, ctx = {}) {
75939
75962
  tags: [SS_PASSWORD_POLICY_TAG]
75940
75963
  }];
75941
75964
  }
75942
- var SS_FORBID_ARRAY_ROOT_TAG = "writ-forbid-array-root-502";
75965
+ var SS_FORBID_ARRAY_ROOT_TAG = "x-security-forbid-array-root-502";
75943
75966
  function buildForbidArrayRootPlugins(resp, ctx = {}) {
75944
75967
  if (!resp || resp.forbidArrayRoot !== true) return [];
75945
75968
  const lua = [
75946
- `-- Writ v0.7 forbidArrayRoot post-function for endpoint=${ctx.endpoint ?? "?"}`,
75969
+ `-- XSecurity v0.7 forbidArrayRoot post-function for endpoint=${ctx.endpoint ?? "?"}`,
75947
75970
  `local cjson = require("cjson.safe")`,
75948
75971
  `local raw = kong.response.get_raw_body()`,
75949
75972
  `if raw and #raw > 0 then`,
@@ -75959,7 +75982,7 @@ function buildForbidArrayRootPlugins(resp, ctx = {}) {
75959
75982
  ` kong.log.warn("[${SS_FORBID_ARRAY_ROOT_TAG}] bare top-level array response blocked (JSON hijacking)")`,
75960
75983
  ` kong.response.set_status(502)`,
75961
75984
  ` kong.response.set_header("Content-Type", "application/json")`,
75962
- ` kong.response.set_raw_body('{"message":"Writ: bare top-level array response forbidden","tag":"${SS_FORBID_ARRAY_ROOT_TAG}"}')`,
75985
+ ` kong.response.set_raw_body('{"message":"XSecurity: bare top-level array response forbidden","tag":"${SS_FORBID_ARRAY_ROOT_TAG}"}')`,
75963
75986
  ` end`,
75964
75987
  ` end`,
75965
75988
  `end`
@@ -75970,8 +75993,8 @@ function buildForbidArrayRootPlugins(resp, ctx = {}) {
75970
75993
  tags: [SS_FORBID_ARRAY_ROOT_TAG]
75971
75994
  }];
75972
75995
  }
75973
- var SS_IDEMPOTENCY_TAG = "writ-idempotency-replay-409";
75974
- var SS_IDEMPOTENCY_CACHE_DICT = "writ_idempotency_cache";
75996
+ var SS_IDEMPOTENCY_TAG = "x-security-idempotency-replay-409";
75997
+ var SS_IDEMPOTENCY_CACHE_DICT = "x_security_idempotency_cache";
75975
75998
  var SS_IDEMPOTENCY_CACHE_DICT_SIZE = "10m";
75976
75999
  function buildIdempotencyKeyPlugins(request9, ctx = {}) {
75977
76000
  const idem = request9?.idempotencyKey;
@@ -75987,8 +76010,8 @@ function buildIdempotencyKeyPlugins(request9, ctx = {}) {
75987
76010
  });
75988
76011
  }
75989
76012
  const lua = [
75990
- `-- Writ v0.7 idempotencyKey pre-function (PARTIAL) for endpoint=${ctx.endpoint ?? "?"}`,
75991
- `-- Best-effort replay suppression only \u2014 NOT atomic idempotency. See _writ_warnings.`,
76013
+ `-- XSecurity v0.7 idempotencyKey pre-function (PARTIAL) for endpoint=${ctx.endpoint ?? "?"}`,
76014
+ `-- Best-effort replay suppression only \u2014 NOT atomic idempotency. See _x_security_warnings.`,
75992
76015
  `local key = kong.request.get_header(${luaStr3(idem.header)})`,
75993
76016
  `if key ~= nil and key ~= "" then`,
75994
76017
  ` local cache = ngx.shared.${SS_IDEMPOTENCY_CACHE_DICT}`,
@@ -75997,7 +76020,7 @@ function buildIdempotencyKeyPlugins(request9, ctx = {}) {
75997
76020
  ` if seen then`,
75998
76021
  ` kong.log.warn("[${SS_IDEMPOTENCY_TAG}] replayed idempotency key '" .. tostring(key) .. "' within ttl")`,
75999
76022
  ` return kong.response.exit(409, {`,
76000
- ` message = "Writ: idempotency key replay rejected",`,
76023
+ ` message = "XSecurity: idempotency key replay rejected",`,
76001
76024
  ` tag = "${SS_IDEMPOTENCY_TAG}"`,
76002
76025
  ` })`,
76003
76026
  ` end`,
@@ -76012,7 +76035,7 @@ function buildIdempotencyKeyPlugins(request9, ctx = {}) {
76012
76035
  tags: [SS_IDEMPOTENCY_TAG]
76013
76036
  }];
76014
76037
  }
76015
- var SS_ACCOUNT_LOCKOUT_TAG = "writ-account-lockout";
76038
+ var SS_ACCOUNT_LOCKOUT_TAG = "x-security-account-lockout";
76016
76039
  function lockoutBucket(seconds) {
76017
76040
  if (seconds <= 1) return "second";
76018
76041
  if (seconds <= 60) return "minute";
@@ -76116,31 +76139,31 @@ function buildConsumers(spec, options = {}) {
76116
76139
  const username = consumerName(role);
76117
76140
  bundle.consumers.push({
76118
76141
  username,
76119
- tags: [`writ_role=${role}`]
76142
+ tags: [`x_security_role=${role}`]
76120
76143
  });
76121
76144
  bundle.acls.push({ consumer: username, group: role });
76122
76145
  if (hasApiKey) {
76123
76146
  bundle.keyauth_credentials.push({
76124
76147
  consumer: username,
76125
- key: `${role}_test_key_${shortHash(`${spec.info?.title ?? "writ"}|${role}`)}`
76148
+ key: `${role}_test_key_${shortHash(`${spec.info?.title ?? "x-security"}|${role}`)}`
76126
76149
  });
76127
76150
  }
76128
76151
  if (hasJwt) {
76129
- const key = jwtIssuer ?? `writ-${shortHash((spec.info?.title ?? "writ") || "spec", 6)}`;
76152
+ const key = jwtIssuer ?? `x-security-${shortHash((spec.info?.title ?? "x-security") || "spec", 6)}`;
76130
76153
  bundle.jwt_secrets.push({
76131
76154
  consumer: username,
76132
76155
  // Per-consumer key MUST be unique across the spec (Kong primary key
76133
76156
  // on `key`), so we suffix the role.
76134
76157
  key: `${key}#${role}`,
76135
76158
  algorithm: "HS256",
76136
- secret: `${role}_jwt_secret_${shortHash(`${spec.info?.title ?? "writ"}|${role}|jwt`, 16)}`
76159
+ secret: `${role}_jwt_secret_${shortHash(`${spec.info?.title ?? "x-security"}|${role}|jwt`, 16)}`
76137
76160
  });
76138
76161
  }
76139
76162
  if (hasHmac) {
76140
76163
  bundle.hmacauth_credentials.push({
76141
76164
  consumer: username,
76142
76165
  username: role,
76143
- secret: `${role}_hmac_secret_${shortHash(`${spec.info?.title ?? "writ"}|${role}|hmac`, 16)}`
76166
+ secret: `${role}_hmac_secret_${shortHash(`${spec.info?.title ?? "x-security"}|${role}|hmac`, 16)}`
76144
76167
  });
76145
76168
  }
76146
76169
  }
@@ -76174,8 +76197,8 @@ function buildConsumers(spec, options = {}) {
76174
76197
  }
76175
76198
 
76176
76199
  // src/generators/kong/index.ts
76177
- var WRIT_KONG_NAMESPACE = "a4f7e8c2-1b3d-4e5f-9a8b-7c6d5e4f3a2b";
76178
- function uuidv5(name, namespace = WRIT_KONG_NAMESPACE) {
76200
+ var X_SECURITY_KONG_NAMESPACE = "a4f7e8c2-1b3d-4e5f-9a8b-7c6d5e4f3a2b";
76201
+ function uuidv5(name, namespace = X_SECURITY_KONG_NAMESPACE) {
76179
76202
  const nsBytes = Buffer.from(namespace.replace(/-/g, ""), "hex");
76180
76203
  const hash = createHash2("sha1").update(nsBytes).update(name).digest();
76181
76204
  const bytes = Buffer.from(hash.subarray(0, 16));
@@ -76240,7 +76263,7 @@ function buildEndpointPlugins(ep, ctx) {
76240
76263
  warn: ctx.warn
76241
76264
  }),
76242
76265
  // v0.7 authentication.accountLockout — per-credential rate-limit throttle
76243
- // (PARTIAL — not a true failed-attempt lockout; see _writ_warnings).
76266
+ // (PARTIAL — not a true failed-attempt lockout; see _x_security_warnings).
76244
76267
  ...buildAccountLockoutPlugins(p2.authentication, {
76245
76268
  ...ep.operationId ? { endpoint: ep.operationId } : {},
76246
76269
  warn: ctx.warn
@@ -76267,7 +76290,7 @@ function buildEndpointPlugins(ep, ctx) {
76267
76290
  warn: ctx.warn
76268
76291
  }),
76269
76292
  // W26: rate-limit fingerprint composite key (ip+ua hash) — runs before
76270
- // request-side checks so the X-Writ-Fingerprint header is set
76293
+ // request-side checks so the X-XSecurity-Fingerprint header is set
76271
76294
  // before any plugin (or upstream) might consume it.
76272
76295
  ...buildRateLimitFingerprintPlugins(p2.rateLimit, {
76273
76296
  ...ep.operationId ? { endpoint: ep.operationId } : {}
@@ -76295,7 +76318,7 @@ function buildEndpointPlugins(ep, ctx) {
76295
76318
  warn: ctx.warn
76296
76319
  }),
76297
76320
  // v0.7 request.idempotencyKey — best-effort shared_dict replay suppression
76298
- // (PARTIAL — not atomic idempotency; see _writ_warnings).
76321
+ // (PARTIAL — not atomic idempotency; see _x_security_warnings).
76299
76322
  ...buildIdempotencyKeyPlugins(p2.request, {
76300
76323
  ...ep.operationId ? { endpoint: ep.operationId } : {},
76301
76324
  warn: ctx.warn
@@ -76478,9 +76501,9 @@ function createKongGenerator(opts = {}) {
76478
76501
  "request.schema": "partial",
76479
76502
  // request-validator is enterprise-only for body_schema; OSS users see degraded enforcement (mass-assign + sqli pre-functions partially cover)
76480
76503
  "request.schema.injectionGuard": "unsupported",
76481
- // HONEST: Kong OSS has no libinjection/@detectSQLi; a pre-function regex would be a fragile fake (Rule D-1). Abstain + _writ_warnings divergence note; front with coraza/bunkerweb which carry SSEC-INJECTION.
76504
+ // HONEST: Kong OSS has no libinjection/@detectSQLi; a pre-function regex would be a fragile fake (Rule D-1). Abstain + _x_security_warnings divergence note; front with coraza/bunkerweb which carry SSEC-INJECTION.
76482
76505
  "request.denyUnknownFields": "full",
76483
- // K-3: pre-function rejects unknown top-level body fields with writ-mass-assign-403
76506
+ // K-3: pre-function rejects unknown top-level body fields with x-security-mass-assign-403
76484
76507
  "request.allowedFields": "full",
76485
76508
  // K-3: pre-function allowlist enforcement
76486
76509
  "request.signature": "partial",
@@ -76533,11 +76556,11 @@ function createKongGenerator(opts = {}) {
76533
76556
  "response.forbidArrayRoot": "full",
76534
76557
  // v0.7 API3: body_filter post-function cjson-decodes the body and rewrites a bare top-level array response to 502. Decoded-value check (not raw regex) → full.
76535
76558
  "request.idempotencyKey": "partial",
76536
- // v0.7 API6: HONEST — Kong OSS has no native idempotency. pre-function shared_dict dedupe is best-effort (per-instance, non-atomic check-and-set); suppresses sequential replays but races on concurrent first-requests. _writ_warnings divergence note. Never full.
76559
+ // v0.7 API6: HONEST — Kong OSS has no native idempotency. pre-function shared_dict dedupe is best-effort (per-instance, non-atomic check-and-set); suppresses sequential replays but races on concurrent first-requests. _x_security_warnings divergence note. Never full.
76537
76560
  "authentication.accountLockout": "partial",
76538
- // v0.7: HONEST — no native per-credential lockout. rate-limiting limit_by=header throttles per-credential attempts (blunts brute-force) but is NOT a fixed-duration lockout and counts all attempts (gateway can't see the auth verdict). Body-field identifiers degrade to unsupported+warning. _writ_warnings note. Never full.
76561
+ // v0.7: HONEST — no native per-credential lockout. rate-limiting limit_by=header throttles per-credential attempts (blunts brute-force) but is NOT a fixed-duration lockout and counts all attempts (gateway can't see the auth verdict). Body-field identifiers degrade to unsupported+warning. _x_security_warnings note. Never full.
76539
76562
  "deprecated": "full",
76540
- // K-5: pre-function returns 410 with writ-deprecated-endpoint-block
76563
+ // K-5: pre-function returns 410 with x-security-deprecated-endpoint-block
76541
76564
  "sunsetDate": "full",
76542
76565
  // K-5: 410 + RFC 8594 Deprecation: true & Sunset: <date> response headers
76543
76566
  "replacementEndpoint": "partial",
@@ -76551,9 +76574,9 @@ function createKongGenerator(opts = {}) {
76551
76574
  }
76552
76575
  function renderWarningsHeader(warnings, deployment, edition) {
76553
76576
  const lines = [];
76554
- lines.push(`# Writ Kong generator \u2014 deployment=${deployment} edition=${edition}`);
76577
+ lines.push(`# XSecurity Kong generator \u2014 deployment=${deployment} edition=${edition}`);
76555
76578
  if (!warnings.length) {
76556
- lines.push("# _writ_warnings: [] (no spec\u2192runtime divergences detected)");
76579
+ lines.push("# _x_security_warnings: [] (no spec\u2192runtime divergences detected)");
76557
76580
  lines.push("");
76558
76581
  return lines.join("\n");
76559
76582
  }
@@ -76562,7 +76585,7 @@ function renderWarningsHeader(warnings, deployment, edition) {
76562
76585
  const ep = w.endpoint ? ` (endpoint=${w.endpoint})` : "";
76563
76586
  lines.push(`# WARNING: ${w.field}${ep}: declared="${w.declared}" emitted="${w.emitted}" \u2014 ${w.reason}`);
76564
76587
  }
76565
- lines.push("# _writ_warnings:");
76588
+ lines.push("# _x_security_warnings:");
76566
76589
  for (const w of warnings) {
76567
76590
  lines.push(`# - field: ${yamlScalar(w.field)}`);
76568
76591
  if (w.endpoint) lines.push(`# endpoint: ${yamlScalar(w.endpoint)}`);