Skip to content

Instantly share code, notes, and snippets.

@kerski
Created July 2, 2026 20:50
Show Gist options
  • Select an option

  • Save kerski/ce77b44d852c722e16049e60a223a536 to your computer and use it in GitHub Desktop.

Select an option

Save kerski/ce77b44d852c722e16049e60a223a536 to your computer and use it in GitHub Desktop.
powerbi-command-guard.js - Example of Hook Script
#!/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