Is Django safe?
- Unsafe deserialization
- Python dynamic code execution
- Python shell/command execution
Django is an AI python_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 7 risky constructs are reported for review. It can: dynamic code execution, filesystem read, filesystem write, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 30/100 (medium).
Django 6.0.6
Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of Django's authors. Report a false positive.
Findings (7)
It loads data with a format that can rebuild arbitrary objects (e.g. pickle, or unsafe YAML).
value = pickle.loads(base64.b64decode(value.encode()))
return pickle.loads(zlib.decompress(f.read()))
previous_value = pickle.loads(zlib.decompress(f.read()))
exp = pickle.load(f)
return pickle.loads(pickled)
value = pickle.loads(pickled)
return pickle.loads(data)
Why it matters: Feeding such a loader untrusted data can execute code hidden inside that data.
Fix: Deserialize untrusted data with a safe format/loader: JSON, or yaml.safe_load / Loader=SafeLoader. Reserve pickle/marshal for data you fully control.
The code turns strings into live code at runtime (eval / new Function / exec).
exec(compile(pythonrc_code, pythonrc, "exec"), imported_objects)
exec(options["command"], {**globals(), **self.get_namespace(**options)})exec(sys.stdin.read(), {**globals(), **self.get_namespace(**options)})return eval(code, {}, {"datetime": datetime, "timezone": timezone})Why it matters: If those strings aren't fixed and trusted, they become a way to run arbitrary code.
Fix: Avoid evaluating dynamically constructed code; if unavoidable, ensure the input is a trusted constant and never derived from external data.
The component can run operating-system commands or spawn processes.
p = run(args, capture_output=True, close_fds=os.name != "nt")
subprocess.run(
[black_path, "--fast", "--", *written_files],
capture_output=True,
)subprocess.run(args, env=env, check=True)
subprocess.Popen(
dump_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=dump_env
) as dump_proc,subprocess.Popen(
load_cmd,
stdin=dump_proc.stdout,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
env=load_env,
) as load_proc,p = subprocess.run(args, env=new_environ, close_fds=False)
git_log = subprocess.run(
"git log --pretty=format:%ct --quiet -1 HEAD",
capture_output=True,
shell=True,
cwd=repo_dir,
text=True,
)Why it matters: Powerful and often legitimate — confirm the commands aren't built from untrusted input.
Fix: Confirm the command and its arguments are fully controlled and not derived from untrusted input; avoid shell=True.
A server is bound to all network interfaces (0.0.0.0), not just your own machine.
addr = "0.0.0.0"
Why it matters: Without authentication, other hosts on the network can reach it.
Fix: Bind to 127.0.0.1 for local-only use, or require authentication and restrict access if remote exposure is intended.
The component reads files from disk.
template_contents = template_file.read_text()
with open(password_list_path) as f:
with open(file_h, "rb") as file_h:
with open(self._key_to_file(), encoding="ascii") as session_file:
with open(fname, "rb") as f:
with open(fname, "rb") as f:
self.file = open(self.name, mode or self.mode, *args, **kwargs)
file = open(file_or_path, "rb")
with open(old_file_name, "rb") as old_file:
return File(open(self.path(name), mode))
return super().open(mode)
with open(self.path, encoding="utf-8") as fp:
msgs = Path(pofile).read_text(encoding="utf-8")
with open(potfile, encoding="utf-8") as fp:
with open(django_po, encoding="utf-8") as fp:
with open(pythonrc) as handle:
with open(old_path, encoding="utf-8") as template_file:
with open(template_file, encoding="utf-8") as fp:
source = exception_file.read_text()
with open(origin.name, encoding=self.engine.file_charset) as fp:
with builtin_template_path("csrf_403.html").open(encoding="utf-8") as fh:with open(filename, "rb") as fp:
with builtin_template_path("technical_404.html").open(encoding="utf-8") as fh:with builtin_template_path("default_urlconf.html").open(encoding="utf-8") as fh:Why it matters: Usually legitimate, but worth confirming it can't be steered into reading sensitive files.
Fix: Confirm which files are read and that paths cannot be influenced by untrusted input to reach sensitive locations.
The component writes or deletes files on disk.
shutil.move(output_file_name, session_file_name)
os.unlink(output_file_name)
os.unlink(self._key_to_file(session_key))
os.unlink(full_path)
os.unlink(full_path)
with open(fd, "wb") as f:
os.remove(tmp_path)
with open(self._key_to_file(key, version), "r+b") as f:
os.remove(fname)
copystat(old_file_name, new_file_name)
copymode(old_file_name, new_file_name)
os.remove(old_file_name)
os.remove(name)
os.remove(content.temporary_file_path())
os.remove(temp_location)
self.stream = open(self._get_filename(), "ab")
with open(self.work_path, "w", encoding="utf-8") as fp:
os.unlink(self.work_path)
with open(potfile, "a", encoding="utf-8", newline="\n") as fp:
with open(potfile, "w", encoding="utf-8") as fp:
os.unlink(pot_path)
with open(pofile, "w", encoding="utf-8") as fp:
open(init_path, "w").close()
with open(writer.path, "w", encoding="utf-8") as fh:
os.remove(prev_path)
Why it matters: Usually legitimate, but worth confirming the paths can't be controlled by untrusted input.
Fix: Confirm which files are written/deleted and that paths cannot be influenced by untrusted input.
The component makes outbound network requests.
from urllib.parse import urlsplit
elif not settings.DEBUG or urlsplit(prefix).netloc:
from urllib.parse import parse_qsl
from urllib.parse import quote as urlquote
from urllib.parse import urlsplit
query_string = urlsplit(request.build_absolute_uri()).query
return parse_qsl(query_string.replace(preserved_filters, ""))
obj_repr = format_html('<a href="{}">{}</a>', urlquote(obj_url), obj)"obj": format_html('<a href="{}">{}</a>', urlquote(request.path), obj),from urllib.parse import parse_qsl, unquote, urlsplit, urlunsplit
parsed_url = list(urlsplit(url))
parsed_qs = dict(parse_qsl(parsed_url[3]))
preserved_filters = dict(parse_qsl(preserved_filters))
match_url = "/%s" % unquote(url).partition(get_script_prefix())[2]
parse_qsl(preserved_filters["_changelist_filters"])
return urlunsplit(parsed_url)
from urllib.parse import urlsplit
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
current_scheme, current_netloc = urlsplit(path)[:2]
from urllib.parse import urlsplit
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
current_scheme, current_netloc = urlsplit(path)[:2]
from urllib.parse import urlsplit
login_scheme, login_netloc = urlsplit(resolved_login_url)[:2]
current_scheme, current_netloc = urlsplit(path)[:2]
Why it matters: Usually legitimate, but confirm the destinations are expected and no sensitive data leaves.
Fix: Confirm the destination hosts are expected and that no sensitive data is sent off-host.
Check your own component
Run the same evidence-backed scan on any MCP server, agent skill, or package.
Scan your own componentOr get notified if this component's risk changes:
How we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →