slopbrick 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/worker.cjs +589 -6
- package/dist/engine/worker.js +589 -6
- package/dist/index.cjs +686 -11
- package/dist/index.js +686 -11
- package/package.json +1 -1
package/dist/engine/worker.js
CHANGED
|
@@ -36971,6 +36971,211 @@ var goStructTagInconsistencyRule = createRule({
|
|
|
36971
36971
|
}
|
|
36972
36972
|
});
|
|
36973
36973
|
|
|
36974
|
+
// src/rules/java/command-injection.ts
|
|
36975
|
+
var COMMAND_INVOCATION_REGEX = /\b(?:Runtime\.exec|Runtime\.getRuntime\(\)\.exec|ProcessBuilder)\s*\(/;
|
|
36976
|
+
var STRING_CONCAT_REGEX = /["'][^"']*["']\s*\+/;
|
|
36977
|
+
var javaCommandInjectionRule = createRule({
|
|
36978
|
+
id: "java/command-injection",
|
|
36979
|
+
category: "security",
|
|
36980
|
+
severity: "high",
|
|
36981
|
+
aiSpecific: false,
|
|
36982
|
+
description: "Runtime.exec() or ProcessBuilder with string concat \u2014 use List<String> args + validation",
|
|
36983
|
+
create(_context) {
|
|
36984
|
+
return {};
|
|
36985
|
+
},
|
|
36986
|
+
analyze(_context, facts) {
|
|
36987
|
+
const issues = [];
|
|
36988
|
+
const source = facts.v2?._source;
|
|
36989
|
+
if (!source) return issues;
|
|
36990
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
36991
|
+
const lines = source.split("\n");
|
|
36992
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36993
|
+
const line = lines[i];
|
|
36994
|
+
if (!COMMAND_INVOCATION_REGEX.test(line)) continue;
|
|
36995
|
+
if (!STRING_CONCAT_REGEX.test(line)) continue;
|
|
36996
|
+
issues.push({
|
|
36997
|
+
ruleId: "java/command-injection",
|
|
36998
|
+
category: "security",
|
|
36999
|
+
severity: "high",
|
|
37000
|
+
aiSpecific: false,
|
|
37001
|
+
message: `Command invocation with string concat at line ${i + 1}`,
|
|
37002
|
+
line: i + 1,
|
|
37003
|
+
column: 1,
|
|
37004
|
+
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)."
|
|
37005
|
+
});
|
|
37006
|
+
}
|
|
37007
|
+
return issues;
|
|
37008
|
+
}
|
|
37009
|
+
});
|
|
37010
|
+
|
|
37011
|
+
// src/rules/java/hardcoded-credential.ts
|
|
37012
|
+
var KEY_REGEX = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
37013
|
+
var VALUE_REGEX = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
37014
|
+
var FALSE_POSITIVES = /* @__PURE__ */ new Set([
|
|
37015
|
+
"passwordless",
|
|
37016
|
+
"tokenize",
|
|
37017
|
+
"tokenizer",
|
|
37018
|
+
"authorizationrequired",
|
|
37019
|
+
"authenticated",
|
|
37020
|
+
"authenticatortoken",
|
|
37021
|
+
"authtoken",
|
|
37022
|
+
"placeholder",
|
|
37023
|
+
"changeme"
|
|
37024
|
+
]);
|
|
37025
|
+
var javaHardcodedCredentialRule = createRule({
|
|
37026
|
+
id: "java/hardcoded-credential",
|
|
37027
|
+
category: "security",
|
|
37028
|
+
severity: "high",
|
|
37029
|
+
aiSpecific: false,
|
|
37030
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
37031
|
+
create(_context) {
|
|
37032
|
+
return {};
|
|
37033
|
+
},
|
|
37034
|
+
analyze(_context, facts) {
|
|
37035
|
+
const issues = [];
|
|
37036
|
+
const source = facts.v2?._source;
|
|
37037
|
+
if (!source) return issues;
|
|
37038
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37039
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
37040
|
+
const lines = source.split("\n");
|
|
37041
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37042
|
+
const line = lines[i];
|
|
37043
|
+
if (!KEY_REGEX.test(line)) continue;
|
|
37044
|
+
const valueMatch = VALUE_REGEX.exec(line);
|
|
37045
|
+
if (!valueMatch) continue;
|
|
37046
|
+
const value = valueMatch[1];
|
|
37047
|
+
if (FALSE_POSITIVES.has(value.toLowerCase())) continue;
|
|
37048
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
37049
|
+
if (value.startsWith("$")) continue;
|
|
37050
|
+
issues.push({
|
|
37051
|
+
ruleId: "java/hardcoded-credential",
|
|
37052
|
+
category: "security",
|
|
37053
|
+
severity: "high",
|
|
37054
|
+
aiSpecific: false,
|
|
37055
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
37056
|
+
line: i + 1,
|
|
37057
|
+
column: 1,
|
|
37058
|
+
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)."
|
|
37059
|
+
});
|
|
37060
|
+
}
|
|
37061
|
+
return issues;
|
|
37062
|
+
}
|
|
37063
|
+
});
|
|
37064
|
+
|
|
37065
|
+
// src/rules/java/sql-string-concat.ts
|
|
37066
|
+
var SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
37067
|
+
var UNSAFE_REGEX = /\+/;
|
|
37068
|
+
var SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|createQuery.*:.*\b(?:set|bind)|:name\b|\?\s*,)/;
|
|
37069
|
+
var javaSqlStringConcatRule = createRule({
|
|
37070
|
+
id: "java/sql-string-concat",
|
|
37071
|
+
category: "security",
|
|
37072
|
+
severity: "high",
|
|
37073
|
+
aiSpecific: false,
|
|
37074
|
+
description: "SQL query built via string concat \u2014 use PreparedStatement or setParameter()",
|
|
37075
|
+
create(_context) {
|
|
37076
|
+
return {};
|
|
37077
|
+
},
|
|
37078
|
+
analyze(_context, facts) {
|
|
37079
|
+
const issues = [];
|
|
37080
|
+
const source = facts.v2?._source;
|
|
37081
|
+
if (!source) return issues;
|
|
37082
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37083
|
+
const lines = source.split("\n");
|
|
37084
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37085
|
+
const line = lines[i];
|
|
37086
|
+
if (!SQL_KEYWORD_REGEX.test(line)) continue;
|
|
37087
|
+
if (!UNSAFE_REGEX.test(line)) continue;
|
|
37088
|
+
if (SAFE_REGEX.test(line)) continue;
|
|
37089
|
+
issues.push({
|
|
37090
|
+
ruleId: "java/sql-string-concat",
|
|
37091
|
+
category: "security",
|
|
37092
|
+
severity: "high",
|
|
37093
|
+
aiSpecific: false,
|
|
37094
|
+
message: `SQL query built via string concat at line ${i + 1}`,
|
|
37095
|
+
line: i + 1,
|
|
37096
|
+
column: 1,
|
|
37097
|
+
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).'
|
|
37098
|
+
});
|
|
37099
|
+
}
|
|
37100
|
+
return issues;
|
|
37101
|
+
}
|
|
37102
|
+
});
|
|
37103
|
+
|
|
37104
|
+
// src/rules/java/system-out-println.ts
|
|
37105
|
+
var SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
|
|
37106
|
+
var 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)/;
|
|
37107
|
+
var javaSystemOutPrintlnRule = createRule({
|
|
37108
|
+
id: "java/system-out-println",
|
|
37109
|
+
category: "logic",
|
|
37110
|
+
severity: "low",
|
|
37111
|
+
aiSpecific: false,
|
|
37112
|
+
description: "System.out.println() used for logging \u2014 use SLF4J, Log4j2, or java.util.logging",
|
|
37113
|
+
create(_context) {
|
|
37114
|
+
return {};
|
|
37115
|
+
},
|
|
37116
|
+
analyze(_context, facts) {
|
|
37117
|
+
const issues = [];
|
|
37118
|
+
const source = facts.v2?._source;
|
|
37119
|
+
if (!source) return issues;
|
|
37120
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37121
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
37122
|
+
if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
|
|
37123
|
+
let m;
|
|
37124
|
+
SYSTEM_OUT_REGEX.lastIndex = 0;
|
|
37125
|
+
while ((m = SYSTEM_OUT_REGEX.exec(source)) !== null) {
|
|
37126
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37127
|
+
issues.push({
|
|
37128
|
+
ruleId: "java/system-out-println",
|
|
37129
|
+
category: "logic",
|
|
37130
|
+
severity: "low",
|
|
37131
|
+
aiSpecific: false,
|
|
37132
|
+
message: `System.out.println() at line ${line}`,
|
|
37133
|
+
line,
|
|
37134
|
+
column: 1,
|
|
37135
|
+
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."
|
|
37136
|
+
});
|
|
37137
|
+
}
|
|
37138
|
+
return issues;
|
|
37139
|
+
}
|
|
37140
|
+
});
|
|
37141
|
+
|
|
37142
|
+
// src/rules/java/thread-sleep-in-loop.ts
|
|
37143
|
+
var THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
|
|
37144
|
+
var javaThreadSleepInLoopRule = createRule({
|
|
37145
|
+
id: "java/thread-sleep-in-loop",
|
|
37146
|
+
category: "perf",
|
|
37147
|
+
severity: "medium",
|
|
37148
|
+
aiSpecific: false,
|
|
37149
|
+
description: "Thread.sleep() in a loop \u2014 use ScheduledExecutorService or BlockingQueue",
|
|
37150
|
+
create(_context) {
|
|
37151
|
+
return {};
|
|
37152
|
+
},
|
|
37153
|
+
analyze(_context, facts) {
|
|
37154
|
+
const issues = [];
|
|
37155
|
+
const source = facts.v2?._source;
|
|
37156
|
+
if (!source) return issues;
|
|
37157
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37158
|
+
if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
|
|
37159
|
+
if (!/\b(?:for|while|do)\b/.test(source)) return issues;
|
|
37160
|
+
let m;
|
|
37161
|
+
THREAD_SLEEP_REGEX.lastIndex = 0;
|
|
37162
|
+
while ((m = THREAD_SLEEP_REGEX.exec(source)) !== null) {
|
|
37163
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37164
|
+
issues.push({
|
|
37165
|
+
ruleId: "java/thread-sleep-in-loop",
|
|
37166
|
+
category: "perf",
|
|
37167
|
+
severity: "medium",
|
|
37168
|
+
aiSpecific: false,
|
|
37169
|
+
message: `Thread.sleep() at line ${line}`,
|
|
37170
|
+
line,
|
|
37171
|
+
column: 1,
|
|
37172
|
+
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.'
|
|
37173
|
+
});
|
|
37174
|
+
}
|
|
37175
|
+
return issues;
|
|
37176
|
+
}
|
|
37177
|
+
});
|
|
37178
|
+
|
|
36974
37179
|
// src/rules/kotlin/coroutine-global-scope.ts
|
|
36975
37180
|
var GLOBAL_SCOPE_REGEX = /GlobalScope\s*\.\s*(?:launch|async|runBlocking)\s*[({]/g;
|
|
36976
37181
|
var kotlinCoroutineGlobalScopeRule = createRule({
|
|
@@ -37063,6 +37268,101 @@ var kotlinDataClassDefaultsOveruseRule = createRule({
|
|
|
37063
37268
|
}
|
|
37064
37269
|
});
|
|
37065
37270
|
|
|
37271
|
+
// src/rules/kotlin/force-unwrap.ts
|
|
37272
|
+
var FORCE_UNWRAP_REGEX = /!!(?=\s*[.\)}\};,\n\[])/g;
|
|
37273
|
+
var kotlinForceUnwrapRule = createRule({
|
|
37274
|
+
id: "kotlin/force-unwrap",
|
|
37275
|
+
category: "logic",
|
|
37276
|
+
severity: "medium",
|
|
37277
|
+
aiSpecific: false,
|
|
37278
|
+
description: "!! force-unwrap \u2014 use ?. (safe call) or a proper null check",
|
|
37279
|
+
create(_context) {
|
|
37280
|
+
return {};
|
|
37281
|
+
},
|
|
37282
|
+
analyze(_context, facts) {
|
|
37283
|
+
const issues = [];
|
|
37284
|
+
const source = facts.v2?._source;
|
|
37285
|
+
if (!source) return issues;
|
|
37286
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37287
|
+
let m;
|
|
37288
|
+
FORCE_UNWRAP_REGEX.lastIndex = 0;
|
|
37289
|
+
while ((m = FORCE_UNWRAP_REGEX.exec(source)) !== null) {
|
|
37290
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37291
|
+
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
|
37292
|
+
const lineText = source.slice(lineStart, m.index);
|
|
37293
|
+
const quoteCount = (lineText.match(/"/g) || []).length;
|
|
37294
|
+
if (quoteCount % 2 === 1) continue;
|
|
37295
|
+
issues.push({
|
|
37296
|
+
ruleId: "kotlin/force-unwrap",
|
|
37297
|
+
category: "logic",
|
|
37298
|
+
severity: "medium",
|
|
37299
|
+
aiSpecific: false,
|
|
37300
|
+
message: `!! force-unwrap at line ${line}`,
|
|
37301
|
+
line,
|
|
37302
|
+
column: m.index - lineStart + 1,
|
|
37303
|
+
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."
|
|
37304
|
+
});
|
|
37305
|
+
}
|
|
37306
|
+
return issues;
|
|
37307
|
+
}
|
|
37308
|
+
});
|
|
37309
|
+
|
|
37310
|
+
// src/rules/kotlin/hardcoded-credential.ts
|
|
37311
|
+
var KEY_REGEX2 = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
37312
|
+
var VALUE_REGEX2 = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
37313
|
+
var FALSE_POSITIVES2 = /* @__PURE__ */ new Set([
|
|
37314
|
+
"passwordless",
|
|
37315
|
+
"tokenize",
|
|
37316
|
+
"tokenizer",
|
|
37317
|
+
"authorizationrequired",
|
|
37318
|
+
"authenticated",
|
|
37319
|
+
"authenticatortoken",
|
|
37320
|
+
"authtoken",
|
|
37321
|
+
"placeholder",
|
|
37322
|
+
// common in test fixtures
|
|
37323
|
+
"changeme"
|
|
37324
|
+
// placeholder, but still suspicious — kept for review
|
|
37325
|
+
]);
|
|
37326
|
+
var kotlinHardcodedCredentialRule = createRule({
|
|
37327
|
+
id: "kotlin/hardcoded-credential",
|
|
37328
|
+
category: "security",
|
|
37329
|
+
severity: "high",
|
|
37330
|
+
aiSpecific: false,
|
|
37331
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
37332
|
+
create(_context) {
|
|
37333
|
+
return {};
|
|
37334
|
+
},
|
|
37335
|
+
analyze(_context, facts) {
|
|
37336
|
+
const issues = [];
|
|
37337
|
+
const source = facts.v2?._source;
|
|
37338
|
+
if (!source) return issues;
|
|
37339
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37340
|
+
const lines = source.split("\n");
|
|
37341
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37342
|
+
const line = lines[i];
|
|
37343
|
+
if (!KEY_REGEX2.test(line)) continue;
|
|
37344
|
+
const valueMatch = VALUE_REGEX2.exec(line);
|
|
37345
|
+
if (!valueMatch) continue;
|
|
37346
|
+
const value = valueMatch[1];
|
|
37347
|
+
if (FALSE_POSITIVES2.has(value.toLowerCase())) continue;
|
|
37348
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
37349
|
+
if (value.startsWith("$")) continue;
|
|
37350
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
37351
|
+
issues.push({
|
|
37352
|
+
ruleId: "kotlin/hardcoded-credential",
|
|
37353
|
+
category: "security",
|
|
37354
|
+
severity: "high",
|
|
37355
|
+
aiSpecific: false,
|
|
37356
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
37357
|
+
line: i + 1,
|
|
37358
|
+
column: 1,
|
|
37359
|
+
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)."
|
|
37360
|
+
});
|
|
37361
|
+
}
|
|
37362
|
+
return issues;
|
|
37363
|
+
}
|
|
37364
|
+
});
|
|
37365
|
+
|
|
37066
37366
|
// src/rules/kotlin/object-singleton-misuse.ts
|
|
37067
37367
|
var OBJECT_DECL_REGEX = /\bobject\s+(?!\w*Companion\b)(\w+)\s*\{/g;
|
|
37068
37368
|
var kotlinObjectSingletonMisuseRule = createRule({
|
|
@@ -37113,8 +37413,46 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37113
37413
|
}
|
|
37114
37414
|
});
|
|
37115
37415
|
|
|
37416
|
+
// src/rules/kotlin/println-as-log.ts
|
|
37417
|
+
var PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
37418
|
+
var 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)/;
|
|
37419
|
+
var kotlinPrintlnAsLogRule = createRule({
|
|
37420
|
+
id: "kotlin/println-as-log",
|
|
37421
|
+
category: "logic",
|
|
37422
|
+
severity: "low",
|
|
37423
|
+
aiSpecific: false,
|
|
37424
|
+
description: "println() used for logging \u2014 use slf4j, kermit, or android.util.Log",
|
|
37425
|
+
create(_context) {
|
|
37426
|
+
return {};
|
|
37427
|
+
},
|
|
37428
|
+
analyze(_context, facts) {
|
|
37429
|
+
const issues = [];
|
|
37430
|
+
const source = facts.v2?._source;
|
|
37431
|
+
if (!source) return issues;
|
|
37432
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37433
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
37434
|
+
if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
|
|
37435
|
+
let m;
|
|
37436
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
37437
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
37438
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37439
|
+
issues.push({
|
|
37440
|
+
ruleId: "kotlin/println-as-log",
|
|
37441
|
+
category: "logic",
|
|
37442
|
+
severity: "low",
|
|
37443
|
+
aiSpecific: false,
|
|
37444
|
+
message: `println() as logger at line ${line}`,
|
|
37445
|
+
line,
|
|
37446
|
+
column: 1,
|
|
37447
|
+
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."
|
|
37448
|
+
});
|
|
37449
|
+
}
|
|
37450
|
+
return issues;
|
|
37451
|
+
}
|
|
37452
|
+
});
|
|
37453
|
+
|
|
37116
37454
|
// src/rules/kotlin/println-debug.ts
|
|
37117
|
-
var
|
|
37455
|
+
var PRINTLN_REGEX2 = /^\s*println\s*\(/gm;
|
|
37118
37456
|
var DEFAULT_THRESHOLD = 1;
|
|
37119
37457
|
var kotlinPrintlnDebugRule = createRule({
|
|
37120
37458
|
id: "kotlin/println-debug",
|
|
@@ -37132,8 +37470,8 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37132
37470
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37133
37471
|
const matches = [];
|
|
37134
37472
|
let m;
|
|
37135
|
-
|
|
37136
|
-
while ((m =
|
|
37473
|
+
PRINTLN_REGEX2.lastIndex = 0;
|
|
37474
|
+
while ((m = PRINTLN_REGEX2.exec(source)) !== null) {
|
|
37137
37475
|
matches.push(m.index);
|
|
37138
37476
|
}
|
|
37139
37477
|
if (matches.length <= context.threshold) return issues;
|
|
@@ -37156,8 +37494,83 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37156
37494
|
}
|
|
37157
37495
|
});
|
|
37158
37496
|
|
|
37497
|
+
// src/rules/kotlin/runblocking-misuse.ts
|
|
37498
|
+
var RUN_BLOCKING_REGEX = /\brunBlocking\s*[({]/g;
|
|
37499
|
+
var kotlinRunBlockingMisuseRule = createRule({
|
|
37500
|
+
id: "kotlin/runblocking-misuse",
|
|
37501
|
+
category: "perf",
|
|
37502
|
+
severity: "medium",
|
|
37503
|
+
aiSpecific: false,
|
|
37504
|
+
description: "runBlocking { ... } \u2014 blocks the calling thread; use coroutineScope {} or call suspend fun directly",
|
|
37505
|
+
create(_context) {
|
|
37506
|
+
return {};
|
|
37507
|
+
},
|
|
37508
|
+
analyze(_context, facts) {
|
|
37509
|
+
const issues = [];
|
|
37510
|
+
const source = facts.v2?._source;
|
|
37511
|
+
if (!source) return issues;
|
|
37512
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37513
|
+
if (/\bfun\s+main\s*\(/.test(source)) return issues;
|
|
37514
|
+
let m;
|
|
37515
|
+
RUN_BLOCKING_REGEX.lastIndex = 0;
|
|
37516
|
+
while ((m = RUN_BLOCKING_REGEX.exec(source)) !== null) {
|
|
37517
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37518
|
+
issues.push({
|
|
37519
|
+
ruleId: "kotlin/runblocking-misuse",
|
|
37520
|
+
category: "perf",
|
|
37521
|
+
severity: "medium",
|
|
37522
|
+
aiSpecific: false,
|
|
37523
|
+
message: `runBlocking { ... } at line ${line}`,
|
|
37524
|
+
line,
|
|
37525
|
+
column: 1,
|
|
37526
|
+
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.'
|
|
37527
|
+
});
|
|
37528
|
+
}
|
|
37529
|
+
return issues;
|
|
37530
|
+
}
|
|
37531
|
+
});
|
|
37532
|
+
|
|
37533
|
+
// src/rules/kotlin/sql-string-concat.ts
|
|
37534
|
+
var SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
37535
|
+
var UNSAFE_REGEX2 = /(?:\+|\$\{)/;
|
|
37536
|
+
var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
|
|
37537
|
+
var kotlinSqlStringConcatRule = createRule({
|
|
37538
|
+
id: "kotlin/sql-string-concat",
|
|
37539
|
+
category: "security",
|
|
37540
|
+
severity: "high",
|
|
37541
|
+
aiSpecific: false,
|
|
37542
|
+
description: "SQL query built via string concat / template \u2014 use PreparedStatement or setParameter()",
|
|
37543
|
+
create(_context) {
|
|
37544
|
+
return {};
|
|
37545
|
+
},
|
|
37546
|
+
analyze(_context, facts) {
|
|
37547
|
+
const issues = [];
|
|
37548
|
+
const source = facts.v2?._source;
|
|
37549
|
+
if (!source) return issues;
|
|
37550
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37551
|
+
const lines = source.split("\n");
|
|
37552
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37553
|
+
const line = lines[i];
|
|
37554
|
+
if (!SQL_KEYWORD_REGEX2.test(line)) continue;
|
|
37555
|
+
if (!UNSAFE_REGEX2.test(line)) continue;
|
|
37556
|
+
if (SAFE_REGEX2.test(line)) continue;
|
|
37557
|
+
issues.push({
|
|
37558
|
+
ruleId: "kotlin/sql-string-concat",
|
|
37559
|
+
category: "security",
|
|
37560
|
+
severity: "high",
|
|
37561
|
+
aiSpecific: false,
|
|
37562
|
+
message: `SQL query built via string concat/template at line ${i + 1}`,
|
|
37563
|
+
line: i + 1,
|
|
37564
|
+
column: 1,
|
|
37565
|
+
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).'
|
|
37566
|
+
});
|
|
37567
|
+
}
|
|
37568
|
+
return issues;
|
|
37569
|
+
}
|
|
37570
|
+
});
|
|
37571
|
+
|
|
37159
37572
|
// src/rules/kotlin/string-concat-loop.ts
|
|
37160
|
-
var
|
|
37573
|
+
var STRING_CONCAT_REGEX2 = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
|
|
37161
37574
|
var kotlinStringConcatLoopRule = createRule({
|
|
37162
37575
|
id: "kotlin/string-concat-loop",
|
|
37163
37576
|
category: "perf",
|
|
@@ -37174,8 +37587,8 @@ var kotlinStringConcatLoopRule = createRule({
|
|
|
37174
37587
|
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37175
37588
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
37176
37589
|
let m;
|
|
37177
|
-
|
|
37178
|
-
while ((m =
|
|
37590
|
+
STRING_CONCAT_REGEX2.lastIndex = 0;
|
|
37591
|
+
while ((m = STRING_CONCAT_REGEX2.exec(source)) !== null) {
|
|
37179
37592
|
const line = source.slice(0, m.index).split("\n").length;
|
|
37180
37593
|
const lineText = source.slice(0, m.index).split("\n").pop() ?? "";
|
|
37181
37594
|
if (/\.append\s*\(/.test(lineText)) continue;
|
|
@@ -42716,10 +43129,20 @@ var builtinRules = [
|
|
|
42716
43129
|
goErrorWrapWithoutContextRule,
|
|
42717
43130
|
goNilSliceVsEmptyRule,
|
|
42718
43131
|
goStructTagInconsistencyRule,
|
|
43132
|
+
javaCommandInjectionRule,
|
|
43133
|
+
javaHardcodedCredentialRule,
|
|
43134
|
+
javaSqlStringConcatRule,
|
|
43135
|
+
javaSystemOutPrintlnRule,
|
|
43136
|
+
javaThreadSleepInLoopRule,
|
|
42719
43137
|
kotlinCoroutineGlobalScopeRule,
|
|
42720
43138
|
kotlinDataClassDefaultsOveruseRule,
|
|
43139
|
+
kotlinForceUnwrapRule,
|
|
43140
|
+
kotlinHardcodedCredentialRule,
|
|
42721
43141
|
kotlinObjectSingletonMisuseRule,
|
|
43142
|
+
kotlinPrintlnAsLogRule,
|
|
42722
43143
|
kotlinPrintlnDebugRule,
|
|
43144
|
+
kotlinRunBlockingMisuseRule,
|
|
43145
|
+
kotlinSqlStringConcatRule,
|
|
42723
43146
|
kotlinStringConcatLoopRule,
|
|
42724
43147
|
gapMonopolyRule,
|
|
42725
43148
|
mathElementUniformityRule,
|
|
@@ -44938,6 +45361,166 @@ var signal_strength_default = {
|
|
|
44938
45361
|
_v9Precision: 0.125,
|
|
44939
45362
|
defaultOff: true
|
|
44940
45363
|
},
|
|
45364
|
+
"kotlin/sql-string-concat": {
|
|
45365
|
+
recall: 469e-5,
|
|
45366
|
+
fpRate: 63e-4,
|
|
45367
|
+
ratio: 0.75,
|
|
45368
|
+
precision: 0.0556,
|
|
45369
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45370
|
+
verdict: "DORMANT",
|
|
45371
|
+
_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.",
|
|
45372
|
+
aiSpecific: false,
|
|
45373
|
+
_v9Verdict: "DORMANT",
|
|
45374
|
+
_v9Lift: 0.75,
|
|
45375
|
+
_v9Recall: 469e-5,
|
|
45376
|
+
_v9FpRate: 63e-4,
|
|
45377
|
+
_v9Precision: 0.0556,
|
|
45378
|
+
defaultOff: true
|
|
45379
|
+
},
|
|
45380
|
+
"kotlin/hardcoded-credential": {
|
|
45381
|
+
recall: 0,
|
|
45382
|
+
fpRate: 0,
|
|
45383
|
+
ratio: 0,
|
|
45384
|
+
precision: 0,
|
|
45385
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45386
|
+
verdict: "DORMANT",
|
|
45387
|
+
_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.",
|
|
45388
|
+
aiSpecific: false,
|
|
45389
|
+
_v9Verdict: "DORMANT",
|
|
45390
|
+
_v9Lift: 0,
|
|
45391
|
+
_v9Recall: 0,
|
|
45392
|
+
_v9FpRate: 0,
|
|
45393
|
+
_v9Precision: 0,
|
|
45394
|
+
defaultOff: true
|
|
45395
|
+
},
|
|
45396
|
+
"kotlin/runblocking-misuse": {
|
|
45397
|
+
recall: 0.10798,
|
|
45398
|
+
fpRate: 0.21534,
|
|
45399
|
+
ratio: 0.5,
|
|
45400
|
+
precision: 0.0381,
|
|
45401
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45402
|
+
verdict: "DORMANT",
|
|
45403
|
+
_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.",
|
|
45404
|
+
aiSpecific: false,
|
|
45405
|
+
_v9Verdict: "DORMANT",
|
|
45406
|
+
_v9Lift: 0.5,
|
|
45407
|
+
_v9Recall: 0.10798,
|
|
45408
|
+
_v9FpRate: 0.21534,
|
|
45409
|
+
_v9Precision: 0.0381,
|
|
45410
|
+
defaultOff: true
|
|
45411
|
+
},
|
|
45412
|
+
"kotlin/println-as-log": {
|
|
45413
|
+
recall: 0.08451,
|
|
45414
|
+
fpRate: 0.04596,
|
|
45415
|
+
ratio: 1.84,
|
|
45416
|
+
precision: 0.1268,
|
|
45417
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45418
|
+
verdict: "OK",
|
|
45419
|
+
_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).",
|
|
45420
|
+
aiSpecific: false,
|
|
45421
|
+
_v9Verdict: "OK",
|
|
45422
|
+
_v9Lift: 1.84,
|
|
45423
|
+
_v9Recall: 0.08451,
|
|
45424
|
+
_v9FpRate: 0.04596,
|
|
45425
|
+
_v9Precision: 0.1268,
|
|
45426
|
+
defaultOff: true
|
|
45427
|
+
},
|
|
45428
|
+
"kotlin/force-unwrap": {
|
|
45429
|
+
recall: 0.11737,
|
|
45430
|
+
fpRate: 0.33284,
|
|
45431
|
+
ratio: 0.35,
|
|
45432
|
+
precision: 0.0271,
|
|
45433
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45434
|
+
verdict: "DORMANT",
|
|
45435
|
+
_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.",
|
|
45436
|
+
aiSpecific: false,
|
|
45437
|
+
_v9Verdict: "DORMANT",
|
|
45438
|
+
_v9Lift: 0.35,
|
|
45439
|
+
_v9Recall: 0.11737,
|
|
45440
|
+
_v9FpRate: 0.33284,
|
|
45441
|
+
_v9Precision: 0.0271,
|
|
45442
|
+
defaultOff: true
|
|
45443
|
+
},
|
|
45444
|
+
"java/sql-string-concat": {
|
|
45445
|
+
recall: 0.01116,
|
|
45446
|
+
fpRate: 0.01892,
|
|
45447
|
+
ratio: 0.59,
|
|
45448
|
+
precision: 0.0691,
|
|
45449
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45450
|
+
verdict: "DORMANT",
|
|
45451
|
+
_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.",
|
|
45452
|
+
aiSpecific: false,
|
|
45453
|
+
_v9Verdict: "DORMANT",
|
|
45454
|
+
_v9Lift: 0.59,
|
|
45455
|
+
_v9Recall: 0.01116,
|
|
45456
|
+
_v9FpRate: 0.01892,
|
|
45457
|
+
_v9Precision: 0.0691,
|
|
45458
|
+
defaultOff: true
|
|
45459
|
+
},
|
|
45460
|
+
"java/hardcoded-credential": {
|
|
45461
|
+
recall: 0,
|
|
45462
|
+
fpRate: 4e-5,
|
|
45463
|
+
ratio: 0,
|
|
45464
|
+
precision: 0,
|
|
45465
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45466
|
+
verdict: "DORMANT",
|
|
45467
|
+
_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.",
|
|
45468
|
+
aiSpecific: false,
|
|
45469
|
+
_v9Verdict: "DORMANT",
|
|
45470
|
+
_v9Lift: 0,
|
|
45471
|
+
_v9Recall: 0,
|
|
45472
|
+
_v9FpRate: 4e-5,
|
|
45473
|
+
_v9Precision: 0,
|
|
45474
|
+
defaultOff: true
|
|
45475
|
+
},
|
|
45476
|
+
"java/thread-sleep-in-loop": {
|
|
45477
|
+
recall: 0.0228,
|
|
45478
|
+
fpRate: 0.02343,
|
|
45479
|
+
ratio: 0.97,
|
|
45480
|
+
precision: 0.1091,
|
|
45481
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45482
|
+
verdict: "DORMANT",
|
|
45483
|
+
_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.",
|
|
45484
|
+
aiSpecific: false,
|
|
45485
|
+
_v9Verdict: "DORMANT",
|
|
45486
|
+
_v9Lift: 0.97,
|
|
45487
|
+
_v9Recall: 0.0228,
|
|
45488
|
+
_v9FpRate: 0.02343,
|
|
45489
|
+
_v9Precision: 0.1091,
|
|
45490
|
+
defaultOff: true
|
|
45491
|
+
},
|
|
45492
|
+
"java/system-out-println": {
|
|
45493
|
+
recall: 0.16681,
|
|
45494
|
+
fpRate: 0.05064,
|
|
45495
|
+
ratio: 3.29,
|
|
45496
|
+
precision: 0.293,
|
|
45497
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45498
|
+
verdict: "OK",
|
|
45499
|
+
_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.",
|
|
45500
|
+
aiSpecific: false,
|
|
45501
|
+
_v9Verdict: "OK",
|
|
45502
|
+
_v9Lift: 3.29,
|
|
45503
|
+
_v9Recall: 0.16681,
|
|
45504
|
+
_v9FpRate: 0.05064,
|
|
45505
|
+
_v9Precision: 0.293,
|
|
45506
|
+
defaultOff: true
|
|
45507
|
+
},
|
|
45508
|
+
"java/command-injection": {
|
|
45509
|
+
recall: 0,
|
|
45510
|
+
fpRate: 1e-4,
|
|
45511
|
+
ratio: 0,
|
|
45512
|
+
precision: 0,
|
|
45513
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45514
|
+
verdict: "DORMANT",
|
|
45515
|
+
_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.",
|
|
45516
|
+
aiSpecific: false,
|
|
45517
|
+
_v9Verdict: "DORMANT",
|
|
45518
|
+
_v9Lift: 0,
|
|
45519
|
+
_v9Recall: 0,
|
|
45520
|
+
_v9FpRate: 1e-4,
|
|
45521
|
+
_v9Precision: 0,
|
|
45522
|
+
defaultOff: true
|
|
45523
|
+
},
|
|
44941
45524
|
"swift/force-unwrap": {
|
|
44942
45525
|
recall: 0,
|
|
44943
45526
|
fpRate: 0,
|