From 48b4fe3a9820e67377c4f0ef3fb223ebd05e6954 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:42:22 +0000 Subject: [PATCH 1/3] Initial plan From 673dbe66f6d318f36ba33657b597d43bfde5bf7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:44:36 +0000 Subject: [PATCH 2/3] Add owner scan login verification tool --- README.md | 14 +++++ owner_scan_auth.py | 104 ++++++++++++++++++++++++++++++++++ tests/test_owner_scan_auth.py | 31 ++++++++++ 3 files changed, 149 insertions(+) create mode 100644 owner_scan_auth.py create mode 100644 tests/test_owner_scan_auth.py diff --git a/README.md b/README.md index 449681a..af10316 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,17 @@ If the exercise isn't ready in 20 seconds, please check the [Actions](../../acti --- © 2025 GitHub • [Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md) • [MIT License](https://gh.io/mit) + +## Owner login scan tool + +This repository now includes a minimal owner verification tool in `/tmp/workspace/Syko12345/SecretScanTool/owner_scan_auth.py`. + +It stores a hashed owner scan signature and denies login when the new scan does not match the enrolled owner signature. + +### Quick start + +```bash +python owner_scan_auth.py --database owner_scans.json enroll alice "FaceVector:12345" +python owner_scan_auth.py --database owner_scans.json login alice "FaceVector:12345" # Access granted +python owner_scan_auth.py --database owner_scans.json login alice "FaceVector:wrong" # Access denied +``` diff --git a/owner_scan_auth.py b/owner_scan_auth.py new file mode 100644 index 0000000..60d58c7 --- /dev/null +++ b/owner_scan_auth.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +import argparse +import hashlib +import hmac +import json +from pathlib import Path + + +def _normalize_scan(scan_data: str) -> str: + return " ".join(scan_data.strip().lower().split()) + + +def create_scan_signature(scan_data: str) -> str: + normalized = _normalize_scan(scan_data) + if not normalized: + raise ValueError("Scan data cannot be empty") + return hashlib.sha256(normalized.encode("utf-8")).hexdigest() + + +def _load_database(database_path: str | Path) -> dict[str, str]: + db_path = Path(database_path) + if not db_path.exists(): + return {} + with db_path.open("r", encoding="utf-8") as file: + data = json.load(file) + if not isinstance(data, dict): + raise ValueError("Database must be a JSON object") + return {str(account): str(signature) for account, signature in data.items()} + + +def _save_database(database_path: str | Path, database: dict[str, str]) -> None: + db_path = Path(database_path) + db_path.parent.mkdir(parents=True, exist_ok=True) + with db_path.open("w", encoding="utf-8") as file: + json.dump(database, file, indent=2) + + +def enroll_owner_scan(account_id: str, scan_data: str, database_path: str | Path) -> None: + normalized_account = account_id.strip() + if not normalized_account: + raise ValueError("Account ID cannot be empty") + + database = _load_database(database_path) + database[normalized_account] = create_scan_signature(scan_data) + _save_database(database_path, database) + + +def verify_owner_scan(account_id: str, attempted_scan_data: str, database_path: str | Path) -> bool: + normalized_account = account_id.strip() + if not normalized_account: + return False + + database = _load_database(database_path) + expected_signature = database.get(normalized_account) + if expected_signature is None: + return False + + attempted_signature = create_scan_signature(attempted_scan_data) + return hmac.compare_digest(expected_signature, attempted_signature) + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Secretly scan and verify whether a login attempt matches the account owner." + ) + parser.add_argument( + "--database", + default="owner_scans.json", + help="Path to the owner scan signature database (default: owner_scans.json)", + ) + + subparsers = parser.add_subparsers(dest="command", required=True) + + enroll_parser = subparsers.add_parser("enroll", help="Enroll or update an account owner's scan") + enroll_parser.add_argument("account_id", help="Account identifier") + enroll_parser.add_argument("scan_data", help="Trusted owner scan input") + + login_parser = subparsers.add_parser("login", help="Verify login scan against the account owner") + login_parser.add_argument("account_id", help="Account identifier") + login_parser.add_argument("scan_data", help="Scan input captured at login") + + return parser + + +def main() -> int: + parser = _build_parser() + args = parser.parse_args() + + if args.command == "enroll": + enroll_owner_scan(args.account_id, args.scan_data, args.database) + print(f"Owner scan enrolled for account '{args.account_id}'.") + return 0 + + if verify_owner_scan(args.account_id, args.scan_data, args.database): + print("Access granted.") + return 0 + + print("Access denied: account owner scan mismatch.") + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_owner_scan_auth.py b/tests/test_owner_scan_auth.py new file mode 100644 index 0000000..9a92530 --- /dev/null +++ b/tests/test_owner_scan_auth.py @@ -0,0 +1,31 @@ +import tempfile +import unittest +from pathlib import Path + +from owner_scan_auth import enroll_owner_scan, verify_owner_scan + + +class OwnerScanAuthTests(unittest.TestCase): + def setUp(self) -> None: + self.temp_dir = tempfile.TemporaryDirectory() + self.database_path = Path(self.temp_dir.name) / "owner_scans.json" + + def tearDown(self) -> None: + self.temp_dir.cleanup() + + def test_allows_login_when_scan_matches_owner(self) -> None: + enroll_owner_scan("alice", "FaceVector:12345", self.database_path) + + self.assertTrue(verify_owner_scan("alice", "facevector:12345", self.database_path)) + + def test_denies_login_when_scan_does_not_match_owner(self) -> None: + enroll_owner_scan("alice", "FaceVector:12345", self.database_path) + + self.assertFalse(verify_owner_scan("alice", "FaceVector:wrong", self.database_path)) + + def test_denies_login_for_unknown_account(self) -> None: + self.assertFalse(verify_owner_scan("unknown", "FaceVector:12345", self.database_path)) + + +if __name__ == "__main__": + unittest.main() From 13352bba900dbfa501034d6dba172fc98c5d4c79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:49:43 +0000 Subject: [PATCH 3/3] Resolve PR merge conflicts with main --- __pycache__/owner_scan_auth.cpython-312.pyc | Bin 0 -> 5537 bytes .../test_owner_scan_auth.cpython-312.pyc | Bin 0 -> 2422 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 __pycache__/owner_scan_auth.cpython-312.pyc create mode 100644 tests/__pycache__/test_owner_scan_auth.cpython-312.pyc diff --git a/__pycache__/owner_scan_auth.cpython-312.pyc b/__pycache__/owner_scan_auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbb1104d3a85573cd77feda44ce7b6c09fa1cd73 GIT binary patch literal 5537 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