slopbrick 0.29.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 +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.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({
|
|
@@ -30796,14 +31036,14 @@ var init_force_unwrap = __esm({
|
|
|
30796
31036
|
});
|
|
30797
31037
|
|
|
30798
31038
|
// src/rules/kotlin/hardcoded-credential.ts
|
|
30799
|
-
var
|
|
30800
|
-
var
|
|
31039
|
+
var KEY_REGEX2, VALUE_REGEX2, FALSE_POSITIVES2, kotlinHardcodedCredentialRule;
|
|
31040
|
+
var init_hardcoded_credential2 = __esm({
|
|
30801
31041
|
"src/rules/kotlin/hardcoded-credential.ts"() {
|
|
30802
31042
|
"use strict";
|
|
30803
31043
|
init_rule();
|
|
30804
|
-
|
|
30805
|
-
|
|
30806
|
-
|
|
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([
|
|
30807
31047
|
"passwordless",
|
|
30808
31048
|
"tokenize",
|
|
30809
31049
|
"tokenizer",
|
|
@@ -30833,11 +31073,11 @@ var init_hardcoded_credential = __esm({
|
|
|
30833
31073
|
const lines = source.split("\n");
|
|
30834
31074
|
for (let i = 0; i < lines.length; i++) {
|
|
30835
31075
|
const line = lines[i];
|
|
30836
|
-
if (!
|
|
30837
|
-
const valueMatch =
|
|
31076
|
+
if (!KEY_REGEX2.test(line)) continue;
|
|
31077
|
+
const valueMatch = VALUE_REGEX2.exec(line);
|
|
30838
31078
|
if (!valueMatch) continue;
|
|
30839
31079
|
const value = valueMatch[1];
|
|
30840
|
-
if (
|
|
31080
|
+
if (FALSE_POSITIVES2.has(value.toLowerCase())) continue;
|
|
30841
31081
|
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
30842
31082
|
if (value.startsWith("$")) continue;
|
|
30843
31083
|
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
@@ -30916,13 +31156,13 @@ var init_object_singleton_misuse = __esm({
|
|
|
30916
31156
|
});
|
|
30917
31157
|
|
|
30918
31158
|
// src/rules/kotlin/println-as-log.ts
|
|
30919
|
-
var PRINTLN_REGEX,
|
|
31159
|
+
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, kotlinPrintlnAsLogRule;
|
|
30920
31160
|
var init_println_as_log = __esm({
|
|
30921
31161
|
"src/rules/kotlin/println-as-log.ts"() {
|
|
30922
31162
|
"use strict";
|
|
30923
31163
|
init_rule();
|
|
30924
31164
|
PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
30925
|
-
|
|
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)/;
|
|
30926
31166
|
kotlinPrintlnAsLogRule = createRule({
|
|
30927
31167
|
id: "kotlin/println-as-log",
|
|
30928
31168
|
category: "logic",
|
|
@@ -30938,7 +31178,7 @@ var init_println_as_log = __esm({
|
|
|
30938
31178
|
if (!source) return issues;
|
|
30939
31179
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30940
31180
|
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
30941
|
-
if (
|
|
31181
|
+
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
30942
31182
|
let m;
|
|
30943
31183
|
PRINTLN_REGEX.lastIndex = 0;
|
|
30944
31184
|
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
@@ -31054,14 +31294,14 @@ var init_runblocking_misuse = __esm({
|
|
|
31054
31294
|
});
|
|
31055
31295
|
|
|
31056
31296
|
// src/rules/kotlin/sql-string-concat.ts
|
|
31057
|
-
var
|
|
31058
|
-
var
|
|
31297
|
+
var SQL_KEYWORD_REGEX2, UNSAFE_REGEX2, SAFE_REGEX2, kotlinSqlStringConcatRule;
|
|
31298
|
+
var init_sql_string_concat2 = __esm({
|
|
31059
31299
|
"src/rules/kotlin/sql-string-concat.ts"() {
|
|
31060
31300
|
"use strict";
|
|
31061
31301
|
init_rule();
|
|
31062
|
-
|
|
31063
|
-
|
|
31064
|
-
|
|
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*,)/;
|
|
31065
31305
|
kotlinSqlStringConcatRule = createRule({
|
|
31066
31306
|
id: "kotlin/sql-string-concat",
|
|
31067
31307
|
category: "security",
|
|
@@ -31079,9 +31319,9 @@ var init_sql_string_concat = __esm({
|
|
|
31079
31319
|
const lines = source.split("\n");
|
|
31080
31320
|
for (let i = 0; i < lines.length; i++) {
|
|
31081
31321
|
const line = lines[i];
|
|
31082
|
-
if (!
|
|
31083
|
-
if (!
|
|
31084
|
-
if (
|
|
31322
|
+
if (!SQL_KEYWORD_REGEX2.test(line)) continue;
|
|
31323
|
+
if (!UNSAFE_REGEX2.test(line)) continue;
|
|
31324
|
+
if (SAFE_REGEX2.test(line)) continue;
|
|
31085
31325
|
issues.push({
|
|
31086
31326
|
ruleId: "kotlin/sql-string-concat",
|
|
31087
31327
|
category: "security",
|
|
@@ -31100,12 +31340,12 @@ var init_sql_string_concat = __esm({
|
|
|
31100
31340
|
});
|
|
31101
31341
|
|
|
31102
31342
|
// src/rules/kotlin/string-concat-loop.ts
|
|
31103
|
-
var
|
|
31343
|
+
var STRING_CONCAT_REGEX2, kotlinStringConcatLoopRule;
|
|
31104
31344
|
var init_string_concat_loop = __esm({
|
|
31105
31345
|
"src/rules/kotlin/string-concat-loop.ts"() {
|
|
31106
31346
|
"use strict";
|
|
31107
31347
|
init_rule();
|
|
31108
|
-
|
|
31348
|
+
STRING_CONCAT_REGEX2 = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
|
|
31109
31349
|
kotlinStringConcatLoopRule = createRule({
|
|
31110
31350
|
id: "kotlin/string-concat-loop",
|
|
31111
31351
|
category: "perf",
|
|
@@ -31122,8 +31362,8 @@ var init_string_concat_loop = __esm({
|
|
|
31122
31362
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31123
31363
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
31124
31364
|
let m;
|
|
31125
|
-
|
|
31126
|
-
while ((m =
|
|
31365
|
+
STRING_CONCAT_REGEX2.lastIndex = 0;
|
|
31366
|
+
while ((m = STRING_CONCAT_REGEX2.exec(source)) !== null) {
|
|
31127
31367
|
const line = source.slice(0, m.index).split("\n").length;
|
|
31128
31368
|
const lineText = source.slice(0, m.index).split("\n").pop() ?? "";
|
|
31129
31369
|
if (/\.append\s*\(/.test(lineText)) continue;
|
|
@@ -46542,15 +46782,20 @@ var init_builtins = __esm({
|
|
|
46542
46782
|
init_error_wrap_without_context();
|
|
46543
46783
|
init_nil_slice_vs_empty();
|
|
46544
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();
|
|
46545
46790
|
init_coroutine_global_scope();
|
|
46546
46791
|
init_data_class_defaults_overuse();
|
|
46547
46792
|
init_force_unwrap();
|
|
46548
|
-
|
|
46793
|
+
init_hardcoded_credential2();
|
|
46549
46794
|
init_object_singleton_misuse();
|
|
46550
46795
|
init_println_as_log();
|
|
46551
46796
|
init_println_debug();
|
|
46552
46797
|
init_runblocking_misuse();
|
|
46553
|
-
|
|
46798
|
+
init_sql_string_concat2();
|
|
46554
46799
|
init_string_concat_loop();
|
|
46555
46800
|
init_gap_monopoly();
|
|
46556
46801
|
init_math_element_uniformity();
|
|
@@ -46676,6 +46921,11 @@ var init_builtins = __esm({
|
|
|
46676
46921
|
goErrorWrapWithoutContextRule,
|
|
46677
46922
|
goNilSliceVsEmptyRule,
|
|
46678
46923
|
goStructTagInconsistencyRule,
|
|
46924
|
+
javaCommandInjectionRule,
|
|
46925
|
+
javaHardcodedCredentialRule,
|
|
46926
|
+
javaSqlStringConcatRule,
|
|
46927
|
+
javaSystemOutPrintlnRule,
|
|
46928
|
+
javaThreadSleepInLoopRule,
|
|
46679
46929
|
kotlinCoroutineGlobalScopeRule,
|
|
46680
46930
|
kotlinDataClassDefaultsOveruseRule,
|
|
46681
46931
|
kotlinForceUnwrapRule,
|
|
@@ -51285,6 +51535,86 @@ var init_signal_strength = __esm({
|
|
|
51285
51535
|
_v9Precision: 0.0271,
|
|
51286
51536
|
defaultOff: true
|
|
51287
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
|
+
},
|
|
51288
51618
|
"swift/force-unwrap": {
|
|
51289
51619
|
recall: 0,
|
|
51290
51620
|
fpRate: 0,
|
|
@@ -59424,6 +59754,12 @@ var RULE_HINTS = {
|
|
|
59424
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.)",
|
|
59425
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.)",
|
|
59426
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.)",
|
|
59427
59763
|
// v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
|
|
59428
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.)",
|
|
59429
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.)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slopbrick",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
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": {
|