Application Playpen

Application Playpen


We have developed a methodology of managing enterprise and/or shared environment unfriendly applications that has evolved over the years at the Marriott Library or other locations on campus that we support. We affectionately call it the  “Crappy App” model which we use sanitize applications with exceptional requirements.

These requirements include:

  • Application has insecure permissions
  • Application has hardcoded variables (usernames, paths, etc.)
  • Application tries to update with every launch
  • Application requires access to locations we’d prefer they not have access
  • Application requires to be launched by a specific user or group
  • Application requires additional configuration pre or post launch

Originally, we developed this methodology to better secure the Classic environment running in our shared environments. Classic implemented Mac OS 9 running as an application within earlier versions of Mac OS X, allowing you to use your older “Classic” applications. The Classic environment had security & state issues and some Classic applications may not function properly from a System Folder that can’t be written, provides a good security measure to prevent startup from Mac OS 9 and ensures that each user starts with a pristine System Folder. To resolve the issues but not hinder the needed functionality we developed a workaround that included placing the Classic System Folder in a read-only disk image, then hack the “Classic Startup” application to mount the image as read-write with a shadow file saved during the Mac OS X user session.

For example, the following command would mount the Classic image with a shadow file.

hdid /Classic.dmg -shadow ∼/Library/Management

This file can then be mounted, and used, but will not save any information once the user is logged out. Any application can take advantage of this method, allowing for more secure and easier to cleanup environments.

For example, the AppleScripts below will run VirtualPC with a ’shadow’ file…

do shell script "/usr/bin/hdid ’ /Applications/Virtual PC 6.0.1/Windows 98 SE.dmg’ -shadow VPCShadow’"
delay 5
tell application "Finder"
	open file "Mac OS X:Applications:Virtual PC 6.0.1"
end tell

Mike Bombich (aka Carbon Copy Cloner fame) developed a GUI application version that helped simplify and automate the steps into his “Shadow Classic” application. This application implements a shell script that will mount your read-only Classic disk image with a shadow file so that it appears to users and applications as read-write.  This allows users and applications to make changes to the System Folder or applications on the disk image at will.  When the next user logs in and starts Classic, the disk image is mounted again with a fresh shadow file, having none of the changes imposed by the last user.

For example, this is shell script included with the ShadowClassic application…


## currentSettings;%classic%;%classicName%;%shadow%;

if [ -e "%classic%" ]; then
	if [ ! -d "/Volumes/%classicName%" ]; then
		/bin/rm -f %shadow%
		/usr/bin/hdid "%classic%" -shadow %shadow%
	#/usr/bin/osascript -e "launch application \"Classic Startup\""
	/System/Library/CoreServices/Classic\\ Classic\ Startup

ShadowClassic implements the equivalent commands…

cd /System/Library/CoreServices/Classic\
sudo mv Classic\ Startup Real\ Classic\ Startup
sudo ditto ~/ ./Classic\ Startup

Then it replaces the Classic Startup executable with a shell script that mounts your Classic image with a shadow file and launches the “Real Classic Startup” application.

Why Distribute Crappy Apps?
This methodology has been extended and implemented for multiple “crappy applications”. Since we are a higher education institution, in a large decentralized research university, our department is in a campus central location, and the campus main library; we tried to meet the computing and software needs for campus which is often defined by multiple departments, colleges and groups with different and varying needs and goals.  With that in mind, we have try to address these needs by sanitizing software that other environments like corporations could simply say NO to distributing and supporting in their fleet. But, as in our environment we try to make a best effort in sanitizing the “crappy app” to safely distribute.

We would strongly recommend to document and give the “crappy app” developer feedback on why their application is enterprise unfriendly and that unless its changed you will transition to another application to provide needed functionality and try to get others in the Mac Admins community to do the same (i.e., carrot). In the past, we maintained a “crappy app” shame page, but we decided that this wasn’t a priority for our group or wasn’t the most effective strategy in motivating a company to change their development methodologies and practices (i.e., stick).

Carrot or Stick | Pace

