diff --git a/daemon/engine.c b/daemon/engine.c
index b2e2bcd81aaa0c6b8b8c498a5412d8aeebe828dc..af71bcef2e8529f08fea46233748f1449e730d3e 100644
--- a/daemon/engine.c
+++ b/daemon/engine.c
@@ -245,6 +245,7 @@ static int init_resolver(struct engine *engine)
 {
 	/* Open resolution context */
 	engine->resolver.trust_anchors = map_make();
+	engine->resolver.negative_anchors = map_make();
 	engine->resolver.pool = engine->pool;
 	engine->resolver.modules = &engine->modules;
 	/* Create OPT RR */
@@ -359,18 +360,19 @@ void engine_deinit(struct engine *engine)
 	lru_deinit(engine->resolver.cache_rtt);
 	lru_deinit(engine->resolver.cache_rep);
 
-	/* Unload modules. */
+	/* Unload modules and engine. */
 	for (size_t i = 0; i < engine->modules.len; ++i) {
 		engine_unload(engine, engine->modules.at[i]);
 	}
-	array_clear(engine->modules);
-	array_clear(engine->storage_registry);
-	kr_ta_clear(&engine->resolver.trust_anchors);
-
 	if (engine->L) {
 		lua_close(engine->L);
 	}
 
+	/* Free data structures */
+	array_clear(engine->modules);
+	array_clear(engine->storage_registry);
+	kr_ta_clear(&engine->resolver.trust_anchors);
+	kr_ta_clear(&engine->resolver.negative_anchors);
 }
 
 int engine_pcall(lua_State *L, int argc)
diff --git a/daemon/lua/kres.lua b/daemon/lua/kres.lua
index 649224a1fdc9b5aefb4dd754abf9628c89718454..ee2e34abd2267e85441760172a6c8f4d94946e9d 100644
--- a/daemon/lua/kres.lua
+++ b/daemon/lua/kres.lua
@@ -166,13 +166,21 @@ struct kr_context
 	uint32_t options;
 	knot_rrset_t *opt_rr;
 	map_t trust_anchors;
+	map_t negative_anchors;
 	uint8_t _stub[]; /* Do not touch */
 };
 
-/* libknot API
+/*
+ * libc APIs
  */
+void free(void *ptr);
 
+/*
+ * libknot APIs
+ */
 /* Domain names */
+knot_dname_t *knot_dname_from_str(uint8_t *dst, const char *name, size_t maxlen);
+char *knot_dname_to_str(char *dst, const knot_dname_t *name, size_t maxlen);
 /* Resource records */
 /* Packet */
 const knot_dname_t *knot_pkt_qname(const knot_pkt_t *pkt);
@@ -181,9 +189,9 @@ uint16_t knot_pkt_qclass(const knot_pkt_t *pkt);
 int knot_pkt_begin(knot_pkt_t *pkt, int section_id);
 int knot_pkt_put_question(knot_pkt_t *pkt, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype);
 
-/* libkres API
+/* 
+ * libkres API
  */
-
 /* Resolution request */
 struct kr_rplan *kr_resolve_plan(struct kr_request *request);
 /* Resolution plan */
@@ -259,6 +267,24 @@ ffi.metatype( kr_request_t, {
 	},
 })
 
