Skip to content

Security: Missing Auth on Dashboard Views, Command Injection via Resume, and Systemic Cross-Company IDOR #226

@lighthousekeeper1212

Description

@lighthousekeeper1212

Security Disclosure — Authorized Audit Findings

This report covers approximately 18 security vulnerabilities identified during an authorized security audit of this repository. Findings range from critical (unauthenticated job post takeover, command injection) to high (systemic cross-company IDOR across agency and candidate views).

These follow the same patterns found in Django-CRM from the same organization. Notably, the newer API v1 endpoints and modal views are properly secured — the issues are concentrated in legacy view functions.


1. Dashboard status_change() Missing All Auth — CRITICAL

File: backend/dashboard/views/job_management.py:131

The status_change() view has no @login_required and no @permission_required decorator. Any user (including unauthenticated users, depending on middleware configuration) can toggle any job post between Live and Expired status.

Adjacent functions deactivate_job() and delete_job() correctly use @permission_required("activity_edit"), making this a classic 1-of-N inconsistency.

Impact: Unauthenticated job post status manipulation.


2. Dashboard edit_job_title() Missing All Auth — CRITICAL

File: backend/dashboard/views/job_management.py:710

The edit_job_title() view has no authentication or authorization decorators. Any user can edit any job post's title, description, status, company, skills, salary, and locations — full job post takeover.

Impact: Complete unauthenticated job post takeover.


3. Command Injection via Resume Filename — HIGH/CRITICAL

File: backend/mpcomp/views.py:218-223

The get_resume_data() function passes the uploaded resume filename directly into an os.system() call:

os.system("pdftotext '%s' '%s'" % (resume_path, text_path))

A single-quote escape in the filename (e.g., resume'; whoami; '.pdf) breaks out of the quoted string and enables arbitrary command execution on the server.

Recommended fix: Use subprocess.run(["pdftotext", resume_path, text_path]) with a list of arguments to avoid shell interpretation entirely.

Impact: Remote code execution via crafted resume upload.


4. Cross-Company Recruiter IDOR — HIGH (5 instances)

All agency views use @recruiter_login_required (which verifies the user is a recruiter) but do not verify that the target resource belongs to the recruiter's company. Any recruiter from any company can operate on any other company's data:

View File Impact
applicant_status_change() agency/views.py:325 Any recruiter changes any applicant's status
delete_applicant_status() agency/views.py:336 Any recruiter deletes any application
view_resumes() agency/views.py:347 Any recruiter views resumes for any job
job_status_change() agency/views.py:430 Any recruiter marks any job as "Hired"
delete_resume() agency/views.py:262 Agency admin deletes any resume

Recommended fix: Filter all queries by company=request.user.company or equivalent ownership check.


5. Candidate Profile IDOR — HIGH (6+ instances)

The following candidate profile edit/delete views query by object ID without filtering by user=request.user. Any logged-in user can edit or delete any other user's profile data:

  • edit_education() / delete_education()backend/candidate/views/education_views.py
  • edit_experience() / delete_experience()backend/candidate/views/experience_views.py
  • edit_project() / delete_project()backend/candidate/views/project_views.py
  • edit_technicalskill() / delete_technicalskill()backend/candidate/views/skill_views.py

Recommended fix: Add user=request.user to all .get() / .filter() queries on user-owned resources.


6. Job Alert Missing Auth + IDOR (2 instances)

File: backend/candidate/views/alert_views.py:209,327

modify_job_alert() and delete_job_alert() have no @login_required decorator and no ownership check. Any user (including unauthenticated) can modify or delete any user's job alerts.


7. Email IDOR

File: backend/candidate/views/profile_views.py:229

edit_email() fetches a UserEmail object by the POST-supplied ID without checking user=request.user. Any logged-in user can edit any other user's email address.


Summary

Category Count Severity
Missing auth on dashboard views 2 Critical
Command injection (resume filename) 1 High/Critical
Cross-company recruiter IDOR 5 High
Candidate profile IDOR 6+ High
Job alert missing auth + IDOR 2 High
Email IDOR 1 High
Total ~18

Recommended Remediation

  1. Add @login_required and @permission_required decorators to status_change() and edit_job_title() in dashboard views, consistent with adjacent functions.
  2. Replace os.system() with subprocess.run() using argument lists (no shell) for resume parsing.
  3. Add company-level ownership checks to all agency views (company=request.user.company).
  4. Add user-level ownership checks to all candidate profile edit/delete views (user=request.user).
  5. Add @login_required and ownership verification to job alert modification views.
  6. Add ownership check to email edit view.

These are well-understood patterns with straightforward fixes. The newer API v1 code in this repository already implements most of these correctly — the fixes would bring the legacy views to the same standard.


This report was generated as part of an authorized security research project. Findings are disclosed responsibly to help improve the security of open-source software.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions