slopbrick 0.34.10 → 0.35.1
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 +150 -0
- package/dist/engine/worker.js +150 -0
- package/dist/index.cjs +171 -1
- package/dist/index.js +171 -1
- package/package.json +1 -1
package/dist/engine/worker.cjs
CHANGED
|
@@ -37139,6 +37139,67 @@ var javaHardcodedCredentialRule = createRule({
|
|
|
37139
37139
|
}
|
|
37140
37140
|
});
|
|
37141
37141
|
|
|
37142
|
+
// src/rules/java/lost-stack-trace.ts
|
|
37143
|
+
var THROW_NEW_REGEX = /throw\s+new\s+(\w+(?:Exception|Error|Throwable))\s*\(([^)]*)\)/g;
|
|
37144
|
+
var CATCH_REGEX = /catch\s*\(\s*(?:final\s+)?[\w<>,\s]+\s+(\w+)\s*\)\s*\{/g;
|
|
37145
|
+
var javaLostStackTraceRule = createRule({
|
|
37146
|
+
id: "java/lost-stack-trace",
|
|
37147
|
+
category: "logic",
|
|
37148
|
+
severity: "medium",
|
|
37149
|
+
aiSpecific: false,
|
|
37150
|
+
description: "catch block throws a new exception without the original cause \u2014 stack trace is lost",
|
|
37151
|
+
create(_context) {
|
|
37152
|
+
return {};
|
|
37153
|
+
},
|
|
37154
|
+
analyze(_context, facts) {
|
|
37155
|
+
const issues = [];
|
|
37156
|
+
const source = facts.v2?._source;
|
|
37157
|
+
if (!source) return issues;
|
|
37158
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37159
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
37160
|
+
const catchBlocks = [];
|
|
37161
|
+
let cm;
|
|
37162
|
+
CATCH_REGEX.lastIndex = 0;
|
|
37163
|
+
while ((cm = CATCH_REGEX.exec(source)) !== null) {
|
|
37164
|
+
const catchStart = cm.index;
|
|
37165
|
+
const exVar = cm[1];
|
|
37166
|
+
let depth = 1;
|
|
37167
|
+
let i = cm.index + cm[0].length;
|
|
37168
|
+
while (i < source.length && depth > 0) {
|
|
37169
|
+
const ch = source[i];
|
|
37170
|
+
if (ch === "{") depth++;
|
|
37171
|
+
else if (ch === "}") depth--;
|
|
37172
|
+
i++;
|
|
37173
|
+
}
|
|
37174
|
+
catchBlocks.push({ start: catchStart, end: i, exVar });
|
|
37175
|
+
}
|
|
37176
|
+
let m;
|
|
37177
|
+
THROW_NEW_REGEX.lastIndex = 0;
|
|
37178
|
+
while ((m = THROW_NEW_REGEX.exec(source)) !== null) {
|
|
37179
|
+
const throwPos = m.index;
|
|
37180
|
+
const args = m[2].trim();
|
|
37181
|
+
const enclosing = catchBlocks.find(
|
|
37182
|
+
(cb) => cb.start < throwPos && throwPos < cb.end
|
|
37183
|
+
);
|
|
37184
|
+
if (!enclosing) continue;
|
|
37185
|
+
const exVarPattern = new RegExp(`\\b${enclosing.exVar}\\b`);
|
|
37186
|
+
if (exVarPattern.test(args)) continue;
|
|
37187
|
+
const line = source.slice(0, throwPos).split("\n").length;
|
|
37188
|
+
issues.push({
|
|
37189
|
+
ruleId: "java/lost-stack-trace",
|
|
37190
|
+
category: "logic",
|
|
37191
|
+
severity: "medium",
|
|
37192
|
+
aiSpecific: false,
|
|
37193
|
+
message: `throw new ${m[1]}(${args}) \u2014 original exception \`${enclosing.exVar}\` is not included as cause`,
|
|
37194
|
+
line,
|
|
37195
|
+
column: 1,
|
|
37196
|
+
advice: `The catch block declares exception variable \`${enclosing.exVar}\` but the throw statement doesn't include it as a cause. The original stack trace is lost. Fix: \`throw new ${m[1]}("...", ${enclosing.exVar})\` \u2014 the second argument to the exception constructor is the cause, which Java's Throwable framework preserves in the stack trace chain. Reference: java/lost-stack-trace v0.35.1 (Raidar-inspired content-based detection of AI-polished error handling).`
|
|
37197
|
+
});
|
|
37198
|
+
}
|
|
37199
|
+
return issues;
|
|
37200
|
+
}
|
|
37201
|
+
});
|
|
37202
|
+
|
|
37142
37203
|
// src/rules/java/sql-string-concat.ts
|
|
37143
37204
|
var SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
37144
37205
|
var UNSAFE_REGEX = /\+/;
|
|
@@ -37185,6 +37246,61 @@ var javaSqlStringConcatRule = createRule({
|
|
|
37185
37246
|
}
|
|
37186
37247
|
});
|
|
37187
37248
|
|
|
37249
|
+
// src/rules/java/suspicious-implementation.ts
|
|
37250
|
+
var STRONG_VERB_REGEX = /(?:validate|validates|validated|verifies?|verify|check|checks?|checked|ensures?|ensure|ensured|verifies|verified|sanitize|sanitizes|sanitized|escape|escapes|escaped|authenticate|authenticates|authenticated|authorize|authorizes|authorized|audit|audits|audited|inspect|inspects|inspected|filter|filters?|filtered|normalize|normalizes|normalized|encrypt|encrypts|encrypted|decrypt|decrypts|decrypted|hash|hashes|hashed|sign|signs?|signed|compress|compresses|compressed|decompress|decompresses|decompressed|parse|parses|parsed|format|formats?|formatted)(?=[A-Z]|[^a-zA-Z]|$)/i;
|
|
37251
|
+
var METHOD_DECL_REGEX = /(?:public|private|protected)\s+(?:static\s+)?[\w<>,\s\[\]]+?\s+(\w+)\s*\([^)]*\)\s*\{([^{}]*)\}/g;
|
|
37252
|
+
var EMPTY_BODY_REGEX = /^\s*$/;
|
|
37253
|
+
var RETURN_CONSTANT_REGEX = /^\s*return\s+(?:null|true|false|0|1|0L|0\.0|"")\s*;\s*$/;
|
|
37254
|
+
var RETURN_INPUT_REGEX = /^\s*return\s+(\w+)\s*;\s*$/;
|
|
37255
|
+
var JUST_THROW_REGEX = /^\s*throw\s+new\s+(?:UnsupportedOperationException|UnsupportedOperationException\([^)]*\)|RuntimeException\([^)]*\)|IllegalStateException\([^)]*\))\s*;\s*$/;
|
|
37256
|
+
var javaSuspiciousImplementationRule = createRule({
|
|
37257
|
+
id: "java/suspicious-implementation",
|
|
37258
|
+
category: "security",
|
|
37259
|
+
severity: "high",
|
|
37260
|
+
aiSpecific: false,
|
|
37261
|
+
description: "function name suggests validation/encryption/auth but body is empty or trivially wrong \u2014 content mismatch",
|
|
37262
|
+
create(_context) {
|
|
37263
|
+
return {};
|
|
37264
|
+
},
|
|
37265
|
+
analyze(_context, facts) {
|
|
37266
|
+
const issues = [];
|
|
37267
|
+
const source = facts.v2?._source;
|
|
37268
|
+
if (!source) return issues;
|
|
37269
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37270
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
37271
|
+
let m;
|
|
37272
|
+
METHOD_DECL_REGEX.lastIndex = 0;
|
|
37273
|
+
while ((m = METHOD_DECL_REGEX.exec(source)) !== null) {
|
|
37274
|
+
const methodName = m[1];
|
|
37275
|
+
const body = m[2];
|
|
37276
|
+
if (!STRONG_VERB_REGEX.test(methodName)) continue;
|
|
37277
|
+
let reason = null;
|
|
37278
|
+
if (EMPTY_BODY_REGEX.test(body)) {
|
|
37279
|
+
reason = "empty body";
|
|
37280
|
+
} else if (RETURN_CONSTANT_REGEX.test(body)) {
|
|
37281
|
+
reason = "returns a constant (null/true/false/0/1)";
|
|
37282
|
+
} else if (RETURN_INPUT_REGEX.test(body)) {
|
|
37283
|
+
reason = "returns the input unchanged (pass-through stub)";
|
|
37284
|
+
} else if (JUST_THROW_REGEX.test(body)) {
|
|
37285
|
+
reason = "throws UnsupportedOperationException \u2014 not implemented";
|
|
37286
|
+
}
|
|
37287
|
+
if (!reason) continue;
|
|
37288
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37289
|
+
issues.push({
|
|
37290
|
+
ruleId: "java/suspicious-implementation",
|
|
37291
|
+
category: "security",
|
|
37292
|
+
severity: "high",
|
|
37293
|
+
aiSpecific: false,
|
|
37294
|
+
message: `function ${methodName} (${reason}) \u2014 content-based mismatch`,
|
|
37295
|
+
line,
|
|
37296
|
+
column: 1,
|
|
37297
|
+
advice: `The function name "${methodName}" suggests a real validation/encryption/auth/filter operation, but the body is ${reason}. This is a content mismatch \u2014 the code's claimed behavior doesn't match its actual behavior. Real production code that ships this is a silent security failure (OWASP A04:2021 \u2014 Insecure Design). The fix is to either implement the operation or rename the function to reflect what it actually does. Reference: java/suspicious-implementation v0.35.0 (CoCoNUTS-inspired content-based detection).`
|
|
37298
|
+
});
|
|
37299
|
+
}
|
|
37300
|
+
return issues;
|
|
37301
|
+
}
|
|
37302
|
+
});
|
|
37303
|
+
|
|
37188
37304
|
// src/rules/java/system-out-println.ts
|
|
37189
37305
|
var SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
|
|
37190
37306
|
var REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging)/;
|
|
@@ -43320,7 +43436,9 @@ var builtinRules = [
|
|
|
43320
43436
|
goStructTagInconsistencyRule,
|
|
43321
43437
|
javaCommandInjectionRule,
|
|
43322
43438
|
javaHardcodedCredentialRule,
|
|
43439
|
+
javaLostStackTraceRule,
|
|
43323
43440
|
javaSqlStringConcatRule,
|
|
43441
|
+
javaSuspiciousImplementationRule,
|
|
43324
43442
|
javaSystemOutPrintlnRule,
|
|
43325
43443
|
javaThreadSleepInLoopRule,
|
|
43326
43444
|
kotlinCoroutineGlobalScopeRule,
|
|
@@ -45710,6 +45828,38 @@ var signal_strength_default = {
|
|
|
45710
45828
|
_v9Precision: 0,
|
|
45711
45829
|
defaultOff: true
|
|
45712
45830
|
},
|
|
45831
|
+
"java/suspicious-implementation": {
|
|
45832
|
+
recall: 0,
|
|
45833
|
+
fpRate: 0,
|
|
45834
|
+
ratio: 0,
|
|
45835
|
+
precision: 0,
|
|
45836
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45837
|
+
verdict: "OK",
|
|
45838
|
+
_calibrationNote: "v0.35.0: NEW RULE \u2014 content-based detection (CoCoNUTS-inspired). Detects function names that claim a strong operation (validate, encrypt, hash, sanitize, check, verify, authenticate, etc.) but whose body is trivially empty, returns a constant, or returns the input unchanged. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (i.e. ON by default) and verdict=OK. The CoCoNUTS paper (2025) showed that style-based AI-detectors fail under paraphrasing; this rule looks at semantic content (function body behavior) rather than surface features. v9 calibration is pending \u2014 the rule was added at the end of the v0.34.X refinement series and has not been measured against the v9 corpus yet. The expected impact: low recall (most production code doesn't have these stubs) but high precision (when it fires, it's a real bug). The full v9 calibration is the next step for v0.35.0.x patches.",
|
|
45839
|
+
aiSpecific: false,
|
|
45840
|
+
_v9Verdict: "OK",
|
|
45841
|
+
_v9Lift: 0,
|
|
45842
|
+
_v9Recall: 0,
|
|
45843
|
+
_v9FpRate: 0,
|
|
45844
|
+
_v9Precision: 0,
|
|
45845
|
+
defaultOff: false
|
|
45846
|
+
},
|
|
45847
|
+
"java/lost-stack-trace": {
|
|
45848
|
+
recall: 0,
|
|
45849
|
+
fpRate: 0,
|
|
45850
|
+
ratio: 0,
|
|
45851
|
+
precision: 0,
|
|
45852
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45853
|
+
verdict: "OK",
|
|
45854
|
+
_calibrationNote: "v0.35.1: NEW RULE \u2014 Raidar-inspired content-based detection. The Raidar paper (ICLR 2024) showed that LLMs tend to 'polish' error handling by wrapping exceptions but losing the original cause; the inverse observation is that AI-generated code often has this pattern. This rule detects catch blocks that throw a new exception WITHOUT including the original exception as a cause \u2014 the original stack trace is lost. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (ON by default) and verdict=OK. v9 calibration is pending. The expected impact: low recall (most production code preserves the original exception) but very high precision (when it fires, it's a real bug \u2014 debugging without the stack trace is impossible).",
|
|
45855
|
+
aiSpecific: false,
|
|
45856
|
+
_v9Verdict: "OK",
|
|
45857
|
+
_v9Lift: 0,
|
|
45858
|
+
_v9Recall: 0,
|
|
45859
|
+
_v9FpRate: 0,
|
|
45860
|
+
_v9Precision: 0,
|
|
45861
|
+
defaultOff: false
|
|
45862
|
+
},
|
|
45713
45863
|
"swift/force-unwrap": {
|
|
45714
45864
|
recall: 0.10211,
|
|
45715
45865
|
fpRate: 0.21462,
|
package/dist/engine/worker.js
CHANGED
|
@@ -37110,6 +37110,67 @@ var javaHardcodedCredentialRule = createRule({
|
|
|
37110
37110
|
}
|
|
37111
37111
|
});
|
|
37112
37112
|
|
|
37113
|
+
// src/rules/java/lost-stack-trace.ts
|
|
37114
|
+
var THROW_NEW_REGEX = /throw\s+new\s+(\w+(?:Exception|Error|Throwable))\s*\(([^)]*)\)/g;
|
|
37115
|
+
var CATCH_REGEX = /catch\s*\(\s*(?:final\s+)?[\w<>,\s]+\s+(\w+)\s*\)\s*\{/g;
|
|
37116
|
+
var javaLostStackTraceRule = createRule({
|
|
37117
|
+
id: "java/lost-stack-trace",
|
|
37118
|
+
category: "logic",
|
|
37119
|
+
severity: "medium",
|
|
37120
|
+
aiSpecific: false,
|
|
37121
|
+
description: "catch block throws a new exception without the original cause \u2014 stack trace is lost",
|
|
37122
|
+
create(_context) {
|
|
37123
|
+
return {};
|
|
37124
|
+
},
|
|
37125
|
+
analyze(_context, facts) {
|
|
37126
|
+
const issues = [];
|
|
37127
|
+
const source = facts.v2?._source;
|
|
37128
|
+
if (!source) return issues;
|
|
37129
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37130
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
37131
|
+
const catchBlocks = [];
|
|
37132
|
+
let cm;
|
|
37133
|
+
CATCH_REGEX.lastIndex = 0;
|
|
37134
|
+
while ((cm = CATCH_REGEX.exec(source)) !== null) {
|
|
37135
|
+
const catchStart = cm.index;
|
|
37136
|
+
const exVar = cm[1];
|
|
37137
|
+
let depth = 1;
|
|
37138
|
+
let i = cm.index + cm[0].length;
|
|
37139
|
+
while (i < source.length && depth > 0) {
|
|
37140
|
+
const ch = source[i];
|
|
37141
|
+
if (ch === "{") depth++;
|
|
37142
|
+
else if (ch === "}") depth--;
|
|
37143
|
+
i++;
|
|
37144
|
+
}
|
|
37145
|
+
catchBlocks.push({ start: catchStart, end: i, exVar });
|
|
37146
|
+
}
|
|
37147
|
+
let m;
|
|
37148
|
+
THROW_NEW_REGEX.lastIndex = 0;
|
|
37149
|
+
while ((m = THROW_NEW_REGEX.exec(source)) !== null) {
|
|
37150
|
+
const throwPos = m.index;
|
|
37151
|
+
const args = m[2].trim();
|
|
37152
|
+
const enclosing = catchBlocks.find(
|
|
37153
|
+
(cb) => cb.start < throwPos && throwPos < cb.end
|
|
37154
|
+
);
|
|
37155
|
+
if (!enclosing) continue;
|
|
37156
|
+
const exVarPattern = new RegExp(`\\b${enclosing.exVar}\\b`);
|
|
37157
|
+
if (exVarPattern.test(args)) continue;
|
|
37158
|
+
const line = source.slice(0, throwPos).split("\n").length;
|
|
37159
|
+
issues.push({
|
|
37160
|
+
ruleId: "java/lost-stack-trace",
|
|
37161
|
+
category: "logic",
|
|
37162
|
+
severity: "medium",
|
|
37163
|
+
aiSpecific: false,
|
|
37164
|
+
message: `throw new ${m[1]}(${args}) \u2014 original exception \`${enclosing.exVar}\` is not included as cause`,
|
|
37165
|
+
line,
|
|
37166
|
+
column: 1,
|
|
37167
|
+
advice: `The catch block declares exception variable \`${enclosing.exVar}\` but the throw statement doesn't include it as a cause. The original stack trace is lost. Fix: \`throw new ${m[1]}("...", ${enclosing.exVar})\` \u2014 the second argument to the exception constructor is the cause, which Java's Throwable framework preserves in the stack trace chain. Reference: java/lost-stack-trace v0.35.1 (Raidar-inspired content-based detection of AI-polished error handling).`
|
|
37168
|
+
});
|
|
37169
|
+
}
|
|
37170
|
+
return issues;
|
|
37171
|
+
}
|
|
37172
|
+
});
|
|
37173
|
+
|
|
37113
37174
|
// src/rules/java/sql-string-concat.ts
|
|
37114
37175
|
var SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
37115
37176
|
var UNSAFE_REGEX = /\+/;
|
|
@@ -37156,6 +37217,61 @@ var javaSqlStringConcatRule = createRule({
|
|
|
37156
37217
|
}
|
|
37157
37218
|
});
|
|
37158
37219
|
|
|
37220
|
+
// src/rules/java/suspicious-implementation.ts
|
|
37221
|
+
var STRONG_VERB_REGEX = /(?:validate|validates|validated|verifies?|verify|check|checks?|checked|ensures?|ensure|ensured|verifies|verified|sanitize|sanitizes|sanitized|escape|escapes|escaped|authenticate|authenticates|authenticated|authorize|authorizes|authorized|audit|audits|audited|inspect|inspects|inspected|filter|filters?|filtered|normalize|normalizes|normalized|encrypt|encrypts|encrypted|decrypt|decrypts|decrypted|hash|hashes|hashed|sign|signs?|signed|compress|compresses|compressed|decompress|decompresses|decompressed|parse|parses|parsed|format|formats?|formatted)(?=[A-Z]|[^a-zA-Z]|$)/i;
|
|
37222
|
+
var METHOD_DECL_REGEX = /(?:public|private|protected)\s+(?:static\s+)?[\w<>,\s\[\]]+?\s+(\w+)\s*\([^)]*\)\s*\{([^{}]*)\}/g;
|
|
37223
|
+
var EMPTY_BODY_REGEX = /^\s*$/;
|
|
37224
|
+
var RETURN_CONSTANT_REGEX = /^\s*return\s+(?:null|true|false|0|1|0L|0\.0|"")\s*;\s*$/;
|
|
37225
|
+
var RETURN_INPUT_REGEX = /^\s*return\s+(\w+)\s*;\s*$/;
|
|
37226
|
+
var JUST_THROW_REGEX = /^\s*throw\s+new\s+(?:UnsupportedOperationException|UnsupportedOperationException\([^)]*\)|RuntimeException\([^)]*\)|IllegalStateException\([^)]*\))\s*;\s*$/;
|
|
37227
|
+
var javaSuspiciousImplementationRule = createRule({
|
|
37228
|
+
id: "java/suspicious-implementation",
|
|
37229
|
+
category: "security",
|
|
37230
|
+
severity: "high",
|
|
37231
|
+
aiSpecific: false,
|
|
37232
|
+
description: "function name suggests validation/encryption/auth but body is empty or trivially wrong \u2014 content mismatch",
|
|
37233
|
+
create(_context) {
|
|
37234
|
+
return {};
|
|
37235
|
+
},
|
|
37236
|
+
analyze(_context, facts) {
|
|
37237
|
+
const issues = [];
|
|
37238
|
+
const source = facts.v2?._source;
|
|
37239
|
+
if (!source) return issues;
|
|
37240
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
37241
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
37242
|
+
let m;
|
|
37243
|
+
METHOD_DECL_REGEX.lastIndex = 0;
|
|
37244
|
+
while ((m = METHOD_DECL_REGEX.exec(source)) !== null) {
|
|
37245
|
+
const methodName = m[1];
|
|
37246
|
+
const body = m[2];
|
|
37247
|
+
if (!STRONG_VERB_REGEX.test(methodName)) continue;
|
|
37248
|
+
let reason = null;
|
|
37249
|
+
if (EMPTY_BODY_REGEX.test(body)) {
|
|
37250
|
+
reason = "empty body";
|
|
37251
|
+
} else if (RETURN_CONSTANT_REGEX.test(body)) {
|
|
37252
|
+
reason = "returns a constant (null/true/false/0/1)";
|
|
37253
|
+
} else if (RETURN_INPUT_REGEX.test(body)) {
|
|
37254
|
+
reason = "returns the input unchanged (pass-through stub)";
|
|
37255
|
+
} else if (JUST_THROW_REGEX.test(body)) {
|
|
37256
|
+
reason = "throws UnsupportedOperationException \u2014 not implemented";
|
|
37257
|
+
}
|
|
37258
|
+
if (!reason) continue;
|
|
37259
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37260
|
+
issues.push({
|
|
37261
|
+
ruleId: "java/suspicious-implementation",
|
|
37262
|
+
category: "security",
|
|
37263
|
+
severity: "high",
|
|
37264
|
+
aiSpecific: false,
|
|
37265
|
+
message: `function ${methodName} (${reason}) \u2014 content-based mismatch`,
|
|
37266
|
+
line,
|
|
37267
|
+
column: 1,
|
|
37268
|
+
advice: `The function name "${methodName}" suggests a real validation/encryption/auth/filter operation, but the body is ${reason}. This is a content mismatch \u2014 the code's claimed behavior doesn't match its actual behavior. Real production code that ships this is a silent security failure (OWASP A04:2021 \u2014 Insecure Design). The fix is to either implement the operation or rename the function to reflect what it actually does. Reference: java/suspicious-implementation v0.35.0 (CoCoNUTS-inspired content-based detection).`
|
|
37269
|
+
});
|
|
37270
|
+
}
|
|
37271
|
+
return issues;
|
|
37272
|
+
}
|
|
37273
|
+
});
|
|
37274
|
+
|
|
37159
37275
|
// src/rules/java/system-out-println.ts
|
|
37160
37276
|
var SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
|
|
37161
37277
|
var REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging)/;
|
|
@@ -43291,7 +43407,9 @@ var builtinRules = [
|
|
|
43291
43407
|
goStructTagInconsistencyRule,
|
|
43292
43408
|
javaCommandInjectionRule,
|
|
43293
43409
|
javaHardcodedCredentialRule,
|
|
43410
|
+
javaLostStackTraceRule,
|
|
43294
43411
|
javaSqlStringConcatRule,
|
|
43412
|
+
javaSuspiciousImplementationRule,
|
|
43295
43413
|
javaSystemOutPrintlnRule,
|
|
43296
43414
|
javaThreadSleepInLoopRule,
|
|
43297
43415
|
kotlinCoroutineGlobalScopeRule,
|
|
@@ -45681,6 +45799,38 @@ var signal_strength_default = {
|
|
|
45681
45799
|
_v9Precision: 0,
|
|
45682
45800
|
defaultOff: true
|
|
45683
45801
|
},
|
|
45802
|
+
"java/suspicious-implementation": {
|
|
45803
|
+
recall: 0,
|
|
45804
|
+
fpRate: 0,
|
|
45805
|
+
ratio: 0,
|
|
45806
|
+
precision: 0,
|
|
45807
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45808
|
+
verdict: "OK",
|
|
45809
|
+
_calibrationNote: "v0.35.0: NEW RULE \u2014 content-based detection (CoCoNUTS-inspired). Detects function names that claim a strong operation (validate, encrypt, hash, sanitize, check, verify, authenticate, etc.) but whose body is trivially empty, returns a constant, or returns the input unchanged. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (i.e. ON by default) and verdict=OK. The CoCoNUTS paper (2025) showed that style-based AI-detectors fail under paraphrasing; this rule looks at semantic content (function body behavior) rather than surface features. v9 calibration is pending \u2014 the rule was added at the end of the v0.34.X refinement series and has not been measured against the v9 corpus yet. The expected impact: low recall (most production code doesn't have these stubs) but high precision (when it fires, it's a real bug). The full v9 calibration is the next step for v0.35.0.x patches.",
|
|
45810
|
+
aiSpecific: false,
|
|
45811
|
+
_v9Verdict: "OK",
|
|
45812
|
+
_v9Lift: 0,
|
|
45813
|
+
_v9Recall: 0,
|
|
45814
|
+
_v9FpRate: 0,
|
|
45815
|
+
_v9Precision: 0,
|
|
45816
|
+
defaultOff: false
|
|
45817
|
+
},
|
|
45818
|
+
"java/lost-stack-trace": {
|
|
45819
|
+
recall: 0,
|
|
45820
|
+
fpRate: 0,
|
|
45821
|
+
ratio: 0,
|
|
45822
|
+
precision: 0,
|
|
45823
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45824
|
+
verdict: "OK",
|
|
45825
|
+
_calibrationNote: "v0.35.1: NEW RULE \u2014 Raidar-inspired content-based detection. The Raidar paper (ICLR 2024) showed that LLMs tend to 'polish' error handling by wrapping exceptions but losing the original cause; the inverse observation is that AI-generated code often has this pattern. This rule detects catch blocks that throw a new exception WITHOUT including the original exception as a cause \u2014 the original stack trace is lost. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (ON by default) and verdict=OK. v9 calibration is pending. The expected impact: low recall (most production code preserves the original exception) but very high precision (when it fires, it's a real bug \u2014 debugging without the stack trace is impossible).",
|
|
45826
|
+
aiSpecific: false,
|
|
45827
|
+
_v9Verdict: "OK",
|
|
45828
|
+
_v9Lift: 0,
|
|
45829
|
+
_v9Recall: 0,
|
|
45830
|
+
_v9FpRate: 0,
|
|
45831
|
+
_v9Precision: 0,
|
|
45832
|
+
defaultOff: false
|
|
45833
|
+
},
|
|
45684
45834
|
"swift/force-unwrap": {
|
|
45685
45835
|
recall: 0.10211,
|
|
45686
45836
|
fpRate: 0.21462,
|
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.35.1";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -30797,6 +30797,74 @@ var init_hardcoded_credential = __esm({
|
|
|
30797
30797
|
}
|
|
30798
30798
|
});
|
|
30799
30799
|
|
|
30800
|
+
// src/rules/java/lost-stack-trace.ts
|
|
30801
|
+
var THROW_NEW_REGEX, CATCH_REGEX, javaLostStackTraceRule;
|
|
30802
|
+
var init_lost_stack_trace = __esm({
|
|
30803
|
+
"src/rules/java/lost-stack-trace.ts"() {
|
|
30804
|
+
"use strict";
|
|
30805
|
+
init_rule();
|
|
30806
|
+
THROW_NEW_REGEX = /throw\s+new\s+(\w+(?:Exception|Error|Throwable))\s*\(([^)]*)\)/g;
|
|
30807
|
+
CATCH_REGEX = /catch\s*\(\s*(?:final\s+)?[\w<>,\s]+\s+(\w+)\s*\)\s*\{/g;
|
|
30808
|
+
javaLostStackTraceRule = createRule({
|
|
30809
|
+
id: "java/lost-stack-trace",
|
|
30810
|
+
category: "logic",
|
|
30811
|
+
severity: "medium",
|
|
30812
|
+
aiSpecific: false,
|
|
30813
|
+
description: "catch block throws a new exception without the original cause \u2014 stack trace is lost",
|
|
30814
|
+
create(_context) {
|
|
30815
|
+
return {};
|
|
30816
|
+
},
|
|
30817
|
+
analyze(_context, facts) {
|
|
30818
|
+
const issues = [];
|
|
30819
|
+
const source = facts.v2?._source;
|
|
30820
|
+
if (!source) return issues;
|
|
30821
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30822
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30823
|
+
const catchBlocks = [];
|
|
30824
|
+
let cm;
|
|
30825
|
+
CATCH_REGEX.lastIndex = 0;
|
|
30826
|
+
while ((cm = CATCH_REGEX.exec(source)) !== null) {
|
|
30827
|
+
const catchStart = cm.index;
|
|
30828
|
+
const exVar = cm[1];
|
|
30829
|
+
let depth = 1;
|
|
30830
|
+
let i = cm.index + cm[0].length;
|
|
30831
|
+
while (i < source.length && depth > 0) {
|
|
30832
|
+
const ch = source[i];
|
|
30833
|
+
if (ch === "{") depth++;
|
|
30834
|
+
else if (ch === "}") depth--;
|
|
30835
|
+
i++;
|
|
30836
|
+
}
|
|
30837
|
+
catchBlocks.push({ start: catchStart, end: i, exVar });
|
|
30838
|
+
}
|
|
30839
|
+
let m;
|
|
30840
|
+
THROW_NEW_REGEX.lastIndex = 0;
|
|
30841
|
+
while ((m = THROW_NEW_REGEX.exec(source)) !== null) {
|
|
30842
|
+
const throwPos = m.index;
|
|
30843
|
+
const args = m[2].trim();
|
|
30844
|
+
const enclosing = catchBlocks.find(
|
|
30845
|
+
(cb) => cb.start < throwPos && throwPos < cb.end
|
|
30846
|
+
);
|
|
30847
|
+
if (!enclosing) continue;
|
|
30848
|
+
const exVarPattern = new RegExp(`\\b${enclosing.exVar}\\b`);
|
|
30849
|
+
if (exVarPattern.test(args)) continue;
|
|
30850
|
+
const line = source.slice(0, throwPos).split("\n").length;
|
|
30851
|
+
issues.push({
|
|
30852
|
+
ruleId: "java/lost-stack-trace",
|
|
30853
|
+
category: "logic",
|
|
30854
|
+
severity: "medium",
|
|
30855
|
+
aiSpecific: false,
|
|
30856
|
+
message: `throw new ${m[1]}(${args}) \u2014 original exception \`${enclosing.exVar}\` is not included as cause`,
|
|
30857
|
+
line,
|
|
30858
|
+
column: 1,
|
|
30859
|
+
advice: `The catch block declares exception variable \`${enclosing.exVar}\` but the throw statement doesn't include it as a cause. The original stack trace is lost. Fix: \`throw new ${m[1]}("...", ${enclosing.exVar})\` \u2014 the second argument to the exception constructor is the cause, which Java's Throwable framework preserves in the stack trace chain. Reference: java/lost-stack-trace v0.35.1 (Raidar-inspired content-based detection of AI-polished error handling).`
|
|
30860
|
+
});
|
|
30861
|
+
}
|
|
30862
|
+
return issues;
|
|
30863
|
+
}
|
|
30864
|
+
});
|
|
30865
|
+
}
|
|
30866
|
+
});
|
|
30867
|
+
|
|
30800
30868
|
// src/rules/java/sql-string-concat.ts
|
|
30801
30869
|
var SQL_KEYWORD_REGEX, UNSAFE_REGEX, SAFE_REGEX, javaSqlStringConcatRule;
|
|
30802
30870
|
var init_sql_string_concat = __esm({
|
|
@@ -30850,6 +30918,68 @@ var init_sql_string_concat = __esm({
|
|
|
30850
30918
|
}
|
|
30851
30919
|
});
|
|
30852
30920
|
|
|
30921
|
+
// src/rules/java/suspicious-implementation.ts
|
|
30922
|
+
var STRONG_VERB_REGEX, METHOD_DECL_REGEX, EMPTY_BODY_REGEX, RETURN_CONSTANT_REGEX, RETURN_INPUT_REGEX, JUST_THROW_REGEX, javaSuspiciousImplementationRule;
|
|
30923
|
+
var init_suspicious_implementation = __esm({
|
|
30924
|
+
"src/rules/java/suspicious-implementation.ts"() {
|
|
30925
|
+
"use strict";
|
|
30926
|
+
init_rule();
|
|
30927
|
+
STRONG_VERB_REGEX = /(?:validate|validates|validated|verifies?|verify|check|checks?|checked|ensures?|ensure|ensured|verifies|verified|sanitize|sanitizes|sanitized|escape|escapes|escaped|authenticate|authenticates|authenticated|authorize|authorizes|authorized|audit|audits|audited|inspect|inspects|inspected|filter|filters?|filtered|normalize|normalizes|normalized|encrypt|encrypts|encrypted|decrypt|decrypts|decrypted|hash|hashes|hashed|sign|signs?|signed|compress|compresses|compressed|decompress|decompresses|decompressed|parse|parses|parsed|format|formats?|formatted)(?=[A-Z]|[^a-zA-Z]|$)/i;
|
|
30928
|
+
METHOD_DECL_REGEX = /(?:public|private|protected)\s+(?:static\s+)?[\w<>,\s\[\]]+?\s+(\w+)\s*\([^)]*\)\s*\{([^{}]*)\}/g;
|
|
30929
|
+
EMPTY_BODY_REGEX = /^\s*$/;
|
|
30930
|
+
RETURN_CONSTANT_REGEX = /^\s*return\s+(?:null|true|false|0|1|0L|0\.0|"")\s*;\s*$/;
|
|
30931
|
+
RETURN_INPUT_REGEX = /^\s*return\s+(\w+)\s*;\s*$/;
|
|
30932
|
+
JUST_THROW_REGEX = /^\s*throw\s+new\s+(?:UnsupportedOperationException|UnsupportedOperationException\([^)]*\)|RuntimeException\([^)]*\)|IllegalStateException\([^)]*\))\s*;\s*$/;
|
|
30933
|
+
javaSuspiciousImplementationRule = createRule({
|
|
30934
|
+
id: "java/suspicious-implementation",
|
|
30935
|
+
category: "security",
|
|
30936
|
+
severity: "high",
|
|
30937
|
+
aiSpecific: false,
|
|
30938
|
+
description: "function name suggests validation/encryption/auth but body is empty or trivially wrong \u2014 content mismatch",
|
|
30939
|
+
create(_context) {
|
|
30940
|
+
return {};
|
|
30941
|
+
},
|
|
30942
|
+
analyze(_context, facts) {
|
|
30943
|
+
const issues = [];
|
|
30944
|
+
const source = facts.v2?._source;
|
|
30945
|
+
if (!source) return issues;
|
|
30946
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30947
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30948
|
+
let m;
|
|
30949
|
+
METHOD_DECL_REGEX.lastIndex = 0;
|
|
30950
|
+
while ((m = METHOD_DECL_REGEX.exec(source)) !== null) {
|
|
30951
|
+
const methodName = m[1];
|
|
30952
|
+
const body = m[2];
|
|
30953
|
+
if (!STRONG_VERB_REGEX.test(methodName)) continue;
|
|
30954
|
+
let reason = null;
|
|
30955
|
+
if (EMPTY_BODY_REGEX.test(body)) {
|
|
30956
|
+
reason = "empty body";
|
|
30957
|
+
} else if (RETURN_CONSTANT_REGEX.test(body)) {
|
|
30958
|
+
reason = "returns a constant (null/true/false/0/1)";
|
|
30959
|
+
} else if (RETURN_INPUT_REGEX.test(body)) {
|
|
30960
|
+
reason = "returns the input unchanged (pass-through stub)";
|
|
30961
|
+
} else if (JUST_THROW_REGEX.test(body)) {
|
|
30962
|
+
reason = "throws UnsupportedOperationException \u2014 not implemented";
|
|
30963
|
+
}
|
|
30964
|
+
if (!reason) continue;
|
|
30965
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30966
|
+
issues.push({
|
|
30967
|
+
ruleId: "java/suspicious-implementation",
|
|
30968
|
+
category: "security",
|
|
30969
|
+
severity: "high",
|
|
30970
|
+
aiSpecific: false,
|
|
30971
|
+
message: `function ${methodName} (${reason}) \u2014 content-based mismatch`,
|
|
30972
|
+
line,
|
|
30973
|
+
column: 1,
|
|
30974
|
+
advice: `The function name "${methodName}" suggests a real validation/encryption/auth/filter operation, but the body is ${reason}. This is a content mismatch \u2014 the code's claimed behavior doesn't match its actual behavior. Real production code that ships this is a silent security failure (OWASP A04:2021 \u2014 Insecure Design). The fix is to either implement the operation or rename the function to reflect what it actually does. Reference: java/suspicious-implementation v0.35.0 (CoCoNUTS-inspired content-based detection).`
|
|
30975
|
+
});
|
|
30976
|
+
}
|
|
30977
|
+
return issues;
|
|
30978
|
+
}
|
|
30979
|
+
});
|
|
30980
|
+
}
|
|
30981
|
+
});
|
|
30982
|
+
|
|
30853
30983
|
// src/rules/java/system-out-println.ts
|
|
30854
30984
|
var SYSTEM_OUT_REGEX, REAL_LOGGING_IMPORT_REGEX, javaSystemOutPrintlnRule;
|
|
30855
30985
|
var init_system_out_println = __esm({
|
|
@@ -46963,7 +47093,9 @@ var init_builtins = __esm({
|
|
|
46963
47093
|
init_struct_tag_inconsistency();
|
|
46964
47094
|
init_command_injection();
|
|
46965
47095
|
init_hardcoded_credential();
|
|
47096
|
+
init_lost_stack_trace();
|
|
46966
47097
|
init_sql_string_concat();
|
|
47098
|
+
init_suspicious_implementation();
|
|
46967
47099
|
init_system_out_println();
|
|
46968
47100
|
init_thread_sleep_in_loop();
|
|
46969
47101
|
init_coroutine_global_scope();
|
|
@@ -47102,7 +47234,9 @@ var init_builtins = __esm({
|
|
|
47102
47234
|
goStructTagInconsistencyRule,
|
|
47103
47235
|
javaCommandInjectionRule,
|
|
47104
47236
|
javaHardcodedCredentialRule,
|
|
47237
|
+
javaLostStackTraceRule,
|
|
47105
47238
|
javaSqlStringConcatRule,
|
|
47239
|
+
javaSuspiciousImplementationRule,
|
|
47106
47240
|
javaSystemOutPrintlnRule,
|
|
47107
47241
|
javaThreadSleepInLoopRule,
|
|
47108
47242
|
kotlinCoroutineGlobalScopeRule,
|
|
@@ -51797,6 +51931,38 @@ var init_signal_strength = __esm({
|
|
|
51797
51931
|
_v9Precision: 0,
|
|
51798
51932
|
defaultOff: true
|
|
51799
51933
|
},
|
|
51934
|
+
"java/suspicious-implementation": {
|
|
51935
|
+
recall: 0,
|
|
51936
|
+
fpRate: 0,
|
|
51937
|
+
ratio: 0,
|
|
51938
|
+
precision: 0,
|
|
51939
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51940
|
+
verdict: "OK",
|
|
51941
|
+
_calibrationNote: "v0.35.0: NEW RULE \u2014 content-based detection (CoCoNUTS-inspired). Detects function names that claim a strong operation (validate, encrypt, hash, sanitize, check, verify, authenticate, etc.) but whose body is trivially empty, returns a constant, or returns the input unchanged. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (i.e. ON by default) and verdict=OK. The CoCoNUTS paper (2025) showed that style-based AI-detectors fail under paraphrasing; this rule looks at semantic content (function body behavior) rather than surface features. v9 calibration is pending \u2014 the rule was added at the end of the v0.34.X refinement series and has not been measured against the v9 corpus yet. The expected impact: low recall (most production code doesn't have these stubs) but high precision (when it fires, it's a real bug). The full v9 calibration is the next step for v0.35.0.x patches.",
|
|
51942
|
+
aiSpecific: false,
|
|
51943
|
+
_v9Verdict: "OK",
|
|
51944
|
+
_v9Lift: 0,
|
|
51945
|
+
_v9Recall: 0,
|
|
51946
|
+
_v9FpRate: 0,
|
|
51947
|
+
_v9Precision: 0,
|
|
51948
|
+
defaultOff: false
|
|
51949
|
+
},
|
|
51950
|
+
"java/lost-stack-trace": {
|
|
51951
|
+
recall: 0,
|
|
51952
|
+
fpRate: 0,
|
|
51953
|
+
ratio: 0,
|
|
51954
|
+
precision: 0,
|
|
51955
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51956
|
+
verdict: "OK",
|
|
51957
|
+
_calibrationNote: "v0.35.1: NEW RULE \u2014 Raidar-inspired content-based detection. The Raidar paper (ICLR 2024) showed that LLMs tend to 'polish' error handling by wrapping exceptions but losing the original cause; the inverse observation is that AI-generated code often has this pattern. This rule detects catch blocks that throw a new exception WITHOUT including the original exception as a cause \u2014 the original stack trace is lost. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (ON by default) and verdict=OK. v9 calibration is pending. The expected impact: low recall (most production code preserves the original exception) but very high precision (when it fires, it's a real bug \u2014 debugging without the stack trace is impossible).",
|
|
51958
|
+
aiSpecific: false,
|
|
51959
|
+
_v9Verdict: "OK",
|
|
51960
|
+
_v9Lift: 0,
|
|
51961
|
+
_v9Recall: 0,
|
|
51962
|
+
_v9FpRate: 0,
|
|
51963
|
+
_v9Precision: 0,
|
|
51964
|
+
defaultOff: false
|
|
51965
|
+
},
|
|
51800
51966
|
"swift/force-unwrap": {
|
|
51801
51967
|
recall: 0.10211,
|
|
51802
51968
|
fpRate: 0.21462,
|
|
@@ -60063,6 +60229,10 @@ var RULE_HINTS = {
|
|
|
60063
60229
|
"java/thread-sleep-in-loop": "Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the polling anti-pattern \u2014 ties up Tomcat/Jetty/Netty threads. (v0.30.0 \u2014 DORMANT, era-confounded ratio 0.97.)",
|
|
60064
60230
|
"java/system-out-println": "The file imports a real logger (SLF4J/Log4j2/java.util.logging) but uses System.out.println. Use the declared log object instead. (v0.31.0 \u2014 OK; ratio 1.73, refined from v0.30.)",
|
|
60065
60231
|
"java/command-injection": "Use ProcessBuilder with a List<String> of args (no shell parsing) and validate each arg. Runtime.exec() with concat is the canonical command-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT.)",
|
|
60232
|
+
// v0.35.0 — content-based detection (CoCoNUTS-inspired)
|
|
60233
|
+
"java/suspicious-implementation": "Function name claims validate/encrypt/hash/sanitize but body is empty, returns null/true, or returns the input. This is a content mismatch \u2014 the function's claimed behavior doesn't match its actual behavior. OWASP A04:2021.",
|
|
60234
|
+
// v0.35.1 — Raidar-inspired content-based detection
|
|
60235
|
+
"java/lost-stack-trace": 'catch block throws a new exception without the original cause \u2014 stack trace is lost. Pass the original exception as the second arg to the new exception\'s constructor: `throw new XxxException("msg", e)`.',
|
|
60066
60236
|
// v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
|
|
60067
60237
|
"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.)",
|
|
60068
60238
|
"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/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.35.1";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -30778,6 +30778,74 @@ var init_hardcoded_credential = __esm({
|
|
|
30778
30778
|
}
|
|
30779
30779
|
});
|
|
30780
30780
|
|
|
30781
|
+
// src/rules/java/lost-stack-trace.ts
|
|
30782
|
+
var THROW_NEW_REGEX, CATCH_REGEX, javaLostStackTraceRule;
|
|
30783
|
+
var init_lost_stack_trace = __esm({
|
|
30784
|
+
"src/rules/java/lost-stack-trace.ts"() {
|
|
30785
|
+
"use strict";
|
|
30786
|
+
init_rule();
|
|
30787
|
+
THROW_NEW_REGEX = /throw\s+new\s+(\w+(?:Exception|Error|Throwable))\s*\(([^)]*)\)/g;
|
|
30788
|
+
CATCH_REGEX = /catch\s*\(\s*(?:final\s+)?[\w<>,\s]+\s+(\w+)\s*\)\s*\{/g;
|
|
30789
|
+
javaLostStackTraceRule = createRule({
|
|
30790
|
+
id: "java/lost-stack-trace",
|
|
30791
|
+
category: "logic",
|
|
30792
|
+
severity: "medium",
|
|
30793
|
+
aiSpecific: false,
|
|
30794
|
+
description: "catch block throws a new exception without the original cause \u2014 stack trace is lost",
|
|
30795
|
+
create(_context) {
|
|
30796
|
+
return {};
|
|
30797
|
+
},
|
|
30798
|
+
analyze(_context, facts) {
|
|
30799
|
+
const issues = [];
|
|
30800
|
+
const source = facts.v2?._source;
|
|
30801
|
+
if (!source) return issues;
|
|
30802
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30803
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30804
|
+
const catchBlocks = [];
|
|
30805
|
+
let cm;
|
|
30806
|
+
CATCH_REGEX.lastIndex = 0;
|
|
30807
|
+
while ((cm = CATCH_REGEX.exec(source)) !== null) {
|
|
30808
|
+
const catchStart = cm.index;
|
|
30809
|
+
const exVar = cm[1];
|
|
30810
|
+
let depth = 1;
|
|
30811
|
+
let i = cm.index + cm[0].length;
|
|
30812
|
+
while (i < source.length && depth > 0) {
|
|
30813
|
+
const ch = source[i];
|
|
30814
|
+
if (ch === "{") depth++;
|
|
30815
|
+
else if (ch === "}") depth--;
|
|
30816
|
+
i++;
|
|
30817
|
+
}
|
|
30818
|
+
catchBlocks.push({ start: catchStart, end: i, exVar });
|
|
30819
|
+
}
|
|
30820
|
+
let m;
|
|
30821
|
+
THROW_NEW_REGEX.lastIndex = 0;
|
|
30822
|
+
while ((m = THROW_NEW_REGEX.exec(source)) !== null) {
|
|
30823
|
+
const throwPos = m.index;
|
|
30824
|
+
const args = m[2].trim();
|
|
30825
|
+
const enclosing = catchBlocks.find(
|
|
30826
|
+
(cb) => cb.start < throwPos && throwPos < cb.end
|
|
30827
|
+
);
|
|
30828
|
+
if (!enclosing) continue;
|
|
30829
|
+
const exVarPattern = new RegExp(`\\b${enclosing.exVar}\\b`);
|
|
30830
|
+
if (exVarPattern.test(args)) continue;
|
|
30831
|
+
const line = source.slice(0, throwPos).split("\n").length;
|
|
30832
|
+
issues.push({
|
|
30833
|
+
ruleId: "java/lost-stack-trace",
|
|
30834
|
+
category: "logic",
|
|
30835
|
+
severity: "medium",
|
|
30836
|
+
aiSpecific: false,
|
|
30837
|
+
message: `throw new ${m[1]}(${args}) \u2014 original exception \`${enclosing.exVar}\` is not included as cause`,
|
|
30838
|
+
line,
|
|
30839
|
+
column: 1,
|
|
30840
|
+
advice: `The catch block declares exception variable \`${enclosing.exVar}\` but the throw statement doesn't include it as a cause. The original stack trace is lost. Fix: \`throw new ${m[1]}("...", ${enclosing.exVar})\` \u2014 the second argument to the exception constructor is the cause, which Java's Throwable framework preserves in the stack trace chain. Reference: java/lost-stack-trace v0.35.1 (Raidar-inspired content-based detection of AI-polished error handling).`
|
|
30841
|
+
});
|
|
30842
|
+
}
|
|
30843
|
+
return issues;
|
|
30844
|
+
}
|
|
30845
|
+
});
|
|
30846
|
+
}
|
|
30847
|
+
});
|
|
30848
|
+
|
|
30781
30849
|
// src/rules/java/sql-string-concat.ts
|
|
30782
30850
|
var SQL_KEYWORD_REGEX, UNSAFE_REGEX, SAFE_REGEX, javaSqlStringConcatRule;
|
|
30783
30851
|
var init_sql_string_concat = __esm({
|
|
@@ -30831,6 +30899,68 @@ var init_sql_string_concat = __esm({
|
|
|
30831
30899
|
}
|
|
30832
30900
|
});
|
|
30833
30901
|
|
|
30902
|
+
// src/rules/java/suspicious-implementation.ts
|
|
30903
|
+
var STRONG_VERB_REGEX, METHOD_DECL_REGEX, EMPTY_BODY_REGEX, RETURN_CONSTANT_REGEX, RETURN_INPUT_REGEX, JUST_THROW_REGEX, javaSuspiciousImplementationRule;
|
|
30904
|
+
var init_suspicious_implementation = __esm({
|
|
30905
|
+
"src/rules/java/suspicious-implementation.ts"() {
|
|
30906
|
+
"use strict";
|
|
30907
|
+
init_rule();
|
|
30908
|
+
STRONG_VERB_REGEX = /(?:validate|validates|validated|verifies?|verify|check|checks?|checked|ensures?|ensure|ensured|verifies|verified|sanitize|sanitizes|sanitized|escape|escapes|escaped|authenticate|authenticates|authenticated|authorize|authorizes|authorized|audit|audits|audited|inspect|inspects|inspected|filter|filters?|filtered|normalize|normalizes|normalized|encrypt|encrypts|encrypted|decrypt|decrypts|decrypted|hash|hashes|hashed|sign|signs?|signed|compress|compresses|compressed|decompress|decompresses|decompressed|parse|parses|parsed|format|formats?|formatted)(?=[A-Z]|[^a-zA-Z]|$)/i;
|
|
30909
|
+
METHOD_DECL_REGEX = /(?:public|private|protected)\s+(?:static\s+)?[\w<>,\s\[\]]+?\s+(\w+)\s*\([^)]*\)\s*\{([^{}]*)\}/g;
|
|
30910
|
+
EMPTY_BODY_REGEX = /^\s*$/;
|
|
30911
|
+
RETURN_CONSTANT_REGEX = /^\s*return\s+(?:null|true|false|0|1|0L|0\.0|"")\s*;\s*$/;
|
|
30912
|
+
RETURN_INPUT_REGEX = /^\s*return\s+(\w+)\s*;\s*$/;
|
|
30913
|
+
JUST_THROW_REGEX = /^\s*throw\s+new\s+(?:UnsupportedOperationException|UnsupportedOperationException\([^)]*\)|RuntimeException\([^)]*\)|IllegalStateException\([^)]*\))\s*;\s*$/;
|
|
30914
|
+
javaSuspiciousImplementationRule = createRule({
|
|
30915
|
+
id: "java/suspicious-implementation",
|
|
30916
|
+
category: "security",
|
|
30917
|
+
severity: "high",
|
|
30918
|
+
aiSpecific: false,
|
|
30919
|
+
description: "function name suggests validation/encryption/auth but body is empty or trivially wrong \u2014 content mismatch",
|
|
30920
|
+
create(_context) {
|
|
30921
|
+
return {};
|
|
30922
|
+
},
|
|
30923
|
+
analyze(_context, facts) {
|
|
30924
|
+
const issues = [];
|
|
30925
|
+
const source = facts.v2?._source;
|
|
30926
|
+
if (!source) return issues;
|
|
30927
|
+
if (!/\.java$/i.test(facts.filePath)) return issues;
|
|
30928
|
+
if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
|
|
30929
|
+
let m;
|
|
30930
|
+
METHOD_DECL_REGEX.lastIndex = 0;
|
|
30931
|
+
while ((m = METHOD_DECL_REGEX.exec(source)) !== null) {
|
|
30932
|
+
const methodName = m[1];
|
|
30933
|
+
const body = m[2];
|
|
30934
|
+
if (!STRONG_VERB_REGEX.test(methodName)) continue;
|
|
30935
|
+
let reason = null;
|
|
30936
|
+
if (EMPTY_BODY_REGEX.test(body)) {
|
|
30937
|
+
reason = "empty body";
|
|
30938
|
+
} else if (RETURN_CONSTANT_REGEX.test(body)) {
|
|
30939
|
+
reason = "returns a constant (null/true/false/0/1)";
|
|
30940
|
+
} else if (RETURN_INPUT_REGEX.test(body)) {
|
|
30941
|
+
reason = "returns the input unchanged (pass-through stub)";
|
|
30942
|
+
} else if (JUST_THROW_REGEX.test(body)) {
|
|
30943
|
+
reason = "throws UnsupportedOperationException \u2014 not implemented";
|
|
30944
|
+
}
|
|
30945
|
+
if (!reason) continue;
|
|
30946
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
30947
|
+
issues.push({
|
|
30948
|
+
ruleId: "java/suspicious-implementation",
|
|
30949
|
+
category: "security",
|
|
30950
|
+
severity: "high",
|
|
30951
|
+
aiSpecific: false,
|
|
30952
|
+
message: `function ${methodName} (${reason}) \u2014 content-based mismatch`,
|
|
30953
|
+
line,
|
|
30954
|
+
column: 1,
|
|
30955
|
+
advice: `The function name "${methodName}" suggests a real validation/encryption/auth/filter operation, but the body is ${reason}. This is a content mismatch \u2014 the code's claimed behavior doesn't match its actual behavior. Real production code that ships this is a silent security failure (OWASP A04:2021 \u2014 Insecure Design). The fix is to either implement the operation or rename the function to reflect what it actually does. Reference: java/suspicious-implementation v0.35.0 (CoCoNUTS-inspired content-based detection).`
|
|
30956
|
+
});
|
|
30957
|
+
}
|
|
30958
|
+
return issues;
|
|
30959
|
+
}
|
|
30960
|
+
});
|
|
30961
|
+
}
|
|
30962
|
+
});
|
|
30963
|
+
|
|
30834
30964
|
// src/rules/java/system-out-println.ts
|
|
30835
30965
|
var SYSTEM_OUT_REGEX, REAL_LOGGING_IMPORT_REGEX, javaSystemOutPrintlnRule;
|
|
30836
30966
|
var init_system_out_println = __esm({
|
|
@@ -46944,7 +47074,9 @@ var init_builtins = __esm({
|
|
|
46944
47074
|
init_struct_tag_inconsistency();
|
|
46945
47075
|
init_command_injection();
|
|
46946
47076
|
init_hardcoded_credential();
|
|
47077
|
+
init_lost_stack_trace();
|
|
46947
47078
|
init_sql_string_concat();
|
|
47079
|
+
init_suspicious_implementation();
|
|
46948
47080
|
init_system_out_println();
|
|
46949
47081
|
init_thread_sleep_in_loop();
|
|
46950
47082
|
init_coroutine_global_scope();
|
|
@@ -47083,7 +47215,9 @@ var init_builtins = __esm({
|
|
|
47083
47215
|
goStructTagInconsistencyRule,
|
|
47084
47216
|
javaCommandInjectionRule,
|
|
47085
47217
|
javaHardcodedCredentialRule,
|
|
47218
|
+
javaLostStackTraceRule,
|
|
47086
47219
|
javaSqlStringConcatRule,
|
|
47220
|
+
javaSuspiciousImplementationRule,
|
|
47087
47221
|
javaSystemOutPrintlnRule,
|
|
47088
47222
|
javaThreadSleepInLoopRule,
|
|
47089
47223
|
kotlinCoroutineGlobalScopeRule,
|
|
@@ -51775,6 +51909,38 @@ var init_signal_strength = __esm({
|
|
|
51775
51909
|
_v9Precision: 0,
|
|
51776
51910
|
defaultOff: true
|
|
51777
51911
|
},
|
|
51912
|
+
"java/suspicious-implementation": {
|
|
51913
|
+
recall: 0,
|
|
51914
|
+
fpRate: 0,
|
|
51915
|
+
ratio: 0,
|
|
51916
|
+
precision: 0,
|
|
51917
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51918
|
+
verdict: "OK",
|
|
51919
|
+
_calibrationNote: "v0.35.0: NEW RULE \u2014 content-based detection (CoCoNUTS-inspired). Detects function names that claim a strong operation (validate, encrypt, hash, sanitize, check, verify, authenticate, etc.) but whose body is trivially empty, returns a constant, or returns the input unchanged. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (i.e. ON by default) and verdict=OK. The CoCoNUTS paper (2025) showed that style-based AI-detectors fail under paraphrasing; this rule looks at semantic content (function body behavior) rather than surface features. v9 calibration is pending \u2014 the rule was added at the end of the v0.34.X refinement series and has not been measured against the v9 corpus yet. The expected impact: low recall (most production code doesn't have these stubs) but high precision (when it fires, it's a real bug). The full v9 calibration is the next step for v0.35.0.x patches.",
|
|
51920
|
+
aiSpecific: false,
|
|
51921
|
+
_v9Verdict: "OK",
|
|
51922
|
+
_v9Lift: 0,
|
|
51923
|
+
_v9Recall: 0,
|
|
51924
|
+
_v9FpRate: 0,
|
|
51925
|
+
_v9Precision: 0,
|
|
51926
|
+
defaultOff: false
|
|
51927
|
+
},
|
|
51928
|
+
"java/lost-stack-trace": {
|
|
51929
|
+
recall: 0,
|
|
51930
|
+
fpRate: 0,
|
|
51931
|
+
ratio: 0,
|
|
51932
|
+
precision: 0,
|
|
51933
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
51934
|
+
verdict: "OK",
|
|
51935
|
+
_calibrationNote: "v0.35.1: NEW RULE \u2014 Raidar-inspired content-based detection. The Raidar paper (ICLR 2024) showed that LLMs tend to 'polish' error handling by wrapping exceptions but losing the original cause; the inverse observation is that AI-generated code often has this pattern. This rule detects catch blocks that throw a new exception WITHOUT including the original exception as a cause \u2014 the original stack trace is lost. The rule is a real engineering defect detector (not AI fingerprint), so it is defaultOff=false (ON by default) and verdict=OK. v9 calibration is pending. The expected impact: low recall (most production code preserves the original exception) but very high precision (when it fires, it's a real bug \u2014 debugging without the stack trace is impossible).",
|
|
51936
|
+
aiSpecific: false,
|
|
51937
|
+
_v9Verdict: "OK",
|
|
51938
|
+
_v9Lift: 0,
|
|
51939
|
+
_v9Recall: 0,
|
|
51940
|
+
_v9FpRate: 0,
|
|
51941
|
+
_v9Precision: 0,
|
|
51942
|
+
defaultOff: false
|
|
51943
|
+
},
|
|
51778
51944
|
"swift/force-unwrap": {
|
|
51779
51945
|
recall: 0.10211,
|
|
51780
51946
|
fpRate: 0.21462,
|
|
@@ -59962,6 +60128,10 @@ var RULE_HINTS = {
|
|
|
59962
60128
|
"java/thread-sleep-in-loop": "Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the polling anti-pattern \u2014 ties up Tomcat/Jetty/Netty threads. (v0.30.0 \u2014 DORMANT, era-confounded ratio 0.97.)",
|
|
59963
60129
|
"java/system-out-println": "The file imports a real logger (SLF4J/Log4j2/java.util.logging) but uses System.out.println. Use the declared log object instead. (v0.31.0 \u2014 OK; ratio 1.73, refined from v0.30.)",
|
|
59964
60130
|
"java/command-injection": "Use ProcessBuilder with a List<String> of args (no shell parsing) and validate each arg. Runtime.exec() with concat is the canonical command-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT.)",
|
|
60131
|
+
// v0.35.0 — content-based detection (CoCoNUTS-inspired)
|
|
60132
|
+
"java/suspicious-implementation": "Function name claims validate/encrypt/hash/sanitize but body is empty, returns null/true, or returns the input. This is a content mismatch \u2014 the function's claimed behavior doesn't match its actual behavior. OWASP A04:2021.",
|
|
60133
|
+
// v0.35.1 — Raidar-inspired content-based detection
|
|
60134
|
+
"java/lost-stack-trace": 'catch block throws a new exception without the original cause \u2014 stack trace is lost. Pass the original exception as the second arg to the new exception\'s constructor: `throw new XxxException("msg", e)`.',
|
|
59965
60135
|
// v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
|
|
59966
60136
|
"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.)",
|
|
59967
60137
|
"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.35.1",
|
|
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": {
|