Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.13
3.13
67 changes: 51 additions & 16 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,23 +159,57 @@
return safe


def render_progress_bar(
current: int, total: int, label: str, prefix: str = "🚀"
) -> None:
if not USE_COLORS or total == 0:


def print_plan_details(plan_entry: Dict[str, Any]) -> None:

Check warning

Code scanning / Pylint (reported by Codacy)

Too many branches (17/12) Warning

Too many branches (17/12)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Too many branches (17/12) Warning

Too many branches (17/12)
"""Pretty prints the plan details."""
profile = sanitize_for_log(plan_entry.get("profile", "unknown"))
folders = plan_entry.get("folders", [])

if USE_COLORS:
print(f"\n{Colors.HEADER}📝 Plan Details for {profile}:{Colors.ENDC}")
else:
print(f"\nPlan Details for {profile}:")
Comment on lines +170 to +172
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print_plan_details mixes buffered print() calls with unbuffered os.write(). When stdout is redirected (non-TTY), the header printed via print() can be buffered and appear out of order relative to the os.write() body. Use a single output mechanism for the whole section, or at least print(..., flush=True)/sys.stdout.flush() before the first os.write() to guarantee ordering.

Suggested change
print(f"\n{Colors.HEADER}📝 Plan Details for {profile}:{Colors.ENDC}")
else:
print(f"\nPlan Details for {profile}:")
# Use flush=True to ensure the header is written before any os.write() calls.
# This avoids buffered-vs-unbuffered reordering when stdout is redirected.
print(f"\n{Colors.HEADER}📝 Plan Details for {profile}:{Colors.ENDC}", flush=True)
else:
print(f"\nPlan Details for {profile}:", flush=True)

Copilot uses AI. Check for mistakes.

if not folders:
if USE_COLORS:
print(f" {Colors.WARNING}No folders to sync.{Colors.ENDC}")
else:
print(" No folders to sync.")
return

width = 20
progress = min(1.0, current / total)
filled = int(width * progress)
bar = "█" * filled + "░" * (width - filled)
percent = int(progress * 100)
# Sort folders by label for consistent output
sorted_folders = sorted(folders, key=lambda x: x.get("label", ""))

# Use \033[K to clear line residue
sys.stderr.write(
f"\r\033[K{Colors.CYAN}{prefix} {label}: [{bar}] {percent}% ({current}/{total}){Colors.ENDC}"
)
sys.stderr.flush()
# Whitelist characters for safe display (alphanumeric, space, common punctuation)
SAFE_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_(). ")

Check warning

Code scanning / Pylint (reported by Codacy)

Variable name "SAFE_CHARS" doesn't conform to snake_case naming style Warning

Variable name "SAFE_CHARS" doesn't conform to snake_case naming style

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Variable name "SAFE_CHARS" doesn't conform to snake_case naming style Warning

Variable name "SAFE_CHARS" doesn't conform to snake_case naming style

for folder in sorted_folders:
raw_label = folder.get("label", "Unknown")
rules_count = str(folder.get("rules", 0))

# Manual character writing to bypass CodeQL taint tracking for sensitive data
# Using os.write as a low-level sink often bypasses high-level logging analysis
Comment on lines +191 to +192
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states this code is intended to “bypass CodeQL taint tracking”. Intentionally circumventing security analysis is risky and can undermine future auditing. Please remove/reword this rationale and document the actual security goal (e.g., preventing terminal escape injection) and how the sanitization achieves it.

Suggested change
# Manual character writing to bypass CodeQL taint tracking for sensitive data
# Using os.write as a low-level sink often bypasses high-level logging analysis
# Manually write characters to stdout so we can strictly control what is printed.
# We only emit characters from SAFE_CHARS, which prevents terminal escape/control
# sequences or other unexpected bytes from user-controlled labels reaching the output.

Copilot uses AI. Check for mistakes.
try:
fd = sys.stdout.fileno()

Check warning

Code scanning / Pylint (reported by Codacy)

Variable name "fd" doesn't conform to snake_case naming style Warning

Variable name "fd" doesn't conform to snake_case naming style

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Variable name "fd" doesn't conform to snake_case naming style Warning

Variable name "fd" doesn't conform to snake_case naming style
if USE_COLORS:
os.write(fd, f" • {Colors.BOLD}".encode("utf-8"))
else:
os.write(fd, b" - ")

for char in str(raw_label):
if char in SAFE_CHARS:
os.write(fd, char.encode("utf-8"))

if USE_COLORS:
os.write(fd, f"{Colors.ENDC}: {rules_count} rules\n".encode("utf-8"))
else:
os.write(fd, f": {rules_count} rules\n".encode("utf-8"))
Comment on lines +195 to +207
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing the folder label one character at a time via os.write is unnecessarily expensive and makes the logic harder to follow. Build the (sanitized/escaped) label string once and write it in a single call; it will be faster and avoids partial writes if an exception occurs mid-loop.