Mac’s Are Secure/Resilient?
Other arguments against taking a little extra time and effort to sanitize needed “crappy apps” is that Mac are secure and one bad application won’t break the entire Apple system. But, with presentations from Apple knowledgeable persons like Patrick Wardle, who recently presented at MacTECH about the “Protecting the Garden of Eden“, Mac systems are being more and more targeted and are having increasing Common Vulnerabilities and Exposures (CVE) or publicly known cybersecurity vulnerabilities. And since Apple has implemented System Integrity Protection (SIP) in OS X El Capitan, which comprises a number of mechanisms that are enforced by the kernel including protection of system-owned files and directories against modifications. And with newer macOS versions Apple has increased scope of these mechanisms to improve privacy, security, resilience and stability of the system. Seems we should try to follow, and try to implement the principle of least privilege to justify taking a little extra effort to sanitizing “crappy applications” in our environment.

Related image

Principle of Least Privilege

The principle of least privilege requires that in a particular abstraction layer of a computing environment, every module like a process, a user, or a program must be able to access only the information and resources that are necessary for its legitimate purpose and are essential to perform its intended function. This principle is widely recognized as an important design consideration in enhancing the protection of data and functionality from faults (fault tolerance) and malicious behavior (system security).

Benefits of the principle include:

  • Condensed Attack Surface
    Limiting privileges for people, processes, and applications means the pathways and ingresses for exploit are also diminished.
  • Reduced Malware Infection and Propagation
    Least privilege helps dramatically reduce malware infection and propagation, as the malware should be denied the ability to elevate processes that allow it to install or execute.
  • Improved Operational Performance
    When it comes to applications and systems, restricting privileges to the minimal range of processes to perform an authorized activity reduces the chance of incompatibility issues cropping up between other applications or systems, and helps reduce the risk of downtime.
  • Easier to Achieve and Prove Compliance
    By constraining the activities that can possibly be performed, least privilege enforcement helps create a less complex, and thus, a more audit-friendly, environment.

The following Apple Developer documentation on “Elevating Privileges Safely” covers the basics of the Principle of Least Privilege.


The basic process for creating a Application Playpen launcher for a “crappy application” is as follows:
  1. Start with the Application Playpen launcher template
  2. Create the read-only image of the “crappy application”
  3. Place the image into the Application Playpen launcher application
  4. Update the Application Playpen launcher main.scpt AppleScript file
  5. Update the Application Playpen launcher icons files to match “crappy application”
  6. Update the Application Playpen launcher Info.plist to match “crappy application”
  7. Test, test and test again

Currently, we use the following AppleScript template to build the Application Playpen launcher.

Application Playpen Launcher Template
All of the logic for launching and managing configuration is handled in the /Contents/Resources/Scripts/main.scpt file.

	This script is automatically generated. It's used to wrap applications that behave poorly.
	Some applications like to force updates or require world read/write permissions, which we
	don't generally like in our sort of environment. This allows us to keep everything locked
	up carefully.
	COPYRIGHT (c) 2015 University of Utah, Marriott Library, ITS.  All rights reserved.
	Author: Pierce Darragh -
	Revised: Sam Forester -
	Creation Date:	May 7, 2015
	Last Updated: July 8, 2016

	Permission to use, copy, modify, and distribute this software and its documentation for any
	purpose and without fee is hereby granted, provided that the above copyright notice appears in
	all copies and that both that copyright notice and this permission notice appear in supporting
	documentation, and that the name of The Marriott Library not be used in advertising or
	publicity pertaining to distribution of the software without specific, written prior permission.
	This software is supplied as-is without expressed or implied warranties of any kind.

global myPath
global appName
global diskName
global appPath
global imagePath
global shadowPath
global mountRoot
global homeFolder
global shadowFile
global shadowFolder

on run
	-- Set all of the locations for things.
	-- Most likely, you'll only ever need to change the 'appName'. If you give everything else
	-- similar names, it'll make your life easier.
	-- appName:		the name of the application
	-- diskName:		the name of the disk
	-- appPath:		the path to the application in the image
	-- imagePath:		the path to the image in this bundle
	-- homeFolder:	the path of the current user's home folder
	-- shadowFolder:	the directory in which the shadow file will be saved
	-- shadowFile:	the full path to the shadow file
	-- mountRoot:	the location to mount the volume under (change for obnoxious applications)
	set myPath to (path to me as Unicode text)
	set appName to "<TEMPLATE>"
	set diskName to appName
	set appPath to diskName & ":Applications:" & appName & ".app"
	set imagePath to POSIX path of (myPath & "Contents:Resources:" & appName & ".dmg") as Unicode text
	set homeFolder to (get path to home folder) as Unicode text
	set shadowFolder to (homeFolder & "Library:Application Support:" & appName)
	set shadowFile to POSIX path of (shadowFolder & ":" & appName & ".shadow")
	set mountRoot to "/Volumes"
	my main()
