nexus-agents 8.8.2 → 8.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/{ast-rule-runner-WANOR3MX.js → ast-rule-runner-BEUFFQWL.js} +3 -3
  2. package/dist/{child-mcp-config-N4HLLCEH.js → child-mcp-config-QFPSL7Y2.js} +2 -2
  3. package/dist/{chunk-VADACTVB.js → chunk-2RZWKIDU.js} +2 -2
  4. package/dist/{chunk-QCSXKQTK.js → chunk-36BA6D6J.js} +2 -2
  5. package/dist/{chunk-BYUVLVN7.js → chunk-3W4L63ML.js} +1 -1
  6. package/dist/chunk-3W4L63ML.js.map +1 -0
  7. package/dist/{chunk-3YKFLM2B.js → chunk-46YGVSBD.js} +2 -2
  8. package/dist/chunk-46YGVSBD.js.map +1 -0
  9. package/dist/{chunk-7J7UYJXC.js → chunk-4GSFZS6M.js} +7 -7
  10. package/dist/{chunk-ERL3XHQO.js → chunk-AOBIUJX5.js} +3 -3
  11. package/dist/{chunk-276GRITW.js → chunk-BTPPGHG3.js} +4 -4
  12. package/dist/{chunk-II5NJRKH.js → chunk-CMQO6WEQ.js} +2 -2
  13. package/dist/{chunk-WXU4Y5T3.js → chunk-D3TZZO6L.js} +2 -2
  14. package/dist/chunk-DJFYMDMD.js +7 -0
  15. package/dist/{chunk-K2PVLCHR.js → chunk-DLVIES6W.js} +2 -2
  16. package/dist/{chunk-3XQK4MEF.js → chunk-FDZ3SLMV.js} +2 -2
  17. package/dist/{chunk-W6SM3MU5.js → chunk-FMKEGR6N.js} +3 -3
  18. package/dist/{chunk-F2GERNMF.js → chunk-FOYSPWOA.js} +2 -2
  19. package/dist/{chunk-MXQG3A5W.js → chunk-FPHFCZ2E.js} +976 -132
  20. package/dist/chunk-FPHFCZ2E.js.map +1 -0
  21. package/dist/{chunk-TZWJ7QLW.js → chunk-HQ2A4DGU.js} +2 -2
  22. package/dist/{chunk-3CLKYFE3.js → chunk-I3J7WUMV.js} +2 -2
  23. package/dist/{chunk-64VMTY3M.js → chunk-K2GDWPVK.js} +2 -2
  24. package/dist/{chunk-QY3HOLGD.js → chunk-KE5IGD6G.js} +7 -7
  25. package/dist/{chunk-N347WXTT.js → chunk-M33EQB7P.js} +2 -2
  26. package/dist/chunk-M33EQB7P.js.map +1 -0
  27. package/dist/{chunk-CQCVEC7P.js → chunk-MYERGZK3.js} +945 -1111
  28. package/dist/chunk-MYERGZK3.js.map +1 -0
  29. package/dist/{chunk-SNSPYZQT.js → chunk-MZUUIMRQ.js} +5 -5
  30. package/dist/{chunk-7QQ5F7T5.js → chunk-OJSG734P.js} +8 -8
  31. package/dist/{chunk-YQUM3UWK.js → chunk-P2DV7PDG.js} +4 -4
  32. package/dist/{chunk-SQCFKQUR.js → chunk-P64GQUQW.js} +9 -9
  33. package/dist/{chunk-GXLI2E4U.js → chunk-PCPQ75EC.js} +4 -4
  34. package/dist/{chunk-Q5V3KTUL.js → chunk-Q55JVMUD.js} +2 -2
  35. package/dist/{chunk-EALWAQAQ.js → chunk-S2B4UBHK.js} +5 -5
  36. package/dist/{chunk-NR62IQDC.js → chunk-SGOBMAZK.js} +4 -4
  37. package/dist/{chunk-NR62IQDC.js.map → chunk-SGOBMAZK.js.map} +1 -1
  38. package/dist/{chunk-FBWOWMEB.js → chunk-UXCE2OBL.js} +21 -19
  39. package/dist/chunk-UXCE2OBL.js.map +1 -0
  40. package/dist/{chunk-7AU5WLAJ.js → chunk-V7PXKAZD.js} +2 -2
  41. package/dist/{chunk-FKKVZRSY.js → chunk-VQRXDFVF.js} +2 -2
  42. package/dist/{chunk-6HETGJSB.js → chunk-WANBZFP2.js} +2 -2
  43. package/dist/{chunk-ZGHJMFQ6.js → chunk-WFZARRYN.js} +3 -3
  44. package/dist/{chunk-AOS7F4QR.js → chunk-WKYDGROH.js} +2 -2
  45. package/dist/{cli-circuit-breaker-AAHYLFXZ.js → cli-circuit-breaker-BOZNYIZE.js} +3 -3
  46. package/dist/cli.js +82 -64
  47. package/dist/cli.js.map +1 -1
  48. package/dist/{composite-router-VAVDLJGT.js → composite-router-NG7BLVVR.js} +2 -2
  49. package/dist/{consensus-vote-L4CN33XE.js → consensus-vote-3FCOFDON.js} +17 -17
  50. package/dist/{context-retriever-MYKBBDAN.js → context-retriever-QKMSSV6W.js} +8 -8
  51. package/dist/{doctor-deep-AKOPPCXB.js → doctor-deep-CPMR6TDF.js} +3 -3
  52. package/dist/{doctor-live-3PVIYQST.js → doctor-live-GWE5IA7D.js} +9 -9
  53. package/dist/{expert-bridge-G3KN7QWL.js → expert-bridge-6WZ333NT.js} +4 -4
  54. package/dist/{factory-KVMLFEWJ.js → factory-2DD2YGBU.js} +5 -5
  55. package/dist/{factory-ROTP63IZ.js → factory-73WKMDVR.js} +9 -9
  56. package/dist/{improvement-review-WTZ4JV5V.js → improvement-review-RMDGMKIB.js} +6 -6
  57. package/dist/index.d.ts +158 -15
  58. package/dist/index.js +169 -658
  59. package/dist/index.js.map +1 -1
  60. package/dist/{init-opencode-C63LASGX.js → init-opencode-7PKVI3LD.js} +6 -6
  61. package/dist/issue-triage-MLBUABPA.js +17 -0
  62. package/dist/{pr-reviewer-helpers-4VDGMGV5.js → pr-reviewer-helpers-7DSP3YTT.js} +5 -5
  63. package/dist/{registry-command-IRDP2L2R.js → registry-command-YXFZ7LVE.js} +2 -2
  64. package/dist/{repo-security-plan-NS47HQEF.js → repo-security-plan-M36GODNF.js} +3 -3
  65. package/dist/{research-helpers-synthesize-5NBL637T.js → research-helpers-synthesize-7CI4MFYP.js} +4 -4
  66. package/dist/{routing-memory-2IELT5GT.js → routing-memory-MIK3LGDH.js} +2 -2
  67. package/dist/{session-memory-CH3CVLIT.js → session-memory-T2X4BM33.js} +3 -3
  68. package/dist/{setup-config-MKZZDUJP.js → setup-config-6JSBY6OG.js} +3 -3
  69. package/dist/{setup-custom-api-2I52S7XU.js → setup-custom-api-2OXG6ERI.js} +4 -4
  70. package/dist/setup-data-dir-CWU367E6.js +29 -0
  71. package/dist/{tool-memory-STS3DWOK.js → tool-memory-AL3RAR67.js} +5 -5
  72. package/dist/{unified-registry-NJS4ZWC3.js → unified-registry-5YUSIEML.js} +10 -10
  73. package/dist/{weather-report-YG4T4Q23.js → weather-report-W34L4HW5.js} +2 -2
  74. package/package.json +1 -1
  75. package/dist/chunk-3YKFLM2B.js.map +0 -1
  76. package/dist/chunk-BYUVLVN7.js.map +0 -1
  77. package/dist/chunk-CQCVEC7P.js.map +0 -1
  78. package/dist/chunk-FBWOWMEB.js.map +0 -1
  79. package/dist/chunk-H5DTHO7O.js +0 -7
  80. package/dist/chunk-MXQG3A5W.js.map +0 -1
  81. package/dist/chunk-N347WXTT.js.map +0 -1
  82. package/dist/issue-triage-PIE3YVCV.js +0 -17
  83. package/dist/setup-data-dir-NU3YBIZB.js +0 -29
  84. /package/dist/{ast-rule-runner-WANOR3MX.js.map → ast-rule-runner-BEUFFQWL.js.map} +0 -0
  85. /package/dist/{child-mcp-config-N4HLLCEH.js.map → child-mcp-config-QFPSL7Y2.js.map} +0 -0
  86. /package/dist/{chunk-VADACTVB.js.map → chunk-2RZWKIDU.js.map} +0 -0
  87. /package/dist/{chunk-QCSXKQTK.js.map → chunk-36BA6D6J.js.map} +0 -0
  88. /package/dist/{chunk-7J7UYJXC.js.map → chunk-4GSFZS6M.js.map} +0 -0
  89. /package/dist/{chunk-ERL3XHQO.js.map → chunk-AOBIUJX5.js.map} +0 -0
  90. /package/dist/{chunk-276GRITW.js.map → chunk-BTPPGHG3.js.map} +0 -0
  91. /package/dist/{chunk-II5NJRKH.js.map → chunk-CMQO6WEQ.js.map} +0 -0
  92. /package/dist/{chunk-WXU4Y5T3.js.map → chunk-D3TZZO6L.js.map} +0 -0
  93. /package/dist/{chunk-H5DTHO7O.js.map → chunk-DJFYMDMD.js.map} +0 -0
  94. /package/dist/{chunk-K2PVLCHR.js.map → chunk-DLVIES6W.js.map} +0 -0
  95. /package/dist/{chunk-3XQK4MEF.js.map → chunk-FDZ3SLMV.js.map} +0 -0
  96. /package/dist/{chunk-W6SM3MU5.js.map → chunk-FMKEGR6N.js.map} +0 -0
  97. /package/dist/{chunk-F2GERNMF.js.map → chunk-FOYSPWOA.js.map} +0 -0
  98. /package/dist/{chunk-TZWJ7QLW.js.map → chunk-HQ2A4DGU.js.map} +0 -0
  99. /package/dist/{chunk-3CLKYFE3.js.map → chunk-I3J7WUMV.js.map} +0 -0
  100. /package/dist/{chunk-64VMTY3M.js.map → chunk-K2GDWPVK.js.map} +0 -0
  101. /package/dist/{chunk-QY3HOLGD.js.map → chunk-KE5IGD6G.js.map} +0 -0
  102. /package/dist/{chunk-SNSPYZQT.js.map → chunk-MZUUIMRQ.js.map} +0 -0
  103. /package/dist/{chunk-7QQ5F7T5.js.map → chunk-OJSG734P.js.map} +0 -0
  104. /package/dist/{chunk-YQUM3UWK.js.map → chunk-P2DV7PDG.js.map} +0 -0
  105. /package/dist/{chunk-SQCFKQUR.js.map → chunk-P64GQUQW.js.map} +0 -0
  106. /package/dist/{chunk-GXLI2E4U.js.map → chunk-PCPQ75EC.js.map} +0 -0
  107. /package/dist/{chunk-Q5V3KTUL.js.map → chunk-Q55JVMUD.js.map} +0 -0
  108. /package/dist/{chunk-EALWAQAQ.js.map → chunk-S2B4UBHK.js.map} +0 -0
  109. /package/dist/{chunk-7AU5WLAJ.js.map → chunk-V7PXKAZD.js.map} +0 -0
  110. /package/dist/{chunk-FKKVZRSY.js.map → chunk-VQRXDFVF.js.map} +0 -0
  111. /package/dist/{chunk-6HETGJSB.js.map → chunk-WANBZFP2.js.map} +0 -0
  112. /package/dist/{chunk-ZGHJMFQ6.js.map → chunk-WFZARRYN.js.map} +0 -0
  113. /package/dist/{chunk-AOS7F4QR.js.map → chunk-WKYDGROH.js.map} +0 -0
  114. /package/dist/{cli-circuit-breaker-AAHYLFXZ.js.map → cli-circuit-breaker-BOZNYIZE.js.map} +0 -0
  115. /package/dist/{composite-router-VAVDLJGT.js.map → composite-router-NG7BLVVR.js.map} +0 -0
  116. /package/dist/{consensus-vote-L4CN33XE.js.map → consensus-vote-3FCOFDON.js.map} +0 -0
  117. /package/dist/{context-retriever-MYKBBDAN.js.map → context-retriever-QKMSSV6W.js.map} +0 -0
  118. /package/dist/{doctor-deep-AKOPPCXB.js.map → doctor-deep-CPMR6TDF.js.map} +0 -0
  119. /package/dist/{doctor-live-3PVIYQST.js.map → doctor-live-GWE5IA7D.js.map} +0 -0
  120. /package/dist/{expert-bridge-G3KN7QWL.js.map → expert-bridge-6WZ333NT.js.map} +0 -0
  121. /package/dist/{factory-KVMLFEWJ.js.map → factory-2DD2YGBU.js.map} +0 -0
  122. /package/dist/{factory-ROTP63IZ.js.map → factory-73WKMDVR.js.map} +0 -0
  123. /package/dist/{improvement-review-WTZ4JV5V.js.map → improvement-review-RMDGMKIB.js.map} +0 -0
  124. /package/dist/{init-opencode-C63LASGX.js.map → init-opencode-7PKVI3LD.js.map} +0 -0
  125. /package/dist/{issue-triage-PIE3YVCV.js.map → issue-triage-MLBUABPA.js.map} +0 -0
  126. /package/dist/{pr-reviewer-helpers-4VDGMGV5.js.map → pr-reviewer-helpers-7DSP3YTT.js.map} +0 -0
  127. /package/dist/{registry-command-IRDP2L2R.js.map → registry-command-YXFZ7LVE.js.map} +0 -0
  128. /package/dist/{repo-security-plan-NS47HQEF.js.map → repo-security-plan-M36GODNF.js.map} +0 -0
  129. /package/dist/{research-helpers-synthesize-5NBL637T.js.map → research-helpers-synthesize-7CI4MFYP.js.map} +0 -0
  130. /package/dist/{routing-memory-2IELT5GT.js.map → routing-memory-MIK3LGDH.js.map} +0 -0
  131. /package/dist/{session-memory-CH3CVLIT.js.map → session-memory-T2X4BM33.js.map} +0 -0
  132. /package/dist/{setup-config-MKZZDUJP.js.map → setup-config-6JSBY6OG.js.map} +0 -0
  133. /package/dist/{setup-custom-api-2I52S7XU.js.map → setup-custom-api-2OXG6ERI.js.map} +0 -0
  134. /package/dist/{setup-data-dir-NU3YBIZB.js.map → setup-data-dir-CWU367E6.js.map} +0 -0
  135. /package/dist/{tool-memory-STS3DWOK.js.map → tool-memory-AL3RAR67.js.map} +0 -0
  136. /package/dist/{unified-registry-NJS4ZWC3.js.map → unified-registry-5YUSIEML.js.map} +0 -0
  137. /package/dist/{weather-report-YG4T4Q23.js.map → weather-report-W34L4HW5.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  GitHubProvider,
3
3
  ScmError
4
- } from "./chunk-Q5V3KTUL.js";
4
+ } from "./chunk-Q55JVMUD.js";
5
5
  import {
6
6
  ROLE_DEFAULT_TRUST,
7
7
  ReputationCache,
@@ -9,22 +9,223 @@ import {
9
9
  TrustTierSchema,
10
10
  assessReputation,
11
11
  gateWithReputation,
12
+ resolveEnvMode,
12
13
  resolveReputationGatingMode,
13
14
  sanitizeInput
14
- } from "./chunk-N347WXTT.js";
15
+ } from "./chunk-M33EQB7P.js";
15
16
  import {
16
- emitPolicyEvent
17
- } from "./chunk-3YKFLM2B.js";
17
+ MAX_STRIPPED_ELEMENTS_PER_EVENT,
18
+ createAuditTrail,
19
+ emitPolicyEvent,
20
+ emitReputationEvent,
21
+ emitSanitizationEvent,
22
+ emitTrustEvent
23
+ } from "./chunk-46YGVSBD.js";
18
24
  import {
19
25
  hasToken
20
- } from "./chunk-II5NJRKH.js";
26
+ } from "./chunk-CMQO6WEQ.js";
21
27
  import {
22
28
  CLI_SUBPROCESS_TIMEOUTS,
23
29
  createLogger,
24
30
  err,
31
+ getErrorMessage,
25
32
  getTimeProvider,
26
33
  ok
27
- } from "./chunk-ERL3XHQO.js";
34
+ } from "./chunk-AOBIUJX5.js";
35
+
36
+ // src/security/firewall/firewall-policy-mode.ts
37
+ import { z } from "zod";
38
+ var FirewallPolicyModeSchema = z.enum(["off", "audit", "enforce"]);
39
+ var DEFAULT_FIREWALL_POLICY_MODE = "off";
40
+ var FIREWALL_POLICY_ENV_VAR = "NEXUS_FIREWALL_POLICY";
41
+ function resolveFirewallPolicyMode(env = process.env, logger6) {
42
+ return resolveEnvMode(
43
+ env[FIREWALL_POLICY_ENV_VAR],
44
+ FirewallPolicyModeSchema,
45
+ DEFAULT_FIREWALL_POLICY_MODE,
46
+ FIREWALL_POLICY_ENV_VAR,
47
+ {
48
+ ...logger6 !== void 0 ? { logger: logger6 } : {},
49
+ // An explicit-but-invalid value lands on `audit`, NOT on the unset
50
+ // default `off`. Unset means "the operator has not opted in" and `off` is
51
+ // right for it; a typo means "the operator opted in and mistyped how",
52
+ // and answering that with the most permissive mode hands them a firewall
53
+ // that refuses nothing while they believe it is on. `audit` reports
54
+ // without refusing, so it cannot break an external caller any more than
55
+ // `off` can, and it emits the `wouldRefuse` telemetry the rollout is
56
+ // supposed to be collecting — which `off` does not.
57
+ invalidFallback: "audit"
58
+ }
59
+ );
60
+ }
61
+
62
+ // src/security/audit-bridge.ts
63
+ var logger = createLogger({ component: "SecurityAuditBridge" });
64
+ function systemActor(id, name) {
65
+ return name !== void 0 ? { type: "system", id, name } : { type: "system", id };
66
+ }
67
+ function userActor(username) {
68
+ return { type: "user", id: username };
69
+ }
70
+ function mapTrust(e) {
71
+ const severity = e.wasDowngraded ? "warning" : "info";
72
+ return {
73
+ category: "authorization",
74
+ severity,
75
+ outcome: "success",
76
+ action: "security.trust_classification",
77
+ actor: userActor(e.username),
78
+ description: e.reason,
79
+ metadata: {
80
+ assignedTier: e.assignedTier,
81
+ userRole: e.userRole,
82
+ // Absent when no allowlist was consulted (#4992) — never a default false.
83
+ ...e.isAllowlisted !== void 0 ? { isAllowlisted: e.isAllowlisted } : {},
84
+ wasDowngraded: e.wasDowngraded,
85
+ component: e.component
86
+ }
87
+ };
88
+ }
89
+ function pipelinePolicyMetadata(e) {
90
+ return {
91
+ ...e.actionType !== void 0 ? { actionType: e.actionType } : {},
92
+ ...e.mode !== void 0 ? { mode: e.mode } : {},
93
+ ...e.ruleIds !== void 0 ? { ruleIds: e.ruleIds } : {},
94
+ ...e.stageType !== void 0 ? { stageType: e.stageType } : {},
95
+ ...e.recordKind !== void 0 ? { recordKind: e.recordKind } : {},
96
+ ...e.violationCount !== void 0 ? { violationCount: e.violationCount } : {}
97
+ };
98
+ }
99
+ function mapPolicyGate(e) {
100
+ const outcome = e.allowed ? "success" : "denied";
101
+ return {
102
+ category: "authorization",
103
+ severity: e.allowed ? "info" : "warning",
104
+ outcome,
105
+ action: "security.policy_gate",
106
+ actor: systemActor(e.component),
107
+ policyName: "security.policy_gate",
108
+ policyDecision: e.allowed ? "allow" : "deny",
109
+ ...e.violationRules.length > 0 ? { violationType: e.violationRules.join(",") } : {},
110
+ metadata: {
111
+ requiresApproval: e.requiresApproval,
112
+ inputTrustTier: e.inputTrustTier,
113
+ violationRules: e.violationRules,
114
+ ...pipelinePolicyMetadata(e)
115
+ }
116
+ };
117
+ }
118
+ function mapCorroboration(e) {
119
+ return {
120
+ category: "security",
121
+ severity: e.satisfied ? "info" : "warning",
122
+ outcome: e.satisfied ? "success" : "denied",
123
+ action: "security.corroboration",
124
+ actor: systemActor(e.component),
125
+ metadata: {
126
+ actionType: e.actionType,
127
+ sourceCount: e.sourceCount,
128
+ missingRequirements: e.missingRequirements
129
+ }
130
+ };
131
+ }
132
+ function mapReputation(e) {
133
+ return {
134
+ category: "security",
135
+ severity: e.isSuspicious ? "warning" : "info",
136
+ outcome: "success",
137
+ action: "security.reputation",
138
+ actor: userActor(e.username),
139
+ metadata: {
140
+ reputationScore: e.reputationScore,
141
+ isSuspicious: e.isSuspicious,
142
+ effectiveTier: e.effectiveTier,
143
+ signalCount: e.signalCount,
144
+ component: e.component
145
+ }
146
+ };
147
+ }
148
+ function mapSanitization(e) {
149
+ return {
150
+ category: "security",
151
+ severity: e.injectionFlagCount > 0 ? "warning" : "info",
152
+ outcome: "success",
153
+ action: "security.sanitization",
154
+ actor: systemActor(e.component, e.source),
155
+ metadata: {
156
+ source: e.source,
157
+ wasModified: e.wasModified,
158
+ strippedCount: e.strippedCount,
159
+ injectionFlagCount: e.injectionFlagCount,
160
+ strippedElements: e.strippedElements
161
+ }
162
+ };
163
+ }
164
+ function mapGraphExecution(e) {
165
+ return {
166
+ category: "system",
167
+ severity: "info",
168
+ outcome: "success",
169
+ action: "security.graph_execution",
170
+ actor: systemActor(e.component),
171
+ description: e.detail,
172
+ metadata: {
173
+ graphEvent: e.graphEvent,
174
+ ...e.nodeId !== void 0 ? { nodeId: e.nodeId } : {},
175
+ stepNumber: e.stepNumber
176
+ }
177
+ };
178
+ }
179
+ function mapClawGuard(e) {
180
+ return {
181
+ category: "authorization",
182
+ severity: "warning",
183
+ outcome: "success",
184
+ action: "security.clawguard_violation",
185
+ actor: systemActor(e.component),
186
+ metadata: {
187
+ toolName: e.toolName,
188
+ warning: e.warning,
189
+ policySource: e.policySource,
190
+ mode: e.mode,
191
+ requestId: e.requestId
192
+ }
193
+ };
194
+ }
195
+ function securityAuditEventToInput(event) {
196
+ switch (event.type) {
197
+ case "trust_classification":
198
+ return mapTrust(event);
199
+ case "policy_gate":
200
+ return mapPolicyGate(event);
201
+ case "corroboration":
202
+ return mapCorroboration(event);
203
+ case "reputation":
204
+ return mapReputation(event);
205
+ case "sanitization":
206
+ return mapSanitization(event);
207
+ case "graph_execution":
208
+ return mapGraphExecution(event);
209
+ case "clawguard_violation":
210
+ return mapClawGuard(event);
211
+ }
212
+ }
213
+ function createDurableAuditSink(auditLogger) {
214
+ return (event) => {
215
+ try {
216
+ auditLogger.log(securityAuditEventToInput(event));
217
+ } catch (error) {
218
+ logger.warn("Failed to mirror security event to durable audit log", {
219
+ type: event.type,
220
+ error: getErrorMessage(error)
221
+ });
222
+ }
223
+ };
224
+ }
225
+ function createDurableAuditTrail(auditLogger) {
226
+ if (auditLogger === void 0) return void 0;
227
+ return createAuditTrail(createDurableAuditSink(auditLogger));
228
+ }
28
229
 