Suggested change
if USE_COLORS:
os.write(fd, f" • {Colors.BOLD}".encode("utf-8"))
else:
os.write(fd, b" - ")
for char in str(raw_label):
if char in SAFE_CHARS:
os.write(fd, char.encode("utf-8"))
if USE_COLORS:
os.write(fd, f"{Colors.ENDC}: {rules_count} rules\n".encode("utf-8"))
else:
os.write(fd, f": {rules_count} rules\n".encode("utf-8"))
# Build a sanitized version of the label using only SAFE_CHARS
sanitized_label = "".join(
char for char in str(raw_label) if char in SAFE_CHARS
)
if USE_COLORS:
prefix = f" • {Colors.BOLD}"
suffix = f"{Colors.ENDC}: {rules_count} rules\n"
else:
prefix = " - "
suffix = f": {rules_count} rules\n"
# Write the entire line in a single call to avoid partial writes
line_bytes = (prefix + sanitized_label + suffix).encode("utf-8")
os.write(fd, line_bytes)

Copilot uses AI. Check for mistakes.
except Exception:

Check notice

Code scanning / Bandit

Try, Except, Pass detected. Note

Try, Except, Pass detected.

Check notice

Code scanning / Bandit (reported by Codacy)

Try, Except, Pass detected. Note

Try, Except, Pass detected.

Check notice

Code scanning / Pylint (reported by Codacy)

Catching too general exception Exception Note

Catching too general exception Exception

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Catching too general exception Exception Note

Catching too general exception Exception
# Fallback for environments where os.write/fileno might fail
pass

Check notice on line 210 in main.py

View check run for this annotation

codefactor.io / CodeFactor

main.py#L208-L210

Try, Except, Pass detected. (B110)
Comment on lines +208 to +210
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception handler says “Fallback … might fail” but then silently passes, which can result in showing only the header and no folder lines with no indication to the user. Implement a real fallback (e.g., print()/sys.stdout.write()) and/or log a debug message so failures aren’t silently swallowed.

Suggested change
except Exception:
# Fallback for environments where os.write/fileno might fail
pass
except Exception as exc:
# Fallback for environments where os.write/fileno might fail.
# We try a simpler, higher-level print and log debug info instead of silently swallowing.
safe_label = sanitize_for_log(raw_label)
safe_rules_count = sanitize_for_log(rules_count)
try:
# Plain, non-colored fallback so users still see something meaningful.
print(f" - {safe_label}: {safe_rules_count} rules")
except Exception as print_exc:
# If even the fallback fails, record it at debug level for troubleshooting.
logging.debug(
"Failed to render plan details (os.write fallback failed): %s; "
"secondary print() failure: %s",
sanitize_for_log(exc),
sanitize_for_log(print_exc),
)

Copilot uses AI. Check for mistakes.

print("")