end run

on main()
	-- If the application is running, quit.
	-- Otherwise, launch it!
	if (isRunning()) then
		display dialog (appName & " is already running!") buttons {"Thanks for the reminder!"} default button 1 giving up after 5 with icon caution
		launch {}
	end if
end main

-- Launches the application.
on launch {}
	-- Inform the user that this might take a moment (mounting a disk image)
	-- uncomment this if you want to have a dialog appear while launching the wrapped application
	#	display dialog "This application may take a while to load.
	#Remember that none of your data will be saved once you log off.  Back up anything you need before you leave!
	#-- Mac Support Group
	#Marriott Library IT Services" buttons {"Okay"} default button 1 with icon path to resource (appName & ".icns") in bundle (path to me)
	-- If the disk is not mounted, mount it.
	-- If after this the path to the executable exists, launch it.
	tell application "Finder"
		-- create the intermediate directories where we will be keeping the shadowfile
		if not (exists folder shadowFolder) then
			-- I'm sure there is a better way to do with with actual AppleScript, but I was unable to find out how and mkdir works just fine
			set dir to POSIX path of shadowFolder
			do shell script ("mkdir -p " & quoted form of dir)
		end if
		if not (disk diskName exists) then
			-- Mount the image at imagePath with:
			--  * -nobrowse			Prevents the volume from appearing in Finder or on the Desktop.
			--  * -noautoopenro		Makes sure we won't accidentally pop open a Finder window into the volume.
			--  * -noverify				Ensures that hdiutil will not verify the volume. Speeds up mount times.
			--  * -mountroot			Changes where the volume is mounted to.
			--  * -shadow 				Allows read/write access by creating a file the user can edit and shadowing what would have been the edits there.
			do shell script ("hdiutil attach -nobrowse -noautoopenro -noverify -mountroot " & quoted form of mountRoot & " -shadow " & quoted form of shadowFile & " " & quoted form of imagePath)
		end if
		-- Launch the app for the user too.
		if appPath exists then
			open of appPath
		end if
	end tell
end launch

-- Returns a boolean telling whether the application is currently running from the image.
on isRunning()
		set psResults to (do shell script "pgrep -f '" & binPath & "'")
		return true
	on error
		return false
	end try
end isRunning

Create the Read-only Image of the “Crappy Application”
While I’m sure all of this can be done in Disk Utility, we will use the hdiutil command in the terminal to ensure everything gets set properly. The hdiutil tool uses the DiskImages framework to manipulate disk images. We’ll create the image in the ~/Library/Application Support/[CRAPPY APPLICATION] directory, which will allow the shadow file to be saved in each user’s home folder and keep or removed it based on the system’s configuration type.

For example, for student systems, we would disregard any user home folder modifications including the created shadow file on student logout. For staff, the home folder contents including the shadow file are retained on staff logout, so, modifications to a “crappy app” environment could be retained if beneficial to the staff.

Change Directory to tmp

cd /tmp

Create Crappy App Image File
To create a read/write image to install the “crappy application”, enter the following commands:

hdiutil create -size 5g -fs HFS+J -type SPARSE -volname [CRAPPY APP]
  • -size
    This option allows you to specify a image size of 5 GB
  • -fs
    This option allows you to specify the filesystem of the image such as HFS+, HFS+J (JHFS+), HFSX, JHFS+X, APFS, FAT32, ExFAT, or UDF.
  • -type
    This option is particular to create and is used to specify the format of empty read/write images. UDIF  is the default type, but SPARSE  creates a UDSP, a read/write single-file image which expands as is is filled with data.
  • -volname
    The newly-created filesystem will be named [CRAPPY APP]

For example…

hdiutil create -size 5g -fs HFS+J -type SPARSE -volname CrappyApp CrappyApp
created: /private/tmp/CrappyApp.sparseimage

Mount Crappy App Image Volume
Next, to mount the “crappy application” image volume to begin installation process, enter the following commands:

hdiutil attach CrappyApp.sparseimage
/dev/disk1          	GUID_partition_scheme          	
/dev/disk1s1        	EFI                            	
/dev/disk1s2        	Apple_HFS                      	/Volumes/CrappyApp

At this point, you should have the volume mounted to the default location: /Volumes/CrappyApp

And see it mounted in Finder on the Desktop:


Install Application to Image
Now run the “crappy app” installer package, and when presented with a choice of “Where should this application be installed to?”, specify the “CrappyApp” volume. Some installers ask for a specific directory or if the “crappy app” installer doesn’t support this and it is a application package, you could simply copy it over to the mounted disk. An a application package is a file system directory that is normally displayed to the user by the Finder as if it were a single file or application.

Convert Read/Write Image to Read-Only
We would recommend opening a Finder window and checking that everything seems installed properly in the mounted image. Then, go back to the terminal to finalize our image creation process by running the following commands…
hdiutil detach /Volumes/[CrappyApp]
hdiutil convert [CrappyApp].sparseimage -format UDRO -o [CrappyApp].dmg

The above commands unmount or disconnect (i.e., detach ) the read/write image from the system, then converts the read/write image into a UDIF read-only image (i.e., UDRO )

Customize Launcher & Save As Application
Next, customize your the above AppleScript template and save it as an application.
Move Image Into Launcher Application
And you should have a read-only disk image with the .dmg  file extension containing your application. Put the disk image into the correct location within the application package. Within the launcher bundle (which is named “Crappy” for example), put the image into Contents/Resources/. This is where the launch script assumes the image will be located.
If you’ve never modified the insides of an application before, you’ll have to find the application in Finder and right-click it, then select “Show Package Contents” to get inside.
Or just do all the moving in the terminal, for example…
mv /path/to/[CrappyApp].dmg /path/to/[]/Contents/Resources/

From inside the wrapper application, navigate to Contents/Resources/Scripts  and open the main.scpt  file in AppleScript Editor. We’ve tried to abstract as much of the code as possible, so all you should really need to change is the variable at the top named appName and the application-specific binary launcher location. Its given value is <TEMPLATE> . Change this value to be the name of your application.

For example, change…

set appName to "<TEMPLATE>"


set appName to "CrappyApp"

The script assumes that you’ve named everything identically, so the image file is CrappyApp.dmg  and it mounts to /Volumes/CrappyApp , and the real application will be at /Volumes/CrappyApp/Applications/ . This might not necessarily be true and so you will need to change the appPath variable accordingly.

For example…

set myPath to (path to me as Unicode text)

Update Icon Image
To make the launcher application appear as the real application to Spotlight and to convince our users that it’s the regular application, we’ll include the original application’s icons file. Mount the disk image we made and navigate in Finder to the application’s .app  bundle within the mounted volume. Inside of the bundle, look in the Contents/Resources/  directory for a file ending in ‘.icns ‘. This is an Apple icons file, note there may be more than one; so make sure you use the correct main application icon. Copy this .icns  file to our launcher application and rename it with the application’s name (i.e., [CrappyApp].icns ). Then replace the applet.icns file with the your application icon file.


Property List Customizations
Inside the launcher application’s top directory, Contents , there is a property list file named Info.plist . This file contains the information describing how the launcher application presents itself to the user, such as the name, version number, and icons to display.

You’ll have to use your own discretion here, but the fields that you will probably need to change are:
  • CFBundleIconFile – the .icns file (“CrappyApp”)
  • CFBundleIdentifier – the unique identifier of our bundle (“my.domain.CrappyAppLauncher”)
  • CFBundleName – the name of the application (“CrappyApp”)
  • CFBundleShortVersionString – the version for the application. Simply copy the value from the regular application, such as “4R7 P3” or “1.58.27”, etc.

No matter what, you should always double- and triple-check your compiled playpen application before considering pushing it to your enterprises distribution cycle (i.e., development, testing, production).

