{"owner":"MobSF","repo":"Mobile-Security-Framework-MobSF","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# MobSF Agent Guidelines\n\nMobSF is a security analysis platform. Every code path processes attacker-supplied\ninput (APKs, ZIPs, IPAs, manifests) from authenticated but potentially malicious\nusers. Security must be the default, not an afterthought.\n\n---\n\n## Code Quality — Mandatory Before Every Commit\n\nRun lint and fix all errors before finishing any task:\n\n```bash\ntox -e lint\n```\n\nNever leave a task with a non-zero exit code from this command.\n\n---\n\n## Security Architecture\n\nCentralized security helpers live in **`mobsf/MobSF/security.py`**. When adding new\nsecurity checks, prefer adding them there. Some legacy validators still live in\n`mobsf/MobSF/utils.py`; use existing helpers where they are already established.\n\n### Available Security Functions\n\nImport only the helpers needed for the change:\n\n```python\nfrom mobsf.MobSF.security import (\n    # Path safety\n    is_path_traversal,   # Check raw string for .. sequences, absolute paths, URL encoding tricks\n    is_safe_path,        # Containment check after path construction via realpath()\n\n    # Input validation\n    is_attack_pattern,   # Detect shell injection: ;, $(), ||, &&\n    cmd_injection_check, # Detect OS command injection characters\n    is_pipe_or_link,     # Detect symlinks and named FIFOs before reading files\n\n    # Output sanitization\n    sanitize_filename,   # Safe filename for Content-Disposition headers\n    sanitize_for_logging,# Strip newlines and control chars before logging user input\n    sanitize_redirect,   # Allow only relative paths in redirects\n    sanitize_svg,        # Strip XSS vectors from SVG content (bleach-based)\n    clean_filename,      # Windows-safe filename (unicode normalization)\n\n    # Network / SSRF\n    valid_host,          # DNS-resolves host; rejects private/loopback/multicast IPs\n)\n```\n\n---\n\n## Past Vulnerabilities and Insecure Patterns\n\nRead `.github/SECURITY.md` to understand the full history of security issues in this\ncodebase. Use it as a guide for what classes of bugs to watch for and what patterns\nhave been exploited before. When in doubt about whether a pattern is safe, check\nwhether a similar pattern has appeared in the advisory history.\n\n---\n\n## Incomplete Fix Anti-Pattern\n\nThe most common source of security regressions in this codebase is applying a fix to\none code path but not its siblings. Before closing any security fix:\n\n1. Search for all functions or patterns that perform the same operation (e.g., every\n   place that resolves an icon path, every place that extracts an archive entry).\n2. Verify the fix is applied consistently across **all** of them.\n3. Check both the APK binary flow and the source-ZIP flow — they are separate code paths\n   with separate callsites and have diverged in the past.\n\n---\n\n## Input Trust Model\n\n- `request.GET` / `request.POST`: untrusted. Validate with forms or explicit checks;\n  escape on output.\n- File uploads: untrusted. Validate magic bytes, size limits, and extension allowlists.\n- Archive entries (`zip`, `tar`, `ar`): untrusted. Check each entry before extraction.\n- `AndroidManifest.xml` values: untrusted. Treat as attacker-controlled before using\n  them in filesystem operations or rendering them.\n- `Info.plist` values: untrusted. Apply the same treatment as manifest values.\n- `md5` / `hash` URL parameters: semi-trusted only after validation. Always validate\n  with `is_md5()` before using them in paths.\n- Device identifiers: untrusted. Use command-injection checks plus format validation.\n\n---\n\n## Django-Specific Security Features\n\n### Form Validation — The Primary Input Sanitization Layer\n\nPrefer Django forms for new request validation. If a view does not use a form, validate\nevery `request.GET[...]` or `request.POST[...]` value explicitly before using it.\n\nThe project uses a mixin composition pattern. Combine the appropriate mixins rather than\nwriting ad-hoc validation in view code:\n\n```python\n# StaticAnalyzer/forms.py — mixins to compose from\nAttackDetect   # is_path_traversal + extension allowlist on a 'file' param\nAPIChecks      # MD5 format check on a 'hash' param (API mode)\nWebChecks      # MD5 format check on an 'md5' param (HTML mode)\nAndroidChecks  # ChoiceField allowlist for Android scan type\nIOSChecks      # ChoiceField allowlist for iOS scan type\n```\n\nCustom field validators belong in a `clean_<field>()` method that raises\n`forms.ValidationError` on rejection — never return a partial result and check it in\nthe view. `FormUtil.errors_message(form)` produces the standard error envelope to return\nto the caller when `form.is_valid()` is False.\n\n**Use `ChoiceField` for any parameter with a finite set of valid values.** This\neliminates an entire class of injection risk at the form layer with no extra code.\nNever use `CharField` and then manually compare the value against an allowlist in the\nview — let the form do it.\n\n### View Decorators — Apply All Three\n\nViews that handle sensitive operations should use the applicable Django decorators for\nauthentication, authorization, and method restriction:\n\n```python\n@login_required\n@permission_required(Permissions.SCAN)   # or DELETE, SUPPRESS, etc.\n@require_http_methods(['POST'])           # or ['GET'] — never omit this\ndef my_view(request, api=False):\n    ...\n```\n\n- `@login_required` blocks unauthenticated access.\n- `@permission_required` enforces role-based access beyond authentication.\n- `@require_http_methods` rejects wrong HTTP verbs before any logic runs,\n  preventing CSRF-via-GET and other method-confusion issues.\n\n### Template Auto-Escaping\n\nDjango's template engine escapes variables by default. Do **not** use `{% autoescape off %}`\nor the `|safe` filter on any value derived from scan data, manifests, or user input.\nWhen rendering user-controlled strings outside of templates (e.g., in a JSON response\nbuilt by hand), use `django.utils.html.escape()` explicitly.\n\n### ORM — No Raw SQL\n\nUse the Django ORM for all database access. Never use `.raw()` or string-formatted SQL.\nWhen a queryset filter value comes from user input, pass it as a keyword argument\n(the ORM parameterizes it automatically):\n\n```python\n# Correct\nRecentScansDB.objects.filter(MD5=checksum)\n\n# Wrong\nRecentScansDB.objects.raw(f'SELECT * FROM ... WHERE MD5 = \"{checksum}\"')\n```\n\n### CSRF\n\nDjango's `CsrfViewMiddleware` is enabled globally. Do not use `@csrf_exempt` on any\nview that modifies state. API endpoints that accept an `X-Csrftoken` header or use\ntoken-based auth are the only legitimate exception, and that pattern is already\nestablished in the existing API views.\n\n---\n\n## Archive Extraction Safety\n\n### TAR\n\nNever use a hand-rolled name-only check with `os.path.abspath`. The symlink +\nnested-entry combination bypasses it: a symlink member named `escape` passes the\nname check, gets extracted to disk, and then a file member named `escape/pwned.txt`\nis written through the symlink to an arbitrary location.\n\n`os.path.abspath` normalises `..` but does **not** resolve symlinks.\n`os.path.realpath` resolves both — but even `realpath`-based checks that run before\nextraction have a TOCTOU window.\n\nUse Python 3.12's built-in filter instead (MobSF requires `python = \"^3.12\"`):\n\n```python\n# Correct — per-member, type-aware, symlink-aware\ntar.extractall(dest, members=safe_members_generator, filter='data')\n\n# Wrong — abspath-based name check; blind to symlinks\nfor member in tar.getmembers():\n    if not os.path.abspath(join(dest, member.name)).startswith(dest):\n        raise ...\ntar.extractall(dest, members=...)\n```\n\n`filter='data'` rejects: symlinks outside destination, hardlinks outside destination,\nabsolute paths, path traversal, and device files — per member, before extraction.\n\nFor code that must support Python < 3.12, fall back to: skip all symlink and hardlink\nmembers (`member.issym()` / `member.islnk()`), then use `realpath` for the boundary\ncheck, and validate-then-extract per member rather than batch-validate-then-extractall.\n\n### ZIP\n\nPython's `zipfile` module does not create real filesystem symlinks from Unix symlink\nentries — it writes the link target as plain file bytes. The TAR symlink attack does\nnot apply to ZIP extraction. Use `is_path_traversal` + `is_safe_path` for member name\nvalidation and validate per-member before calling `zip_ref.extract(member, dest)`.\n\n---\n\n## Import Conventions\n\nWhen adding new imports, maintain alphabetical order within each import group to satisfy\n`flake8-import-order`. Group order: stdlib → third-party → Django → local MobSF.\n\n---\n\n## Checklist for Any Change That Touches File I/O or User Input\n\n- [ ] Raw input validated with `is_path_traversal` before path construction\n- [ ] Constructed filesystem paths verified with `is_safe_path` when a safe root exists\n- [ ] Symlinks and FIFOs rejected with `is_pipe_or_link` before file reads\n- [ ] Shell arguments passed as a list, not a formatted string\n- [ ] User-controlled strings escaped with `django.utils.html.escape` before rendering\n- [ ] SVG content piped through `sanitize_svg`\n- [ ] Outbound URLs checked with `valid_host`\n- [ ] Redirects wrapped in `sanitize_redirect`\n- [ ] Log statements use `sanitize_for_logging` on any user-derived value\n- [ ] TAR extraction uses `filter='data'` — not a hand-rolled `abspath` check\n- [ ] ZIP extraction validates each member path with `realpath` before `extract()`\n- [ ] Every security guard has `continue` / `return` / `raise` — logging alone is not a guard\n- [ ] Fix applied symmetrically to all equivalent code paths\n- [ ] `tox -e lint` passes with exit code 0\n"},"files":{"AGENTS.md":"# MobSF Agent Guidelines\n\nMobSF is a security analysis platform. Every code path processes attacker-supplied\ninput (APKs, ZIPs, IPAs, manifests) from authenticated but potentially malicious\nusers. Security must be the default, not an afterthought.\n\n---\n\n## Code Quality — Mandatory Before Every Commit\n\nRun lint and fix all errors before finishing any task:\n\n```bash\ntox -e lint\n```\n\nNever leave a task with a non-zero exit code from this command.\n\n---\n\n## Security Architecture\n\nCentralized security helpers live in **`mobsf/MobSF/security.py`**. When adding new\nsecurity checks, prefer adding them there. Some legacy validators still live in\n`mobsf/MobSF/utils.py`; use existing helpers where they are already established.\n\n### Available Security Functions\n\nImport only the helpers needed for the change:\n\n```python\nfrom mobsf.MobSF.security import (\n    # Path safety\n    is_path_traversal,   # Check raw string for .. sequences, absolute paths, URL encoding tricks\n    is_safe_path,        # Containment check after path construction via realpath()\n\n    # Input validation\n    is_attack_pattern,   # Detect shell injection: ;, $(), ||, &&\n    cmd_injection_check, # Detect OS command injection characters\n    is_pipe_or_link,     # Detect symlinks and named FIFOs before reading files\n\n    # Output sanitization\n    sanitize_filename,   # Safe filename for Content-Disposition headers\n    sanitize_for_logging,# Strip newlines and control chars before logging user input\n    sanitize_redirect,   # Allow only relative paths in redirects\n    sanitize_svg,        # Strip XSS vectors from SVG content (bleach-based)\n    clean_filename,      # Windows-safe filename (unicode normalization)\n\n    # Network / SSRF\n    valid_host,          # DNS-resolves host; rejects private/loopback/multicast IPs\n)\n```\n\n---\n\n## Past Vulnerabilities and Insecure Patterns\n\nRead `.github/SECURITY.md` to understand the full history of security issues in this\ncodebase. Use it as a guide for what classes of bugs to watch for and what patterns\nhave been exploited before. When in doubt about whether a pattern is safe, check\nwhether a similar pattern has appeared in the advisory history.\n\n---\n\n## Incomplete Fix Anti-Pattern\n\nThe most common source of security regressions in this codebase is applying a fix to\none code path but not its siblings. Before closing any security fix:\n\n1. Search for all functions or patterns that perform the same operation (e.g., every\n   place that resolves an icon path, every place that extracts an archive entry).\n2. Verify the fix is applied consistently across **all** of them.\n3. Check both the APK binary flow and the source-ZIP flow — they are separate code paths\n   with separate callsites and have diverged in the past.\n\n---\n\n## Input Trust Model\n\n- `request.GET` / `request.POST`: untrusted. Validate with forms or explicit checks;\n  escape on output.\n- File uploads: untrusted. Validate magic bytes, size limits, and extension allowlists.\n- Archive entries (`zip`, `tar`, `ar`): untrusted. Check each entry before extraction.\n- `AndroidManifest.xml` values: untrusted. Treat as attacker-controlled before using\n  them in filesystem operations or rendering them.\n- `Info.plist` values: untrusted. Apply the same treatment as manifest values.\n- `md5` / `hash` URL parameters: semi-trusted only after validation. Always validate\n  with `is_md5()` before using them in paths.\n- Device identifiers: untrusted. Use command-injection checks plus format validation.\n\n---\n\n## Django-Specific Security Features\n\n### Form Validation — The Primary Input Sanitization Layer\n\nPrefer Django forms for new request validation. If a view does not use a form, validate\nevery `request.GET[...]` or `request.POST[...]` value explicitly before using it.\n\nThe project uses a mixin composition pattern. Combine the appropriate mixins rather than\nwriting ad-hoc validation in view code:\n\n```python\n# StaticAnalyzer/forms.py — mixins to compose from\nAttackDetect   # is_path_traversal + extension allowlist on a 'file' param\nAPIChecks      # MD5 format check on a 'hash' param (API mode)\nWebChecks      # MD5 format check on an 'md5' param (HTML mode)\nAndroidChecks  # ChoiceField allowlist for Android scan type\nIOSChecks      # ChoiceField allowlist for iOS scan type\n```\n\nCustom field validators belong in a `clean_<field>()` method that raises\n`forms.ValidationError` on rejection — never return a partial result and check it in\nthe view. `FormUtil.errors_message(form)` produces the standard error envelope to return\nto the caller when `form.is_valid()` is False.\n\n**Use `ChoiceField` for any parameter with a finite set of valid values.** This\neliminates an entire class of injection risk at the form layer with no extra code.\nNever use `CharField` and then manually compare the value against an allowlist in the\nview — let the form do it.\n\n### View Decorators — Apply All Three\n\nViews that handle sensitive operations should use the applicable Django decorators for\nauthentication, authorization, and method restriction:\n\n```python\n@login_required\n@permission_required(Permissions.SCAN)   # or DELETE, SUPPRESS, etc.\n@require_http_methods(['POST'])           # or ['GET'] — never omit this\ndef my_view(request, api=False):\n    ...\n```\n\n- `@login_required` blocks unauthenticated access.\n- `@permission_required` enforces role-based access beyond authentication.\n- `@require_http_methods` rejects wrong HTTP verbs before any logic runs,\n  preventing CSRF-via-GET and other method-confusion issues.\n\n### Template Auto-Escaping\n\nDjango's template engine escapes variables by default. Do **not** use `{% autoescape off %}`\nor the `|safe` filter on any value derived from scan data, manifests, or user input.\nWhen rendering user-controlled strings outside of templates (e.g., in a JSON response\nbuilt by hand), use `django.utils.html.escape()` explicitly.\n\n### ORM — No Raw SQL\n\nUse the Django ORM for all database access. Never use `.raw()` or string-formatted SQL.\nWhen a queryset filter value comes from user input, pass it as a keyword argument\n(the ORM parameterizes it automatically):\n\n```python\n# Correct\nRecentScansDB.objects.filter(MD5=checksum)\n\n# Wrong\nRecentScansDB.objects.raw(f'SELECT * FROM ... WHERE MD5 = \"{checksum}\"')\n```\n\n### CSRF\n\nDjango's `CsrfViewMiddleware` is enabled globally. Do not use `@csrf_exempt` on any\nview that modifies state. API endpoints that accept an `X-Csrftoken` header or use\ntoken-based auth are the only legitimate exception, and that pattern is already\nestablished in the existing API views.\n\n---\n\n## Archive Extraction Safety\n\n### TAR\n\nNever use a hand-rolled name-only check with `os.path.abspath`. The symlink +\nnested-entry combination bypasses it: a symlink member named `escape` passes the\nname check, gets extracted to disk, and then a file member named `escape/pwned.txt`\nis written through the symlink to an arbitrary location.\n\n`os.path.abspath` normalises `..` but does **not** resolve symlinks.\n`os.path.realpath` resolves both — but even `realpath`-based checks that run before\nextraction have a TOCTOU window.\n\nUse Python 3.12's built-in filter instead (MobSF requires `python = \"^3.12\"`):\n\n```python\n# Correct — per-member, type-aware, symlink-aware\ntar.extractall(dest, members=safe_members_generator, filter='data')\n\n# Wrong — abspath-based name check; blind to symlinks\nfor member in tar.getmembers():\n    if not os.path.abspath(join(dest, member.name)).startswith(dest):\n        raise ...\ntar.extractall(dest, members=...)\n```\n\n`filter='data'` rejects: symlinks outside destination, hardlinks outside destination,\nabsolute paths, path traversal, and device files — per member, before extraction.\n\nFor code that must support Python < 3.12, fall back to: skip all symlink and hardlink\nmembers (`member.issym()` / `member.islnk()`), then use `realpath` for the boundary\ncheck, and validate-then-extract per member rather than batch-validate-then-extractall.\n\n### ZIP\n\nPython's `zipfile` module does not create real filesystem symlinks from Unix symlink\nentries — it writes the link target as plain file bytes. The TAR symlink attack does\nnot apply to ZIP extraction. Use `is_path_traversal` + `is_safe_path` for member name\nvalidation and validate per-member before calling `zip_ref.extract(member, dest)`.\n\n---\n\n## Import Conventions\n\nWhen adding new imports, maintain alphabetical order within each import group to satisfy\n`flake8-import-order`. Group order: stdlib → third-party → Django → local MobSF.\n\n---\n\n## Checklist for Any Change That Touches File I/O or User Input\n\n- [ ] Raw input validated with `is_path_traversal` before path construction\n- [ ] Constructed filesystem paths verified with `is_safe_path` when a safe root exists\n- [ ] Symlinks and FIFOs rejected with `is_pipe_or_link` before file reads\n- [ ] Shell arguments passed as a list, not a formatted string\n- [ ] User-controlled strings escaped with `django.utils.html.escape` before rendering\n- [ ] SVG content piped through `sanitize_svg`\n- [ ] Outbound URLs checked with `valid_host`\n- [ ] Redirects wrapped in `sanitize_redirect`\n- [ ] Log statements use `sanitize_for_logging` on any user-derived value\n- [ ] TAR extraction uses `filter='data'` — not a hand-rolled `abspath` check\n- [ ] ZIP extraction validates each member path with `realpath` before `extract()`\n- [ ] Every security guard has `continue` / `return` / `raise` — logging alone is not a guard\n- [ ] Fix applied symmetrically to all equivalent code paths\n- [ ] `tox -e lint` passes with exit code 0\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# MobSF Agent Guidelines\n\nMobSF is a security analysis platform. Every code path processes attacker-supplied\ninput (APKs, ZIPs, IPAs, manifests) from authenticated but potentially malicious\nusers. Security must be the default, not an afterthought.\n\n---\n\n## Code Quality — Mandatory Before Every Commit\n\nRun lint and fix all errors before finishing any task:\n\n```bash\ntox -e lint\n```\n\nNever leave a task with a non-zero exit code from this command.\n\n---\n\n## Security Architecture\n\nCentralized security helpers live in **`mobsf/MobSF/security.py`**. When adding new\nsecurity checks, prefer adding them there. Some legacy validators still live in\n`mobsf/MobSF/utils.py`; use existing helpers where they are already established.\n\n### Available Security Functions\n\nImport only the helpers needed for the change:\n\n```python\nfrom mobsf.MobSF.security import (\n    # Path safety\n    is_path_traversal,   # Check raw string for .. sequences, absolute paths, URL encoding tricks\n    is_safe_path,        # Containment check after path construction via realpath()\n\n    # Input validation\n    is_attack_pattern,   # Detect shell injection: ;, $(), ||, &&\n    cmd_injection_check, # Detect OS command injection characters\n    is_pipe_or_link,     # Detect symlinks and named FIFOs before reading files\n\n    # Output sanitization\n    sanitize_filename,   # Safe filename for Content-Disposition headers\n    sanitize_for_logging,# Strip newlines and control chars before logging user input\n    sanitize_redirect,   # Allow only relative paths in redirects\n    sanitize_svg,        # Strip XSS vectors from SVG content (bleach-based)\n    clean_filename,      # Windows-safe filename (unicode normalization)\n\n    # Network / SSRF\n    valid_host,          # DNS-resolves host; rejects private/loopback/multicast IPs\n)\n```\n\n---\n\n## Past Vulnerabilities and Insecure Patterns\n\nRead `.github/SECURITY.md` to understand the full history of security issues in this\ncodebase. Use it as a guide for what classes of bugs to watch for and what patterns\nhave been exploited before. When in doubt about whether a pattern is safe, check\nwhether a similar pattern has appeared in the advisory history.\n\n---\n\n## Incomplete Fix Anti-Pattern\n\nThe most common source of security regressions in this codebase is applying a fix to\none code path but not its siblings. Before closing any security fix:\n\n1. Search for all functions or patterns that perform the same operation (e.g., every\n   place that resolves an icon path, every place that extracts an archive entry).\n2. Verify the fix is applied consistently across **all** of them.\n3. Check both the APK binary flow and the source-ZIP flow — they are separate code paths\n   with separate callsites and have diverged in the past.\n\n---\n\n## Input Trust Model\n\n- `request.GET` / `request.POST`: untrusted. Validate with forms or explicit checks;\n  escape on output.\n- File uploads: untrusted. Validate magic bytes, size limits, and extension allowlists.\n- Archive entries (`zip`, `tar`, `ar`): untrusted. Check each entry before extraction.\n- `AndroidManifest.xml` values: untrusted. Treat as attacker-controlled before using\n  them in filesystem operations or rendering them.\n- `Info.plist` values: untrusted. Apply the same treatment as manifest values.\n- `md5` / `hash` URL parameters: semi-trusted only after validation. Always validate\n  with `is_md5()` before using them in paths.\n- Device identifiers: untrusted. Use command-injection checks plus format validation.\n\n---\n\n## Django-Specific Security Features\n\n### Form Validation — The Primary Input Sanitization Layer\n\nPrefer Django forms for new request validation. If a view does not use a form, validate\nevery `request.GET[...]` or `request.POST[...]` value explicitly before using it.\n\nThe project uses a mixin composition pattern. Combine the appropriate mixins rather than\nwriting ad-hoc validation in view code:\n\n```python\n# StaticAnalyzer/forms.py — mixins to compose from\nAttackDetect   # is_path_traversal + extension allowlist on a 'file' param\nAPIChecks      # MD5 format check on a 'hash' param (API mode)\nWebChecks      # MD5 format check on an 'md5' param (HTML mode)\nAndroidChecks  # ChoiceField allowlist for Android scan type\nIOSChecks      # ChoiceField allowlist for iOS scan type\n```\n\nCustom field validators belong in a `clean_<field>()` method that raises\n`forms.ValidationError` on rejection — never return a partial result and check it in\nthe view. `FormUtil.errors_message(form)` produces the standard error envelope to return\nto the caller when `form.is_valid()` is False.\n\n**Use `ChoiceField` for any parameter with a finite set of valid values.** This\neliminates an entire class of injection risk at the form layer with no extra code.\nNever use `CharField` and then manually compare the value against an allowlist in the\nview — let the form do it.\n\n### View Decorators — Apply All Three\n\nViews that handle sensitive operations should use the applicable Django decorators for\nauthentication, authorization, and method restriction:\n\n```python\n@login_required\n@permission_required(Permissions.SCAN)   # or DELETE, SUPPRESS, etc.\n@require_http_methods(['POST'])           # or ['GET'] — never omit this\ndef my_view(request, api=False):\n    ...\n```\n\n- `@login_required` blocks unauthenticated access.\n- `@permission_required` enforces role-based access beyond authentication.\n- `@require_http_methods` rejects wrong HTTP verbs before any logic runs,\n  preventing CSRF-via-GET and other method-confusion issues.\n\n### Template Auto-Escaping\n\nDjango's template engine escapes variables by default. Do **not** use `{% autoescape off %}`\nor the `|safe` filter on any value derived from scan data, manifests, or user input.\nWhen rendering user-controlled strings outside of templates (e.g., in a JSON response\nbuilt by hand), use `django.utils.html.escape()` explicitly.\n\n### ORM — No Raw SQL\n\nUse the Django ORM for all database access. Never use `.raw()` or string-formatted SQL.\nWhen a queryset filter value comes from user input, pass it as a keyword argument\n(the ORM parameterizes it automatically):\n\n```python\n# Correct\nRecentScansDB.objects.filter(MD5=checksum)\n\n# Wrong\nRecentScansDB.objects.raw(f'SELECT * FROM ... WHERE MD5 = \"{checksum}\"')\n```\n\n### CSRF\n\nDjango's `CsrfViewMiddleware` is enabled globally. Do not use `@csrf_exempt` on any\nview that modifies state. API endpoints that accept an `X-Csrftoken` header or use\ntoken-based auth are the only legitimate exception, and that pattern is already\nestablished in the existing API views.\n\n---\n\n## Archive Extraction Safety\n\n### TAR\n\nNever use a hand-rolled name-only check with `os.path.abspath`. The symlink +\nnested-entry combination bypasses it: a symlink member named `escape` passes the\nname check, gets extracted to disk, and then a file member named `escape/pwned.txt`\nis written through the symlink to an arbitrary location.\n\n`os.path.abspath` normalises `..` but does **not** resolve symlinks.\n`os.path.realpath` resolves both — but even `realpath`-based checks that run before\nextraction have a TOCTOU window.\n\nUse Python 3.12's built-in filter instead (MobSF requires `python = \"^3.12\"`):\n\n```python\n# Correct — per-member, type-aware, symlink-aware\ntar.extractall(dest, members=safe_members_generator, filter='data')\n\n# Wrong — abspath-based name check; blind to symlinks\nfor member in tar.getmembers():\n    if not os.path.abspath(join(dest, member.name)).startswith(dest):\n        raise ...\ntar.extractall(dest, members=...)\n```\n\n`filter='data'` rejects: symlinks outside destination, hardlinks outside destination,\nabsolute paths, path traversal, and device files — per member, before extraction.\n\nFor code that must support Python < 3.12, fall back to: skip all symlink and hardlink\nmembers (`member.issym()` / `member.islnk()`), then use `realpath` for the boundary\ncheck, and validate-then-extract per member rather than batch-validate-then-extractall.\n\n### ZIP\n\nPython's `zipfile` module does not create real filesystem symlinks from Unix symlink\nentries — it writes the link target as plain file bytes. The TAR symlink attack does\nnot apply to ZIP extraction. Use `is_path_traversal` + `is_safe_path` for member name\nvalidation and validate per-member before calling `zip_ref.extract(member, dest)`.\n\n---\n\n## Import Conventions\n\nWhen adding new imports, maintain alphabetical order within each import group to satisfy\n`flake8-import-order`. Group order: stdlib → third-party → Django → local MobSF.\n\n---\n\n## Checklist for Any Change That Touches File I/O or User Input\n\n- [ ] Raw input validated with `is_path_traversal` before path construction\n- [ ] Constructed filesystem paths verified with `is_safe_path` when a safe root exists\n- [ ] Symlinks and FIFOs rejected with `is_pipe_or_link` before file reads\n- [ ] Shell arguments passed as a list, not a formatted string\n- [ ] User-controlled strings escaped with `django.utils.html.escape` before rendering\n- [ ] SVG content piped through `sanitize_svg`\n- [ ] Outbound URLs checked with `valid_host`\n- [ ] Redirects wrapped in `sanitize_redirect`\n- [ ] Log statements use `sanitize_for_logging` on any user-derived value\n- [ ] TAR extraction uses `filter='data'` — not a hand-rolled `abspath` check\n- [ ] ZIP extraction validates each member path with `realpath` before `extract()`\n- [ ] Every security guard has `continue` / `return` / `raise` — logging alone is not a guard\n- [ ] Fix applied symmetrically to all equivalent code paths\n- [ ] `tox -e lint` passes with exit code 0\n","category":"root","tokens":2376}]}