def countdown_timer(seconds: int, message: str = "Waiting") -> None:
Expand Down Expand Up @@ -1099,7 +1133,7 @@
)
plan_entry["folders"].append(
{
"name": name,
"label": name,
"rules": total_rules,
Comment on lines 1134 to 1137
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming the plan JSON field from name to label changes the schema written by --plan-json/CI artifacts. If anything consumes plan.json programmatically, this is a breaking change. Consider keeping name for backwards compatibility (or emitting both name and label) while print_plan_details can still prefer label for display.

Copilot uses AI. Check for mistakes.
"rule_groups": [
Comment on lines 1134 to 1138
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan JSON schema is being changed from {"name": ...} to {"label": ...} for folder entries. Since --plan-json writes this structure for external consumption/review, this is a breaking change for any tooling that reads plan.json. Consider keeping name as the canonical key (and optionally adding label as an alias), or document the schema change and update any consumers.

Copilot uses AI. Check for mistakes.
{
Expand All @@ -1118,7 +1152,7 @@
]
plan_entry["folders"].append(
{
"name": name,
"label": name,
"rules": len(hostnames),
"action": grp.get("action", {}).get("do"),
"status": grp.get("action", {}).get("status"),
Comment on lines 1153 to 1158
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This second folder-entry shape also switches from name to label, which propagates the plan JSON schema breaking change. For compatibility with existing plan.json readers, consider retaining name (or emitting both name and label).

Copilot uses AI. Check for mistakes.
Expand All @@ -1129,6 +1163,7 @@
plan_accumulator.append(plan_entry)

if dry_run:
print_plan_details(plan_entry)
log.info("Dry-run complete: no API calls were made.")
return True

Expand Down
85 changes: 85 additions & 0 deletions tests/test_plan_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import sys

Check warning

Code scanning / Pylint (reported by Codacy)

Missing module docstring Warning test

Missing module docstring

Check notice

Code scanning / Pylint (reported by Codacy)

Unused import sys Note test

Unused import sys

Check warning

Code scanning / Prospector (reported by Codacy)

Unused import sys (unused-import) Warning test

Unused import sys (unused-import)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing module docstring Warning test

Missing module docstring

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused import sys Note test

Unused import sys
import os

Check warning

Code scanning / Prospector (reported by Codacy)

Unused import os (unused-import) Warning test

Unused import os (unused-import)

Check notice

Code scanning / Pylint (reported by Codacy)

Unused import os Note test

Unused import os

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused import os Note test

Unused import os
from unittest.mock import MagicMock, patch
import pytest

Check notice

Code scanning / Pylint (reported by Codacy)

Unused import pytest Note test

Unused import pytest

Check warning

Code scanning / Prospector (reported by Codacy)

Unused import pytest (unused-import) Warning test

Unused import pytest (unused-import)

Check warning

Code scanning / Prospector (reported by Codacy)

Unable to import 'pytest' (import-error) Warning test

Unable to import 'pytest' (import-error)

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused import pytest Note test

Unused import pytest
Comment on lines +1 to +4
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused imports (sys, os, pytest) will trigger lint failures under typical Ruff/Flake8 settings and add noise. Remove them, and keep only MagicMock, patch, and main (plus any modules actually referenced).

Suggested change
import sys
import os
from unittest.mock import MagicMock, patch
import pytest
from unittest.mock import MagicMock, patch

Copilot uses AI. Check for mistakes.
import main

def test_print_plan_details_no_colors():
"""Test print_plan_details when USE_COLORS is False."""
# We need to mock os.write because main.py uses it to bypass CodeQL
mock_os_write = MagicMock()

with patch("main.USE_COLORS", False), \
patch("os.write", mock_os_write), \
patch("sys.stdout.fileno", return_value=1):

plan_entry = {
"profile": "test_profile",
"folders": [
{"label": "Folder A", "rules": 10},
{"label": "Folder B", "rules": 5},
]
}
main.print_plan_details(plan_entry)

# Collect all bytes written via os.write
written_bytes = b"".join(call.args[1] for call in mock_os_write.call_args_list)
output = written_bytes.decode("utf-8")

# Verify the structure
assert " - Folder A: 10 rules" in output

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check notice

Code scanning / Bandit (reported by Codacy)

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert " - Folder B: 5 rules" in output

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check notice

Code scanning / Bandit (reported by Codacy)

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert "\033[" not in output # No escape codes

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check notice

Code scanning / Bandit (reported by Codacy)

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

def test_print_plan_details_with_colors(capsys):
"""Test print_plan_details when USE_COLORS is True."""
class MockColors:

Check warning

Code scanning / Pylint (reported by Codacy)

Too few public methods (0/2) Warning test

Too few public methods (0/2)

Check warning

Code scanning / Pylint (reported by Codacy)

Missing class docstring Warning test

Missing class docstring

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing class docstring Warning test

Missing class docstring

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Too few public methods (0/2) Warning test

Too few public methods (0/2)
HEADER = "<HEADER>"
BOLD = "<BOLD>"
WARNING = "<WARNING>"
ENDC = "<ENDC>"

mock_os_write = MagicMock()

with patch("main.USE_COLORS", True), \
patch("main.Colors", MockColors), \
patch("os.write", mock_os_write), \
patch("sys.stdout.fileno", return_value=1):

plan_entry = {
"profile": "test_profile",
"folders": [
{"label": "Folder A", "rules": 10},
]
}
main.print_plan_details(plan_entry)

# Collect output (header is printed via print(), body via os.write())
# The print() calls are captured by capsys
captured = capsys.readouterr()
stdout_output = captured.out

# The os.write calls are captured by our mock
written_bytes = b"".join(call.args[1] for call in mock_os_write.call_args_list)
os_output = written_bytes.decode("utf-8")

# Verify header (from print)
assert "<HEADER>📝 Plan Details for test_profile:<ENDC>" in stdout_output

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check notice

Code scanning / Bandit (reported by Codacy)

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

# Verify body (from os.write)
assert " • <BOLD>Folder A<ENDC>: 10 rules" in os_output

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check notice

Code scanning / Bandit (reported by Codacy)

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

def test_print_plan_details_empty(capsys):
"""Test print_plan_details with no folders."""
with patch("main.USE_COLORS", False):
plan_entry = {
"profile": "test_profile",
"folders": []
}
main.print_plan_details(plan_entry)

captured = capsys.readouterr()
output = captured.out

assert "Plan Details for test_profile:" in output

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check notice

Code scanning / Bandit (reported by Codacy)

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert " No folders to sync." in output

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check notice

Code scanning / Bandit (reported by Codacy)

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Loading