Skip to content

Commit e7f9d13

Browse files
committed
Add script shebang validation across script hooks
1 parent 3b963a0 commit e7f9d13

6 files changed

Lines changed: 90 additions & 32 deletions

File tree

pre_commit_hooks/check_jamf_extension_attributes.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import argparse
66
import re
77

8+
from util import validate_shebangs
9+
810

911
def build_argument_parser():
1012
"""Build and return the argument parser."""
@@ -13,6 +15,12 @@ def build_argument_parser():
1315
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
1416
)
1517
parser.add_argument("filenames", nargs="*", help="Filenames to check.")
18+
parser.add_argument(
19+
"--valid-shebangs",
20+
nargs="+",
21+
default=[],
22+
help="Add other valid shebangs for your environment",
23+
)
1624
return parser
1725

1826

@@ -28,13 +36,22 @@ def main(argv=None):
2836
with open(filename, "r", encoding="utf-8") as openfile:
2937
ea_content = openfile.read()
3038

39+
# Ensure script contains both <result> and </result> tags
3140
if "<result>" not in ea_content or "</result>" not in ea_content:
3241
print(f"{filename}: missing <result> and/or </result> tags")
3342
retval = 1
34-
all_results = len(re.findall("result.*\/result", ea_content))
35-
proper_results = len(re.findall("<result>.*<\/result>", ea_content))
43+
44+
# Ensure result tags are in proper order (open then close)
45+
all_results = len(re.findall(r"result.*\/result", ea_content))
46+
proper_results = len(re.findall(r"<result>.*<\/result>", ea_content))
3647
if proper_results < all_results:
37-
print(f"{filename}: has incomplete <result> tags!")
48+
print(f"{filename}: has incomplete <result> tags")
49+
retval = 1
50+
51+
# Ensure all pkginfo scripts have a proper shebang.
52+
if not validate_shebangs(ea_content, filename, args.valid_shebangs):
53+
print(f"{filename}: does not start with a valid shebang")
54+
retval = 1
3855

3956
return retval
4057

pre_commit_hooks/check_jamf_scripts.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import argparse
66

7+
from util import validate_shebangs
8+
79

810
def build_argument_parser():
911
"""Build and return the argument parser."""
@@ -12,6 +14,12 @@ def build_argument_parser():
1214
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
1315
)
1416
parser.add_argument("filenames", nargs="*", help="Filenames to check.")
17+
parser.add_argument(
18+
"--valid-shebangs",
19+
nargs="+",
20+
default=[],
21+
help="Add other valid shebangs for your environment",
22+
)
1523
return parser
1624

1725

@@ -29,16 +37,17 @@ def main(argv=None):
2937

3038
# Ensure script starts with a shebang of some sort.
3139
if not script_content.startswith("#!/"):
32-
print("{}: missing shebang".format(filename))
40+
print(f"{filename}: missing shebang")
3341
retval = 1
3442

3543
# Ensure we're not using env for root-context scripts.
3644
if script_content.startswith("#!/usr/bin/env"):
37-
print(
38-
"{}: using env for root-context scripts is not recommended".format(
39-
filename
40-
)
41-
)
45+
print(f"{filename}: using env for root-context scripts is not recommended")
46+
retval = 1
47+
48+
# Ensure all pkginfo scripts have a proper shebang.
49+
if not validate_shebangs(script_content, filename, args.valid_shebangs):
50+
print(f"{filename}: does not start with a valid shebang")
4251
retval = 1
4352

4453
return retval

pre_commit_hooks/check_munki_pkgsinfo.py

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from pathlib import Path
99
from xml.parsers.expat import ExpatError
1010

