slopbrick 0.34.7 → 0.34.9

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.
@@ -37142,7 +37142,7 @@ var javaHardcodedCredentialRule = createRule({
37142
37142
  // src/rules/java/sql-string-concat.ts
37143
37143
  var SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37144
37144
  var UNSAFE_REGEX = /\+/;
37145
- var SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?\s*,)/;
37145
+ var SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?)/;
37146
37146
  var javaSqlStringConcatRule = createRule({
37147
37147
  id: "java/sql-string-concat",
37148
37148
  category: "security",
@@ -37160,7 +37160,14 @@ var javaSqlStringConcatRule = createRule({
37160
37160
  const lines = source.split("\n");
37161
37161
  for (let i = 0; i < lines.length; i++) {
37162
37162
  const line = lines[i];
37163
- if (!SQL_KEYWORD_REGEX.test(line)) continue;
37163
+ const keywordMatch = SQL_KEYWORD_REGEX.exec(line);
37164
+ if (!keywordMatch) continue;
37165
+ const keywordIdx = keywordMatch.index;
37166
+ let j = keywordIdx - 1;
37167
+ while (j >= 0 && /\s/.test(line[j])) j--;
37168
+ if (j < 0) continue;
37169
+ const prevChar = line[j];
37170
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
37164
37171
  if (!UNSAFE_REGEX.test(line)) continue;
37165
37172
  if (SAFE_REGEX.test(line)) continue;
37166
37173
  issues.push({
@@ -37171,7 +37178,7 @@ var javaSqlStringConcatRule = createRule({
37171
37178
  message: `SQL query built via string concat at line ${i + 1}`,
37172
37179
  line: i + 1,
37173
37180
  column: 1,
37174
- advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.30 (OWASP A03:2021).'
37181
+ advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.34.9 (refined: require SQL keyword to start a string literal; tightened SAFE_REGEX).'
37175
37182
  });
37176
37183
  }
37177
37184
  return issues;
@@ -37701,9 +37708,9 @@ var kotlinRunBlockingMisuseRule = createRule({
37701
37708
  });
37702
37709
 
37703
37710
  // src/rules/kotlin/sql-string-concat.ts
37704
- var SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37711
+ var SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37705
37712
  var UNSAFE_REGEX2 = /(?:\+|\$\{)/;
37706
- var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
37713
+ var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
37707
37714
  var kotlinSqlStringConcatRule = createRule({
37708
37715
  id: "kotlin/sql-string-concat",
37709
37716
  category: "security",
@@ -37721,7 +37728,14 @@ var kotlinSqlStringConcatRule = createRule({
37721
37728
  const lines = source.split("\n");
37722
37729
  for (let i = 0; i < lines.length; i++) {
37723
37730
  const line = lines[i];
37724
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
37731
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
37732
+ if (!keywordMatch) continue;
37733
+ const keywordIdx = keywordMatch.index;
37734
+ let j = keywordIdx - 1;
37735
+ while (j >= 0 && /\s/.test(line[j])) j--;
37736
+ if (j < 0) continue;
37737
+ const prevChar = line[j];
37738
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
37725
37739
  if (!UNSAFE_REGEX2.test(line)) continue;
37726
37740
  if (SAFE_REGEX2.test(line)) continue;
37727
37741
  issues.push({
@@ -37732,7 +37746,7 @@ var kotlinSqlStringConcatRule = createRule({
37732
37746
  message: `SQL query built via string concat/template at line ${i + 1}`,
37733
37747
  line: i + 1,
37734
37748
  column: 1,
37735
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
37749
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
37736
37750
  });
37737
37751
  }
37738
37752
  return issues;
@@ -45540,7 +45554,7 @@ var signal_strength_default = {
45540
45554
  precision: 0.0556,
45541
45555
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45542
45556
  verdict: "DORMANT",
45543
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
45557
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
45544
45558
  aiSpecific: false,
45545
45559
  _v9Verdict: "DORMANT",
45546
45560
  _v9Lift: 0.75,
@@ -45620,7 +45634,7 @@ var signal_strength_default = {
45620
45634
  precision: 0.0691,
45621
45635
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45622
45636
  verdict: "DORMANT",
45623
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.59 \u2014 fires 1.7x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. Same direction as kotlin/sql-string-concat. 1664 total fires \u2014 Java uses ORMs (Hibernate) heavily so SQL concat is rare in both arms. defaultOff.",
45637
+ _calibrationNote: 'v0.34.9: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and tightened the SAFE_REGEX. Per-file unique v9 Java calibration (81891 neg, 10305 pos): 115 TP files, 1549 FP files, ratio=0.59 (DORMANT). 1664 total fires. The refinement is expected to push precision from 6.9% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `String msg = "Selected 1 row: " + count`). Also tightened SAFE_REGEX from `:??` to `:?` (the original was over-broad). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline (same pattern applied to kotlin/sql-string-concat and java/sql-string-concat). INSUFFICIENT_DATA: pos arm 10305 files, below the 10k floor. Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. defaultOff.',
45624
45638
  aiSpecific: false,
45625
45639
  _v9Verdict: "DORMANT",
45626
45640
  _v9Lift: 0.59,
@@ -37113,7 +37113,7 @@ var javaHardcodedCredentialRule = createRule({
37113
37113
  // src/rules/java/sql-string-concat.ts
37114
37114
  var SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37115
37115
  var UNSAFE_REGEX = /\+/;
37116
- var SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?\s*,)/;
37116
+ var SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?)/;
37117
37117
  var javaSqlStringConcatRule = createRule({
37118
37118
  id: "java/sql-string-concat",
37119
37119
  category: "security",
@@ -37131,7 +37131,14 @@ var javaSqlStringConcatRule = createRule({
37131
37131
  const lines = source.split("\n");
37132
37132
  for (let i = 0; i < lines.length; i++) {
37133
37133
  const line = lines[i];
37134
- if (!SQL_KEYWORD_REGEX.test(line)) continue;
37134
+ const keywordMatch = SQL_KEYWORD_REGEX.exec(line);
37135
+ if (!keywordMatch) continue;
37136
+ const keywordIdx = keywordMatch.index;
37137
+ let j = keywordIdx - 1;
37138
+ while (j >= 0 && /\s/.test(line[j])) j--;
37139
+ if (j < 0) continue;
37140
+ const prevChar = line[j];
37141
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
37135
37142
  if (!UNSAFE_REGEX.test(line)) continue;
37136
37143
  if (SAFE_REGEX.test(line)) continue;
37137
37144
  issues.push({
@@ -37142,7 +37149,7 @@ var javaSqlStringConcatRule = createRule({
37142
37149
  message: `SQL query built via string concat at line ${i + 1}`,
37143
37150
  line: i + 1,
37144
37151
  column: 1,
37145
- advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.30 (OWASP A03:2021).'
37152
+ advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.34.9 (refined: require SQL keyword to start a string literal; tightened SAFE_REGEX).'
37146
37153
  });
37147
37154
  }
37148
37155
  return issues;
@@ -37672,9 +37679,9 @@ var kotlinRunBlockingMisuseRule = createRule({
37672
37679
  });
37673
37680
 
37674
37681
  // src/rules/kotlin/sql-string-concat.ts
37675
- var SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37682
+ var SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37676
37683
  var UNSAFE_REGEX2 = /(?:\+|\$\{)/;
37677
- var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
37684
+ var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
37678
37685
  var kotlinSqlStringConcatRule = createRule({
37679
37686
  id: "kotlin/sql-string-concat",
37680
37687
  category: "security",
@@ -37692,7 +37699,14 @@ var kotlinSqlStringConcatRule = createRule({
37692
37699
  const lines = source.split("\n");
37693
37700
  for (let i = 0; i < lines.length; i++) {
37694
37701
  const line = lines[i];
37695
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
37702
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
37703
+ if (!keywordMatch) continue;
37704
+ const keywordIdx = keywordMatch.index;
37705
+ let j = keywordIdx - 1;
37706
+ while (j >= 0 && /\s/.test(line[j])) j--;
37707
+ if (j < 0) continue;
37708
+ const prevChar = line[j];
37709
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
37696
37710
  if (!UNSAFE_REGEX2.test(line)) continue;
37697
37711
  if (SAFE_REGEX2.test(line)) continue;
37698
37712
  issues.push({
@@ -37703,7 +37717,7 @@ var kotlinSqlStringConcatRule = createRule({
37703
37717
  message: `SQL query built via string concat/template at line ${i + 1}`,
37704
37718
  line: i + 1,
37705
37719
  column: 1,
37706
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
37720
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
37707
37721
  });
37708
37722
  }
37709
37723
  return issues;
@@ -45511,7 +45525,7 @@ var signal_strength_default = {
45511
45525
  precision: 0.0556,
45512
45526
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45513
45527
  verdict: "DORMANT",
45514
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
45528
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
45515
45529
  aiSpecific: false,
45516
45530
  _v9Verdict: "DORMANT",
45517
45531
  _v9Lift: 0.75,
@@ -45591,7 +45605,7 @@ var signal_strength_default = {
45591
45605
  precision: 0.0691,
45592
45606
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45593
45607
  verdict: "DORMANT",
45594
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.59 \u2014 fires 1.7x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. Same direction as kotlin/sql-string-concat. 1664 total fires \u2014 Java uses ORMs (Hibernate) heavily so SQL concat is rare in both arms. defaultOff.",
45608
+ _calibrationNote: 'v0.34.9: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and tightened the SAFE_REGEX. Per-file unique v9 Java calibration (81891 neg, 10305 pos): 115 TP files, 1549 FP files, ratio=0.59 (DORMANT). 1664 total fires. The refinement is expected to push precision from 6.9% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `String msg = "Selected 1 row: " + count`). Also tightened SAFE_REGEX from `:??` to `:?` (the original was over-broad). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline (same pattern applied to kotlin/sql-string-concat and java/sql-string-concat). INSUFFICIENT_DATA: pos arm 10305 files, below the 10k floor. Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. defaultOff.',
45595
45609
  aiSpecific: false,
45596
45610
  _v9Verdict: "DORMANT",
45597
45611
  _v9Lift: 0.59,
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ var VERSION;
36
36
  var init_header = __esm({
37
37
  "src/types/_header.ts"() {
38
38
  "use strict";
39
- VERSION = "0.34.7";
39
+ VERSION = "0.34.9";
40
40
  }
41
41
  });
42
42
 
@@ -30805,7 +30805,7 @@ var init_sql_string_concat = __esm({
30805
30805
  init_rule();
30806
30806
  SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
30807
30807
  UNSAFE_REGEX = /\+/;
30808
- SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?\s*,)/;
30808
+ SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?)/;
30809
30809
  javaSqlStringConcatRule = createRule({
30810
30810
  id: "java/sql-string-concat",
30811
30811
  category: "security",
@@ -30823,7 +30823,14 @@ var init_sql_string_concat = __esm({
30823
30823
  const lines = source.split("\n");
30824
30824
  for (let i = 0; i < lines.length; i++) {
30825
30825
  const line = lines[i];
30826
- if (!SQL_KEYWORD_REGEX.test(line)) continue;
30826
+ const keywordMatch = SQL_KEYWORD_REGEX.exec(line);
30827
+ if (!keywordMatch) continue;
30828
+ const keywordIdx = keywordMatch.index;
30829
+ let j = keywordIdx - 1;
30830
+ while (j >= 0 && /\s/.test(line[j])) j--;
30831
+ if (j < 0) continue;
30832
+ const prevChar = line[j];
30833
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
30827
30834
  if (!UNSAFE_REGEX.test(line)) continue;
30828
30835
  if (SAFE_REGEX.test(line)) continue;
30829
30836
  issues.push({
@@ -30834,7 +30841,7 @@ var init_sql_string_concat = __esm({
30834
30841
  message: `SQL query built via string concat at line ${i + 1}`,
30835
30842
  line: i + 1,
30836
30843
  column: 1,
30837
- advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.30 (OWASP A03:2021).'
30844
+ advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.34.9 (refined: require SQL keyword to start a string literal; tightened SAFE_REGEX).'
30838
30845
  });
30839
30846
  }
30840
30847
  return issues;
@@ -31441,9 +31448,9 @@ var init_sql_string_concat2 = __esm({
31441
31448
  "src/rules/kotlin/sql-string-concat.ts"() {
31442
31449
  "use strict";
31443
31450
  init_rule();
31444
- SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31451
+ SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31445
31452
  UNSAFE_REGEX2 = /(?:\+|\$\{)/;
31446
- SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
31453
+ SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
31447
31454
  kotlinSqlStringConcatRule = createRule({
31448
31455
  id: "kotlin/sql-string-concat",
31449
31456
  category: "security",
@@ -31461,7 +31468,14 @@ var init_sql_string_concat2 = __esm({
31461
31468
  const lines = source.split("\n");
31462
31469
  for (let i = 0; i < lines.length; i++) {
31463
31470
  const line = lines[i];
31464
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
31471
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
31472
+ if (!keywordMatch) continue;
31473
+ const keywordIdx = keywordMatch.index;
31474
+ let j = keywordIdx - 1;
31475
+ while (j >= 0 && /\s/.test(line[j])) j--;
31476
+ if (j < 0) continue;
31477
+ const prevChar = line[j];
31478
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
31465
31479
  if (!UNSAFE_REGEX2.test(line)) continue;
31466
31480
  if (SAFE_REGEX2.test(line)) continue;
31467
31481
  issues.push({
@@ -31472,7 +31486,7 @@ var init_sql_string_concat2 = __esm({
31472
31486
  message: `SQL query built via string concat/template at line ${i + 1}`,
31473
31487
  line: i + 1,
31474
31488
  column: 1,
31475
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
31489
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
31476
31490
  });
31477
31491
  }
31478
31492
  return issues;
@@ -51627,7 +51641,7 @@ var init_signal_strength = __esm({
51627
51641
  precision: 0.0556,
51628
51642
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51629
51643
  verdict: "DORMANT",
51630
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
51644
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
51631
51645
  aiSpecific: false,
51632
51646
  _v9Verdict: "DORMANT",
51633
51647
  _v9Lift: 0.75,
@@ -51707,7 +51721,7 @@ var init_signal_strength = __esm({
51707
51721
  precision: 0.0691,
51708
51722
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51709
51723
  verdict: "DORMANT",
51710
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.59 \u2014 fires 1.7x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. Same direction as kotlin/sql-string-concat. 1664 total fires \u2014 Java uses ORMs (Hibernate) heavily so SQL concat is rare in both arms. defaultOff.",
51724
+ _calibrationNote: 'v0.34.9: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and tightened the SAFE_REGEX. Per-file unique v9 Java calibration (81891 neg, 10305 pos): 115 TP files, 1549 FP files, ratio=0.59 (DORMANT). 1664 total fires. The refinement is expected to push precision from 6.9% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `String msg = "Selected 1 row: " + count`). Also tightened SAFE_REGEX from `:??` to `:?` (the original was over-broad). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline (same pattern applied to kotlin/sql-string-concat and java/sql-string-concat). INSUFFICIENT_DATA: pos arm 10305 files, below the 10k floor. Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. defaultOff.',
51711
51725
  aiSpecific: false,
51712
51726
  _v9Verdict: "DORMANT",
51713
51727
  _v9Lift: 0.59,
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var VERSION;
19
19
  var init_header = __esm({
20
20
  "src/types/_header.ts"() {
21
21
  "use strict";
22
- VERSION = "0.34.7";
22
+ VERSION = "0.34.9";
23
23
  }
24
24
  });
25
25
 
@@ -30786,7 +30786,7 @@ var init_sql_string_concat = __esm({
30786
30786
  init_rule();
30787
30787
  SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
30788
30788
  UNSAFE_REGEX = /\+/;
30789
- SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?\s*,)/;
30789
+ SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?)/;
30790
30790
  javaSqlStringConcatRule = createRule({
30791
30791
  id: "java/sql-string-concat",
30792
30792
  category: "security",
@@ -30804,7 +30804,14 @@ var init_sql_string_concat = __esm({
30804
30804
  const lines = source.split("\n");
30805
30805
  for (let i = 0; i < lines.length; i++) {
30806
30806
  const line = lines[i];
30807
- if (!SQL_KEYWORD_REGEX.test(line)) continue;
30807
+ const keywordMatch = SQL_KEYWORD_REGEX.exec(line);
30808
+ if (!keywordMatch) continue;
30809
+ const keywordIdx = keywordMatch.index;
30810
+ let j = keywordIdx - 1;
30811
+ while (j >= 0 && /\s/.test(line[j])) j--;
30812
+ if (j < 0) continue;
30813
+ const prevChar = line[j];
30814
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
30808
30815
  if (!UNSAFE_REGEX.test(line)) continue;
30809
30816
  if (SAFE_REGEX.test(line)) continue;
30810
30817
  issues.push({
@@ -30815,7 +30822,7 @@ var init_sql_string_concat = __esm({
30815
30822
  message: `SQL query built via string concat at line ${i + 1}`,
30816
30823
  line: i + 1,
30817
30824
  column: 1,
30818
- advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.30 (OWASP A03:2021).'
30825
+ advice: 'Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{} binding). String concatenation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: java/sql-string-concat v0.34.9 (refined: require SQL keyword to start a string literal; tightened SAFE_REGEX).'
30819
30826
  });
30820
30827
  }
30821
30828
  return issues;
@@ -31422,9 +31429,9 @@ var init_sql_string_concat2 = __esm({
31422
31429
  "src/rules/kotlin/sql-string-concat.ts"() {
31423
31430
  "use strict";
31424
31431
  init_rule();
31425
- SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31432
+ SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31426
31433
  UNSAFE_REGEX2 = /(?:\+|\$\{)/;
31427
- SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
31434
+ SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
31428
31435
  kotlinSqlStringConcatRule = createRule({
31429
31436
  id: "kotlin/sql-string-concat",
31430
31437
  category: "security",
@@ -31442,7 +31449,14 @@ var init_sql_string_concat2 = __esm({
31442
31449
  const lines = source.split("\n");
31443
31450
  for (let i = 0; i < lines.length; i++) {
31444
31451
  const line = lines[i];
31445
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
31452
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
31453
+ if (!keywordMatch) continue;
31454
+ const keywordIdx = keywordMatch.index;
31455
+ let j = keywordIdx - 1;
31456
+ while (j >= 0 && /\s/.test(line[j])) j--;
31457
+ if (j < 0) continue;
31458
+ const prevChar = line[j];
31459
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
31446
31460
  if (!UNSAFE_REGEX2.test(line)) continue;
31447
31461
  if (SAFE_REGEX2.test(line)) continue;
31448
31462
  issues.push({
@@ -31453,7 +31467,7 @@ var init_sql_string_concat2 = __esm({
31453
31467
  message: `SQL query built via string concat/template at line ${i + 1}`,
31454
31468
  line: i + 1,
31455
31469
  column: 1,
31456
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
31470
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
31457
31471
  });
31458
31472
  }
31459
31473
  return issues;
@@ -51605,7 +51619,7 @@ var init_signal_strength = __esm({
51605
51619
  precision: 0.0556,
51606
51620
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51607
51621
  verdict: "DORMANT",
51608
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
51622
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
51609
51623
  aiSpecific: false,
51610
51624
  _v9Verdict: "DORMANT",
51611
51625
  _v9Lift: 0.75,
@@ -51685,7 +51699,7 @@ var init_signal_strength = __esm({
51685
51699
  precision: 0.0691,
51686
51700
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51687
51701
  verdict: "DORMANT",
51688
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.59 \u2014 fires 1.7x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. Same direction as kotlin/sql-string-concat. 1664 total fires \u2014 Java uses ORMs (Hibernate) heavily so SQL concat is rare in both arms. defaultOff.",
51702
+ _calibrationNote: 'v0.34.9: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and tightened the SAFE_REGEX. Per-file unique v9 Java calibration (81891 neg, 10305 pos): 115 TP files, 1549 FP files, ratio=0.59 (DORMANT). 1664 total fires. The refinement is expected to push precision from 6.9% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `String msg = "Selected 1 row: " + count`). Also tightened SAFE_REGEX from `:??` to `:?` (the original was over-broad). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline (same pattern applied to kotlin/sql-string-concat and java/sql-string-concat). INSUFFICIENT_DATA: pos arm 10305 files, below the 10k floor. Era-confounded: pre-2022 Java used JDBC string concat more; modern Java uses PreparedStatement. defaultOff.',
51689
51703
  aiSpecific: false,
51690
51704
  _v9Verdict: "DORMANT",
51691
51705
  _v9Lift: 0.59,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopbrick",
3
- "version": "0.34.7",
3
+ "version": "0.34.9",
4
4
  "description": "Discovered, modeled, and governed repository structure. SlopBrick scans source code, classifies it against 95+ rules in 15 categories, computes 4 scores (aiSlopScore: lower=cleaner, engineeringHygiene, security, repositoryHealth composite), and persists the structure for AI agents and CI.",
5
5
  "type": "module",
6
6
  "bin": {