Apple - Is there a way to password protect individual apps?

Regarding your comment on Paul's answer, wanting to leave your computer for a moment: you have to lock your computer. Period.

Open System Preferences, click Security (top row, second to last option), under the "General" tab, check the box for "Require password [immediately] after sleep or screen saver begins".

Then, when you go to walk away from your computer;

Ctrl ⌃+Shift ⇧+Eject ⏏

(Additional notes: Click the above line.)

Lock your Mac. Walk away. Come back, enter your password to unlock it. Secured console.


You can just set parental controls on the account and then determine which apps can be used.

Alternatively you could set a password on your screen saver and also define a suitable "hot corner" to activate the screen saver - that way when you step away from your computer you can just move the mouse into the hot corner to effectively lock the screen such that it require a password to gain access.


It's possible using scripts.

First, you should enable the script menu in the OS X menu bar. Read the "Script Menu" section here: Enable the Script Menu

Now open your Library/Scripts folder and create a file called "run_with_password.rb" with these contents (change "johndoe" to your username):

#!/usr/bin/env ruby
# run an app at lower privilege

require 'etc'
require 'find'

# Note: anyone with sudo access will be able to run as this user. But they could do that anyway.
# run 'id' at the terminal to find out what your username is.
RUN_USER = 'johndoe'

def get_root_info
  root_entry = Etc.getpwnam('root')
  return root_entry.uid, root_entry.gid
end

ROOT_UID, ROOT_GID = get_root_info

def ensure_root
  Process.uid = ROOT_UID
  Process.gid = ROOT_GID
end

def print_user_info
  [
   [:uid, Process.uid],
   [:gid, Process.gid],
   [:euid, Process.euid],
   [:egid, Process.egid],
  ].each do |arr|
    $stderr.puts arr.inspect
  end
end

def set_effective(euid, egid)
  $stderr.puts "setting effective to #{[euid, egid].inspect}"  if $DEBUG
  # must set group first
  Process.egid = egid
  Process.euid = euid
end

def do_privileged(&block)
  orig_euid = Process.euid
  orig_egid = Process.egid
  begin
    $stderr.puts "raising privileges"  if $DEBUG
    set_effective(ROOT_UID, ROOT_GID)
    yield orig_euid, orig_egid
  ensure
    $stderr.puts "lowering privileges"  if $DEBUG
    set_effective(orig_euid, orig_egid)
  end
end

# must be called after ROOT_UID, ROOT_GID are set
def chmod_files_in_dir(mode, dir)
  mode_str = nil
  case mode
  when Integer
    mode_str = '%o' % mode
  when String
    mode_str = mode
  else
    raise TypeError
  end
  chmod_proc = proc do
    Find.find(dir) {|entry|
      if File.directory?(entry) and entry != dir
        Find.prune  # don't recurse into subdirs
      elsif File.file?(entry)
        $stderr.puts "chmod #{mode_str} #{entry}"  if $DEBUG
        system 'chmod', mode_str, entry
      end
    }
  end
  # assume that if dir is owned by root, the executables are also.
  if File.stat(dir).uid == ROOT_UID
    do_privileged(&chmod_proc)
  else
    chmod_proc.call
  end
end

def main(argv)
  # Important: this is to abort if we're not running as root.
  ensure_root

  app_path = argv.shift or raise "Need path to .app file, e.g. /Applications/Mail.app"
  app_macos_dir = File.join(app_path, 'Contents/MacOS')
  File.directory?(app_path) or raise "#{app_path} is not an app bundle"
  File.directory?(app_macos_dir) or raise "#{app_path} bundle doesn't have expected MacOS structure"

  pw_entry = Etc.getpwnam(RUN_USER)
  run_uid = pw_entry.uid
  run_gid = pw_entry.gid


  if $DEBUG
    $stderr.puts [:run_uid, run_uid].inspect
    $stderr.puts [:run_gid, run_gid].inspect
    print_user_info
  end

  # Effectively become RUN_USER
  set_effective(run_uid, run_gid)

  if $DEBUG
    print_user_info
  end

  begin
    chmod_files_in_dir('+x', app_macos_dir)
    # 'open' is asynchronous, so the ensure will run immediately after, and before the app exits.
    $stderr.puts "Running app: #{app_path}"  if $DEBUG
    system 'open', app_path
  ensure
    chmod_files_in_dir('-x', app_macos_dir)
  end
end

if __FILE__ == $0
  $DEBUG = false
  main(ARGV)
end

Next, start Script Editor and paste in this code (again changing johndoe to your username):

do shell script "ruby /Users/johndoe/Library/Scripts/run_with_password.rb /Applications/Mail.app" with administrator privileges

Save the file into Library/Scripts as "mail_with_password", making sure the File Format is "Script".

Now "mail_with_password" will appear in your script menu. Every time you run it, it will ask you for your password (just like some installers do). After it's done running, it will disable access to the regular Mail application. So run the script once, then try running the Mail app. It won't run. Note that it means ALL users on your machine will be prevented from running Mail directly, not just your user.

If you ever want to allow Mail to run normally again, run this command at the Terminal:

sudo chmod +x /Applications/Mail.app/Contents/MacOS/Mail

You might be able to omit the "sudo". Use sudo if you get "Operation not permitted". Note that sudo will ask you for your password to allow privileged operation.

Caveats

  1. If you didn't need the "sudo" command above to do the chmod, that means a savvy user might be able to figure out how to enable the Mail app again. You can tighten up security by changing the owner of the MacOS/Mail file to root. That's left as an exercise for the reader.
  2. If someone is able to copy the Mail app to your computer (e.g. via USB drive) they can still get access to your mail.
  3. The ruby script is meant to work for most OS X application bundles. I don't recommended tweaking the ruby script unless you really know what you're doing because it's doing certain things as root (the privileged user). Tweaking the applescript code should be harmless; but you should know how to adjust the chmod command to make your app directly runnable again.
  4. If the path to the app in the applescript file has spaces or other special characters, you'll have to do something like putting single quotes around the whole path.
  5. Edit: User Austin suggested that this procedure doesn't protect the .emlx files. I actually don't use the Mail app so I'm not familiar with the data storage. Similar issues apply to all apps - because this solution does not hide user data.

Paranoia

If someone who knows ruby get access to your logged in user, they could modify the ruby script in a way that wreaks all sorts of havoc when you run the script, since it runs as root for part of the time. If you think this might happen, you should make the script only writable by root. You'll also have to make sure someone doesn't replace the script with their own - they can do this if the folder is writable by you. If you're starting to get scared by these warnings and don't know how to protect yourself, you probably should forget about this solution and just remember to lock your screen when you leave the computer.