Created
July 2, 2026 20:50
-
-
Save kerski/ce77b44d852c722e16049e60a223a536 to your computer and use it in GitHub Desktop.
powerbi-command-guard.js - Example of Hook Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| // Debug flag - set to false to disable logging | |
| const DEBUG = true; | |
| // Log file for debugging | |
| const logFile = path.join(__dirname, 'hook-debug.log'); | |
| function log(message) { | |
| if (!DEBUG) return; | |
| const timestamp = new Date().toISOString(); | |
| fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`); | |
| } | |
| log('Hook script started'); | |
| // Read stdin | |
| let input = ''; | |
| process.stdin.on('data', chunk => input += chunk); | |
| process.stdin.on('end', () => { | |
| try { | |
| log(`Received input: ${input.substring(0, 200)}...`); | |
| const payload = JSON.parse(input); | |
| log(`Tool name: ${payload.tool_name}`); | |
| // Only intercept run_in_terminal tool | |
| if (payload.tool_name !== 'run_in_terminal') { | |
| process.stdout.write(JSON.stringify({ continue: true })); | |
| process.exit(0); | |
| } | |
| const originalCommand = payload.tool_input?.command || ''; | |
| let modifiedCommand = originalCommand; | |
| // Pattern 1: Block global npm installs and tell user to install locally | |
| const globalInstallPattern = /npm\s+install\s+-g\s+(@microsoft\/powerbi-(?:report-authoring-cli|desktop-bridge-cli)(?:\s+@microsoft\/powerbi-(?:report-authoring-cli|desktop-bridge-cli))?)/; | |
| const match = modifiedCommand.match(globalInstallPattern); | |
| if (match) { | |
| const packages = match[1]; | |
| // Check if package.json exists in the current directory | |
| const packageJsonPath = path.join(process.cwd(), 'package.json'); | |
| const hasPackageJson = fs.existsSync(packageJsonPath); | |
| let localCommand; | |
| if (!hasPackageJson) { | |
| // Initialize npm first, then install | |
| localCommand = `npm init -y; npm install --save-dev ${packages}`; | |
| // Check if .gitignore exists and contains node_modules | |
| const gitignorePath = path.join(process.cwd(), '.gitignore'); | |
| if (fs.existsSync(gitignorePath)) { | |
| const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8'); | |
| if (!gitignoreContent.includes('node_modules')) { | |
| fs.appendFileSync(gitignorePath, '\nnode_modules\n'); | |
| log('Added node_modules to .gitignore'); | |
| } | |
| } else { | |
| fs.writeFileSync(gitignorePath, 'node_modules\n'); | |
| log('Created .gitignore with node_modules'); | |
| } | |
| } else { | |
| localCommand = `npm install --save-dev ${packages}`; | |
| } | |
| const response = { | |
| hookSpecificOutput: { | |
| permissionDecision: 'deny', | |
| permissionDecisionReason: `Global install blocked by project policy. Please run: ${localCommand}` | |
| } | |
| }; | |
| log(`Blocked global install. Suggested: ${localCommand}`); | |
| process.stdout.write(JSON.stringify(response)); | |
| process.exit(0); | |
| } | |
| // Pattern 2: Require npx with full scoped package names (since global installs are blocked) | |
| if (!modifiedCommand.startsWith('npx ')) { | |
| const cliMappings = { | |
| 'powerbi-desktop': '@microsoft/powerbi-desktop-bridge-cli', | |
| 'powerbi-report-author': '@microsoft/powerbi-report-authoring-cli' | |
| }; | |
| for (const [cliCommand, packageName] of Object.entries(cliMappings)) { | |
| const match = modifiedCommand.match(new RegExp(`^${cliCommand}(\\s+.+)?$`)); | |
| if (match) { | |
| const args = match[1] || ''; | |
| const packageJsonPath = path.join(process.cwd(), 'package.json'); | |
| const hasPackageJson = fs.existsSync(packageJsonPath); | |
| let suggestion; | |
| let isInstalled = false; | |
| // Check if package is already installed in node_modules or package.json | |
| if (hasPackageJson) { | |
| try { | |
| const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); | |
| const devDeps = packageJson.devDependencies || {}; | |
| const deps = packageJson.dependencies || {}; | |
| isInstalled = devDeps[packageName] || deps[packageName]; | |
| } catch (e) { | |
| log(`Error reading package.json: ${e.message}`); | |
| } | |
| } | |
| if (isInstalled) { | |
| // Package is installed - use short form which uses local cache | |
| suggestion = `npx ${cliCommand}${args}`; | |
| } else if (hasPackageJson) { | |
| // package.json exists but package not installed - install then use short form | |
| suggestion = `npm install --save-dev ${packageName}; npx ${cliCommand}${args}`; | |
| } else { | |
| // No package.json - full setup | |
| suggestion = `npm init -y; npm install --save-dev ${packageName}; npx ${cliCommand}${args}`; | |
| } | |
| const response = { | |
| hookSpecificOutput: { | |
| permissionDecision: 'deny', | |
| permissionDecisionReason: `PowerBI CLI commands must use npx. Please run: ${suggestion}` | |
| } | |
| }; | |
| log(`Blocked direct CLI call. Suggested: ${suggestion}`); | |
| process.stdout.write(JSON.stringify(response)); | |
| process.exit(0); | |
| } | |
| } | |
| } | |
| // Allow all other commands | |
| log('No issues found, allowing through'); | |
| process.stdout.write(JSON.stringify({ continue: true })); | |
| process.exit(0); | |
| } catch (error) { | |
| // On error, allow the operation but log the error | |
| log(`Error: ${error.message}`); | |
| console.error('Hook error:', error.message); | |
| process.stdout.write(JSON.stringify({ continue: true })); | |
| process.exit(0); | |
| } | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment