From 2c41210019f4a4134fe38f87a5e17243950a9a97 Mon Sep 17 00:00:00 2001 From: Mads Lorentzen Date: Fri, 31 Jul 2026 12:55:09 +0200 Subject: [PATCH] fix(security-guards): sync gitignore guard with the Cover_*.* and cv/*.txt rules Two personal-data ignore rules existed in .gitignore but not in REQUIRED_IGNORE_RULES, so a change weakening either would have passed CI: cover_letters/Cover_*.* (the uppercase naming variant /apply recognizes) and cv/*.txt (ATS text extractions of tailored CVs). Also: regression tests pinning #252's ragged-row bounds fix in convert_salary_excel.py (mutation-verified), and removal of the vestigial cover_letters/OpenFonts/cover.cls, which since #252's rename ambiguously declared the same class as the real cover.cls (zero references; cover letter re-compiled and page-verified after removal). Guard-list gap surfaced by CodeRabbit's review on jakob1379's Nix demo fork PR (jakob1379/ai-job-search#1). Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 17 +++++ cover_letters/OpenFonts/cover.cls | 101 ----------------------------- tests/test_convert_salary_excel.py | 33 ++++++++++ tools/security_guards.py | 4 ++ 4 files changed, 54 insertions(+), 101 deletions(-) delete mode 100644 cover_letters/OpenFonts/cover.cls diff --git a/CHANGELOG.md b/CHANGELOG.md index ef65526..f91ca1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,23 @@ per-file diff commands. ## [Unreleased] +### Security & privacy + +- **The gitignore guard now covers two more personal-data rules** - `security_guards.py` + requires `cover_letters/Cover_*.*` (the uppercase cover-letter naming variant `/apply` + recognizes) and `cv/*.txt` (ATS text extractions of tailored CVs) in `.gitignore`, so a + future change weakening either rule fails CI instead of silently making personal files + trackable. Both rules were already present in `.gitignore`; only the guard lagged. + +### Fixed + +- Removed the vestigial `cover_letters/OpenFonts/cover.cls` - an unreferenced remnant of + the original font bundle that, since #252's class rename, ambiguously declared the same + `cover` class as the real `cover_letters/cover.cls`. +- Added regression tests pinning #252's ragged-row bounds fix in + `tools/convert_salary_excel.py` (dimension-less workbooks read in `read_only` mode + yield rows shorter than the header). + ## [1.1.0] - 2026-07-30 ### Security & privacy diff --git a/cover_letters/OpenFonts/cover.cls b/cover_letters/OpenFonts/cover.cls deleted file mode 100644 index 051a1ed..0000000 --- a/cover_letters/OpenFonts/cover.cls +++ /dev/null @@ -1,101 +0,0 @@ -% Intro Options -\ProvidesClass{cover}[2024/04/30 Cover letter class] -\NeedsTeXFormat{LaTeX2e} -\DeclareOption{print}{\def\@cv@print{}} -\DeclareOption*{% - \PassOptionsToClass{\CurrentOption}{article} -} -\ProcessOptions\relax -\LoadClass{article} - -% Package Imports -\usepackage[hmargin=2.54cm, vmargin=2.54cm]{geometry} -\usepackage[hidelinks]{hyperref} -\usepackage[usenames,dvipsnames]{xcolor} -\usepackage{titlesec} -\usepackage[absolute]{textpos} -\usepackage{fontspec,xltxtra,xunicode} - -% Publications -\usepackage{cite} -\renewcommand\refname{\vskip -1.5cm} - -% Color definitions -\usepackage[usenames,dvipsnames]{xcolor} -\definecolor{date}{HTML}{666666} -\definecolor{primary}{HTML}{2b2b2b} -\definecolor{headings}{HTML}{6A6A6A} -\definecolor{subheadings}{HTML}{333333} - -% Set main fonts -\usepackage{fontspec} -\setmainfont[Color=primary, Path = OpenFonts/fonts/lato/,BoldItalicFont=Lato-RegIta,BoldFont=Lato-Reg,ItalicFont=Lato-LigIta]{Lato-Lig} -\setsansfont[Scale=MatchLowercase,Mapping=tex-text, Path = OpenFonts/fonts/raleway/]{Raleway-ExtraLight} - -% Date command -\usepackage[absolute]{textpos} -% \usepackage[UKenglish]{isodate} -\setlength{\TPHorizModule}{1mm} -\setlength{\TPVertModule}{1mm} -\newcommand{\lastupdated}{\begin{textblock}{60}(155,5) -\color{date}\fontspec[Path = fonts/raleway/]{Raleway-ExtraLight}\fontsize{8pt}{10pt}\selectfont -Last Updated on \today -\end{textblock}} - -% Name command -\newcommand{\namesection}[3]{ -\centering{ -\fontsize{40pt}{60pt} -\fontspec[Path = fonts/lato/]{Lato-Hai}\selectfont #1 -\fontspec[Path = fonts/lato/]{Lato-Lig}\selectfont #2 -} \\[5pt] -\centering{ -\color{headings} -\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{14pt}\selectfont #3} -\noindent\makebox[\linewidth]{\color{headings}\rule{\paperwidth}{0.0pt}} -\vspace{0pt} -} - -% Section seperators -\usepackage{titlesec} -\titlespacing{\section}{0pt}{0pt}{0pt} -\titlespacing{\subsection}{0pt}{0pt}{0pt} -\newcommand{\sectionsep}{\vspace{8pt}} - -% Headings command -\titleformat{\section}{\color{headings} -\scshape\fontspec[Path = fonts/lato/]{Lato-Lig}\fontsize{16pt}{24pt}\selectfont \raggedright\uppercase}{}{0em}{} - -% Subeadings command -\titleformat{\subsection}{ -\color{subheadings}\fontspec[Path = fonts/lato/]{Lato-Bol}\fontsize{12pt}{12pt}\selectfont\bfseries\uppercase}{}{0em}{} - -\newcommand{\runsubsection}[1]{ -\color{subheadings}\fontspec[Path = fonts/lato/]{Lato-Bol}\fontsize{12pt}{12pt}\selectfont\bfseries\uppercase {#1} \normalfont} - -% Descriptors command -\newcommand{\descript}[1]{ -\color{subheadings}\raggedright\scshape\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{13pt}\selectfont {#1 \\} \normalfont} - -% Location command -\newcommand{\location}[1]{ -\color{headings}\raggedright\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{10pt}{12pt}\selectfont {#1\\} \normalfont} - -% Bullet Lists with fewer gaps command -\newenvironment{tightemize}{ -\vspace{-\topsep}\begin{itemize}\itemsep1pt \parskip0pt \parsep0pt} -{\end{itemize}\vspace{-\topsep}} - -% Cover Letter -\newcommand{\companyname}[1]{\raggedright\fontspec[Path = fonts/lato/]{Lato-Bol}\fontsize{12pt}{14pt}\selectfont {#1 \\} \normalfont} - -\newcommand{\companyaddress}[1]{\raggedright\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{13pt}\selectfont {#1 \\}\mbox{}\\\mbox{}\\ \normalfont} - -\newcommand{\currentdate}[1]{\raggedleft\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{13pt}\selectfont {#1 \\} \normalfont} - -% Letter content command -\newcommand{\lettercontent}[1]{\raggedright\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{13pt}\selectfont {#1 \\}\mbox{}\\ \normalfont} - -\newcommand{\closing}[1]{\raggedright\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{13pt}\selectfont {#1 \\}\mbox{}\\\mbox{}\\ \normalfont} - -\newcommand{\signature}[1]{\raggedright\fontspec[Path = fonts/raleway/]{Raleway-Medium}\fontsize{11pt}{13pt}\selectfont {#1 \\} \normalfont} \ No newline at end of file diff --git a/tests/test_convert_salary_excel.py b/tests/test_convert_salary_excel.py index 54e944c..5b6a858 100644 --- a/tests/test_convert_salary_excel.py +++ b/tests/test_convert_salary_excel.py @@ -120,6 +120,39 @@ class DetectColumnTypeTests(unittest.TestCase): self.assertEqual(len(companies), 1) self.assertEqual(companies[0]["city"], "Aarhus") + def test_parse_sheet_handles_ragged_rows(self): + # openpyxl's read_only mode yields ragged tuples for dimension-less + # workbooks: a row can be shorter than the header. A company row that + # omits its city and category cells must parse without an IndexError, + # be retained, and get an empty city. + ws = FakeWorksheet([ + ("Company", "City", "Engineering Count", "Engineering Index"), + ("Example Corp",), + ("Other Corp", "Aarhus", 12, 105.5), + ]) + + companies = parse_sheet(ws) + + self.assertEqual(len(companies), 2) + self.assertEqual(companies[0]["company"], "Example Corp") + self.assertEqual(companies[0]["city"], "") + self.assertEqual(companies[0]["categories"], {}) + self.assertEqual(companies[1]["categories"]["engineering"], {"count": 12, "index": 105.5}) + + def test_parse_sheet_skips_row_shorter_than_company_column(self): + # A ragged row that ends before the company column has no company cell + # at all; it must be skipped, not crash the parse. + ws = FakeWorksheet([ + ("Notes", "Company", "Salary Index"), + ("stray",), + ("", "Example Corp", 105.5), + ]) + + companies = parse_sheet(ws) + + self.assertEqual(len(companies), 1) + self.assertEqual(companies[0]["company"], "Example Corp") + def test_skips_free_text_column(self): # A free-text "Notes" column must not become a bogus salary category. ws = FakeWorksheet([ diff --git a/tools/security_guards.py b/tools/security_guards.py index d307b28..28400c2 100644 --- a/tools/security_guards.py +++ b/tools/security_guards.py @@ -50,7 +50,11 @@ REQUIRED_IGNORE_RULES = [ "**/job_scraper/seen_jobs.json", "cv/main_*.*", "!cv/main_example.tex", + # ATS text extractions (/apply step 5d) carry the CV's full text. + "cv/*.txt", "cover_letters/cover_*.*", + # /apply also recognizes the uppercase Cover_* naming variant. + "cover_letters/Cover_*.*", "documents/cv/**", "documents/linkedin/**", "documents/diplomas/**",