security: untrack DataProtection keys and runtime exports; remove dead legacy controllers

- git rm --cached on committed DataProtection key XMLs (keys/, JobTrackerApi/keys/)
  and daily export JSON snapshots; extend .gitignore so runtime data
  (keys, exports, CV artifacts/exports/benchmarks) can never be committed again.
- Delete root Controller/ stubs: an early prototype compiled by no project
  (JobTrackerApi excludes them; JobTrackerBackend globs only JobTrackerApi/Controllers).
- NOTE: the removed key XMLs remain in git history; rotating DataProtection
  keys on the server is recommended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-02 21:23:50 +02:00
parent aa43ada16a
commit 519c32efd7
9 changed files with 7 additions and 359 deletions
+7
View File
@@ -46,6 +46,13 @@ todo jobtracker.txt
tmp/
/tmp/
# Runtime data that must never be committed (DataProtection keys, exports, CV artifacts)
keys/
JobTrackerApi/exports/
JobTrackerApi/CvArtifacts/
JobTrackerApi/CvExports/
JobTrackerApi/CvBenchmarks/
# Local app data
*.db
*.db-*
-23
View File
@@ -1,23 +0,0 @@
[ApiController]
[Route("api/[controller]")]
public class AttachmentsController : ControllerBase
{
private readonly IWebHostEnvironment _env;
public AttachmentsController(IWebHostEnvironment env) => _env = env;
[HttpPost]
public async Task<IActionResult> Upload([FromForm] IFormFileCollection files, [FromForm] int jobId)
{
var folder = Path.Combine(_env.ContentRootPath, "Attachments", jobId.ToString());
Directory.CreateDirectory(folder);
foreach (var file in files)
{
var path = Path.Combine(folder, file.FileName);
using var stream = new FileStream(path, FileMode.Create);
await file.CopyToAsync(stream);
}
return Ok();
}
}
-27
View File
@@ -1,27 +0,0 @@
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace JobTrackerApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class CompaniesController : ControllerBase
{
private readonly JobTrackerContext _context;
public CompaniesController(JobTrackerContext context) => _context = context;
[HttpGet]
public async Task<IEnumerable<Company>> Get() =>
await _context.Companies.Include(c => c.Jobs).ToListAsync();
[HttpPost]
public async Task<ActionResult<Company>> Post(Company company)
{
_context.Companies.Add(company);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(Get), new { id = company.Id }, company);
}
}
}
-34
View File
@@ -1,34 +0,0 @@
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace JobTrackerApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class CorrespondenceController : ControllerBase
{
private readonly JobTrackerContext _context;
public CorrespondenceController(JobTrackerContext context) => _context = context;
// GET all messages for a job
[HttpGet("{jobId}")]
public async Task<IEnumerable<Correspondence>> GetForJob(int jobId)
{
return await _context.Correspondences
.Where(c => c.JobApplicationId == jobId)
.OrderBy(c => c.Date)
.ToListAsync();
}
// POST new message
[HttpPost]
public async Task<ActionResult<Correspondence>> Post(Correspondence message)
{
_context.Correspondences.Add(message);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetForJob), new { jobId = message.JobApplicationId }, message);
}
}
}
-45
View File
@@ -1,45 +0,0 @@
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace JobTrackerApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class JobApplicationsController : ControllerBase
{
private readonly JobTrackerContext _context;
public JobApplicationsController(JobTrackerContext context) => _context = context;
[HttpGet]
public async Task<IEnumerable<JobApplication>> Get() =>
await _context.JobApplications.Include(j => j.Company).ToListAsync();
[HttpPost]
public async Task<ActionResult<JobApplication>> Post(JobApplication job)
{
_context.JobApplications.Add(job);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(Get), new { id = job.Id }, job);
}
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, JobApplication updatedJob)
{
var job = await _context.JobApplications.FindAsync(id);
if (job == null) return NotFound();
job.JobTitle = updatedJob.JobTitle;
job.Status = updatedJob.Status;
job.ResponseReceived = updatedJob.ResponseReceived;
job.ResponseDate = updatedJob.ResponseDate;
job.Notes = updatedJob.Notes;
job.CoverLetterText = updatedJob.CoverLetterText;
job.JobUrl = updatedJob.JobUrl;
await _context.SaveChangesAsync();
return NoContent();
}
}
}
@@ -1,99 +0,0 @@
{
"Version": "dailyexport.v1",
"CreatedAt": "2026-03-25T02:00:00.0368687+01:00",
"Companies": [
{
"Id": 1,
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
"Name": "Acme Browser QA",
"Location": null,
"Source": null,
"RecruiterName": "Maria Recruiter",
"RecruiterEmail": "maria@acme.test",
"RecruiterLinkedIn": null,
"LastContactedAt": "2026-03-24T11:15:21.4772436",
"NextContactAt": "2026-03-24T00:00:00",
"PipelineStage": null
}
],
"JobApplications": [
{
"Id": 1,
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
"JobTitle": "Backend Developer",
"CompanyId": 1,
"Company": null,
"Status": "Waiting",
"DateApplied": "2026-03-01T13:00:00+01:00",
"Location": null,
"Salary": null,
"NextAction": null,
"FollowUpAt": "2026-03-24T00:00:00",
"FeedbackRequestedAt": null,
"RecruiterMessageDraft": "Saved browser recruiter message",
"HasResume": true,
"HasCoverLetter": true,
"HasPortfolio": false,
"HasOtherAttachment": false,
"IsDeleted": false,
"DeletedAt": null,
"ResponseReceived": true,
"ResponseDate": null,
"Notes": "Browser-seeded notes\n\n\u003C\u003C\u003CAPPLICATION_ANSWER_DRAFT\u003E\u003E\u003E\nSaved browser application answer\n\u003C\u003C\u003CEND_APPLICATION_ANSWER_DRAFT\u003E\u003E\u003E",
"CoverLetterText": "Saved browser cover letter",
"JobUrl": "https://example.test/backend-developer",
"Description": "Need .NET APIs and strong stakeholder communication.",
"TranslatedDescription": null,
"DescriptionLanguage": null,
"Tags": "[\u0022.NET\u0022, \u0022APIs\u0022, \u0022Communication\u0022]",
"Deadline": null,
"ShortSummary": "Strong overlap in backend API delivery.",
"TailoredCvText": "Saved browser tailored CV",
"TailoredCvUpdatedAt": "2026-03-24T10:58:13.226164+01:00",
"LastReminderEmailSentAt": null,
"Messages": [],
"Attachments": [],
"Events": [],
"DaysSince": 23
}
],
"Correspondence": [
{
"Id": 1,
"JobApplicationId": 1,
"From": "Company",
"Subject": "Backend Developer application update",
"Channel": "Email",
"ExternalMessageId": "browser-msg-1",
"ExternalThreadId": "browser-thread-1",
"ExternalFrom": "Maria Recruiter \u003Cmaria@acme.test\u003E",
"ExternalTo": "admin@example.com",
"Content": "We are aligning interview slots and need someone who can own the API layer.",
"Date": "2026-03-10T10:00:00+01:00"
},
{
"Id": 2,
"JobApplicationId": 1,
"From": "Me",
"Subject": "Re: Backend Developer application update",
"Channel": "Email",
"ExternalMessageId": null,
"ExternalThreadId": null,
"ExternalFrom": null,
"ExternalTo": null,
"Content": "Hi Maria,\n\nEdited browser follow-up.\n\nThanks,\nadmin@example.com",
"Date": "2026-03-24T11:15:21.4521755"
}
],
"Attachments": [],
"Events": [],
"Rules": {
"Id": 1,
"AppliedFollowUpDays": 14,
"AppliedGhostDays": 30,
"OfferFollowUpDays": 7,
"OfferGhostDays": 14,
"FeedbackFollowUpDays": 7,
"FeedbackGhostDays": 14
}
}
@@ -1,99 +0,0 @@
{
"Version": "dailyexport.v1",
"CreatedAt": "2026-03-26T02:00:00.005823+01:00",
"Companies": [
{
"Id": 1,
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
"Name": "Acme Browser QA",
"Location": null,
"Source": null,
"RecruiterName": "Maria Recruiter",
"RecruiterEmail": "maria@acme.test",
"RecruiterLinkedIn": null,
"LastContactedAt": "2026-03-24T11:15:21.4772436",
"NextContactAt": "2026-03-24T00:00:00",
"PipelineStage": null
}
],
"JobApplications": [
{
"Id": 1,
"OwnerUserId": "23dc196b-f227-4499-93fe-403d8801e21c",
"JobTitle": "Backend Developer",
"CompanyId": 1,
"Company": null,
"Status": "Waiting",
"DateApplied": "2026-03-01T13:00:00+01:00",
"Location": null,
"Salary": null,
"NextAction": null,
"FollowUpAt": "2026-03-24T00:00:00",
"FeedbackRequestedAt": null,
"RecruiterMessageDraft": "Saved browser recruiter message",
"HasResume": true,
"HasCoverLetter": true,
"HasPortfolio": false,
"HasOtherAttachment": false,
"IsDeleted": false,
"DeletedAt": null,
"ResponseReceived": true,
"ResponseDate": null,
"Notes": "Browser-seeded notes\n\n\u003C\u003C\u003CAPPLICATION_ANSWER_DRAFT\u003E\u003E\u003E\nSaved browser application answer\n\u003C\u003C\u003CEND_APPLICATION_ANSWER_DRAFT\u003E\u003E\u003E",
"CoverLetterText": "Saved browser cover letter",
"JobUrl": "https://example.test/backend-developer",
"Description": "Need .NET APIs and strong stakeholder communication.",
"TranslatedDescription": null,
"DescriptionLanguage": null,
"Tags": "[\u0022.NET\u0022, \u0022APIs\u0022, \u0022Communication\u0022]",
"Deadline": null,
"ShortSummary": "Strong overlap in backend API delivery.",
"TailoredCvText": "Saved browser tailored CV",
"TailoredCvUpdatedAt": "2026-03-24T10:58:13.226164+01:00",
"LastReminderEmailSentAt": null,
"Messages": [],
"Attachments": [],
"Events": [],
"DaysSince": 24
}
],
"Correspondence": [
{
"Id": 1,
"JobApplicationId": 1,
"From": "Company",
"Subject": "Backend Developer application update",
"Channel": "Email",
"ExternalMessageId": "browser-msg-1",
"ExternalThreadId": "browser-thread-1",
"ExternalFrom": "Maria Recruiter \u003Cmaria@acme.test\u003E",
"ExternalTo": "admin@example.com",
"Content": "We are aligning interview slots and need someone who can own the API layer.",
"Date": "2026-03-10T10:00:00+01:00"
},
{
"Id": 2,
"JobApplicationId": 1,
"From": "Me",
"Subject": "Re: Backend Developer application update",
"Channel": "Email",
"ExternalMessageId": null,
"ExternalThreadId": null,
"ExternalFrom": null,
"ExternalTo": null,
"Content": "Hi Maria,\n\nEdited browser follow-up.\n\nThanks,\nadmin@example.com",
"Date": "2026-03-24T11:15:21.4521755"
}
],
"Attachments": [],
"Events": [],
"Rules": {
"Id": 1,
"AppliedFollowUpDays": 14,
"AppliedGhostDays": 30,
"OfferFollowUpDays": 7,
"OfferGhostDays": 14,
"FeedbackFollowUpDays": 7,
"FeedbackGhostDays": 14
}
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<key id="9a89a42c-d2bd-4770-83fb-5930685432db" version="1">
<creationDate>2026-03-24T09:54:28.8487759Z</creationDate>
<activationDate>2026-03-24T09:54:28.8487759Z</activationDate>
<expirationDate>2026-06-22T09:54:28.8487759Z</expirationDate>
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
<descriptor>
<encryption algorithm="AES_256_CBC" />
<validation algorithm="HMACSHA256" />
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
<!-- Warning: the key below is in an unencrypted form. -->
<value>LXbXqbpiEXn0OM6fr/TuXDBcZd83DvOInTI09PGZRr1Z20LQCD/PUKF1oo9UwC4O1VgK3wA//yxH9PPCIPzEaw==</value>
</masterKey>
</descriptor>
</descriptor>
</key>
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<key id="b3ca4672-1056-4ac2-ba47-0432608a4115" version="1">
<creationDate>2026-03-27T07:52:25.0540436Z</creationDate>
<activationDate>2026-03-27T07:52:25.0540436Z</activationDate>
<expirationDate>2026-06-25T07:52:25.0540436Z</expirationDate>
<descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
<descriptor>
<encryption algorithm="AES_256_CBC" />
<validation algorithm="HMACSHA256" />
<masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
<!-- Warning: the key below is in an unencrypted form. -->
<value>mfglwuKFrMSiWcbTVDEbPYM0eGAqlsOMHe89hNOsZUguUMMiusdx3m3ZQJvxnBCxeXte6OS+zvpZl3tIizvgHg==</value>
</masterKey>
</descriptor>
</descriptor>
</key>