nexus-agents 2.90.0 → 2.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EUR32RSL.js → chunk-HPV2D4HN.js} +7 -5
- package/dist/{chunk-EUR32RSL.js.map → chunk-HPV2D4HN.js.map} +1 -1
- package/dist/{chunk-L3S7HOAS.js → chunk-L6JZCAFH.js} +146 -620
- package/dist/chunk-L6JZCAFH.js.map +1 -0
- package/dist/{chunk-IBRZK662.js → chunk-LOBVTRQQ.js} +3 -3
- package/dist/chunk-NR4NSTJH.js +547 -0
- package/dist/chunk-NR4NSTJH.js.map +1 -0
- package/dist/{chunk-3RZWLQSC.js → chunk-RS6RGSYG.js} +31 -2
- package/dist/chunk-RS6RGSYG.js.map +1 -0
- package/dist/{chunk-X2M7OF27.js → chunk-S4UF577T.js} +3 -1
- package/dist/chunk-S4UF577T.js.map +1 -0
- package/dist/{chunk-3NNVITJF.js → chunk-VW2LBC7B.js} +2 -2
- package/dist/cli.js +58 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.js +19 -17
- package/dist/index.js.map +1 -1
- package/dist/{issue-triage-J2MUXK6R.js → issue-triage-FVZOVIRX.js} +3 -2
- package/dist/{pr-reviewer-helpers-WYPUYQ2U.js → pr-reviewer-helpers-7D2W5HTO.js} +10 -3
- package/dist/{setup-command-Z7TRZUDI.js → setup-command-UN3XDJ2H.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-3RZWLQSC.js.map +0 -1
- package/dist/chunk-L3S7HOAS.js.map +0 -1
- package/dist/chunk-X2M7OF27.js.map +0 -1
- /package/dist/{chunk-IBRZK662.js.map → chunk-LOBVTRQQ.js.map} +0 -0
- /package/dist/{chunk-3NNVITJF.js.map → chunk-VW2LBC7B.js.map} +0 -0
- /package/dist/{issue-triage-J2MUXK6R.js.map → issue-triage-FVZOVIRX.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-WYPUYQ2U.js.map → pr-reviewer-helpers-7D2W5HTO.js.map} +0 -0
- /package/dist/{setup-command-Z7TRZUDI.js.map → setup-command-UN3XDJ2H.js.map} +0 -0
|
@@ -3,332 +3,22 @@ import {
|
|
|
3
3
|
ScmError
|
|
4
4
|
} from "./chunk-EFHZHCF2.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
ROLE_DEFAULT_TRUST,
|
|
7
|
+
ReputationCache,
|
|
8
|
+
TRUST_TIER_NUMERIC,
|
|
9
|
+
TrustTierSchema,
|
|
10
|
+
assessReputation,
|
|
11
|
+
gateWithReputation,
|
|
12
|
+
resolveReputationGatingMode,
|
|
13
|
+
sanitizeInput
|
|
14
|
+
} from "./chunk-NR4NSTJH.js";
|
|
15
|
+
import {
|
|
7
16
|
createLogger,
|
|
8
17
|
err,
|
|
9
18
|
getTimeProvider,
|
|
10
19
|
ok
|
|
11
20
|
} from "./chunk-W5I6L4UT.js";
|
|
12
21
|
|
|
13
|
-
// src/security/trust-types.ts
|
|
14
|
-
import { z } from "zod";
|
|
15
|
-
var TrustTierSchema = z.enum(["1", "2", "3", "4"]);
|
|
16
|
-
var TRUST_TIER_NUMERIC = {
|
|
17
|
-
"1": 1,
|
|
18
|
-
"2": 2,
|
|
19
|
-
"3": 3,
|
|
20
|
-
"4": 4
|
|
21
|
-
};
|
|
22
|
-
var GitHubUserRoleSchema = z.enum([
|
|
23
|
-
"owner",
|
|
24
|
-
"maintainer",
|
|
25
|
-
"collaborator",
|
|
26
|
-
"contributor",
|
|
27
|
-
"member",
|
|
28
|
-
"unknown"
|
|
29
|
-
]);
|
|
30
|
-
var ROLE_DEFAULT_TRUST = {
|
|
31
|
-
owner: "1",
|
|
32
|
-
maintainer: "1",
|
|
33
|
-
collaborator: "2",
|
|
34
|
-
contributor: "2",
|
|
35
|
-
member: "3",
|
|
36
|
-
unknown: "3"
|
|
37
|
-
};
|
|
38
|
-
var InjectionFlagSchema = z.enum([
|
|
39
|
-
"authority_claim",
|
|
40
|
-
"instruction_pattern",
|
|
41
|
-
"system_prompt_manipulation",
|
|
42
|
-
"hidden_content",
|
|
43
|
-
"urgency_manipulation",
|
|
44
|
-
"fake_conversation",
|
|
45
|
-
"base64_encoded",
|
|
46
|
-
"external_link_instruction"
|
|
47
|
-
]);
|
|
48
|
-
var StrippedElementSchema = z.object({
|
|
49
|
-
/** Type of element stripped. */
|
|
50
|
-
tag: z.string().min(1),
|
|
51
|
-
/** Reason for stripping. */
|
|
52
|
-
reason: z.string().min(1),
|
|
53
|
-
/** Start index in original content. */
|
|
54
|
-
startIndex: z.number().int().nonnegative(),
|
|
55
|
-
/** Length of stripped content. */
|
|
56
|
-
length: z.number().int().positive()
|
|
57
|
-
});
|
|
58
|
-
var SanitizedInputSchema = z.object({
|
|
59
|
-
/** Sanitized content with dangerous elements removed. */
|
|
60
|
-
content: z.string(),
|
|
61
|
-
/** Original content before sanitization (for audit). */
|
|
62
|
-
originalLength: z.number().int().nonnegative(),
|
|
63
|
-
/** Assigned trust tier based on user role and content analysis. */
|
|
64
|
-
trustTier: TrustTierSchema,
|
|
65
|
-
/** GitHub user role of the input source. */
|
|
66
|
-
userRole: GitHubUserRoleSchema,
|
|
67
|
-
/** Injection patterns detected in content. */
|
|
68
|
-
injectionFlags: z.array(InjectionFlagSchema),
|
|
69
|
-
/** Elements stripped during sanitization (audit trail). */
|
|
70
|
-
strippedElements: z.array(StrippedElementSchema),
|
|
71
|
-
/** Whether any dangerous content was detected and stripped. */
|
|
72
|
-
wasModified: z.boolean(),
|
|
73
|
-
/** Timestamp of sanitization (ISO 8601). */
|
|
74
|
-
sanitizedAt: z.iso.datetime()
|
|
75
|
-
});
|
|
76
|
-
var SanitizerConfigSchema = z.object({
|
|
77
|
-
/** GitHub usernames that are always Tier 1 (allowlisted maintainers). */
|
|
78
|
-
allowlistedMaintainers: z.array(z.string().min(1)).default([]),
|
|
79
|
-
/** Whether to fail open (log only) or fail closed (block). Phase 1 = open. */
|
|
80
|
-
failOpen: z.boolean().default(true),
|
|
81
|
-
/** Maximum input length before truncation. */
|
|
82
|
-
maxInputLength: z.number().int().positive().default(5e4)
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// src/security/input-sanitizer.ts
|
|
86
|
-
var DANGEROUS_HTML_PATTERN = /<(picture|source|img)\b[^>]*>[\s\S]*?<\/\1>|<(picture|source|img)\b[^>]*\/?>/gi;
|
|
87
|
-
var XML_INJECTION_PATTERN = /<\/?(system|human|assistant|instructions|user|prompt|context|tool_use|tool_result)\b[^>]*>/gi;
|
|
88
|
-
var HTML_COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
|
|
89
|
-
var INJECTION_PATTERNS = [
|
|
90
|
-
{
|
|
91
|
-
flag: "authority_claim",
|
|
92
|
-
pattern: /\b(as (?:a|the) (?:maintainer|admin|owner|security lead|repo owner|developer))\b/i
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
flag: "authority_claim",
|
|
96
|
-
pattern: /\b(i(?:'m| am) the (?:repo |project )?(?:owner|maintainer|admin))\b/i
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
flag: "instruction_pattern",
|
|
100
|
-
pattern: /\b(please (?:close|merge|label|mark|apply|delete|remove|approve|reject))\b/i
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
flag: "instruction_pattern",
|
|
104
|
-
pattern: /\b(you (?:should|must|need to) (?:close|merge|label|apply|delete))\b/i
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
flag: "system_prompt_manipulation",
|
|
108
|
-
pattern: /\b(ignore (?:all )?previous (?:instructions|rules|prompts))\b/i
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
flag: "system_prompt_manipulation",
|
|
112
|
-
pattern: /\b(forget (?:your |all )?(?:instructions|rules|safety))\b/i
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
flag: "system_prompt_manipulation",
|
|
116
|
-
pattern: /\b(new (?:instructions|rules|system prompt|directives))\b/i
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
flag: "urgency_manipulation",
|
|
120
|
-
pattern: /\b(critical|emergency|urgent|must act now|immediately|time[- ]?sensitive)\b/i
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
flag: "fake_conversation",
|
|
124
|
-
pattern: /<(?:assistant|human|user|system)>/i
|
|
125
|
-
},
|
|
126
|
-
// base64_encoded is detected separately by `looksLikeBase64Payload` below.
|
|
127
|
-
// The lookahead-based regex it replaced exhibited catastrophic backtracking
|
|
128
|
-
// on long hex-only inputs (#2191) — see `looksLikeBase64Payload` for the
|
|
129
|
-
// two-phase rewrite that preserves the #1811 SHA-hash false-positive guard.
|
|
130
|
-
{
|
|
131
|
-
flag: "external_link_instruction",
|
|
132
|
-
pattern: /(?:apply|run|execute|install)\s+(?:this\s+)?(?:from\s+)?https?:\/\//i
|
|
133
|
-
}
|
|
134
|
-
];
|
|
135
|
-
var BASE64_RUN_GLOBAL = /[A-Za-z0-9+/]{40,}={0,2}/g;
|
|
136
|
-
var BASE64_DISCRIMINATOR = /[g-zG-Z+/=]/;
|
|
137
|
-
function looksLikeBase64Payload(content) {
|
|
138
|
-
for (const match of content.matchAll(BASE64_RUN_GLOBAL)) {
|
|
139
|
-
if (BASE64_DISCRIMINATOR.test(match[0])) return true;
|
|
140
|
-
}
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
var DANGEROUS_TAG_NAMES = "picture|source|img|system|human|assistant|instructions|user|prompt|context|tool_use|tool_result";
|
|
144
|
-
var ENCODED_DANGEROUS_TAG_PATTERN = new RegExp(
|
|
145
|
-
`&(?:lt|#0*60|#x0*3c);\\s*\\/?\\s*(?:${DANGEROUS_TAG_NAMES})\\b`,
|
|
146
|
-
"i"
|
|
147
|
-
);
|
|
148
|
-
function decodeEntities(content) {
|
|
149
|
-
return content.replace(/&#(\d+);/g, (_match, dec) => {
|
|
150
|
-
const code = Number.parseInt(dec, 10);
|
|
151
|
-
return Number.isFinite(code) ? String.fromCodePoint(code) : _match;
|
|
152
|
-
}).replace(/&#x([0-9a-f]+);/gi, (_match, hex) => {
|
|
153
|
-
const code = Number.parseInt(hex, 16);
|
|
154
|
-
return Number.isFinite(code) ? String.fromCodePoint(code) : _match;
|
|
155
|
-
}).replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'/gi, "'").replace(/&/gi, "&");
|
|
156
|
-
}
|
|
157
|
-
function applyEntityEvasionDefense(content) {
|
|
158
|
-
if (!ENCODED_DANGEROUS_TAG_PATTERN.test(content)) {
|
|
159
|
-
return { cleaned: content, stripped: [] };
|
|
160
|
-
}
|
|
161
|
-
const decoded = decodeEntities(content);
|
|
162
|
-
return {
|
|
163
|
-
cleaned: decoded,
|
|
164
|
-
stripped: [
|
|
165
|
-
{
|
|
166
|
-
tag: "&\u2026;",
|
|
167
|
-
reason: "HTML entity-encoded dangerous tag decoded for stripping (CWE-79)",
|
|
168
|
-
startIndex: 0,
|
|
169
|
-
length: content.length
|
|
170
|
-
}
|
|
171
|
-
]
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
function stripDangerousHtml(content) {
|
|
175
|
-
const stripped = [];
|
|
176
|
-
let cleaned = content;
|
|
177
|
-
const MAX_PASSES = 5;
|
|
178
|
-
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
179
|
-
DANGEROUS_HTML_PATTERN.lastIndex = 0;
|
|
180
|
-
if (!DANGEROUS_HTML_PATTERN.test(cleaned)) break;
|
|
181
|
-
cleaned = cleaned.replace(DANGEROUS_HTML_PATTERN, (match, _g1, _g2, offset) => {
|
|
182
|
-
stripped.push({
|
|
183
|
-
tag: match.slice(0, 30) + (match.length > 30 ? "..." : ""),
|
|
184
|
-
reason: "Dangerous HTML tag (Trail of Bits injection vector)",
|
|
185
|
-
startIndex: offset,
|
|
186
|
-
length: match.length
|
|
187
|
-
});
|
|
188
|
-
return "";
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
return { cleaned, stripped };
|
|
192
|
-
}
|
|
193
|
-
function stripXmlTags(content) {
|
|
194
|
-
const stripped = [];
|
|
195
|
-
let cleaned = content;
|
|
196
|
-
const MAX_PASSES = 5;
|
|
197
|
-
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
198
|
-
XML_INJECTION_PATTERN.lastIndex = 0;
|
|
199
|
-
if (!XML_INJECTION_PATTERN.test(cleaned)) break;
|
|
200
|
-
cleaned = cleaned.replace(XML_INJECTION_PATTERN, (match, _g1, offset) => {
|
|
201
|
-
stripped.push({
|
|
202
|
-
tag: match,
|
|
203
|
-
reason: "XML-like conversation injection tag",
|
|
204
|
-
startIndex: offset,
|
|
205
|
-
length: match.length
|
|
206
|
-
});
|
|
207
|
-
return "";
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
return { cleaned, stripped };
|
|
211
|
-
}
|
|
212
|
-
function stripHtmlComments(content) {
|
|
213
|
-
const stripped = [];
|
|
214
|
-
let cleaned = content;
|
|
215
|
-
const MAX_PASSES = 5;
|
|
216
|
-
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
217
|
-
HTML_COMMENT_PATTERN.lastIndex = 0;
|
|
218
|
-
const prevLength = cleaned.length;
|
|
219
|
-
cleaned = cleaned.replace(HTML_COMMENT_PATTERN, (match, offset) => {
|
|
220
|
-
const hasInstruction = /\b(ignore|execute|close|merge|delete|apply)\b/i.test(match);
|
|
221
|
-
if (!hasInstruction) return match;
|
|
222
|
-
stripped.push({
|
|
223
|
-
tag: "<!-- ... -->",
|
|
224
|
-
reason: "HTML comment with instruction-like content",
|
|
225
|
-
startIndex: offset,
|
|
226
|
-
length: match.length
|
|
227
|
-
});
|
|
228
|
-
return "";
|
|
229
|
-
});
|
|
230
|
-
if (cleaned.length === prevLength) break;
|
|
231
|
-
}
|
|
232
|
-
let searchFrom = 0;
|
|
233
|
-
while (searchFrom < cleaned.length) {
|
|
234
|
-
const openIdx = cleaned.indexOf("<!--", searchFrom);
|
|
235
|
-
if (openIdx === -1) break;
|
|
236
|
-
const closeIdx = cleaned.indexOf("-->", openIdx + 4);
|
|
237
|
-
if (closeIdx === -1) {
|
|
238
|
-
stripped.push({
|
|
239
|
-
tag: "<!--",
|
|
240
|
-
reason: "Unclosed HTML comment (potential injection vector)",
|
|
241
|
-
startIndex: openIdx,
|
|
242
|
-
length: cleaned.length - openIdx
|
|
243
|
-
});
|
|
244
|
-
cleaned = cleaned.slice(0, openIdx);
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
searchFrom = closeIdx + 3;
|
|
248
|
-
}
|
|
249
|
-
return { cleaned, stripped };
|
|
250
|
-
}
|
|
251
|
-
function detectInjectionPatterns(content) {
|
|
252
|
-
const flags = /* @__PURE__ */ new Set();
|
|
253
|
-
for (const { flag, pattern } of INJECTION_PATTERNS) {
|
|
254
|
-
pattern.lastIndex = 0;
|
|
255
|
-
if (pattern.test(content)) {
|
|
256
|
-
flags.add(flag);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
if (looksLikeBase64Payload(content)) {
|
|
260
|
-
flags.add("base64_encoded");
|
|
261
|
-
}
|
|
262
|
-
return Array.from(flags);
|
|
263
|
-
}
|
|
264
|
-
function normalizeRole(userRole) {
|
|
265
|
-
const lower = userRole.toLowerCase();
|
|
266
|
-
if (lower in ROLE_DEFAULT_TRUST) return lower;
|
|
267
|
-
return "unknown";
|
|
268
|
-
}
|
|
269
|
-
function assignTrustTier(userRole, injectionFlags, allowlisted) {
|
|
270
|
-
if (allowlisted) return "1";
|
|
271
|
-
const normalizedRole = normalizeRole(userRole);
|
|
272
|
-
const baseTier = ROLE_DEFAULT_TRUST[normalizedRole];
|
|
273
|
-
const hostileFlags = ["system_prompt_manipulation", "fake_conversation"];
|
|
274
|
-
if (injectionFlags.some((f) => hostileFlags.includes(f))) return "4";
|
|
275
|
-
if (injectionFlags.includes("authority_claim") && normalizedRole !== "owner" && normalizedRole !== "maintainer") {
|
|
276
|
-
return "4";
|
|
277
|
-
}
|
|
278
|
-
return baseTier;
|
|
279
|
-
}
|
|
280
|
-
function sanitizeInput(content, userRole, username, config) {
|
|
281
|
-
const cfg = SanitizerConfigSchema.parse(config ?? {});
|
|
282
|
-
const truncated = content.slice(0, cfg.maxInputLength);
|
|
283
|
-
const allowlisted = cfg.allowlistedMaintainers.includes(username);
|
|
284
|
-
try {
|
|
285
|
-
const entityDecoded = applyEntityEvasionDefense(truncated);
|
|
286
|
-
const html = stripDangerousHtml(entityDecoded.cleaned);
|
|
287
|
-
const xml = stripXmlTags(html.cleaned);
|
|
288
|
-
const comments = stripHtmlComments(xml.cleaned);
|
|
289
|
-
const allStripped = [
|
|
290
|
-
...entityDecoded.stripped,
|
|
291
|
-
...html.stripped,
|
|
292
|
-
...xml.stripped,
|
|
293
|
-
...comments.stripped
|
|
294
|
-
];
|
|
295
|
-
const injectionFlags = detectInjectionPatterns(truncated);
|
|
296
|
-
const trustTier = assignTrustTier(userRole, injectionFlags, allowlisted);
|
|
297
|
-
return {
|
|
298
|
-
content: comments.cleaned,
|
|
299
|
-
originalLength: content.length,
|
|
300
|
-
trustTier,
|
|
301
|
-
userRole,
|
|
302
|
-
injectionFlags,
|
|
303
|
-
strippedElements: allStripped,
|
|
304
|
-
wasModified: allStripped.length > 0,
|
|
305
|
-
sanitizedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
306
|
-
};
|
|
307
|
-
} catch (err2) {
|
|
308
|
-
return buildFailClosedResult(err2, content, truncated, userRole, allowlisted);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
function buildFailClosedResult(err2, originalContent, truncated, userRole, allowlisted) {
|
|
312
|
-
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
313
|
-
return {
|
|
314
|
-
content: "",
|
|
315
|
-
originalLength: originalContent.length,
|
|
316
|
-
trustTier: allowlisted ? "1" : "4",
|
|
317
|
-
userRole,
|
|
318
|
-
injectionFlags: [],
|
|
319
|
-
strippedElements: [
|
|
320
|
-
{
|
|
321
|
-
tag: "(pipeline-failure)",
|
|
322
|
-
reason: `Sanitizer pipeline threw; input discarded as fail-closed: ${message}`,
|
|
323
|
-
startIndex: 0,
|
|
324
|
-
length: truncated.length
|
|
325
|
-
}
|
|
326
|
-
],
|
|
327
|
-
wasModified: true,
|
|
328
|
-
sanitizedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
22
|
// src/security/trust-classifier.ts
|
|
333
23
|
function mapAuthorAssociation(association) {
|
|
334
24
|
switch (association.toUpperCase()) {
|
|
@@ -413,106 +103,106 @@ function getRequiredTrustTier(actionType) {
|
|
|
413
103
|
}
|
|
414
104
|
|
|
415
105
|
// src/security/policy-gate.ts
|
|
416
|
-
import { z as
|
|
106
|
+
import { z as z2 } from "zod";
|
|
417
107
|
|
|
418
108
|
// src/security/action-schema.ts
|
|
419
|
-
import { z
|
|
420
|
-
var RepoFileSource =
|
|
421
|
-
type:
|
|
422
|
-
path:
|
|
423
|
-
line:
|
|
424
|
-
commit:
|
|
109
|
+
import { z } from "zod";
|
|
110
|
+
var RepoFileSource = z.object({
|
|
111
|
+
type: z.literal("repoFile"),
|
|
112
|
+
path: z.string().min(1),
|
|
113
|
+
line: z.number().int().positive().optional(),
|
|
114
|
+
commit: z.string().regex(/^[a-f0-9]{7,40}$/).optional()
|
|
425
115
|
});
|
|
426
|
-
var IssueCommentSource =
|
|
427
|
-
type:
|
|
428
|
-
issueNumber:
|
|
429
|
-
commentId:
|
|
430
|
-
author:
|
|
116
|
+
var IssueCommentSource = z.object({
|
|
117
|
+
type: z.literal("issueComment"),
|
|
118
|
+
issueNumber: z.number().int().positive(),
|
|
119
|
+
commentId: z.number().int().positive(),
|
|
120
|
+
author: z.string().min(1),
|
|
431
121
|
authorTrustTier: TrustTierSchema
|
|
432
122
|
});
|
|
433
|
-
var CIResultSource =
|
|
434
|
-
type:
|
|
435
|
-
runId:
|
|
436
|
-
status:
|
|
437
|
-
job:
|
|
123
|
+
var CIResultSource = z.object({
|
|
124
|
+
type: z.literal("ciResult"),
|
|
125
|
+
runId: z.number().int().positive(),
|
|
126
|
+
status: z.enum(["pass", "fail"]),
|
|
127
|
+
job: z.string().min(1)
|
|
438
128
|
});
|
|
439
|
-
var PolicyDocSource =
|
|
440
|
-
type:
|
|
441
|
-
path:
|
|
442
|
-
section:
|
|
129
|
+
var PolicyDocSource = z.object({
|
|
130
|
+
type: z.literal("policyDoc"),
|
|
131
|
+
path: z.string().min(1),
|
|
132
|
+
section: z.string().min(1)
|
|
443
133
|
});
|
|
444
|
-
var MaintainerCommandSource =
|
|
445
|
-
type:
|
|
446
|
-
username:
|
|
447
|
-
commentId:
|
|
134
|
+
var MaintainerCommandSource = z.object({
|
|
135
|
+
type: z.literal("maintainerCommand"),
|
|
136
|
+
username: z.string().min(1),
|
|
137
|
+
commentId: z.number().int().positive()
|
|
448
138
|
});
|
|
449
|
-
var SourceCitationSchema =
|
|
139
|
+
var SourceCitationSchema = z.discriminatedUnion("type", [
|
|
450
140
|
RepoFileSource,
|
|
451
141
|
IssueCommentSource,
|
|
452
142
|
CIResultSource,
|
|
453
143
|
PolicyDocSource,
|
|
454
144
|
MaintainerCommandSource
|
|
455
145
|
]);
|
|
456
|
-
var SummarizeIssueAction =
|
|
457
|
-
type:
|
|
458
|
-
summary:
|
|
459
|
-
sources:
|
|
146
|
+
var SummarizeIssueAction = z.object({
|
|
147
|
+
type: z.literal("SummarizeIssue"),
|
|
148
|
+
summary: z.string().min(10).max(2e3),
|
|
149
|
+
sources: z.array(SourceCitationSchema).min(1).max(20)
|
|
460
150
|
});
|
|
461
|
-
var ProposeLabelsAction =
|
|
462
|
-
type:
|
|
463
|
-
labels:
|
|
464
|
-
reason:
|
|
465
|
-
sources:
|
|
151
|
+
var ProposeLabelsAction = z.object({
|
|
152
|
+
type: z.literal("ProposeLabels"),
|
|
153
|
+
labels: z.array(z.string()).min(1).max(5),
|
|
154
|
+
reason: z.string().min(10).max(500),
|
|
155
|
+
sources: z.array(SourceCitationSchema).min(1).max(20)
|
|
466
156
|
});
|
|
467
|
-
var DraftReplyAction =
|
|
468
|
-
type:
|
|
469
|
-
body:
|
|
470
|
-
requiresApproval:
|
|
471
|
-
sources:
|
|
157
|
+
var DraftReplyAction = z.object({
|
|
158
|
+
type: z.literal("DraftReply"),
|
|
159
|
+
body: z.string().min(10).max(2e3),
|
|
160
|
+
requiresApproval: z.literal(true),
|
|
161
|
+
sources: z.array(SourceCitationSchema).min(1).max(20)
|
|
472
162
|
});
|
|
473
|
-
var RequestHumanApprovalAction =
|
|
474
|
-
type:
|
|
475
|
-
reason:
|
|
476
|
-
context:
|
|
163
|
+
var RequestHumanApprovalAction = z.object({
|
|
164
|
+
type: z.literal("RequestHumanApproval"),
|
|
165
|
+
reason: z.string().min(10).max(500),
|
|
166
|
+
context: z.string().min(10).max(2e3)
|
|
477
167
|
});
|
|
478
|
-
var GeneratePatchPlanAction =
|
|
479
|
-
type:
|
|
480
|
-
files:
|
|
481
|
-
|
|
482
|
-
path:
|
|
483
|
-
operation:
|
|
484
|
-
description:
|
|
168
|
+
var GeneratePatchPlanAction = z.object({
|
|
169
|
+
type: z.literal("GeneratePatchPlan"),
|
|
170
|
+
files: z.array(
|
|
171
|
+
z.object({
|
|
172
|
+
path: z.string().min(1),
|
|
173
|
+
operation: z.enum(["modify", "create", "delete"]),
|
|
174
|
+
description: z.string().min(10).max(500)
|
|
485
175
|
})
|
|
486
176
|
).min(1).max(10),
|
|
487
|
-
rationale:
|
|
488
|
-
requiresApproval:
|
|
489
|
-
sources:
|
|
177
|
+
rationale: z.string().min(10).max(1e3),
|
|
178
|
+
requiresApproval: z.literal(true),
|
|
179
|
+
sources: z.array(SourceCitationSchema).min(2).max(20)
|
|
490
180
|
});
|
|
491
|
-
var ClassifyIssueAction =
|
|
492
|
-
type:
|
|
493
|
-
category:
|
|
494
|
-
confidence:
|
|
495
|
-
sources:
|
|
181
|
+
var ClassifyIssueAction = z.object({
|
|
182
|
+
type: z.literal("ClassifyIssue"),
|
|
183
|
+
category: z.enum(["bug", "feature", "question", "documentation", "security", "performance"]),
|
|
184
|
+
confidence: z.number().min(0).max(1),
|
|
185
|
+
sources: z.array(SourceCitationSchema).min(1).max(20)
|
|
496
186
|
});
|
|
497
|
-
var IdentifyDuplicatesAction =
|
|
498
|
-
type:
|
|
499
|
-
candidates:
|
|
500
|
-
similarity:
|
|
501
|
-
sources:
|
|
187
|
+
var IdentifyDuplicatesAction = z.object({
|
|
188
|
+
type: z.literal("IdentifyDuplicates"),
|
|
189
|
+
candidates: z.array(z.number().int().positive()).min(1).max(10),
|
|
190
|
+
similarity: z.array(z.number().min(0).max(1)),
|
|
191
|
+
sources: z.array(SourceCitationSchema).min(1).max(20)
|
|
502
192
|
});
|
|
503
|
-
var RefuseActionAction =
|
|
504
|
-
type:
|
|
505
|
-
reason:
|
|
506
|
-
escalateTo:
|
|
193
|
+
var RefuseActionAction = z.object({
|
|
194
|
+
type: z.literal("RefuseAction"),
|
|
195
|
+
reason: z.string().min(10).max(500),
|
|
196
|
+
escalateTo: z.enum(["maintainer", "security"])
|
|
507
197
|
});
|
|
508
|
-
var HandoffMessageAction =
|
|
509
|
-
type:
|
|
510
|
-
targetCapability:
|
|
511
|
-
reason:
|
|
512
|
-
inputTrustTier:
|
|
513
|
-
sources:
|
|
198
|
+
var HandoffMessageAction = z.object({
|
|
199
|
+
type: z.literal("HandoffMessage"),
|
|
200
|
+
targetCapability: z.string().min(1).max(100),
|
|
201
|
+
reason: z.string().min(5).max(500),
|
|
202
|
+
inputTrustTier: z.enum(["1", "2", "3", "4"]),
|
|
203
|
+
sources: z.array(SourceCitationSchema).min(1).max(20)
|
|
514
204
|
});
|
|
515
|
-
var AgentActionSchema =
|
|
205
|
+
var AgentActionSchema = z.discriminatedUnion("type", [
|
|
516
206
|
SummarizeIssueAction,
|
|
517
207
|
ProposeLabelsAction,
|
|
518
208
|
DraftReplyAction,
|
|
@@ -564,13 +254,13 @@ function requiresCitation(actionType) {
|
|
|
564
254
|
}
|
|
565
255
|
|
|
566
256
|
// src/security/policy-gate.ts
|
|
567
|
-
var ViolationSchema =
|
|
257
|
+
var ViolationSchema = z2.object({
|
|
568
258
|
/** Machine-readable rule identifier. */
|
|
569
|
-
rule:
|
|
259
|
+
rule: z2.string().min(1),
|
|
570
260
|
/** Human-readable description of the violation. */
|
|
571
|
-
message:
|
|
261
|
+
message: z2.string().min(1),
|
|
572
262
|
/** Severity: 'block' prevents execution, 'warn' logs only. */
|
|
573
|
-
severity:
|
|
263
|
+
severity: z2.enum(["block", "warn"])
|
|
574
264
|
});
|
|
575
265
|
function getActionSources(action) {
|
|
576
266
|
if ("sources" in action) {
|
|
@@ -802,193 +492,6 @@ function getCorroborationRules(actionType) {
|
|
|
802
492
|
return ACTION_CORROBORATION_RULES[actionType];
|
|
803
493
|
}
|
|
804
494
|
|
|
805
|
-
// src/security/reputation-model.ts
|
|
806
|
-
import { z as z4 } from "zod";
|
|
807
|
-
var SuspiciousSignalSchema = z4.enum([
|
|
808
|
-
"new_account",
|
|
809
|
-
"no_prior_contributions",
|
|
810
|
-
"injection_patterns_detected",
|
|
811
|
-
"rapid_comments",
|
|
812
|
-
"mismatched_authority_claim"
|
|
813
|
-
]);
|
|
814
|
-
var DAYS_PER_YEAR_APPROX = 36.5;
|
|
815
|
-
var SUSPICIOUS_THRESHOLDS = {
|
|
816
|
-
/** Account younger than this (days) is flagged. */
|
|
817
|
-
newAccountDays: 30,
|
|
818
|
-
/** Fewer contributions than this is flagged. */
|
|
819
|
-
minContributions: 1,
|
|
820
|
-
/** More comments than this in the window triggers rapid-comment flag. */
|
|
821
|
-
rapidCommentThreshold: 5,
|
|
822
|
-
/** Time window (minutes) for rapid comment detection. */
|
|
823
|
-
rapidCommentWindowMinutes: 10
|
|
824
|
-
};
|
|
825
|
-
var DEFAULT_TTL_MS = CACHE_TIMEOUTS.reputationTtlMs;
|
|
826
|
-
var DEFAULT_MAX_SIZE = 1e3;
|
|
827
|
-
var ReputationCache = class {
|
|
828
|
-
cache = /* @__PURE__ */ new Map();
|
|
829
|
-
ttlMs;
|
|
830
|
-
maxSize;
|
|
831
|
-
constructor(ttlMs = DEFAULT_TTL_MS, maxSize = DEFAULT_MAX_SIZE) {
|
|
832
|
-
this.ttlMs = ttlMs;
|
|
833
|
-
this.maxSize = maxSize;
|
|
834
|
-
}
|
|
835
|
-
get(username) {
|
|
836
|
-
const entry = this.cache.get(username);
|
|
837
|
-
if (entry === void 0) return void 0;
|
|
838
|
-
if (getTimeProvider().now() > entry.expiresAt) {
|
|
839
|
-
this.cache.delete(username);
|
|
840
|
-
return void 0;
|
|
841
|
-
}
|
|
842
|
-
return entry.assessment;
|
|
843
|
-
}
|
|
844
|
-
set(username, assessment) {
|
|
845
|
-
if (this.cache.size >= this.maxSize && !this.cache.has(username)) {
|
|
846
|
-
this.evictOldest();
|
|
847
|
-
}
|
|
848
|
-
this.cache.set(username, {
|
|
849
|
-
assessment,
|
|
850
|
-
expiresAt: getTimeProvider().now() + this.ttlMs
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
/** Evict a batch of oldest entries (10% of maxSize, minimum 1). */
|
|
854
|
-
evictOldest() {
|
|
855
|
-
const batchSize = Math.max(1, Math.floor(this.maxSize * 0.1));
|
|
856
|
-
const keys = this.cache.keys();
|
|
857
|
-
for (let i = 0; i < batchSize; i++) {
|
|
858
|
-
const next = keys.next();
|
|
859
|
-
if (next.done === true) break;
|
|
860
|
-
this.cache.delete(next.value);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
clear() {
|
|
864
|
-
this.cache.clear();
|
|
865
|
-
}
|
|
866
|
-
get size() {
|
|
867
|
-
return this.cache.size;
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
var HOSTILE_INJECTION_FLAGS = [
|
|
871
|
-
"system_prompt_manipulation",
|
|
872
|
-
"fake_conversation",
|
|
873
|
-
"authority_claim",
|
|
874
|
-
"hidden_content"
|
|
875
|
-
];
|
|
876
|
-
function hasHostileInjection(flags) {
|
|
877
|
-
return flags.some((f) => HOSTILE_INJECTION_FLAGS.includes(f));
|
|
878
|
-
}
|
|
879
|
-
function isRapidCommenting(m) {
|
|
880
|
-
return m.recentCommentCount !== void 0 && m.recentCommentWindowMinutes !== void 0 && m.recentCommentCount > SUSPICIOUS_THRESHOLDS.rapidCommentThreshold && m.recentCommentWindowMinutes <= SUSPICIOUS_THRESHOLDS.rapidCommentWindowMinutes;
|
|
881
|
-
}
|
|
882
|
-
function isMismatchedAuthority(m) {
|
|
883
|
-
if (!m.injectionFlags.includes("authority_claim")) return false;
|
|
884
|
-
const association = m.authorAssociation.toUpperCase();
|
|
885
|
-
return association !== "OWNER" && association !== "MEMBER";
|
|
886
|
-
}
|
|
887
|
-
function detectSuspiciousSignals(metadata) {
|
|
888
|
-
const signals = [];
|
|
889
|
-
const { accountAgeDays, priorContributions } = metadata;
|
|
890
|
-
if (accountAgeDays !== void 0 && accountAgeDays < SUSPICIOUS_THRESHOLDS.newAccountDays) {
|
|
891
|
-
signals.push("new_account");
|
|
892
|
-
}
|
|
893
|
-
if (priorContributions !== void 0 && priorContributions < SUSPICIOUS_THRESHOLDS.minContributions) {
|
|
894
|
-
signals.push("no_prior_contributions");
|
|
895
|
-
}
|
|
896
|
-
if (hasHostileInjection(metadata.injectionFlags)) signals.push("injection_patterns_detected");
|
|
897
|
-
if (isRapidCommenting(metadata)) signals.push("rapid_comments");
|
|
898
|
-
if (isMismatchedAuthority(metadata)) signals.push("mismatched_authority_claim");
|
|
899
|
-
return signals;
|
|
900
|
-
}
|
|
901
|
-
function calculateReputationScore(metadata, signals, userRole) {
|
|
902
|
-
let score = 50;
|
|
903
|
-
const roleBonus = {
|
|
904
|
-
owner: 40,
|
|
905
|
-
maintainer: 35,
|
|
906
|
-
collaborator: 25,
|
|
907
|
-
contributor: 15,
|
|
908
|
-
member: 5,
|
|
909
|
-
unknown: 0
|
|
910
|
-
};
|
|
911
|
-
score += roleBonus[userRole];
|
|
912
|
-
if (metadata.accountAgeDays !== void 0) {
|
|
913
|
-
score += Math.min(metadata.accountAgeDays / DAYS_PER_YEAR_APPROX, 10);
|
|
914
|
-
}
|
|
915
|
-
if (metadata.priorContributions !== void 0) {
|
|
916
|
-
score += Math.min(metadata.priorContributions, 10);
|
|
917
|
-
}
|
|
918
|
-
const signalPenalty = {
|
|
919
|
-
new_account: -15,
|
|
920
|
-
no_prior_contributions: -10,
|
|
921
|
-
injection_patterns_detected: -25,
|
|
922
|
-
rapid_comments: -20,
|
|
923
|
-
mismatched_authority_claim: -30
|
|
924
|
-
};
|
|
925
|
-
for (const signal of signals) {
|
|
926
|
-
score += signalPenalty[signal];
|
|
927
|
-
}
|
|
928
|
-
return Math.max(0, Math.min(100, Math.round(score)));
|
|
929
|
-
}
|
|
930
|
-
function mapRole(association) {
|
|
931
|
-
switch (association.toUpperCase()) {
|
|
932
|
-
case "OWNER":
|
|
933
|
-
return "owner";
|
|
934
|
-
case "MEMBER":
|
|
935
|
-
return "member";
|
|
936
|
-
case "COLLABORATOR":
|
|
937
|
-
return "collaborator";
|
|
938
|
-
case "CONTRIBUTOR":
|
|
939
|
-
return "contributor";
|
|
940
|
-
default:
|
|
941
|
-
return "unknown";
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
function determineEffectiveTier(userRole, signals) {
|
|
945
|
-
const baseTier = ROLE_DEFAULT_TRUST[userRole];
|
|
946
|
-
const hostileSignals = [
|
|
947
|
-
"injection_patterns_detected",
|
|
948
|
-
"mismatched_authority_claim"
|
|
949
|
-
];
|
|
950
|
-
if (signals.some((s) => hostileSignals.includes(s))) return "4";
|
|
951
|
-
if (signals.length >= 2) {
|
|
952
|
-
const baseNumeric = TRUST_TIER_NUMERIC[baseTier];
|
|
953
|
-
const downgraded = Math.min(baseNumeric + 1, 4);
|
|
954
|
-
return String(downgraded);
|
|
955
|
-
}
|
|
956
|
-
return baseTier;
|
|
957
|
-
}
|
|
958
|
-
function assessReputation(metadata, cache) {
|
|
959
|
-
const cached = cache?.get(metadata.username);
|
|
960
|
-
if (cached !== void 0) return cached;
|
|
961
|
-
const userRole = mapRole(metadata.authorAssociation);
|
|
962
|
-
const signals = detectSuspiciousSignals(metadata);
|
|
963
|
-
const score = calculateReputationScore(metadata, signals, userRole);
|
|
964
|
-
const effectiveTier = determineEffectiveTier(userRole, signals);
|
|
965
|
-
const assessment = {
|
|
966
|
-
username: metadata.username,
|
|
967
|
-
userRole,
|
|
968
|
-
suspiciousSignals: signals,
|
|
969
|
-
isSuspicious: signals.length > 0,
|
|
970
|
-
effectiveTrustTier: effectiveTier,
|
|
971
|
-
reputationScore: score,
|
|
972
|
-
reason: buildReason(userRole, signals, effectiveTier),
|
|
973
|
-
assessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
974
|
-
};
|
|
975
|
-
cache?.set(metadata.username, assessment);
|
|
976
|
-
return assessment;
|
|
977
|
-
}
|
|
978
|
-
function reconcileTrustTier(classifierTier, reputation) {
|
|
979
|
-
if (classifierTier === "1") return "1";
|
|
980
|
-
const repTier = reputation?.effectiveTrustTier;
|
|
981
|
-
if (repTier === void 0) return classifierTier;
|
|
982
|
-
return TRUST_TIER_NUMERIC[repTier] > TRUST_TIER_NUMERIC[classifierTier] ? repTier : classifierTier;
|
|
983
|
-
}
|
|
984
|
-
function buildReason(role, signals, tier) {
|
|
985
|
-
if (signals.length === 0) {
|
|
986
|
-
return `Role ${role} \u2192 Tier ${tier} (no suspicious signals)`;
|
|
987
|
-
}
|
|
988
|
-
const signalList = signals.join(", ");
|
|
989
|
-
return `Role ${role} \u2192 Tier ${tier} (signals: ${signalList})`;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
495
|
// src/scm/url-parsers.ts
|
|
993
496
|
function parsePRUrl(url) {
|
|
994
497
|
const httpPattern = /github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/;
|
|
@@ -1244,7 +747,7 @@ function createFullGitHubProvider(repo) {
|
|
|
1244
747
|
}
|
|
1245
748
|
|
|
1246
749
|
// src/dogfooding/issue-triage-types.ts
|
|
1247
|
-
import { z as
|
|
750
|
+
import { z as z3 } from "zod";
|
|
1248
751
|
var CATEGORY_DISPLAY_NAMES = {
|
|
1249
752
|
bug: "Bug Report",
|
|
1250
753
|
feature: "Feature Request",
|
|
@@ -1266,11 +769,11 @@ var DEFAULT_ISSUE_TRIAGE_CONFIG = {
|
|
|
1266
769
|
maxComments: 50,
|
|
1267
770
|
enableReputation: true
|
|
1268
771
|
};
|
|
1269
|
-
var IssueTriageConfigSchema =
|
|
1270
|
-
dryRun:
|
|
1271
|
-
githubToken:
|
|
1272
|
-
maxComments:
|
|
1273
|
-
enableReputation:
|
|
772
|
+
var IssueTriageConfigSchema = z3.object({
|
|
773
|
+
dryRun: z3.boolean().default(true),
|
|
774
|
+
githubToken: z3.string().optional(),
|
|
775
|
+
maxComments: z3.number().int().min(1).max(100).default(50),
|
|
776
|
+
enableReputation: z3.boolean().default(true)
|
|
1274
777
|
});
|
|
1275
778
|
|
|
1276
779
|
// src/dogfooding/issue-triage-helpers.ts
|
|
@@ -1396,18 +899,33 @@ var IssueTriage = class {
|
|
|
1396
899
|
const { owner, repo, issueNumber } = parseResult.value;
|
|
1397
900
|
const fetchResult = await this.fetchIssueData(owner, repo, issueNumber);
|
|
1398
901
|
if (!fetchResult.ok) return fetchResult;
|
|
1399
|
-
const { issue: issueResult, comments } = fetchResult.value;
|
|
902
|
+
const { issue: issueResult, comments, accountAgeDays } = fetchResult.value;
|
|
1400
903
|
const safeTitle = this.sanitizeContent(issueResult.title, issueResult.author);
|
|
1401
904
|
const safeBody = this.sanitizeContent(issueResult.body, issueResult.author);
|
|
1402
905
|
const trustResult = this.classifyAuthor(issueResult);
|
|
1403
|
-
const reputation = this.assessAuthorReputation(issueResult, comments);
|
|
906
|
+
const reputation = this.assessAuthorReputation(issueResult, comments, accountAgeDays);
|
|
907
|
+
const gateDecision = gateWithReputation(
|
|
908
|
+
trustResult.trustTier,
|
|
909
|
+
reputation,
|
|
910
|
+
resolveReputationGatingMode()
|
|
911
|
+
);
|
|
912
|
+
if (gateDecision.demotionSuppressed) {
|
|
913
|
+
logger2.warn("Reputation demotion suppressed by gating mode (would block under enforce)", {
|
|
914
|
+
issueNumber,
|
|
915
|
+
author: issueResult.author,
|
|
916
|
+
mode: gateDecision.mode,
|
|
917
|
+
classifierTier: trustResult.trustTier,
|
|
918
|
+
reconciledTier: gateDecision.reconciledTier
|
|
919
|
+
});
|
|
920
|
+
}
|
|
1404
921
|
const actions = this.generateActions(safeTitle, safeBody, issueResult, trustResult);
|
|
1405
|
-
const validatedActions = this.validateActions(actions,
|
|
922
|
+
const validatedActions = this.validateActions(actions, gateDecision);
|
|
1406
923
|
const result = this.buildResult({
|
|
1407
924
|
issue: issueResult,
|
|
1408
925
|
actions: validatedActions,
|
|
1409
926
|
trustResult,
|
|
1410
927
|
reputation,
|
|
928
|
+
gateDecision,
|
|
1411
929
|
safeContent: { title: safeTitle, body: safeBody },
|
|
1412
930
|
startTime
|
|
1413
931
|
});
|
|
@@ -1450,12 +968,12 @@ var IssueTriage = class {
|
|
|
1450
968
|
* This is one of the two NEW security module wirings completing #828.
|
|
1451
969
|
* (Source: Issue #828 — reputation-model wiring)
|
|
1452
970
|
*/
|
|
1453
|
-
assessAuthorReputation(issue, comments) {
|
|
971
|
+
assessAuthorReputation(issue, comments, accountAgeDays) {
|
|
1454
972
|
if (!this.config.enableReputation) return void 0;
|
|
1455
973
|
const sanitizeResult = sanitizeInput(issue.body, "unknown", issue.author);
|
|
1456
974
|
const metadata = {
|
|
1457
975
|
username: issue.author,
|
|
1458
|
-
accountAgeDays:
|
|
976
|
+
...accountAgeDays !== void 0 ? { accountAgeDays } : {},
|
|
1459
977
|
priorContributions: countAuthorComments(issue.author, comments),
|
|
1460
978
|
recentCommentCount: countRecentComments(issue.author, comments),
|
|
1461
979
|
recentCommentWindowMinutes: 10,
|
|
@@ -1501,9 +1019,9 @@ var IssueTriage = class {
|
|
|
1501
1019
|
* This completes the #828 wiring by integrating corroboration-validator.
|
|
1502
1020
|
* (Source: Issue #828 — policy-gate + corroboration-validator wiring)
|
|
1503
1021
|
*/
|
|
1504
|
-
validateActions(actions,
|
|
1022
|
+
validateActions(actions, gateDecision) {
|
|
1505
1023
|
const context = {
|
|
1506
|
-
inputTrustTier:
|
|
1024
|
+
inputTrustTier: gateDecision.enforcedTier,
|
|
1507
1025
|
hasWriteAccess: !this.config.dryRun,
|
|
1508
1026
|
hasSecretAccess: false
|
|
1509
1027
|
};
|
|
@@ -1531,7 +1049,7 @@ var IssueTriage = class {
|
|
|
1531
1049
|
* Builds the final triage result.
|
|
1532
1050
|
*/
|
|
1533
1051
|
buildResult(opts) {
|
|
1534
|
-
const { issue, actions, trustResult, reputation, safeContent, startTime } = opts;
|
|
1052
|
+
const { issue, actions, trustResult, reputation, gateDecision, safeContent, startTime } = opts;
|
|
1535
1053
|
const [category, confidence] = categorizeIssue(safeContent.title, safeContent.body);
|
|
1536
1054
|
const isTier1 = trustResult.trustTier === "1";
|
|
1537
1055
|
const trustAssessment = {
|
|
@@ -1540,7 +1058,13 @@ var IssueTriage = class {
|
|
|
1540
1058
|
isAllowlisted: trustResult.isAllowlisted,
|
|
1541
1059
|
reputationScore: reputation?.reputationScore,
|
|
1542
1060
|
suspiciousSignals: isTier1 ? [] : reputation?.suspiciousSignals ?? [],
|
|
1543
|
-
isSuspicious: isTier1 ? false : reputation?.isSuspicious ?? false
|
|
1061
|
+
isSuspicious: isTier1 ? false : reputation?.isSuspicious ?? false,
|
|
1062
|
+
// #3122: surface both the enforced tier (what the gate used) and the
|
|
1063
|
+
// reconciled tier (what reputation computed) so telemetry can't mistake
|
|
1064
|
+
// a would-be demotion for an enforced one.
|
|
1065
|
+
enforcedTrustTier: gateDecision.enforcedTier,
|
|
1066
|
+
reputationReconciledTier: gateDecision.reconciledTier,
|
|
1067
|
+
gatingMode: gateDecision.mode
|
|
1544
1068
|
};
|
|
1545
1069
|
return {
|
|
1546
1070
|
issueNumber: issue.number,
|
|
@@ -1591,13 +1115,28 @@ var IssueTriage = class {
|
|
|
1591
1115
|
authorAssociation: c.authorAssociation,
|
|
1592
1116
|
createdAt: c.createdAt
|
|
1593
1117
|
})) : [];
|
|
1594
|
-
|
|
1118
|
+
const accountAgeDays = await this.fetchAccountAgeDays(provider, detail.author);
|
|
1119
|
+
return ok(
|
|
1120
|
+
accountAgeDays !== void 0 ? { issue, comments, accountAgeDays } : { issue, comments }
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Best-effort lookup of a GitHub user's account age in days. Returns
|
|
1125
|
+
* `undefined` if the user-metadata fetch fails or the creation date can't be
|
|
1126
|
+
* parsed (#3121) — callers must treat absence as "unknown", not "benign".
|
|
1127
|
+
*/
|
|
1128
|
+
async fetchAccountAgeDays(provider, username) {
|
|
1129
|
+
try {
|
|
1130
|
+
const result = await provider.fetchUserMetadata(username);
|
|
1131
|
+
if (!result.ok) return void 0;
|
|
1132
|
+
const createdMs = Date.parse(result.value.createdAt);
|
|
1133
|
+
if (!Number.isFinite(createdMs)) return void 0;
|
|
1134
|
+
return Math.floor((getTimeProvider().now() - createdMs) / 864e5);
|
|
1135
|
+
} catch {
|
|
1136
|
+
return void 0;
|
|
1137
|
+
}
|
|
1595
1138
|
}
|
|
1596
1139
|
};
|
|
1597
|
-
var DEFAULT_ACCOUNT_AGE_DAYS = 365;
|
|
1598
|
-
function estimateAccountAge(_createdAt) {
|
|
1599
|
-
return DEFAULT_ACCOUNT_AGE_DAYS;
|
|
1600
|
-
}
|
|
1601
1140
|
function countAuthorComments(author, comments) {
|
|
1602
1141
|
return comments.filter((c) => c.author === author).length;
|
|
1603
1142
|
}
|
|
@@ -1632,15 +1171,6 @@ function createIssueTriage(config) {
|
|
|
1632
1171
|
}
|
|
1633
1172
|
|
|
1634
1173
|
export {
|
|
1635
|
-
TrustTierSchema,
|
|
1636
|
-
TRUST_TIER_NUMERIC,
|
|
1637
|
-
GitHubUserRoleSchema,
|
|
1638
|
-
ROLE_DEFAULT_TRUST,
|
|
1639
|
-
InjectionFlagSchema,
|
|
1640
|
-
StrippedElementSchema,
|
|
1641
|
-
SanitizedInputSchema,
|
|
1642
|
-
SanitizerConfigSchema,
|
|
1643
|
-
sanitizeInput,
|
|
1644
1174
|
mapAuthorAssociation,
|
|
1645
1175
|
classifyTrust,
|
|
1646
1176
|
canInfluenceDecisions,
|
|
@@ -1657,10 +1187,6 @@ export {
|
|
|
1657
1187
|
canProceed,
|
|
1658
1188
|
validateCorroboration,
|
|
1659
1189
|
getCorroborationRules,
|
|
1660
|
-
SuspiciousSignalSchema,
|
|
1661
|
-
ReputationCache,
|
|
1662
|
-
assessReputation,
|
|
1663
|
-
reconcileTrustTier,
|
|
1664
1190
|
parsePRUrl,
|
|
1665
1191
|
GitHubReviewer,
|
|
1666
1192
|
GitHubUserInfo,
|
|
@@ -1669,4 +1195,4 @@ export {
|
|
|
1669
1195
|
IssueTriage,
|
|
1670
1196
|
createIssueTriage
|
|
1671
1197
|
};
|
|
1672
|
-
//# sourceMappingURL=chunk-
|
|
1198
|
+
//# sourceMappingURL=chunk-L6JZCAFH.js.map
|