security: support private identifier deny lists
This commit is contained in:
parent
16c1b5f192
commit
bd53ee640f
3 changed files with 43 additions and 3 deletions
|
|
@ -22,6 +22,9 @@ project artifact is created, edited, renamed, or reorganized there.
|
|||
- Never commit LightBurn Support Data to the public repository.
|
||||
- Do not execute software acquired from the installer stick during analysis.
|
||||
- Run the public repository guard before each public push.
|
||||
- When PrivateStash is available, pass its private deny-list to the public
|
||||
guard so known serials, source IDs, and local identifiers are checked without
|
||||
storing those values publicly.
|
||||
|
||||
## Redaction flow
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def tracked_files(root: Path) -> list[Path]:
|
|||
return [root / item.decode("utf-8") for item in result.stdout.split(b"\0") if item]
|
||||
|
||||
|
||||
def check(root: Path) -> list[str]:
|
||||
def check(root: Path, deny_patterns: tuple[str, ...] = ()) -> list[str]:
|
||||
failures: list[str] = []
|
||||
for path in tracked_files(root):
|
||||
relative = path.relative_to(root).as_posix()
|
||||
|
|
@ -48,14 +48,36 @@ def check(root: Path) -> list[str]:
|
|||
for label, pattern in SENSITIVE_TEXT.items():
|
||||
if pattern.search(content):
|
||||
failures.append(f"{label} found in {relative}")
|
||||
folded = content.casefold()
|
||||
for pattern in deny_patterns:
|
||||
if pattern.casefold() in folded:
|
||||
failures.append(f"private deny-list token found in {relative}")
|
||||
return failures
|
||||
|
||||
|
||||
def load_deny_patterns(path: Path | None) -> tuple[str, ...]:
|
||||
if path is None:
|
||||
return ()
|
||||
patterns = []
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
candidate = line.strip()
|
||||
if candidate and not candidate.startswith("#"):
|
||||
patterns.append(candidate)
|
||||
return tuple(patterns)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("root", type=Path, nargs="?", default=Path.cwd())
|
||||
parser.add_argument(
|
||||
"--deny-pattern-file",
|
||||
type=Path,
|
||||
help="private newline-delimited literal tokens that must not appear publicly",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
failures = check(args.root.resolve())
|
||||
failures = check(
|
||||
args.root.resolve(), load_deny_patterns(args.deny_pattern_file)
|
||||
)
|
||||
if failures:
|
||||
for failure in failures:
|
||||
print(f"ERROR: {failure}")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import unittest
|
|||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from scripts.check_public_repo import check
|
||||
from scripts.check_public_repo import check, load_deny_patterns
|
||||
|
||||
|
||||
class PublicRepoGuardTests(unittest.TestCase):
|
||||
|
|
@ -36,6 +36,21 @@ class PublicRepoGuardTests(unittest.TestCase):
|
|||
with patch("scripts.check_public_repo.tracked_files", return_value=[candidate]):
|
||||
self.assertEqual(check(root), [])
|
||||
|
||||
def test_private_deny_list_is_applied_without_echoing_token(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
candidate = root / "notes.md"
|
||||
candidate.write_text("machine-sensitive-value", encoding="utf-8")
|
||||
with patch("scripts.check_public_repo.tracked_files", return_value=[candidate]):
|
||||
failures = check(root, ("sensitive-value",))
|
||||
self.assertEqual(failures, ["private deny-list token found in notes.md"])
|
||||
|
||||
def test_deny_list_ignores_comments_and_blank_lines(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path = Path(temporary) / "deny.txt"
|
||||
path.write_text("# private values\n\n first-token \nsecond-token\n", encoding="utf-8")
|
||||
self.assertEqual(load_deny_patterns(path), ("first-token", "second-token"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue