File size: 3,529 Bytes
7a1ad33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import * as fs from 'node:fs';
import * as path from 'node:path';
import { Storage } from '../config/storage.js';
import {
  getHookKey,
  HookType,
  type HookDefinition,
  type HookEventName,
} from './types.js';
import { debugLogger } from '../utils/debugLogger.js';

interface TrustedHooksConfig {
  [projectPath: string]: string[]; // Array of trusted hook keys (name:command)
}

export class TrustedHooksManager {
  private configPath: string;
  private trustedHooks: TrustedHooksConfig = {};

  constructor() {
    this.configPath = path.join(
      Storage.getGlobalGeminiDir(),
      'trusted_hooks.json',
    );
    this.load();
  }

  private load(): void {
    try {
      if (fs.existsSync(this.configPath)) {
        const content = fs.readFileSync(this.configPath, 'utf-8');
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
        this.trustedHooks = JSON.parse(content);
      }
    } catch (error) {
      debugLogger.warn('Failed to load trusted hooks config', error);
      this.trustedHooks = {};
    }
  }

  private save(): void {
    try {
      const dir = path.dirname(this.configPath);
      if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
      }
      fs.writeFileSync(
        this.configPath,
        JSON.stringify(this.trustedHooks, null, 2),
      );
    } catch (error) {
      debugLogger.warn('Failed to save trusted hooks config', error);
    }
  }

  /**
   * Get untrusted hooks for a project
   * @param projectPath Absolute path to the project root
   * @param hooks The hooks configuration to check
   * @returns List of untrusted hook commands/names
   */
  getUntrustedHooks(
    projectPath: string,
    hooks: { [K in HookEventName]?: HookDefinition[] },
  ): string[] {
    const trustedKeys = new Set(this.trustedHooks[projectPath] || []);
    const untrusted: string[] = [];

    for (const eventName of Object.keys(hooks)) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
      const definitions = hooks[eventName as HookEventName];
      if (!Array.isArray(definitions)) continue;

      for (const def of definitions) {
        if (!def || !Array.isArray(def.hooks)) continue;
        for (const hook of def.hooks) {
          if (hook.type === HookType.Runtime) continue;
          const key = getHookKey(hook);
          if (!trustedKeys.has(key)) {
            // Return friendly name or command
            untrusted.push(hook.name || hook.command || 'unknown-hook');
          }
        }
      }
    }

    return Array.from(new Set(untrusted)); // Deduplicate
  }

  /**
   * Trust all provided hooks for a project
   */
  trustHooks(
    projectPath: string,
    hooks: { [K in HookEventName]?: HookDefinition[] },
  ): void {
    const currentTrusted = new Set(this.trustedHooks[projectPath] || []);

    for (const eventName of Object.keys(hooks)) {
      // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
      const definitions = hooks[eventName as HookEventName];
      if (!Array.isArray(definitions)) continue;

      for (const def of definitions) {
        if (!def || !Array.isArray(def.hooks)) continue;
        for (const hook of def.hooks) {
          if (hook.type === HookType.Runtime) continue;
          currentTrusted.add(getHookKey(hook));
        }
      }
    }

    this.trustedHooks[projectPath] = Array.from(currentTrusted);
    this.save();
  }
}