mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
fix(security_guards): reject un-allowlisted .gitignore negations (#195)
check_gitignore() verified each required personal-data rule was present via set membership, but .gitignore is order-sensitive: a later !pattern re-includes a file an earlier rule excluded, so the required line stays physically present while the file is no longer ignored - the guard failed open on exactly the weakening its docstring claims to catch. Keeps the required-rules-present check and additionally rejects any negation line outside a small reviewed ALLOWED_IGNORE_NEGATIONS allowlist (same explicit-widening pattern as ALLOWED_PERMISSIONS). Fixes #194. By @thejesh23. Verified: allowlist matches the four negations currently in .gitignore; guard test suite passes locally (17 tests) and in CI. Closes #194
This commit is contained in:
@@ -125,6 +125,29 @@ class GitignoreGuardTests(GuardRepoFixture):
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
|
||||
|
||||
class GitignoreNegationTests(GuardRepoFixture):
|
||||
def test_negation_reincluding_personal_data_fails(self):
|
||||
# .gitignore is order-sensitive: `!salary_data.json` after the
|
||||
# `salary_data.json` rule re-includes the file, so the required rule is
|
||||
# still present but no longer takes effect. Set membership on the
|
||||
# required rules cannot see this, so the negation must be rejected.
|
||||
self.write_gitignore(list(security_guards.REQUIRED_IGNORE_RULES) + ["!salary_data.json"])
|
||||
result = run_guards(self.root)
|
||||
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
||||
self.assertIn("negation rule not in the reviewed allowlist", result.stdout)
|
||||
self.assertIn("!salary_data.json", result.stdout)
|
||||
|
||||
def test_allowlisted_negations_pass(self):
|
||||
# The template's own benign negations (example CV/cover letter, fonts,
|
||||
# .gitkeep placeholders) must keep passing.
|
||||
self.write_gitignore(
|
||||
list(security_guards.REQUIRED_IGNORE_RULES)
|
||||
+ sorted(security_guards.ALLOWED_IGNORE_NEGATIONS)
|
||||
)
|
||||
result = run_guards(self.root)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
|
||||
|
||||
class ManifestGuardTests(GuardRepoFixture):
|
||||
def test_each_lifecycle_script_fails(self):
|
||||
for script in sorted(security_guards.FORBIDDEN_SCRIPTS):
|
||||
|
||||
Reference in New Issue
Block a user