diff --git a/include/sway/config.h b/include/sway/config.h
index febde63df..d77fbd51a 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -203,7 +203,6 @@ enum secure_feature {
 	FEATURE_FULLSCREEN = 16,
 	FEATURE_KEYBOARD = 32,
 	FEATURE_MOUSE = 64,
-	FEATURE_IPC = 128,
 };
 
 struct feature_policy {
@@ -225,7 +224,17 @@ enum ipc_feature {
 	IPC_FEATURE_EVENT_MODE = 1024,
 	IPC_FEATURE_EVENT_WINDOW = 2048,
 	IPC_FEATURE_EVENT_BINDING = 4096,
-	IPC_FEATURE_EVENT_INPUT = 8192
+	IPC_FEATURE_EVENT_INPUT = 8192,
+
+	IPC_FEATURE_ALL_COMMANDS = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+	IPC_FEATURE_ALL_EVENTS = 256 | 512 | 1024 | 2048 | 4096 | 8192,
+
+	IPC_FEATURE_ALL = IPC_FEATURE_ALL_COMMANDS | IPC_FEATURE_ALL_EVENTS,
+};
+
+struct ipc_policy {
+	char *program;
+	uint32_t features;
 };
 
 /**
@@ -300,7 +309,7 @@ struct sway_config {
 	// Security
 	list_t *command_policies;
 	list_t *feature_policies;
-	uint32_t ipc_policy;
+	list_t *ipc_policies;
 };
 
 void pid_workspace_add(struct pid_workspace *pw);
@@ -331,6 +340,8 @@ void free_config(struct sway_config *config);
  */
 char *do_var_replacement(char *str);
 
+struct cmd_results *check_security_config();
+
 int input_identifier_cmp(const void *item, const void *data);
 void merge_input_config(struct input_config *dst, struct input_config *src);
 void apply_input_config(struct input_config *ic, struct libinput_device *dev);
diff --git a/include/sway/security.h b/include/sway/security.h
index 1cc85beec..c3a5cfd4b 100644
--- a/include/sway/security.h
+++ b/include/sway/security.h
@@ -3,12 +3,14 @@
 #include <unistd.h>
 #include "sway/config.h"
 
-enum secure_feature get_feature_policy(pid_t pid);
-enum command_context get_command_policy(const char *cmd);
+uint32_t get_feature_policy(pid_t pid);
+uint32_t get_ipc_policy(pid_t pid);
+uint32_t get_command_policy(const char *cmd);
 
 const char *command_policy_str(enum command_context context);
 
 struct feature_policy *alloc_feature_policy(const char *program);
+struct ipc_policy *alloc_ipc_policy(const char *program);
 struct command_policy *alloc_command_policy(const char *command);
 
 #endif
diff --git a/security.in b/security.d/00-defaults.in
similarity index 61%
rename from security.in
rename to security.d/00-defaults.in
index 16897adee..34831c65f 100644
--- a/security.in
+++ b/security.d/00-defaults.in
@@ -5,36 +5,42 @@
 # You MUST read this man page if you intend to attempt to secure your sway
 # installation.
 #
-# This file should live at __SYSCONFDIR__/sway/security and will be
-# automatically read by sway.
+# DO NOT CHANGE THIS FILE. Override these defaults by writing new files in
+# __SYSCONFDIR__/sway/security.d/*
 
-# Configures which programs are allowed to use which sway features
-permit * fullscreen keyboard mouse ipc
+# Configures enabled compositor features for specific programs
+permit * fullscreen keyboard mouse
 permit __PREFIX__/bin/swaylock lock
-permit __PREFIX__/bin/swaybar panel
 permit __PREFIX__/bin/swaybg background
 permit __PREFIX__/bin/swaygrab screenshot
+permit __PREFIX__/bin/swaybar panel
 
-# Configures which IPC features are enabled
-ipc {
-    command enabled
+# Configures enabled IPC features for specific programs
+ipc __PREFIX__/bin/swaymsg {
+    * enabled
+
+    events {
+        * disabled
+    }
+}
+
+ipc __PREFIX__/bin/swaybar {
+    bar-config enabled
     outputs enabled
     workspaces enabled
-    tree enabled
-    marks enabled
-    bar-config enabled
-    inputs enabled
+    command enabled
 
     events {
         workspace enabled
-        output enabled
         mode enabled
-        window enabled
-        input enabled
-        binding disabled
     }
 }
 
+ipc __PREFIX__/bin/swaygrab {
+    outputs enabled
+    tree enabled
+}
+
 # Limits the contexts from which certain commands are permitted
 commands {
     * all
diff --git a/sway/CMakeLists.txt b/sway/CMakeLists.txt
index d5453003c..981f8a071 100644
--- a/sway/CMakeLists.txt
+++ b/sway/CMakeLists.txt
@@ -91,7 +91,7 @@ function(add_config name source destination)
 endfunction()
 
 add_config(config config sway)
-add_config(security security sway)
+add_config(00-defaults security.d/00-defaults sway/security.d)
 
 add_manpage(sway 1)
 add_manpage(sway 5)
diff --git a/sway/commands.c b/sway/commands.c
index c15cb00ad..068e8866b 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -297,6 +297,7 @@ static struct cmd_handler bar_colors_handlers[] = {
 };
 
 static struct cmd_handler ipc_handlers[] = {
+	{ "*", cmd_ipc_cmd },
 	{ "bar-config", cmd_ipc_cmd },
 	{ "command", cmd_ipc_cmd },
 	{ "events", cmd_ipc_events },
@@ -308,6 +309,7 @@ static struct cmd_handler ipc_handlers[] = {
 };
 
 static struct cmd_handler ipc_event_handlers[] = {
+	{ "*", cmd_ipc_event_cmd },
 	{ "binding", cmd_ipc_event_cmd },
 	{ "input", cmd_ipc_event_cmd },
 	{ "mode", cmd_ipc_event_cmd },
diff --git a/sway/commands/commands.c b/sway/commands/commands.c
index 8c7ed487e..0c64970c9 100644
--- a/sway/commands/commands.c
+++ b/sway/commands/commands.c
@@ -10,6 +10,9 @@ struct cmd_results *cmd_commands(int argc, char **argv) {
 	if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
 		return error;
 	}
+	if ((error = check_security_config())) {
+		return error;
+	}
 
 	if (strcmp(argv[0], "{") != 0) {
 		return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration");
@@ -19,10 +22,5 @@ struct cmd_results *cmd_commands(int argc, char **argv) {
 		return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file.");
 	}
 
-	if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
-		return cmd_results_new(CMD_INVALID, "permit",
-				"This command is only permitted to run from " SYSCONFDIR "/sway/security");
-	}
-
 	return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
 }
diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c
index 113a975b0..8a7b849f9 100644
--- a/sway/commands/ipc.c
+++ b/sway/commands/ipc.c
@@ -1,18 +1,26 @@
 #include <stdio.h>
 #include <string.h>
+#include "sway/security.h"
 #include "sway/commands.h"
 #include "sway/config.h"
 #include "ipc.h"
 #include "log.h"
 #include "util.h"
 
+static struct ipc_policy *current_policy = NULL;
+
 struct cmd_results *cmd_ipc(int argc, char **argv) {
 	struct cmd_results *error = NULL;
-	if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) {
+	if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 2))) {
+		return error;
+	}
+	if ((error = check_security_config())) {
 		return error;
 	}
 
-	if (config->reading && strcmp("{", argv[0]) != 0) {
+	const char *program = argv[0];
+
+	if (config->reading && strcmp("{", argv[1]) != 0) {
 		return cmd_results_new(CMD_INVALID, "ipc",
 				"Expected '{' at start of IPC config definition.");
 	}
@@ -21,10 +29,8 @@ struct cmd_results *cmd_ipc(int argc, char **argv) {
 		return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file.");
 	}
 
-	if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
-		return cmd_results_new(CMD_INVALID, "permit",
-				"This command is only permitted to run from " SYSCONFDIR "/sway/security");
-	}
+	current_policy = alloc_ipc_policy(program);
+	list_add(config->ipc_policies, current_policy);
 
 	return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
 }
@@ -67,6 +73,7 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
 		char *name;
 		enum ipc_feature type;
 	} types[] = {
+		{ "*", IPC_FEATURE_ALL_COMMANDS },
 		{ "command", IPC_FEATURE_COMMAND },
 		{ "workspaces", IPC_FEATURE_GET_WORKSPACES },
 		{ "outputs", IPC_FEATURE_GET_OUTPUTS },
@@ -86,10 +93,10 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
 	}
 
 	if (enabled) {
-		config->ipc_policy |= type;
+		current_policy->features |= type;
 		sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
 	} else {
-		config->ipc_policy &= ~type;
+		current_policy->features &= ~type;
 		sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
 	}
 
