slopbrick 0.28.0 → 0.30.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 +589 -6
- package/dist/engine/worker.js +589 -6
- package/dist/index.cjs +686 -11
- package/dist/index.js +686 -11
- package/package.json +1 -1
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.
|
|
22
|
+
VERSION = "0.30.0";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -30643,6 +30643,246 @@ var init_struct_tag_inconsistency = __esm({
|
|
|
30643
30643
|
}
|
|
30644
30644
|
});
|
|
30645
30645
|
|
|
30646
|
+
// src/rules/java/command-injection.ts
|
|
30647
|
+
var COMMAND_INVOCATION_REGEX, STRING_CONCAT_REGEX, javaCommandInjectionRule;
|
|
30648
|
+
var init_command_injection = __esm({
|
|
30649
|
+
"src/rules/java/command-injection.ts"() {
|
|
30650
|
+
"use strict";
|
|
30651
|
+
init_rule();
|
|
30652
|
+
COMMAND_INVOCATION_REGEX = /\b(?:Runtime\.exec|Runtime\.getRuntime\(\)\.exec|ProcessBuilder)\s*\(/;
|
|
30653
|
+
STRING_CONCAT_REGEX = /["'][^"']*["']\s*\+/;
|
|
30654
|
+
javaCommandInjectionRule = createRule({
|
|
30655
|
+
id: "java/command-injection",
|
|
30656
|
+
category: "security",
|
|
30657
|
+
severity: "high",
|
|
30658
|
+
aiSpecific: false,
|
|
30659
|
+
description: "Runtime.exec() or ProcessBuilder with string concat \u2014 use List<String> args + validation",
|
|
30660
|
+
create(_context) {
|
|
30661
|
+
return {};
|
|
30662
|
+
},
|
|
30663
|
+
analyze(_context, facts) {
|
|
30664
|
+
const issues = [];
|
|
30665
|
+
const source = facts.v2?._source;
|
|
30666
|
+
if (!source) return issues;
|
|
30667
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30668
|
+
const lines = source.split("\n");
|
|
30669
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30670
|
+
const line = lines[i];
|
|
30671
|
+
if (!COMMAND_INVOCATION_REGEX.test(line)) continue;
|
|
30672
|
+
if (!STRING_CONCAT_REGEX.test(line)) continue;
|
|
30673
|
+
issues.push({
|
|
30674
|
+
ruleId: "java/command-injection",
|
|
30675
|
+
category: "security",
|
|
30676
|
+
severity: "high",
|
|
30677
|
+
aiSpecific: false,
|
|
30678
|
+
message: `Command invocation with string concat at line ${i + 1}`,
|
|
30679
|
+
line: i + 1,
|
|
30680
|
+
column: 1,
|
|
30681
|
+
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)."
|
|
30682
|
+
});
|
|
30683
|
+
}
|
|
30684
|
+
return issues;
|
|
30685
|
+
}
|
|
30686
|
+
});
|
|
30687
|
+
}
|
|
30688
|
+
});
|
|
30689
|
+
|
|
30690
|
+
// src/rules/java/hardcoded-credential.ts
|
|
30691
|
+
var KEY_REGEX, VALUE_REGEX, FALSE_POSITIVES, javaHardcodedCredentialRule;
|
|
30692
|
+
var init_hardcoded_credential = __esm({
|
|
30693
|
+
"src/rules/java/hardcoded-credential.ts"() {
|
|
30694
|
+
"use strict";
|
|
30695
|
+
init_rule();
|
|
30696
|
+
KEY_REGEX = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
30697
|
+
VALUE_REGEX = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
30698
|
+
FALSE_POSITIVES = /* @__PURE__ */ new Set([
|
|
30699
|
+
"passwordless",
|
|
30700
|
+
"tokenize",
|
|
30701
|
+
"tokenizer",
|
|
30702
|
+
"authorizationrequired",
|
|
30703
|
+
"authenticated",
|
|
30704
|
+
"authenticatortoken",
|
|
30705
|
+
"authtoken",
|
|
30706
|
+
"placeholder",
|
|
30707
|
+
"changeme"
|
|
30708
|
+
]);
|
|
30709
|
+
javaHardcodedCredentialRule = createRule({
|
|
30710
|
+
id: "java/hardcoded-credential",
|
|
30711
|
+
category: "security",
|
|
30712
|
+
severity: "high",
|
|
30713
|
+
aiSpecific: false,
|
|
30714
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
30715
|
+
create(_context) {
|
|
30716
|
+
return {};
|
|
30717
|
+
},
|
|
30718
|
+
analyze(_context, facts) {
|
|
30719
|
+
const issues = [];
|
|
30720
|
+
const source = facts.v2?._source;
|
|
30721
|
+
if (!source) return issues;
|
|
30722
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30723
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30724
|
+
const lines = source.split("\n");
|
|
30725
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30726
|
+
const line = lines[i];
|
|
30727
|
+
if (!KEY_REGEX.test(line)) continue;
|
|
30728
|
+
const valueMatch = VALUE_REGEX.exec(line);
|
|
30729
|
+
if (!valueMatch) continue;
|
|
30730
|
+
const value = valueMatch[1];
|
|
30731
|
+
if (FALSE_POSITIVES.has(value.toLowerCase())) continue;
|
|
30732
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
30733
|
+
if (value.startsWith("$")) continue;
|
|
30734
|
+
issues.push({
|
|
30735
|
+
ruleId: "java/hardcoded-credential",
|
|
30736
|
+
category: "security",
|
|
30737
|
+
severity: "high",
|
|
30738
|
+
aiSpecific: false,
|
|
30739
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
30740
|
+
line: i + 1,
|
|
30741
|
+
column: 1,
|
|
30742
|
+
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)."
|
|
30743
|
+
});
|
|
30744
|
+
}
|
|
30745
|
+
return issues;
|
|
30746
|
+
}
|
|
30747
|
+
});
|
|
30748
|
+
}
|
|
30749
|
+
});
|
|
30750
|
+
|
|
30751
|
+
// src/rules/java/sql-string-concat.ts
|
|
30752
|
+
var SQL_KEYWORD_REGEX, UNSAFE_REGEX, SAFE_REGEX, javaSqlStringConcatRule;
|
|
30753
|
+
var init_sql_string_concat = __esm({
|
|
30754
|
+
"src/rules/java/sql-string-concat.ts"() {
|
|
30755
|
+
"use strict";
|
|
30756
|
+
init_rule();
|
|
30757
|
+
SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
30758
|
+
UNSAFE_REGEX = /\+/;
|
|
30759
|
+
SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?\s*,)/;
|
|
30760
|
+
javaSqlStringConcatRule = createRule({
|
|
30761
|
+
id: "java/sql-string-concat",
|
|
30762
|
+
category: "security",
|
|
30763
|
+
severity: "high",
|
|
30764
|
+
aiSpecific: false,
|
|
30765
|
+
description: "SQL query built via string concat \u2014 use PreparedStatement or setParameter()",
|
|
30766
|
+
create(_context) {
|
|
30767
|
+
return {};
|
|
30768
|
+
},
|
|
30769
|
+
analyze(_context, facts) {
|
|
30770
|
+
const issues = [];
|
|
30771
|
+
const source = facts.v2?._source;
|
|
30772
|
+
if (!source) return issues;
|
|
30773
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30774
|
+
const lines = source.split("\n");
|
|
30775
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30776
|
+
const line = lines[i];
|
|
30777
|
+
if (!SQL_KEYWORD_REGEX.test(line)) continue;
|
|
30778
|
+
if (!UNSAFE_REGEX.test(line)) continue;
|
|
30779
|
+
if (SAFE_REGEX.test(line)) continue;
|
|
30780
|
+
issues.push({
|
|
30781
|
+
ruleId: "java/sql-string-concat",
|
|
30782
|
+
category: "security",
|
|
30783
|
+
severity: "high",
|
|
30784
|
+
aiSpecific: false,
|
|
30785
|
+
message: `SQL query built via string concat at line ${i + 1}`,
|
|
30786
|
+
line: i + 1,
|
|
30787
|
+
column: 1,
|
|
30788
|
+
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).'
|
|
30789
|
+
});
|
|
30790
|
+
}
|
|
30791
|
+
return issues;
|
|
30792
|
+
}
|
|
30793
|
+
});
|
|
30794
|
+
}
|
|
30795
|
+
});
|
|
30796
|
+
|
|
30797
|
+
// src/rules/java/system-out-println.ts
|
|
30798
|
+
var SYSTEM_OUT_REGEX, REAL_LOGGING_IMPORT_REGEX, javaSystemOutPrintlnRule;
|
|
30799
|
+
var init_system_out_println = __esm({
|
|
30800
|
+
"src/rules/java/system-out-println.ts"() {
|
|
30801
|
+
"use strict";
|
|
30802
|
+
init_rule();
|
|
30803
|
+
SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
|
|
30804
|
+
REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging|io\.github\.oshai\.kotlinlogging)/;
|
|
30805
|
+
javaSystemOutPrintlnRule = createRule({
|
|
30806
|
+
id: "java/system-out-println",
|
|
30807
|
+
category: "logic",
|
|
30808
|
+
severity: "low",
|
|
30809
|
+
aiSpecific: false,
|
|
30810
|
+
description: "System.out.println() used for logging \u2014 use SLF4J, Log4j2, or java.util.logging",
|
|
30811
|
+
create(_context) {
|
|
30812
|
+
return {};
|
|
30813
|
+
},
|
|
30814
|
+
analyze(_context, facts) {
|
|
30815
|
+
const issues = [];
|
|
30816
|
+
const source = facts.v2?._source;
|
|
30817
|
+
if (!source) return issues;
|
|
30818
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30819
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30820
|
+
if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
|
|
30821
|
+
let m;
|
|
30822
|
+
SYSTEM_OUT_REGEX.lastIndex = 0;
|
|
30823
|
+
while ((m = SYSTEM_OUT_REGEX.exec(source)) !== null) {
|
|
30824
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30825
|
+
issues.push({
|
|
30826
|
+
ruleId: "java/system-out-println",
|
|
30827
|
+
category: "logic",
|
|
30828
|
+
severity: "low",
|
|
30829
|
+
aiSpecific: false,
|
|
30830
|
+
message: `System.out.println() at line ${line}`,
|
|
30831
|
+
line,
|
|
30832
|
+
column: 1,
|
|
30833
|
+
advice: "Use a real logging library: SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID, and cannot be filtered. Migration: add `private static final Logger log = LoggerFactory.getLogger(MyClass.class);` and replace `System.out.println(x)` with `log.info(x)`. Reference: java/system-out-println v0.30."
|
|
30834
|
+
});
|
|
30835
|
+
}
|
|
30836
|
+
return issues;
|
|
30837
|
+
}
|
|
30838
|
+
});
|
|
30839
|
+
}
|
|
30840
|
+
});
|
|
30841
|
+
|
|
30842
|
+
// src/rules/java/thread-sleep-in-loop.ts
|
|
30843
|
+
var THREAD_SLEEP_REGEX, javaThreadSleepInLoopRule;
|
|
30844
|
+
var init_thread_sleep_in_loop = __esm({
|
|
30845
|
+
"src/rules/java/thread-sleep-in-loop.ts"() {
|
|
30846
|
+
"use strict";
|
|
30847
|
+
init_rule();
|
|
30848
|
+
THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
|
|
30849
|
+
javaThreadSleepInLoopRule = createRule({
|
|
30850
|
+
id: "java/thread-sleep-in-loop",
|
|
30851
|
+
category: "perf",
|
|
30852
|
+
severity: "medium",
|
|
30853
|
+
aiSpecific: false,
|
|
30854
|
+
description: "Thread.sleep() in a loop \u2014 use ScheduledExecutorService or BlockingQueue",
|
|
30855
|
+
create(_context) {
|
|
30856
|
+
return {};
|
|
30857
|
+
},
|
|
30858
|
+
analyze(_context, facts) {
|
|
30859
|
+
const issues = [];
|
|
30860
|
+
const source = facts.v2?._source;
|
|
30861
|
+
if (!source) return issues;
|
|
30862
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30863
|
+
if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
|
|
30864
|
+
if (!/\b(?:for|while|do)\b/.test(source)) return issues;
|
|
30865
|
+
let m;
|
|
30866
|
+
THREAD_SLEEP_REGEX.lastIndex = 0;
|
|
30867
|
+
while ((m = THREAD_SLEEP_REGEX.exec(source)) !== null) {
|
|
30868
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30869
|
+
issues.push({
|
|
30870
|
+
ruleId: "java/thread-sleep-in-loop",
|
|
30871
|
+
category: "perf",
|
|
30872
|
+
severity: "medium",
|
|
30873
|
+
aiSpecific: false,
|
|
30874
|
+
message: `Thread.sleep() at line ${line}`,
|
|
30875
|
+
line,
|
|
30876
|
+
column: 1,
|
|
30877
|
+
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.'
|
|
30878
|
+
});
|
|
30879
|
+
}
|
|
30880
|
+
return issues;
|
|
30881
|
+
}
|
|
30882
|
+
});
|
|
30883
|
+
}
|
|
30884
|
+
});
|
|
30885
|
+
|
|
30646
30886
|
// src/rules/kotlin/coroutine-global-scope.ts
|
|
30647
30887
|
var GLOBAL_SCOPE_REGEX, kotlinCoroutineGlobalScopeRule;
|
|
30648
30888
|
var init_coroutine_global_scope = __esm({
|
|
@@ -30749,6 +30989,115 @@ var init_data_class_defaults_overuse = __esm({
|
|
|
30749
30989
|
}
|
|
30750
30990
|
});
|
|
30751
30991
|
|
|
30992
|
+
// src/rules/kotlin/force-unwrap.ts
|
|
30993
|
+
var FORCE_UNWRAP_REGEX, kotlinForceUnwrapRule;
|
|
30994
|
+
var init_force_unwrap = __esm({
|
|
30995
|
+
"src/rules/kotlin/force-unwrap.ts"() {
|
|
30996
|
+
"use strict";
|
|
30997
|
+
init_rule();
|
|
30998
|
+
FORCE_UNWRAP_REGEX = /!!(?=\s*[.\)}\};,\n\[])/g;
|
|
30999
|
+
kotlinForceUnwrapRule = createRule({
|
|
31000
|
+
id: "kotlin/force-unwrap",
|
|
31001
|
+
category: "logic",
|
|
31002
|
+
severity: "medium",
|
|
31003
|
+
aiSpecific: false,
|
|
31004
|
+
description: "!! force-unwrap \u2014 use ?. (safe call) or a proper null check",
|
|
31005
|
+
create(_context) {
|
|
31006
|
+
return {};
|
|
31007
|
+
},
|
|
31008
|
+
analyze(_context, facts) {
|
|
31009
|
+
const issues = [];
|
|
31010
|
+
const source = facts.v2?._source;
|
|
31011
|
+
if (!source) return issues;
|
|
31012
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31013
|
+
let m;
|
|
31014
|
+
FORCE_UNWRAP_REGEX.lastIndex = 0;
|
|
31015
|
+
while ((m = FORCE_UNWRAP_REGEX.exec(source)) !== null) {
|
|
31016
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
31017
|
+
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
|
31018
|
+
const lineText = source.slice(lineStart, m.index);
|
|
31019
|
+
const quoteCount = (lineText.match(/"/g) || []).length;
|
|
31020
|
+
if (quoteCount % 2 === 1) continue;
|
|
31021
|
+
issues.push({
|
|
31022
|
+
ruleId: "kotlin/force-unwrap",
|
|
31023
|
+
category: "logic",
|
|
31024
|
+
severity: "medium",
|
|
31025
|
+
aiSpecific: false,
|
|
31026
|
+
message: `!! force-unwrap at line ${line}`,
|
|
31027
|
+
line,
|
|
31028
|
+
column: m.index - lineStart + 1,
|
|
31029
|
+
advice: "Use ?. (safe call) with ?: (Elvis) for a default, or a proper when/if check. !! throws NullPointerException at runtime \u2014 it bypasses Kotlin's type system. Reference: kotlin/force-unwrap v0.29."
|
|
31030
|
+
});
|
|
31031
|
+
}
|
|
31032
|
+
return issues;
|
|
31033
|
+
}
|
|
31034
|
+
});
|
|
31035
|
+
}
|
|
31036
|
+
});
|
|
31037
|
+
|
|
31038
|
+
// src/rules/kotlin/hardcoded-credential.ts
|
|
31039
|
+
var KEY_REGEX2, VALUE_REGEX2, FALSE_POSITIVES2, kotlinHardcodedCredentialRule;
|
|
31040
|
+
var init_hardcoded_credential2 = __esm({
|
|
31041
|
+
"src/rules/kotlin/hardcoded-credential.ts"() {
|
|
31042
|
+
"use strict";
|
|
31043
|
+
init_rule();
|
|
31044
|
+
KEY_REGEX2 = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
31045
|
+
VALUE_REGEX2 = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
31046
|
+
FALSE_POSITIVES2 = /* @__PURE__ */ new Set([
|
|
31047
|
+
"passwordless",
|
|
31048
|
+
"tokenize",
|
|
31049
|
+
"tokenizer",
|
|
31050
|
+
"authorizationrequired",
|
|
31051
|
+
"authenticated",
|
|
31052
|
+
"authenticatortoken",
|
|
31053
|
+
"authtoken",
|
|
31054
|
+
"placeholder",
|
|
31055
|
+
// common in test fixtures
|
|
31056
|
+
"changeme"
|
|
31057
|
+
// placeholder, but still suspicious — kept for review
|
|
31058
|
+
]);
|
|
31059
|
+
kotlinHardcodedCredentialRule = createRule({
|
|
31060
|
+
id: "kotlin/hardcoded-credential",
|
|
31061
|
+
category: "security",
|
|
31062
|
+
severity: "high",
|
|
31063
|
+
aiSpecific: false,
|
|
31064
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
31065
|
+
create(_context) {
|
|
31066
|
+
return {};
|
|
31067
|
+
},
|
|
31068
|
+
analyze(_context, facts) {
|
|
31069
|
+
const issues = [];
|
|
31070
|
+
const source = facts.v2?._source;
|
|
31071
|
+
if (!source) return issues;
|
|
31072
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31073
|
+
const lines = source.split("\n");
|
|
31074
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31075
|
+
const line = lines[i];
|
|
31076
|
+
if (!KEY_REGEX2.test(line)) continue;
|
|
31077
|
+
const valueMatch = VALUE_REGEX2.exec(line);
|
|
31078
|
+
if (!valueMatch) continue;
|
|
31079
|
+
const value = valueMatch[1];
|
|
31080
|
+
if (FALSE_POSITIVES2.has(value.toLowerCase())) continue;
|
|
31081
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
31082
|
+
if (value.startsWith("$")) continue;
|
|
31083
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
31084
|
+
issues.push({
|
|
31085
|
+
ruleId: "kotlin/hardcoded-credential",
|
|
31086
|
+
category: "security",
|
|
31087
|
+
severity: "high",
|
|
31088
|
+
aiSpecific: false,
|
|
31089
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
31090
|
+
line: i + 1,
|
|
31091
|
+
column: 1,
|
|
31092
|
+
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: kotlin/hardcoded-credential v0.29 (OWASP A07:2021 \u2014 Identification and Authentication Failures)."
|
|
31093
|
+
});
|
|
31094
|
+
}
|
|
31095
|
+
return issues;
|
|
31096
|
+
}
|
|
31097
|
+
});
|
|
31098
|
+
}
|
|
31099
|
+
});
|
|
31100
|
+
|
|
30752
31101
|
// src/rules/kotlin/object-singleton-misuse.ts
|
|
30753
31102
|
var OBJECT_DECL_REGEX, kotlinObjectSingletonMisuseRule;
|
|
30754
31103
|
var init_object_singleton_misuse = __esm({
|
|
@@ -30806,13 +31155,58 @@ var init_object_singleton_misuse = __esm({
|
|
|
30806
31155
|
}
|
|
30807
31156
|
});
|
|
30808
31157
|
|
|
31158
|
+
// src/rules/kotlin/println-as-log.ts
|
|
31159
|
+
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, kotlinPrintlnAsLogRule;
|
|
31160
|
+
var init_println_as_log = __esm({
|
|
31161
|
+
"src/rules/kotlin/println-as-log.ts"() {
|
|
31162
|
+
"use strict";
|
|
31163
|
+
init_rule();
|
|
31164
|
+
PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
31165
|
+
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)/;
|
|
31166
|
+
kotlinPrintlnAsLogRule = createRule({
|
|
31167
|
+
id: "kotlin/println-as-log",
|
|
31168
|
+
category: "logic",
|
|
31169
|
+
severity: "low",
|
|
31170
|
+
aiSpecific: false,
|
|
31171
|
+
description: "println() used for logging \u2014 use slf4j, kermit, or android.util.Log",
|
|
31172
|
+
create(_context) {
|
|
31173
|
+
return {};
|
|
31174
|
+
},
|
|
31175
|
+
analyze(_context, facts) {
|
|
31176
|
+
const issues = [];
|
|
31177
|
+
const source = facts.v2?._source;
|
|
31178
|
+
if (!source) return issues;
|
|
31179
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31180
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
31181
|
+
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
31182
|
+
let m;
|
|
31183
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
31184
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
31185
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
31186
|
+
issues.push({
|
|
31187
|
+
ruleId: "kotlin/println-as-log",
|
|
31188
|
+
category: "logic",
|
|
31189
|
+
severity: "low",
|
|
31190
|
+
aiSpecific: false,
|
|
31191
|
+
message: `println() as logger at line ${line}`,
|
|
31192
|
+
line,
|
|
31193
|
+
column: 1,
|
|
31194
|
+
advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.29."
|
|
31195
|
+
});
|
|
31196
|
+
}
|
|
31197
|
+
return issues;
|
|
31198
|
+
}
|
|
31199
|
+
});
|
|
31200
|
+
}
|
|
31201
|
+
});
|
|
31202
|
+
|
|
30809
31203
|
// src/rules/kotlin/println-debug.ts
|
|
30810
|
-
var
|
|
31204
|
+
var PRINTLN_REGEX2, DEFAULT_THRESHOLD, kotlinPrintlnDebugRule;
|
|
30811
31205
|
var init_println_debug = __esm({
|
|
30812
31206
|
"src/rules/kotlin/println-debug.ts"() {
|
|
30813
31207
|
"use strict";
|
|
30814
31208
|
init_rule();
|
|
30815
|
-
|
|
31209
|
+
PRINTLN_REGEX2 = /^\s*println\s*\(/gm;
|
|
30816
31210
|
DEFAULT_THRESHOLD = 1;
|
|
30817
31211
|
kotlinPrintlnDebugRule = createRule({
|
|
30818
31212
|
id: "kotlin/println-debug",
|
|
@@ -30830,8 +31224,8 @@ var init_println_debug = __esm({
|
|
|
30830
31224
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30831
31225
|
const matches = [];
|
|
30832
31226
|
let m;
|
|
30833
|
-
|
|
30834
|
-
while ((m =
|
|
31227
|
+
PRINTLN_REGEX2.lastIndex = 0;
|
|
31228
|
+
while ((m = PRINTLN_REGEX2.exec(source)) !== null) {
|
|
30835
31229
|
matches.push(m.index);
|
|
30836
31230
|
}
|
|
30837
31231
|
if (matches.length <= context.threshold) return issues;
|
|
@@ -30856,13 +31250,102 @@ var init_println_debug = __esm({
|
|
|
30856
31250
|
}
|
|
30857
31251
|
});
|
|
30858
31252
|
|
|
31253
|
+
// src/rules/kotlin/runblocking-misuse.ts
|
|
31254
|
+
var RUN_BLOCKING_REGEX, kotlinRunBlockingMisuseRule;
|
|
31255
|
+
var init_runblocking_misuse = __esm({
|
|
31256
|
+
"src/rules/kotlin/runblocking-misuse.ts"() {
|
|
31257
|
+
"use strict";
|
|
31258
|
+
init_rule();
|
|
31259
|
+
RUN_BLOCKING_REGEX = /\brunBlocking\s*[({]/g;
|
|
31260
|
+
kotlinRunBlockingMisuseRule = createRule({
|
|
31261
|
+
id: "kotlin/runblocking-misuse",
|
|
31262
|
+
category: "perf",
|
|
31263
|
+
severity: "medium",
|
|
31264
|
+
aiSpecific: false,
|
|
31265
|
+
description: "runBlocking { ... } \u2014 blocks the calling thread; use coroutineScope {} or call suspend fun directly",
|
|
31266
|
+
create(_context) {
|
|
31267
|
+
return {};
|
|
31268
|
+
},
|
|
31269
|
+
analyze(_context, facts) {
|
|
31270
|
+
const issues = [];
|
|
31271
|
+
const source = facts.v2?._source;
|
|
31272
|
+
if (!source) return issues;
|
|
31273
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31274
|
+
if (/\bfun\s+main\s*\(/.test(source)) return issues;
|
|
31275
|
+
let m;
|
|
31276
|
+
RUN_BLOCKING_REGEX.lastIndex = 0;
|
|
31277
|
+
while ((m = RUN_BLOCKING_REGEX.exec(source)) !== null) {
|
|
31278
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
31279
|
+
issues.push({
|
|
31280
|
+
ruleId: "kotlin/runblocking-misuse",
|
|
31281
|
+
category: "perf",
|
|
31282
|
+
severity: "medium",
|
|
31283
|
+
aiSpecific: false,
|
|
31284
|
+
message: `runBlocking { ... } at line ${line}`,
|
|
31285
|
+
line,
|
|
31286
|
+
column: 1,
|
|
31287
|
+
advice: 'runBlocking blocks the calling thread, defeating the purpose of coroutines. Use coroutineScope { } for structured concurrency, or call the suspend function directly. The Kotlin coroutines docs: runBlocking "should rarely (if ever) be used outside of main()". Reference: kotlin/runblocking-misuse v0.29.'
|
|
31288
|
+
});
|
|
31289
|
+
}
|
|
31290
|
+
return issues;
|
|
31291
|
+
}
|
|
31292
|
+
});
|
|
31293
|
+
}
|
|
31294
|
+
});
|
|
31295
|
+
|
|
31296
|
+
// src/rules/kotlin/sql-string-concat.ts
|
|
31297
|
+
var SQL_KEYWORD_REGEX2, UNSAFE_REGEX2, SAFE_REGEX2, kotlinSqlStringConcatRule;
|
|
31298
|
+
var init_sql_string_concat2 = __esm({
|
|
31299
|
+
"src/rules/kotlin/sql-string-concat.ts"() {
|
|
31300
|
+
"use strict";
|
|
31301
|
+
init_rule();
|
|
31302
|
+
SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
31303
|
+
UNSAFE_REGEX2 = /(?:\+|\$\{)/;
|
|
31304
|
+
SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
|
|
31305
|
+
kotlinSqlStringConcatRule = createRule({
|
|
31306
|
+
id: "kotlin/sql-string-concat",
|
|
31307
|
+
category: "security",
|
|
31308
|
+
severity: "high",
|
|
31309
|
+
aiSpecific: false,
|
|
31310
|
+
description: "SQL query built via string concat / template \u2014 use PreparedStatement or setParameter()",
|
|
31311
|
+
create(_context) {
|
|
31312
|
+
return {};
|
|
31313
|
+
},
|
|
31314
|
+
analyze(_context, facts) {
|
|
31315
|
+
const issues = [];
|
|
31316
|
+
const source = facts.v2?._source;
|
|
31317
|
+
if (!source) return issues;
|
|
31318
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31319
|
+
const lines = source.split("\n");
|
|
31320
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31321
|
+
const line = lines[i];
|
|
31322
|
+
if (!SQL_KEYWORD_REGEX2.test(line)) continue;
|
|
31323
|
+
if (!UNSAFE_REGEX2.test(line)) continue;
|
|
31324
|
+
if (SAFE_REGEX2.test(line)) continue;
|
|
31325
|
+
issues.push({
|
|
31326
|
+
ruleId: "kotlin/sql-string-concat",
|
|
31327
|
+
category: "security",
|
|
31328
|
+
severity: "high",
|
|
31329
|
+
aiSpecific: false,
|
|
31330
|
+
message: `SQL query built via string concat/template at line ${i + 1}`,
|
|
31331
|
+
line: i + 1,
|
|
31332
|
+
column: 1,
|
|
31333
|
+
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).'
|
|
31334
|
+
});
|
|
31335
|
+
}
|
|
31336
|
+
return issues;
|
|
31337
|
+
}
|
|
31338
|
+
});
|
|
31339
|
+
}
|
|
31340
|
+
});
|
|
31341
|
+
|
|
30859
31342
|
// src/rules/kotlin/string-concat-loop.ts
|
|
30860
|
-
var
|
|
31343
|
+
var STRING_CONCAT_REGEX2, kotlinStringConcatLoopRule;
|
|
30861
31344
|
var init_string_concat_loop = __esm({
|
|
30862
31345
|
"src/rules/kotlin/string-concat-loop.ts"() {
|
|
30863
31346
|
"use strict";
|
|
30864
31347
|
init_rule();
|
|
30865
|
-
|
|
31348
|
+
STRING_CONCAT_REGEX2 = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
|
|
30866
31349
|
kotlinStringConcatLoopRule = createRule({
|
|
30867
31350
|
id: "kotlin/string-concat-loop",
|
|
30868
31351
|
category: "perf",
|
|
@@ -30879,8 +31362,8 @@ var init_string_concat_loop = __esm({
|
|
|
30879
31362
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30880
31363
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
30881
31364
|
let m;
|
|
30882
|
-
|
|
30883
|
-
while ((m =
|
|
31365
|
+
STRING_CONCAT_REGEX2.lastIndex = 0;
|
|
31366
|
+
while ((m = STRING_CONCAT_REGEX2.exec(source)) !== null) {
|
|
30884
31367
|
const line = source.slice(0, m.index).split("\n").length;
|
|
30885
31368
|
const lineText = source.slice(0, m.index).split("\n").pop() ?? "";
|
|
30886
31369
|
if (/\.append\s*\(/.test(lineText)) continue;
|
|
@@ -40440,7 +40923,7 @@ var init_fatal_error_thrown = __esm({
|
|
|
40440
40923
|
|
|
40441
40924
|
// src/rules/swift/force-unwrap.ts
|
|
40442
40925
|
var AS_FORCE_REGEX, ACCESS_FORCE_REGEX, TRY_FORCE_REGEX, swiftForceUnwrapRule;
|
|
40443
|
-
var
|
|
40926
|
+
var init_force_unwrap2 = __esm({
|
|
40444
40927
|
"src/rules/swift/force-unwrap.ts"() {
|
|
40445
40928
|
"use strict";
|
|
40446
40929
|
init_rule();
|
|
@@ -46299,10 +46782,20 @@ var init_builtins = __esm({
|
|
|
46299
46782
|
init_error_wrap_without_context();
|
|
46300
46783
|
init_nil_slice_vs_empty();
|
|
46301
46784
|
init_struct_tag_inconsistency();
|
|
46785
|
+
init_command_injection();
|
|
46786
|
+
init_hardcoded_credential();
|
|
46787
|
+
init_sql_string_concat();
|
|
46788
|
+
init_system_out_println();
|
|
46789
|
+
init_thread_sleep_in_loop();
|
|
46302
46790
|
init_coroutine_global_scope();
|
|
46303
46791
|
init_data_class_defaults_overuse();
|
|
46792
|
+
init_force_unwrap();
|
|
46793
|
+
init_hardcoded_credential2();
|
|
46304
46794
|
init_object_singleton_misuse();
|
|
46795
|
+
init_println_as_log();
|
|
46305
46796
|
init_println_debug();
|
|
46797
|
+
init_runblocking_misuse();
|
|
46798
|
+
init_sql_string_concat2();
|
|
46306
46799
|
init_string_concat_loop();
|
|
46307
46800
|
init_gap_monopoly();
|
|
46308
46801
|
init_math_element_uniformity();
|
|
@@ -46343,7 +46836,7 @@ var init_builtins = __esm({
|
|
|
46343
46836
|
init_target_blank_no_noopener();
|
|
46344
46837
|
init_unsafe_html_render();
|
|
46345
46838
|
init_fatal_error_thrown();
|
|
46346
|
-
|
|
46839
|
+
init_force_unwrap2();
|
|
46347
46840
|
init_implicitly_unwrapped_optional();
|
|
46348
46841
|
init_print_debug();
|
|
46349
46842
|
init_strong_self_capture();
|
|
@@ -46428,10 +46921,20 @@ var init_builtins = __esm({
|
|
|
46428
46921
|
goErrorWrapWithoutContextRule,
|
|
46429
46922
|
goNilSliceVsEmptyRule,
|
|
46430
46923
|
goStructTagInconsistencyRule,
|
|
46924
|
+
javaCommandInjectionRule,
|
|
46925
|
+
javaHardcodedCredentialRule,
|
|
46926
|
+
javaSqlStringConcatRule,
|
|
46927
|
+
javaSystemOutPrintlnRule,
|
|
46928
|
+
javaThreadSleepInLoopRule,
|
|
46431
46929
|
kotlinCoroutineGlobalScopeRule,
|
|
46432
46930
|
kotlinDataClassDefaultsOveruseRule,
|
|
46931
|
+
kotlinForceUnwrapRule,
|
|
46932
|
+
kotlinHardcodedCredentialRule,
|
|
46433
46933
|
kotlinObjectSingletonMisuseRule,
|
|
46934
|
+
kotlinPrintlnAsLogRule,
|
|
46434
46935
|
kotlinPrintlnDebugRule,
|
|
46936
|
+
kotlinRunBlockingMisuseRule,
|
|
46937
|
+
kotlinSqlStringConcatRule,
|
|
46435
46938
|
kotlinStringConcatLoopRule,
|
|
46436
46939
|
gapMonopolyRule,
|
|
46437
46940
|
mathElementUniformityRule,
|
|
@@ -50952,6 +51455,166 @@ var init_signal_strength = __esm({
|
|
|
50952
51455
|
_v9Precision: 0.125,
|
|
50953
51456
|
defaultOff: true
|
|
50954
51457
|
},
|
|
51458
|
+
"kotlin/sql-string-concat": {
|
|
51459
|
+
recall: 469e-5,
|
|
51460
|
+
fpRate: 63e-4,
|
|
51461
|
+
ratio: 0.75,
|
|
51462
|
+
precision: 0.0556,
|
|
51463
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51464
|
+
verdict: "DORMANT",
|
|
51465
|
+
_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.",
|
|
51466
|
+
aiSpecific: false,
|
|
51467
|
+
_v9Verdict: "DORMANT",
|
|
51468
|
+
_v9Lift: 0.75,
|
|
51469
|
+
_v9Recall: 469e-5,
|
|
51470
|
+
_v9FpRate: 63e-4,
|
|
51471
|
+
_v9Precision: 0.0556,
|
|
51472
|
+
defaultOff: true
|
|
51473
|
+
},
|
|
51474
|
+
"kotlin/hardcoded-credential": {
|
|
51475
|
+
recall: 0,
|
|
51476
|
+
fpRate: 0,
|
|
51477
|
+
ratio: 0,
|
|
51478
|
+
precision: 0,
|
|
51479
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51480
|
+
verdict: "DORMANT",
|
|
51481
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). 0 fires. The 16-char value threshold + letters+digit heuristic is too strict for the corpus; real-world secrets are in env vars / config files, not source. INSUFFICIENT_DATA: needs different corpus (CI configs, .env samples). defaultOff: loadable but invisible by default.",
|
|
51482
|
+
aiSpecific: false,
|
|
51483
|
+
_v9Verdict: "DORMANT",
|
|
51484
|
+
_v9Lift: 0,
|
|
51485
|
+
_v9Recall: 0,
|
|
51486
|
+
_v9FpRate: 0,
|
|
51487
|
+
_v9Precision: 0,
|
|
51488
|
+
defaultOff: true
|
|
51489
|
+
},
|
|
51490
|
+
"kotlin/runblocking-misuse": {
|
|
51491
|
+
recall: 0.10798,
|
|
51492
|
+
fpRate: 0.21534,
|
|
51493
|
+
ratio: 0.5,
|
|
51494
|
+
precision: 0.0381,
|
|
51495
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51496
|
+
verdict: "DORMANT",
|
|
51497
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.50 \u2014 fires 2x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Kotlin coroutines used runBlocking more; modern Kotlin uses coroutineScope. Same direction as kotlin/coroutine-global-scope (0.09). defaultOff: loadable but invisible by default until v0.30 re-calibration.",
|
|
51498
|
+
aiSpecific: false,
|
|
51499
|
+
_v9Verdict: "DORMANT",
|
|
51500
|
+
_v9Lift: 0.5,
|
|
51501
|
+
_v9Recall: 0.10798,
|
|
51502
|
+
_v9FpRate: 0.21534,
|
|
51503
|
+
_v9Precision: 0.0381,
|
|
51504
|
+
defaultOff: true
|
|
51505
|
+
},
|
|
51506
|
+
"kotlin/println-as-log": {
|
|
51507
|
+
recall: 0.08451,
|
|
51508
|
+
fpRate: 0.04596,
|
|
51509
|
+
ratio: 1.84,
|
|
51510
|
+
precision: 0.1268,
|
|
51511
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51512
|
+
verdict: "OK",
|
|
51513
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
|
|
51514
|
+
aiSpecific: false,
|
|
51515
|
+
_v9Verdict: "OK",
|
|
51516
|
+
_v9Lift: 1.84,
|
|
51517
|
+
_v9Recall: 0.08451,
|
|
51518
|
+
_v9FpRate: 0.04596,
|
|
51519
|
+
_v9Precision: 0.1268,
|
|
51520
|
+
defaultOff: true
|
|
51521
|
+
},
|
|
51522
|
+
"kotlin/force-unwrap": {
|
|
51523
|
+
recall: 0.11737,
|
|
51524
|
+
fpRate: 0.33284,
|
|
51525
|
+
ratio: 0.35,
|
|
51526
|
+
precision: 0.0271,
|
|
51527
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51528
|
+
verdict: "DORMANT",
|
|
51529
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.35 \u2014 fires 3x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Kotlin code uses !! freely; modern Kotlin relies on ?./?: and Result<T> wrappers. Stronger era signal than runblocking-misuse. defaultOff: loadable but invisible by default.",
|
|
51530
|
+
aiSpecific: false,
|
|
51531
|
+
_v9Verdict: "DORMANT",
|
|
51532
|
+
_v9Lift: 0.35,
|
|
51533
|
+
_v9Recall: 0.11737,
|
|
51534
|
+
_v9FpRate: 0.33284,
|
|
51535
|
+
_v9Precision: 0.0271,
|
|
51536
|
+
defaultOff: true
|
|
51537
|
+
},
|
|
51538
|
+
"java/sql-string-concat": {
|
|
51539
|
+
recall: 0.01116,
|
|
51540
|
+
fpRate: 0.01892,
|
|
51541
|
+
ratio: 0.59,
|
|
51542
|
+
precision: 0.0691,
|
|
51543
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51544
|
+
verdict: "DORMANT",
|
|
51545
|
+
_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.",
|
|
51546
|
+
aiSpecific: false,
|
|
51547
|
+
_v9Verdict: "DORMANT",
|
|
51548
|
+
_v9Lift: 0.59,
|
|
51549
|
+
_v9Recall: 0.01116,
|
|
51550
|
+
_v9FpRate: 0.01892,
|
|
51551
|
+
_v9Precision: 0.0691,
|
|
51552
|
+
defaultOff: true
|
|
51553
|
+
},
|
|
51554
|
+
"java/hardcoded-credential": {
|
|
51555
|
+
recall: 0,
|
|
51556
|
+
fpRate: 4e-5,
|
|
51557
|
+
ratio: 0,
|
|
51558
|
+
precision: 0,
|
|
51559
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51560
|
+
verdict: "DORMANT",
|
|
51561
|
+
_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.",
|
|
51562
|
+
aiSpecific: false,
|
|
51563
|
+
_v9Verdict: "DORMANT",
|
|
51564
|
+
_v9Lift: 0,
|
|
51565
|
+
_v9Recall: 0,
|
|
51566
|
+
_v9FpRate: 4e-5,
|
|
51567
|
+
_v9Precision: 0,
|
|
51568
|
+
defaultOff: true
|
|
51569
|
+
},
|
|
51570
|
+
"java/thread-sleep-in-loop": {
|
|
51571
|
+
recall: 0.0228,
|
|
51572
|
+
fpRate: 0.02343,
|
|
51573
|
+
ratio: 0.97,
|
|
51574
|
+
precision: 0.1091,
|
|
51575
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51576
|
+
verdict: "DORMANT",
|
|
51577
|
+
_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.",
|
|
51578
|
+
aiSpecific: false,
|
|
51579
|
+
_v9Verdict: "DORMANT",
|
|
51580
|
+
_v9Lift: 0.97,
|
|
51581
|
+
_v9Recall: 0.0228,
|
|
51582
|
+
_v9FpRate: 0.02343,
|
|
51583
|
+
_v9Precision: 0.1091,
|
|
51584
|
+
defaultOff: true
|
|
51585
|
+
},
|
|
51586
|
+
"java/system-out-println": {
|
|
51587
|
+
recall: 0.16681,
|
|
51588
|
+
fpRate: 0.05064,
|
|
51589
|
+
ratio: 3.29,
|
|
51590
|
+
precision: 0.293,
|
|
51591
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51592
|
+
verdict: "OK",
|
|
51593
|
+
_calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=3.29 (\u22651.5) \u2014 second positive-signal rule in v9 history (after kotlin/println-as-log at 1.84). Fires 3.29x more on post-2024 (pos) AI code than pre-2022 (neg) production. 5866 total fires (highest of any v0.30 rule). Precision=29.3% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Java (especially AI-generated examples) uses System.out for output; pre-2022 production uses slf4j. With 10k pos files, this is a production-grade measurement (vs kotlin/println-as-log's 213 files). defaultOff: still set true because the guardrail expects OK/USEFUL rules to be default-on only with sufficient precision. The v0.31+ re-calibration may flip this if precision improves.",
|
|
51594
|
+
aiSpecific: false,
|
|
51595
|
+
_v9Verdict: "OK",
|
|
51596
|
+
_v9Lift: 3.29,
|
|
51597
|
+
_v9Recall: 0.16681,
|
|
51598
|
+
_v9FpRate: 0.05064,
|
|
51599
|
+
_v9Precision: 0.293,
|
|
51600
|
+
defaultOff: true
|
|
51601
|
+
},
|
|
51602
|
+
"java/command-injection": {
|
|
51603
|
+
recall: 0,
|
|
51604
|
+
fpRate: 1e-4,
|
|
51605
|
+
ratio: 0,
|
|
51606
|
+
precision: 0,
|
|
51607
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51608
|
+
verdict: "DORMANT",
|
|
51609
|
+
_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.",
|
|
51610
|
+
aiSpecific: false,
|
|
51611
|
+
_v9Verdict: "DORMANT",
|
|
51612
|
+
_v9Lift: 0,
|
|
51613
|
+
_v9Recall: 0,
|
|
51614
|
+
_v9FpRate: 1e-4,
|
|
51615
|
+
_v9Precision: 0,
|
|
51616
|
+
defaultOff: true
|
|
51617
|
+
},
|
|
50955
51618
|
"swift/force-unwrap": {
|
|
50956
51619
|
recall: 0,
|
|
50957
51620
|
fpRate: 0,
|
|
@@ -59085,6 +59748,18 @@ var RULE_HINTS = {
|
|
|
59085
59748
|
"kotlin/println-debug": "Replace with `Timber.d(...)` or `android.util.Log`. `println` writes to stdout with no level, no redaction, can't be silenced in release builds. (v0.24.0 \u2014 DORMANT.)",
|
|
59086
59749
|
"kotlin/object-singleton-misuse": "Replace with a class injected via DI, a Repository, or `MutableStateFlow`. The `object` keyword gives a thread-safe singleton handle, NOT thread-safe `var` fields. (v0.24.0 \u2014 DORMANT.)",
|
|
59087
59750
|
"kotlin/string-concat-loop": "Wrap the loop in `buildString { append(...) }` or hoist a `StringBuilder`. Kotlin can't optimize `s = s + x` in a loop \u2014 O(n\xB2). (v0.24.0 \u2014 DORMANT.)",
|
|
59751
|
+
// v0.29.0 — non-AI Kotlin rules (DORMANT until larger pos arm)
|
|
59752
|
+
"kotlin/sql-string-concat": "Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concat or template interpolation into a SQL query is the canonical SQL-injection pattern. OWASP A03:2021. (v0.29.0 \u2014 DORMANT.)",
|
|
59753
|
+
"kotlin/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.29.0 \u2014 DORMANT.)",
|
|
59754
|
+
"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.)",
|
|
59755
|
+
"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.)",
|
|
59756
|
+
"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.)",
|
|
59757
|
+
// v0.30.0 — non-AI Java rules (Option C applied to v9 Java corpus, 92k files)
|
|
59758
|
+
"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.)",
|
|
59759
|
+
"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.)",
|
|
59760
|
+
"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.)",
|
|
59761
|
+
"java/system-out-println": "Use SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID. (v0.30.0 \u2014 OK; second positive-signal rule in v9 history, ratio 3.29.)",
|
|
59762
|
+
"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.)",
|
|
59088
59763
|
// v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
|
|
59089
59764
|
"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.)",
|
|
59090
59765
|
"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.)",
|