29
230
  // src/security/trust-classifier.ts
30
231
  function mapAuthorAssociation(association) {
@@ -109,47 +310,180 @@ function getRequiredTrustTier(actionType) {
109
310
  }
110
311
  }
111
312
 
112
- // src/security/policy-gate.ts
313
+ // src/security/firewall/firewall-types.ts
113
314
  import { z as z2 } from "zod";
315
+ var FirewallStagesSchema = z2.object({
316
+ sanitization: z2.boolean().default(true),
317
+ trustClassification: z2.boolean().default(true),
318
+ reputationAssessment: z2.boolean().default(false),
319
+ policyEnforcement: z2.boolean().default(true),
320
+ corroboration: z2.boolean().default(false),
321
+ audit: z2.boolean().default(true)
322
+ });
323
+ var FirewallConfigSchema = z2.object({
324
+ stages: FirewallStagesSchema.default(() => ({
325
+ sanitization: true,
326
+ trustClassification: true,
327
+ reputationAssessment: false,
328
+ policyEnforcement: true,
329
+ corroboration: false,
330
+ audit: true
331
+ })),
332
+ allowlistedMaintainers: z2.array(z2.string().min(1)).default([]),
333
+ maxInputLength: z2.number().int().positive().default(5e4),
334
+ context: z2.object({
335
+ hasWriteAccess: z2.boolean().default(false),
336
+ hasSecretAccess: z2.boolean().default(false)
337
+ }).default(() => ({
338
+ hasWriteAccess: false,
339
+ hasSecretAccess: false
340
+ }))
341
+ });
342
+ var ATLDataSchema = z2.object({
343
+ tier: z2.enum(["1", "2", "3", "4"]),
344
+ source: z2.string().min(1),
345
+ user: z2.string().min(1),
346
+ sanitized: z2.boolean(),
347
+ rep: z2.number().min(0).max(1).optional()
348
+ });
349
+
350
+ // src/security/firewall/agent-trust-labels.ts
351
+ var ATL_PREFIX = "[ATL:";
352
+ var ATL_SUFFIX = "]";
353
+ var ATL_PATTERN = /^\[ATL:(.+)\]$/;
354
+ function generateATL(data) {
355
+ const validated = ATLDataSchema.parse(data);
356
+ const parts = [
357
+ `tier=${validated.tier}`,
358
+ `source=${encodeATLValue(validated.source)}`,
359
+ `user=${encodeATLValue(validated.user)}`,
360
+ `sanitized=${String(validated.sanitized)}`
361
+ ];
362
+ if (validated.rep !== void 0) {
363
+ parts.push(`rep=${validated.rep.toFixed(2)}`);
364
+ }
365
+ return `${ATL_PREFIX}${parts.join(",")}${ATL_SUFFIX}`;
366
+ }
367
+ function parseATL(atl) {
368
+ const match = ATL_PATTERN.exec(atl.trim());
369
+ if (match === null) return void 0;
370
+ const body = match[1];
371
+ if (body === void 0) return void 0;
372
+ const raw = parseKeyValuePairs(body);
373
+ if (raw === void 0) return void 0;
374
+ return validateParsedATL(raw);
375
+ }
376
+ function parseKeyValuePairs(body) {
377
+ const pairs = body.split(",");
378
+ const raw = {};
379
+ for (const pair of pairs) {
380
+ const eqIndex = pair.indexOf("=");
381
+ if (eqIndex < 1) return void 0;
382
+ raw[pair.slice(0, eqIndex)] = pair.slice(eqIndex + 1);
383
+ }
384
+ return raw;
385
+ }
386
+ function validateParsedATL(raw) {
387
+ const tier = raw["tier"];
388
+ const source = raw["source"];
389
+ const user = raw["user"];
390
+ const sanitized = raw["sanitized"];
391
+ if (tier === void 0 || source === void 0 || user === void 0 || sanitized === void 0) {
392
+ return void 0;
393
+ }
394
+ const parsed = {
395
+ tier,
396
+ source: decodeATLValue(source),
397
+ user: decodeATLValue(user),
398
+ sanitized: sanitized === "true",
399
+ ...raw["rep"] !== void 0 ? { rep: parseFloat(raw["rep"]) } : {}
400
+ };
401
+ const result = ATLDataSchema.safeParse(parsed);
402
+ return result.success ? result.data : void 0;
403
+ }
404
+ function encodeATLValue(value) {
405
+ return value.replace(/,/g, "%2C").replace(/=/g, "%3D").replace(/\]/g, "%5D");
406
+ }
407
+ function decodeATLValue(value) {
408
+ return value.replace(/%2C/g, ",").replace(/%3D/g, "=").replace(/%5D/g, "]");
409
+ }
410
+
411
+ // src/security/firewall/firewall-passthrough.ts
412
+ function createPassthroughSanitized(meta) {
413
+ return {
414
+ content: meta.content,
415
+ originalLength: meta.content.length,
416
+ trustTier: "3",
417
+ userRole: mapAuthorAssociation(meta.authorAssociation),
418
+ injectionFlags: [],
419
+ strippedElements: [],
420
+ wasModified: false,
421
+ sanitizedAt: (/* @__PURE__ */ new Date()).toISOString()
422
+ };
423
+ }
424
+ function createPassthroughClassification(meta) {
425
+ return {
426
+ trustTier: "3",
427
+ userRole: mapAuthorAssociation(meta.authorAssociation),
428
+ isAllowlisted: false,
429
+ wasDowngraded: false,
430
+ reason: "Trust classification disabled \u2014 default Tier 3"
431
+ };
432
+ }
433
+
434
+ // src/security/firewall/firewall-trust-reason.ts
435
+ function describeGate(reason, demoted, gate) {
436
+ if (gate === void 0) return reason;
437
+ if (demoted) {
438
+ return `${reason}; demoted to Tier ${gate.enforcedTier} by reputation gating (${gate.mode})`;
439
+ }
440
+ if (gate.demotionSuppressed) {
441
+ return `${reason}; would demote to Tier ${gate.reconciledTier} (reputation gating: ${gate.mode})`;
442
+ }
443
+ return reason;
444
+ }
445
+
446
+ // src/security/policy-gate.ts
447
+ import { z as z4 } from "zod";
114
448
 
