108 lines
2.9 KiB
Rego
108 lines
2.9 KiB
Rego
# METADATA
|
|
# scope: package
|
|
# title: Git Block No-Verify - Builtin Policy
|
|
# authors: ["Cupcake Builtins"]
|
|
# custom:
|
|
# severity: HIGH
|
|
# id: BUILTIN-GIT-BLOCK-NO-VERIFY
|
|
# routing:
|
|
# required_events: ["PreToolUse"]
|
|
# required_tools: ["Bash"]
|
|
package cupcake.policies.builtins.git_block_no_verify
|
|
|
|
import rego.v1
|
|
|
|
import data.cupcake.helpers.commands
|
|
|
|
# Block git commands that bypass verification hooks
|
|
deny contains decision if {
|
|
input.hook_event_name == "PreToolUse"
|
|
input.tool_name == "Bash"
|
|
|
|
# Get the command from tool input
|
|
command := lower(input.tool_input.command)
|
|
|
|
# Check if it's a git command with --no-verify flag
|
|
contains_git_no_verify(command)
|
|
|
|
decision := {
|
|
"rule_id": "BUILTIN-GIT-BLOCK-NO-VERIFY",
|
|
"reason": "Git operations with --no-verify are not permitted. Commit hooks must run for code quality and security checks.",
|
|
"severity": "HIGH",
|
|
}
|
|
}
|
|
|
|
# Check if command contains git with --no-verify flag
|
|
# Uses helper library to prevent spacing bypass (TOB-EQTY-LAB-CUPCAKE-3)
|
|
contains_git_no_verify(cmd) if {
|
|
# Check for git commit with --no-verify
|
|
commands.has_verb(cmd, "git")
|
|
commands.has_verb(cmd, "commit")
|
|
contains(cmd, "--no-verify")
|
|
}
|
|
|
|
contains_git_no_verify(cmd) if {
|
|
# Check for git commit with -n (shorthand for --no-verify)
|
|
commands.has_verb(cmd, "git")
|
|
commands.has_verb(cmd, "commit")
|
|
regex.match(`\s-[a-z]*n[a-z]*\s`, concat(" ", [cmd, " "])) # Matches -n, -an, -nm, etc.
|
|
}
|
|
|
|
contains_git_no_verify(cmd) if {
|
|
# Check for git push with --no-verify
|
|
commands.has_verb(cmd, "git")
|
|
commands.has_verb(cmd, "push")
|
|
contains(cmd, "--no-verify")
|
|
}
|
|
|
|
contains_git_no_verify(cmd) if {
|
|
# Check for git merge with --no-verify
|
|
commands.has_verb(cmd, "git")
|
|
commands.has_verb(cmd, "merge")
|
|
contains(cmd, "--no-verify")
|
|
}
|
|
|
|
# Also block attempts to disable hooks via config
|
|
deny contains decision if {
|
|
input.hook_event_name == "PreToolUse"
|
|
input.tool_name == "Bash"
|
|
|
|
command := lower(input.tool_input.command)
|
|
|
|
# Check if trying to disable hooks via git config
|
|
contains_hook_disable(command)
|
|
|
|
decision := {
|
|
"rule_id": "BUILTIN-GIT-BLOCK-NO-VERIFY",
|
|
"reason": "Disabling git hooks is not permitted. Hooks are required for code quality and security.",
|
|
"severity": "HIGH",
|
|
}
|
|
}
|
|
|
|
contains_hook_disable(cmd) if {
|
|
commands.has_verb(cmd, "git")
|
|
commands.has_verb(cmd, "config")
|
|
contains(cmd, "core.hooksPath")
|
|
contains(cmd, "/dev/null")
|
|
}
|
|
|
|
contains_hook_disable(cmd) if {
|
|
# Detect attempts to chmod hooks to non-executable
|
|
commands.has_verb(cmd, "chmod")
|
|
regex.match(`\.git/hooks`, cmd)
|
|
regex.match(`-x|-[0-9]*0[0-9]*`, cmd) # Removing execute permission
|
|
}
|
|
|
|
contains_hook_disable(cmd) if {
|
|
# Detect attempts to remove hook files
|
|
contains(cmd, ".git/hooks")
|
|
removal_cmds := {"rm", "unlink", "trash"}
|
|
commands.has_dangerous_verb(cmd, removal_cmds)
|
|
}
|
|
|
|
contains_hook_disable(cmd) if {
|
|
# Detect moving/renaming hooks to disable them
|
|
commands.has_verb(cmd, "mv")
|
|
contains(cmd, ".git/hooks")
|
|
}
|