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
- Add
@login_required and @permission_required decorators to status_change() and edit_job_title() in dashboard views, consistent with adjacent functions.
- Replace
os.system() with subprocess.run() using argument lists (no shell) for resume parsing.
- Add company-level ownership checks to all agency views (
company=request.user.company).
- Add user-level ownership checks to all candidate profile edit/delete views (
user=request.user).
- Add
@login_required and ownership verification to job alert modification views.
- 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.
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 — CRITICALFile:
backend/dashboard/views/job_management.py:131The
status_change()view has no@login_requiredand no@permission_requireddecorator. Any user (including unauthenticated users, depending on middleware configuration) can toggle any job post between Live and Expired status.Adjacent functions
deactivate_job()anddelete_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 — CRITICALFile:
backend/dashboard/views/job_management.py:710The
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-223The
get_resume_data()function passes the uploaded resume filename directly into anos.system()call: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:applicant_status_change()agency/views.py:325delete_applicant_status()agency/views.py:336view_resumes()agency/views.py:347job_status_change()agency/views.py:430delete_resume()agency/views.py:262Recommended fix: Filter all queries by
company=request.user.companyor 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.pyedit_experience()/delete_experience()—backend/candidate/views/experience_views.pyedit_project()/delete_project()—backend/candidate/views/project_views.pyedit_technicalskill()/delete_technicalskill()—backend/candidate/views/skill_views.pyRecommended fix: Add
user=request.userto all.get()/.filter()queries on user-owned resources.6. Job Alert Missing Auth + IDOR (2 instances)
File:
backend/candidate/views/alert_views.py:209,327modify_job_alert()anddelete_job_alert()have no@login_requireddecorator 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:229edit_email()fetches aUserEmailobject by the POST-supplied ID without checkinguser=request.user. Any logged-in user can edit any other user's email address.Summary
Recommended Remediation
@login_requiredand@permission_requireddecorators tostatus_change()andedit_job_title()in dashboard views, consistent with adjacent functions.os.system()withsubprocess.run()using argument lists (no shell) for resume parsing.company=request.user.company).user=request.user).@login_requiredand ownership verification to job alert modification views.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.