pi-harness-runtime 1.1.429 → 1.1.431

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 (139) hide show
  1. package/package.json +1 -1
  2. package/packages/a2a-adapter/package.json +1 -1
  3. package/packages/agent-policy/package.json +1 -1
  4. package/packages/architecture-generator/package.json +1 -1
  5. package/packages/auth/package.json +1 -1
  6. package/packages/autonomous-refactor/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/cache-strategy/package.json +1 -1
  9. package/packages/capability-registry/package.json +1 -1
  10. package/packages/checkpoint/package.json +1 -1
  11. package/packages/cli-plugin-sdk/package.json +1 -1
  12. package/packages/clipboard/package.json +1 -1
  13. package/packages/clipboard-plugin/package.json +1 -1
  14. package/packages/code-generation/package.json +1 -1
  15. package/packages/code-review/package.json +1 -1
  16. package/packages/codex-adapter/package.json +1 -1
  17. package/packages/codex-watcher/package.json +1 -1
  18. package/packages/config-capture/package.json +1 -1
  19. package/packages/connection-runtime/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/contracts/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/durable-review/package.json +1 -1
  30. package/packages/e2e-tester/package.json +1 -1
  31. package/packages/evaluation-engine/package.json +1 -1
  32. package/packages/evaluation-runner/package.json +1 -1
  33. package/packages/event-bus/package.json +1 -1
  34. package/packages/event-sink/package.json +1 -1
  35. package/packages/event-store/package.json +1 -1
  36. package/packages/experience-replay/package.json +1 -1
  37. package/packages/feedback-collector/package.json +1 -1
  38. package/packages/file-copy-helper/package.json +1 -1
  39. package/packages/framework-detector/package.json +1 -1
  40. package/packages/framework-plugin-sdk/package.json +1 -1
  41. package/packages/frappe-plugin/package.json +1 -1
  42. package/packages/generic-web-plugin/package.json +1 -1
  43. package/packages/health-monitor/package.json +1 -1
  44. package/packages/intent-analyzer/package.json +1 -1
  45. package/packages/jev-judge/package.json +1 -1
  46. package/packages/knowledge-graph/package.json +1 -1
  47. package/packages/knowledge-retrieval/package.json +1 -1
  48. package/packages/laravel-plugin/package.json +1 -1
  49. package/packages/learning-engine/package.json +1 -1
  50. package/packages/logging/package.json +1 -1
  51. package/packages/mcp-adapter/package.json +1 -1
  52. package/packages/memory-engine/package.json +1 -1
  53. package/packages/milestone-manager/package.json +1 -1
  54. package/packages/model-registry/package.json +1 -1
  55. package/packages/nextjs-plugin/package.json +1 -1
  56. package/packages/notification/package.json +1 -1
  57. package/packages/observability/package.json +1 -1
  58. package/packages/okf-indexer/package.json +1 -1
  59. package/packages/paperclip-integration/package.json +1 -1
  60. package/packages/performance-optimizer/package.json +1 -1
  61. package/packages/permission-mode/package.json +1 -1
  62. package/packages/privilege-broker/package.json +1 -1
  63. package/packages/project-analyzer/package.json +1 -1
  64. package/packages/project-bootstrap/package.json +1 -1
  65. package/packages/projection-engine/package.json +1 -1
  66. package/packages/prompt-compiler/package.json +1 -1
  67. package/packages/prompt-versioning/package.json +1 -1
  68. package/packages/provider-adapter-sdk/package.json +1 -1
  69. package/packages/provider-router/package.json +1 -1
  70. package/packages/provider-selector/package.json +1 -1
  71. package/packages/providers/package.json +1 -1
  72. package/packages/qdrant-skills/package.json +1 -1
  73. package/packages/quota-manager/package.json +1 -1
  74. package/packages/rate-limiter/package.json +1 -1
  75. package/packages/react-vite-plugin/package.json +1 -1
  76. package/packages/relay/package.json +1 -1
  77. package/packages/release-manager/package.json +1 -1
  78. package/packages/requirement-compiler/package.json +1 -1
  79. package/packages/runtime/dist/runtime-api.d.ts +61 -6
  80. package/packages/runtime/dist/runtime-api.d.ts.map +1 -1
  81. package/packages/runtime/dist/runtime-api.js +83 -30
  82. package/packages/runtime/dist/runtime-api.js.map +1 -1
  83. package/packages/runtime/package.json +1 -1
  84. package/packages/runtime/src/runtime-api.ts +168 -52
  85. package/packages/runtime/test/runtime-api.test.ts +169 -0
  86. package/packages/scheduler/package.json +1 -1
  87. package/packages/scheduler-adapter/package.json +1 -1
  88. package/packages/session/package.json +1 -1
  89. package/packages/session-api/package.json +1 -1
  90. package/packages/session-export/package.json +1 -1
  91. package/packages/shared-context/package.json +1 -1
  92. package/packages/skill-mcp-client/package.json +1 -1
  93. package/packages/skill-registry/package.json +1 -1
  94. package/packages/skills/package.json +1 -1
  95. package/packages/sprint-planner/package.json +1 -1
  96. package/packages/subscription-engine/package.json +1 -1
  97. package/packages/task-compiler/package.json +1 -1
  98. package/packages/tencentdb-memory/package.json +1 -1
  99. package/packages/tencentdb-sync/package.json +1 -1
  100. package/packages/test-data-generator/package.json +1 -1
  101. package/packages/test-generator/package.json +1 -1
  102. package/packages/todo-bd-sync/package.json +1 -1
  103. package/packages/token-estimation/package.json +1 -1
  104. package/packages/token-optimizer/package.json +1 -1
  105. package/packages/tooling-preference/package.json +1 -1
  106. package/packages/trajectory/package.json +1 -1
  107. package/packages/tui/package.json +1 -1
  108. package/packages/two-phase-review/package.json +1 -1
  109. package/packages/types/package.json +1 -1
  110. package/packages/voice-api/package.json +1 -1
  111. package/packages/webmcp-tester/package.json +1 -1
  112. package/packages/wiki/package.json +1 -1
  113. package/packages/workflow-events/package.json +1 -1
  114. package/packages/workspace-scanner/package.json +1 -1
  115. package/packages/worktree/package.json +1 -1
  116. package/packages/write-review/package.json +1 -1
  117. package/scripts/typecheck-packages.mjs +1 -1
  118. package/harness/task-graph.d.ts +0 -51
  119. package/packages/notification/adapters/email-adapter.ts +0 -126
  120. package/packages/notification/adapters/ntfy-adapter.ts +0 -100
  121. package/packages/notification/adapters/telegram-adapter.ts +0 -107
  122. package/packages/notification/adapters/webhook-adapter.ts +0 -75
  123. package/packages/notification/base-adapter.ts +0 -75
  124. package/packages/notification/index.ts +0 -51
  125. package/packages/notification/notification-center.ts +0 -286
  126. package/packages/notification/types.ts +0 -89
  127. package/packages/quota-manager/index.ts +0 -28
  128. package/packages/quota-manager/quota-manager.ts +0 -339
  129. package/packages/quota-manager/tui-usage-monitor.ts +0 -355
  130. package/packages/runtime/command-executor.ts +0 -523
  131. package/packages/runtime/index.ts +0 -50
  132. package/packages/runtime/local-runtime-agent.ts +0 -480
  133. package/packages/runtime/policy-engine.ts +0 -813
  134. package/packages/runtime/runtime-api.ts +0 -665
  135. package/packages/runtime/workspace-manager.ts +0 -591
  136. package/packages/types/src/ai-providers.d.ts +0 -36
  137. package/packages/types/src/ai-providers.js +0 -66
  138. package/packages/types/src/runtime-types.d.ts +0 -418
  139. package/packages/types/src/runtime-types.js +0 -3
@@ -1,813 +0,0 @@
1
- /**
2
- * Policy Engine — RFC-0028
3
- *
4
- * Policy enforcement for command execution, network access, and file operations.
5
- *
6
- * Features:
7
- * - Command allowlist/denylist
8
- * - Network access control
9
- * - File operation restrictions
10
- * - Rate limiting
11
- * - Audit logging
12
- * - Policy inheritance and composition
13
- */
14
-
15
- import { EventEmitter } from "node:events";
16
-
17
- export type PolicyEffect = "allow" | "deny" | "ask";
18
-
19
- export interface PolicyResult {
20
- effect: PolicyEffect;
21
- reason?: string;
22
- policy?: string;
23
- score?: number;
24
- }
25
-
26
- export interface PolicyContext {
27
- /** Who/what is requesting access */
28
- subject: {
29
- user?: string;
30
- role?: string;
31
- agent?: string;
32
- ip?: string;
33
- };
34
- /** What resource is being accessed */
35
- resource: {
36
- type: "command" | "file" | "network" | "api" | "system";
37
- path?: string;
38
- url?: string;
39
- method?: string;
40
- operation?: string;
41
- };
42
- /** Environment context */
43
- environment?: {
44
- jobId?: string;
45
- taskId?: string;
46
- worktree?: string;
47
- workspace?: string;
48
- };
49
- }
50
-
51
- export interface Policy {
52
- id: string;
53
- name: string;
54
- description?: string;
55
- priority: number;
56
- effect: PolicyEffect;
57
- condition: PolicyCondition;
58
- action?: string;
59
- reason?: string;
60
- audit?: boolean;
61
- }
62
-
63
- export interface PolicyCondition {
64
- /** Match subject */
65
- subject?: {
66
- user?: string | string[];
67
- role?: string | string[];
68
- agent?: string | string[];
69
- ip?: string | string[];
70
- };
71
- /** Match resource */
72
- resource?: {
73
- type?: "command" | "file" | "network" | "api" | "system" | string;
74
- path?: string | string[];
75
- pathPattern?: string;
76
- url?: string | string[];
77
- urlPattern?: string;
78
- method?: string | string[];
79
- operation?: string | string[];
80
- };
81
- /** Match environment */
82
- environment?: {
83
- jobId?: string | string[];
84
- taskId?: string | string[];
85
- worktree?: string | string[];
86
- workspace?: string | string[];
87
- };
88
- /** Time-based conditions */
89
- time?: {
90
- startHour?: number;
91
- endHour?: number;
92
- days?: number[];
93
- };
94
- /** Custom condition function */
95
- custom?: (context: PolicyContext) => boolean;
96
- }
97
-
98
- export interface PolicyRule {
99
- pattern: RegExp;
100
- effect: PolicyEffect;
101
- reason?: string;
102
- }
103
-
104
- export interface RateLimit {
105
- maxRequests: number;
106
- windowMs: number;
107
- }
108
-
109
- export interface AuditEntry {
110
- timestamp: string;
111
- policy: string;
112
- effect: PolicyEffect;
113
- context: PolicyContext;
114
- reason?: string;
115
- }
116
-
117
- const DEFAULT_RATE_LIMIT: RateLimit = {
118
- maxRequests: 100,
119
- windowMs: 60000,
120
- };
121
-
122
- export class PolicyEngine extends EventEmitter {
123
- private policies: Policy[] = [];
124
- private commandRules: PolicyRule[] = [];
125
- private fileRules: PolicyRule[] = [];
126
- private networkRules: PolicyRule[] = [];
127
- private rateLimits: Map<string, RateLimit> = new Map();
128
- private requestCounts: Map<string, { count: number; windowStart: number }> = new Map();
129
- private auditLog: AuditEntry[] = [];
130
- private maxAuditEntries = 10000;
131
-
132
- constructor() {
133
- super();
134
- this.initializeDefaultPolicies();
135
- }
136
-
137
- /**
138
- * Initialize default security policies
139
- */
140
- private initializeDefaultPolicies(): void {
141
- // Deny dangerous commands
142
- this.addPolicy({
143
- id: "deny-rm-rf",
144
- name: "Deny rm -rf /",
145
- description: "Block rm -rf on root",
146
- priority: 100,
147
- effect: "deny",
148
- condition: {
149
- subject: {},
150
- resource: {
151
- type: "command",
152
- pathPattern: "^\\s*rm\\s+-rf\\s+\\/",
153
- },
154
- },
155
- reason: "Cannot delete root directory",
156
- });
157
-
158
- // Allow safe development commands
159
- this.addPolicy({
160
- id: "allow-git",
161
- name: "Allow git commands",
162
- description: "Allow git operations",
163
- priority: 10,
164
- effect: "allow",
165
- condition: {
166
- resource: {
167
- type: "command",
168
- pathPattern: "^\\s*git\\s+",
169
- },
170
- },
171
- });
172
-
173
- // Allow npm/npx commands
174
- this.addPolicy({
175
- id: "allow-npm",
176
- name: "Allow npm/npx",
177
- description: "Allow npm package management",
178
- priority: 10,
179
- effect: "allow",
180
- condition: {
181
- resource: {
182
- type: "command",
183
- pathPattern: "^(npm|npx|yarn|pnpm)\\s+",
184
- },
185
- },
186
- });
187
-
188
- // Deny network to private IPs
189
- this.addPolicy({
190
- id: "deny-private-network",
191
- name: "Deny private network access",
192
- description: "Block access to private IP ranges",
193
- priority: 90,
194
- effect: "deny",
195
- condition: {
196
- resource: {
197
- type: "network",
198
- urlPattern: "^(10\\.|172\\.(1[6-9]|2[0-9]|3[0-1])\\.|192\\.168\\.|127\\.|localhost)",
199
- },
200
- },
201
- reason: "Private network access is restricted",
202
- });
203
-
204
- // Allow HTTPS only
205
- this.addPolicy({
206
- id: "allow-https",
207
- name: "Allow HTTPS",
208
- description: "Allow secure HTTPS connections",
209
- priority: 20,
210
- effect: "allow",
211
- condition: {
212
- resource: {
213
- type: "network",
214
- urlPattern: "^https://",
215
- },
216
- },
217
- });
218
-
219
- // Block file access outside workspace
220
- this.addPolicy({
221
- id: "deny-outside-workspace",
222
- name: "Deny access outside workspace",
223
- description: "Block file operations outside workspace",
224
- priority: 80,
225
- effect: "deny",
226
- condition: {
227
- resource: {
228
- type: "file",
229
- pathPattern: "^\\.\\.(/|$)",
230
- },
231
- },
232
- reason: "Cannot access paths outside workspace",
233
- });
234
- }
235
-
236
- /**
237
- * Add a policy
238
- */
239
- addPolicy(policy: Policy): void {
240
- this.policies.push(policy);
241
- this.policies.sort((a, b) => b.priority - a.priority);
242
- }
243
-
244
- /**
245
- * Remove a policy
246
- */
247
- removePolicy(id: string): boolean {
248
- const index = this.policies.findIndex((p) => p.id === id);
249
- if (index !== -1) {
250
- this.policies.splice(index, 1);
251
- return true;
252
- }
253
- return false;
254
- }
255
-
256
- /**
257
- * Get all policies
258
- */
259
- getPolicies(): Policy[] {
260
- return [...this.policies];
261
- }
262
-
263
- /**
264
- * Check if a request is allowed
265
- */
266
- evaluate(context: PolicyContext): PolicyResult {
267
- // Check rate limits first
268
- const rateLimitResult = this.checkRateLimit(context);
269
- if (rateLimitResult.effect === "deny") {
270
- return rateLimitResult;
271
- }
272
-
273
- // Find matching policy
274
- for (const policy of this.policies) {
275
- if (this.matchesPolicy(context, policy)) {
276
- // Audit if enabled
277
- if (policy.audit) {
278
- this.logAudit(policy, context);
279
- }
280
-
281
- return {
282
- effect: policy.effect,
283
- reason: policy.reason,
284
- policy: policy.id,
285
- };
286
- }
287
- }
288
-
289
- // Default: ask for unknown resources
290
- return {
291
- effect: "ask",
292
- reason: "No matching policy found",
293
- };
294
- }
295
-
296
- /**
297
- * Check if a command is allowed
298
- */
299
- canExecuteCommand(command: string, context?: Partial<PolicyContext>): PolicyResult {
300
- const fullContext: PolicyContext = {
301
- subject: context?.subject ?? {},
302
- resource: {
303
- type: "command",
304
- path: command,
305
- },
306
- environment: context?.environment,
307
- };
308
-
309
- // Check against command rules first
310
- for (const rule of this.commandRules) {
311
- if (rule.pattern.test(command)) {
312
- this.logAudit(
313
- { id: "command-rule", name: "Command Rule", priority: 0, effect: rule.effect } as Policy,
314
- fullContext,
315
- );
316
-
317
- return {
318
- effect: rule.effect,
319
- reason: rule.reason,
320
- };
321
- }
322
- }
323
-
324
- return this.evaluate(fullContext);
325
- }
326
-
327
- /**
328
- * Check if a file operation is allowed
329
- */
330
- canAccessFile(
331
- path: string,
332
- operation: "read" | "write" | "delete" | "execute",
333
- context?: Partial<PolicyContext>,
334
- ): PolicyResult {
335
- const fullContext: PolicyContext = {
336
- subject: context?.subject ?? {},
337
- resource: {
338
- type: "file",
339
- path,
340
- operation,
341
- },
342
- environment: context?.environment,
343
- };
344
-
345
- // Check against file rules
346
- for (const rule of this.fileRules) {
347
- if (rule.pattern.test(path)) {
348
- this.logAudit(
349
- { id: "file-rule", name: "File Rule", priority: 0, effect: rule.effect } as Policy,
350
- fullContext,
351
- );
352
-
353
- return {
354
- effect: rule.effect,
355
- reason: rule.reason,
356
- };
357
- }
358
- }
359
-
360
- return this.evaluate(fullContext);
361
- }
362
-
363
- /**
364
- * Check if a network request is allowed
365
- */
366
- canMakeNetworkRequest(
367
- url: string,
368
- method?: string,
369
- context?: Partial<PolicyContext>,
370
- ): PolicyResult {
371
- const fullContext: PolicyContext = {
372
- subject: context?.subject ?? {},
373
- resource: {
374
- type: "network",
375
- url,
376
- method,
377
- },
378
- environment: context?.environment,
379
- };
380
-
381
- // Check against network rules
382
- for (const rule of this.networkRules) {
383
- if (rule.pattern.test(url)) {
384
- this.logAudit(
385
- { id: "network-rule", name: "Network Rule", priority: 0, effect: rule.effect } as Policy,
386
- fullContext,
387
- );
388
-
389
- return {
390
- effect: rule.effect,
391
- reason: rule.reason,
392
- };
393
- }
394
- }
395
-
396
- return this.evaluate(fullContext);
397
- }
398
-
399
- /**
400
- * Add a command rule
401
- */
402
- addCommandRule(pattern: RegExp, effect: PolicyEffect, reason?: string): void {
403
- this.commandRules.push({ pattern, effect, reason });
404
- }
405
-
406
- /**
407
- * Add a file rule
408
- */
409
- addFileRule(pattern: RegExp, effect: PolicyEffect, reason?: string): void {
410
- this.fileRules.push({ pattern, effect, reason });
411
- }
412
-
413
- /**
414
- * Add a network rule
415
- */
416
- addNetworkRule(pattern: RegExp, effect: PolicyEffect, reason?: string): void {
417
- this.networkRules.push({ pattern, effect, reason });
418
- }
419
-
420
- /**
421
- * Set rate limit for a resource
422
- */
423
- setRateLimit(resource: string, limit: RateLimit): void {
424
- this.rateLimits.set(resource, limit);
425
- }
426
-
427
- /**
428
- * Get rate limit for a resource
429
- */
430
- getRateLimit(resource: string): RateLimit | undefined {
431
- return this.rateLimits.get(resource);
432
- }
433
-
434
- /**
435
- * Get audit log
436
- */
437
- getAuditLog(filter?: {
438
- policy?: string;
439
- effect?: PolicyEffect;
440
- since?: Date;
441
- }): AuditEntry[] {
442
- let entries = [...this.auditLog];
443
-
444
- if (filter?.policy) {
445
- entries = entries.filter((e) => e.policy === filter.policy);
446
- }
447
-
448
- if (filter?.effect) {
449
- entries = entries.filter((e) => e.effect === filter.effect);
450
- }
451
-
452
- if (filter?.since) {
453
- entries = entries.filter(
454
- (e) => new Date(e.timestamp) >= filter.since!,
455
- );
456
- }
457
-
458
- return entries;
459
- }
460
-
461
- /**
462
- * Clear audit log
463
- */
464
- clearAuditLog(): void {
465
- this.auditLog = [];
466
- }
467
-
468
- /**
469
- * Export policies as JSON
470
- */
471
- exportPolicies(): string {
472
- return JSON.stringify(
473
- {
474
- policies: this.policies,
475
- commandRules: this.commandRules.map((r) => ({
476
- pattern: r.pattern.source,
477
- effect: r.effect,
478
- reason: r.reason,
479
- })),
480
- fileRules: this.fileRules.map((r) => ({
481
- pattern: r.pattern.source,
482
- effect: r.effect,
483
- reason: r.reason,
484
- })),
485
- networkRules: this.networkRules.map((r) => ({
486
- pattern: r.pattern.source,
487
- effect: r.effect,
488
- reason: r.reason,
489
- })),
490
- rateLimits: Object.fromEntries(this.rateLimits),
491
- },
492
- null,
493
- 2,
494
- );
495
- }
496
-
497
- /**
498
- * Import policies from JSON
499
- */
500
- importPolicies(json: string): boolean {
501
- try {
502
- const data = JSON.parse(json);
503
-
504
- if (data.policies) {
505
- this.policies = data.policies;
506
- }
507
-
508
- if (data.commandRules) {
509
- this.commandRules = data.commandRules.map(
510
- (r: { pattern: string; effect: PolicyEffect; reason?: string }) => ({
511
- pattern: new RegExp(r.pattern),
512
- effect: r.effect,
513
- reason: r.reason,
514
- }),
515
- );
516
- }
517
-
518
- if (data.fileRules) {
519
- this.fileRules = data.fileRules.map(
520
- (r: { pattern: string; effect: PolicyEffect; reason?: string }) => ({
521
- pattern: new RegExp(r.pattern),
522
- effect: r.effect,
523
- reason: r.reason,
524
- }),
525
- );
526
- }
527
-
528
- if (data.networkRules) {
529
- this.networkRules = data.networkRules.map(
530
- (r: { pattern: string; effect: PolicyEffect; reason?: string }) => ({
531
- pattern: new RegExp(r.pattern),
532
- effect: r.effect,
533
- reason: r.reason,
534
- }),
535
- );
536
- }
537
-
538
- if (data.rateLimits) {
539
- this.rateLimits = new Map(Object.entries(data.rateLimits));
540
- }
541
-
542
- return true;
543
- } catch {
544
- return false;
545
- }
546
- }
547
-
548
- // ─── Private Methods ────────────────────────────────────────────────
549
-
550
- private matchesPolicy(context: PolicyContext, policy: Policy): boolean {
551
- const condition = policy.condition;
552
-
553
- // Check subject
554
- if (condition.subject && !this.matchesSubject(context, condition.subject)) {
555
- return false;
556
- }
557
-
558
- // Check resource
559
- if (condition.resource && !this.matchesResource(context, condition.resource)) {
560
- return false;
561
- }
562
-
563
- // Check environment
564
- if (condition.environment && !this.matchesEnvironment(context, condition.environment)) {
565
- return false;
566
- }
567
-
568
- // Check time
569
- if (condition.time && !this.matchesTime(condition.time)) {
570
- return false;
571
- }
572
-
573
- // Check custom
574
- if (condition.custom && !condition.custom(context)) {
575
- return false;
576
- }
577
-
578
- return true;
579
- }
580
-
581
- private matchesSubject(
582
- context: PolicyContext,
583
- condition: NonNullable<PolicyCondition["subject"]>,
584
- ): boolean {
585
- const { user, role, agent, ip } = context.subject;
586
-
587
- if (user && condition.user) {
588
- if (!this.matchesValue(user, condition.user)) return false;
589
- }
590
-
591
- if (role && condition.role) {
592
- if (!this.matchesValue(role, condition.role)) return false;
593
- }
594
-
595
- if (agent && condition.agent) {
596
- if (!this.matchesValue(agent, condition.agent)) return false;
597
- }
598
-
599
- if (ip && condition.ip) {
600
- if (!this.matchesValue(ip, condition.ip)) return false;
601
- }
602
-
603
- return true;
604
- }
605
-
606
- private matchesResource(
607
- context: PolicyContext,
608
- condition: NonNullable<PolicyCondition["resource"]>,
609
- ): boolean {
610
- const { type, path, url, method, operation } = context.resource;
611
-
612
- if (type && condition.type) {
613
- if (type !== condition.type && condition.type !== "*") return false;
614
- }
615
-
616
- if (path && (condition.path || condition.pathPattern)) {
617
- if (condition.path && !this.matchesValue(path, condition.path)) return false;
618
- if (condition.pathPattern && !new RegExp(condition.pathPattern).test(path)) {
619
- return false;
620
- }
621
- }
622
-
623
- if (url && (condition.url || condition.urlPattern)) {
624
- if (condition.url && !this.matchesValue(url, condition.url)) return false;
625
- if (condition.urlPattern && !new RegExp(condition.urlPattern).test(url)) {
626
- return false;
627
- }
628
- }
629
-
630
- if (method && condition.method) {
631
- if (!this.matchesValue(method, condition.method)) return false;
632
- }
633
-
634
- if (operation && condition.operation) {
635
- if (!this.matchesValue(operation, condition.operation)) return false;
636
- }
637
-
638
- return true;
639
- }
640
-
641
- private matchesEnvironment(
642
- context: PolicyContext,
643
- condition: NonNullable<PolicyCondition["environment"]>,
644
- ): boolean {
645
- const env = context.environment ?? {};
646
-
647
- if (env.jobId && condition.jobId) {
648
- if (!this.matchesValue(env.jobId, condition.jobId)) return false;
649
- }
650
-
651
- if (env.taskId && condition.taskId) {
652
- if (!this.matchesValue(env.taskId, condition.taskId)) return false;
653
- }
654
-
655
- if (env.worktree && condition.worktree) {
656
- if (!this.matchesValue(env.worktree, condition.worktree)) return false;
657
- }
658
-
659
- if (env.workspace && condition.workspace) {
660
- if (!this.matchesValue(env.workspace, condition.workspace)) return false;
661
- }
662
-
663
- return true;
664
- }
665
-
666
- private matchesTime(condition: NonNullable<PolicyCondition["time"]>): boolean {
667
- const now = new Date();
668
-
669
- if (condition.startHour !== undefined && condition.endHour !== undefined) {
670
- const hour = now.getHours();
671
- if (condition.startHour <= condition.endHour) {
672
- if (hour < condition.startHour || hour > condition.endHour) return false;
673
- } else {
674
- // Handles overnight ranges like 22:00 - 06:00
675
- if (hour < condition.startHour && hour > condition.endHour) return false;
676
- }
677
- }
678
-
679
- if (condition.days && condition.days.length > 0) {
680
- const day = now.getDay();
681
- if (!condition.days.includes(day)) return false;
682
- }
683
-
684
- return true;
685
- }
686
-
687
- private matchesValue(value: string, pattern: string | string[]): boolean {
688
- if (Array.isArray(pattern)) {
689
- return pattern.includes(value);
690
- }
691
-
692
- // Check if pattern is a regex
693
- if (pattern.startsWith("/") && pattern.endsWith("/")) {
694
- try {
695
- return new RegExp(pattern.slice(1, -1)).test(value);
696
- } catch {
697
- return false;
698
- }
699
- }
700
-
701
- // Glob pattern
702
- if (pattern.includes("*")) {
703
- const regex = new RegExp(
704
- "^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
705
- );
706
- return regex.test(value);
707
- }
708
-
709
- return value === pattern;
710
- }
711
-
712
- private checkRateLimit(context: PolicyContext): PolicyResult {
713
- const key = this.getRateLimitKey(context);
714
- const limit = this.rateLimits.get(key) ?? DEFAULT_RATE_LIMIT;
715
-
716
- const now = Date.now();
717
- let record = this.requestCounts.get(key);
718
-
719
- if (!record || now - record.windowStart > limit.windowMs) {
720
- // Start new window
721
- record = { count: 1, windowStart: now };
722
- this.requestCounts.set(key, record);
723
- return { effect: "allow" };
724
- }
725
-
726
- record.count++;
727
- this.requestCounts.set(key, record);
728
-
729
- if (record.count > limit.maxRequests) {
730
- return {
731
- effect: "deny",
732
- reason: `Rate limit exceeded: ${record.count}/${limit.maxRequests} requests in ${limit.windowMs}ms`,
733
- };
734
- }
735
-
736
- return { effect: "allow" };
737
- }
738
-
739
- private getRateLimitKey(context: PolicyContext): string {
740
- const parts: string[] = [context.resource.type];
741
-
742
- if (context.subject.user) parts.push(context.subject.user);
743
- if (context.subject.agent) parts.push(context.subject.agent);
744
- if (context.resource.path) parts.push(context.resource.path);
745
- if (context.resource.url) parts.push(context.resource.url);
746
-
747
- return parts.join(":");
748
- }
749
-
750
- private logAudit(policy: Policy, context: PolicyContext): void {
751
- const entry: AuditEntry = {
752
- timestamp: new Date().toISOString(),
753
- policy: policy.id,
754
- effect: policy.effect,
755
- context,
756
- reason: policy.reason,
757
- };
758
-
759
- this.auditLog.push(entry);
760
-
761
- // Trim log if too large
762
- if (this.auditLog.length > this.maxAuditEntries) {
763
- this.auditLog = this.auditLog.slice(-this.maxAuditEntries / 2);
764
- }
765
-
766
- this.emit("audit", entry);
767
- }
768
- }
769
-
770
- /**
771
- * Create a PolicyEngine with default harness policies
772
- */
773
- export function createHarnessPolicyEngine(): PolicyEngine {
774
- const engine = new PolicyEngine();
775
-
776
- // Add additional harness-specific policies
777
- engine.addPolicy({
778
- id: "allow-harness-internal",
779
- name: "Allow harness internal commands",
780
- description: "Allow internal harness commands",
781
- priority: 15,
782
- effect: "allow",
783
- condition: {
784
- resource: {
785
- type: "command",
786
- pathPattern: "^(pi|harness)-",
787
- },
788
- },
789
- });
790
-
791
- // Default deny for system commands
792
- engine.addPolicy({
793
- id: "deny-system",
794
- name: "Deny system commands",
795
- description: "Block system-level commands by default",
796
- priority: 70,
797
- effect: "deny",
798
- condition: {
799
- resource: {
800
- type: "command",
801
- pathPattern: "^(sudo|su|chmod|chown|mount|umount|reboot|shutdown)\\s",
802
- },
803
- },
804
- reason: "System commands are restricted",
805
- });
806
-
807
- // Set rate limits
808
- engine.setRateLimit("command", { maxRequests: 60, windowMs: 60000 }); // 60 commands/minute
809
- engine.setRateLimit("network", { maxRequests: 100, windowMs: 60000 }); // 100 requests/minute
810
- engine.setRateLimit("file", { maxRequests: 200, windowMs: 60000 }); // 200 file ops/minute
811
-
812
- return engine;
813
- }