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.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var VERSION;
|
|
|
36
36
|
var init_header = __esm({
|
|
37
37
|
"src/types/_header.ts"() {
|
|
38
38
|
"use strict";
|
|
39
|
-
VERSION = "0.
|
|
39
|
+
VERSION = "0.29.0";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -30682,7 +30682,7 @@ var init_coroutine_global_scope = __esm({
|
|
|
30682
30682
|
const issues = [];
|
|
30683
30683
|
const source = facts.v2?._source;
|
|
30684
30684
|
if (!source) return issues;
|
|
30685
|
-
if (!/\.
|
|
30685
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30686
30686
|
let m;
|
|
30687
30687
|
GLOBAL_SCOPE_REGEX.lastIndex = 0;
|
|
30688
30688
|
while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
|
|
@@ -30726,7 +30726,7 @@ var init_data_class_defaults_overuse = __esm({
|
|
|
30726
30726
|
const issues = [];
|
|
30727
30727
|
const source = facts.v2?._source;
|
|
30728
30728
|
if (!source) return issues;
|
|
30729
|
-
if (!/\.
|
|
30729
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30730
30730
|
let m;
|
|
30731
30731
|
DATA_CLASS_HEAD_REGEX.lastIndex = 0;
|
|
30732
30732
|
while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
|
|
@@ -30768,6 +30768,115 @@ var init_data_class_defaults_overuse = __esm({
|
|
|
30768
30768
|
}
|
|
30769
30769
|
});
|
|
30770
30770
|
|
|
30771
|
+
// src/rules/kotlin/force-unwrap.ts
|
|
30772
|
+
var FORCE_UNWRAP_REGEX, kotlinForceUnwrapRule;
|
|
30773
|
+
var init_force_unwrap = __esm({
|
|
30774
|
+
"src/rules/kotlin/force-unwrap.ts"() {
|
|
30775
|
+
"use strict";
|
|
30776
|
+
init_rule();
|
|
30777
|
+
FORCE_UNWRAP_REGEX = /!!(?=\s*[.\)}\};,\n\[])/g;
|
|
30778
|
+
kotlinForceUnwrapRule = createRule({
|
|
30779
|
+
id: "kotlin/force-unwrap",
|
|
30780
|
+
category: "logic",
|
|
30781
|
+
severity: "medium",
|
|
30782
|
+
aiSpecific: false,
|
|
30783
|
+
description: "!! force-unwrap \u2014 use ?. (safe call) or a proper null check",
|
|
30784
|
+
create(_context) {
|
|
30785
|
+
return {};
|
|
30786
|
+
},
|
|
30787
|
+
analyze(_context, facts) {
|
|
30788
|
+
const issues = [];
|
|
30789
|
+
const source = facts.v2?._source;
|
|
30790
|
+
if (!source) return issues;
|
|
30791
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30792
|
+
let m;
|
|
30793
|
+
FORCE_UNWRAP_REGEX.lastIndex = 0;
|
|
30794
|
+
while ((m = FORCE_UNWRAP_REGEX.exec(source)) !== null) {
|
|
30795
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30796
|
+
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
|
30797
|
+
const lineText = source.slice(lineStart, m.index);
|
|
30798
|
+
const quoteCount = (lineText.match(/"/g) || []).length;
|
|
30799
|
+
if (quoteCount % 2 === 1) continue;
|
|
30800
|
+
issues.push({
|
|
30801
|
+
ruleId: "kotlin/force-unwrap",
|
|
30802
|
+
category: "logic",
|
|
30803
|
+
severity: "medium",
|
|
30804
|
+
aiSpecific: false,
|
|
30805
|
+
message: `!! force-unwrap at line ${line}`,
|
|
30806
|
+
line,
|
|
30807
|
+
column: m.index - lineStart + 1,
|
|
30808
|
+
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."
|
|
30809
|
+
});
|
|
30810
|
+
}
|
|
30811
|
+
return issues;
|
|
30812
|
+
}
|
|
30813
|
+
});
|
|
30814
|
+
}
|
|
30815
|
+
});
|
|
30816
|
+
|
|
30817
|
+
// src/rules/kotlin/hardcoded-credential.ts
|
|
30818
|
+
var KEY_REGEX, VALUE_REGEX, FALSE_POSITIVES, kotlinHardcodedCredentialRule;
|
|
30819
|
+
var init_hardcoded_credential = __esm({
|
|
30820
|
+
"src/rules/kotlin/hardcoded-credential.ts"() {
|
|
30821
|
+
"use strict";
|
|
30822
|
+
init_rule();
|
|
30823
|
+
KEY_REGEX = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
30824
|
+
VALUE_REGEX = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
30825
|
+
FALSE_POSITIVES = /* @__PURE__ */ new Set([
|
|
30826
|
+
"passwordless",
|
|
30827
|
+
"tokenize",
|
|
30828
|
+
"tokenizer",
|
|
30829
|
+
"authorizationrequired",
|
|
30830
|
+
"authenticated",
|
|
30831
|
+
"authenticatortoken",
|
|
30832
|
+
"authtoken",
|
|
30833
|
+
"placeholder",
|
|
30834
|
+
// common in test fixtures
|
|
30835
|
+
"changeme"
|
|
30836
|
+
// placeholder, but still suspicious — kept for review
|
|
30837
|
+
]);
|
|
30838
|
+
kotlinHardcodedCredentialRule = createRule({
|
|
30839
|
+
id: "kotlin/hardcoded-credential",
|
|
30840
|
+
category: "security",
|
|
30841
|
+
severity: "high",
|
|
30842
|
+
aiSpecific: false,
|
|
30843
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
30844
|
+
create(_context) {
|
|
30845
|
+
return {};
|
|
30846
|
+
},
|
|
30847
|
+
analyze(_context, facts) {
|
|
30848
|
+
const issues = [];
|
|
30849
|
+
const source = facts.v2?._source;
|
|
30850
|
+
if (!source) return issues;
|
|
30851
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30852
|
+
const lines = source.split("\n");
|
|
30853
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30854
|
+
const line = lines[i];
|
|
30855
|
+
if (!KEY_REGEX.test(line)) continue;
|
|
30856
|
+
const valueMatch = VALUE_REGEX.exec(line);
|
|
30857
|
+
if (!valueMatch) continue;
|
|
30858
|
+
const value = valueMatch[1];
|
|
30859
|
+
if (FALSE_POSITIVES.has(value.toLowerCase())) continue;
|
|
30860
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
30861
|
+
if (value.startsWith("$")) continue;
|
|
30862
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
30863
|
+
issues.push({
|
|
30864
|
+
ruleId: "kotlin/hardcoded-credential",
|
|
30865
|
+
category: "security",
|
|
30866
|
+
severity: "high",
|
|
30867
|
+
aiSpecific: false,
|
|
30868
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
30869
|
+
line: i + 1,
|
|
30870
|
+
column: 1,
|
|
30871
|
+
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)."
|
|
30872
|
+
});
|
|
30873
|
+
}
|
|
30874
|
+
return issues;
|
|
30875
|
+
}
|
|
30876
|
+
});
|
|
30877
|
+
}
|
|
30878
|
+
});
|
|
30879
|
+
|
|
30771
30880
|
// src/rules/kotlin/object-singleton-misuse.ts
|
|
30772
30881
|
var OBJECT_DECL_REGEX, kotlinObjectSingletonMisuseRule;
|
|
30773
30882
|
var init_object_singleton_misuse = __esm({
|
|
@@ -30788,7 +30897,7 @@ var init_object_singleton_misuse = __esm({
|
|
|
30788
30897
|
const issues = [];
|
|
30789
30898
|
const source = facts.v2?._source;
|
|
30790
30899
|
if (!source) return issues;
|
|
30791
|
-
if (!/\.
|
|
30900
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30792
30901
|
let m;
|
|
30793
30902
|
OBJECT_DECL_REGEX.lastIndex = 0;
|
|
30794
30903
|
while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
|
|
@@ -30825,13 +30934,58 @@ var init_object_singleton_misuse = __esm({
|
|
|
30825
30934
|
}
|
|
30826
30935
|
});
|
|
30827
30936
|
|
|
30937
|
+
// src/rules/kotlin/println-as-log.ts
|
|
30938
|
+
var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX, kotlinPrintlnAsLogRule;
|
|
30939
|
+
var init_println_as_log = __esm({
|
|
30940
|
+
"src/rules/kotlin/println-as-log.ts"() {
|
|
30941
|
+
"use strict";
|
|
30942
|
+
init_rule();
|
|
30943
|
+
PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
30944
|
+
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)/;
|
|
30945
|
+
kotlinPrintlnAsLogRule = createRule({
|
|
30946
|
+
id: "kotlin/println-as-log",
|
|
30947
|
+
category: "logic",
|
|
30948
|
+
severity: "low",
|
|
30949
|
+
aiSpecific: false,
|
|
30950
|
+
description: "println() used for logging \u2014 use slf4j, kermit, or android.util.Log",
|
|
30951
|
+
create(_context) {
|
|
30952
|
+
return {};
|
|
30953
|
+
},
|
|
30954
|
+
analyze(_context, facts) {
|
|
30955
|
+
const issues = [];
|
|
30956
|
+
const source = facts.v2?._source;
|
|
30957
|
+
if (!source) return issues;
|
|
30958
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30959
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
30960
|
+
if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
|
|
30961
|
+
let m;
|
|
30962
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
30963
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
30964
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30965
|
+
issues.push({
|
|
30966
|
+
ruleId: "kotlin/println-as-log",
|
|
30967
|
+
category: "logic",
|
|
30968
|
+
severity: "low",
|
|
30969
|
+
aiSpecific: false,
|
|
30970
|
+
message: `println() as logger at line ${line}`,
|
|
30971
|
+
line,
|
|
30972
|
+
column: 1,
|
|
30973
|
+
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."
|
|
30974
|
+
});
|
|
30975
|
+
}
|
|
30976
|
+
return issues;
|
|
30977
|
+
}
|
|
30978
|
+
});
|
|
30979
|
+
}
|
|
30980
|
+
});
|
|
30981
|
+
|
|
30828
30982
|
// src/rules/kotlin/println-debug.ts
|
|
30829
|
-
var
|
|
30983
|
+
var PRINTLN_REGEX2, DEFAULT_THRESHOLD, kotlinPrintlnDebugRule;
|
|
30830
30984
|
var init_println_debug = __esm({
|
|
30831
30985
|
"src/rules/kotlin/println-debug.ts"() {
|
|
30832
30986
|
"use strict";
|
|
30833
30987
|
init_rule();
|
|
30834
|
-
|
|
30988
|
+
PRINTLN_REGEX2 = /^\s*println\s*\(/gm;
|
|
30835
30989
|
DEFAULT_THRESHOLD = 1;
|
|
30836
30990
|
kotlinPrintlnDebugRule = createRule({
|
|
30837
30991
|
id: "kotlin/println-debug",
|
|
@@ -30846,11 +31000,11 @@ var init_println_debug = __esm({
|
|
|
30846
31000
|
const issues = [];
|
|
30847
31001
|
const source = facts.v2?._source;
|
|
30848
31002
|
if (!source) return issues;
|
|
30849
|
-
if (!/\.
|
|
31003
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30850
31004
|
const matches = [];
|
|
30851
31005
|
let m;
|
|
30852
|
-
|
|
30853
|
-
while ((m =
|
|
31006
|
+
PRINTLN_REGEX2.lastIndex = 0;
|
|
31007
|
+
while ((m = PRINTLN_REGEX2.exec(source)) !== null) {
|
|
30854
31008
|
matches.push(m.index);
|
|
30855
31009
|
}
|
|
30856
31010
|
if (matches.length <= context.threshold) return issues;
|
|
@@ -30875,6 +31029,95 @@ var init_println_debug = __esm({
|
|
|
30875
31029
|
}
|
|
30876
31030
|
});
|
|
30877
31031
|
|
|
31032
|
+
// src/rules/kotlin/runblocking-misuse.ts
|
|
31033
|
+
var RUN_BLOCKING_REGEX, kotlinRunBlockingMisuseRule;
|
|
31034
|
+
var init_runblocking_misuse = __esm({
|
|
31035
|
+
"src/rules/kotlin/runblocking-misuse.ts"() {
|
|
31036
|
+
"use strict";
|
|
31037
|
+
init_rule();
|
|
31038
|
+
RUN_BLOCKING_REGEX = /\brunBlocking\s*[({]/g;
|
|
31039
|
+
kotlinRunBlockingMisuseRule = createRule({
|
|
31040
|
+
id: "kotlin/runblocking-misuse",
|
|
31041
|
+
category: "perf",
|
|
31042
|
+
severity: "medium",
|
|
31043
|
+
aiSpecific: false,
|
|
31044
|
+
description: "runBlocking { ... } \u2014 blocks the calling thread; use coroutineScope {} or call suspend fun directly",
|
|
31045
|
+
create(_context) {
|
|
31046
|
+
return {};
|
|
31047
|
+
},
|
|
31048
|
+
analyze(_context, facts) {
|
|
31049
|
+
const issues = [];
|
|
31050
|
+
const source = facts.v2?._source;
|
|
31051
|
+
if (!source) return issues;
|
|
31052
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31053
|
+
if (/\bfun\s+main\s*\(/.test(source)) return issues;
|
|
31054
|
+
let m;
|
|
31055
|
+
RUN_BLOCKING_REGEX.lastIndex = 0;
|
|
31056
|
+
while ((m = RUN_BLOCKING_REGEX.exec(source)) !== null) {
|
|
31057
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
31058
|
+
issues.push({
|
|
31059
|
+
ruleId: "kotlin/runblocking-misuse",
|
|
31060
|
+
category: "perf",
|
|
31061
|
+
severity: "medium",
|
|
31062
|
+
aiSpecific: false,
|
|
31063
|
+
message: `runBlocking { ... } at line ${line}`,
|
|
31064
|
+
line,
|
|
31065
|
+
column: 1,
|
|
31066
|
+
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.'
|
|
31067
|
+
});
|
|
31068
|
+
}
|
|
31069
|
+
return issues;
|
|
31070
|
+
}
|
|
31071
|
+
});
|
|
31072
|
+
}
|
|
31073
|
+
});
|
|
31074
|
+
|
|
31075
|
+
// src/rules/kotlin/sql-string-concat.ts
|
|
31076
|
+
var SQL_KEYWORD_REGEX, UNSAFE_REGEX, SAFE_REGEX, kotlinSqlStringConcatRule;
|
|
31077
|
+
var init_sql_string_concat = __esm({
|
|
31078
|
+
"src/rules/kotlin/sql-string-concat.ts"() {
|
|
31079
|
+
"use strict";
|
|
31080
|
+
init_rule();
|
|
31081
|
+
SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
31082
|
+
UNSAFE_REGEX = /(?:\+|\$\{)/;
|
|
31083
|
+
SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
|
|
31084
|
+
kotlinSqlStringConcatRule = createRule({
|
|
31085
|
+
id: "kotlin/sql-string-concat",
|
|
31086
|
+
category: "security",
|
|
31087
|
+
severity: "high",
|
|
31088
|
+
aiSpecific: false,
|
|
31089
|
+
description: "SQL query built via string concat / template \u2014 use PreparedStatement or setParameter()",
|
|
31090
|
+
create(_context) {
|
|
31091
|
+
return {};
|
|
31092
|
+
},
|
|
31093
|
+
analyze(_context, facts) {
|
|
31094
|
+
const issues = [];
|
|
31095
|
+
const source = facts.v2?._source;
|
|
31096
|
+
if (!source) return issues;
|
|
31097
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
31098
|
+
const lines = source.split("\n");
|
|
31099
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31100
|
+
const line = lines[i];
|
|
31101
|
+
if (!SQL_KEYWORD_REGEX.test(line)) continue;
|
|
31102
|
+
if (!UNSAFE_REGEX.test(line)) continue;
|
|
31103
|
+
if (SAFE_REGEX.test(line)) continue;
|
|
31104
|
+
issues.push({
|
|
31105
|
+
ruleId: "kotlin/sql-string-concat",
|
|
31106
|
+
category: "security",
|
|
31107
|
+
severity: "high",
|
|
31108
|
+
aiSpecific: false,
|
|
31109
|
+
message: `SQL query built via string concat/template at line ${i + 1}`,
|
|
31110
|
+
line: i + 1,
|
|
31111
|
+
column: 1,
|
|
31112
|
+
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).'
|
|
31113
|
+
});
|
|
31114
|
+
}
|
|
31115
|
+
return issues;
|
|
31116
|
+
}
|
|
31117
|
+
});
|
|
31118
|
+
}
|
|
31119
|
+
});
|
|
31120
|
+
|
|
30878
31121
|
// src/rules/kotlin/string-concat-loop.ts
|
|
30879
31122
|
var STRING_CONCAT_REGEX, kotlinStringConcatLoopRule;
|
|
30880
31123
|
var init_string_concat_loop = __esm({
|
|
@@ -30895,7 +31138,7 @@ var init_string_concat_loop = __esm({
|
|
|
30895
31138
|
const issues = [];
|
|
30896
31139
|
const source = facts.v2?._source;
|
|
30897
31140
|
if (!source) return issues;
|
|
30898
|
-
if (!/\.
|
|
31141
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
30899
31142
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
30900
31143
|
let m;
|
|
30901
31144
|
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
@@ -36117,6 +36360,15 @@ function parseSource(source, filePath) {
|
|
|
36117
36360
|
case "go":
|
|
36118
36361
|
case "rs":
|
|
36119
36362
|
case "java":
|
|
36363
|
+
// v0.28.0: Kotlin files get the same parseBlankModule path as
|
|
36364
|
+
// Java (v0.24.5). All 5 `kotlin/*` rules are regex-based and
|
|
36365
|
+
// gate themselves on `/\.kts?$/i.test(filePath)` inside their
|
|
36366
|
+
// `analyze()`. The tree-sitter Kotlin integration is a larger
|
|
36367
|
+
// lift; the v0.27.0 methodology paper confirmed era-confounding
|
|
36368
|
+
// is the dominant signal anyway, so a regex-only Kotlin pass is
|
|
36369
|
+
// sufficient for the v9 calibration goal.
|
|
36370
|
+
case "kt":
|
|
36371
|
+
case "kts":
|
|
36120
36372
|
return parseBlankModule(source);
|
|
36121
36373
|
default:
|
|
36122
36374
|
return parseWithSwc(source, filePath);
|
|
@@ -40450,7 +40702,7 @@ var init_fatal_error_thrown = __esm({
|
|
|
40450
40702
|
|
|
40451
40703
|
// src/rules/swift/force-unwrap.ts
|
|
40452
40704
|
var AS_FORCE_REGEX, ACCESS_FORCE_REGEX, TRY_FORCE_REGEX, swiftForceUnwrapRule;
|
|
40453
|
-
var
|
|
40705
|
+
var init_force_unwrap2 = __esm({
|
|
40454
40706
|
"src/rules/swift/force-unwrap.ts"() {
|
|
40455
40707
|
"use strict";
|
|
40456
40708
|
init_rule();
|
|
@@ -46311,8 +46563,13 @@ var init_builtins = __esm({
|
|
|
46311
46563
|
init_struct_tag_inconsistency();
|
|
46312
46564
|
init_coroutine_global_scope();
|
|
46313
46565
|
init_data_class_defaults_overuse();
|
|
46566
|
+
init_force_unwrap();
|
|
46567
|
+
init_hardcoded_credential();
|
|
46314
46568
|
init_object_singleton_misuse();
|
|
46569
|
+
init_println_as_log();
|
|
46315
46570
|
init_println_debug();
|
|
46571
|
+
init_runblocking_misuse();
|
|
46572
|
+
init_sql_string_concat();
|
|
46316
46573
|
init_string_concat_loop();
|
|
46317
46574
|
init_gap_monopoly();
|
|
46318
46575
|
init_math_element_uniformity();
|
|
@@ -46353,7 +46610,7 @@ var init_builtins = __esm({
|
|
|
46353
46610
|
init_target_blank_no_noopener();
|
|
46354
46611
|
init_unsafe_html_render();
|
|
46355
46612
|
init_fatal_error_thrown();
|
|
46356
|
-
|
|
46613
|
+
init_force_unwrap2();
|
|
46357
46614
|
init_implicitly_unwrapped_optional();
|
|
46358
46615
|
init_print_debug();
|
|
46359
46616
|
init_strong_self_capture();
|
|
@@ -46440,8 +46697,13 @@ var init_builtins = __esm({
|
|
|
46440
46697
|
goStructTagInconsistencyRule,
|
|
46441
46698
|
kotlinCoroutineGlobalScopeRule,
|
|
46442
46699
|
kotlinDataClassDefaultsOveruseRule,
|
|
46700
|
+
kotlinForceUnwrapRule,
|
|
46701
|
+
kotlinHardcodedCredentialRule,
|
|
46443
46702
|
kotlinObjectSingletonMisuseRule,
|
|
46703
|
+
kotlinPrintlnAsLogRule,
|
|
46444
46704
|
kotlinPrintlnDebugRule,
|
|
46705
|
+
kotlinRunBlockingMisuseRule,
|
|
46706
|
+
kotlinSqlStringConcatRule,
|
|
46445
46707
|
kotlinStringConcatLoopRule,
|
|
46446
46708
|
gapMonopolyRule,
|
|
46447
46709
|
mathElementUniformityRule,
|
|
@@ -50851,13 +51113,13 @@ var init_signal_strength = __esm({
|
|
|
50851
51113
|
defaultOff: true
|
|
50852
51114
|
},
|
|
50853
51115
|
"kotlin/data-class-defaults-overuse": {
|
|
50854
|
-
recall:
|
|
50855
|
-
fpRate:
|
|
50856
|
-
ratio:
|
|
50857
|
-
precision: 0,
|
|
50858
|
-
lastCalibratedAt: "2026-07-
|
|
51116
|
+
recall: 469e-5,
|
|
51117
|
+
fpRate: 222e-5,
|
|
51118
|
+
ratio: 2.11,
|
|
51119
|
+
precision: 0.1429,
|
|
51120
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50859
51121
|
verdict: "DORMANT",
|
|
50860
|
-
_calibrationNote: "v0.
|
|
51122
|
+
_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.",
|
|
50861
51123
|
aiSpecific: true,
|
|
50862
51124
|
_v7Verdict: "DORMANT",
|
|
50863
51125
|
_v7Lift: 1,
|
|
@@ -50866,16 +51128,21 @@ var init_signal_strength = __esm({
|
|
|
50866
51128
|
_v7Precision: 0,
|
|
50867
51129
|
_v8Verdict: "DORMANT",
|
|
50868
51130
|
_v8Lift: 1,
|
|
51131
|
+
_v9Verdict: "DORMANT",
|
|
51132
|
+
_v9Lift: 2.11,
|
|
51133
|
+
_v9Recall: 469e-5,
|
|
51134
|
+
_v9FpRate: 222e-5,
|
|
51135
|
+
_v9Precision: 0.1429,
|
|
50869
51136
|
defaultOff: true
|
|
50870
51137
|
},
|
|
50871
51138
|
"kotlin/coroutine-global-scope": {
|
|
50872
|
-
recall:
|
|
50873
|
-
fpRate: 0,
|
|
50874
|
-
ratio:
|
|
50875
|
-
precision:
|
|
50876
|
-
lastCalibratedAt: "2026-07-
|
|
51139
|
+
recall: 469e-5,
|
|
51140
|
+
fpRate: 0.05004,
|
|
51141
|
+
ratio: 0.09,
|
|
51142
|
+
precision: 735e-5,
|
|
51143
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50877
51144
|
verdict: "DORMANT",
|
|
50878
|
-
_calibrationNote: "v0.
|
|
51145
|
+
_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.",
|
|
50879
51146
|
aiSpecific: true,
|
|
50880
51147
|
_v7Verdict: "DORMANT",
|
|
50881
51148
|
_v7Lift: 1,
|
|
@@ -50884,16 +51151,21 @@ var init_signal_strength = __esm({
|
|
|
50884
51151
|
_v7Precision: 0,
|
|
50885
51152
|
_v8Verdict: "DORMANT",
|
|
50886
51153
|
_v8Lift: 1,
|
|
51154
|
+
_v9Verdict: "DORMANT",
|
|
51155
|
+
_v9Lift: 0.09,
|
|
51156
|
+
_v9Recall: 469e-5,
|
|
51157
|
+
_v9FpRate: 0.05004,
|
|
51158
|
+
_v9Precision: 735e-5,
|
|
50887
51159
|
defaultOff: true
|
|
50888
51160
|
},
|
|
50889
51161
|
"kotlin/println-debug": {
|
|
50890
|
-
recall: 0,
|
|
50891
|
-
fpRate: 0,
|
|
50892
|
-
ratio:
|
|
50893
|
-
precision: 0,
|
|
50894
|
-
lastCalibratedAt: "2026-07-
|
|
51162
|
+
recall: 0.07042,
|
|
51163
|
+
fpRate: 0.11379,
|
|
51164
|
+
ratio: 0.62,
|
|
51165
|
+
precision: 0.04659,
|
|
51166
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50895
51167
|
verdict: "DORMANT",
|
|
50896
|
-
_calibrationNote: "v0.
|
|
51168
|
+
_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.",
|
|
50897
51169
|
aiSpecific: true,
|
|
50898
51170
|
_v7Verdict: "DORMANT",
|
|
50899
51171
|
_v7Lift: 1,
|
|
@@ -50902,16 +51174,21 @@ var init_signal_strength = __esm({
|
|
|
50902
51174
|
_v7Precision: 0,
|
|
50903
51175
|
_v8Verdict: "DORMANT",
|
|
50904
51176
|
_v8Lift: 1,
|
|
51177
|
+
_v9Verdict: "DORMANT",
|
|
51178
|
+
_v9Lift: 0.62,
|
|
51179
|
+
_v9Recall: 0.07042,
|
|
51180
|
+
_v9FpRate: 0.11379,
|
|
51181
|
+
_v9Precision: 0.04659,
|
|
50905
51182
|
defaultOff: true
|
|
50906
51183
|
},
|
|
50907
51184
|
"kotlin/object-singleton-misuse": {
|
|
50908
|
-
recall:
|
|
50909
|
-
fpRate:
|
|
50910
|
-
ratio: 1,
|
|
50911
|
-
precision: 0,
|
|
50912
|
-
lastCalibratedAt: "2026-07-
|
|
51185
|
+
recall: 469e-5,
|
|
51186
|
+
fpRate: 371e-5,
|
|
51187
|
+
ratio: 1.27,
|
|
51188
|
+
precision: 0.09091,
|
|
51189
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50913
51190
|
verdict: "DORMANT",
|
|
50914
|
-
_calibrationNote: "v0.
|
|
51191
|
+
_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).",
|
|
50915
51192
|
aiSpecific: true,
|
|
50916
51193
|
_v7Verdict: "DORMANT",
|
|
50917
51194
|
_v7Lift: 1,
|
|
@@ -50920,16 +51197,21 @@ var init_signal_strength = __esm({
|
|
|
50920
51197
|
_v7Precision: 0,
|
|
50921
51198
|
_v8Verdict: "DORMANT",
|
|
50922
51199
|
_v8Lift: 1,
|
|
51200
|
+
_v9Verdict: "DORMANT",
|
|
51201
|
+
_v9Lift: 1.27,
|
|
51202
|
+
_v9Recall: 469e-5,
|
|
51203
|
+
_v9FpRate: 371e-5,
|
|
51204
|
+
_v9Precision: 0.09091,
|
|
50923
51205
|
defaultOff: true
|
|
50924
51206
|
},
|
|
50925
51207
|
"kotlin/string-concat-loop": {
|
|
50926
|
-
recall:
|
|
50927
|
-
fpRate:
|
|
50928
|
-
ratio: 1,
|
|
50929
|
-
precision: 0,
|
|
50930
|
-
lastCalibratedAt: "2026-07-
|
|
51208
|
+
recall: 939e-5,
|
|
51209
|
+
fpRate: 519e-5,
|
|
51210
|
+
ratio: 1.81,
|
|
51211
|
+
precision: 0.125,
|
|
51212
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
50931
51213
|
verdict: "DORMANT",
|
|
50932
|
-
_calibrationNote: "v0.
|
|
51214
|
+
_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.",
|
|
50933
51215
|
aiSpecific: true,
|
|
50934
51216
|
_v7Verdict: "DORMANT",
|
|
50935
51217
|
_v7Lift: 1,
|
|
@@ -50938,6 +51220,91 @@ var init_signal_strength = __esm({
|
|
|
50938
51220
|
_v7Precision: 0,
|
|
50939
51221
|
_v8Verdict: "DORMANT",
|
|
50940
51222
|
_v8Lift: 1,
|
|
51223
|
+
_v9Verdict: "DORMANT",
|
|
51224
|
+
_v9Lift: 1.81,
|
|
51225
|
+
_v9Recall: 939e-5,
|
|
51226
|
+
_v9FpRate: 519e-5,
|
|
51227
|
+
_v9Precision: 0.125,
|
|
51228
|
+
defaultOff: true
|
|
51229
|
+
},
|
|
51230
|
+
"kotlin/sql-string-concat": {
|
|
51231
|
+
recall: 469e-5,
|
|
51232
|
+
fpRate: 63e-4,
|
|
51233
|
+
ratio: 0.75,
|
|
51234
|
+
precision: 0.0556,
|
|
51235
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51236
|
+
verdict: "DORMANT",
|
|
51237
|
+
_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.",
|
|
51238
|
+
aiSpecific: false,
|
|
51239
|
+
_v9Verdict: "DORMANT",
|
|
51240
|
+
_v9Lift: 0.75,
|
|
51241
|
+
_v9Recall: 469e-5,
|
|
51242
|
+
_v9FpRate: 63e-4,
|
|
51243
|
+
_v9Precision: 0.0556,
|
|
51244
|
+
defaultOff: true
|
|
51245
|
+
},
|
|
51246
|
+
"kotlin/hardcoded-credential": {
|
|
51247
|
+
recall: 0,
|
|
51248
|
+
fpRate: 0,
|
|
51249
|
+
ratio: 0,
|
|
51250
|
+
precision: 0,
|
|
51251
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51252
|
+
verdict: "DORMANT",
|
|
51253
|
+
_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.",
|
|
51254
|
+
aiSpecific: false,
|
|
51255
|
+
_v9Verdict: "DORMANT",
|
|
51256
|
+
_v9Lift: 0,
|
|
51257
|
+
_v9Recall: 0,
|
|
51258
|
+
_v9FpRate: 0,
|
|
51259
|
+
_v9Precision: 0,
|
|
51260
|
+
defaultOff: true
|
|
51261
|
+
},
|
|
51262
|
+
"kotlin/runblocking-misuse": {
|
|
51263
|
+
recall: 0.10798,
|
|
51264
|
+
fpRate: 0.21534,
|
|
51265
|
+
ratio: 0.5,
|
|
51266
|
+
precision: 0.0381,
|
|
51267
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51268
|
+
verdict: "DORMANT",
|
|
51269
|
+
_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.",
|
|
51270
|
+
aiSpecific: false,
|
|
51271
|
+
_v9Verdict: "DORMANT",
|
|
51272
|
+
_v9Lift: 0.5,
|
|
51273
|
+
_v9Recall: 0.10798,
|
|
51274
|
+
_v9FpRate: 0.21534,
|
|
51275
|
+
_v9Precision: 0.0381,
|
|
51276
|
+
defaultOff: true
|
|
51277
|
+
},
|
|
51278
|
+
"kotlin/println-as-log": {
|
|
51279
|
+
recall: 0.08451,
|
|
51280
|
+
fpRate: 0.04596,
|
|
51281
|
+
ratio: 1.84,
|
|
51282
|
+
precision: 0.1268,
|
|
51283
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51284
|
+
verdict: "OK",
|
|
51285
|
+
_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).",
|
|
51286
|
+
aiSpecific: false,
|
|
51287
|
+
_v9Verdict: "OK",
|
|
51288
|
+
_v9Lift: 1.84,
|
|
51289
|
+
_v9Recall: 0.08451,
|
|
51290
|
+
_v9FpRate: 0.04596,
|
|
51291
|
+
_v9Precision: 0.1268,
|
|
51292
|
+
defaultOff: true
|
|
51293
|
+
},
|
|
51294
|
+
"kotlin/force-unwrap": {
|
|
51295
|
+
recall: 0.11737,
|
|
51296
|
+
fpRate: 0.33284,
|
|
51297
|
+
ratio: 0.35,
|
|
51298
|
+
precision: 0.0271,
|
|
51299
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51300
|
+
verdict: "DORMANT",
|
|
51301
|
+
_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.",
|
|
51302
|
+
aiSpecific: false,
|
|
51303
|
+
_v9Verdict: "DORMANT",
|
|
51304
|
+
_v9Lift: 0.35,
|
|
51305
|
+
_v9Recall: 0.11737,
|
|
51306
|
+
_v9FpRate: 0.33284,
|
|
51307
|
+
_v9Precision: 0.0271,
|
|
50941
51308
|
defaultOff: true
|
|
50942
51309
|
},
|
|
50943
51310
|
"swift/force-unwrap": {
|
|
@@ -51198,8 +51565,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
|
51198
51565
|
const ext = (0, import_node_path12.extname)(filePath).toLowerCase();
|
|
51199
51566
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
51200
51567
|
".swift",
|
|
51201
|
-
".kt",
|
|
51202
|
-
".kts",
|
|
51203
51568
|
".dart",
|
|
51204
51569
|
".cpp",
|
|
51205
51570
|
".cc",
|
|
@@ -59154,6 +59519,12 @@ var RULE_HINTS = {
|
|
|
59154
59519
|
"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.)",
|
|
59155
59520
|
"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.)",
|
|
59156
59521
|
"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.)",
|
|
59522
|
+
// v0.29.0 — non-AI Kotlin rules (DORMANT until larger pos arm)
|
|
59523
|
+
"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.)",
|
|
59524
|
+
"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.)",
|
|
59525
|
+
"kotlin/runblocking-misuse": "Use coroutineScope { } for structured concurrency, or call the suspend function directly. runBlocking blocks the calling thread and is rarely correct outside main(). (v0.29.0 \u2014 DORMANT.)",
|
|
59526
|
+
"kotlin/println-as-log": "Use slf4j (JVM), android.util.Log (Android), Timber, kermit, or kotlin-logging. println has no log level, no timestamp, no correlation ID. (v0.29.0 \u2014 OK; first positive-signal rule in v9 history, ratio 1.84.)",
|
|
59527
|
+
"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.)",
|
|
59157
59528
|
// v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
|
|
59158
59529
|
"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.)",
|
|
59159
59530
|
"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.)",
|