115
449
  // src/security/action-schema.ts
116
- import { z } from "zod";
117
- var RepoFileSource = z.object({
118
- type: z.literal("repoFile"),
119
- path: z.string().min(1),
120
- line: z.number().int().positive().optional(),
121
- commit: z.string().regex(/^[a-f0-9]{7,40}$/).optional()
450
+ import { z as z3 } from "zod";
451
+ var RepoFileSource = z3.object({
452
+ type: z3.literal("repoFile"),
453
+ path: z3.string().min(1),
454
+ line: z3.number().int().positive().optional(),
455
+ commit: z3.string().regex(/^[a-f0-9]{7,40}$/).optional()
122
456
  });
123
- var IssueCommentSource = z.object({
124
- type: z.literal("issueComment"),
125
- issueNumber: z.number().int().positive(),
126
- commentId: z.number().int().positive(),
127
- author: z.string().min(1),
457
+ var IssueCommentSource = z3.object({
458
+ type: z3.literal("issueComment"),
459
+ issueNumber: z3.number().int().positive(),
460
+ commentId: z3.number().int().positive(),
461
+ author: z3.string().min(1),
128
462
  authorTrustTier: TrustTierSchema
129
463
  });
130
- var IssueBodySource = z.object({
131
- type: z.literal("issueBody"),
132
- issueNumber: z.number().int().positive(),
133
- author: z.string().min(1),
464
+ var IssueBodySource = z3.object({
465
+ type: z3.literal("issueBody"),
466
+ issueNumber: z3.number().int().positive(),
467
+ author: z3.string().min(1),
134
468
  authorTrustTier: TrustTierSchema
135
469
  });
136
- var CIResultSource = z.object({
137
- type: z.literal("ciResult"),
138
- runId: z.number().int().positive(),
139
- status: z.enum(["pass", "fail"]),
140
- job: z.string().min(1)
470
+ var CIResultSource = z3.object({
471
+ type: z3.literal("ciResult"),
472
+ runId: z3.number().int().positive(),
473
+ status: z3.enum(["pass", "fail"]),
474
+ job: z3.string().min(1)
141
475
  });
142
- var PolicyDocSource = z.object({
143
- type: z.literal("policyDoc"),
144
- path: z.string().min(1),
145
- section: z.string().min(1)
476
+ var PolicyDocSource = z3.object({
477
+ type: z3.literal("policyDoc"),
478
+ path: z3.string().min(1),
479
+ section: z3.string().min(1)
146
480
  });
147
- var MaintainerCommandSource = z.object({
148
- type: z.literal("maintainerCommand"),
149
- username: z.string().min(1),
150
- commentId: z.number().int().positive()
481
+ var MaintainerCommandSource = z3.object({
482
+ type: z3.literal("maintainerCommand"),
483
+ username: z3.string().min(1),
484
+ commentId: z3.number().int().positive()
151
485
  });
152
- var SourceCitationSchema = z.discriminatedUnion("type", [
486
+ var SourceCitationSchema = z3.discriminatedUnion("type", [
153
487
  IssueBodySource,
154
488
  RepoFileSource,
155
489
  IssueCommentSource,
@@ -157,66 +491,66 @@ var SourceCitationSchema = z.discriminatedUnion("type", [
157
491
  PolicyDocSource,
158
492
  MaintainerCommandSource
159
493
  ]);
160
- var SummarizeIssueAction = z.object({
161
- type: z.literal("SummarizeIssue"),
162
- summary: z.string().min(10).max(2e3),
163
- sources: z.array(SourceCitationSchema).min(1).max(20)
494
+ var SummarizeIssueAction = z3.object({
495
+ type: z3.literal("SummarizeIssue"),
496
+ summary: z3.string().min(10).max(2e3),
497
+ sources: z3.array(SourceCitationSchema).min(1).max(20)
164
498
  });
165
- var ProposeLabelsAction = z.object({
166
- type: z.literal("ProposeLabels"),
167
- labels: z.array(z.string()).min(1).max(5),
168
- reason: z.string().min(10).max(500),
169
- sources: z.array(SourceCitationSchema).min(1).max(20)
499
+ var ProposeLabelsAction = z3.object({
500
+ type: z3.literal("ProposeLabels"),
501
+ labels: z3.array(z3.string()).min(1).max(5),
502
+ reason: z3.string().min(10).max(500),
503
+ sources: z3.array(SourceCitationSchema).min(1).max(20)
170
504
  });
171
- var DraftReplyAction = z.object({
172
- type: z.literal("DraftReply"),
173
- body: z.string().min(10).max(2e3),
174
- requiresApproval: z.literal(true),
175
- sources: z.array(SourceCitationSchema).min(1).max(20)
505
+ var DraftReplyAction = z3.object({
506
+ type: z3.literal("DraftReply"),
507
+ body: z3.string().min(10).max(2e3),
508
+ requiresApproval: z3.literal(true),
509
+ sources: z3.array(SourceCitationSchema).min(1).max(20)
176
510
  });
177
- var RequestHumanApprovalAction = z.object({
178
- type: z.literal("RequestHumanApproval"),
179
- reason: z.string().min(10).max(500),
180
- context: z.string().min(10).max(2e3)
511
+ var RequestHumanApprovalAction = z3.object({
512
+ type: z3.literal("RequestHumanApproval"),
513
+ reason: z3.string().min(10).max(500),
514
+ context: z3.string().min(10).max(2e3)
181
515
  });
182
- var GeneratePatchPlanAction = z.object({
183
- type: z.literal("GeneratePatchPlan"),
184
- files: z.array(
185
- z.object({
186
- path: z.string().min(1),
187
- operation: z.enum(["modify", "create", "delete"]),
188
- description: z.string().min(10).max(500)
516
+ var GeneratePatchPlanAction = z3.object({
517
+ type: z3.literal("GeneratePatchPlan"),
518
+ files: z3.array(
519
+ z3.object({
520
+ path: z3.string().min(1),
521
+ operation: z3.enum(["modify", "create", "delete"]),
522
+ description: z3.string().min(10).max(500)
189
523
  })
190
524
  ).min(1).max(10),
191
- rationale: z.string().min(10).max(1e3),
192
- requiresApproval: z.literal(true),
193
- sources: z.array(SourceCitationSchema).min(2).max(20)
525
+ rationale: z3.string().min(10).max(1e3),
526
+ requiresApproval: z3.literal(true),
527
+ sources: z3.array(SourceCitationSchema).min(2).max(20)
194
528
  });
195
- var ClassifyIssueAction = z.object({
196
- type: z.literal("ClassifyIssue"),
197
- category: z.enum(["bug", "feature", "question", "documentation", "security", "performance"]),
198
- confidence: z.number().min(0).max(1),
199
- sources: z.array(SourceCitationSchema).min(1).max(20)
529
+ var ClassifyIssueAction = z3.object({
530
+ type: z3.literal("ClassifyIssue"),
531
+ category: z3.enum(["bug", "feature", "question", "documentation", "security", "performance"]),
532
+ confidence: z3.number().min(0).max(1),
533
+ sources: z3.array(SourceCitationSchema).min(1).max(20)
200
534
  });
201
- var IdentifyDuplicatesAction = z.object({
202
- type: z.literal("IdentifyDuplicates"),
203
- candidates: z.array(z.number().int().positive()).min(1).max(10),
204
- similarity: z.array(z.number().min(0).max(1)),
205
- sources: z.array(SourceCitationSchema).min(1).max(20)
535
+ var IdentifyDuplicatesAction = z3.object({
536
+ type: z3.literal("IdentifyDuplicates"),
537
+ candidates: z3.array(z3.number().int().positive()).min(1).max(10),
538
+ similarity: z3.array(z3.number().min(0).max(1)),
539
+ sources: z3.array(SourceCitationSchema).min(1).max(20)
206
540
  });
207
- var RefuseActionAction = z.object({
208
- type: z.literal("RefuseAction"),
209
- reason: z.string().min(10).max(500),
210
- escalateTo: z.enum(["maintainer", "security"])
541
+ var RefuseActionAction = z3.object({
542
+ type: z3.literal("RefuseAction"),
543
+ reason: z3.string().min(10).max(500),
544
+ escalateTo: z3.enum(["maintainer", "security"])
211
545
  });
212
- var HandoffMessageAction = z.object({
213
- type: z.literal("HandoffMessage"),
214
- targetCapability: z.string().min(1).max(100),
215
- reason: z.string().min(5).max(500),
216
- inputTrustTier: z.enum(["1", "2", "3", "4"]),
217
- sources: z.array(SourceCitationSchema).min(1).max(20)
546
+ var HandoffMessageAction = z3.object({
547
+ type: z3.literal("HandoffMessage"),
548
+ targetCapability: z3.string().min(1).max(100),
549
+ reason: z3.string().min(5).max(500),
550
+ inputTrustTier: z3.enum(["1", "2", "3", "4"]),
551
+ sources: z3.array(SourceCitationSchema).min(1).max(20)
218
552
  });
219
- var AgentActionSchema = z.discriminatedUnion("type", [
553
+ var AgentActionSchema = z3.discriminatedUnion("type", [
220
554
  SummarizeIssueAction,
221
555
  ProposeLabelsAction,
222
556
  DraftReplyAction,
@@ -275,13 +609,13 @@ function requiresCitation(actionType) {
275
609
  }
276
610
 
277
611
  // src/security/policy-gate.ts
278
- var ViolationSchema = z2.object({
612
+ var ViolationSchema = z4.object({
279
613
  /** Machine-readable rule identifier. */
280
- rule: z2.string().min(1),
614
+ rule: z4.string().min(1),
281
615
  /** Human-readable description of the violation. */
282
- message: z2.string().min(1),
616
+ message: z4.string().min(1),
283
617
  /** Severity: 'block' prevents execution, 'warn' logs only. */
284
- severity: z2.enum(["block", "warn"])
618
+ severity: z4.enum(["block", "warn"])
285
619
  });
286
620
  function getActionSources(action) {
287
621
  if ("sources" in action) {
@@ -554,6 +888,468 @@ function getCorroborationRules(actionType) {
554
888
  return ACTION_CORROBORATION_RULES[actionType];
555
889
  }
556
890
 
891
+ // src/security/firewall/firewall-pipeline.ts
892
+ var logger2 = createLogger({ component: "HostileInputFirewall" });
893
+ var HostileInputFirewall = class {
894
+ stages;
895
+ /**
896
+ * Construction-time allowlist, or `undefined` when none was supplied (#4992).
897
+ * The Zod default of `[]` is deliberately NOT taken here: an absent list and
898
+ * an empty list must stay distinguishable so `isAllowlisted` can be omitted
899
+ * rather than recorded as `false` when nothing was consulted.
900
+ */
901
+ allowlisted;
902
+ maxInputLength;
903
+ adapter;
904
+ reputationCache;
905
+ auditTrail;
906
+ context;
907
+ /** #5382 rollout gate; resolved once at construction, not per call. */
908
+ policyMode;
909
+ /** #5381 reputation-demotion gate; a DIFFERENT knob from `policyMode`. */
910
+ reputationGatingMode;
911
+ /** #5405 seam: how a reputation assessment is obtained. */
912
+ assessReputationFn;
913
+ /** #4992: whether the sanitizer's content tier downgrades the classifier tier. */
914
+ contentDowngrade;
915
+ /** #4992 review: whether a durable logger was configured (not per-call delivery). */
916
+ auditSink;
917
+ constructor(config) {
918
+ const validated = FirewallConfigSchema.parse({
919
+ stages: config.stages,
920
+ allowlistedMaintainers: config.allowlistedMaintainers,
921
+ maxInputLength: config.maxInputLength,
922
+ context: config.context
923
+ });
924
+ this.stages = validated.stages;
925
+ this.allowlisted = config.allowlistedMaintainers !== void 0 ? validated.allowlistedMaintainers : void 0;
926
+ this.maxInputLength = validated.maxInputLength;
927
+ this.adapter = config.adapter;
928
+ this.reputationCache = new ReputationCache();
929
+ this.auditTrail = createAuditTrail(
930
+ config.auditLogger !== void 0 ? createDurableAuditSink(config.auditLogger) : void 0
931
+ );
932
+ this.auditSink = config.auditLogger !== void 0 ? "configured" : "none";
933
+ this.context = validated.context;
934
+ this.policyMode = config.policyMode ?? resolveFirewallPolicyMode(config.env ?? process.env);
935
+ this.reputationGatingMode = config.reputationGatingMode ?? resolveReputationGatingMode(config.env ?? process.env);
936
+ this.assessReputationFn = config.reputationAssessor ?? ((metadata) => assessReputation(metadata, this.reputationCache));
937
+ this.contentDowngrade = config.contentDowngrade ?? true;
938
+ }
939
+ /**
940
+ * Processes untrusted input through the firewall pipeline.
941
+ * Returns a structured FirewallResult or a typed FirewallError.
942
+ *
943
+ * `options` carries the per-call facts (#4992): the repository's maintainer
944
+ * allowlist and the caller's access posture. Each replaces its
945
+ * construction-time counterpart for this call only, so one shared instance
946
+ * never holds a repository's allowlist or a caller's posture process-wide.
947
+ */
948
+ process(input, options) {
949
+ const start = Date.now();
950
+ this.auditTrail.clear();
951
+ const allowlist = options?.allowlistedMaintainers ?? this.allowlisted;
952
+ const context = options?.context ?? this.context;
953
+ const metaResult = this.runExtraction(input);
954
+ if (!metaResult.ok) return metaResult;
955
+ const meta = metaResult.value;
956
+ const sanitized = this.runSanitization(meta, allowlist);
957
+ const trust = this.runClassification(meta, sanitized, allowlist);
958
+ const reputation = this.runReputation(meta, sanitized, options?.reputation);
959
+ const { tier: effectiveTrustTier, gate: reputationGate } = this.runReputationGate(
960
+ trust.trustTier,
961
+ reputation,
962
+ options?.reputation !== void 0
963
+ );
964
+ this.recordTrustDecision(meta, trust, effectiveTrustTier, reputationGate, allowlist);
965
+ const atl = this.buildATL(meta, effectiveTrustTier, sanitized, reputation);
966
+ const ruleOfTwoViolation = this.runPolicyEnforcement(meta, effectiveTrustTier, context);
967
+ const refusal = this.refuseIfEnforcing(ruleOfTwoViolation, meta, effectiveTrustTier);
968
+ if (refusal !== void 0) return err(refusal);
969
+ return ok(
970
+ this.assembleResult({
971
+ sanitized,
972
+ trust,
973
+ // Measured or absent: present only when an allowlist was consulted.
974
+ isAllowlisted: allowlist !== void 0 ? trust.isAllowlisted : void 0,
975
+ reputation,
976
+ effectiveTrustTier,
977
+ reputationGate,
978
+ atl,
979
+ ruleOfTwoViolation,
980
+ start
981
+ })
982
+ );
983
+ }
984
+ /**
985
+ * Evaluates the Rule of Two against the call's access posture and surfaces
986
+ * the violation (previously the `policyEnforcement` stage was declared but
987
+ * never read — #3198). Returns `undefined` when the stage is disabled or the
988
+ * rule holds.
989
+ */
990
+ runPolicyEnforcement(meta, effectiveTrustTier, context) {
991
+ if (!this.stages.policyEnforcement) return void 0;
992
+ const violation = checkRuleOfTwo({
993
+ inputTrustTier: effectiveTrustTier,
994
+ hasWriteAccess: context.hasWriteAccess,
995
+ hasSecretAccess: context.hasSecretAccess
996
+ });
997
+ if (violation !== void 0) {
998
+ logger2.warn("Firewall surfaced a Rule-of-Two violation", {
999
+ user: meta.username,
1000
+ effectiveTrustTier,
1001
+ rule: violation.rule
1002
+ });
1003
+ }
1004
+ return violation;
1005
+ }
1006
+ /**
1007
+ * Builds the successful result. Optional fields are spread in only when they
1008
+ * were evaluated, so absence keeps meaning "not measured" (see the field
1009
+ * docs on {@link FirewallResult}).
1010
+ */
1011
+ assembleResult(parts) {
1012
+ const { isAllowlisted, reputation, reputationGate, ruleOfTwoViolation } = parts;
1013
+ const blocking = ruleOfTwoViolation?.severity === "block";
1014
+ return {
1015
+ sanitized: parts.sanitized,
1016
+ trust: parts.trust,
1017
+ ...isAllowlisted !== void 0 ? { isAllowlisted } : {},
1018
+ ...reputation !== void 0 ? { reputation } : {},
1019
+ effectiveTrustTier: parts.effectiveTrustTier,
1020
+ ...reputationGate !== void 0 ? { reputationGate } : {},
1021
+ atl: parts.atl,
1022
+ ...ruleOfTwoViolation !== void 0 ? { ruleOfTwoViolation } : {},
1023
+ policyMode: this.policyMode,
1024
+ // Reached only when we did NOT refuse, so this is "audit mode saw
1025
+ // something enforce would have stopped". Under `off` it stays false: the
1026
+ // signal is already on `ruleOfTwoViolation`, and reporting a would-be
1027
+ // refusal for a mode that has not opted in would overstate the gate.
1028
+ wouldRefuse: blocking && this.policyMode === "audit",
1029
+ auditEvents: this.auditTrail.query().map((e) => ({ id: e.id, type: e.type })),
1030
+ auditSink: this.auditSink,
1031
+ durationMs: Date.now() - parts.start
1032
+ };
1033
+ }
1034
+ /**
1035
+ * The fail-closed half of the #5382 gate: under `enforce`, a blocking policy
1036
+ * violation refuses the input instead of riding along as a signal on an
1037
+ * `ok()` result that a caller checking only `result.ok` walks straight past.
1038
+ *
1039
+ * This gates the RESPONSE to a violation, never its detection. It cannot
1040
+ * manufacture a refusal where the `policyEnforcement` stage never ran, and it
1041
+ * cannot refuse a non-blocking (`warn`) violation — so `enforce` is not a
1042
+ * kill switch, and an allowlisted maintainer stays served.
1043
+ *
1044
+ * @returns the refusal, or `undefined` when the input passes or the mode has
1045
+ * not opted in.
1046
+ */
1047
+ refuseIfEnforcing(violation, meta, effectiveTrustTier) {
1048
+ if (violation?.severity !== "block" || this.policyMode !== "enforce") return void 0;
1049
+ logger2.warn("Firewall REFUSED input under enforce mode", {
1050
+ user: meta.username,
1051
+ effectiveTrustTier,
1052
+ rule: violation.rule
1053
+ });
1054
+ return {
1055
+ code: "POLICY_REFUSED",
1056
+ message: `Refused by firewall policy: ${violation.rule} \u2014 ${violation.message}`,
1057
+ stage: "policy"
1058
+ };
1059
+ }
1060
+ /**
1061
+ * Validates corroboration for a decided action (#5382).
1062
+ *
1063
+ * Separate from {@link process} because the two operate at different points
1064
+ * in the lifecycle, which is the real shape of the divergence epic #5281
1065
+ * found: `process()` is INPUT-shaped — it sanitizes, classifies and labels
1066
+ * untrusted content — while corroboration is ACTION-shaped, asking whether a
1067
+ * decision the consumer has now reached is backed by sources of sufficient
1068
+ * tier. There is no `AgentAction` in scope during `process()`, so the
1069
+ * `stages.corroboration` flag could never have been wired there; this is the
1070
+ * entry point that makes it readable.
1071
+ *
1072
+ * It is also the shape #5383 needs: production validates corroboration per
1073
+ * action (`issue-triage.ts:391`), so those callers cannot migrate onto the
1074
+ * firewall unless it offers a per-action surface.
1075
+ *
1076
+ * Returns `evaluated: false` when the stage is disabled — never a satisfied
1077
+ * verdict for a check that did not run. Under `enforce` an unsatisfied action
1078
+ * is refused with `POLICY_REFUSED`; under `audit` the would-be refusal is
1079
+ * reported via `wouldRefuse` and the action is allowed through.
1080
+ */
1081
+ validateAction(action) {
1082
+ if (!this.stages.corroboration) {
1083
+ return ok({
1084
+ evaluated: false,
1085
+ reason: "corroboration-stage-disabled",
1086
+ policyMode: this.policyMode
1087
+ });
1088
+ }
1089
+ const corroboration = validateCorroboration(action);
1090
+ if (!corroboration.satisfied && this.policyMode === "enforce") {
1091
+ logger2.warn("Firewall REFUSED an uncorroborated action under enforce mode", {
1092
+ actionType: corroboration.actionType,
1093
+ missing: corroboration.missing
1094
+ });
1095
+ return err({
1096
+ code: "POLICY_REFUSED",
1097
+ message: `Refused by firewall policy: ${corroboration.actionType} lacks required corroboration \u2014 ${corroboration.missing.join("; ")}`,
1098
+ stage: "corroboration"
1099
+ });
1100
+ }
1101
+ return ok({
1102
+ evaluated: true,
1103
+ satisfied: corroboration.satisfied,
1104
+ missing: corroboration.missing,
1105
+ corroboratingSources: corroboration.corroboratingSources,
1106
+ policyMode: this.policyMode,
1107
+ wouldRefuse: !corroboration.satisfied && this.policyMode === "audit"
1108
+ });
1109
+ }
1110
+ /** Returns the internal audit trail for inspection. */
1111
+ getAuditTrail() {
1112
+ return this.auditTrail;
1113
+ }
1114
+ // ==========================================================================
1115
+ // Pipeline Stages (private)
1116
+ // ==========================================================================
1117
+ runExtraction(input) {
1118
+ try {
1119
+ const meta = this.adapter.extractMetadata(input);
1120
+ return ok(meta);
1121
+ } catch (cause) {
1122
+ const message = cause instanceof Error ? cause.message : String(cause);
1123
+ return err({
1124
+ code: "EXTRACTION_FAILED",
1125
+ message: `Adapter extraction failed: ${message}`,
1126
+ stage: "extraction"
1127
+ });
1128
+ }
1129
+ }
1130
+ runSanitization(meta, allowlist) {
1131
+ if (!this.stages.sanitization) {
1132
+ return createPassthroughSanitized(meta);
1133
+ }
1134
+ const result = sanitizeInput(
1135
+ meta.content,
1136
+ mapAuthorAssociation(meta.authorAssociation),
1137
+ meta.username,
1138
+ {
1139
+ allowlistedMaintainers: [...allowlist ?? []],
1140
+ maxInputLength: this.maxInputLength
1141
+ }
1142
+ );
1143
+ if (this.stages.audit) {
1144
+ emitSanitizationEvent(this.auditTrail, {
1145
+ source: meta.sourceType,
1146
+ wasModified: result.wasModified,
1147
+ strippedCount: result.strippedElements.length,
1148
+ injectionFlagCount: result.injectionFlags.length,
1149
+ strippedElements: result.strippedElements.slice(0, MAX_STRIPPED_ELEMENTS_PER_EVENT).map((e) => ({ tag: e.tag, reason: e.reason }))
1150
+ });
1151
+ }
1152
+ return result;
1153
+ }
1154
+ runClassification(meta, sanitized, allowlist) {
1155
+ if (!this.stages.trustClassification) {
1156
+ return createPassthroughClassification(meta);
1157
+ }
1158
+ return classifyTrust({
1159
+ username: meta.username,
1160
+ authorAssociation: meta.authorAssociation,
1161
+ // #4992: `contentDowngrade: false` withholds the sanitizer's content tier
1162
+ // from the classifier; the flags themselves were still measured above.
1163
+ ...this.contentDowngrade ? { sanitizedInput: sanitized } : {},
1164
+ config: {
1165
+ allowlistedMaintainers: [...allowlist ?? []]
1166
+ }
1167
+ });
1168
+ }
1169
+ /**
1170
+ * Emits the trust audit event for the tier the consumer acts on — after the
1171
+ * reputation gate, so the record cannot describe a tier nobody enforced
1172
+ * (#4992 review). A demotion is named in `reason`.
1173
+ */
1174
+ recordTrustDecision(meta, trust, effectiveTrustTier, gate, allowlist) {
1175
+ if (!this.stages.audit) return;
1176
+ const demoted = effectiveTrustTier !== trust.trustTier;
1177
+ emitTrustEvent(this.auditTrail, {
1178
+ username: meta.username,
1179
+ assignedTier: effectiveTrustTier,
1180
+ userRole: trust.userRole,
1181
+ // Recorded only when an allowlist was consulted (#4992).
1182
+ ...allowlist !== void 0 ? { isAllowlisted: trust.isAllowlisted } : {},
1183
+ wasDowngraded: trust.wasDowngraded || demoted,
1184
+ reason: describeGate(trust.reason, demoted, gate)
1185
+ });
1186
+ }
1187
+ /**
1188
+ * Reconcile the classifier tier with reputation, under the rollout mode
1189
+ * production honours (#3106 reconciliation, #5381 gating).
1190
+ *
1191
+ * #3106's reconciliation is demotion-only; Tier-1/allowlist wins; it equals
1192
+ * the classifier tier when reputation is absent. #5381 puts it behind
1193
+ * `NEXUS_REPUTATION_GATING`, so `audit` is audit-only here too — previously
1194
+ * the firewall enforced unconditionally while production suppressed, on
1195
+ * identical configuration.
1196
+ *
1197
+ * Returns no gate at all when the stage is off, so absence in the result
1198
+ * means "not evaluated" rather than "evaluated, nothing suppressed" —
1199
+ * `demotionSuppressed` is a required boolean and would otherwise report
1200
+ * `false` for a check that never ran.
1201
+ */
1202
+ runReputationGate(classifierTier, reputation, callerSupplied) {
1203
+ if (!this.stages.reputationAssessment && !callerSupplied) return { tier: classifierTier };
1204
+ const gate = gateWithReputation(classifierTier, reputation, this.reputationGatingMode);
1205
+ return { tier: gate.enforcedTier, gate };
1206
+ }
1207
+ runReputation(meta, sanitized, supplied) {
1208
+ if (supplied !== void 0) {
1209
+ if (supplied.assessment !== void 0 && this.stages.audit) {
1210
+ emitReputationEvent(this.auditTrail, {
1211
+ username: meta.username,
1212
+ reputationScore: supplied.assessment.reputationScore,
1213
+ isSuspicious: supplied.assessment.isSuspicious,
1214
+ effectiveTier: supplied.assessment.effectiveTrustTier,
1215
+ signalCount: supplied.assessment.suspiciousSignals.length
1216
+ });
1217
+ }
1218
+ return supplied.assessment;
1219
+ }
1220
+ if (!this.stages.reputationAssessment) return void 0;
1221
+ const metadata = {
1222
+ username: meta.username,
1223
+ authorAssociation: meta.authorAssociation.toUpperCase(),
1224
+ injectionFlags: sanitized.injectionFlags
1225
+ };
1226
+ const result = this.assessReputationFn(metadata);
1227
+ if (this.stages.audit) {
1228
+ emitReputationEvent(this.auditTrail, {
1229
+ username: meta.username,
1230
+ reputationScore: result.reputationScore,
1231
+ isSuspicious: result.isSuspicious,
1232
+ effectiveTier: result.effectiveTrustTier,
1233
+ signalCount: result.suspiciousSignals.length
1234
+ });
1235
+ }
1236
+ return result;
1237
+ }
1238
+ buildATL(meta, effectiveTrustTier, sanitized, reputation) {
1239
+ const data = {
1240
+ tier: effectiveTrustTier,
1241
+ source: meta.sourceType,
1242
+ user: meta.username,
1243
+ sanitized: sanitized.wasModified,
1244
+ ...reputation !== void 0 ? { rep: reputation.reputationScore / 100 } : {}
1245
+ };
1246
+ return generateATL(data);
1247
+ }
1248
+ };
1249
+
1250
+ // src/security/firewall/github-adapter.ts
1251
+ import { z as z5 } from "zod";
1252
+ var GitHubIssueSchema = z5.object({
1253
+ type: z5.literal("issue"),
1254
+ username: z5.string().min(1),
1255
+ authorAssociation: z5.string().min(1),
1256
+ title: z5.string().default(""),
1257
+ body: z5.string().default("")
1258
+ });
1259
+ var GitHubCommentSchema = z5.object({
1260
+ type: z5.literal("comment"),
1261
+ username: z5.string().min(1),
1262
+ authorAssociation: z5.string().min(1),
1263
+ body: z5.string().default("")
1264
+ });
1265
+ var GitHubPRSchema = z5.object({
1266
+ type: z5.literal("pull_request"),
1267
+ username: z5.string().min(1),
1268
+ authorAssociation: z5.string().min(1),
1269
+ title: z5.string().default(""),
1270
+ body: z5.string().default("")
1271
+ });
1272
+ var GitHubInputSchema = z5.discriminatedUnion("type", [
1273
+ GitHubIssueSchema,
1274
+ GitHubCommentSchema,
1275
+ GitHubPRSchema
1276
+ ]);
1277
+ function toSourceType(type) {
1278
+ const mapping = {
1279
+ issue: "github-issue",
1280
+ comment: "github-comment",
1281
+ pull_request: "github-pr"
1282
+ };
1283
+ return mapping[type];
1284
+ }
1285
+ function extractContent(input) {
1286
+ if (input.type === "comment") return input.body;
1287
+ const title = input.title;
1288
+ const body = input.body;
1289
+ return title && body ? `${title}
1290
+
1291
+ ${body}` : title || body;
1292
+ }
1293
+ function createGitHubAdapter() {
1294
+ return {
1295
+ platform: "github",
1296
+ extractMetadata(input) {
1297
+ const result = GitHubInputSchema.safeParse(input);
1298
+ if (!result.success) {
1299
+ const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
1300
+ throw new Error(`GitHub input validation failed: ${issues}`);
1301
+ }
1302
+ const parsed = result.data;
1303
+ const role = mapAuthorAssociation(parsed.authorAssociation);
1304
+ return {
1305
+ username: parsed.username,
1306
+ authorAssociation: role,
1307
+ content: extractContent(parsed),
1308
+ sourceType: toSourceType(parsed.type)
1309
+ };
1310
+ }
1311
+ };
1312
+ }
1313
+
1314
+ // src/dogfooding/untrusted-input-firewall.ts
1315
+ var logger3 = createLogger({ component: "UntrustedInputFirewall" });
1316
+ var singleton;
1317
+ var configuredAuditLogger;
1318
+ function configureUntrustedInputFirewall(deps) {
1319
+ configuredAuditLogger = deps.auditLogger;
1320
+ singleton = void 0;
1321
+ }
1322
+ function getUntrustedInputFirewall() {
1323
+ singleton ??= new HostileInputFirewall({
1324
+ adapter: createGitHubAdapter(),
1325
+ contentDowngrade: false,
1326
+ ...configuredAuditLogger !== void 0 ? { auditLogger: configuredAuditLogger } : {}
1327
+ });
1328
+ return singleton;
1329
+ }
1330
+ function runUntrustedInputFirewall(input, options) {
1331
+ const result = getUntrustedInputFirewall().process(input, options);
1332
+ if (!result.ok) {
1333
+ const { code, stage, message } = result.error;
1334
+ logger3.warn("Untrusted-input firewall rejected the input", {
1335
+ user: input.username,
1336
+ sourceType: input.type,
1337
+ code,
1338
+ stage
1339
+ });
1340
+ return err(new Error(`Untrusted-input firewall ${code} at stage ${stage}: ${message}`));
1341
+ }
1342
+ if (result.value.wouldRefuse) {
1343
+ logger3.warn("Untrusted-input firewall would refuse under enforce (audit mode)", {
1344
+ user: input.username,
1345
+ sourceType: input.type,
1346
+ trustTier: result.value.effectiveTrustTier,
1347
+ rule: result.value.ruleOfTwoViolation?.rule
1348
+ });
1349
+ }
1350
+ return ok(result.value);
1351
+ }
1352
+
557
1353
  // src/scm/url-parsers.ts
558
1354
  function parsePRUrl(url) {
559
1355
  const httpPattern = /github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/;
@@ -595,7 +1391,7 @@ function parseIssueUrl(url) {
595
1391
  }
596
1392
 
597
1393
  // src/scm/github-provider-traits.ts
598
- var logger = createLogger({ component: "GitHubProviderTraits" });
1394
+ var logger4 = createLogger({ component: "GitHubProviderTraits" });
599
1395
  var cachedGhToken = null;
600
1396
  var ghTokenPromise;
601
1397
  async function resolveGhToken() {
@@ -672,7 +1468,7 @@ var GitHubReviewer = class {
672
1468
  provider;
673
1469
  async getPullRequestDetail(prNumber) {
674
1470
  const repo = this.provider.repo;
675
- logger.debug("Getting PR detail", { repo, prNumber });
1471
+ logger4.debug("Getting PR detail", { repo, prNumber });
676
1472
  const prResult = await execGhApi(`repos/${repo}/pulls/${String(prNumber)}`);
677
1473
  if (!prResult.ok) return prResult;
678
1474
  const filesResult = await execGhApi(`repos/${repo}/pulls/${String(prNumber)}/files`);
@@ -707,7 +1503,7 @@ var GitHubReviewer = class {
707
1503
  request_changes: "REQUEST_CHANGES",
708
1504
  comment: "COMMENT"
709
1505
  };
710
- logger.info("Creating review", { repo, prNumber, decision });
1506
+ logger4.info("Creating review", { repo, prNumber, decision });
711
1507
  const { execFile } = await import("node:child_process");
712
1508
  const { promisify } = await import("node:util");
713
1509
  const exec = promisify(execFile);
@@ -734,7 +1530,7 @@ var GitHubReviewer = class {
734
1530
  }
735
1531
  async getIssueDetail(issueNumber) {
736
1532
  const repo = this.provider.repo;
737
- logger.debug("Getting issue detail", { repo, issueNumber });
1533
+ logger4.debug("Getting issue detail", { repo, issueNumber });
738
1534
  const result = await execGhApi(`repos/${repo}/issues/${String(issueNumber)}`);
739
1535
  if (!result.ok) return result;
740
1536
  try {
@@ -756,7 +1552,7 @@ var GitHubReviewer = class {
756
1552
  }
757
1553
  async listCommentDetails(issueNumber) {
758
1554
  const repo = this.provider.repo;
759
- logger.debug("Listing comment details", { repo, issueNumber });
1555
+ logger4.debug("Listing comment details", { repo, issueNumber });
760
1556
  const result = await execGhApi(`repos/${repo}/issues/${String(issueNumber)}/comments`);
761
1557
  if (!result.ok) return result;
762
1558
  try {
@@ -777,7 +1573,7 @@ var GitHubReviewer = class {
777
1573
  };
778
1574
  var GitHubUserInfo = class {
779
1575
  async fetchUserMetadata(username) {
780
- logger.debug("Fetching user metadata", { username });
1576
+ logger4.debug("Fetching user metadata", { username });
781
1577
  const result = await execGhApi(`users/${username}`);
782
1578
  if (!result.ok) return result;
783
1579
  try {
@@ -810,17 +1606,17 @@ function createFullGitHubProvider(repo) {
810
1606
  }
811
1607
 
812
1608
  // src/dogfooding/issue-triage-types.ts
813
- import { z as z3 } from "zod";
1609
+ import { z as z6 } from "zod";
814
1610
  var DEFAULT_ISSUE_TRIAGE_CONFIG = {
815
1611
  dryRun: true,
816
1612
  maxComments: 50,
817
1613
  enableReputation: true
818
1614
  };
819
- var IssueTriageConfigSchema = z3.object({
820
- dryRun: z3.boolean().default(true),
821
- githubToken: z3.string().optional(),
822
- maxComments: z3.number().int().min(1).max(100).default(50),
823
- enableReputation: z3.boolean().default(true)
1615
+ var IssueTriageConfigSchema = z6.object({
1616
+ dryRun: z6.boolean().default(true),
1617
+ githubToken: z6.string().optional(),
1618
+ maxComments: z6.number().int().min(1).max(100).default(50),
1619
+ enableReputation: z6.boolean().default(true)
824
1620
  });
825
1621
 
826
1622
  // src/dogfooding/issue-triage-helpers.ts
@@ -886,7 +1682,7 @@ function extractLabelsFromBody(title, body) {
886
1682
  }
887
1683
 
888
1684
  // src/dogfooding/issue-triage.ts
889
- var logger2 = createLogger({ component: "IssueTriage" });
1685
+ var logger5 = createLogger({ component: "IssueTriage" });
890
1686
  function applySafetyActions(actions, gateDecision, reputation, issue) {
891
1687
  const withRefusal = appendRefusalIfHostile(actions, gateDecision, issue);
892
1688
  return appendApprovalIfBorderline(withRefusal, gateDecision, reputation, issue);
@@ -932,7 +1728,7 @@ var IssueTriage = class {
932
1728
  */
933
1729
  async triageIssue(issueUrl) {
934
1730
  const startTime = getTimeProvider().now();
935
- logger2.info("Starting issue triage", { issueUrl });
1731
+ logger5.info("Starting issue triage", { issueUrl });
936
1732
  const parseResult = parseIssueUrl(issueUrl);
937
1733
  if (!parseResult.ok) return parseResult;
938
1734
  const { owner, repo, issueNumber } = parseResult.value;
@@ -941,15 +1737,16 @@ var IssueTriage = class {
941
1737
  const { issue: issueResult, comments, accountAgeDays } = fetchResult.value;
942
1738
  const safeTitle = this.sanitizeContent(issueResult.title, issueResult.author);
943
1739
  const safeBody = this.sanitizeContent(issueResult.body, issueResult.author);
944
- const trustResult = this.classifyAuthor(issueResult);
945
1740
  const reputation = this.assessAuthorReputation(issueResult, comments, accountAgeDays);
946
- const gateDecision = gateWithReputation(
947
- trustResult.trustTier,
948
- reputation,
949
- resolveReputationGatingMode()
950
- );
1741
+ const firewall = this.classifyAuthor(issueResult, reputation);
1742
+ if (!firewall.ok) return firewall;
1743
+ const trustResult = firewall.value.trust;
1744
+ const gateDecision = firewall.value.reputationGate;
1745
+ if (gateDecision === void 0) {
1746
+ return err(new Error("Untrusted-input firewall returned no reputation gate decision"));
1747
+ }
951
1748
  if (gateDecision.demotionSuppressed) {
952
- logger2.warn("Reputation demotion suppressed by gating mode (would block under enforce)", {
1749
+ logger5.warn("Reputation demotion suppressed by gating mode (would block under enforce)", {
953
1750
  issueNumber,
954
1751
  author: issueResult.author,
955
1752
  mode: gateDecision.mode,
@@ -964,12 +1761,14 @@ var IssueTriage = class {
964
1761
  issue: issueResult,
965
1762
  actions: validatedActions,
966
1763
  trustResult,
1764
+ isAllowlisted: firewall.value.isAllowlisted,
1765
+ auditSink: firewall.value.auditSink,
967
1766
  reputation,
968
1767
  gateDecision,
969
1768
  safeContent: { title: safeTitle, body: safeBody },
970
1769
  startTime
971
1770
  });
972
- logger2.info("Issue triage completed", {
1771
+ logger5.info("Issue triage completed", {
973
1772
  issueNumber,
974
1773
  category: result.category,
975
1774
  actionCount: result.proposedActions.length,
@@ -985,7 +1784,7 @@ var IssueTriage = class {
985
1784
  if (text.length === 0) return text;
986
1785
  const result = sanitizeInput(text, "unknown", author);
987
1786
  if (result.wasModified) {
988
- logger2.warn("Issue content sanitized", {
1787
+ logger5.warn("Issue content sanitized", {
989
1788
  author,
990
1789
  strippedCount: result.strippedElements.length,
991
1790
  injectionFlags: result.injectionFlags
@@ -994,14 +1793,44 @@ var IssueTriage = class {
994
1793
  return result.content;
995
1794
  }
996
1795
  /**
997
- * Classifies the issue author's trust tier.
998
- * (Source: Issue #828 — trust-classifier wiring)
1796
+ * Classifies the issue author's trust tier through the shared firewall
1797
+ * (#4992; originally Issue #828 — trust-classifier wiring).
1798
+ *
1799
+ * The access posture is passed per call so the firewall's Rule-of-Two check
1800
+ * sees the same facts `validateActions` enforces on. No maintainer allowlist
1801
+ * is passed: there is no source for one today (no config field, no env var),
1802
+ * so none is consulted and `isAllowlisted` stays absent on the result rather
1803
+ * than being recorded as `false`.
999
1804
  */
1000
- classifyAuthor(issue) {
1001
- return classifyTrust({
1002
- username: issue.author,
1003
- authorAssociation: issue.authorAssociation
1004
- });
1805
+ classifyAuthor(issue, reputation) {
1806
+ return runUntrustedInputFirewall(
1807
+ {
1808
+ type: "issue",
1809
+ username: issue.author,
1810
+ authorAssociation: issue.authorAssociation,
1811
+ title: issue.title,
1812
+ body: issue.body
1813
+ },
1814
+ { context: this.accessContext(), reputation: { assessment: reputation } }
1815
+ );
1816
+ }
1817
+ /**
1818
+ * The access posture this triage runs under — the two Rule-of-Two conjuncts
1819
+ * that are about the agent rather than the input. Shared by the firewall
1820
+ * (#4992) and `validateActions` so the two cannot disagree.
1821
+ */
1822
+ accessContext() {
1823
+ return {
1824
+ hasWriteAccess: !this.config.dryRun,
1825
+ // #4681: read the token from where it ACTUALLY comes from. The previous
1826
+ // form (`this.config.githubToken !== undefined`) was written to make this
1827
+ // conjunct "real", but no production caller ever sets `githubToken` — the
1828
+ // SCM provider resolves the live credential from GITHUB_TOKEN/GH_TOKEN.
1829
+ // So the conjunct was permanently false and Rule of Two could never trip,
1830
+ // which is precisely the constant it was introduced to remove.
1831
+ // `hasToken()` is the canonical resolver-backed check.
1832
+ hasSecretAccess: this.config.githubToken !== void 0 || hasToken("github")
1833
+ };
1005
1834
  }
1006
1835
  /**
1007
1836
  * Assesses the issue author's reputation using the reputation model.
@@ -1064,15 +1893,7 @@ var IssueTriage = class {
1064
1893
  validateActions(actions, gateDecision) {
1065
1894
  const context = {
1066
1895
  inputTrustTier: gateDecision.enforcedTier,
1067
- hasWriteAccess: !this.config.dryRun,
1068
- // #4681: read the token from where it ACTUALLY comes from. The previous
1069
- // form (`this.config.githubToken !== undefined`) was written to make this
1070
- // conjunct "real", but no production caller ever sets `githubToken` — the
1071
- // SCM provider resolves the live credential from GITHUB_TOKEN/GH_TOKEN.
1072
- // So the conjunct was permanently false and Rule of Two could never trip,
1073
- // which is precisely the constant it was introduced to remove.
1074
- // `hasToken()` is the canonical resolver-backed check.
1075
- hasSecretAccess: this.config.githubToken !== void 0 || hasToken("github")
1896
+ ...this.accessContext()
1076
1897
  };
1077
1898
  return actions.map((action) => {
1078
1899
  const policyDecision = evaluatePolicy(action, context);
@@ -1098,13 +1919,25 @@ var IssueTriage = class {
1098
1919
  * Builds the final triage result.
1099
1920
  */
1100
1921
  buildResult(opts) {
1101
- const { issue, actions, trustResult, reputation, gateDecision, safeContent, startTime } = opts;
1922
+ const {
1923
+ issue,
1924
+ actions,
1925
+ trustResult,
1926
+ isAllowlisted,
1927
+ auditSink,
1928
+ reputation,
1929
+ gateDecision,
1930
+ safeContent,
1931
+ startTime
1932
+ } = opts;
1102
1933
  const [category, confidence] = categorizeIssue(safeContent.title, safeContent.body);
1103
1934
  const isTier1 = trustResult.trustTier === "1";
1104
1935
  const trustAssessment = {
1105
1936
  trustTier: trustResult.trustTier,
1106
1937
  userRole: trustResult.userRole,
1107
- isAllowlisted: trustResult.isAllowlisted,
1938
+ // Measured or absent (#4992): never the classifier's default `false`.
1939
+ ...isAllowlisted !== void 0 ? { isAllowlisted } : {},
1940
+ auditSink,
1108
1941
  reputationScore: reputation?.reputationScore,
1109
1942
  suspiciousSignals: isTier1 ? [] : reputation?.suspiciousSignals ?? [],
1110
1943
  isSuspicious: isTier1 ? false : reputation?.isSuspicious ?? false,
@@ -1222,11 +2055,19 @@ function createIssueTriage(config) {
1222
2055
  }
1223
2056
 
1224
2057
  export {
2058
+ createDurableAuditSink,
2059
+ createDurableAuditTrail,
2060
+ FirewallPolicyModeSchema,
2061
+ DEFAULT_FIREWALL_POLICY_MODE,
2062
+ FIREWALL_POLICY_ENV_VAR,
2063
+ resolveFirewallPolicyMode,
1225
2064
  mapAuthorAssociation,
1226
2065
  classifyTrust,
1227
2066
  canInfluenceDecisions,
1228
2067
  requiresCorroboration,
1229
2068
  getRequiredTrustTier,
2069
+ generateATL,
2070
+ parseATL,
1230
2071
  SourceCitationSchema,
1231
2072
  AgentActionSchema,
1232
2073
  validateAgentAction,
@@ -1235,11 +2076,14 @@ export {
1235
2076
  requiresHumanApproval,
1236
2077
  requiresCitation,
1237
2078
  ViolationSchema,
1238
- checkRuleOfTwo,
1239
2079
  evaluatePolicy,
1240
2080
  canProceed,
1241
2081
  validateCorroboration,
1242
2082
  getCorroborationRules,
2083
+ HostileInputFirewall,
2084
+ createGitHubAdapter,
2085
+ configureUntrustedInputFirewall,
2086
+ runUntrustedInputFirewall,
1243
2087
  parsePRUrl,
1244
2088
  GitHubReviewer,
1245
2089
  GitHubUserInfo,
@@ -1247,4 +2091,4 @@ export {
1247
2091
  IssueTriage,
1248
2092
  createIssueTriage
1249
2093
  };
1250
- //# sourceMappingURL=chunk-MXQG3A5W.js.map
2094
+ //# sourceMappingURL=chunk-FPHFCZ2E.js.map