@harness-engineering/core 0.15.0 → 0.16.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/README.md +1 -0
- package/dist/architecture/matchers.d.mts +1 -1
- package/dist/architecture/matchers.d.ts +1 -1
- package/dist/index.d.mts +334 -85
- package/dist/index.d.ts +334 -85
- package/dist/index.js +1225 -72
- package/dist/index.mjs +1192 -68
- package/dist/{matchers-Dj1t5vpg.d.mts → matchers-D20x48U9.d.mts} +46 -46
- package/dist/{matchers-Dj1t5vpg.d.ts → matchers-D20x48U9.d.ts} +46 -46
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ __export(index_exports, {
|
|
|
45
45
|
BlueprintGenerator: () => BlueprintGenerator,
|
|
46
46
|
BundleConstraintsSchema: () => BundleConstraintsSchema,
|
|
47
47
|
BundleSchema: () => BundleSchema,
|
|
48
|
+
CACHE_TTL_MS: () => CACHE_TTL_MS,
|
|
48
49
|
COMPLIANCE_DESCRIPTOR: () => COMPLIANCE_DESCRIPTOR,
|
|
49
50
|
CategoryBaselineSchema: () => CategoryBaselineSchema,
|
|
50
51
|
CategoryRegressionSchema: () => CategoryRegressionSchema,
|
|
@@ -62,6 +63,7 @@ __export(index_exports, {
|
|
|
62
63
|
DEFAULT_SECURITY_CONFIG: () => DEFAULT_SECURITY_CONFIG,
|
|
63
64
|
DEFAULT_STATE: () => DEFAULT_STATE,
|
|
64
65
|
DEFAULT_STREAM_INDEX: () => DEFAULT_STREAM_INDEX,
|
|
66
|
+
DESTRUCTIVE_BASH: () => DESTRUCTIVE_BASH,
|
|
65
67
|
DepDepthCollector: () => DepDepthCollector,
|
|
66
68
|
EXTENSION_MAP: () => EXTENSION_MAP,
|
|
67
69
|
EmitInteractionInputSchema: () => EmitInteractionInputSchema,
|
|
@@ -76,6 +78,7 @@ __export(index_exports, {
|
|
|
76
78
|
HandoffSchema: () => HandoffSchema,
|
|
77
79
|
HarnessStateSchema: () => HarnessStateSchema,
|
|
78
80
|
InteractionTypeSchema: () => InteractionTypeSchema,
|
|
81
|
+
LITELLM_PRICING_URL: () => LITELLM_PRICING_URL,
|
|
79
82
|
LayerViolationCollector: () => LayerViolationCollector,
|
|
80
83
|
LockfilePackageSchema: () => LockfilePackageSchema,
|
|
81
84
|
LockfileSchema: () => LockfileSchema,
|
|
@@ -92,6 +95,7 @@ __export(index_exports, {
|
|
|
92
95
|
RegressionDetector: () => RegressionDetector,
|
|
93
96
|
RuleRegistry: () => RuleRegistry,
|
|
94
97
|
SECURITY_DESCRIPTOR: () => SECURITY_DESCRIPTOR,
|
|
98
|
+
STALENESS_WARNING_DAYS: () => STALENESS_WARNING_DAYS,
|
|
95
99
|
SecurityConfigSchema: () => SecurityConfigSchema,
|
|
96
100
|
SecurityScanner: () => SecurityScanner,
|
|
97
101
|
SharableBoundaryConfigSchema: () => SharableBoundaryConfigSchema,
|
|
@@ -108,6 +112,8 @@ __export(index_exports, {
|
|
|
108
112
|
ViolationSchema: () => ViolationSchema,
|
|
109
113
|
addProvenance: () => addProvenance,
|
|
110
114
|
agentConfigRules: () => agentConfigRules,
|
|
115
|
+
aggregateByDay: () => aggregateByDay,
|
|
116
|
+
aggregateBySession: () => aggregateBySession,
|
|
111
117
|
analyzeDiff: () => analyzeDiff,
|
|
112
118
|
analyzeLearningPatterns: () => analyzeLearningPatterns,
|
|
113
119
|
appendFailure: () => appendFailure,
|
|
@@ -126,13 +132,18 @@ __export(index_exports, {
|
|
|
126
132
|
buildDependencyGraph: () => buildDependencyGraph,
|
|
127
133
|
buildExclusionSet: () => buildExclusionSet,
|
|
128
134
|
buildSnapshot: () => buildSnapshot,
|
|
135
|
+
calculateCost: () => calculateCost,
|
|
129
136
|
checkDocCoverage: () => checkDocCoverage,
|
|
130
137
|
checkEligibility: () => checkEligibility,
|
|
131
138
|
checkEvidenceCoverage: () => checkEvidenceCoverage,
|
|
139
|
+
checkTaint: () => checkTaint,
|
|
132
140
|
classifyFinding: () => classifyFinding,
|
|
133
141
|
clearEventHashCache: () => clearEventHashCache,
|
|
134
142
|
clearFailuresCache: () => clearFailuresCache,
|
|
135
143
|
clearLearningsCache: () => clearLearningsCache,
|
|
144
|
+
clearTaint: () => clearTaint,
|
|
145
|
+
computeOverallSeverity: () => computeOverallSeverity,
|
|
146
|
+
computeScanExitCode: () => computeScanExitCode,
|
|
136
147
|
configureFeedback: () => configureFeedback,
|
|
137
148
|
constraintRuleId: () => constraintRuleId,
|
|
138
149
|
contextBudget: () => contextBudget,
|
|
@@ -187,35 +198,48 @@ __export(index_exports, {
|
|
|
187
198
|
getActionEmitter: () => getActionEmitter,
|
|
188
199
|
getExitCode: () => getExitCode,
|
|
189
200
|
getFeedbackConfig: () => getFeedbackConfig,
|
|
201
|
+
getInjectionPatterns: () => getInjectionPatterns,
|
|
202
|
+
getModelPrice: () => getModelPrice,
|
|
190
203
|
getOutline: () => getOutline,
|
|
191
204
|
getParser: () => getParser,
|
|
192
205
|
getPhaseCategories: () => getPhaseCategories,
|
|
193
206
|
getStreamForBranch: () => getStreamForBranch,
|
|
207
|
+
getTaintFilePath: () => getTaintFilePath,
|
|
194
208
|
getUpdateNotification: () => getUpdateNotification,
|
|
195
209
|
goRules: () => goRules,
|
|
196
210
|
injectionRules: () => injectionRules,
|
|
211
|
+
insecureDefaultsRules: () => insecureDefaultsRules,
|
|
212
|
+
isDuplicateFinding: () => isDuplicateFinding,
|
|
197
213
|
isSmallSuggestion: () => isSmallSuggestion,
|
|
198
214
|
isUpdateCheckEnabled: () => isUpdateCheckEnabled,
|
|
199
215
|
listActiveSessions: () => listActiveSessions,
|
|
200
216
|
listStreams: () => listStreams,
|
|
217
|
+
listTaintedSessions: () => listTaintedSessions,
|
|
201
218
|
loadBudgetedLearnings: () => loadBudgetedLearnings,
|
|
202
219
|
loadEvents: () => loadEvents,
|
|
203
220
|
loadFailures: () => loadFailures,
|
|
204
221
|
loadHandoff: () => loadHandoff,
|
|
205
222
|
loadIndexEntries: () => loadIndexEntries,
|
|
223
|
+
loadPricingData: () => loadPricingData,
|
|
206
224
|
loadRelevantLearnings: () => loadRelevantLearnings,
|
|
207
225
|
loadSessionSummary: () => loadSessionSummary,
|
|
208
226
|
loadState: () => loadState,
|
|
209
227
|
loadStreamIndex: () => loadStreamIndex,
|
|
210
228
|
logAgentAction: () => logAgentAction,
|
|
229
|
+
mapInjectionFindings: () => mapInjectionFindings,
|
|
230
|
+
mapSecurityFindings: () => mapSecurityFindings,
|
|
231
|
+
mapSecuritySeverity: () => mapSecuritySeverity,
|
|
211
232
|
mcpRules: () => mcpRules,
|
|
212
233
|
migrateToStreams: () => migrateToStreams,
|
|
213
234
|
networkRules: () => networkRules,
|
|
214
235
|
nodeRules: () => nodeRules,
|
|
236
|
+
parseCCRecords: () => parseCCRecords,
|
|
215
237
|
parseDateFromEntry: () => parseDateFromEntry,
|
|
216
238
|
parseDiff: () => parseDiff,
|
|
217
239
|
parseFile: () => parseFile,
|
|
218
240
|
parseFrontmatter: () => parseFrontmatter,
|
|
241
|
+
parseHarnessIgnore: () => parseHarnessIgnore,
|
|
242
|
+
parseLiteLLMData: () => parseLiteLLMData,
|
|
219
243
|
parseManifest: () => parseManifest,
|
|
220
244
|
parseRoadmap: () => parseRoadmap,
|
|
221
245
|
parseSecurityConfig: () => parseSecurityConfig,
|
|
@@ -226,9 +250,11 @@ __export(index_exports, {
|
|
|
226
250
|
pruneLearnings: () => pruneLearnings,
|
|
227
251
|
reactRules: () => reactRules,
|
|
228
252
|
readCheckState: () => readCheckState,
|
|
253
|
+
readCostRecords: () => readCostRecords,
|
|
229
254
|
readLockfile: () => readLockfile,
|
|
230
255
|
readSessionSection: () => readSessionSection,
|
|
231
256
|
readSessionSections: () => readSessionSections,
|
|
257
|
+
readTaint: () => readTaint,
|
|
232
258
|
removeContributions: () => removeContributions,
|
|
233
259
|
removeProvenance: () => removeProvenance,
|
|
234
260
|
requestMultiplePeerReviews: () => requestMultiplePeerReviews,
|
|
@@ -255,11 +281,13 @@ __export(index_exports, {
|
|
|
255
281
|
saveHandoff: () => saveHandoff,
|
|
256
282
|
saveState: () => saveState,
|
|
257
283
|
saveStreamIndex: () => saveStreamIndex,
|
|
284
|
+
scanForInjection: () => scanForInjection,
|
|
258
285
|
scopeContext: () => scopeContext,
|
|
259
286
|
searchSymbols: () => searchSymbols,
|
|
260
287
|
secretRules: () => secretRules,
|
|
261
288
|
serializeRoadmap: () => serializeRoadmap,
|
|
262
289
|
setActiveStream: () => setActiveStream,
|
|
290
|
+
sharpEdgesRules: () => sharpEdgesRules,
|
|
263
291
|
shouldRunCheck: () => shouldRunCheck,
|
|
264
292
|
spawnBackgroundCheck: () => spawnBackgroundCheck,
|
|
265
293
|
syncConstraintNodes: () => syncConstraintNodes,
|
|
@@ -284,6 +312,7 @@ __export(index_exports, {
|
|
|
284
312
|
writeConfig: () => writeConfig,
|
|
285
313
|
writeLockfile: () => writeLockfile,
|
|
286
314
|
writeSessionSummary: () => writeSessionSummary,
|
|
315
|
+
writeTaint: () => writeTaint,
|
|
287
316
|
xssRules: () => xssRules
|
|
288
317
|
});
|
|
289
318
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -307,17 +336,17 @@ var import_node_path = require("path");
|
|
|
307
336
|
var import_glob = require("glob");
|
|
308
337
|
var accessAsync = (0, import_util.promisify)(import_fs.access);
|
|
309
338
|
var readFileAsync = (0, import_util.promisify)(import_fs.readFile);
|
|
310
|
-
async function fileExists(
|
|
339
|
+
async function fileExists(path26) {
|
|
311
340
|
try {
|
|
312
|
-
await accessAsync(
|
|
341
|
+
await accessAsync(path26, import_fs.constants.F_OK);
|
|
313
342
|
return true;
|
|
314
343
|
} catch {
|
|
315
344
|
return false;
|
|
316
345
|
}
|
|
317
346
|
}
|
|
318
|
-
async function readFileContent(
|
|
347
|
+
async function readFileContent(path26) {
|
|
319
348
|
try {
|
|
320
|
-
const content = await readFileAsync(
|
|
349
|
+
const content = await readFileAsync(path26, "utf-8");
|
|
321
350
|
return (0, import_types.Ok)(content);
|
|
322
351
|
} catch (error) {
|
|
323
352
|
return (0, import_types.Err)(error);
|
|
@@ -368,15 +397,15 @@ function validateConfig(data, schema) {
|
|
|
368
397
|
let message = "Configuration validation failed";
|
|
369
398
|
const suggestions = [];
|
|
370
399
|
if (firstError) {
|
|
371
|
-
const
|
|
372
|
-
const pathDisplay =
|
|
400
|
+
const path26 = firstError.path.join(".");
|
|
401
|
+
const pathDisplay = path26 ? ` at "${path26}"` : "";
|
|
373
402
|
if (firstError.code === "invalid_type") {
|
|
374
403
|
const received = firstError.received;
|
|
375
404
|
const expected = firstError.expected;
|
|
376
405
|
if (received === "undefined") {
|
|
377
406
|
code = "MISSING_FIELD";
|
|
378
407
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
379
|
-
suggestions.push(`Field "${
|
|
408
|
+
suggestions.push(`Field "${path26}" is required and must be of type "${expected}"`);
|
|
380
409
|
} else {
|
|
381
410
|
code = "INVALID_TYPE";
|
|
382
411
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -592,27 +621,27 @@ function extractSections(content) {
|
|
|
592
621
|
}
|
|
593
622
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
594
623
|
}
|
|
595
|
-
function isExternalLink(
|
|
596
|
-
return
|
|
624
|
+
function isExternalLink(path26) {
|
|
625
|
+
return path26.startsWith("http://") || path26.startsWith("https://") || path26.startsWith("#") || path26.startsWith("mailto:");
|
|
597
626
|
}
|
|
598
627
|
function resolveLinkPath(linkPath, baseDir) {
|
|
599
628
|
return linkPath.startsWith(".") ? (0, import_path.join)(baseDir, linkPath) : linkPath;
|
|
600
629
|
}
|
|
601
|
-
async function validateAgentsMap(
|
|
602
|
-
const contentResult = await readFileContent(
|
|
630
|
+
async function validateAgentsMap(path26 = "./AGENTS.md") {
|
|
631
|
+
const contentResult = await readFileContent(path26);
|
|
603
632
|
if (!contentResult.ok) {
|
|
604
633
|
return (0, import_types.Err)(
|
|
605
634
|
createError(
|
|
606
635
|
"PARSE_ERROR",
|
|
607
636
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
608
|
-
{ path:
|
|
637
|
+
{ path: path26 },
|
|
609
638
|
["Ensure the file exists", "Check file permissions"]
|
|
610
639
|
)
|
|
611
640
|
);
|
|
612
641
|
}
|
|
613
642
|
const content = contentResult.value;
|
|
614
643
|
const sections = extractSections(content);
|
|
615
|
-
const baseDir = (0, import_path.dirname)(
|
|
644
|
+
const baseDir = (0, import_path.dirname)(path26);
|
|
616
645
|
const sectionTitles = sections.map((s) => s.title);
|
|
617
646
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
618
647
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -753,8 +782,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
753
782
|
|
|
754
783
|
// src/context/knowledge-map.ts
|
|
755
784
|
var import_path3 = require("path");
|
|
756
|
-
function suggestFix(
|
|
757
|
-
const targetName = (0, import_path3.basename)(
|
|
785
|
+
function suggestFix(path26, existingFiles) {
|
|
786
|
+
const targetName = (0, import_path3.basename)(path26).toLowerCase();
|
|
758
787
|
const similar = existingFiles.find((file) => {
|
|
759
788
|
const fileName = (0, import_path3.basename)(file).toLowerCase();
|
|
760
789
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -762,7 +791,7 @@ function suggestFix(path23, existingFiles) {
|
|
|
762
791
|
if (similar) {
|
|
763
792
|
return `Did you mean "${similar}"?`;
|
|
764
793
|
}
|
|
765
|
-
return `Create the file "${
|
|
794
|
+
return `Create the file "${path26}" or remove the link`;
|
|
766
795
|
}
|
|
767
796
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
768
797
|
const agentsPath = (0, import_path3.join)(rootDir, "AGENTS.md");
|
|
@@ -1368,8 +1397,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
1368
1397
|
return (0, import_types.Ok)(result.data);
|
|
1369
1398
|
}
|
|
1370
1399
|
const suggestions = result.error.issues.map((issue) => {
|
|
1371
|
-
const
|
|
1372
|
-
return
|
|
1400
|
+
const path26 = issue.path.join(".");
|
|
1401
|
+
return path26 ? `${path26}: ${issue.message}` : issue.message;
|
|
1373
1402
|
});
|
|
1374
1403
|
return (0, import_types.Err)(
|
|
1375
1404
|
createError(
|
|
@@ -2001,11 +2030,11 @@ function processExportListSpecifiers(exportDecl, exports2) {
|
|
|
2001
2030
|
var TypeScriptParser = class {
|
|
2002
2031
|
name = "typescript";
|
|
2003
2032
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
2004
|
-
async parseFile(
|
|
2005
|
-
const contentResult = await readFileContent(
|
|
2033
|
+
async parseFile(path26) {
|
|
2034
|
+
const contentResult = await readFileContent(path26);
|
|
2006
2035
|
if (!contentResult.ok) {
|
|
2007
2036
|
return (0, import_types.Err)(
|
|
2008
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
2037
|
+
createParseError("NOT_FOUND", `File not found: ${path26}`, { path: path26 }, [
|
|
2009
2038
|
"Check that the file exists",
|
|
2010
2039
|
"Verify the path is correct"
|
|
2011
2040
|
])
|
|
@@ -2015,7 +2044,7 @@ var TypeScriptParser = class {
|
|
|
2015
2044
|
const ast = (0, import_typescript_estree.parse)(contentResult.value, {
|
|
2016
2045
|
loc: true,
|
|
2017
2046
|
range: true,
|
|
2018
|
-
jsx:
|
|
2047
|
+
jsx: path26.endsWith(".tsx"),
|
|
2019
2048
|
errorOnUnknownASTType: false
|
|
2020
2049
|
});
|
|
2021
2050
|
return (0, import_types.Ok)({
|
|
@@ -2026,7 +2055,7 @@ var TypeScriptParser = class {
|
|
|
2026
2055
|
} catch (e) {
|
|
2027
2056
|
const error = e;
|
|
2028
2057
|
return (0, import_types.Err)(
|
|
2029
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
2058
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path26}: ${error.message}`, { path: path26 }, [
|
|
2030
2059
|
"Check for syntax errors in the file",
|
|
2031
2060
|
"Ensure valid TypeScript syntax"
|
|
2032
2061
|
])
|
|
@@ -2211,22 +2240,22 @@ function extractInlineRefs(content) {
|
|
|
2211
2240
|
}
|
|
2212
2241
|
return refs;
|
|
2213
2242
|
}
|
|
2214
|
-
async function parseDocumentationFile(
|
|
2215
|
-
const contentResult = await readFileContent(
|
|
2243
|
+
async function parseDocumentationFile(path26) {
|
|
2244
|
+
const contentResult = await readFileContent(path26);
|
|
2216
2245
|
if (!contentResult.ok) {
|
|
2217
2246
|
return (0, import_types.Err)(
|
|
2218
2247
|
createEntropyError(
|
|
2219
2248
|
"PARSE_ERROR",
|
|
2220
|
-
`Failed to read documentation file: ${
|
|
2221
|
-
{ file:
|
|
2249
|
+
`Failed to read documentation file: ${path26}`,
|
|
2250
|
+
{ file: path26 },
|
|
2222
2251
|
["Check that the file exists"]
|
|
2223
2252
|
)
|
|
2224
2253
|
);
|
|
2225
2254
|
}
|
|
2226
2255
|
const content = contentResult.value;
|
|
2227
|
-
const type =
|
|
2256
|
+
const type = path26.endsWith(".md") ? "markdown" : "text";
|
|
2228
2257
|
return (0, import_types.Ok)({
|
|
2229
|
-
path:
|
|
2258
|
+
path: path26,
|
|
2230
2259
|
type,
|
|
2231
2260
|
content,
|
|
2232
2261
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -9074,6 +9103,208 @@ var mcpRules = [
|
|
|
9074
9103
|
}
|
|
9075
9104
|
];
|
|
9076
9105
|
|
|
9106
|
+
// src/security/rules/insecure-defaults.ts
|
|
9107
|
+
var insecureDefaultsRules = [
|
|
9108
|
+
{
|
|
9109
|
+
id: "SEC-DEF-001",
|
|
9110
|
+
name: "Security-Sensitive Fallback to Hardcoded Default",
|
|
9111
|
+
category: "insecure-defaults",
|
|
9112
|
+
severity: "warning",
|
|
9113
|
+
confidence: "medium",
|
|
9114
|
+
patterns: [
|
|
9115
|
+
/(?:SECRET|KEY|TOKEN|PASSWORD|SALT|PEPPER|SIGNING|ENCRYPTION|AUTH|JWT|SESSION).*(?:\|\||\?\?)\s*['"][^'"]+['"]/i
|
|
9116
|
+
],
|
|
9117
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9118
|
+
message: "Security-sensitive variable falls back to a hardcoded default when env var is missing",
|
|
9119
|
+
remediation: "Throw an error if the env var is missing instead of falling back to a default. Use a startup validation check.",
|
|
9120
|
+
references: ["CWE-1188"]
|
|
9121
|
+
},
|
|
9122
|
+
{
|
|
9123
|
+
id: "SEC-DEF-002",
|
|
9124
|
+
name: "TLS/SSL Disabled by Default",
|
|
9125
|
+
category: "insecure-defaults",
|
|
9126
|
+
severity: "warning",
|
|
9127
|
+
confidence: "medium",
|
|
9128
|
+
patterns: [
|
|
9129
|
+
/(?:tls|ssl|https|secure)\s*(?:=|:)\s*(?:false|config\??\.\w+\s*(?:\?\?|&&|\|\|)\s*false)/i
|
|
9130
|
+
],
|
|
9131
|
+
fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
|
|
9132
|
+
message: "Security feature defaults to disabled; missing configuration degrades to insecure mode",
|
|
9133
|
+
remediation: "Default security features to enabled (true). Require explicit opt-out, not opt-in.",
|
|
9134
|
+
references: ["CWE-1188"]
|
|
9135
|
+
},
|
|
9136
|
+
{
|
|
9137
|
+
id: "SEC-DEF-003",
|
|
9138
|
+
name: "Swallowed Authentication/Authorization Error",
|
|
9139
|
+
category: "insecure-defaults",
|
|
9140
|
+
severity: "warning",
|
|
9141
|
+
confidence: "low",
|
|
9142
|
+
patterns: [
|
|
9143
|
+
// Matches single-line empty catch: catch(e) { } or catch(e) { // ignore }
|
|
9144
|
+
// Note: multi-line catch blocks are handled by AI review, not this rule
|
|
9145
|
+
/catch\s*\([^)]*\)\s*\{\s*(?:\/\/\s*(?:ignore|skip|noop|todo)\b.*)?\s*\}/
|
|
9146
|
+
],
|
|
9147
|
+
fileGlob: "**/*auth*.{ts,js,mjs,cjs},**/*session*.{ts,js,mjs,cjs},**/*token*.{ts,js,mjs,cjs}",
|
|
9148
|
+
message: "Single-line empty catch block in authentication/authorization code may silently allow unauthorized access. Note: multi-line empty catch blocks are detected by AI review, not this mechanical rule.",
|
|
9149
|
+
remediation: "Re-throw the error or return an explicit denial. Never silently swallow auth errors.",
|
|
9150
|
+
references: ["CWE-754", "CWE-390"]
|
|
9151
|
+
},
|
|
9152
|
+
{
|
|
9153
|
+
id: "SEC-DEF-004",
|
|
9154
|
+
name: "Permissive CORS Fallback",
|
|
9155
|
+
category: "insecure-defaults",
|
|
9156
|
+
severity: "warning",
|
|
9157
|
+
confidence: "medium",
|
|
9158
|
+
patterns: [
|
|
9159
|
+
/(?:origin|cors)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*['"]\*/
|
|
9160
|
+
],
|
|
9161
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9162
|
+
message: "CORS origin falls back to wildcard (*) when configuration is missing",
|
|
9163
|
+
remediation: "Default to a restrictive origin list. Require explicit configuration for permissive CORS.",
|
|
9164
|
+
references: ["CWE-942"]
|
|
9165
|
+
},
|
|
9166
|
+
{
|
|
9167
|
+
id: "SEC-DEF-005",
|
|
9168
|
+
name: "Rate Limiting Disabled by Default",
|
|
9169
|
+
category: "insecure-defaults",
|
|
9170
|
+
severity: "info",
|
|
9171
|
+
confidence: "low",
|
|
9172
|
+
patterns: [
|
|
9173
|
+
/(?:rateLimit|rateLimiting|throttle)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*(?:false|0|null|undefined)/i
|
|
9174
|
+
],
|
|
9175
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9176
|
+
message: "Rate limiting defaults to disabled when configuration is missing",
|
|
9177
|
+
remediation: "Default to a sensible rate limit. Require explicit opt-out for disabling.",
|
|
9178
|
+
references: ["CWE-770"]
|
|
9179
|
+
}
|
|
9180
|
+
];
|
|
9181
|
+
|
|
9182
|
+
// src/security/rules/sharp-edges.ts
|
|
9183
|
+
var sharpEdgesRules = [
|
|
9184
|
+
// --- Deprecated Crypto APIs ---
|
|
9185
|
+
{
|
|
9186
|
+
id: "SEC-EDGE-001",
|
|
9187
|
+
name: "Deprecated createCipher API",
|
|
9188
|
+
category: "sharp-edges",
|
|
9189
|
+
severity: "error",
|
|
9190
|
+
confidence: "high",
|
|
9191
|
+
patterns: [/crypto\.createCipher\s*\(/],
|
|
9192
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9193
|
+
message: "crypto.createCipher is deprecated \u2014 uses weak key derivation (no IV)",
|
|
9194
|
+
remediation: "Use crypto.createCipheriv with a random IV and proper key derivation (scrypt/pbkdf2)",
|
|
9195
|
+
references: ["CWE-327"]
|
|
9196
|
+
},
|
|
9197
|
+
{
|
|
9198
|
+
id: "SEC-EDGE-002",
|
|
9199
|
+
name: "Deprecated createDecipher API",
|
|
9200
|
+
category: "sharp-edges",
|
|
9201
|
+
severity: "error",
|
|
9202
|
+
confidence: "high",
|
|
9203
|
+
patterns: [/crypto\.createDecipher\s*\(/],
|
|
9204
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9205
|
+
message: "crypto.createDecipher is deprecated \u2014 uses weak key derivation (no IV)",
|
|
9206
|
+
remediation: "Use crypto.createDecipheriv with the same IV used for encryption",
|
|
9207
|
+
references: ["CWE-327"]
|
|
9208
|
+
},
|
|
9209
|
+
{
|
|
9210
|
+
id: "SEC-EDGE-003",
|
|
9211
|
+
name: "ECB Mode Selection",
|
|
9212
|
+
category: "sharp-edges",
|
|
9213
|
+
severity: "warning",
|
|
9214
|
+
confidence: "high",
|
|
9215
|
+
patterns: [/-ecb['"]/],
|
|
9216
|
+
fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
|
|
9217
|
+
message: "ECB mode does not provide semantic security \u2014 identical plaintext blocks produce identical ciphertext",
|
|
9218
|
+
remediation: "Use CBC, CTR, or GCM mode instead of ECB",
|
|
9219
|
+
references: ["CWE-327"]
|
|
9220
|
+
},
|
|
9221
|
+
// --- Unsafe Deserialization ---
|
|
9222
|
+
{
|
|
9223
|
+
id: "SEC-EDGE-004",
|
|
9224
|
+
name: "yaml.load Without Safe Loader",
|
|
9225
|
+
category: "sharp-edges",
|
|
9226
|
+
severity: "error",
|
|
9227
|
+
confidence: "high",
|
|
9228
|
+
patterns: [
|
|
9229
|
+
/yaml\.load\s*\(/
|
|
9230
|
+
// Python: yaml.load() without SafeLoader
|
|
9231
|
+
],
|
|
9232
|
+
fileGlob: "**/*.py",
|
|
9233
|
+
message: "yaml.load() executes arbitrary Python objects \u2014 use yaml.safe_load() instead",
|
|
9234
|
+
remediation: "Replace yaml.load() with yaml.safe_load() or yaml.load(data, Loader=SafeLoader). Note: this rule will flag yaml.load(data, Loader=SafeLoader) \u2014 suppress with # harness-ignore SEC-EDGE-004: safe usage with SafeLoader",
|
|
9235
|
+
references: ["CWE-502"]
|
|
9236
|
+
},
|
|
9237
|
+
{
|
|
9238
|
+
id: "SEC-EDGE-005",
|
|
9239
|
+
name: "Pickle/Marshal Deserialization",
|
|
9240
|
+
category: "sharp-edges",
|
|
9241
|
+
severity: "error",
|
|
9242
|
+
confidence: "high",
|
|
9243
|
+
patterns: [/pickle\.loads?\s*\(/, /marshal\.loads?\s*\(/],
|
|
9244
|
+
fileGlob: "**/*.py",
|
|
9245
|
+
message: "pickle/marshal deserialization executes arbitrary code \u2014 never use on untrusted data",
|
|
9246
|
+
remediation: "Use JSON, MessagePack, or Protocol Buffers for untrusted data serialization",
|
|
9247
|
+
references: ["CWE-502"]
|
|
9248
|
+
},
|
|
9249
|
+
// --- TOCTOU (Time-of-Check to Time-of-Use) ---
|
|
9250
|
+
{
|
|
9251
|
+
id: "SEC-EDGE-006",
|
|
9252
|
+
name: "Check-Then-Act File Operation",
|
|
9253
|
+
category: "sharp-edges",
|
|
9254
|
+
severity: "warning",
|
|
9255
|
+
confidence: "medium",
|
|
9256
|
+
// Patterns use .{0,N} since scanner matches single lines only (no multiline mode)
|
|
9257
|
+
patterns: [
|
|
9258
|
+
/(?:existsSync|accessSync|statSync)\s*\([^)]+\).{0,50}(?:readFileSync|writeFileSync|unlinkSync|mkdirSync)\s*\(/
|
|
9259
|
+
],
|
|
9260
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9261
|
+
message: "Check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
|
|
9262
|
+
remediation: "Use the operation directly and handle ENOENT/EEXIST errors instead of checking first",
|
|
9263
|
+
references: ["CWE-367"]
|
|
9264
|
+
},
|
|
9265
|
+
{
|
|
9266
|
+
id: "SEC-EDGE-007",
|
|
9267
|
+
name: "Check-Then-Act File Operation (Async)",
|
|
9268
|
+
category: "sharp-edges",
|
|
9269
|
+
severity: "warning",
|
|
9270
|
+
confidence: "medium",
|
|
9271
|
+
// Uses .{0,N} since scanner matches single lines only (no multiline mode)
|
|
9272
|
+
patterns: [/(?:access|stat)\s*\([^)]+\).{0,80}(?:readFile|writeFile|unlink|mkdir)\s*\(/],
|
|
9273
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9274
|
+
message: "Async check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
|
|
9275
|
+
remediation: "Use the operation directly with try/catch instead of checking existence first",
|
|
9276
|
+
references: ["CWE-367"]
|
|
9277
|
+
},
|
|
9278
|
+
// --- Stringly-Typed Security ---
|
|
9279
|
+
{
|
|
9280
|
+
id: "SEC-EDGE-008",
|
|
9281
|
+
name: 'JWT Algorithm "none"',
|
|
9282
|
+
category: "sharp-edges",
|
|
9283
|
+
severity: "error",
|
|
9284
|
+
confidence: "high",
|
|
9285
|
+
patterns: [
|
|
9286
|
+
/algorithm[s]?\s*[:=]\s*\[?\s*['"]none['"]/i,
|
|
9287
|
+
/alg(?:orithm)?\s*[:=]\s*['"]none['"]/i
|
|
9288
|
+
],
|
|
9289
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
9290
|
+
message: 'JWT "none" algorithm disables signature verification entirely',
|
|
9291
|
+
remediation: 'Specify an explicit algorithm (e.g., "HS256", "RS256") and set algorithms allowlist in verify options',
|
|
9292
|
+
references: ["CWE-345"]
|
|
9293
|
+
},
|
|
9294
|
+
{
|
|
9295
|
+
id: "SEC-EDGE-009",
|
|
9296
|
+
name: "DES/RC4 Algorithm Selection",
|
|
9297
|
+
category: "sharp-edges",
|
|
9298
|
+
severity: "error",
|
|
9299
|
+
confidence: "high",
|
|
9300
|
+
patterns: [/['"]\s*(?:des|des-ede|des-ede3|des3|rc4|rc2|blowfish)\s*['"]/i],
|
|
9301
|
+
fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
|
|
9302
|
+
message: "Weak/deprecated cipher algorithm selected \u2014 DES, RC4, RC2, and Blowfish are broken or deprecated",
|
|
9303
|
+
remediation: "Use AES-256-GCM or ChaCha20-Poly1305",
|
|
9304
|
+
references: ["CWE-327"]
|
|
9305
|
+
}
|
|
9306
|
+
];
|
|
9307
|
+
|
|
9077
9308
|
// src/security/rules/stack/node.ts
|
|
9078
9309
|
var nodeRules = [
|
|
9079
9310
|
{
|
|
@@ -9187,6 +9418,14 @@ var goRules = [
|
|
|
9187
9418
|
];
|
|
9188
9419
|
|
|
9189
9420
|
// src/security/scanner.ts
|
|
9421
|
+
function parseHarnessIgnore(line, ruleId) {
|
|
9422
|
+
if (!line.includes("harness-ignore")) return null;
|
|
9423
|
+
if (!line.includes(ruleId)) return null;
|
|
9424
|
+
const match = line.match(/(?:\/\/|#)\s*harness-ignore\s+(SEC-[A-Z]+-\d+)(?::\s*(.+))?/);
|
|
9425
|
+
if (match?.[1] !== ruleId) return null;
|
|
9426
|
+
const text = match[2]?.trim();
|
|
9427
|
+
return { ruleId, justification: text || null };
|
|
9428
|
+
}
|
|
9190
9429
|
var SecurityScanner = class {
|
|
9191
9430
|
registry;
|
|
9192
9431
|
config;
|
|
@@ -9203,7 +9442,9 @@ var SecurityScanner = class {
|
|
|
9203
9442
|
...networkRules,
|
|
9204
9443
|
...deserializationRules,
|
|
9205
9444
|
...agentConfigRules,
|
|
9206
|
-
...mcpRules
|
|
9445
|
+
...mcpRules,
|
|
9446
|
+
...insecureDefaultsRules,
|
|
9447
|
+
...sharpEdgesRules
|
|
9207
9448
|
]);
|
|
9208
9449
|
this.registry.registerAll([...nodeRules, ...expressRules, ...reactRules, ...goRules]);
|
|
9209
9450
|
this.activeRules = this.registry.getAll();
|
|
@@ -9220,42 +9461,8 @@ var SecurityScanner = class {
|
|
|
9220
9461
|
*/
|
|
9221
9462
|
scanContent(content, filePath, startLine = 1) {
|
|
9222
9463
|
if (!this.config.enabled) return [];
|
|
9223
|
-
const findings = [];
|
|
9224
9464
|
const lines = content.split("\n");
|
|
9225
|
-
|
|
9226
|
-
const resolved = resolveRuleSeverity(
|
|
9227
|
-
rule.id,
|
|
9228
|
-
rule.severity,
|
|
9229
|
-
this.config.rules ?? {},
|
|
9230
|
-
this.config.strict
|
|
9231
|
-
);
|
|
9232
|
-
if (resolved === "off") continue;
|
|
9233
|
-
for (let i = 0; i < lines.length; i++) {
|
|
9234
|
-
const line = lines[i] ?? "";
|
|
9235
|
-
if (line.includes("harness-ignore") && line.includes(rule.id)) continue;
|
|
9236
|
-
for (const pattern of rule.patterns) {
|
|
9237
|
-
pattern.lastIndex = 0;
|
|
9238
|
-
if (pattern.test(line)) {
|
|
9239
|
-
findings.push({
|
|
9240
|
-
ruleId: rule.id,
|
|
9241
|
-
ruleName: rule.name,
|
|
9242
|
-
category: rule.category,
|
|
9243
|
-
severity: resolved,
|
|
9244
|
-
confidence: rule.confidence,
|
|
9245
|
-
file: filePath,
|
|
9246
|
-
line: startLine + i,
|
|
9247
|
-
match: line.trim(),
|
|
9248
|
-
context: line,
|
|
9249
|
-
message: rule.message,
|
|
9250
|
-
remediation: rule.remediation,
|
|
9251
|
-
...rule.references ? { references: rule.references } : {}
|
|
9252
|
-
});
|
|
9253
|
-
break;
|
|
9254
|
-
}
|
|
9255
|
-
}
|
|
9256
|
-
}
|
|
9257
|
-
}
|
|
9258
|
-
return findings;
|
|
9465
|
+
return this.scanLinesWithRules(lines, this.activeRules, filePath, startLine);
|
|
9259
9466
|
}
|
|
9260
9467
|
async scanFile(filePath) {
|
|
9261
9468
|
if (!this.config.enabled) return [];
|
|
@@ -9264,14 +9471,22 @@ var SecurityScanner = class {
|
|
|
9264
9471
|
}
|
|
9265
9472
|
scanContentForFile(content, filePath, startLine = 1) {
|
|
9266
9473
|
if (!this.config.enabled) return [];
|
|
9267
|
-
const findings = [];
|
|
9268
9474
|
const lines = content.split("\n");
|
|
9269
9475
|
const applicableRules = this.activeRules.filter((rule) => {
|
|
9270
9476
|
if (!rule.fileGlob) return true;
|
|
9271
9477
|
const globs = rule.fileGlob.split(",").map((g) => g.trim());
|
|
9272
9478
|
return globs.some((glob2) => (0, import_minimatch5.minimatch)(filePath, glob2, { dot: true }));
|
|
9273
9479
|
});
|
|
9274
|
-
|
|
9480
|
+
return this.scanLinesWithRules(lines, applicableRules, filePath, startLine);
|
|
9481
|
+
}
|
|
9482
|
+
/**
|
|
9483
|
+
* Core scanning loop shared by scanContent and scanContentForFile.
|
|
9484
|
+
* Evaluates each rule against each line, handling suppression (FP gate)
|
|
9485
|
+
* and pattern matching uniformly.
|
|
9486
|
+
*/
|
|
9487
|
+
scanLinesWithRules(lines, rules, filePath, startLine) {
|
|
9488
|
+
const findings = [];
|
|
9489
|
+
for (const rule of rules) {
|
|
9275
9490
|
const resolved = resolveRuleSeverity(
|
|
9276
9491
|
rule.id,
|
|
9277
9492
|
rule.severity,
|
|
@@ -9281,7 +9496,25 @@ var SecurityScanner = class {
|
|
|
9281
9496
|
if (resolved === "off") continue;
|
|
9282
9497
|
for (let i = 0; i < lines.length; i++) {
|
|
9283
9498
|
const line = lines[i] ?? "";
|
|
9284
|
-
|
|
9499
|
+
const suppressionMatch = parseHarnessIgnore(line, rule.id);
|
|
9500
|
+
if (suppressionMatch) {
|
|
9501
|
+
if (!suppressionMatch.justification) {
|
|
9502
|
+
findings.push({
|
|
9503
|
+
ruleId: rule.id,
|
|
9504
|
+
ruleName: rule.name,
|
|
9505
|
+
category: rule.category,
|
|
9506
|
+
severity: this.config.strict ? "error" : "warning",
|
|
9507
|
+
confidence: "high",
|
|
9508
|
+
file: filePath,
|
|
9509
|
+
line: startLine + i,
|
|
9510
|
+
match: line.trim(),
|
|
9511
|
+
context: line,
|
|
9512
|
+
message: `Suppression of ${rule.id} requires justification: // harness-ignore ${rule.id}: <reason>`,
|
|
9513
|
+
remediation: `Add justification after colon: // harness-ignore ${rule.id}: false positive because ...`
|
|
9514
|
+
});
|
|
9515
|
+
}
|
|
9516
|
+
continue;
|
|
9517
|
+
}
|
|
9285
9518
|
for (const pattern of rule.patterns) {
|
|
9286
9519
|
pattern.lastIndex = 0;
|
|
9287
9520
|
if (pattern.test(line)) {
|
|
@@ -9327,6 +9560,414 @@ var SecurityScanner = class {
|
|
|
9327
9560
|
}
|
|
9328
9561
|
};
|
|
9329
9562
|
|
|
9563
|
+
// src/security/injection-patterns.ts
|
|
9564
|
+
var hiddenUnicodePatterns = [
|
|
9565
|
+
{
|
|
9566
|
+
ruleId: "INJ-UNI-001",
|
|
9567
|
+
severity: "high",
|
|
9568
|
+
category: "hidden-unicode",
|
|
9569
|
+
description: "Zero-width characters that can hide malicious instructions",
|
|
9570
|
+
// eslint-disable-next-line no-misleading-character-class -- intentional: regex detects zero-width chars for security scanning
|
|
9571
|
+
pattern: /[\u200B\u200C\u200D\uFEFF\u2060]/
|
|
9572
|
+
},
|
|
9573
|
+
{
|
|
9574
|
+
ruleId: "INJ-UNI-002",
|
|
9575
|
+
severity: "high",
|
|
9576
|
+
category: "hidden-unicode",
|
|
9577
|
+
description: "RTL/LTR override characters that can disguise text direction",
|
|
9578
|
+
pattern: /[\u202A-\u202E\u2066-\u2069]/
|
|
9579
|
+
}
|
|
9580
|
+
];
|
|
9581
|
+
var reRolingPatterns = [
|
|
9582
|
+
{
|
|
9583
|
+
ruleId: "INJ-REROL-001",
|
|
9584
|
+
severity: "high",
|
|
9585
|
+
category: "explicit-re-roling",
|
|
9586
|
+
description: "Instruction to ignore/disregard/forget previous instructions",
|
|
9587
|
+
pattern: /(?:ignore|disregard|forget)\s+(?:all\s+)?(?:previous|prior|above|earlier)\s+(?:instructions?|prompts?|context|rules?|guidelines?)/i
|
|
9588
|
+
},
|
|
9589
|
+
{
|
|
9590
|
+
ruleId: "INJ-REROL-002",
|
|
9591
|
+
severity: "high",
|
|
9592
|
+
category: "explicit-re-roling",
|
|
9593
|
+
description: "Attempt to reassign the AI role",
|
|
9594
|
+
pattern: /you\s+are\s+now\s+(?:a\s+|an\s+)?(?:new\s+)?(?:helpful\s+)?(?:my\s+)?(?:\w+\s+)?(?:assistant|agent|AI|bot|chatbot|system|persona)\b/i
|
|
9595
|
+
},
|
|
9596
|
+
{
|
|
9597
|
+
ruleId: "INJ-REROL-003",
|
|
9598
|
+
severity: "high",
|
|
9599
|
+
category: "explicit-re-roling",
|
|
9600
|
+
description: "Direct instruction override attempt",
|
|
9601
|
+
pattern: /(?:new\s+)?(?:system\s+)?(?:instruction|directive|role|persona)\s*[:=]\s*/i
|
|
9602
|
+
}
|
|
9603
|
+
];
|
|
9604
|
+
var permissionEscalationPatterns = [
|
|
9605
|
+
{
|
|
9606
|
+
ruleId: "INJ-PERM-001",
|
|
9607
|
+
severity: "high",
|
|
9608
|
+
category: "permission-escalation",
|
|
9609
|
+
description: "Attempt to enable all tools or grant unrestricted access",
|
|
9610
|
+
pattern: /(?:allow|enable|grant)\s+all\s+(?:tools?|permissions?|access)/i
|
|
9611
|
+
},
|
|
9612
|
+
{
|
|
9613
|
+
ruleId: "INJ-PERM-002",
|
|
9614
|
+
severity: "high",
|
|
9615
|
+
category: "permission-escalation",
|
|
9616
|
+
description: "Attempt to disable safety or security features",
|
|
9617
|
+
pattern: /(?:disable|turn\s+off|remove|bypass)\s+(?:all\s+)?(?:safety|security|restrictions?|guardrails?|protections?|checks?)/i
|
|
9618
|
+
},
|
|
9619
|
+
{
|
|
9620
|
+
ruleId: "INJ-PERM-003",
|
|
9621
|
+
severity: "high",
|
|
9622
|
+
category: "permission-escalation",
|
|
9623
|
+
description: "Auto-approve directive that bypasses human review",
|
|
9624
|
+
pattern: /(?:auto[- ]?approve|--no-verify|--dangerously-skip-permissions)/i
|
|
9625
|
+
}
|
|
9626
|
+
];
|
|
9627
|
+
var encodedPayloadPatterns = [
|
|
9628
|
+
{
|
|
9629
|
+
ruleId: "INJ-ENC-001",
|
|
9630
|
+
severity: "high",
|
|
9631
|
+
category: "encoded-payloads",
|
|
9632
|
+
description: "Base64-encoded string long enough to contain instructions (>=28 chars)",
|
|
9633
|
+
// Match base64 strings of 28+ chars (7+ groups of 4).
|
|
9634
|
+
// Excludes JWT tokens (eyJ prefix) and Bearer-prefixed tokens.
|
|
9635
|
+
pattern: /(?<!Bearer\s)(?<![:])(?<![A-Za-z0-9/])(?!eyJ)(?:[A-Za-z0-9+/]{4}){7,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?(?![A-Za-z0-9/])/
|
|
9636
|
+
},
|
|
9637
|
+
{
|
|
9638
|
+
ruleId: "INJ-ENC-002",
|
|
9639
|
+
severity: "high",
|
|
9640
|
+
category: "encoded-payloads",
|
|
9641
|
+
description: "Hex-encoded string long enough to contain directives (>=20 hex chars)",
|
|
9642
|
+
// Excludes hash-prefixed hex (sha256:, sha512:, md5:, etc.) and hex preceded by 0x.
|
|
9643
|
+
// Note: 40-char git SHA hashes (e.g. in `git log` output) may match — downstream
|
|
9644
|
+
// callers should filter matches of exactly 40 hex chars if scanning git output.
|
|
9645
|
+
pattern: /(?<![:x])(?<![A-Fa-f0-9])(?:[0-9a-fA-F]{2}){10,}(?![A-Fa-f0-9])/
|
|
9646
|
+
}
|
|
9647
|
+
];
|
|
9648
|
+
var indirectInjectionPatterns = [
|
|
9649
|
+
{
|
|
9650
|
+
ruleId: "INJ-IND-001",
|
|
9651
|
+
severity: "medium",
|
|
9652
|
+
category: "indirect-injection",
|
|
9653
|
+
description: "Instruction to influence future responses",
|
|
9654
|
+
pattern: /(?:when\s+the\s+user\s+asks|if\s+(?:the\s+user|someone|anyone)\s+asks)\s*,?\s*(?:say|respond|reply|answer|tell)/i
|
|
9655
|
+
},
|
|
9656
|
+
{
|
|
9657
|
+
ruleId: "INJ-IND-002",
|
|
9658
|
+
severity: "medium",
|
|
9659
|
+
category: "indirect-injection",
|
|
9660
|
+
description: "Directive to include content in responses",
|
|
9661
|
+
pattern: /(?:include|insert|add|embed|put)\s+(?:this|the\s+following)\s+(?:in|into|to)\s+(?:your|the)\s+(?:response|output|reply|answer)/i
|
|
9662
|
+
},
|
|
9663
|
+
{
|
|
9664
|
+
ruleId: "INJ-IND-003",
|
|
9665
|
+
severity: "medium",
|
|
9666
|
+
category: "indirect-injection",
|
|
9667
|
+
description: "Standing instruction to always respond a certain way",
|
|
9668
|
+
pattern: /always\s+(?:respond|reply|answer|say|output)\s+(?:with|that|by)/i
|
|
9669
|
+
}
|
|
9670
|
+
];
|
|
9671
|
+
var contextManipulationPatterns = [
|
|
9672
|
+
{
|
|
9673
|
+
ruleId: "INJ-CTX-001",
|
|
9674
|
+
severity: "medium",
|
|
9675
|
+
category: "context-manipulation",
|
|
9676
|
+
description: "Claim about system prompt content",
|
|
9677
|
+
pattern: /(?:the\s+)?(?:system\s+prompt|system\s+message|hidden\s+instructions?)\s+(?:says?|tells?|instructs?|contains?|is)/i
|
|
9678
|
+
},
|
|
9679
|
+
{
|
|
9680
|
+
ruleId: "INJ-CTX-002",
|
|
9681
|
+
severity: "medium",
|
|
9682
|
+
category: "context-manipulation",
|
|
9683
|
+
description: "Claim about AI instructions",
|
|
9684
|
+
pattern: /your\s+(?:instructions?|directives?|guidelines?|rules?)\s+(?:are|say|tell|state)/i
|
|
9685
|
+
},
|
|
9686
|
+
{
|
|
9687
|
+
ruleId: "INJ-CTX-003",
|
|
9688
|
+
severity: "medium",
|
|
9689
|
+
category: "context-manipulation",
|
|
9690
|
+
description: "Fake XML/HTML system or instruction tags",
|
|
9691
|
+
// Case-sensitive: only match lowercase tags to avoid false positives on
|
|
9692
|
+
// React components like <User>, <Context>, <Role> etc.
|
|
9693
|
+
pattern: /<\/?(?:system|instruction|prompt|role|context|tool_call|function_call|assistant|human|user)[^>]*>/
|
|
9694
|
+
},
|
|
9695
|
+
{
|
|
9696
|
+
ruleId: "INJ-CTX-004",
|
|
9697
|
+
severity: "medium",
|
|
9698
|
+
category: "context-manipulation",
|
|
9699
|
+
description: "Fake JSON role assignment mimicking chat format",
|
|
9700
|
+
pattern: /[{,]\s*"role"\s*:\s*"(?:system|assistant|function)"/i
|
|
9701
|
+
}
|
|
9702
|
+
];
|
|
9703
|
+
var socialEngineeringPatterns = [
|
|
9704
|
+
{
|
|
9705
|
+
ruleId: "INJ-SOC-001",
|
|
9706
|
+
severity: "medium",
|
|
9707
|
+
category: "social-engineering",
|
|
9708
|
+
description: "Urgency pressure to bypass checks",
|
|
9709
|
+
pattern: /(?:this\s+is\s+(?:very\s+)?urgent|this\s+is\s+(?:an?\s+)?emergency|do\s+(?:this|it)\s+(?:now|immediately))\b/i
|
|
9710
|
+
},
|
|
9711
|
+
{
|
|
9712
|
+
ruleId: "INJ-SOC-002",
|
|
9713
|
+
severity: "medium",
|
|
9714
|
+
category: "social-engineering",
|
|
9715
|
+
description: "False authority claim",
|
|
9716
|
+
pattern: /(?:the\s+)?(?:admin|administrator|manager|CEO|CTO|owner|supervisor)\s+(?:authorized|approved|said|told|confirmed|requested)/i
|
|
9717
|
+
},
|
|
9718
|
+
{
|
|
9719
|
+
ruleId: "INJ-SOC-003",
|
|
9720
|
+
severity: "medium",
|
|
9721
|
+
category: "social-engineering",
|
|
9722
|
+
description: "Testing pretext to bypass safety",
|
|
9723
|
+
pattern: /(?:for\s+testing\s+purposes?|this\s+is\s+(?:just\s+)?a\s+test|in\s+test\s+mode)\b/i
|
|
9724
|
+
}
|
|
9725
|
+
];
|
|
9726
|
+
var suspiciousPatterns = [
|
|
9727
|
+
{
|
|
9728
|
+
ruleId: "INJ-SUS-001",
|
|
9729
|
+
severity: "low",
|
|
9730
|
+
category: "suspicious-patterns",
|
|
9731
|
+
description: "Excessive consecutive whitespace (>10 chars) mid-line that may hide content",
|
|
9732
|
+
// Only match whitespace runs not at the start of a line (indentation is normal)
|
|
9733
|
+
pattern: /\S\s{11,}/
|
|
9734
|
+
},
|
|
9735
|
+
{
|
|
9736
|
+
ruleId: "INJ-SUS-002",
|
|
9737
|
+
severity: "low",
|
|
9738
|
+
category: "suspicious-patterns",
|
|
9739
|
+
description: "Repeated delimiters (>5) that may indicate obfuscation",
|
|
9740
|
+
pattern: /([|;,=\-_~`])\1{5,}/
|
|
9741
|
+
},
|
|
9742
|
+
{
|
|
9743
|
+
ruleId: "INJ-SUS-003",
|
|
9744
|
+
severity: "low",
|
|
9745
|
+
category: "suspicious-patterns",
|
|
9746
|
+
description: "Mathematical alphanumeric symbols used as Latin character substitutes",
|
|
9747
|
+
// Mathematical bold/italic/script Unicode ranges (U+1D400-U+1D7FF)
|
|
9748
|
+
pattern: /[\uD835][\uDC00-\uDFFF]/
|
|
9749
|
+
}
|
|
9750
|
+
];
|
|
9751
|
+
var ALL_PATTERNS = [
|
|
9752
|
+
...hiddenUnicodePatterns,
|
|
9753
|
+
...reRolingPatterns,
|
|
9754
|
+
...permissionEscalationPatterns,
|
|
9755
|
+
...encodedPayloadPatterns,
|
|
9756
|
+
...indirectInjectionPatterns,
|
|
9757
|
+
...contextManipulationPatterns,
|
|
9758
|
+
...socialEngineeringPatterns,
|
|
9759
|
+
...suspiciousPatterns
|
|
9760
|
+
];
|
|
9761
|
+
function scanForInjection(text) {
|
|
9762
|
+
const findings = [];
|
|
9763
|
+
const lines = text.split("\n");
|
|
9764
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
9765
|
+
const line = lines[lineIdx];
|
|
9766
|
+
for (const rule of ALL_PATTERNS) {
|
|
9767
|
+
if (rule.pattern.test(line)) {
|
|
9768
|
+
findings.push({
|
|
9769
|
+
severity: rule.severity,
|
|
9770
|
+
ruleId: rule.ruleId,
|
|
9771
|
+
match: line.trim(),
|
|
9772
|
+
line: lineIdx + 1
|
|
9773
|
+
});
|
|
9774
|
+
}
|
|
9775
|
+
}
|
|
9776
|
+
}
|
|
9777
|
+
const severityOrder = {
|
|
9778
|
+
high: 0,
|
|
9779
|
+
medium: 1,
|
|
9780
|
+
low: 2
|
|
9781
|
+
};
|
|
9782
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
9783
|
+
return findings;
|
|
9784
|
+
}
|
|
9785
|
+
function getInjectionPatterns() {
|
|
9786
|
+
return ALL_PATTERNS;
|
|
9787
|
+
}
|
|
9788
|
+
var DESTRUCTIVE_BASH = [
|
|
9789
|
+
/\bgit\s+push\b/,
|
|
9790
|
+
/\bgit\s+commit\b/,
|
|
9791
|
+
/\brm\s+-rf?\b/,
|
|
9792
|
+
/\brm\s+-r\b/
|
|
9793
|
+
];
|
|
9794
|
+
|
|
9795
|
+
// src/security/taint.ts
|
|
9796
|
+
var import_node_fs4 = require("fs");
|
|
9797
|
+
var import_node_path7 = require("path");
|
|
9798
|
+
var TAINT_DURATION_MS = 30 * 60 * 1e3;
|
|
9799
|
+
var DEFAULT_SESSION_ID = "default";
|
|
9800
|
+
function getTaintFilePath(projectRoot, sessionId) {
|
|
9801
|
+
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9802
|
+
return (0, import_node_path7.join)(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
9803
|
+
}
|
|
9804
|
+
function readTaint(projectRoot, sessionId) {
|
|
9805
|
+
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9806
|
+
let content;
|
|
9807
|
+
try {
|
|
9808
|
+
content = (0, import_node_fs4.readFileSync)(filePath, "utf8");
|
|
9809
|
+
} catch {
|
|
9810
|
+
return null;
|
|
9811
|
+
}
|
|
9812
|
+
let state;
|
|
9813
|
+
try {
|
|
9814
|
+
state = JSON.parse(content);
|
|
9815
|
+
} catch {
|
|
9816
|
+
try {
|
|
9817
|
+
(0, import_node_fs4.unlinkSync)(filePath);
|
|
9818
|
+
} catch {
|
|
9819
|
+
}
|
|
9820
|
+
return null;
|
|
9821
|
+
}
|
|
9822
|
+
if (!state.sessionId || !state.taintedAt || !state.expiresAt || !state.findings) {
|
|
9823
|
+
try {
|
|
9824
|
+
(0, import_node_fs4.unlinkSync)(filePath);
|
|
9825
|
+
} catch {
|
|
9826
|
+
}
|
|
9827
|
+
return null;
|
|
9828
|
+
}
|
|
9829
|
+
return state;
|
|
9830
|
+
}
|
|
9831
|
+
function checkTaint(projectRoot, sessionId) {
|
|
9832
|
+
const state = readTaint(projectRoot, sessionId);
|
|
9833
|
+
if (!state) {
|
|
9834
|
+
return { tainted: false, expired: false, state: null };
|
|
9835
|
+
}
|
|
9836
|
+
const now = /* @__PURE__ */ new Date();
|
|
9837
|
+
const expiresAt = new Date(state.expiresAt);
|
|
9838
|
+
if (now >= expiresAt) {
|
|
9839
|
+
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9840
|
+
try {
|
|
9841
|
+
(0, import_node_fs4.unlinkSync)(filePath);
|
|
9842
|
+
} catch {
|
|
9843
|
+
}
|
|
9844
|
+
return { tainted: false, expired: true, state };
|
|
9845
|
+
}
|
|
9846
|
+
return { tainted: true, expired: false, state };
|
|
9847
|
+
}
|
|
9848
|
+
function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
9849
|
+
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9850
|
+
const filePath = getTaintFilePath(projectRoot, id);
|
|
9851
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9852
|
+
const dir = (0, import_node_path7.dirname)(filePath);
|
|
9853
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
9854
|
+
const existing = readTaint(projectRoot, id);
|
|
9855
|
+
const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
|
|
9856
|
+
const taintFindings = findings.map((f) => ({
|
|
9857
|
+
ruleId: f.ruleId,
|
|
9858
|
+
severity: f.severity,
|
|
9859
|
+
match: f.match,
|
|
9860
|
+
source,
|
|
9861
|
+
detectedAt: now
|
|
9862
|
+
}));
|
|
9863
|
+
const state = {
|
|
9864
|
+
sessionId: id,
|
|
9865
|
+
taintedAt: existing?.taintedAt || now,
|
|
9866
|
+
expiresAt: new Date(Date.now() + TAINT_DURATION_MS).toISOString(),
|
|
9867
|
+
reason,
|
|
9868
|
+
severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
|
|
9869
|
+
findings: [...existing?.findings || [], ...taintFindings]
|
|
9870
|
+
};
|
|
9871
|
+
(0, import_node_fs4.writeFileSync)(filePath, JSON.stringify(state, null, 2) + "\n");
|
|
9872
|
+
return state;
|
|
9873
|
+
}
|
|
9874
|
+
function clearTaint(projectRoot, sessionId) {
|
|
9875
|
+
if (sessionId) {
|
|
9876
|
+
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9877
|
+
try {
|
|
9878
|
+
(0, import_node_fs4.unlinkSync)(filePath);
|
|
9879
|
+
return 1;
|
|
9880
|
+
} catch {
|
|
9881
|
+
return 0;
|
|
9882
|
+
}
|
|
9883
|
+
}
|
|
9884
|
+
const harnessDir = (0, import_node_path7.join)(projectRoot, ".harness");
|
|
9885
|
+
let count = 0;
|
|
9886
|
+
try {
|
|
9887
|
+
const files = (0, import_node_fs4.readdirSync)(harnessDir);
|
|
9888
|
+
for (const file of files) {
|
|
9889
|
+
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
9890
|
+
try {
|
|
9891
|
+
(0, import_node_fs4.unlinkSync)((0, import_node_path7.join)(harnessDir, file));
|
|
9892
|
+
count++;
|
|
9893
|
+
} catch {
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9896
|
+
}
|
|
9897
|
+
} catch {
|
|
9898
|
+
}
|
|
9899
|
+
return count;
|
|
9900
|
+
}
|
|
9901
|
+
function listTaintedSessions(projectRoot) {
|
|
9902
|
+
const harnessDir = (0, import_node_path7.join)(projectRoot, ".harness");
|
|
9903
|
+
const sessions = [];
|
|
9904
|
+
try {
|
|
9905
|
+
const files = (0, import_node_fs4.readdirSync)(harnessDir);
|
|
9906
|
+
for (const file of files) {
|
|
9907
|
+
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
9908
|
+
const sessionId = file.replace("session-taint-", "").replace(".json", "");
|
|
9909
|
+
const result = checkTaint(projectRoot, sessionId);
|
|
9910
|
+
if (result.tainted) {
|
|
9911
|
+
sessions.push(sessionId);
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9914
|
+
}
|
|
9915
|
+
} catch {
|
|
9916
|
+
}
|
|
9917
|
+
return sessions;
|
|
9918
|
+
}
|
|
9919
|
+
|
|
9920
|
+
// src/security/scan-config-shared.ts
|
|
9921
|
+
function mapSecuritySeverity(severity) {
|
|
9922
|
+
if (severity === "error") return "high";
|
|
9923
|
+
if (severity === "warning") return "medium";
|
|
9924
|
+
return "low";
|
|
9925
|
+
}
|
|
9926
|
+
function computeOverallSeverity(findings) {
|
|
9927
|
+
if (findings.length === 0) return "clean";
|
|
9928
|
+
if (findings.some((f) => f.severity === "high")) return "high";
|
|
9929
|
+
if (findings.some((f) => f.severity === "medium")) return "medium";
|
|
9930
|
+
return "low";
|
|
9931
|
+
}
|
|
9932
|
+
function computeScanExitCode(results) {
|
|
9933
|
+
for (const r of results) {
|
|
9934
|
+
if (r.overallSeverity === "high") return 2;
|
|
9935
|
+
}
|
|
9936
|
+
for (const r of results) {
|
|
9937
|
+
if (r.overallSeverity === "medium") return 1;
|
|
9938
|
+
}
|
|
9939
|
+
return 0;
|
|
9940
|
+
}
|
|
9941
|
+
function mapInjectionFindings(injectionFindings) {
|
|
9942
|
+
return injectionFindings.map((f) => ({
|
|
9943
|
+
ruleId: f.ruleId,
|
|
9944
|
+
severity: f.severity,
|
|
9945
|
+
message: `Injection pattern detected: ${f.ruleId}`,
|
|
9946
|
+
match: f.match,
|
|
9947
|
+
...f.line !== void 0 ? { line: f.line } : {}
|
|
9948
|
+
}));
|
|
9949
|
+
}
|
|
9950
|
+
function isDuplicateFinding(existing, secFinding) {
|
|
9951
|
+
return existing.some(
|
|
9952
|
+
(e) => e.line === secFinding.line && e.match === secFinding.match.trim() && e.ruleId.split("-")[0] === secFinding.ruleId.split("-")[0]
|
|
9953
|
+
);
|
|
9954
|
+
}
|
|
9955
|
+
function mapSecurityFindings(secFindings, existing) {
|
|
9956
|
+
const result = [];
|
|
9957
|
+
for (const f of secFindings) {
|
|
9958
|
+
if (!isDuplicateFinding(existing, f)) {
|
|
9959
|
+
result.push({
|
|
9960
|
+
ruleId: f.ruleId,
|
|
9961
|
+
severity: mapSecuritySeverity(f.severity),
|
|
9962
|
+
message: f.message,
|
|
9963
|
+
match: f.match,
|
|
9964
|
+
...f.line !== void 0 ? { line: f.line } : {}
|
|
9965
|
+
});
|
|
9966
|
+
}
|
|
9967
|
+
}
|
|
9968
|
+
return result;
|
|
9969
|
+
}
|
|
9970
|
+
|
|
9330
9971
|
// src/ci/check-orchestrator.ts
|
|
9331
9972
|
var path15 = __toESM(require("path"));
|
|
9332
9973
|
var ALL_CHECKS = [
|
|
@@ -9510,7 +10151,7 @@ async function runPerfCheck(projectRoot, config) {
|
|
|
9510
10151
|
if (perfReport.complexity) {
|
|
9511
10152
|
for (const v of perfReport.complexity.violations) {
|
|
9512
10153
|
issues.push({
|
|
9513
|
-
severity:
|
|
10154
|
+
severity: "warning",
|
|
9514
10155
|
message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
|
|
9515
10156
|
file: v.file,
|
|
9516
10157
|
line: v.line
|
|
@@ -12106,9 +12747,9 @@ async function resolveWasmPath(grammarName) {
|
|
|
12106
12747
|
const { createRequire } = await import("module");
|
|
12107
12748
|
const require2 = createRequire(import_meta.url ?? __filename);
|
|
12108
12749
|
const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
|
|
12109
|
-
const
|
|
12110
|
-
const pkgDir =
|
|
12111
|
-
return
|
|
12750
|
+
const path26 = await import("path");
|
|
12751
|
+
const pkgDir = path26.dirname(pkgPath);
|
|
12752
|
+
return path26.join(pkgDir, "out", `${grammarName}.wasm`);
|
|
12112
12753
|
}
|
|
12113
12754
|
async function loadLanguage(lang) {
|
|
12114
12755
|
const grammarName = GRAMMAR_MAP[lang];
|
|
@@ -12472,6 +13113,489 @@ async function unfoldRange(filePath, startLine, endLine) {
|
|
|
12472
13113
|
};
|
|
12473
13114
|
}
|
|
12474
13115
|
|
|
13116
|
+
// src/pricing/pricing.ts
|
|
13117
|
+
var TOKENS_PER_MILLION = 1e6;
|
|
13118
|
+
function parseLiteLLMData(raw) {
|
|
13119
|
+
const dataset = /* @__PURE__ */ new Map();
|
|
13120
|
+
for (const [modelName, entry] of Object.entries(raw)) {
|
|
13121
|
+
if (modelName === "sample_spec") continue;
|
|
13122
|
+
if (entry.mode && entry.mode !== "chat") continue;
|
|
13123
|
+
const inputCost = entry.input_cost_per_token;
|
|
13124
|
+
const outputCost = entry.output_cost_per_token;
|
|
13125
|
+
if (inputCost == null || outputCost == null) continue;
|
|
13126
|
+
const pricing = {
|
|
13127
|
+
inputPer1M: inputCost * TOKENS_PER_MILLION,
|
|
13128
|
+
outputPer1M: outputCost * TOKENS_PER_MILLION
|
|
13129
|
+
};
|
|
13130
|
+
if (entry.cache_read_input_token_cost != null) {
|
|
13131
|
+
pricing.cacheReadPer1M = entry.cache_read_input_token_cost * TOKENS_PER_MILLION;
|
|
13132
|
+
}
|
|
13133
|
+
if (entry.cache_creation_input_token_cost != null) {
|
|
13134
|
+
pricing.cacheWritePer1M = entry.cache_creation_input_token_cost * TOKENS_PER_MILLION;
|
|
13135
|
+
}
|
|
13136
|
+
dataset.set(modelName, pricing);
|
|
13137
|
+
}
|
|
13138
|
+
return dataset;
|
|
13139
|
+
}
|
|
13140
|
+
function getModelPrice(model, dataset) {
|
|
13141
|
+
if (!model) {
|
|
13142
|
+
console.warn("[harness pricing] No model specified \u2014 cannot look up pricing.");
|
|
13143
|
+
return null;
|
|
13144
|
+
}
|
|
13145
|
+
const pricing = dataset.get(model);
|
|
13146
|
+
if (!pricing) {
|
|
13147
|
+
console.warn(
|
|
13148
|
+
`[harness pricing] No pricing data for model "${model}". Consider updating pricing data.`
|
|
13149
|
+
);
|
|
13150
|
+
return null;
|
|
13151
|
+
}
|
|
13152
|
+
return pricing;
|
|
13153
|
+
}
|
|
13154
|
+
|
|
13155
|
+
// src/pricing/cache.ts
|
|
13156
|
+
var fs23 = __toESM(require("fs/promises"));
|
|
13157
|
+
var path23 = __toESM(require("path"));
|
|
13158
|
+
|
|
13159
|
+
// src/pricing/fallback.json
|
|
13160
|
+
var fallback_default = {
|
|
13161
|
+
_generatedAt: "2026-03-31",
|
|
13162
|
+
_source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
13163
|
+
models: {
|
|
13164
|
+
"claude-opus-4-20250514": {
|
|
13165
|
+
inputPer1M: 15,
|
|
13166
|
+
outputPer1M: 75,
|
|
13167
|
+
cacheReadPer1M: 1.5,
|
|
13168
|
+
cacheWritePer1M: 18.75
|
|
13169
|
+
},
|
|
13170
|
+
"claude-sonnet-4-20250514": {
|
|
13171
|
+
inputPer1M: 3,
|
|
13172
|
+
outputPer1M: 15,
|
|
13173
|
+
cacheReadPer1M: 0.3,
|
|
13174
|
+
cacheWritePer1M: 3.75
|
|
13175
|
+
},
|
|
13176
|
+
"claude-3-5-haiku-20241022": {
|
|
13177
|
+
inputPer1M: 0.8,
|
|
13178
|
+
outputPer1M: 4,
|
|
13179
|
+
cacheReadPer1M: 0.08,
|
|
13180
|
+
cacheWritePer1M: 1
|
|
13181
|
+
},
|
|
13182
|
+
"gpt-4o": {
|
|
13183
|
+
inputPer1M: 2.5,
|
|
13184
|
+
outputPer1M: 10,
|
|
13185
|
+
cacheReadPer1M: 1.25
|
|
13186
|
+
},
|
|
13187
|
+
"gpt-4o-mini": {
|
|
13188
|
+
inputPer1M: 0.15,
|
|
13189
|
+
outputPer1M: 0.6,
|
|
13190
|
+
cacheReadPer1M: 0.075
|
|
13191
|
+
},
|
|
13192
|
+
"gemini-2.0-flash": {
|
|
13193
|
+
inputPer1M: 0.1,
|
|
13194
|
+
outputPer1M: 0.4,
|
|
13195
|
+
cacheReadPer1M: 0.025
|
|
13196
|
+
},
|
|
13197
|
+
"gemini-2.5-pro": {
|
|
13198
|
+
inputPer1M: 1.25,
|
|
13199
|
+
outputPer1M: 10,
|
|
13200
|
+
cacheReadPer1M: 0.3125
|
|
13201
|
+
}
|
|
13202
|
+
}
|
|
13203
|
+
};
|
|
13204
|
+
|
|
13205
|
+
// src/pricing/cache.ts
|
|
13206
|
+
var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
13207
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
13208
|
+
var STALENESS_WARNING_DAYS = 7;
|
|
13209
|
+
function getCachePath(projectRoot) {
|
|
13210
|
+
return path23.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
13211
|
+
}
|
|
13212
|
+
function getStalenessMarkerPath(projectRoot) {
|
|
13213
|
+
return path23.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
13214
|
+
}
|
|
13215
|
+
async function readDiskCache(projectRoot) {
|
|
13216
|
+
try {
|
|
13217
|
+
const raw = await fs23.readFile(getCachePath(projectRoot), "utf-8");
|
|
13218
|
+
return JSON.parse(raw);
|
|
13219
|
+
} catch {
|
|
13220
|
+
return null;
|
|
13221
|
+
}
|
|
13222
|
+
}
|
|
13223
|
+
async function writeDiskCache(projectRoot, data) {
|
|
13224
|
+
const cachePath = getCachePath(projectRoot);
|
|
13225
|
+
await fs23.mkdir(path23.dirname(cachePath), { recursive: true });
|
|
13226
|
+
await fs23.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
13227
|
+
}
|
|
13228
|
+
async function fetchFromNetwork() {
|
|
13229
|
+
try {
|
|
13230
|
+
const response = await fetch(LITELLM_PRICING_URL);
|
|
13231
|
+
if (!response.ok) return null;
|
|
13232
|
+
const data = await response.json();
|
|
13233
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return null;
|
|
13234
|
+
return {
|
|
13235
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13236
|
+
data
|
|
13237
|
+
};
|
|
13238
|
+
} catch {
|
|
13239
|
+
return null;
|
|
13240
|
+
}
|
|
13241
|
+
}
|
|
13242
|
+
function loadFallbackDataset() {
|
|
13243
|
+
const fb = fallback_default;
|
|
13244
|
+
const dataset = /* @__PURE__ */ new Map();
|
|
13245
|
+
for (const [model, pricing] of Object.entries(fb.models)) {
|
|
13246
|
+
dataset.set(model, pricing);
|
|
13247
|
+
}
|
|
13248
|
+
return dataset;
|
|
13249
|
+
}
|
|
13250
|
+
async function checkAndWarnStaleness(projectRoot) {
|
|
13251
|
+
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
13252
|
+
try {
|
|
13253
|
+
const raw = await fs23.readFile(markerPath, "utf-8");
|
|
13254
|
+
const marker = JSON.parse(raw);
|
|
13255
|
+
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
13256
|
+
const now = Date.now();
|
|
13257
|
+
const daysSinceFirstUse = (now - firstUse) / (24 * 60 * 60 * 1e3);
|
|
13258
|
+
if (daysSinceFirstUse > STALENESS_WARNING_DAYS) {
|
|
13259
|
+
console.warn(
|
|
13260
|
+
`[harness pricing] Pricing data is stale \u2014 using bundled fallback for ${Math.floor(daysSinceFirstUse)} days. Connect to the internet to refresh pricing data.`
|
|
13261
|
+
);
|
|
13262
|
+
}
|
|
13263
|
+
} catch {
|
|
13264
|
+
try {
|
|
13265
|
+
await fs23.mkdir(path23.dirname(markerPath), { recursive: true });
|
|
13266
|
+
await fs23.writeFile(
|
|
13267
|
+
markerPath,
|
|
13268
|
+
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
13269
|
+
);
|
|
13270
|
+
} catch {
|
|
13271
|
+
}
|
|
13272
|
+
}
|
|
13273
|
+
}
|
|
13274
|
+
async function clearStalenessMarker(projectRoot) {
|
|
13275
|
+
try {
|
|
13276
|
+
await fs23.unlink(getStalenessMarkerPath(projectRoot));
|
|
13277
|
+
} catch {
|
|
13278
|
+
}
|
|
13279
|
+
}
|
|
13280
|
+
async function loadPricingData(projectRoot) {
|
|
13281
|
+
const cache = await readDiskCache(projectRoot);
|
|
13282
|
+
if (cache) {
|
|
13283
|
+
const cacheAge = Date.now() - new Date(cache.fetchedAt).getTime();
|
|
13284
|
+
if (cacheAge < CACHE_TTL_MS) {
|
|
13285
|
+
await clearStalenessMarker(projectRoot);
|
|
13286
|
+
return parseLiteLLMData(cache.data);
|
|
13287
|
+
}
|
|
13288
|
+
}
|
|
13289
|
+
const fetched = await fetchFromNetwork();
|
|
13290
|
+
if (fetched) {
|
|
13291
|
+
await writeDiskCache(projectRoot, fetched);
|
|
13292
|
+
await clearStalenessMarker(projectRoot);
|
|
13293
|
+
return parseLiteLLMData(fetched.data);
|
|
13294
|
+
}
|
|
13295
|
+
if (cache) {
|
|
13296
|
+
return parseLiteLLMData(cache.data);
|
|
13297
|
+
}
|
|
13298
|
+
await checkAndWarnStaleness(projectRoot);
|
|
13299
|
+
return loadFallbackDataset();
|
|
13300
|
+
}
|
|
13301
|
+
|
|
13302
|
+
// src/pricing/calculator.ts
|
|
13303
|
+
var MICRODOLLARS_PER_DOLLAR = 1e6;
|
|
13304
|
+
var TOKENS_PER_MILLION2 = 1e6;
|
|
13305
|
+
function calculateCost(record, dataset) {
|
|
13306
|
+
if (!record.model) return null;
|
|
13307
|
+
const pricing = getModelPrice(record.model, dataset);
|
|
13308
|
+
if (!pricing) return null;
|
|
13309
|
+
let costUSD = 0;
|
|
13310
|
+
costUSD += record.tokens.inputTokens / TOKENS_PER_MILLION2 * pricing.inputPer1M;
|
|
13311
|
+
costUSD += record.tokens.outputTokens / TOKENS_PER_MILLION2 * pricing.outputPer1M;
|
|
13312
|
+
if (record.cacheReadTokens != null && pricing.cacheReadPer1M != null) {
|
|
13313
|
+
costUSD += record.cacheReadTokens / TOKENS_PER_MILLION2 * pricing.cacheReadPer1M;
|
|
13314
|
+
}
|
|
13315
|
+
if (record.cacheCreationTokens != null && pricing.cacheWritePer1M != null) {
|
|
13316
|
+
costUSD += record.cacheCreationTokens / TOKENS_PER_MILLION2 * pricing.cacheWritePer1M;
|
|
13317
|
+
}
|
|
13318
|
+
return Math.round(costUSD * MICRODOLLARS_PER_DOLLAR);
|
|
13319
|
+
}
|
|
13320
|
+
|
|
13321
|
+
// src/usage/aggregator.ts
|
|
13322
|
+
function aggregateBySession(records) {
|
|
13323
|
+
if (records.length === 0) return [];
|
|
13324
|
+
const sessionMap = /* @__PURE__ */ new Map();
|
|
13325
|
+
for (const record of records) {
|
|
13326
|
+
const tagged = record;
|
|
13327
|
+
const id = record.sessionId;
|
|
13328
|
+
if (!sessionMap.has(id)) {
|
|
13329
|
+
sessionMap.set(id, { harnessRecords: [], ccRecords: [], allRecords: [] });
|
|
13330
|
+
}
|
|
13331
|
+
const bucket = sessionMap.get(id);
|
|
13332
|
+
if (tagged._source === "claude-code") {
|
|
13333
|
+
bucket.ccRecords.push(tagged);
|
|
13334
|
+
} else {
|
|
13335
|
+
bucket.harnessRecords.push(tagged);
|
|
13336
|
+
}
|
|
13337
|
+
bucket.allRecords.push(tagged);
|
|
13338
|
+
}
|
|
13339
|
+
const results = [];
|
|
13340
|
+
for (const [sessionId, bucket] of sessionMap) {
|
|
13341
|
+
const hasHarness = bucket.harnessRecords.length > 0;
|
|
13342
|
+
const hasCC = bucket.ccRecords.length > 0;
|
|
13343
|
+
const isMerged = hasHarness && hasCC;
|
|
13344
|
+
const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
|
|
13345
|
+
const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
13346
|
+
let cacheCreation;
|
|
13347
|
+
let cacheRead;
|
|
13348
|
+
let costMicroUSD = 0;
|
|
13349
|
+
let model;
|
|
13350
|
+
for (const r of tokenSource) {
|
|
13351
|
+
tokens.inputTokens += r.tokens.inputTokens;
|
|
13352
|
+
tokens.outputTokens += r.tokens.outputTokens;
|
|
13353
|
+
tokens.totalTokens += r.tokens.totalTokens;
|
|
13354
|
+
if (r.cacheCreationTokens != null) {
|
|
13355
|
+
cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
|
|
13356
|
+
}
|
|
13357
|
+
if (r.cacheReadTokens != null) {
|
|
13358
|
+
cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
|
|
13359
|
+
}
|
|
13360
|
+
if (r.costMicroUSD != null && costMicroUSD != null) {
|
|
13361
|
+
costMicroUSD += r.costMicroUSD;
|
|
13362
|
+
} else if (r.costMicroUSD == null) {
|
|
13363
|
+
costMicroUSD = null;
|
|
13364
|
+
}
|
|
13365
|
+
if (!model && r.model) {
|
|
13366
|
+
model = r.model;
|
|
13367
|
+
}
|
|
13368
|
+
}
|
|
13369
|
+
if (!model && hasCC) {
|
|
13370
|
+
for (const r of bucket.ccRecords) {
|
|
13371
|
+
if (r.model) {
|
|
13372
|
+
model = r.model;
|
|
13373
|
+
break;
|
|
13374
|
+
}
|
|
13375
|
+
}
|
|
13376
|
+
}
|
|
13377
|
+
const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
|
|
13378
|
+
const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
|
|
13379
|
+
const session = {
|
|
13380
|
+
sessionId,
|
|
13381
|
+
firstTimestamp: timestamps[0] ?? "",
|
|
13382
|
+
lastTimestamp: timestamps[timestamps.length - 1] ?? "",
|
|
13383
|
+
tokens,
|
|
13384
|
+
costMicroUSD,
|
|
13385
|
+
source
|
|
13386
|
+
};
|
|
13387
|
+
if (model) session.model = model;
|
|
13388
|
+
if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
|
|
13389
|
+
if (cacheRead != null) session.cacheReadTokens = cacheRead;
|
|
13390
|
+
results.push(session);
|
|
13391
|
+
}
|
|
13392
|
+
results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
|
|
13393
|
+
return results;
|
|
13394
|
+
}
|
|
13395
|
+
function aggregateByDay(records) {
|
|
13396
|
+
if (records.length === 0) return [];
|
|
13397
|
+
const dayMap = /* @__PURE__ */ new Map();
|
|
13398
|
+
for (const record of records) {
|
|
13399
|
+
const date = record.timestamp.slice(0, 10);
|
|
13400
|
+
if (!dayMap.has(date)) {
|
|
13401
|
+
dayMap.set(date, {
|
|
13402
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
13403
|
+
tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
13404
|
+
costMicroUSD: 0,
|
|
13405
|
+
models: /* @__PURE__ */ new Set()
|
|
13406
|
+
});
|
|
13407
|
+
}
|
|
13408
|
+
const day = dayMap.get(date);
|
|
13409
|
+
day.sessions.add(record.sessionId);
|
|
13410
|
+
day.tokens.inputTokens += record.tokens.inputTokens;
|
|
13411
|
+
day.tokens.outputTokens += record.tokens.outputTokens;
|
|
13412
|
+
day.tokens.totalTokens += record.tokens.totalTokens;
|
|
13413
|
+
if (record.cacheCreationTokens != null) {
|
|
13414
|
+
day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
|
|
13415
|
+
}
|
|
13416
|
+
if (record.cacheReadTokens != null) {
|
|
13417
|
+
day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
|
|
13418
|
+
}
|
|
13419
|
+
if (record.costMicroUSD != null && day.costMicroUSD != null) {
|
|
13420
|
+
day.costMicroUSD += record.costMicroUSD;
|
|
13421
|
+
} else if (record.costMicroUSD == null) {
|
|
13422
|
+
day.costMicroUSD = null;
|
|
13423
|
+
}
|
|
13424
|
+
if (record.model) {
|
|
13425
|
+
day.models.add(record.model);
|
|
13426
|
+
}
|
|
13427
|
+
}
|
|
13428
|
+
const results = [];
|
|
13429
|
+
for (const [date, day] of dayMap) {
|
|
13430
|
+
const entry = {
|
|
13431
|
+
date,
|
|
13432
|
+
sessionCount: day.sessions.size,
|
|
13433
|
+
tokens: day.tokens,
|
|
13434
|
+
costMicroUSD: day.costMicroUSD,
|
|
13435
|
+
models: Array.from(day.models).sort()
|
|
13436
|
+
};
|
|
13437
|
+
if (day.cacheCreation != null) entry.cacheCreationTokens = day.cacheCreation;
|
|
13438
|
+
if (day.cacheRead != null) entry.cacheReadTokens = day.cacheRead;
|
|
13439
|
+
results.push(entry);
|
|
13440
|
+
}
|
|
13441
|
+
results.sort((a, b) => b.date.localeCompare(a.date));
|
|
13442
|
+
return results;
|
|
13443
|
+
}
|
|
13444
|
+
|
|
13445
|
+
// src/usage/jsonl-reader.ts
|
|
13446
|
+
var fs24 = __toESM(require("fs"));
|
|
13447
|
+
var path24 = __toESM(require("path"));
|
|
13448
|
+
function parseLine(line, lineNumber) {
|
|
13449
|
+
let entry;
|
|
13450
|
+
try {
|
|
13451
|
+
entry = JSON.parse(line);
|
|
13452
|
+
} catch {
|
|
13453
|
+
console.warn(`[harness usage] Skipping malformed JSONL line ${lineNumber}`);
|
|
13454
|
+
return null;
|
|
13455
|
+
}
|
|
13456
|
+
const tokenUsage = entry.token_usage;
|
|
13457
|
+
if (!tokenUsage || typeof tokenUsage !== "object") {
|
|
13458
|
+
console.warn(
|
|
13459
|
+
`[harness usage] Skipping malformed JSONL line ${lineNumber}: missing token_usage`
|
|
13460
|
+
);
|
|
13461
|
+
return null;
|
|
13462
|
+
}
|
|
13463
|
+
const inputTokens = tokenUsage.input_tokens ?? 0;
|
|
13464
|
+
const outputTokens = tokenUsage.output_tokens ?? 0;
|
|
13465
|
+
const record = {
|
|
13466
|
+
sessionId: entry.session_id ?? "unknown",
|
|
13467
|
+
timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
13468
|
+
tokens: {
|
|
13469
|
+
inputTokens,
|
|
13470
|
+
outputTokens,
|
|
13471
|
+
totalTokens: inputTokens + outputTokens
|
|
13472
|
+
}
|
|
13473
|
+
};
|
|
13474
|
+
if (entry.cache_creation_tokens != null) {
|
|
13475
|
+
record.cacheCreationTokens = entry.cache_creation_tokens;
|
|
13476
|
+
}
|
|
13477
|
+
if (entry.cache_read_tokens != null) {
|
|
13478
|
+
record.cacheReadTokens = entry.cache_read_tokens;
|
|
13479
|
+
}
|
|
13480
|
+
if (entry.model != null) {
|
|
13481
|
+
record.model = entry.model;
|
|
13482
|
+
}
|
|
13483
|
+
return record;
|
|
13484
|
+
}
|
|
13485
|
+
function readCostRecords(projectRoot) {
|
|
13486
|
+
const costsFile = path24.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
13487
|
+
let raw;
|
|
13488
|
+
try {
|
|
13489
|
+
raw = fs24.readFileSync(costsFile, "utf-8");
|
|
13490
|
+
} catch {
|
|
13491
|
+
return [];
|
|
13492
|
+
}
|
|
13493
|
+
const records = [];
|
|
13494
|
+
const lines = raw.split("\n");
|
|
13495
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13496
|
+
const line = lines[i]?.trim();
|
|
13497
|
+
if (!line) continue;
|
|
13498
|
+
const record = parseLine(line, i + 1);
|
|
13499
|
+
if (record) {
|
|
13500
|
+
records.push(record);
|
|
13501
|
+
}
|
|
13502
|
+
}
|
|
13503
|
+
return records;
|
|
13504
|
+
}
|
|
13505
|
+
|
|
13506
|
+
// src/usage/cc-parser.ts
|
|
13507
|
+
var fs25 = __toESM(require("fs"));
|
|
13508
|
+
var path25 = __toESM(require("path"));
|
|
13509
|
+
var os2 = __toESM(require("os"));
|
|
13510
|
+
function extractUsage(entry) {
|
|
13511
|
+
if (entry.type !== "assistant") return null;
|
|
13512
|
+
const message = entry.message;
|
|
13513
|
+
if (!message || typeof message !== "object") return null;
|
|
13514
|
+
const usage = message.usage;
|
|
13515
|
+
return usage && typeof usage === "object" && !Array.isArray(usage) ? usage : null;
|
|
13516
|
+
}
|
|
13517
|
+
function buildRecord(entry, usage) {
|
|
13518
|
+
const inputTokens = Number(usage.input_tokens) || 0;
|
|
13519
|
+
const outputTokens = Number(usage.output_tokens) || 0;
|
|
13520
|
+
const message = entry.message;
|
|
13521
|
+
const record = {
|
|
13522
|
+
sessionId: entry.sessionId ?? "unknown",
|
|
13523
|
+
timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
13524
|
+
tokens: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens },
|
|
13525
|
+
_source: "claude-code"
|
|
13526
|
+
};
|
|
13527
|
+
const model = message.model;
|
|
13528
|
+
if (model) record.model = model;
|
|
13529
|
+
const cacheCreate = usage.cache_creation_input_tokens;
|
|
13530
|
+
const cacheRead = usage.cache_read_input_tokens;
|
|
13531
|
+
if (typeof cacheCreate === "number" && cacheCreate > 0) record.cacheCreationTokens = cacheCreate;
|
|
13532
|
+
if (typeof cacheRead === "number" && cacheRead > 0) record.cacheReadTokens = cacheRead;
|
|
13533
|
+
return record;
|
|
13534
|
+
}
|
|
13535
|
+
function parseCCLine(line, filePath, lineNumber) {
|
|
13536
|
+
let entry;
|
|
13537
|
+
try {
|
|
13538
|
+
entry = JSON.parse(line);
|
|
13539
|
+
} catch {
|
|
13540
|
+
console.warn(
|
|
13541
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path25.basename(filePath)}`
|
|
13542
|
+
);
|
|
13543
|
+
return null;
|
|
13544
|
+
}
|
|
13545
|
+
const usage = extractUsage(entry);
|
|
13546
|
+
if (!usage) return null;
|
|
13547
|
+
return {
|
|
13548
|
+
record: buildRecord(entry, usage),
|
|
13549
|
+
requestId: entry.requestId ?? null
|
|
13550
|
+
};
|
|
13551
|
+
}
|
|
13552
|
+
function readCCFile(filePath) {
|
|
13553
|
+
let raw;
|
|
13554
|
+
try {
|
|
13555
|
+
raw = fs25.readFileSync(filePath, "utf-8");
|
|
13556
|
+
} catch {
|
|
13557
|
+
return [];
|
|
13558
|
+
}
|
|
13559
|
+
const byRequestId = /* @__PURE__ */ new Map();
|
|
13560
|
+
const noRequestId = [];
|
|
13561
|
+
const lines = raw.split("\n");
|
|
13562
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13563
|
+
const line = lines[i]?.trim();
|
|
13564
|
+
if (!line) continue;
|
|
13565
|
+
const parsed = parseCCLine(line, filePath, i + 1);
|
|
13566
|
+
if (!parsed) continue;
|
|
13567
|
+
if (parsed.requestId) {
|
|
13568
|
+
byRequestId.set(parsed.requestId, parsed.record);
|
|
13569
|
+
} else {
|
|
13570
|
+
noRequestId.push(parsed.record);
|
|
13571
|
+
}
|
|
13572
|
+
}
|
|
13573
|
+
return [...byRequestId.values(), ...noRequestId];
|
|
13574
|
+
}
|
|
13575
|
+
function parseCCRecords() {
|
|
13576
|
+
const homeDir = process.env.HOME ?? os2.homedir();
|
|
13577
|
+
const projectsDir = path25.join(homeDir, ".claude", "projects");
|
|
13578
|
+
let projectDirs;
|
|
13579
|
+
try {
|
|
13580
|
+
projectDirs = fs25.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
|
|
13581
|
+
} catch {
|
|
13582
|
+
return [];
|
|
13583
|
+
}
|
|
13584
|
+
const records = [];
|
|
13585
|
+
for (const dir of projectDirs) {
|
|
13586
|
+
let files;
|
|
13587
|
+
try {
|
|
13588
|
+
files = fs25.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
|
|
13589
|
+
} catch {
|
|
13590
|
+
continue;
|
|
13591
|
+
}
|
|
13592
|
+
for (const file of files) {
|
|
13593
|
+
records.push(...readCCFile(file));
|
|
13594
|
+
}
|
|
13595
|
+
}
|
|
13596
|
+
return records;
|
|
13597
|
+
}
|
|
13598
|
+
|
|
12475
13599
|
// src/index.ts
|
|
12476
13600
|
var VERSION = "0.15.0";
|
|
12477
13601
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -12490,6 +13614,7 @@ var VERSION = "0.15.0";
|
|
|
12490
13614
|
BlueprintGenerator,
|
|
12491
13615
|
BundleConstraintsSchema,
|
|
12492
13616
|
BundleSchema,
|
|
13617
|
+
CACHE_TTL_MS,
|
|
12493
13618
|
COMPLIANCE_DESCRIPTOR,
|
|
12494
13619
|
CategoryBaselineSchema,
|
|
12495
13620
|
CategoryRegressionSchema,
|
|
@@ -12507,6 +13632,7 @@ var VERSION = "0.15.0";
|
|
|
12507
13632
|
DEFAULT_SECURITY_CONFIG,
|
|
12508
13633
|
DEFAULT_STATE,
|
|
12509
13634
|
DEFAULT_STREAM_INDEX,
|
|
13635
|
+
DESTRUCTIVE_BASH,
|
|
12510
13636
|
DepDepthCollector,
|
|
12511
13637
|
EXTENSION_MAP,
|
|
12512
13638
|
EmitInteractionInputSchema,
|
|
@@ -12521,6 +13647,7 @@ var VERSION = "0.15.0";
|
|
|
12521
13647
|
HandoffSchema,
|
|
12522
13648
|
HarnessStateSchema,
|
|
12523
13649
|
InteractionTypeSchema,
|
|
13650
|
+
LITELLM_PRICING_URL,
|
|
12524
13651
|
LayerViolationCollector,
|
|
12525
13652
|
LockfilePackageSchema,
|
|
12526
13653
|
LockfileSchema,
|
|
@@ -12537,6 +13664,7 @@ var VERSION = "0.15.0";
|
|
|
12537
13664
|
RegressionDetector,
|
|
12538
13665
|
RuleRegistry,
|
|
12539
13666
|
SECURITY_DESCRIPTOR,
|
|
13667
|
+
STALENESS_WARNING_DAYS,
|
|
12540
13668
|
SecurityConfigSchema,
|
|
12541
13669
|
SecurityScanner,
|
|
12542
13670
|
SharableBoundaryConfigSchema,
|
|
@@ -12553,6 +13681,8 @@ var VERSION = "0.15.0";
|
|
|
12553
13681
|
ViolationSchema,
|
|
12554
13682
|
addProvenance,
|
|
12555
13683
|
agentConfigRules,
|
|
13684
|
+
aggregateByDay,
|
|
13685
|
+
aggregateBySession,
|
|
12556
13686
|
analyzeDiff,
|
|
12557
13687
|
analyzeLearningPatterns,
|
|
12558
13688
|
appendFailure,
|
|
@@ -12571,13 +13701,18 @@ var VERSION = "0.15.0";
|
|
|
12571
13701
|
buildDependencyGraph,
|
|
12572
13702
|
buildExclusionSet,
|
|
12573
13703
|
buildSnapshot,
|
|
13704
|
+
calculateCost,
|
|
12574
13705
|
checkDocCoverage,
|
|
12575
13706
|
checkEligibility,
|
|
12576
13707
|
checkEvidenceCoverage,
|
|
13708
|
+
checkTaint,
|
|
12577
13709
|
classifyFinding,
|
|
12578
13710
|
clearEventHashCache,
|
|
12579
13711
|
clearFailuresCache,
|
|
12580
13712
|
clearLearningsCache,
|
|
13713
|
+
clearTaint,
|
|
13714
|
+
computeOverallSeverity,
|
|
13715
|
+
computeScanExitCode,
|
|
12581
13716
|
configureFeedback,
|
|
12582
13717
|
constraintRuleId,
|
|
12583
13718
|
contextBudget,
|
|
@@ -12632,35 +13767,48 @@ var VERSION = "0.15.0";
|
|
|
12632
13767
|
getActionEmitter,
|
|
12633
13768
|
getExitCode,
|
|
12634
13769
|
getFeedbackConfig,
|
|
13770
|
+
getInjectionPatterns,
|
|
13771
|
+
getModelPrice,
|
|
12635
13772
|
getOutline,
|
|
12636
13773
|
getParser,
|
|
12637
13774
|
getPhaseCategories,
|
|
12638
13775
|
getStreamForBranch,
|
|
13776
|
+
getTaintFilePath,
|
|
12639
13777
|
getUpdateNotification,
|
|
12640
13778
|
goRules,
|
|
12641
13779
|
injectionRules,
|
|
13780
|
+
insecureDefaultsRules,
|
|
13781
|
+
isDuplicateFinding,
|
|
12642
13782
|
isSmallSuggestion,
|
|
12643
13783
|
isUpdateCheckEnabled,
|
|
12644
13784
|
listActiveSessions,
|
|
12645
13785
|
listStreams,
|
|
13786
|
+
listTaintedSessions,
|
|
12646
13787
|
loadBudgetedLearnings,
|
|
12647
13788
|
loadEvents,
|
|
12648
13789
|
loadFailures,
|
|
12649
13790
|
loadHandoff,
|
|
12650
13791
|
loadIndexEntries,
|
|
13792
|
+
loadPricingData,
|
|
12651
13793
|
loadRelevantLearnings,
|
|
12652
13794
|
loadSessionSummary,
|
|
12653
13795
|
loadState,
|
|
12654
13796
|
loadStreamIndex,
|
|
12655
13797
|
logAgentAction,
|
|
13798
|
+
mapInjectionFindings,
|
|
13799
|
+
mapSecurityFindings,
|
|
13800
|
+
mapSecuritySeverity,
|
|
12656
13801
|
mcpRules,
|
|
12657
13802
|
migrateToStreams,
|
|
12658
13803
|
networkRules,
|
|
12659
13804
|
nodeRules,
|
|
13805
|
+
parseCCRecords,
|
|
12660
13806
|
parseDateFromEntry,
|
|
12661
13807
|
parseDiff,
|
|
12662
13808
|
parseFile,
|
|
12663
13809
|
parseFrontmatter,
|
|
13810
|
+
parseHarnessIgnore,
|
|
13811
|
+
parseLiteLLMData,
|
|
12664
13812
|
parseManifest,
|
|
12665
13813
|
parseRoadmap,
|
|
12666
13814
|
parseSecurityConfig,
|
|
@@ -12671,9 +13819,11 @@ var VERSION = "0.15.0";
|
|
|
12671
13819
|
pruneLearnings,
|
|
12672
13820
|
reactRules,
|
|
12673
13821
|
readCheckState,
|
|
13822
|
+
readCostRecords,
|
|
12674
13823
|
readLockfile,
|
|
12675
13824
|
readSessionSection,
|
|
12676
13825
|
readSessionSections,
|
|
13826
|
+
readTaint,
|
|
12677
13827
|
removeContributions,
|
|
12678
13828
|
removeProvenance,
|
|
12679
13829
|
requestMultiplePeerReviews,
|
|
@@ -12700,11 +13850,13 @@ var VERSION = "0.15.0";
|
|
|
12700
13850
|
saveHandoff,
|
|
12701
13851
|
saveState,
|
|
12702
13852
|
saveStreamIndex,
|
|
13853
|
+
scanForInjection,
|
|
12703
13854
|
scopeContext,
|
|
12704
13855
|
searchSymbols,
|
|
12705
13856
|
secretRules,
|
|
12706
13857
|
serializeRoadmap,
|
|
12707
13858
|
setActiveStream,
|
|
13859
|
+
sharpEdgesRules,
|
|
12708
13860
|
shouldRunCheck,
|
|
12709
13861
|
spawnBackgroundCheck,
|
|
12710
13862
|
syncConstraintNodes,
|
|
@@ -12729,6 +13881,7 @@ var VERSION = "0.15.0";
|
|
|
12729
13881
|
writeConfig,
|
|
12730
13882
|
writeLockfile,
|
|
12731
13883
|
writeSessionSummary,
|
|
13884
|
+
writeTaint,
|
|
12732
13885
|
xssRules,
|
|
12733
13886
|
...require("@harness-engineering/types")
|
|
12734
13887
|
});
|