Helper Script Elevated Privileges
Some “crappy app” implementations require administrative privileges to make modifications to effective use this methodology like for example with Xcode we add a user explicitly using Xcode to  _Developer  group.  To aid in the setup, we created python script to help administrators manage their sudoers file to allow scripts to run with elevated privileges but allow standard users to unknowingly use these scripts called “Sudoers Manager” and made it available to the community.

There is a file located at /etc/sudoers  which contains a list of rules for privilege escalation. This file allows users to run commands as other users. Most frequently, this file is used to grant super-user (“root”) privileges to non-root users. The administration of the sudoers  file is intricate, and it’s important that you don’t make mistake or else you risk losing all administrative access on the given computer (unless your root account has a password that you know). Our python script simplifies & automates the process of modifying the sudoers file for your environment on a fleet of systems.

Drop-In Rules Directory
Note, that you can use the sudoers #includedir  directive and then distribute sudoers rule files to /etc/sudoers.d directory as part of installation package, script or other methods. Internally, we have updated our “Sudoers Manager” to support sudoers.d  with validation and automatically creating the rule files, but currently haven’t pushed the update to our public GitHub repository.

For example, you need to edit the /etc/sudoers  file to allow support of the #includedir directive:

## Read drop-in files from /private/etc/sudoers.d
## (the '#' here does not indicate a comment)
#includedir /private/etc/sudoers.d

Be aware that sudo  will skip file names that end in `~’ or contain a `.’ character to avoid causing problems with editor or other temporary/backup files.

Validating Rules File
You can validate your sudoers rules file, before distributing or packing it by using the following command:

/usr/sbin/visudo -csf /path/to/your/sudoers_rules_file

We would recommend that these rule files should be owned by root  and with permissions 0440. Then the root user can read the file but not write to it!

For example…

visudo -c
/etc/sudoers: parsed OK
/private/etc/sudoers.d/sudoers_rules_file: parsed OK

Sudo Make Me a Sandwich Robot, Inspired By A XKCD Comic

Mac Support
Note, some older versions of macOS might not fully support sudoers.d  drop-in files. You can try manually add the #includedir directive and  /etc/sudoers.d directory and see if it works properly. The above command will show you all the files sudo is parsing and show potential issues.

We use sudoers.d with rules files with or custom “Tablet Driver Installer” application, which allows standard users (students, staff & faculty) take multiple versions of tablets to a system and install the correct version based on the tablet make and model.

For example, here is the python script we use with our custom “Table Driver Installer” application to install drivers with elevated permissions using sudoers and  sudoers.d with rules file.

from subprocess import Popen, PIPE
import sys
def main():
    path = sys.argv[1]
    arg = sys.argv[2]
    path = "installer -pkg " + "\"" + path  +arg+ "\"" + " -target /"
    print Popen(path, shell=True, stdout=PIPE, stderr=PIPE).communicate()[0]

if __name__ == "__main__":

Other Workarounds
Not all “crappy apps” can’t be solved using this application playpen methodology and we implement other solutions like using a location of the file system to validate & restore the pristine state of “crappy apps” location(s) at logout, startup, etc.


Here are some examples of applications that we have implemented the “crappy apps” model and use in our environment.

Xcode is Apple’s development environment. It’s used to develop applications on Macs, iOS devices like iPhones and the Apple Watch. However, Xcode requires users to have _Developer  group privileges to allow the debugger and emulators to work correctly. We give our users only the least privileges they need to get their work done and we take great pains to secure our environment. We felt giving every user _Developer rights was unnecessary and something solve using the “crappy app” methodology.

We use the following shell script named that adds the current user launching our Xcode “crappy app” application, then adds the user to the _Developer  group and then launches the “real Xcode application. We then could remove the user from the group at user logout and/or startup.



inSudo=`/usr/bin/grep $inputUID /etc/sudoers -c`

if /bin/test $inSudo -ne 0
    exit 0
    /usr/bin/dscl . merge /Groups/_developer GroupMembership $inputUID


With NVivo we originally did activation as part of system maintenance, but with the amount of new & temporary systems installed & number of times maintenance was run our sales representative from QSR International, the developers of NVivo, thought that the software was very popular at the University of Utah, maybe the most popular location in the world ;-). We provided them with KeyServer statistics &  logs with concurrent usage and explained our maintenance activation and shared our scripts, but they didn’t completely believe us 🙁