11+
from util import validate_shebangs
12+
1113
from pre_commit_hooks.util import (
1214
validate_pkginfo_key_types,
1315
validate_required_keys,
@@ -216,19 +218,6 @@ def main(argv=None):
216218
retval = 1
217219

218220
# Ensure all pkginfo scripts have a proper shebang.
219-
builtin_shebangs = [
220-
"#!/bin/bash",
221-
"#!/bin/sh",
222-
"#!/bin/zsh",
223-
"#!/usr/bin/osascript",
224-
"#!/usr/bin/perl",
225-
"#!/usr/bin/python3",
226-
"#!/usr/bin/python",
227-
"#!/usr/bin/ruby",
228-
"#!/usr/local/munki/munki-python",
229-
"#!/usr/local/munki/Python.framework/Versions/Current/bin/python3",
230-
]
231-
shebangs = builtin_shebangs + args.valid_shebangs
232221
script_types = (
233222
"installcheck_script",
234223
"uninstallcheck_script",
@@ -238,14 +227,12 @@ def main(argv=None):
238227
"preuninstall_script",
239228
"uninstall_script",
240229
)
241-
for script_type in script_types:
242-
if script_type in pkginfo:
243-
if all(not pkginfo[script_type].startswith(x + "\n") for x in shebangs):
244-
print(
245-
"{}: Has a {} that does not start with a valid shebang.".format(
246-
filename, script_type
247-
)
248-
)
230+
for s_type in script_types:
231+
if s_type in pkginfo:
232+
if not validate_shebangs(
233+
pkginfo[s_type], filename, args.valid_shebangs
234+
):
235+
print(f"{filename}: {s_type} does not start with a valid shebang")
249236
retval = 1
250237

251238
# Ensure the items_to_copy list does not include trailing slashes.

pre_commit_hooks/check_munkiadmin_scripts.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import argparse
66
import os
77

8+
from util import validate_shebangs
9+
810

911
def build_argument_parser():
1012
"""Build and return the argument parser."""
@@ -25,8 +27,17 @@ def main(argv=None):
2527

2628
retval = 0
2729
for filename in args.filenames:
30+
31+
# Ensure scripts are executable
2832
if not os.access(filename, os.X_OK):
29-
print("{}: not executable".format(filename))
33+
print(f"{filename}: not executable")
34+
retval = 1
35+
36+
# Ensure scripts have a proper shebang
37+
with open(filename, "r", encoding="utf-8") as openfile:
38+
script_content = openfile.read()
39+
if not validate_shebangs(script_content, filename):
40+
print(f"{filename}: does not start with a valid shebang")
3041
retval = 1
3142

3243
return retval

pre_commit_hooks/check_outset_scripts.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import argparse
66
import os
77

8+
from util import validate_shebangs
9+
810

911
def build_argument_parser():
1012
"""Build and return the argument parser."""
@@ -26,7 +28,14 @@ def main(argv=None):
2628
retval = 0
2729
for filename in args.filenames:
2830
if not os.access(filename, os.X_OK):
29-
print("{}: not executable".format(filename))
31+
print(f"{filename}: not executable")
32+
retval = 1
33+
34+
# Ensure scripts have a proper shebang
35+
with open(filename, "r", encoding="utf-8") as openfile:
36+
script_content = openfile.read()
37+
if not validate_shebangs(script_content, filename):
38+
print(f"{filename}: does not start with a valid shebang")
3039
retval = 1
3140

3241
return retval

pre_commit_hooks/util.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@
2424
"date": datetime,
2525
}
2626

27+
# List of common shebangs used by Mac admin scripts
28+
# (Can be augmented with --valid-shebangs parameter)
29+
BUILTIN_SHEBANGS = [
30+
"#!/bin/bash",
31+
"#!/bin/sh",
32+
"#!/bin/zsh",
33+
"#!/usr/bin/osascript",
34+
"#!/usr/bin/perl",
35+
"#!/usr/bin/python3",
36+
"#!/usr/bin/python", # removed since macOS 12.3
37+
"#!/usr/bin/ruby",
38+
"#!/usr/local/munki/munki-python",
39+
"#!/usr/local/munki/Python.framework/Versions/Current/bin/python3",
40+
]
41+
2742

2843
def load_autopkg_recipe(path):
2944
"""Loads an AutoPkg recipe in plist, yaml, or json format."""
@@ -169,3 +184,13 @@ def validate_pkginfo_key_types(pkginfo, filename):
169184
passed = False
170185

171186
return passed
187+
188+
189+
def validate_shebangs(script_content, filename, addl_shebangs=[]):
190+
"""Verifies that scripts begin with a valid shebang."""
191+
passed = True
192+
shebangs = BUILTIN_SHEBANGS + addl_shebangs
193+
if not any((script_content.startswith(x) + "\n" for x in shebangs)):
194+
print(f"{filename}: does not start with a valid shebang")
195+
passed = False
196+
return passed

0 commit comments

Comments
 (0)