@@ -116,6 +123,7 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
 		char *name;
 		enum ipc_feature type;
 	} types[] = {
+		{ "*", IPC_FEATURE_ALL_EVENTS },
 		{ "workspace", IPC_FEATURE_EVENT_WORKSPACE },
 		{ "output", IPC_FEATURE_EVENT_OUTPUT },
 		{ "mode", IPC_FEATURE_EVENT_MODE },
@@ -134,10 +142,10 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
 	}
 
 	if (enabled) {
-		config->ipc_policy |= type;
+		current_policy->features |= type;
 		sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
 	} else {
-		config->ipc_policy &= ~type;
+		current_policy->features &= ~type;
 		sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
 	}
 
diff --git a/sway/commands/permit.c b/sway/commands/permit.c
index 1b2a30bfc..e2bec2e24 100644
--- a/sway/commands/permit.c
+++ b/sway/commands/permit.c
@@ -19,7 +19,6 @@ static enum secure_feature get_features(int argc, char **argv,
 		{ "fullscreen", FEATURE_FULLSCREEN },
 		{ "keyboard", FEATURE_KEYBOARD },
 		{ "mouse", FEATURE_MOUSE },
-		{ "ipc", FEATURE_IPC },
 	};
 
 	for (int i = 1; i < argc; ++i) {
@@ -63,19 +62,13 @@ struct cmd_results *cmd_permit(int argc, char **argv) {
 	if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
 		return error;
 	}
-
-	if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
-		return cmd_results_new(CMD_INVALID, "permit",
-				"This command is only permitted to run from " SYSCONFDIR "/sway/security");
+	if ((error = check_security_config())) {
+		return error;
 	}
 
 	struct feature_policy *policy = get_policy(argv[0]);
 	policy->features |= get_features(argc, argv, &error);
 
-	if (error) {
-		return error;
-	}
-
 	sway_log(L_DEBUG, "Permissions granted to %s for features %d",
 			policy->program, policy->features);
 
@@ -87,19 +80,13 @@ struct cmd_results *cmd_reject(int argc, char **argv) {
 	if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
 		return error;
 	}
-
-	if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) {
-		return cmd_results_new(CMD_INVALID, "permit",
-				"This command is only permitted to run from " SYSCONFDIR "/sway/security");
+	if ((error = check_security_config())) {
+		return error;
 	}
 
 	struct feature_policy *policy = get_policy(argv[0]);
 	policy->features &= ~get_features(argc, argv, &error);
 
-	if (error) {
-		return error;
-	}
-
 	sway_log(L_DEBUG, "Permissions granted to %s for features %d",
 			policy->program, policy->features);
 
diff --git a/sway/config.c b/sway/config.c
index 9e758c903..88e6fad1a 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -11,6 +11,7 @@
 #include <libinput.h>
 #include <limits.h>
 #include <float.h>
+#include <dirent.h>
 #include "wayland-desktop-shell-server-protocol.h"
 #include "sway/commands.h"
 #include "sway/config.h"
@@ -379,7 +380,7 @@ static void config_defaults(struct sway_config *config) {
 	// Security
 	if (!(config->command_policies = create_list())) goto cleanup;
 	if (!(config->feature_policies = create_list())) goto cleanup;
-	config->ipc_policy = UINT32_MAX;
+	if (!(config->ipc_policies = create_list())) goto cleanup;
 
 	return;
 cleanup:
@@ -485,6 +486,10 @@ static bool load_config(const char *path, struct sway_config *config) {
 	return true;
 }
 
+static int qstrcmp(const void* a, const void* b) {
+    return strcmp(*((char**) a), *((char**) b));
+}
+
 bool load_main_config(const char *file, bool is_active) {
 	input_init();
 
@@ -512,7 +517,43 @@ bool load_main_config(const char *file, bool is_active) {
 	list_add(config->config_chain, path);
 
 	config->reading = true;
-	bool success = load_config(SYSCONFDIR "/sway/security", config);
+	
+	// Read security configs
+	bool success = true;
+	DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
+	if (!dir) {
+		sway_log(L_ERROR, "%s does not exist, sway will have no security configuration"
+				" and will probably be broken", SYSCONFDIR "/sway/security.d");
+	} else {
+		list_t *secconfigs = create_list();
+		char *base = SYSCONFDIR "/sway/security.d/";
+		struct dirent *ent = readdir(dir);
+		while (ent != NULL) {
+			if (ent->d_type == DT_REG) {
+				char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1);
+				strcpy(_path, base);
+				strcat(_path, ent->d_name);
+				list_add(secconfigs, _path);
+			}
+			ent = readdir(dir);
+		}
+		closedir(dir);
+
+		list_qsort(secconfigs, qstrcmp);
+		for (int i = 0; i < secconfigs->length; ++i) {
+			char *_path = secconfigs->items[i];
+			struct stat s;
+			if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || (s.st_mode & 0777) != 0644) {
+				sway_log(L_ERROR, "Refusing to load %s - it must be owned by root and mode 644", _path);
+				success = false;
+			} else {
+				success = success && load_config(_path, config);
+			}
+		}
+
+		free_flat_list(secconfigs);
+	}
+
 	success = success && load_config(path, config);
 
 	if (is_active) {
@@ -620,6 +661,15 @@ bool load_include_configs(const char *path, struct sway_config *config) {
 	return true;
 }
 
+struct cmd_results *check_security_config() {
+	if (!current_config_path || strncmp(SYSCONFDIR "/sway/security.d/", current_config_path,
+				strlen(SYSCONFDIR "/sway/security.d/")) != 0) {
+		return cmd_results_new(CMD_INVALID, "permit",
+				"This command is only permitted to run from " SYSCONFDIR "/sway/security.d/*");
+	}
+	return NULL;
+}
+
 bool read_config(FILE *file, struct sway_config *config) {
 	bool success = true;
 	enum cmd_status block = CMD_BLOCK_END;
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index be6e411a5..eddae4613 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -35,6 +35,7 @@ struct ipc_client {
 	struct wlc_event_source *event_source;
 	int fd;
 	uint32_t payload_length;
+	uint32_t security_policy;
 	enum ipc_command_type current_command;
 	enum ipc_command_type subscribed_events;
 };
@@ -159,17 +160,6 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
 		return 0;
 	}
 
-	pid_t pid = get_client_pid(client_fd);
-	if (!(get_feature_policy(pid) & FEATURE_IPC)) {
-		sway_log(L_INFO, "Permission to connect to IPC socket denied to %d", pid);
-		const char *error = "{\"success\": false, \"message\": \"Permission denied\"}";
-		if (write(client_fd, &error, sizeof(error)) < (int)sizeof(error)) {
-			sway_log(L_DEBUG, "Failed to write entire error");
-		}
-		close(client_fd);
-		return 0;
-	}
-
 	struct ipc_client* client = malloc(sizeof(struct ipc_client));
 	if (!client) {
 		sway_log(L_ERROR, "Unable to allocate ipc client");
@@ -181,6 +171,9 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
 	client->subscribed_events = 0;
 	client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client);
 
+	pid_t pid = get_client_pid(client->fd);
+	client->security_policy = get_ipc_policy(pid);
+
 	list_add(ipc_client_list, client);
 
 	return 0;
@@ -248,8 +241,7 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
 	return 0;
 }
 
-void ipc_client_disconnect(struct ipc_client *client)
-{
+void ipc_client_disconnect(struct ipc_client *client) {
 	if (!sway_assert(client != NULL, "client != NULL")) {
 		return;
 	}
@@ -333,8 +325,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 		ipc_client_disconnect(client);
 		return;
 	}
-	if (client->payload_length > 0)
-	{
+	if (client->payload_length > 0) {
 		ssize_t received = recv(client->fd, buf, client->payload_length, 0);
 		if (received == -1)
 		{
@@ -351,7 +342,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 	switch (client->current_command) {
 	case IPC_COMMAND:
 	{
-		if (!(config->ipc_policy & IPC_FEATURE_COMMAND)) {
+		if (!(client->security_policy & IPC_FEATURE_COMMAND)) {
 			goto exit_denied;
 		}
 		struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
@@ -365,6 +356,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 
 	case IPC_SUBSCRIBE:
 	{
+		// TODO: Check if they're permitted to use these events
 		struct json_object *request = json_tokener_parse(buf);
 		if (request == NULL) {
 			ipc_send_reply(client, "{\"success\": false}", 18);
@@ -403,7 +395,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 
 	case IPC_GET_WORKSPACES:
 	{
-		if (!(config->ipc_policy & IPC_FEATURE_GET_WORKSPACES)) {
+		if (!(client->security_policy & IPC_FEATURE_GET_WORKSPACES)) {
 			goto exit_denied;
 		}
 		json_object *workspaces = json_object_new_array();
@@ -416,7 +408,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 
 	case IPC_GET_INPUTS:
 	{
-		if (!(config->ipc_policy & IPC_FEATURE_GET_INPUTS)) {
+		if (!(client->security_policy & IPC_FEATURE_GET_INPUTS)) {
 			goto exit_denied;
 		}
 		json_object *inputs = json_object_new_array();
@@ -442,7 +434,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 
 	case IPC_GET_OUTPUTS:
 	{
-		if (!(config->ipc_policy & IPC_FEATURE_GET_OUTPUTS)) {
+		if (!(client->security_policy & IPC_FEATURE_GET_OUTPUTS)) {
 			goto exit_denied;
 		}
 		json_object *outputs = json_object_new_array();
@@ -455,7 +447,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 
 	case IPC_GET_TREE:
 	{
-		if (!(config->ipc_policy & IPC_FEATURE_GET_TREE)) {
+		if (!(client->security_policy & IPC_FEATURE_GET_TREE)) {
 			goto exit_denied;
 		}
 		json_object *tree = ipc_json_describe_container_recursive(&root_container);
@@ -522,7 +514,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 
 	case IPC_GET_BAR_CONFIG:
 	{
-		if (!(config->ipc_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
+		if (!(client->security_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
 			goto exit_denied;
 		}
 		if (!buf[0]) {
@@ -567,6 +559,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
 
 exit_denied:
 	ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied));
+	sway_log(L_DEBUG, "Denied IPC client access to %i", client->current_command);
 
 exit_cleanup:
 	client->payload_length = 0;
@@ -594,6 +587,8 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
 		return false;
 	}
 
+	sway_log(L_DEBUG, "Send IPC reply: %s", payload);
+
 	return true;
 }
 
@@ -616,10 +611,33 @@ void ipc_get_outputs_callback(swayc_t *container, void *data) {
 }
 
 void ipc_send_event(const char *json_string, enum ipc_command_type event) {
+	static struct {
+		enum ipc_command_type event;
+		enum ipc_feature feature;
+	} security_mappings[] = {
+		{ IPC_EVENT_WORKSPACE, IPC_FEATURE_EVENT_WORKSPACE },
+		{ IPC_EVENT_OUTPUT, IPC_FEATURE_EVENT_OUTPUT },
+		{ IPC_EVENT_MODE, IPC_FEATURE_EVENT_MODE },
+		{ IPC_EVENT_WINDOW, IPC_FEATURE_EVENT_WINDOW },
+		{ IPC_EVENT_BINDING, IPC_FEATURE_EVENT_BINDING },
+		{ IPC_EVENT_INPUT, IPC_FEATURE_EVENT_INPUT }
+	};
+
+	uint32_t security_mask = 0;
+	for (size_t i = 0; i < sizeof(security_mappings) / sizeof(security_mappings[0]); ++i) {
+		if (security_mappings[i].event == event) {
+			security_mask = security_mappings[i].feature;
+			break;
+		}
+	}
+
 	int i;
 	struct ipc_client *client;
 	for (i = 0; i < ipc_client_list->length; i++) {
 		client = ipc_client_list->items[i];
+		if (!(client->security_policy & security_mask)) {
+			continue;
+		}
 		if ((client->subscribed_events & event_mask(event)) == 0) {
 			continue;
 		}
@@ -632,9 +650,6 @@ void ipc_send_event(const char *json_string, enum ipc_command_type event) {
 }
 
 void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
-	if (!(config->ipc_policy & IPC_FEATURE_EVENT_WORKSPACE)) {
-		return;
-	}
 	sway_log(L_DEBUG, "Sending workspace::%s event", change);
 	json_object *obj = json_object_new_object();
 	json_object_object_add(obj, "change", json_object_new_string(change));
@@ -659,9 +674,6 @@ void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
 }
 
 void ipc_event_window(swayc_t *window, const char *change) {
-	if (!(config->ipc_policy & IPC_FEATURE_EVENT_WINDOW)) {
-		return;
-	}
 	sway_log(L_DEBUG, "Sending window::%s event", change);
 	json_object *obj = json_object_new_object();
 	json_object_object_add(obj, "change", json_object_new_string(change));
@@ -687,9 +699,6 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
 }
 
 void ipc_event_mode(const char *mode) {
-	if (!(config->ipc_policy & IPC_FEATURE_EVENT_MODE)) {
-		return;
-	}
 	sway_log(L_DEBUG, "Sending mode::%s event", mode);
 	json_object *obj = json_object_new_object();
 	json_object_object_add(obj, "change", json_object_new_string(mode));
@@ -715,9 +724,6 @@ void ipc_event_modifier(uint32_t modifier, const char *state) {
 }
 
 static void ipc_event_binding(json_object *sb_obj) {
-	if (!(config->ipc_policy & IPC_FEATURE_EVENT_BINDING)) {
-		return;
-	}
 	sway_log(L_DEBUG, "Sending binding::run event");
 	json_object *obj = json_object_new_object();
 	json_object_object_add(obj, "change", json_object_new_string("run"));
diff --git a/sway/main.c b/sway/main.c
index 1c4c56c0b..0151e0785 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -175,13 +175,6 @@ static void security_sanity_check() {
 		cap_free(cap);
 	}
 #endif
-	if (!stat(SYSCONFDIR "/sway", &s)) {
-		if (s.st_uid != 0 || s.st_gid != 0
-				|| (s.st_mode & S_IWGRP) || (s.st_mode & S_IWOTH)) {
-			sway_log(L_ERROR,
-				"!! DANGER !! " SYSCONFDIR "/sway is not secure! It should be owned by root and set to 0755 at the minimum");
-		}
-	}
 }
 
 int main(int argc, char **argv) {
diff --git a/sway/security.c b/sway/security.c
index 41a3b94b6..9dfc7d2de 100644
--- a/sway/security.c
+++ b/sway/security.c
@@ -27,6 +27,29 @@ struct feature_policy *alloc_feature_policy(const char *program) {
 	return policy;
 }
 
+struct ipc_policy *alloc_ipc_policy(const char *program) {
+	uint32_t default_policy = 0;
+	for (int i = 0; i < config->ipc_policies->length; ++i) {
+		struct ipc_policy *policy = config->ipc_policies->items[i];
+		if (strcmp(policy->program, "*") == 0) {
+			default_policy = policy->features;
+			break;
+		}
+	}
+
+	struct ipc_policy *policy = malloc(sizeof(struct ipc_policy));
+	if (!policy) {
+		return NULL;
+	}
+	policy->program = strdup(program);
+	if (!policy->program) {
+		free(policy);
+		return NULL;
+	}
+	policy->features = default_policy;
+	return policy;
+}
+
 struct command_policy *alloc_command_policy(const char *command) {
 	struct command_policy *policy = malloc(sizeof(struct command_policy));
 	if (!policy) {
@@ -41,7 +64,7 @@ struct command_policy *alloc_command_policy(const char *command) {
 	return policy;
 }
 
-enum secure_feature get_feature_policy(pid_t pid) {
+static const char *get_pid_exe(pid_t pid) {
 #ifdef __FreeBSD__
 	const char *fmt = "/proc/%d/file";
 #else
@@ -52,9 +75,8 @@ enum secure_feature get_feature_policy(pid_t pid) {
 	if (path) {
 		snprintf(path, pathlen + 1, fmt, pid);
 	}
-	static char link[2048];
 
-	uint32_t default_policy = 0;
+	static char link[2048];
 
 	ssize_t len = !path ? -1 : readlink(path, link, sizeof(link));
 	if (len < 0) {
@@ -67,6 +89,13 @@ enum secure_feature get_feature_policy(pid_t pid) {
 	}
 	free(path);
 
+	return link;
+}
+
+uint32_t get_feature_policy(pid_t pid) {
+	uint32_t default_policy = 0;
+	const char *link = get_pid_exe(pid);
+
 	for (int i = 0; i < config->feature_policies->length; ++i) {
 		struct feature_policy *policy = config->feature_policies->items[i];
 		if (strcmp(policy->program, "*") == 0) {
@@ -80,7 +109,24 @@ enum secure_feature get_feature_policy(pid_t pid) {
 	return default_policy;
 }
 
-enum command_context get_command_policy(const char *cmd) {
+uint32_t get_ipc_policy(pid_t pid) {
+	uint32_t default_policy = 0;
+	const char *link = get_pid_exe(pid);
+
+	for (int i = 0; i < config->ipc_policies->length; ++i) {
+		struct ipc_policy *policy = config->ipc_policies->items[i];
+		if (strcmp(policy->program, "*") == 0) {
+			default_policy = policy->features;
+		}
+		if (strcmp(policy->program, link) == 0) {
+			return policy->features;
+		}
+	}
+
+	return default_policy;
+}
+
+uint32_t get_command_policy(const char *cmd) {
 	uint32_t default_policy = 0;
 
 	for (int i = 0; i < config->command_policies->length; ++i) {
diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt
index 7d8aa4ad9..fb47ffcfd 100644
--- a/sway/sway-security.7.txt
+++ b/sway/sway-security.7.txt
@@ -19,8 +19,13 @@ usually best suited to a distro maintainer who wants to ship a secure sway
 environment in their distro. Sway provides a number of means of securing it but
 you must make a few changes external to sway first.
 
-Security-related configuration is only valid in /etc/sway/config (or whatever path
-is appropriate for your system).
+Configuration of security features is limited to files in the security directory
+(this is likely /etc/sway/security.d/*, but depends on your installation prefix).
+Files in this directory must be owned by root:root and chmod 644. The default
+security configuration is installed to /etc/sway/security.d/00-defaults, and
+should not be modified - it will be updated with the latest recommended security
+defaults between releases. To override the defaults, you should add more files to
+this directory.
 
 Environment security
 --------------------
@@ -160,22 +165,20 @@ Setting a command policy overwrites any previous policy that was in place.
 IPC policies
 ------------
 
-You may whitelist IPC access like so:
+Disabling IPC access via swaymsg is encouraged if you intend to secure the IPC
+socket, because any program that can execute swaymsg could circumvent its own
+security policy by simply invoking swaymsg.
 
-	permit /usr/bin/swaybar ipc
-	permit /usr/bin/swaygrab ipc
-	# etc
+You can configure which features of IPC are available for particular clients:
 
-Note that it's suggested you do not enable swaymsg to access IPC if you intend to
-secure your IPC socket, because any program could just run swaymsg itself instead
-of connecting to IPC directly.
-
-You can also configure which features of IPC are available with an IPC block:
-
-	ipc {
+	ipc <executable> {
 		...
 	}
 
+You may use * for <executable> to configure the default policy for all clients.
+Configuring IPC policies for specific executables is not supported on FreeBSD, and
+the default policy will be applied to all IPC connections.
+
 The following commands are available within this block:
 
 **bar-config** <enabled|disabled>::
@@ -201,7 +204,7 @@ The following commands are available within this block:
 
 You can also control which IPC events can be raised with an events block:
 
-	ipc {
+	ipc <executable> {
 		events {
 			...
 		}
@@ -227,7 +230,8 @@ The following commands are vaild within an ipc events block:
 **workspace** <enabled|disabled>::
 	Controls workspace notifications.
 
-Disabling some of these may cause swaybar to behave incorrectly.
+In each of these blocks, you may use * (as in "* enabled" or "* disabled") to
+control access to every feature at once.
 
 Authors
 -------