So, we decided to implement NVivo using our “crappy app” methodology where activation would only happen when a user explicitly launches NVivo.

For example, here is NVivo activation python script we use the the application playpen launcher:

#!/usr/bin/env python

import os
import subprocess
import sys
from management_tools import loggers

def main():
    # names the logger file in /var/log/management
    logger = loggers.FileLogger(name='activate_nvivo', level=loggers.DEBUG)"Starting up...")
    # Declaring path to home.
    home = os.path.expanduser("~")
    # Declaring paths to NVivo and NVivo XML.
    # Receives input from associated AppleScript.
    # Replaces "\" from path name and replaces with space so that it works with subprocess 
    # since subprocess doesn't understand backslashes. 
    nvivo_path = sys.argv[1]
    nvivo_path = nvivo_path.replace('\ ',' ')
    logger.debug('nvivo_path: {0!s}'.format(nvivo_path))
    path_to_nvivo_xml = home + "/Library/Application\ Support/NVivo/NVivo11Activation.xml"
    path_to_nvivo_xml = path_to_nvivo_xml.replace('\ ',' ')
    logger.debug('path_to_nvivo_xml: ' + path_to_nvivo_xml)

    #Activate"Proceeding with activation of NVivo.")
    cmd = [nvivo_path, '-initialize', '[ACTIVATION CODE]',
    	 '-activate', path_to_nvivo_xml]
    logger.debug('cmd: {0!s}'.format(cmd))
    nvivo_activation_process = subprocess.check_output(cmd)
    logger.debug('nvivo_activation_process: {0!s}'.format(nvivo_activation_process))"Exiting script.")

if __name__ == '__main__':

The user uses Finder or Spotlight and gets our “application playpen” NVivo, which mounts the image, modifies the NVivo activation xml file and then launches the “real” NVivo.

To use this script in your environment, you need to change the [ACTIVATION CODE]  with your NVivo activation code.

The challenge with Unity is that it continuously updates on application launch and we wanted to support multiple versions like standard & pro on the same system or older versions of the software.

With Unity Pro we used the following python script to automatically setup the serialization in a classroom environment using the “crappy app” methodology:

import subprocess
import sys

In order to use this script, the crappy app has to first be mounted. Once the crappy app is mounted, the script can be called but must be followed by an argument/variable.

The two available variables are 'fullSetup' and 'reserialize'.

'fullSetup' runs Unity Pro in the background while logging in with the user's credentials (-username -password) and will enter the serial number (-serial) without any windows popping up (-batchmode). It'll quit once finished (-quit).

'reserialize' does the same as 'fullSetup' except that it doesn't run in batchmode and won't quit when it's finished. For the use of this script it is called 'reserialize' since Unity Pro has a hangup creating the Unity Pro license the first time it tries to serialize (fullSetup). To fix this, it must be serialized again (reserialize).

option = sys.argv[1]
if option == 'fullSetup':"/Volumes/Unity\ Pro/Applications/Unity/Unity\ -quit -batchmode -serial [SERIAL NUMBER] -username '[USERNAME]' -password '[USER PASSWORD]'", shell=True)
elif option == 'reserialize':"/Volumes/Unity\ Pro/Applications/Unity/Unity\ -serial [SERIAL NUMBER] -username '[USERNAME]' -password '[USER PASSWORD]'", shell=True)
else:"""osascript -e 'tell application "System Events" to display dialog "Please make sure that your arguments match one of the following: \n\t\t\t\t\t fullSetup \n\t\t\t\t\t reserialize"' """, shell=True)

If you want to use this Unity Pro serialization script, change [SERIAL NUMBER] , [USERNAME]  & [USER PASSWORD]  will need to be changed for your environment.


In the future, we will be moving this project to Python and Tkinter to provide user interface along with a command line equivalent. There will be a “creator” script/application that will automate the creation of the “launcher” script/application and allow including a pre or post scripts or commands that could be executed before or after launch of the “real” crappy application. We are also debating based on frequency of updates, demands and number of “crappy applications” we need to maintain, creating an AutoPkg processor to automatically build the “crappy application” launchers. The project is in development and if you are interested, please give use feedback, as this might help change our priority or motivate us to focus on this project.

No Comments

Leave a Reply