slopbrick 0.27.0 → 0.29.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 +365 -40
- package/dist/engine/worker.js +365 -40
- package/dist/index.cjs +415 -44
- package/dist/index.js +415 -44
- 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.29.0";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -30663,7 +30663,7 @@ var init_coroutine_global_scope = __esm({
|
|
|
30663
30663
|
const issues = [];
|
|
30664
30664
|
const source = facts.v2?._source;
|
|
30665
30665
|
if (!source) return issues;
|
|
30666
|
-
if (!/\.
|
|
30666
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30667
30667
|
let m;
|
|
30668
30668
|
GLOBAL_SCOPE_REGEX.lastIndex = 0;
|
|
30669
30669
|
while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
|
|
@@ -30707,7 +30707,7 @@ var init_data_class_defaults_overuse = __esm({
|
|
|
30707
30707
|
const issues = [];
|
|
30708
30708
|
const source = facts.v2?._source;
|
|
30709
30709
|
if (!source) return issues;
|
|
30710
|
-
if (!/\.
|
|
30710
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30711
30711
|
let m;
|
|
30712
30712
|
DATA_CLASS_HEAD_REGEX.lastIndex = 0;
|
|
30713
30713
|
while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
|
|
@@ -30749,6 +30749,115 @@ var init_data_class_defaults_overuse = __esm({
|
|
|
30749
30749
|
}
|
|
30750
30750
|
});
|
|
30751
30751
|
|
|
30752
|
+
// src/rules/kotlin/force-unwrap.ts
|
|
30753
|
+
var FORCE_UNWRAP_REGEX, kotlinForceUnwrapRule;
|
|
30754
|
+
var init_force_unwrap = __esm({
|
|
30755
|
+
"src/rules/kotlin/force-unwrap.ts"() {
|
|
30756
|
+
"use strict";
|
|
30757
|
+
init_rule();
|
|
30758
|
+
FORCE_UNWRAP_REGEX = /!!(?=\s*[.\)}\};,\n\[])/g;
|
|
30759
|
+
kotlinForceUnwrapRule = createRule({
|
|
30760
|
+
id: "kotlin/force-unwrap",
|
|
30761
|
+
category: "logic",
|
|
30762
|
+
severity: "medium",
|
|
30763
|
+
aiSpecific: false,
|
|
30764
|
+
description: "!! force-unwrap \u2014 use ?. (safe call) or a proper null check",
|
|
30765
|
+
create(_context) {
|
|
30766
|
+
return {};
|
|
30767
|
+
},
|
|
30768
|
+
analyze(_context, facts) {
|
|
30769
|
+
const issues = [];
|
|
30770
|
+
const source = facts.v2?._source;
|
|
30771
|
+
if (!source) return issues;
|
|
30772
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30773
|
+
let m;
|
|
30774
|
+
FORCE_UNWRAP_REGEX.lastIndex = 0;
|
|
30775
|
+
while ((m = FORCE_UNWRAP_REGEX.exec(source)) !== null) {
|
|
30776
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30777
|
+
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
|
30778
|
+
const lineText = source.slice(lineStart, m.index);
|
|
30779
|
+
const quoteCount = (lineText.match(/"/g) || []).length;
|
|
30780
|
+
if (quoteCount % 2 === 1) continue;
|
|
30781
|
+
issues.push({
|
|
30782
|
+
ruleId: "kotlin/force-unwrap",
|
|
30783
|
+
category: "logic",
|
|
30784
|
+
severity: "medium",
|
|
30785
|
+
aiSpecific: false,
|
|
30786
|
+
message: `!! force-unwrap at line ${line}`,
|
|
30787
|
+
line,
|
|
30788
|
+
column: m.index - lineStart + 1,
|
|
30789
|
+
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."
|
|
30790
|
+
});
|
|
30791
|
+
}
|
|
30792
|
+
return issues;
|
|
30793
|
+
}
|
|
30794
|
+
});
|
|
30795
|
+
}
|
|
30796
|
+
});
|
|
30797
|
+
|
|
30798
|
+
// src/rules/kotlin/hardcoded-credential.ts
|
|
30799
|
+
var KEY_REGEX, VALUE_REGEX, FALSE_POSITIVES, kotlinHardcodedCredentialRule;
|
|
30800
|
+
var init_hardcoded_credential = __esm({
|
|
30801
|
+
"src/rules/kotlin/hardcoded-credential.ts"() {
|
|
30802
|
+
"use strict";
|
|
30803
|
+
init_rule();
|
|
30804
|
+
KEY_REGEX = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
30805
|
+
VALUE_REGEX = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
30806
|
+
FALSE_POSITIVES = /* @__PURE__ */ new Set([
|
|
30807
|
+
"passwordless",
|
|
30808
|
+
"tokenize",
|
|
30809
|
+
"tokenizer",
|
|
30810
|
+
"authorizationrequired",
|
|
30811
|
+
"authenticated",
|
|
30812
|
+
"authenticatortoken",
|
|
30813
|
+
"authtoken",
|
|
30814
|
+
"placeholder",
|
|
30815
|
+
// common in test fixtures
|
|
30816
|
+
"changeme"
|
|
30817
|
+
// placeholder, but still suspicious — kept for review
|
|
30818
|
+
]);
|
|
30819
|
+
kotlinHardcodedCredentialRule = createRule({
|
|
30820
|
+
id: "kotlin/hardcoded-credential",
|
|
30821
|
+
category: "security",
|
|
30822
|
+
severity: "high",
|
|
30823
|
+
aiSpecific: false,
|
|
30824
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
30825
|
+
create(_context) {
|
|
30826
|
+
return {};
|
|
30827
|
+
},
|
|
30828
|
+
analyze(_context, facts) {
|
|
30829
|
+
const issues = [];
|
|
30830
|
+
const source = facts.v2?._source;
|
|
30831
|
+
if (!source) return issues;
|
|
30832
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30833
|
+
const lines = source.split("\n");
|
|
30834
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30835
|
+
const line = lines[i];
|
|
30836
|
+
if (!KEY_REGEX.test(line)) continue;
|
|
30837
|
+
const valueMatch = VALUE_REGEX.exec(line);
|
|
30838
|
+
if (!valueMatch) continue;
|
|
30839
|
+
const value = valueMatch[1];
|
|
30840
|
+
if (FALSE_POSITIVES.has(value.toLowerCase())) continue;
|
|
30841
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
30842
|
+
if (value.startsWith("$")) continue;
|
|
30843
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
30844
|
+
issues.push({
|
|
30845
|
+
ruleId: "kotlin/hardcoded-credential",
|
|
30846
|
+
category: "security",
|
|
30847
|
+
severity: "high",
|
|
30848
|
+
aiSpecific: false,
|
|
30849
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
30850
|
+
line: i + 1,
|
|
30851
|
+
column: 1,
|
|
30852
|
+
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)."
|
|
30853
|
+
});
|
|
30854
|
+
}
|
|
30855
|
+
return issues;
|
|
30856
|
+
}
|
|
30857
|
+
});
|
|
30858
|
+
}
|
|
30859
|
+
});
|
|
30860
|
+
|
|
30752
30861
|
// src/rules/kotlin/object-singleton-misuse.ts
|
|
30753
30862
|
var OBJECT_DECL_REGEX, kotlinObjectSingletonMisuseRule;
|
|
30754
30863
|
var init_object_singleton_misuse = __esm({
|
|
@@ -30769,7 +30878,7 @@ var init_object_singleton_misuse = __esm({
|
|
|
30769
30878
|
const issues = [];
|
|
30770
30879
|
const source = facts.v2?._source;
|
|
30771
30880
|
if (!source) return issues;
|
|
30772
|
-
if (!/\.
|
|
30881
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30773
30882
|
let m;
|
|
30774
30883
|
OBJECT_DECL_REGEX.lastIndex = 0;
|
|
30775
30884
|
while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
|
|
@@ -30806,13 +30915,58 @@ var init_object_singleton_misuse = __esm({
|
|
|
30806
30915
|
}
|
|
30807
30916
|
});
|
|
30808
30917
|
|
|
30918
|
+
// src/rules/kotlin/println-as-log.ts
|
|
30919
|
+
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX, kotlinPrintlnAsLogRule;
|
|
30920
|
+
var init_println_as_log = __esm({
|
|
30921
|
+
"src/rules/kotlin/println-as-log.ts"() {
|
|
30922
|
+
"use strict";
|
|
30923
|
+
init_rule();
|
|
30924
|
+
PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
30925
|
+
REAL_LOGGING_IMPORT_REGEX = /\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
|
+
kotlinPrintlnAsLogRule = createRule({
|
|
30927
|
+
id: "kotlin/println-as-log",
|
|
30928
|
+
category: "logic",
|
|
30929
|
+
severity: "low",
|
|
30930
|
+
aiSpecific: false,
|
|
30931
|
+
description: "println() used for logging \u2014 use slf4j, kermit, or android.util.Log",
|
|
30932
|
+
create(_context) {
|
|
30933
|
+
return {};
|
|
30934
|
+
},
|
|
30935
|
+
analyze(_context, facts) {
|
|
30936
|
+
const issues = [];
|
|
30937
|
+
const source = facts.v2?._source;
|
|
30938
|
+
if (!source) return issues;
|
|
30939
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30940
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
30941
|
+
if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
|
|
30942
|
+
let m;
|
|
30943
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
30944
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
30945
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30946
|
+
issues.push({
|
|
30947
|
+
ruleId: "kotlin/println-as-log",
|
|
30948
|
+
category: "logic",
|
|
30949
|
+
severity: "low",
|
|
30950
|
+
aiSpecific: false,
|
|
30951
|
+
message: `println() as logger at line ${line}`,
|
|
30952
|
+
line,
|
|
30953
|
+
column: 1,
|
|
30954
|
+
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."
|
|
30955
|
+
});
|
|
30956
|
+
}
|
|
30957
|
+
return issues;
|
|
30958
|
+
}
|
|
30959
|
+
});
|
|
30960
|
+
}
|
|
30961
|
+
});
|
|
30962
|
+
|
|
30809
30963
|
// src/rules/kotlin/println-debug.ts
|
|
30810
|
-
var
|
|
30964
|
+
var PRINTLN_REGEX2, DEFAULT_THRESHOLD, kotlinPrintlnDebugRule;
|
|
30811
30965
|
var init_println_debug = __esm({
|
|
30812
30966
|
"src/rules/kotlin/println-debug.ts"() {
|
|
30813
30967
|
"use strict";
|
|
30814
30968
|
init_rule();
|
|
30815
|
-
|
|
30969
|
+
PRINTLN_REGEX2 = /^\s*println\s*\(/gm;
|
|
30816
30970
|
DEFAULT_THRESHOLD = 1;
|
|
30817
30971
|
kotlinPrintlnDebugRule = createRule({
|
|
30818
30972
|
id: "kotlin/println-debug",
|
|
@@ -30827,11 +30981,11 @@ var init_println_debug = __esm({
|
|
|
30827
30981
|
const issues = [];
|
|
30828
30982
|
const source = facts.v2?._source;
|
|
30829
30983
|
if (!source) return issues;
|
|
30830
|
-
if (!/\.
|
|
30984
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30831
30985
|
const matches = [];
|
|
30832
30986
|
let m;
|
|
30833
|
-
|
|
30834
|
-
while ((m =
|
|
30987
|
+
PRINTLN_REGEX2.lastIndex = 0;
|
|
30988
|
+
while ((m = PRINTLN_REGEX2.exec(source)) !== null) {
|
|
30835
30989
|
matches.push(m.index);
|
|
30836
30990
|
}
|
|
30837
30991
|
if (matches.length <= context.threshold) return issues;
|
|
@@ -30856,6 +31010,95 @@ var init_println_debug = __esm({
|
|
|
30856
31010
|
}
|
|
30857
31011
|
});
|
|
30858
31012
|
|
|
31013
|
+
// src/rules/kotlin/runblocking-misuse.ts
|
|
31014
|
+
var RUN_BLOCKING_REGEX, kotlinRunBlockingMisuseRule;
|
|
31015
|
+
var init_runblocking_misuse = __esm({
|
|
31016
|
+
"src/rules/kotlin/runblocking-misuse.ts"() {
|
|
31017
|
+
"use strict";
|
|
31018
|
+
init_rule();
|
|
31019
|
+
RUN_BLOCKING_REGEX = /\brunBlocking\s*[({]/g;
|
|
31020
|
+
kotlinRunBlockingMisuseRule = createRule({
|
|
31021
|
+
id: "kotlin/runblocking-misuse",
|
|
31022
|
+
category: "perf",
|
|
31023
|
+
severity: "medium",
|
|
31024
|
+
aiSpecific: false,
|
|
31025
|
+
description: "runBlocking { ... } \u2014 blocks the calling thread; use coroutineScope {} or call suspend fun directly",
|
|
31026
|
+
create(_context) {
|
|
31027
|
+
return {};
|
|
31028
|
+
},
|
|
31029
|
+
analyze(_context, facts) {
|
|
31030
|
+
const issues = [];
|
|
31031
|
+
const source = facts.v2?._source;
|
|
31032
|
+
if (!source) return issues;
|
|
31033
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31034
|
+
if (/\bfun\s+main\s*\(/.test(source)) return issues;
|
|
31035
|
+
let m;
|
|
31036
|
+
RUN_BLOCKING_REGEX.lastIndex = 0;
|
|
31037
|
+
while ((m = RUN_BLOCKING_REGEX.exec(source)) !== null) {
|
|
31038
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
31039
|
+
issues.push({
|
|
31040
|
+
ruleId: "kotlin/runblocking-misuse",
|
|
31041
|
+
category: "perf",
|
|
31042
|
+
severity: "medium",
|
|
31043
|
+
aiSpecific: false,
|
|
31044
|
+
message: `runBlocking { ... } at line ${line}`,
|
|
31045
|
+
line,
|
|
31046
|
+
column: 1,
|
|
31047
|
+
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.'
|
|
31048
|
+
});
|
|
31049
|
+
}
|
|
31050
|
+
return issues;
|
|
31051
|
+
}
|
|
31052
|
+
});
|
|
31053
|
+
}
|
|
31054
|
+
});
|
|
31055
|
+
|
|
31056
|
+
// src/rules/kotlin/sql-string-concat.ts
|
|
31057
|
+
var SQL_KEYWORD_REGEX, UNSAFE_REGEX, SAFE_REGEX, kotlinSqlStringConcatRule;
|
|
31058
|
+
var init_sql_string_concat = __esm({
|
|
31059
|
+
"src/rules/kotlin/sql-string-concat.ts"() {
|
|
31060
|
+
"use strict";
|
|
31061
|
+
init_rule();
|
|
31062
|
+
SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
31063
|
+
UNSAFE_REGEX = /(?:\+|\$\{)/;
|
|
31064
|
+
SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
|
|
31065
|
+
kotlinSqlStringConcatRule = createRule({
|
|
31066
|
+
id: "kotlin/sql-string-concat",
|
|
31067
|
+
category: "security",
|
|
31068
|
+
severity: "high",
|
|
31069
|
+
aiSpecific: false,
|
|
31070
|
+
description: "SQL query built via string concat / template \u2014 use PreparedStatement or setParameter()",
|
|
31071
|
+
create(_context) {
|
|
31072
|
+
return {};
|
|
31073
|
+
},
|
|
31074
|
+
analyze(_context, facts) {
|
|
31075
|
+
const issues = [];
|
|
31076
|
+
const source = facts.v2?._source;
|
|
31077
|
+
if (!source) return issues;
|
|
31078
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31079
|
+
const lines = source.split("\n");
|
|
31080
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31081
|
+
const line = lines[i];
|
|
31082
|
+
if (!SQL_KEYWORD_REGEX.test(line)) continue;
|
|
31083
|
+
if (!UNSAFE_REGEX.test(line)) continue;
|
|
31084
|
+
if (SAFE_REGEX.test(line)) continue;
|
|
31085
|
+
issues.push({
|
|
31086
|
+
ruleId: "kotlin/sql-string-concat",
|
|
31087
|
+
category: "security",
|
|
31088
|
+
severity: "high",
|
|
31089
|
+
aiSpecific: false,
|
|
31090
|
+
message: `SQL query built via string concat/template at line ${i + 1}`,
|
|
31091
|
+
line: i + 1,
|
|
31092
|
+
column: 1,
|
|
31093
|
+
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).'
|
|
31094
|
+
});
|
|
31095
|
+
}
|
|
31096
|
+
return issues;
|
|
31097
|
+
}
|
|
31098
|
+
});
|
|
31099
|
+
}
|
|
31100
|
+
});
|
|
31101
|
+
|
|
30859
31102
|
// src/rules/kotlin/string-concat-loop.ts
|
|
30860
31103
|
var STRING_CONCAT_REGEX, kotlinStringConcatLoopRule;
|
|
30861
31104
|
var init_string_concat_loop = __esm({
|
|
@@ -30876,7 +31119,7 @@ var init_string_concat_loop = __esm({
|
|
|
30876
31119
|
const issues = [];
|
|
30877
31120
|
const source = facts.v2?._source;
|
|
30878
31121
|
if (!source) return issues;
|
|
30879
|
-
if (!/\.
|
|
31122
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30880
31123
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
30881
31124
|
let m;
|
|
30882
31125
|
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
@@ -36107,6 +36350,15 @@ function parseSource(source, filePath) {
|
|
|
36107
36350
|
case "go":
|
|
36108
36351
|
case "rs":
|
|
36109
36352
|
case "java":
|
|
36353
|
+
// v0.28.0: Kotlin files get the same parseBlankModule path as
|
|
36354
|
+
// Java (v0.24.5). All 5 `kotlin/*` rules are regex-based and
|
|
36355
|
+
// gate themselves on `/\.kts?$/i.test(filePath)` inside their
|
|
36356
|
+
// `analyze()`. The tree-sitter Kotlin integration is a larger
|
|
36357
|
+
// lift; the v0.27.0 methodology paper confirmed era-confounding
|
|
36358
|
+
// is the dominant signal anyway, so a regex-only Kotlin pass is
|
|
36359
|
+
// sufficient for the v9 calibration goal.
|
|
36360
|
+
case "kt":
|
|
36361
|
+
case "kts":
|
|
36110
36362
|
return parseBlankModule(source);
|
|
36111
36363
|
default:
|
|
36112
36364
|
return parseWithSwc(source, filePath);
|
|
@@ -40431,7 +40683,7 @@ var init_fatal_error_thrown = __esm({
|
|
|
40431
40683
|
|
|
40432
40684
|
// src/rules/swift/force-unwrap.ts
|
|
40433
40685
|
var AS_FORCE_REGEX, ACCESS_FORCE_REGEX, TRY_FORCE_REGEX, swiftForceUnwrapRule;
|
|
40434
|
-
var
|
|
40686
|
+
var init_force_unwrap2 = __esm({
|
|
40435
40687
|
"src/rules/swift/force-unwrap.ts"() {
|
|
40436
40688
|
"use strict";
|
|
40437
40689
|
init_rule();
|
|
@@ -46292,8 +46544,13 @@ var init_builtins = __esm({
|
|
|
46292
46544
|
init_struct_tag_inconsistency();
|
|
46293
46545
|
init_coroutine_global_scope();
|
|
46294
46546
|
init_data_class_defaults_overuse();
|
|
46547
|
+
init_force_unwrap();
|
|
46548
|
+
init_hardcoded_credential();
|
|
46295
46549
|
init_object_singleton_misuse();
|
|
46550
|
+
init_println_as_log();
|
|
46296
46551
|
init_println_debug();
|
|
46552
|
+
init_runblocking_misuse();
|
|
46553
|
+
init_sql_string_concat();
|
|
46297
46554
|
init_string_concat_loop();
|
|
46298
46555
|
init_gap_monopoly();
|
|
46299
46556
|
init_math_element_uniformity();
|
|
@@ -46334,7 +46591,7 @@ var init_builtins = __esm({
|
|
|
46334
46591
|
init_target_blank_no_noopener();
|
|
46335
46592
|
init_unsafe_html_render();
|
|
46336
46593
|
init_fatal_error_thrown();
|
|
46337
|
-
|
|
46594
|
+
init_force_unwrap2();
|
|
46338
46595
|
init_implicitly_unwrapped_optional();
|
|
46339
46596
|
init_print_debug();
|
|
46340
46597
|
init_strong_self_capture();
|
|
@@ -46421,8 +46678,13 @@ var init_builtins = __esm({
|
|
|
46421
46678
|
goStructTagInconsistencyRule,
|
|
46422
46679
|
kotlinCoroutineGlobalScopeRule,
|
|
46423
46680
|
kotlinDataClassDefaultsOveruseRule,
|
|
46681
|
+
kotlinForceUnwrapRule,
|
|
46682
|
+
kotlinHardcodedCredentialRule,
|
|
46424
46683
|
kotlinObjectSingletonMisuseRule,
|
|
46684
|
+
kotlinPrintlnAsLogRule,
|
|
46425
46685
|
kotlinPrintlnDebugRule,
|
|
46686
|
+
kotlinRunBlockingMisuseRule,
|
|
46687
|
+
kotlinSqlStringConcatRule,
|
|
46426
46688
|
kotlinStringConcatLoopRule,
|
|
46427
46689
|
gapMonopolyRule,
|
|
46428
46690
|
mathElementUniformityRule,
|
|
@@ -50829,13 +51091,13 @@ var init_signal_strength = __esm({
|
|
|
50829
51091
|
defaultOff: true
|
|
50830
51092
|
},
|
|
50831
51093
|
"kotlin/data-class-defaults-overuse": {
|
|
50832
|
-
recall:
|
|
50833
|
-
fpRate:
|
|
50834
|
-
ratio:
|
|
50835
|
-
precision: 0,
|
|
50836
|
-
lastCalibratedAt: "2026-07-
|
|
51094
|
+
recall: 469e-5,
|
|
51095
|
+
fpRate: 222e-5,
|
|
51096
|
+
ratio: 2.11,
|
|
51097
|
+
precision: 0.1429,
|
|
51098
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50837
51099
|
verdict: "DORMANT",
|
|
50838
|
-
_calibrationNote: "v0.
|
|
51100
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=2.11 (\u22651.5) but precision=14.3% (<50%) \u2014 verdict DORMANT. Era-confounding: pre-2022 Kotlin is sparse; the rule fires on the rare .kt file with multiple default values regardless of authorship.",
|
|
50839
51101
|
aiSpecific: true,
|
|
50840
51102
|
_v7Verdict: "DORMANT",
|
|
50841
51103
|
_v7Lift: 1,
|
|
@@ -50844,16 +51106,21 @@ var init_signal_strength = __esm({
|
|
|
50844
51106
|
_v7Precision: 0,
|
|
50845
51107
|
_v8Verdict: "DORMANT",
|
|
50846
51108
|
_v8Lift: 1,
|
|
51109
|
+
_v9Verdict: "DORMANT",
|
|
51110
|
+
_v9Lift: 2.11,
|
|
51111
|
+
_v9Recall: 469e-5,
|
|
51112
|
+
_v9FpRate: 222e-5,
|
|
51113
|
+
_v9Precision: 0.1429,
|
|
50847
51114
|
defaultOff: true
|
|
50848
51115
|
},
|
|
50849
51116
|
"kotlin/coroutine-global-scope": {
|
|
50850
|
-
recall:
|
|
50851
|
-
fpRate: 0,
|
|
50852
|
-
ratio:
|
|
50853
|
-
precision:
|
|
50854
|
-
lastCalibratedAt: "2026-07-
|
|
51117
|
+
recall: 469e-5,
|
|
51118
|
+
fpRate: 0.05004,
|
|
51119
|
+
ratio: 0.09,
|
|
51120
|
+
precision: 735e-5,
|
|
51121
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50855
51122
|
verdict: "DORMANT",
|
|
50856
|
-
_calibrationNote: "v0.
|
|
51123
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.09 (FAR <1.0) \u2014 fires 135x more on neg than pos. Classic era-confound: pre-2022 Kotlin coroutines had GlobalScope as the default; modern Kotlin uses viewModelScope / lifecycleScope. The rule detects era, not AI authorship.",
|
|
50857
51124
|
aiSpecific: true,
|
|
50858
51125
|
_v7Verdict: "DORMANT",
|
|
50859
51126
|
_v7Lift: 1,
|
|
@@ -50862,16 +51129,21 @@ var init_signal_strength = __esm({
|
|
|
50862
51129
|
_v7Precision: 0,
|
|
50863
51130
|
_v8Verdict: "DORMANT",
|
|
50864
51131
|
_v8Lift: 1,
|
|
51132
|
+
_v9Verdict: "DORMANT",
|
|
51133
|
+
_v9Lift: 0.09,
|
|
51134
|
+
_v9Recall: 469e-5,
|
|
51135
|
+
_v9FpRate: 0.05004,
|
|
51136
|
+
_v9Precision: 735e-5,
|
|
50865
51137
|
defaultOff: true
|
|
50866
51138
|
},
|
|
50867
51139
|
"kotlin/println-debug": {
|
|
50868
|
-
recall: 0,
|
|
50869
|
-
fpRate: 0,
|
|
50870
|
-
ratio:
|
|
50871
|
-
precision: 0,
|
|
50872
|
-
lastCalibratedAt: "2026-07-
|
|
51140
|
+
recall: 0.07042,
|
|
51141
|
+
fpRate: 0.11379,
|
|
51142
|
+
ratio: 0.62,
|
|
51143
|
+
precision: 0.04659,
|
|
51144
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50873
51145
|
verdict: "DORMANT",
|
|
50874
|
-
_calibrationNote: "v0.
|
|
51146
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.62 (\u226A1.0) \u2014 fires 20x more on neg than pos. Classic era-confound: pre-2022 Kotlin code uses println() for debug; modern Kotlin uses Timber / android.util.Log / kermit.",
|
|
50875
51147
|
aiSpecific: true,
|
|
50876
51148
|
_v7Verdict: "DORMANT",
|
|
50877
51149
|
_v7Lift: 1,
|
|
@@ -50880,16 +51152,21 @@ var init_signal_strength = __esm({
|
|
|
50880
51152
|
_v7Precision: 0,
|
|
50881
51153
|
_v8Verdict: "DORMANT",
|
|
50882
51154
|
_v8Lift: 1,
|
|
51155
|
+
_v9Verdict: "DORMANT",
|
|
51156
|
+
_v9Lift: 0.62,
|
|
51157
|
+
_v9Recall: 0.07042,
|
|
51158
|
+
_v9FpRate: 0.11379,
|
|
51159
|
+
_v9Precision: 0.04659,
|
|
50883
51160
|
defaultOff: true
|
|
50884
51161
|
},
|
|
50885
51162
|
"kotlin/object-singleton-misuse": {
|
|
50886
|
-
recall:
|
|
50887
|
-
fpRate:
|
|
50888
|
-
ratio: 1,
|
|
50889
|
-
precision: 0,
|
|
50890
|
-
lastCalibratedAt: "2026-07-
|
|
51163
|
+
recall: 469e-5,
|
|
51164
|
+
fpRate: 371e-5,
|
|
51165
|
+
ratio: 1.27,
|
|
51166
|
+
precision: 0.09091,
|
|
51167
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50891
51168
|
verdict: "DORMANT",
|
|
50892
|
-
_calibrationNote: "v0.
|
|
51169
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.27 (\u22651.0) but <1.5; precision=9.1% \u2014 verdict DORMANT. INSUFFICIENT_DATA: pos arm only 213 files (under 10k minimum).",
|
|
50893
51170
|
aiSpecific: true,
|
|
50894
51171
|
_v7Verdict: "DORMANT",
|
|
50895
51172
|
_v7Lift: 1,
|
|
@@ -50898,16 +51175,21 @@ var init_signal_strength = __esm({
|
|
|
50898
51175
|
_v7Precision: 0,
|
|
50899
51176
|
_v8Verdict: "DORMANT",
|
|
50900
51177
|
_v8Lift: 1,
|
|
51178
|
+
_v9Verdict: "DORMANT",
|
|
51179
|
+
_v9Lift: 1.27,
|
|
51180
|
+
_v9Recall: 469e-5,
|
|
51181
|
+
_v9FpRate: 371e-5,
|
|
51182
|
+
_v9Precision: 0.09091,
|
|
50901
51183
|
defaultOff: true
|
|
50902
51184
|
},
|
|
50903
51185
|
"kotlin/string-concat-loop": {
|
|
50904
|
-
recall:
|
|
50905
|
-
fpRate:
|
|
50906
|
-
ratio: 1,
|
|
50907
|
-
precision: 0,
|
|
50908
|
-
lastCalibratedAt: "2026-07-
|
|
51186
|
+
recall: 939e-5,
|
|
51187
|
+
fpRate: 519e-5,
|
|
51188
|
+
ratio: 1.81,
|
|
51189
|
+
precision: 0.125,
|
|
51190
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50909
51191
|
verdict: "DORMANT",
|
|
50910
|
-
_calibrationNote: "v0.
|
|
51192
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.81 (\u22651.5) but precision=12.5% (<50%) \u2014 verdict DORMANT. INSUFFICIENT_DATA: pos arm only 213 files.",
|
|
50911
51193
|
aiSpecific: true,
|
|
50912
51194
|
_v7Verdict: "DORMANT",
|
|
50913
51195
|
_v7Lift: 1,
|
|
@@ -50916,6 +51198,91 @@ var init_signal_strength = __esm({
|
|
|
50916
51198
|
_v7Precision: 0,
|
|
50917
51199
|
_v8Verdict: "DORMANT",
|
|
50918
51200
|
_v8Lift: 1,
|
|
51201
|
+
_v9Verdict: "DORMANT",
|
|
51202
|
+
_v9Lift: 1.81,
|
|
51203
|
+
_v9Recall: 939e-5,
|
|
51204
|
+
_v9FpRate: 519e-5,
|
|
51205
|
+
_v9Precision: 0.125,
|
|
51206
|
+
defaultOff: true
|
|
51207
|
+
},
|
|
51208
|
+
"kotlin/sql-string-concat": {
|
|
51209
|
+
recall: 469e-5,
|
|
51210
|
+
fpRate: 63e-4,
|
|
51211
|
+
ratio: 0.75,
|
|
51212
|
+
precision: 0.0556,
|
|
51213
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51214
|
+
verdict: "DORMANT",
|
|
51215
|
+
_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.",
|
|
51216
|
+
aiSpecific: false,
|
|
51217
|
+
_v9Verdict: "DORMANT",
|
|
51218
|
+
_v9Lift: 0.75,
|
|
51219
|
+
_v9Recall: 469e-5,
|
|
51220
|
+
_v9FpRate: 63e-4,
|
|
51221
|
+
_v9Precision: 0.0556,
|
|
51222
|
+
defaultOff: true
|
|
51223
|
+
},
|
|
51224
|
+
"kotlin/hardcoded-credential": {
|
|
51225
|
+
recall: 0,
|
|
51226
|
+
fpRate: 0,
|
|
51227
|
+
ratio: 0,
|
|
51228
|
+
precision: 0,
|
|
51229
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51230
|
+
verdict: "DORMANT",
|
|
51231
|
+
_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.",
|
|
51232
|
+
aiSpecific: false,
|
|
51233
|
+
_v9Verdict: "DORMANT",
|
|
51234
|
+
_v9Lift: 0,
|
|
51235
|
+
_v9Recall: 0,
|
|
51236
|
+
_v9FpRate: 0,
|
|
51237
|
+
_v9Precision: 0,
|
|
51238
|
+
defaultOff: true
|
|
51239
|
+
},
|
|
51240
|
+
"kotlin/runblocking-misuse": {
|
|
51241
|
+
recall: 0.10798,
|
|
51242
|
+
fpRate: 0.21534,
|
|
51243
|
+
ratio: 0.5,
|
|
51244
|
+
precision: 0.0381,
|
|
51245
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51246
|
+
verdict: "DORMANT",
|
|
51247
|
+
_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.",
|
|
51248
|
+
aiSpecific: false,
|
|
51249
|
+
_v9Verdict: "DORMANT",
|
|
51250
|
+
_v9Lift: 0.5,
|
|
51251
|
+
_v9Recall: 0.10798,
|
|
51252
|
+
_v9FpRate: 0.21534,
|
|
51253
|
+
_v9Precision: 0.0381,
|
|
51254
|
+
defaultOff: true
|
|
51255
|
+
},
|
|
51256
|
+
"kotlin/println-as-log": {
|
|
51257
|
+
recall: 0.08451,
|
|
51258
|
+
fpRate: 0.04596,
|
|
51259
|
+
ratio: 1.84,
|
|
51260
|
+
precision: 0.1268,
|
|
51261
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51262
|
+
verdict: "OK",
|
|
51263
|
+
_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).",
|
|
51264
|
+
aiSpecific: false,
|
|
51265
|
+
_v9Verdict: "OK",
|
|
51266
|
+
_v9Lift: 1.84,
|
|
51267
|
+
_v9Recall: 0.08451,
|
|
51268
|
+
_v9FpRate: 0.04596,
|
|
51269
|
+
_v9Precision: 0.1268,
|
|
51270
|
+
defaultOff: true
|
|
51271
|
+
},
|
|
51272
|
+
"kotlin/force-unwrap": {
|
|
51273
|
+
recall: 0.11737,
|
|
51274
|
+
fpRate: 0.33284,
|
|
51275
|
+
ratio: 0.35,
|
|
51276
|
+
precision: 0.0271,
|
|
51277
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51278
|
+
verdict: "DORMANT",
|
|
51279
|
+
_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.",
|
|
51280
|
+
aiSpecific: false,
|
|
51281
|
+
_v9Verdict: "DORMANT",
|
|
51282
|
+
_v9Lift: 0.35,
|
|
51283
|
+
_v9Recall: 0.11737,
|
|
51284
|
+
_v9FpRate: 0.33284,
|
|
51285
|
+
_v9Precision: 0.0271,
|
|
50919
51286
|
defaultOff: true
|
|
50920
51287
|
},
|
|
50921
51288
|
"swift/force-unwrap": {
|
|
@@ -51180,8 +51547,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
|
51180
51547
|
const ext = extname5(filePath).toLowerCase();
|
|
51181
51548
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
51182
51549
|
".swift",
|
|
51183
|
-
".kt",
|
|
51184
|
-
".kts",
|
|
51185
51550
|
".dart",
|
|
51186
51551
|
".cpp",
|
|
51187
51552
|
".cc",
|
|
@@ -59053,6 +59418,12 @@ var RULE_HINTS = {
|
|
|
59053
59418
|
"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.)",
|
|
59054
59419
|
"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.)",
|
|
59055
59420
|
"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.)",
|
|
59421
|
+
// v0.29.0 — non-AI Kotlin rules (DORMANT until larger pos arm)
|
|
59422
|
+
"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.)",
|
|
59423
|
+
"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.)",
|
|
59424
|
+
"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
|
+
"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
|
+
"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.)",
|
|
59056
59427
|
// v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
|
|
59057
59428
|
"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.)",
|
|
59058
59429
|
"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.29.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": {
|