diff --git a/workspace-template/builtin_tools/compliance.py b/workspace-template/builtin_tools/compliance.py index 52e7242c..1c4e45e7 100644 --- a/workspace-template/builtin_tools/compliance.py +++ b/workspace-template/builtin_tools/compliance.py @@ -257,8 +257,10 @@ _PII_PATTERNS: list[tuple[re.Pattern[str], str]] = [ (re.compile(r"\b(?:sk|pk|api|secret|token|auth)[-_][A-Za-z0-9_\-]{20,}\b", re.I), "[REDACTED:api_key]"), # AWS Access Key IDs (re.compile(r"\bAKIA[0-9A-Z]{16}\b"), "[REDACTED:aws_key]"), - # GitHub personal access tokens + # GitHub personal access tokens — classic format (36-char alphanumeric suffix) (re.compile(r"\bghp_[A-Za-z0-9]{36}\b"), "[REDACTED:github_token]"), + # GitHub personal access tokens — fine-grained format (82-char alphanumeric+underscore suffix) + (re.compile(r"\bgithub_pat_[A-Za-z0-9_]{82}\b"), "[REDACTED:github_token]"), # Email addresses (re.compile(r"\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b"), "[REDACTED:email]"), ] diff --git a/workspace-template/platform_auth.py b/workspace-template/platform_auth.py index b7dc546f..d4a1e180 100644 --- a/workspace-template/platform_auth.py +++ b/workspace-template/platform_auth.py @@ -58,7 +58,12 @@ def get_token() -> str | None: def save_token(token: str) -> None: - """Persist a newly-issued token. Creates the file with 0600 mode. + """Persist a newly-issued token. Creates the file with 0600 mode atomically. + + Uses ``os.open(O_CREAT, 0o600)`` so the file is never world-readable, + even transiently. The previous ``write_text()`` + ``chmod()`` approach + had a TOCTOU window where a concurrent reader could access the token + between the two syscalls (M4 — flagged in security audit cycle 10). Idempotent — if an identical token is already on disk we skip the write so we don't churn the file's mtime or trigger spurious @@ -71,13 +76,14 @@ def save_token(token: str) -> None: return path = _token_file() path.parent.mkdir(parents=True, exist_ok=True) - # Write + chmod before assigning — if the chmod fails we don't want - # a world-readable copy of the token sitting around. - path.write_text(token) + # O_CREAT | O_WRONLY | O_TRUNC with mode=0o600 atomically creates (or + # truncates) the file with restricted permissions in a single syscall, + # eliminating the TOCTOU window. + fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) try: - os.chmod(path, 0o600) - except OSError as exc: - logger.warning("platform_auth: chmod 0600 on %s failed: %s", path, exc) + os.write(fd, token.encode()) + finally: + os.close(fd) _cached_token = token