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.
Files changed (28) hide show
  1. package/dist/{chunk-EUR32RSL.js → chunk-HPV2D4HN.js} +7 -5
  2. package/dist/{chunk-EUR32RSL.js.map → chunk-HPV2D4HN.js.map} +1 -1
  3. package/dist/{chunk-L3S7HOAS.js → chunk-L6JZCAFH.js} +146 -620
  4. package/dist/chunk-L6JZCAFH.js.map +1 -0
  5. package/dist/{chunk-IBRZK662.js → chunk-LOBVTRQQ.js} +3 -3
  6. package/dist/chunk-NR4NSTJH.js +547 -0
  7. package/dist/chunk-NR4NSTJH.js.map +1 -0
  8. package/dist/{chunk-3RZWLQSC.js → chunk-RS6RGSYG.js} +31 -2
  9. package/dist/chunk-RS6RGSYG.js.map +1 -0
  10. package/dist/{chunk-X2M7OF27.js → chunk-S4UF577T.js} +3 -1
  11. package/dist/chunk-S4UF577T.js.map +1 -0
  12. package/dist/{chunk-3NNVITJF.js → chunk-VW2LBC7B.js} +2 -2
  13. package/dist/cli.js +58 -18
  14. package/dist/cli.js.map +1 -1
  15. package/dist/index.js +19 -17
  16. package/dist/index.js.map +1 -1
  17. package/dist/{issue-triage-J2MUXK6R.js → issue-triage-FVZOVIRX.js} +3 -2
  18. package/dist/{pr-reviewer-helpers-WYPUYQ2U.js → pr-reviewer-helpers-7D2W5HTO.js} +10 -3
  19. package/dist/{setup-command-Z7TRZUDI.js → setup-command-UN3XDJ2H.js} +3 -3
  20. package/package.json +1 -1
  21. package/dist/chunk-3RZWLQSC.js.map +0 -1
  22. package/dist/chunk-L3S7HOAS.js.map +0 -1
  23. package/dist/chunk-X2M7OF27.js.map +0 -1
  24. /package/dist/{chunk-IBRZK662.js.map → chunk-LOBVTRQQ.js.map} +0 -0
  25. /package/dist/{chunk-3NNVITJF.js.map → chunk-VW2LBC7B.js.map} +0 -0
  26. /package/dist/{issue-triage-J2MUXK6R.js.map → issue-triage-FVZOVIRX.js.map} +0 -0
  27. /package/dist/{pr-reviewer-helpers-WYPUYQ2U.js.map → pr-reviewer-helpers-7D2W5HTO.js.map} +0 -0
  28. /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
- CACHE_TIMEOUTS,
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(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&apos;/gi, "'").replace(/&amp;/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 z3 } from "zod";
106
+ import { z as z2 } from "zod";
417
107
 
418
108
  // src/security/action-schema.ts
419
- import { z as z2 } from "zod";
420
- var RepoFileSource = z2.object({
421
- type: z2.literal("repoFile"),
422
- path: z2.string().min(1),
423
- line: z2.number().int().positive().optional(),
424
- commit: z2.string().regex(/^[a-f0-9]{7,40}$/).optional()
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 = z2.object({
427
- type: z2.literal("issueComment"),
428
- issueNumber: z2.number().int().positive(),
429
- commentId: z2.number().int().positive(),
430
- author: z2.string().min(1),
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 = z2.object({
434
- type: z2.literal("ciResult"),
435
- runId: z2.number().int().positive(),
436
- status: z2.enum(["pass", "fail"]),
437
- job: z2.string().min(1)
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 = z2.object({
440
- type: z2.literal("policyDoc"),
441
- path: z2.string().min(1),
442
- section: z2.string().min(1)
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 = z2.object({
445
- type: z2.literal("maintainerCommand"),
446
- username: z2.string().min(1),
447
- commentId: z2.number().int().positive()
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 = z2.discriminatedUnion("type", [
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 = z2.object({
457
- type: z2.literal("SummarizeIssue"),
458
- summary: z2.string().min(10).max(2e3),
459
- sources: z2.array(SourceCitationSchema).min(1).max(20)
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 = z2.object({
462
- type: z2.literal("ProposeLabels"),
463
- labels: z2.array(z2.string()).min(1).max(5),
464
- reason: z2.string().min(10).max(500),
465
- sources: z2.array(SourceCitationSchema).min(1).max(20)
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 = z2.object({
468
- type: z2.literal("DraftReply"),
469
- body: z2.string().min(10).max(2e3),
470
- requiresApproval: z2.literal(true),
471
- sources: z2.array(SourceCitationSchema).min(1).max(20)
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 = z2.object({
474
- type: z2.literal("RequestHumanApproval"),
475
- reason: z2.string().min(10).max(500),
476
- context: z2.string().min(10).max(2e3)
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 = z2.object({
479
- type: z2.literal("GeneratePatchPlan"),
480
- files: z2.array(
481
- z2.object({
482
- path: z2.string().min(1),
483
- operation: z2.enum(["modify", "create", "delete"]),
484
- description: z2.string().min(10).max(500)
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: z2.string().min(10).max(1e3),
488
- requiresApproval: z2.literal(true),
489
- sources: z2.array(SourceCitationSchema).min(2).max(20)
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 = z2.object({
492
- type: z2.literal("ClassifyIssue"),
493
- category: z2.enum(["bug", "feature", "question", "documentation", "security", "performance"]),
494
- confidence: z2.number().min(0).max(1),
495
- sources: z2.array(SourceCitationSchema).min(1).max(20)
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 = z2.object({
498
- type: z2.literal("IdentifyDuplicates"),
499
- candidates: z2.array(z2.number().int().positive()).min(1).max(10),
500
- similarity: z2.array(z2.number().min(0).max(1)),
501
- sources: z2.array(SourceCitationSchema).min(1).max(20)
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 = z2.object({
504
- type: z2.literal("RefuseAction"),
505
- reason: z2.string().min(10).max(500),
506
- escalateTo: z2.enum(["maintainer", "security"])
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 = z2.object({
509
- type: z2.literal("HandoffMessage"),
510
- targetCapability: z2.string().min(1).max(100),
511
- reason: z2.string().min(5).max(500),
512
- inputTrustTier: z2.enum(["1", "2", "3", "4"]),
513
- sources: z2.array(SourceCitationSchema).min(1).max(20)
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 = z2.discriminatedUnion("type", [
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 = z3.object({
257
+ var ViolationSchema = z2.object({
568
258
  /** Machine-readable rule identifier. */
569
- rule: z3.string().min(1),
259
+ rule: z2.string().min(1),
570
260
  /** Human-readable description of the violation. */
571
- message: z3.string().min(1),
261
+ message: z2.string().min(1),
572
262
  /** Severity: 'block' prevents execution, 'warn' logs only. */
573
- severity: z3.enum(["block", "warn"])
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 z5 } from "zod";
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 = z5.object({
1270
- dryRun: z5.boolean().default(true),
1271
- githubToken: z5.string().optional(),
1272
- maxComments: z5.number().int().min(1).max(100).default(50),
1273
- enableReputation: z5.boolean().default(true)
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, trustResult, reputation);
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: estimateAccountAge(issue.createdAt),
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, trustResult, reputation) {
1022
+ validateActions(actions, gateDecision) {
1505
1023
  const context = {
1506
- inputTrustTier: reconcileTrustTier(trustResult.trustTier, reputation),
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
- return ok({ issue, comments });
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-L3S7HOAS.js.map
1198
+ //# sourceMappingURL=chunk-L6JZCAFH.js.map