+-- Module API
+local kres_context = ffi.cast('struct kr_context *', __engine)
+local kres = {
+	-- Constants
+	class = ffi.new('struct rr_class'),
+	type = ffi.new('struct rr_type'),
+	section = ffi.new('struct pkt_section'),
+	rcode = ffi.new('struct pkt_rcode'),
+	NOOP = 0, CONSUME = 1, PRODUCE = 2, DONE = 4, FAIL = 8,
+	-- Metatypes
+	pkt_t = function (udata) return ffi.cast('knot_pkt_t *', udata) end,
+	request_t = function (udata) return ffi.cast('struct kr_request *', udata) end,
+	-- Global API functions
+	str2dname = function(name) return ffi.string(ffi.gc(C.knot_dname_from_str(nil, name, 0), C.free)) end,
+	dname2str = function(dname) return ffi.string(ffi.gc(C.knot_dname_to_str(nil, dname, 0), C.free)) end,
+	context = function () return kres_context end,
+}
+
 -- Return DS/DNSKEY parser that adds keys to TA store
 local function ta_parser(store)
 	local parser = require('zonefile').parser(function (p)
@@ -268,38 +294,26 @@ local function ta_parser(store)
 end
 
 -- TA store management
-local trust_anchors = {
-	current_file = "",
-	is_auto = false,
-	store = ffi.cast('struct kr_context *', __engine).trust_anchors,
+kres.trust_anchors = {
 	-- Load keys from a file
 	config = function (path)
-		ta_parser(trust_anchors.store):parse_file(path)
-		trust_anchors.current_file = path
+		ta_parser(kres_context.trust_anchors):parse_file(path)
+		kres.trust_anchors.current_file = path
+	end,
+	-- Add DS/DNSKEY record(s)
+	add = function (ds) ta_parser(kres_context.trust_anchors):read(ds..'\n') end,
+	-- Negative TA management
+	set_insecure = function (list)
+		C.kr_ta_clear(kres_context.negative_anchors)
+		for i = 1, #list do
+			local dname = kres.str2dname(list[i])
+			C.kr_ta_add(kres_context.negative_anchors, dname, kres.type.DS, 0, nil, 0)
+		end
 	end,
-	-- Add DS/DNSKEY record
-	add = function (ds) ta_parser(trust_anchors.store):read(ds..'\n') end,
-	clear = function() C.kr_ta_clear(trust_anchors.store) end,
 	-- Set/disable RFC5011 TA management
 	set_auto = function (enable)
 		error("not supported")
 	end,
 }
 
--- Module API
-local kres = {
-	-- Constants
-	class = ffi.new('struct rr_class'),
-	type = ffi.new('struct rr_type'),
-	section = ffi.new('struct pkt_section'),
-	rcode = ffi.new('struct pkt_rcode'),
-	NOOP = 0, CONSUME = 1, PRODUCE = 2, DONE = 4, FAIL = 8,
-	-- Metatypes
-	pkt_t = function (udata) return ffi.cast('knot_pkt_t *', udata) end,
-	request_t = function (udata) return ffi.cast('struct kr_request *', udata) end,
-	-- Global API functions
-	context = function () return ffi.cast('struct kr_context *', __engine) end,
-	trust_anchors = trust_anchors,
-}
-
 return kres
\ No newline at end of file
diff --git a/daemon/lua/sandbox.lua b/daemon/lua/sandbox.lua
index a918bf75ec89bc7fbe6cddf87afc5b173dff9219..8ae4947b954472a8e14d6243c2b86addf9fa0f1f 100644
--- a/daemon/lua/sandbox.lua
+++ b/daemon/lua/sandbox.lua
@@ -70,6 +70,7 @@ setmetatable(trust_anchors, {
 	__newindex = function (t,k,v)
 	if     k == 'file' then t.config(v)
 	elseif k == 'auto' then t.set_auto(v)
+	elseif k == 'negative' then t.set_insecure(v)
 	else   rawset(t, k, v) end
 	end,
 })
diff --git a/lib/dnssec/ta.c b/lib/dnssec/ta.c
index e39eef8045b6bb35cfc0512e666382f847cc679f..2ecb1229202a58eb8d453ab80f861400e90472d0 100644
--- a/lib/dnssec/ta.c
+++ b/lib/dnssec/ta.c
@@ -30,7 +30,7 @@ knot_rrset_t *kr_ta_get(map_t *trust_anchors, const knot_dname_t *name)
 int kr_ta_add(map_t *trust_anchors, const knot_dname_t *name, uint16_t type,
                uint32_t ttl, const uint8_t *rdata, uint16_t rdlen)
 {
-	if (!trust_anchors || !name || !rdata) {
+	if (!trust_anchors || !name) {
 		return kr_error(EINVAL);
 	}
 
@@ -52,7 +52,7 @@ int kr_ta_add(map_t *trust_anchors, const knot_dname_t *name, uint16_t type,
 		is_new_key = true;
 	}
 	/* Merge-in new key data */
-	if (!ta_rr || knot_rrset_add_rdata(ta_rr, rdata, rdlen, ttl, NULL) != 0) {
+	if (!ta_rr || (rdlen > 0 && knot_rrset_add_rdata(ta_rr, rdata, rdlen, ttl, NULL) != 0)) {
 		knot_rrset_free(&ta_rr, NULL);
 		return kr_error(ENOMEM);
 	}
@@ -82,7 +82,9 @@ int kr_ta_covers(map_t *trust_anchors, const knot_dname_t *name)
 static int del_record(const char *k, void *v, void *ext)
 {
 	knot_rrset_t *ta_rr = v;
-	knot_rrset_free(&ta_rr, NULL);
+	if (ta_rr) {
+		knot_rrset_free(&ta_rr, NULL);
+	}
 	return 0;
 }
 
diff --git a/lib/resolve.c b/lib/resolve.c
index 074cbfe32d73853c6b96adde4be317026bb197de..a9eea382ab3f38a26a670b0a63af2916430e2d39 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -357,12 +357,14 @@ static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot
 {
 	struct kr_rplan *rplan = &request->rplan;
 	map_t *trust_anchors = &request->ctx->trust_anchors;
+	map_t *negative_anchors = &request->ctx->negative_anchors;
 
 	/* The query wasn't resolved from cache,
 	 * now it's the time to look up closest zone cut from cache. */
 	if (qry->flags & QUERY_AWAIT_CUT) {
 		/* Want DNSSEC if it's posible to secure this name (e.g. is covered by any TA) */
-		if (kr_ta_covers(trust_anchors, qry->zone_cut.name)) {
+		if (!kr_ta_covers(negative_anchors, qry->zone_cut.name) &&
+		    kr_ta_covers(trust_anchors, qry->zone_cut.name)) {
 			qry->flags |= QUERY_DNSSEC_WANT;
 		}
 		int ret = ns_fetch_cut(qry, request, (qry->flags & QUERY_DNSSEC_WANT));
@@ -377,6 +379,11 @@ static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot
 		}
 		qry->flags &= ~QUERY_AWAIT_CUT;
 	}
+	/* Disable DNSSEC if it enters NTA. */
+	if (kr_ta_get(negative_anchors, qry->zone_cut.name)){
+		DEBUG_MSG(">< negative TA, going insecure\n");
+		qry->flags &= ~QUERY_DNSSEC_WANT;
+	}
 	/* Enable DNSSEC if enters a new island of trust. */
 	bool want_secured = (qry->flags & QUERY_DNSSEC_WANT);
 	if (!want_secured && kr_ta_get(trust_anchors, qry->zone_cut.name)) {
@@ -388,7 +395,6 @@ static int zone_cut_check(struct kr_request *request, struct kr_query *qry, knot
 		DEBUG_MSG(">< TA: using '%s'\n", qname_str);
 		}
 	}
-	/* @todo Disable DNSSEC if it encounters NTA */
 	if (want_secured && !qry->zone_cut.trust_anchor) {
 		knot_rrset_t *ta_rr = kr_ta_get(trust_anchors, qry->zone_cut.name);
 		qry->zone_cut.trust_anchor = knot_rrset_copy(ta_rr, qry->zone_cut.pool);
diff --git a/lib/resolve.h b/lib/resolve.h
index d185fe48412a4b402f778573511036bacc9ca3a3..deaaa9e18fd09f0dcc87f9266250b500ca874e1e 100644
--- a/lib/resolve.h
+++ b/lib/resolve.h
@@ -86,6 +86,7 @@ struct kr_context
 	uint32_t options;
 	knot_rrset_t *opt_rr;
 	map_t trust_anchors;
+	map_t negative_anchors;
 	struct kr_zonecut root_hints;
 	struct kr_cache cache;
 	kr_nsrep_lru_t *cache_rtt;
diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua
index 84276cb6ee8a2bf250beaf291767baea28ced431..4a8b28c390b60ae31cc1b2be76738cd8d9c35b86 100644
--- a/modules/policy/policy.lua
+++ b/modules/policy/policy.lua
@@ -148,9 +148,7 @@ end
 -- Convert list of string names to domain names
 function policy.to_domains(names)
 	for i, v in ipairs(names) do
-		names[i] = v:gsub('([^.]*%.)', function (x)
-			return string.format('%s%s', string.char(x:len()-1), x:sub(1,-2))
-		end)
+		names[i] = kres.str2dname(v)
 	end
 end
 
diff --git a/tests/pydnstest/scenario.py b/tests/pydnstest/scenario.py
index 29a40d466d41dab25251a94a228674a94efd28ad..eaae8a1e8771d1f5097a07f117bac21db3ed79cf 100644
--- a/tests/pydnstest/scenario.py
+++ b/tests/pydnstest/scenario.py
@@ -330,6 +330,7 @@ class Step:
         # Wait for a response for a reasonable time
         answer = None
         if not self.data[0].is_raw_data_entry:
+            ctx.child_sock.settimeout(1)
             answer, addr = ctx.child_sock.recvfrom(4096)
         # Remember last answer for checking later
         self.raw_answer = answer