slopbrick 0.29.0 → 0.31.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/dist/engine/worker.cjs +307 -17
- package/dist/engine/worker.js +307 -17
- package/dist/index.cjs +362 -26
- package/dist/index.js +362 -26
- package/package.json +1 -1
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.
|
|
39
|
+
VERSION = "0.31.0";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -30662,6 +30662,246 @@ var init_struct_tag_inconsistency = __esm({
|
|
|
30662
30662
|
}
|
|
30663
30663
|
});
|
|
30664
30664
|
|
|
30665
|
+
// src/rules/java/command-injection.ts
|
|
30666
|
+
var COMMAND_INVOCATION_REGEX, STRING_CONCAT_REGEX, javaCommandInjectionRule;
|
|
30667
|
+
var init_command_injection = __esm({
|
|
30668
|
+
"src/rules/java/command-injection.ts"() {
|
|
30669
|
+
"use strict";
|
|
30670
|
+
init_rule();
|
|
30671
|
+
COMMAND_INVOCATION_REGEX = /\b(?:Runtime\.exec|Runtime\.getRuntime\(\)\.exec|ProcessBuilder)\s*\(/;
|
|
30672
|
+
STRING_CONCAT_REGEX = /["'][^"']*["']\s*\+/;
|
|
30673
|
+
javaCommandInjectionRule = createRule({
|
|
30674
|
+
id: "java/command-injection",
|
|
30675
|
+
category: "security",
|
|
30676
|
+
severity: "high",
|
|
30677
|
+
aiSpecific: false,
|
|
30678
|
+
description: "Runtime.exec() or ProcessBuilder with string concat \u2014 use List<String> args + validation",
|
|
30679
|
+
create(_context) {
|
|
30680
|
+
return {};
|
|
30681
|
+
},
|
|
30682
|
+
analyze(_context, facts) {
|
|
30683
|
+
const issues = [];
|
|
30684
|
+
const source = facts.v2?._source;
|
|
30685
|
+
if (!source) return issues;
|
|
30686
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30687
|
+
const lines = source.split("\n");
|
|
30688
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30689
|
+
const line = lines[i];
|
|
30690
|
+
if (!COMMAND_INVOCATION_REGEX.test(line)) continue;
|
|
30691
|
+
if (!STRING_CONCAT_REGEX.test(line)) continue;
|
|
30692
|
+
issues.push({
|
|
30693
|
+
ruleId: "java/command-injection",
|
|
30694
|
+
category: "security",
|
|
30695
|
+
severity: "high",
|
|
30696
|
+
aiSpecific: false,
|
|
30697
|
+
message: `Command invocation with string concat at line ${i + 1}`,
|
|
30698
|
+
line: i + 1,
|
|
30699
|
+
column: 1,
|
|
30700
|
+
advice: "Use ProcessBuilder with a List<String> of args (no shell parsing) and validate each arg against a whitelist. For shell commands, use bash -c only with a fixed string (no concatenation). String concat into Runtime.exec() is the canonical command-injection pattern \u2014 attackers can break out with `; rm -rf /` or `$(...)`. Reference: java/command-injection v0.30 (OWASP A03:2021)."
|
|
30701
|
+
});
|
|
30702
|
+
}
|
|
30703
|
+
return issues;
|
|
30704
|
+
}
|
|
30705
|
+
});
|
|
30706
|
+
}
|
|
30707
|
+
});
|
|
30708
|
+
|
|
30709
|
+
// src/rules/java/hardcoded-credential.ts
|
|
30710
|
+
var KEY_REGEX, VALUE_REGEX, FALSE_POSITIVES, javaHardcodedCredentialRule;
|
|
30711
|
+
var init_hardcoded_credential = __esm({
|
|
30712
|
+
"src/rules/java/hardcoded-credential.ts"() {
|
|
30713
|
+
"use strict";
|
|
30714
|
+
init_rule();
|
|
30715
|
+
KEY_REGEX = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
30716
|
+
VALUE_REGEX = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
30717
|
+
FALSE_POSITIVES = /* @__PURE__ */ new Set([
|
|
30718
|
+
"passwordless",
|
|
30719
|
+
"tokenize",
|
|
30720
|
+
"tokenizer",
|
|
30721
|
+
"authorizationrequired",
|
|
30722
|
+
"authenticated",
|
|
30723
|
+
"authenticatortoken",
|
|
30724
|
+
"authtoken",
|
|
30725
|
+
"placeholder",
|
|
30726
|
+
"changeme"
|
|
30727
|
+
]);
|
|
30728
|
+
javaHardcodedCredentialRule = createRule({
|
|
30729
|
+
id: "java/hardcoded-credential",
|
|
30730
|
+
category: "security",
|
|
30731
|
+
severity: "high",
|
|
30732
|
+
aiSpecific: false,
|
|
30733
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
30734
|
+
create(_context) {
|
|
30735
|
+
return {};
|
|
30736
|
+
},
|
|
30737
|
+
analyze(_context, facts) {
|
|
30738
|
+
const issues = [];
|
|
30739
|
+
const source = facts.v2?._source;
|
|
30740
|
+
if (!source) return issues;
|
|
30741
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30742
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30743
|
+
const lines = source.split("\n");
|
|
30744
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30745
|
+
const line = lines[i];
|
|
30746
|
+
if (!KEY_REGEX.test(line)) continue;
|
|
30747
|
+
const valueMatch = VALUE_REGEX.exec(line);
|
|
30748
|
+
if (!valueMatch) continue;
|
|
30749
|
+
const value = valueMatch[1];
|
|
30750
|
+
if (FALSE_POSITIVES.has(value.toLowerCase())) continue;
|
|
30751
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
30752
|
+
if (value.startsWith("$")) continue;
|
|
30753
|
+
issues.push({
|
|
30754
|
+
ruleId: "java/hardcoded-credential",
|
|
30755
|
+
category: "security",
|
|
30756
|
+
severity: "high",
|
|
30757
|
+
aiSpecific: false,
|
|
30758
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
30759
|
+
line: i + 1,
|
|
30760
|
+
column: 1,
|
|
30761
|
+
advice: "Move the credential to an environment variable, a .env file that is .gitignore'd, or a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager). Hardcoded credentials are the #1 source of secret leaks on GitHub. Reference: java/hardcoded-credential v0.30 (OWASP A07:2021 \u2014 Identification and Authentication Failures)."
|
|
30762
|
+
});
|
|
30763
|
+
}
|
|
30764
|
+
return issues;
|
|
30765
|
+
}
|
|
30766
|
+
});
|
|
30767
|
+
}
|
|
30768
|
+
});
|
|
30769
|
+
|
|
30770
|
+
// src/rules/java/sql-string-concat.ts
|
|
30771
|
+
var SQL_KEYWORD_REGEX, UNSAFE_REGEX, SAFE_REGEX, javaSqlStringConcatRule;
|
|
30772
|
+
var init_sql_string_concat = __esm({
|
|
30773
|
+
"src/rules/java/sql-string-concat.ts"() {
|
|
30774
|
+
"use strict";
|
|
30775
|
+
init_rule();
|
|
30776
|
+
SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
30777
|
+
UNSAFE_REGEX = /\+/;
|
|
30778
|
+
SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?\s*,)/;
|
|
30779
|
+
javaSqlStringConcatRule = createRule({
|
|
30780
|
+
id: "java/sql-string-concat",
|
|
30781
|
+
category: "security",
|
|
30782
|
+
severity: "high",
|
|
30783
|
+
aiSpecific: false,
|
|
30784
|
+
description: "SQL query built via string concat \u2014 use PreparedStatement or setParameter()",
|
|
30785
|
+
create(_context) {
|
|
30786
|
+
return {};
|
|
30787
|
+
},
|
|
30788
|
+
analyze(_context, facts) {
|
|
30789
|
+
const issues = [];
|
|
30790
|
+
const source = facts.v2?._source;
|
|
30791
|
+
if (!source) return issues;
|
|
30792
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30793
|
+
const lines = source.split("\n");
|
|
30794
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30795
|
+
const line = lines[i];
|
|
30796
|
+
if (!SQL_KEYWORD_REGEX.test(line)) continue;
|
|
30797
|
+
if (!UNSAFE_REGEX.test(line)) continue;
|
|
30798
|
+
if (SAFE_REGEX.test(line)) continue;
|
|
30799
|
+
issues.push({
|
|
30800
|
+
ruleId: "java/sql-string-concat",
|
|
30801
|
+
category: "security",
|
|
30802
|
+
severity: "high",
|
|
30803
|
+
aiSpecific: false,
|
|
30804
|
+
message: `SQL query built via string concat at line ${i + 1}`,
|
|
30805
|
+
line: i + 1,
|
|
30806
|
+
column: 1,
|
|
30807
|
+
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).'
|
|
30808
|
+
});
|
|
30809
|
+
}
|
|
30810
|
+
return issues;
|
|
30811
|
+
}
|
|
30812
|
+
});
|
|
30813
|
+
}
|
|
30814
|
+
});
|
|
30815
|
+
|
|
30816
|
+
// src/rules/java/system-out-println.ts
|
|
30817
|
+
var SYSTEM_OUT_REGEX, REAL_LOGGING_IMPORT_REGEX, javaSystemOutPrintlnRule;
|
|
30818
|
+
var init_system_out_println = __esm({
|
|
30819
|
+
"src/rules/java/system-out-println.ts"() {
|
|
30820
|
+
"use strict";
|
|
30821
|
+
init_rule();
|
|
30822
|
+
SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
|
|
30823
|
+
REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging)/;
|
|
30824
|
+
javaSystemOutPrintlnRule = createRule({
|
|
30825
|
+
id: "java/system-out-println",
|
|
30826
|
+
category: "logic",
|
|
30827
|
+
severity: "low",
|
|
30828
|
+
aiSpecific: false,
|
|
30829
|
+
description: "System.out.println() in a file that imports SLF4J/Log4j2 \u2014 use the declared logger instead",
|
|
30830
|
+
create(_context) {
|
|
30831
|
+
return {};
|
|
30832
|
+
},
|
|
30833
|
+
analyze(_context, facts) {
|
|
30834
|
+
const issues = [];
|
|
30835
|
+
const source = facts.v2?._source;
|
|
30836
|
+
if (!source) return issues;
|
|
30837
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30838
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30839
|
+
if (!REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
|
|
30840
|
+
let m;
|
|
30841
|
+
SYSTEM_OUT_REGEX.lastIndex = 0;
|
|
30842
|
+
while ((m = SYSTEM_OUT_REGEX.exec(source)) !== null) {
|
|
30843
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30844
|
+
issues.push({
|
|
30845
|
+
ruleId: "java/system-out-println",
|
|
30846
|
+
category: "logic",
|
|
30847
|
+
severity: "low",
|
|
30848
|
+
aiSpecific: false,
|
|
30849
|
+
message: `System.out.println() at line ${line} (file imports a real logger)`,
|
|
30850
|
+
line,
|
|
30851
|
+
column: 1,
|
|
30852
|
+
advice: "The file imports a real logging library (SLF4J, Log4j2, or java.util.logging) but uses System.out.println here. Replace `System.out.println(x)` with `log.info(x)` (or log.debug/warn/error as appropriate). The log object is already declared at the top of the file. Reference: java/system-out-println v0.31 (refined from v0.30)."
|
|
30853
|
+
});
|
|
30854
|
+
}
|
|
30855
|
+
return issues;
|
|
30856
|
+
}
|
|
30857
|
+
});
|
|
30858
|
+
}
|
|
30859
|
+
});
|
|
30860
|
+
|
|
30861
|
+
// src/rules/java/thread-sleep-in-loop.ts
|
|
30862
|
+
var THREAD_SLEEP_REGEX, javaThreadSleepInLoopRule;
|
|
30863
|
+
var init_thread_sleep_in_loop = __esm({
|
|
30864
|
+
"src/rules/java/thread-sleep-in-loop.ts"() {
|
|
30865
|
+
"use strict";
|
|
30866
|
+
init_rule();
|
|
30867
|
+
THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
|
|
30868
|
+
javaThreadSleepInLoopRule = createRule({
|
|
30869
|
+
id: "java/thread-sleep-in-loop",
|
|
30870
|
+
category: "perf",
|
|
30871
|
+
severity: "medium",
|
|
30872
|
+
aiSpecific: false,
|
|
30873
|
+
description: "Thread.sleep() in a loop \u2014 use ScheduledExecutorService or BlockingQueue",
|
|
30874
|
+
create(_context) {
|
|
30875
|
+
return {};
|
|
30876
|
+
},
|
|
30877
|
+
analyze(_context, facts) {
|
|
30878
|
+
const issues = [];
|
|
30879
|
+
const source = facts.v2?._source;
|
|
30880
|
+
if (!source) return issues;
|
|
30881
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30882
|
+
if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
|
|
30883
|
+
if (!/\b(?:for|while|do)\b/.test(source)) return issues;
|
|
30884
|
+
let m;
|
|
30885
|
+
THREAD_SLEEP_REGEX.lastIndex = 0;
|
|
30886
|
+
while ((m = THREAD_SLEEP_REGEX.exec(source)) !== null) {
|
|
30887
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30888
|
+
issues.push({
|
|
30889
|
+
ruleId: "java/thread-sleep-in-loop",
|
|
30890
|
+
category: "perf",
|
|
30891
|
+
severity: "medium",
|
|
30892
|
+
aiSpecific: false,
|
|
30893
|
+
message: `Thread.sleep() at line ${line}`,
|
|
30894
|
+
line,
|
|
30895
|
+
column: 1,
|
|
30896
|
+
advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.30.'
|
|
30897
|
+
});
|
|
30898
|
+
}
|
|
30899
|
+
return issues;
|
|
30900
|
+
}
|
|
30901
|
+
});
|
|
30902
|
+
}
|
|
30903
|
+
});
|
|
30904
|
+
|
|
30665
30905
|
// src/rules/kotlin/coroutine-global-scope.ts
|
|
30666
30906
|
var GLOBAL_SCOPE_REGEX, kotlinCoroutineGlobalScopeRule;
|
|
30667
30907
|
var init_coroutine_global_scope = __esm({
|
|
@@ -30815,14 +31055,14 @@ var init_force_unwrap = __esm({
|
|
|
30815
31055
|
});
|
|
30816
31056
|
|
|
30817
31057
|
// src/rules/kotlin/hardcoded-credential.ts
|
|
30818
|
-
var
|
|
30819
|
-
var
|
|
31058
|
+
var KEY_REGEX2, VALUE_REGEX2, FALSE_POSITIVES2, kotlinHardcodedCredentialRule;
|
|
31059
|
+
var init_hardcoded_credential2 = __esm({
|
|
30820
31060
|
"src/rules/kotlin/hardcoded-credential.ts"() {
|
|
30821
31061
|
"use strict";
|
|
30822
31062
|
init_rule();
|
|
30823
|
-
|
|
30824
|
-
|
|
30825
|
-
|
|
31063
|
+
KEY_REGEX2 = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
31064
|
+
VALUE_REGEX2 = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
31065
|
+
FALSE_POSITIVES2 = /* @__PURE__ */ new Set([
|
|
30826
31066
|
"passwordless",
|
|
30827
31067
|
"tokenize",
|
|
30828
31068
|
"tokenizer",
|
|
@@ -30852,11 +31092,11 @@ var init_hardcoded_credential = __esm({
|
|
|
30852
31092
|
const lines = source.split("\n");
|
|
30853
31093
|
for (let i = 0; i < lines.length; i++) {
|
|
30854
31094
|
const line = lines[i];
|
|
30855
|
-
if (!
|
|
30856
|
-
const valueMatch =
|
|
31095
|
+
if (!KEY_REGEX2.test(line)) continue;
|
|
31096
|
+
const valueMatch = VALUE_REGEX2.exec(line);
|
|
30857
31097
|
if (!valueMatch) continue;
|
|
30858
31098
|
const value = valueMatch[1];
|
|
30859
|
-
if (
|
|
31099
|
+
if (FALSE_POSITIVES2.has(value.toLowerCase())) continue;
|
|
30860
31100
|
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
30861
31101
|
if (value.startsWith("$")) continue;
|
|
30862
31102
|
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
@@ -30935,13 +31175,13 @@ var init_object_singleton_misuse = __esm({
|
|
|
30935
31175
|
});
|
|
30936
31176
|
|
|
30937
31177
|
// src/rules/kotlin/println-as-log.ts
|
|
30938
|
-
var PRINTLN_REGEX,
|
|
31178
|
+
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, kotlinPrintlnAsLogRule;
|
|
30939
31179
|
var init_println_as_log = __esm({
|
|
30940
31180
|
"src/rules/kotlin/println-as-log.ts"() {
|
|
30941
31181
|
"use strict";
|
|
30942
31182
|
init_rule();
|
|
30943
31183
|
PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
30944
|
-
|
|
31184
|
+
REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
|
|
30945
31185
|
kotlinPrintlnAsLogRule = createRule({
|
|
30946
31186
|
id: "kotlin/println-as-log",
|
|
30947
31187
|
category: "logic",
|
|
@@ -30957,7 +31197,7 @@ var init_println_as_log = __esm({
|
|
|
30957
31197
|
if (!source) return issues;
|
|
30958
31198
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30959
31199
|
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
30960
|
-
if (
|
|
31200
|
+
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
30961
31201
|
let m;
|
|
30962
31202
|
PRINTLN_REGEX.lastIndex = 0;
|
|
30963
31203
|
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
@@ -31073,14 +31313,14 @@ var init_runblocking_misuse = __esm({
|
|
|
31073
31313
|
});
|
|
31074
31314
|
|
|
31075
31315
|
// src/rules/kotlin/sql-string-concat.ts
|
|
31076
|
-
var
|
|
31077
|
-
var
|
|
31316
|
+
var SQL_KEYWORD_REGEX2, UNSAFE_REGEX2, SAFE_REGEX2, kotlinSqlStringConcatRule;
|
|
31317
|
+
var init_sql_string_concat2 = __esm({
|
|
31078
31318
|
"src/rules/kotlin/sql-string-concat.ts"() {
|
|
31079
31319
|
"use strict";
|
|
31080
31320
|
init_rule();
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
|
|
31321
|
+
SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
31322
|
+
UNSAFE_REGEX2 = /(?:\+|\$\{)/;
|
|
31323
|
+
SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
|
|
31084
31324
|
kotlinSqlStringConcatRule = createRule({
|
|
31085
31325
|
id: "kotlin/sql-string-concat",
|
|
31086
31326
|
category: "security",
|
|
@@ -31098,9 +31338,9 @@ var init_sql_string_concat = __esm({
|
|
|
31098
31338
|
const lines = source.split("\n");
|
|
31099
31339
|
for (let i = 0; i < lines.length; i++) {
|
|
31100
31340
|
const line = lines[i];
|
|
31101
|
-
if (!
|
|
31102
|
-
if (!
|
|
31103
|
-
if (
|
|
31341
|
+
if (!SQL_KEYWORD_REGEX2.test(line)) continue;
|
|
31342
|
+
if (!UNSAFE_REGEX2.test(line)) continue;
|
|
31343
|
+
if (SAFE_REGEX2.test(line)) continue;
|
|
31104
31344
|
issues.push({
|
|
31105
31345
|
ruleId: "kotlin/sql-string-concat",
|
|
31106
31346
|
category: "security",
|
|
@@ -31119,12 +31359,12 @@ var init_sql_string_concat = __esm({
|
|
|
31119
31359
|
});
|
|
31120
31360
|
|
|
31121
31361
|
// src/rules/kotlin/string-concat-loop.ts
|
|
31122
|
-
var
|
|
31362
|
+
var STRING_CONCAT_REGEX2, kotlinStringConcatLoopRule;
|
|
31123
31363
|
var init_string_concat_loop = __esm({
|
|
31124
31364
|
"src/rules/kotlin/string-concat-loop.ts"() {
|
|
31125
31365
|
"use strict";
|
|
31126
31366
|
init_rule();
|
|
31127
|
-
|
|
31367
|
+
STRING_CONCAT_REGEX2 = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
|
|
31128
31368
|
kotlinStringConcatLoopRule = createRule({
|
|
31129
31369
|
id: "kotlin/string-concat-loop",
|
|
31130
31370
|
category: "perf",
|
|
@@ -31141,8 +31381,8 @@ var init_string_concat_loop = __esm({
|
|
|
31141
31381
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31142
31382
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
31143
31383
|
let m;
|
|
31144
|
-
|
|
31145
|
-
while ((m =
|
|
31384
|
+
STRING_CONCAT_REGEX2.lastIndex = 0;
|
|
31385
|
+
while ((m = STRING_CONCAT_REGEX2.exec(source)) !== null) {
|
|
31146
31386
|
const line = source.slice(0, m.index).split("\n").length;
|
|
31147
31387
|
const lineText = source.slice(0, m.index).split("\n").pop() ?? "";
|
|
31148
31388
|
if (/\.append\s*\(/.test(lineText)) continue;
|
|
@@ -46561,15 +46801,20 @@ var init_builtins = __esm({
|
|
|
46561
46801
|
init_error_wrap_without_context();
|
|
46562
46802
|
init_nil_slice_vs_empty();
|
|
46563
46803
|
init_struct_tag_inconsistency();
|
|
46804
|
+
init_command_injection();
|
|
46805
|
+
init_hardcoded_credential();
|
|
46806
|
+
init_sql_string_concat();
|
|
46807
|
+
init_system_out_println();
|
|
46808
|
+
init_thread_sleep_in_loop();
|
|
46564
46809
|
init_coroutine_global_scope();
|
|
46565
46810
|
init_data_class_defaults_overuse();
|
|
46566
46811
|
init_force_unwrap();
|
|
46567
|
-
|
|
46812
|
+
init_hardcoded_credential2();
|
|
46568
46813
|
init_object_singleton_misuse();
|
|
46569
46814
|
init_println_as_log();
|
|
46570
46815
|
init_println_debug();
|
|
46571
46816
|
init_runblocking_misuse();
|
|
46572
|
-
|
|
46817
|
+
init_sql_string_concat2();
|
|
46573
46818
|
init_string_concat_loop();
|
|
46574
46819
|
init_gap_monopoly();
|
|
46575
46820
|
init_math_element_uniformity();
|
|
@@ -46695,6 +46940,11 @@ var init_builtins = __esm({
|
|
|
46695
46940
|
goErrorWrapWithoutContextRule,
|
|
46696
46941
|
goNilSliceVsEmptyRule,
|
|
46697
46942
|
goStructTagInconsistencyRule,
|
|
46943
|
+
javaCommandInjectionRule,
|
|
46944
|
+
javaHardcodedCredentialRule,
|
|
46945
|
+
javaSqlStringConcatRule,
|
|
46946
|
+
javaSystemOutPrintlnRule,
|
|
46947
|
+
javaThreadSleepInLoopRule,
|
|
46698
46948
|
kotlinCoroutineGlobalScopeRule,
|
|
46699
46949
|
kotlinDataClassDefaultsOveruseRule,
|
|
46700
46950
|
kotlinForceUnwrapRule,
|
|
@@ -51307,6 +51557,86 @@ var init_signal_strength = __esm({
|
|
|
51307
51557
|
_v9Precision: 0.0271,
|
|
51308
51558
|
defaultOff: true
|
|
51309
51559
|
},
|
|
51560
|
+
"java/sql-string-concat": {
|
|
51561
|
+
recall: 0.01116,
|
|
51562
|
+
fpRate: 0.01892,
|
|
51563
|
+
ratio: 0.59,
|
|
51564
|
+
precision: 0.0691,
|
|
51565
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51566
|
+
verdict: "DORMANT",
|
|
51567
|
+
_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.",
|
|
51568
|
+
aiSpecific: false,
|
|
51569
|
+
_v9Verdict: "DORMANT",
|
|
51570
|
+
_v9Lift: 0.59,
|
|
51571
|
+
_v9Recall: 0.01116,
|
|
51572
|
+
_v9FpRate: 0.01892,
|
|
51573
|
+
_v9Precision: 0.0691,
|
|
51574
|
+
defaultOff: true
|
|
51575
|
+
},
|
|
51576
|
+
"java/hardcoded-credential": {
|
|
51577
|
+
recall: 0,
|
|
51578
|
+
fpRate: 4e-5,
|
|
51579
|
+
ratio: 0,
|
|
51580
|
+
precision: 0,
|
|
51581
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51582
|
+
verdict: "DORMANT",
|
|
51583
|
+
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). 3 fires total \u2014 only in pre-2022 (neg). Real secrets are in env vars / config files, not source. INSUFFICIENT_DATA: needs different corpus (CI configs, .env samples, leaked-secret datasets). defaultOff.",
|
|
51584
|
+
aiSpecific: false,
|
|
51585
|
+
_v9Verdict: "DORMANT",
|
|
51586
|
+
_v9Lift: 0,
|
|
51587
|
+
_v9Recall: 0,
|
|
51588
|
+
_v9FpRate: 4e-5,
|
|
51589
|
+
_v9Precision: 0,
|
|
51590
|
+
defaultOff: true
|
|
51591
|
+
},
|
|
51592
|
+
"java/thread-sleep-in-loop": {
|
|
51593
|
+
recall: 0.0228,
|
|
51594
|
+
fpRate: 0.02343,
|
|
51595
|
+
ratio: 0.97,
|
|
51596
|
+
precision: 0.1091,
|
|
51597
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51598
|
+
verdict: "DORMANT",
|
|
51599
|
+
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
|
|
51600
|
+
aiSpecific: false,
|
|
51601
|
+
_v9Verdict: "DORMANT",
|
|
51602
|
+
_v9Lift: 0.97,
|
|
51603
|
+
_v9Recall: 0.0228,
|
|
51604
|
+
_v9FpRate: 0.02343,
|
|
51605
|
+
_v9Precision: 0.1091,
|
|
51606
|
+
defaultOff: true
|
|
51607
|
+
},
|
|
51608
|
+
"java/system-out-println": {
|
|
51609
|
+
recall: 116e-5,
|
|
51610
|
+
fpRate: 67e-5,
|
|
51611
|
+
ratio: 1.73,
|
|
51612
|
+
precision: 0.1791,
|
|
51613
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51614
|
+
verdict: "OK",
|
|
51615
|
+
_calibrationNote: "v0.31: v9 Java calibration REFINED \u2014 rule now requires the file to import a real logger (SLF4J, Log4j2, java.util.logging) AND have System.out.println calls. This is the 'set up but didn't use' anti-pattern. ratio=1.73 (\u22651.5) \u2014 second positive-signal rule in v9 history. v0.30.0 unrefined version had ratio=3.29 but precision=29.3% (over 5800 fires diluted by legitimate System.out in non-slf4j files). v0.31.0 refined version: 12 TP, 55 FP \u2014 much more selective, but absolute count is too small for production-grade measurement. precision=17.91% (still below 50% USEFUL threshold). The refinement succeeded: the fires dropped 99% (5866\u219267) and precision went 29.3%\u219217.9% (a tradeoff \u2014 fewer fires but more targeted). The direction is preserved: post-2024 Java has slightly more 'slf4j import + System.out call' than pre-2022. defaultOff: still set true (precision below 50%).",
|
|
51616
|
+
aiSpecific: false,
|
|
51617
|
+
_v9Verdict: "OK",
|
|
51618
|
+
_v9Lift: 1.73,
|
|
51619
|
+
_v9Recall: 116e-5,
|
|
51620
|
+
_v9FpRate: 67e-5,
|
|
51621
|
+
_v9Precision: 0.1791,
|
|
51622
|
+
defaultOff: true
|
|
51623
|
+
},
|
|
51624
|
+
"java/command-injection": {
|
|
51625
|
+
recall: 0,
|
|
51626
|
+
fpRate: 1e-4,
|
|
51627
|
+
ratio: 0,
|
|
51628
|
+
precision: 0,
|
|
51629
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51630
|
+
verdict: "DORMANT",
|
|
51631
|
+
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). 8 fires total, all in neg. Command injection is rare in modern Java \u2014 most apps use ProcessBuilder with List<String> args, not Runtime.exec with concat. INSUFFICIENT_DATA: needs different corpus (security benchmarks, CVE samples). defaultOff.",
|
|
51632
|
+
aiSpecific: false,
|
|
51633
|
+
_v9Verdict: "DORMANT",
|
|
51634
|
+
_v9Lift: 0,
|
|
51635
|
+
_v9Recall: 0,
|
|
51636
|
+
_v9FpRate: 1e-4,
|
|
51637
|
+
_v9Precision: 0,
|
|
51638
|
+
defaultOff: true
|
|
51639
|
+
},
|
|
51310
51640
|
"swift/force-unwrap": {
|
|
51311
51641
|
recall: 0,
|
|
51312
51642
|
fpRate: 0,
|
|
@@ -59525,6 +59855,12 @@ var RULE_HINTS = {
|
|
|
59525
59855
|
"kotlin/runblocking-misuse": "Use coroutineScope { } for structured concurrency, or call the suspend function directly. runBlocking blocks the calling thread and is rarely correct outside main(). (v0.29.0 \u2014 DORMANT.)",
|
|
59526
59856
|
"kotlin/println-as-log": "Use slf4j (JVM), android.util.Log (Android), Timber, kermit, or kotlin-logging. println has no log level, no timestamp, no correlation ID. (v0.29.0 \u2014 OK; first positive-signal rule in v9 history, ratio 1.84.)",
|
|
59527
59857
|
"kotlin/force-unwrap": "Use ?. (safe call) with ?: (Elvis) for a default, or a proper when/if check. !! throws NullPointerException at runtime and bypasses Kotlin's type system. (v0.29.0 \u2014 DORMANT, era-confounded.)",
|
|
59858
|
+
// v0.30.0 — non-AI Java rules (Option C applied to v9 Java corpus, 92k files)
|
|
59859
|
+
"java/sql-string-concat": "Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{}). String concat into SQL is the canonical SQL-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT, ratio 0.59.)",
|
|
59860
|
+
"java/hardcoded-credential": "Move the credential to an env var, a .env file (gitignored), or a secrets manager (Vault, AWS Secrets Manager). Hardcoded creds are the #1 secret-leak vector. OWASP A07:2021. (v0.30.0 \u2014 DORMANT, 0 fires on v9 Java corpus.)",
|
|
59861
|
+
"java/thread-sleep-in-loop": "Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the polling anti-pattern \u2014 ties up Tomcat/Jetty/Netty threads. (v0.30.0 \u2014 DORMANT, era-confounded ratio 0.97.)",
|
|
59862
|
+
"java/system-out-println": "The file imports a real logger (SLF4J/Log4j2/java.util.logging) but uses System.out.println. Use the declared log object instead. (v0.31.0 \u2014 OK; ratio 1.73, refined from v0.30.)",
|
|
59863
|
+
"java/command-injection": "Use ProcessBuilder with a List<String> of args (no shell parsing) and validate each arg. Runtime.exec() with concat is the canonical command-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT.)",
|
|
59528
59864
|
// v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
|
|
59529
59865
|
"swift/force-unwrap": "Replace with the safe form: `as?` + guard/if let, `try?`, or `guard let x = optional else { return }`. `!` crashes unconditionally in release. (v0.24.0 \u2014 DORMANT.)",
|
|
59530
59866
|
"swift/print-debug": "Replace with `Logger(subsystem:..., category:...).info(...)` (os.log). `print` writes to stdout with no level and no redaction. (v0.24.0 \u2014 DORMANT.)",
|