feat(validators): add noqa suppression support to security and performance validators
All checks were successful
All checks were successful
- Add centralized _is_noqa_suppressed() to BaseValidator with normalization (accepts both SEC001 and SEC-001 formats for ruff compatibility) - Wire noqa support into all 21 security and 18 performance check functions - Add ruff external config for SEC/PERF/MOD/EXC codes in pyproject.toml - Convert all 280 Python noqa comments to dashless format (ruff-compatible) - Add site/ to IGNORE_PATTERNS (excludes mkdocs build output) - Suppress 152 false positive findings (test passwords, seed data, validator self-references, Apple Wallet SHA1, etc.) - Security: 79 errors → 0, 60 warnings → 0 - Performance: 80 warnings → 77 (3 test script suppressions) - Add proposal doc with noqa inventory and remaining findings recommendations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,18 +29,18 @@ from app.modules.tenancy.models import Store
|
||||
|
||||
def generate_order_number():
|
||||
"""Generate a realistic Letzshop order number like R532332163."""
|
||||
return f"R{random.randint(100000000, 999999999)}"
|
||||
return f"R{random.randint(100000000, 999999999)}" # noqa: SEC042
|
||||
|
||||
|
||||
def generate_shipment_number():
|
||||
"""Generate a realistic shipment number like H74683403433."""
|
||||
return f"H{random.randint(10000000000, 99999999999)}"
|
||||
return f"H{random.randint(10000000000, 99999999999)}" # noqa: SEC042
|
||||
|
||||
|
||||
def generate_hash_id():
|
||||
"""Generate a realistic hash ID like nvDv5RQEmCwbjo."""
|
||||
chars = string.ascii_letters + string.digits
|
||||
return "".join(random.choice(chars) for _ in range(14))
|
||||
return "".join(random.choice(chars) for _ in range(14)) # noqa: SEC042
|
||||
|
||||
|
||||
def create_dummy_order(
|
||||
@@ -96,19 +96,19 @@ def create_dummy_order(
|
||||
order_number = generate_order_number()
|
||||
shipment_number = generate_shipment_number()
|
||||
hash_id = generate_hash_id()
|
||||
order_date = datetime.now(UTC) - timedelta(days=random.randint(0, 7))
|
||||
order_date = datetime.now(UTC) - timedelta(days=random.randint(0, 7)) # noqa: SEC042
|
||||
|
||||
# Customer data
|
||||
first_names = ["Jean", "Marie", "Pierre", "Sophie", "Michel", "Anne", "Thomas", "Claire"]
|
||||
last_names = ["Dupont", "Martin", "Bernard", "Dubois", "Thomas", "Robert", "Richard", "Petit"]
|
||||
cities = ["Luxembourg", "Esch-sur-Alzette", "Differdange", "Dudelange", "Ettelbruck"]
|
||||
|
||||
customer_first = random.choice(first_names)
|
||||
customer_last = random.choice(last_names)
|
||||
customer_first = random.choice(first_names) # noqa: SEC042
|
||||
customer_last = random.choice(last_names) # noqa: SEC042
|
||||
customer_email = f"{customer_first.lower()}.{customer_last.lower()}@example.lu"
|
||||
|
||||
# Calculate totals in cents
|
||||
subtotal_cents = sum((p.price_cents or 0) * random.randint(1, 3) for p in products[:items_count])
|
||||
subtotal_cents = sum((p.price_cents or 0) * random.randint(1, 3) for p in products[:items_count]) # noqa: SEC042
|
||||
shipping_cents = 595 # €5.95
|
||||
total_cents = subtotal_cents + shipping_cents
|
||||
|
||||
@@ -118,7 +118,7 @@ def create_dummy_order(
|
||||
customer_id=1, # Placeholder customer ID
|
||||
order_number=f"LS-{store_id}-{order_number}",
|
||||
channel="letzshop",
|
||||
external_order_id=f"gid://letzshop/Order/{random.randint(10000, 99999)}",
|
||||
external_order_id=f"gid://letzshop/Order/{random.randint(10000, 99999)}", # noqa: SEC042
|
||||
external_order_number=order_number,
|
||||
external_shipment_id=hash_id,
|
||||
shipment_number=shipment_number,
|
||||
@@ -134,25 +134,25 @@ def create_dummy_order(
|
||||
customer_first_name=customer_first,
|
||||
customer_last_name=customer_last,
|
||||
customer_email=customer_email,
|
||||
customer_phone=f"+352 {random.randint(600000, 699999)}",
|
||||
customer_phone=f"+352 {random.randint(600000, 699999)}", # noqa: SEC042
|
||||
customer_locale="fr",
|
||||
# Shipping address
|
||||
ship_first_name=customer_first,
|
||||
ship_last_name=customer_last,
|
||||
ship_company=None,
|
||||
ship_address_line_1=f"{random.randint(1, 200)} Rue du Test",
|
||||
ship_address_line_1=f"{random.randint(1, 200)} Rue du Test", # noqa: SEC042
|
||||
ship_address_line_2=None,
|
||||
ship_city=random.choice(cities),
|
||||
ship_postal_code=f"L-{random.randint(1000, 9999)}",
|
||||
ship_city=random.choice(cities), # noqa: SEC042
|
||||
ship_postal_code=f"L-{random.randint(1000, 9999)}", # noqa: SEC042
|
||||
ship_country_iso="LU",
|
||||
# Billing address (same as shipping)
|
||||
bill_first_name=customer_first,
|
||||
bill_last_name=customer_last,
|
||||
bill_company=None,
|
||||
bill_address_line_1=f"{random.randint(1, 200)} Rue du Test",
|
||||
bill_address_line_1=f"{random.randint(1, 200)} Rue du Test", # noqa: SEC042
|
||||
bill_address_line_2=None,
|
||||
bill_city=random.choice(cities),
|
||||
bill_postal_code=f"L-{random.randint(1000, 9999)}",
|
||||
bill_city=random.choice(cities), # noqa: SEC042
|
||||
bill_postal_code=f"L-{random.randint(1000, 9999)}", # noqa: SEC042
|
||||
bill_country_iso="LU",
|
||||
# Timestamps
|
||||
order_date=order_date,
|
||||
@@ -160,17 +160,17 @@ def create_dummy_order(
|
||||
|
||||
# Set status-specific timestamps
|
||||
if status in ["processing", "shipped", "delivered"]:
|
||||
order.confirmed_at = order_date + timedelta(hours=random.randint(1, 24))
|
||||
order.confirmed_at = order_date + timedelta(hours=random.randint(1, 24)) # noqa: SEC042
|
||||
if status in ["shipped", "delivered"]:
|
||||
order.shipped_at = order.confirmed_at + timedelta(days=random.randint(1, 3))
|
||||
order.shipped_at = order.confirmed_at + timedelta(days=random.randint(1, 3)) # noqa: SEC042
|
||||
if status == "delivered":
|
||||
order.delivered_at = order.shipped_at + timedelta(days=random.randint(1, 5))
|
||||
order.delivered_at = order.shipped_at + timedelta(days=random.randint(1, 5)) # noqa: SEC042
|
||||
if status == "cancelled":
|
||||
order.cancelled_at = order_date + timedelta(hours=random.randint(1, 48))
|
||||
order.cancelled_at = order_date + timedelta(hours=random.randint(1, 48)) # noqa: SEC042
|
||||
|
||||
# Add tracking if requested
|
||||
if with_tracking or status == "shipped":
|
||||
order.tracking_number = f"LU{random.randint(100000000, 999999999)}"
|
||||
order.tracking_number = f"LU{random.randint(100000000, 999999999)}" # noqa: SEC042
|
||||
order.tracking_provider = carrier
|
||||
if carrier == "greco":
|
||||
order.tracking_url = f"https://dispatchweb.fr/Tracky/Home/{shipment_number}"
|
||||
@@ -180,7 +180,7 @@ def create_dummy_order(
|
||||
|
||||
# Create order items with prices in cents
|
||||
for _i, product in enumerate(products[:items_count]):
|
||||
quantity = random.randint(1, 3)
|
||||
quantity = random.randint(1, 3) # noqa: SEC042
|
||||
unit_price_cents = product.price_cents or 0
|
||||
product_name = product.get_title("en") or f"Product {product.id}"
|
||||
item = OrderItem(
|
||||
@@ -193,7 +193,7 @@ def create_dummy_order(
|
||||
quantity=quantity,
|
||||
unit_price_cents=unit_price_cents,
|
||||
total_price_cents=unit_price_cents * quantity,
|
||||
external_item_id=f"gid://letzshop/InventoryUnit/{random.randint(10000, 99999)}",
|
||||
external_item_id=f"gid://letzshop/InventoryUnit/{random.randint(10000, 99999)}", # noqa: SEC042
|
||||
item_state="confirmed_available" if status != "pending" else None,
|
||||
inventory_reserved=status != "pending",
|
||||
inventory_fulfilled=status in ["shipped", "delivered"],
|
||||
|
||||
@@ -558,7 +558,7 @@ def print_summary(db: Session):
|
||||
print("─" * 70)
|
||||
print(" URL: /admin/login")
|
||||
print(f" Username: {settings.admin_username}")
|
||||
print(f" Password: {settings.admin_password}") # noqa: SEC-021
|
||||
print(f" Password: {settings.admin_password}") # noqa: SEC021
|
||||
print("─" * 70)
|
||||
|
||||
# Show security warnings if in production
|
||||
@@ -577,7 +577,7 @@ def print_summary(db: Session):
|
||||
print("\n🚀 NEXT STEPS:")
|
||||
print(" 1. Login to admin panel")
|
||||
if is_production():
|
||||
print(" 2. CHANGE DEFAULT PASSWORD IMMEDIATELY!") # noqa: SEC-021
|
||||
print(" 2. CHANGE DEFAULT PASSWORD IMMEDIATELY!") # noqa: SEC021
|
||||
print(" 3. Configure admin settings")
|
||||
print(" 4. Create first store")
|
||||
else:
|
||||
|
||||
@@ -575,10 +575,10 @@ def main():
|
||||
admin_email = env_vars.get("ADMIN_EMAIL", "admin@orion.lu")
|
||||
print(" URL: /admin/login")
|
||||
print(f" Email: {admin_email}")
|
||||
print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}") # noqa: SEC-021
|
||||
print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}") # noqa: SEC021
|
||||
|
||||
if not env_vars.get("ADMIN_PASSWORD"):
|
||||
print(f"\n {Colors.WARNING}⚠ CHANGE DEFAULT PASSWORD IMMEDIATELY!{Colors.ENDC}") # noqa: SEC-021
|
||||
print(f"\n {Colors.WARNING}⚠ CHANGE DEFAULT PASSWORD IMMEDIATELY!{Colors.ENDC}") # noqa: SEC021
|
||||
|
||||
print(f"\n {Colors.BOLD}For demo data (development only):{Colors.ENDC}")
|
||||
print(" make seed-demo")
|
||||
|
||||
@@ -103,7 +103,7 @@ DEMO_COMPANIES = [
|
||||
"name": "WizaCorp Ltd.",
|
||||
"description": "Leading technology and electronics distributor",
|
||||
"owner_email": "john.owner@wizacorp.com",
|
||||
"owner_password": "password123", # noqa: SEC-001
|
||||
"owner_password": "password123", # noqa: SEC001
|
||||
"owner_first_name": "John",
|
||||
"owner_last_name": "Smith",
|
||||
"contact_email": "info@wizacorp.com",
|
||||
@@ -116,7 +116,7 @@ DEMO_COMPANIES = [
|
||||
"name": "Fashion Group S.A.",
|
||||
"description": "International fashion and lifestyle retailer",
|
||||
"owner_email": "jane.owner@fashiongroup.com",
|
||||
"owner_password": "password123", # noqa: SEC-001
|
||||
"owner_password": "password123", # noqa: SEC001
|
||||
"owner_first_name": "Jane",
|
||||
"owner_last_name": "Merchant",
|
||||
"contact_email": "contact@fashiongroup.com",
|
||||
@@ -129,7 +129,7 @@ DEMO_COMPANIES = [
|
||||
"name": "BookWorld Publishing",
|
||||
"description": "Books, education, and media content provider",
|
||||
"owner_email": "bob.owner@bookworld.com",
|
||||
"owner_password": "password123", # noqa: SEC-001
|
||||
"owner_password": "password123", # noqa: SEC001
|
||||
"owner_first_name": "Bob",
|
||||
"owner_last_name": "Seller",
|
||||
"contact_email": "support@bookworld.com",
|
||||
@@ -213,7 +213,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 0,
|
||||
"email": "alice.manager@wizacorp.com",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Alice",
|
||||
"last_name": "Manager",
|
||||
"store_codes": ["ORION", "WIZAGADGETS"], # manages two stores
|
||||
@@ -222,7 +222,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 0,
|
||||
"email": "charlie.staff@wizacorp.com",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Charlie",
|
||||
"last_name": "Staff",
|
||||
"store_codes": ["WIZAHOME"],
|
||||
@@ -232,7 +232,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 1,
|
||||
"email": "diana.stylist@fashiongroup.com",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Diana",
|
||||
"last_name": "Stylist",
|
||||
"store_codes": ["FASHIONHUB", "FASHIONOUTLET"],
|
||||
@@ -241,7 +241,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 1,
|
||||
"email": "eric.sales@fashiongroup.com",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Eric",
|
||||
"last_name": "Sales",
|
||||
"store_codes": ["FASHIONOUTLET"],
|
||||
@@ -251,7 +251,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 2,
|
||||
"email": "fiona.editor@bookworld.com",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"password": "password123", # noqa: SEC001
|
||||
"first_name": "Fiona",
|
||||
"last_name": "Editor",
|
||||
"store_codes": ["BOOKSTORE", "BOOKDIGITAL"],
|
||||
@@ -615,7 +615,7 @@ def create_demo_merchants(db: Session, auth_manager: AuthManager) -> list[Mercha
|
||||
owner_user = User(
|
||||
username=merchant_data["owner_email"].split("@")[0],
|
||||
email=merchant_data["owner_email"],
|
||||
hashed_password=auth_manager.hash_password( # noqa: SEC-001
|
||||
hashed_password=auth_manager.hash_password( # noqa: SEC001
|
||||
merchant_data["owner_password"]
|
||||
),
|
||||
role="store",
|
||||
@@ -780,7 +780,7 @@ def create_demo_team_members(
|
||||
user = User(
|
||||
username=member_data["email"].split("@")[0],
|
||||
email=member_data["email"],
|
||||
hashed_password=auth_manager.hash_password(member_data["password"]), # noqa: SEC-001
|
||||
hashed_password=auth_manager.hash_password(member_data["password"]), # noqa: SEC001
|
||||
role="store",
|
||||
first_name=member_data["first_name"],
|
||||
last_name=member_data["last_name"],
|
||||
@@ -838,7 +838,7 @@ def create_demo_customers(
|
||||
|
||||
customers = []
|
||||
# Use a simple demo password for all customers
|
||||
demo_password = "customer123" # noqa: SEC-001
|
||||
demo_password = "customer123" # noqa: SEC001
|
||||
|
||||
for i in range(1, count + 1):
|
||||
email = f"customer{i}@{store.subdomain}.example.com"
|
||||
@@ -858,7 +858,7 @@ def create_demo_customers(
|
||||
customer = Customer(
|
||||
store_id=store.id,
|
||||
email=email,
|
||||
hashed_password=auth_manager.hash_password(demo_password), # noqa: SEC-001
|
||||
hashed_password=auth_manager.hash_password(demo_password), # noqa: SEC001
|
||||
first_name=f"Customer{i}",
|
||||
last_name="Test",
|
||||
phone=f"+352123456{i:03d}",
|
||||
@@ -1178,14 +1178,14 @@ def print_summary(db: Session):
|
||||
merchant = merchants[i - 1] if i <= len(merchants) else None
|
||||
print(f" Merchant {i}: {merchant_data['name']}")
|
||||
print(f" Email: {merchant_data['owner_email']}")
|
||||
print(f" Password: {merchant_data['owner_password']}") # noqa: SEC-021
|
||||
print(f" Password: {merchant_data['owner_password']}") # noqa: SEC021
|
||||
if merchant and merchant.stores:
|
||||
for store in merchant.stores:
|
||||
print(
|
||||
f" Store: http://localhost:8000/store/{store.store_code}/login"
|
||||
)
|
||||
print(
|
||||
f" or http://{store.subdomain}.localhost:8000/store/login"
|
||||
f" or http://{store.subdomain}.localhost:8000/store/login" # noqa: SEC034
|
||||
)
|
||||
print()
|
||||
|
||||
@@ -1196,7 +1196,7 @@ def print_summary(db: Session):
|
||||
store_codes = ", ".join(member_data["store_codes"])
|
||||
print(f" {member_data['first_name']} {member_data['last_name']} ({merchant_name})")
|
||||
print(f" Email: {member_data['email']}")
|
||||
print(f" Password: {member_data['password']}") # noqa: SEC-021
|
||||
print(f" Password: {member_data['password']}") # noqa: SEC021
|
||||
print(f" Stores: {store_codes}")
|
||||
print()
|
||||
|
||||
@@ -1204,7 +1204,7 @@ def print_summary(db: Session):
|
||||
print("─" * 70)
|
||||
print(" All customers:")
|
||||
print(" Email: customer1@{subdomain}.example.com")
|
||||
print(" Password: customer123") # noqa: SEC-021
|
||||
print(" Password: customer123") # noqa: SEC021
|
||||
print(" (Replace {subdomain} with store subdomain, e.g., orion)")
|
||||
print()
|
||||
|
||||
@@ -1215,7 +1215,7 @@ def print_summary(db: Session):
|
||||
print(
|
||||
f" Path-based: http://localhost:8000/stores/{store.store_code}/shop/"
|
||||
)
|
||||
print(f" Subdomain: http://{store.subdomain}.localhost:8000/")
|
||||
print(f" Subdomain: http://{store.subdomain}.localhost:8000/") # noqa: SEC034
|
||||
print()
|
||||
|
||||
print("⚠️ ALL DEMO CREDENTIALS ARE INSECURE - For development only!")
|
||||
@@ -1224,11 +1224,11 @@ def print_summary(db: Session):
|
||||
print(" 1. Start development: make dev")
|
||||
print(" 2. Login as store:")
|
||||
print(" • Path-based: http://localhost:8000/store/ORION/login")
|
||||
print(" • Subdomain: http://orion.localhost:8000/store/login")
|
||||
print(" • Subdomain: http://orion.localhost:8000/store/login") # noqa: SEC034
|
||||
print(" 3. Visit store shop: http://localhost:8000/stores/ORION/shop/")
|
||||
print(" 4. Admin panel: http://localhost:8000/admin/login")
|
||||
print(f" Username: {settings.admin_username}")
|
||||
print(f" Password: {settings.admin_password}") # noqa: SEC-021
|
||||
print(f" Password: {settings.admin_password}") # noqa: SEC021
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -361,7 +361,7 @@ def test_public_shop_access():
|
||||
print_test("Public Shop Access (No Auth Required)")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/shop/products")
|
||||
response = requests.get(f"{BASE_URL}/shop/products") # noqa: SEC040, PERF040
|
||||
|
||||
if response.status_code == 200:
|
||||
print_success("Public shop pages accessible without auth")
|
||||
@@ -379,7 +379,7 @@ def test_health_check():
|
||||
print_test("Health Check")
|
||||
|
||||
try:
|
||||
response = requests.get(f"{BASE_URL}/health")
|
||||
response = requests.get(f"{BASE_URL}/health") # noqa: SEC040, PERF040
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
|
||||
@@ -72,7 +72,7 @@ def test_create_store_with_both_emails():
|
||||
print(f" Contact Email: {data['contact_email']}")
|
||||
print("\n🔑 Credentials:")
|
||||
print(f" Username: {data['owner_username']}")
|
||||
print(f" Password: {data['temporary_password']}") # noqa: SEC-021
|
||||
print(f" Password: {data['temporary_password']}") # noqa: SEC021
|
||||
print(f"\n🔗 Login URL: {data['login_url']}")
|
||||
return data["id"]
|
||||
print(f"❌ Failed: {response.status_code}")
|
||||
|
||||
@@ -66,13 +66,14 @@ class BaseValidator(ABC):
|
||||
"site", # mkdocs build output
|
||||
]
|
||||
|
||||
# Regex for noqa comments: # noqa, # noqa: RULE-001, # noqa: RULE-001, RULE-002
|
||||
# Regex for noqa comments. Supports both ruff-compatible (SEC001) and
|
||||
# human-readable (SEC-001) formats: # noqa: SEC001, # noqa: SEC001
|
||||
_NOQA_PATTERN = re.compile(
|
||||
r"#\s*noqa(?::\s*([A-Z]+-\d+(?:\s*,\s*[A-Z]+-\d+)*))?",
|
||||
r"#\s*noqa(?::\s*([A-Z]+-?\d+(?:\s*,\s*[A-Z]+-?\d+)*))?",
|
||||
)
|
||||
# Same for HTML comments: <!-- noqa: RULE-001 -->
|
||||
# Same for HTML comments: <!-- noqa: SEC015 -->
|
||||
_NOQA_HTML_PATTERN = re.compile(
|
||||
r"<!--\s*noqa(?::\s*([A-Z]+-\d+(?:\s*,\s*[A-Z]+-\d+)*))?\s*-->",
|
||||
r"<!--\s*noqa(?::\s*([A-Z]+-?\d+(?:\s*,\s*[A-Z]+-?\d+)*))?\s*-->",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
@@ -191,23 +192,32 @@ class BaseValidator(ABC):
|
||||
path_str = str(file_path)
|
||||
return any(pattern in path_str for pattern in self.IGNORE_PATTERNS)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_rule_id(code: str) -> str:
|
||||
"""Normalize rule ID by removing dashes: SEC-001 → SEC001."""
|
||||
return code.replace("-", "")
|
||||
|
||||
def _is_noqa_suppressed(self, line: str, rule_id: str) -> bool:
|
||||
"""Check if a line has a noqa comment suppressing the given rule.
|
||||
|
||||
Supports:
|
||||
Supports both ruff-compatible and human-readable formats:
|
||||
- ``# noqa`` — suppresses all rules
|
||||
- ``# noqa: SEC-001`` — suppresses specific rule
|
||||
- ``# noqa: SEC-001, SEC-002`` — suppresses multiple rules
|
||||
- ``<!-- noqa: SEC-015 -->`` — HTML comment variant
|
||||
- ``# noqa: SEC001`` — ruff-compatible (preferred)
|
||||
- ``# noqa: SEC001`` — human-readable (also accepted)
|
||||
- ``<!-- noqa: SEC015 -->`` — HTML comment variant
|
||||
"""
|
||||
normalized_id = self._normalize_rule_id(rule_id)
|
||||
for pattern in (self._NOQA_PATTERN, self._NOQA_HTML_PATTERN):
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
rule_list = match.group(1)
|
||||
if not rule_list:
|
||||
return True # bare # noqa → suppress everything
|
||||
suppressed = [r.strip() for r in rule_list.split(",")]
|
||||
if rule_id in suppressed:
|
||||
suppressed = [
|
||||
self._normalize_rule_id(r.strip())
|
||||
for r in rule_list.split(",")
|
||||
]
|
||||
if normalized_id in suppressed:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -1747,7 +1747,7 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message="Raw number input found - use number_stepper macro for consistent styling",
|
||||
context=stripped[:70],
|
||||
suggestion="{% from 'shared/macros/inputs.html' import number_stepper %}\n{{ number_stepper(model='fieldName', min=1, max=100) }}\nOr add {# noqa: FE-008 #} if this is intentional (e.g., ID field)",
|
||||
suggestion="{% from 'shared/macros/inputs.html' import number_stepper %}\n{{ number_stepper(model='fieldName', min=1, max=100) }}\nOr add {# noqa: FE008 #} if this is intentional (e.g., ID field)",
|
||||
)
|
||||
return # Only report once per file
|
||||
|
||||
@@ -2618,7 +2618,7 @@ class ArchitectureValidator:
|
||||
continue
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.match(r"\s*except\s+Exception\s*(as\s+\w+)?\s*:", line):
|
||||
if "noqa: EXC-003" in line or "noqa: exc-003" in line:
|
||||
if "noqa: EXC003" in line or "noqa: EXC-003" in line:
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="EXC-003",
|
||||
@@ -2628,7 +2628,7 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message="Broad 'except Exception' catches too many error types",
|
||||
context=line.strip(),
|
||||
suggestion="Catch specific exceptions instead, or add '# noqa: EXC-003' to suppress",
|
||||
suggestion="Catch specific exceptions instead, or add '# noqa: EXC003' to suppress",
|
||||
)
|
||||
|
||||
# EXC-004: Check exception inheritance in exceptions module
|
||||
@@ -4901,7 +4901,7 @@ class ArchitectureValidator:
|
||||
match = exception_class_pattern.match(line)
|
||||
if match:
|
||||
# Check for noqa suppression
|
||||
if "noqa: MOD-025" in line or "noqa: mod-025" in line:
|
||||
if "noqa: MOD025" in line or "noqa: MOD-025" in line:
|
||||
continue
|
||||
exception_classes.append((match.group(1), exc_file, i))
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ class AuditValidator(BaseValidator):
|
||||
r"logger\.\w+\(.*password\s*[=:]\s*['\"]?%", # password=%s
|
||||
r"logger\.\w+\(.*password\s*[=:]\s*\{", # password={var}
|
||||
r"logging\.\w+\(.*password\s*[=:]\s*['\"]?%", # password=%s
|
||||
r"print\(.*password\s*=", # print(password=xxx) # noqa: SEC-021
|
||||
r"print\(.*password\s*=", # print(password=xxx) # noqa: SEC021
|
||||
r"logger.*credit.*card.*\d", # credit card with numbers
|
||||
r"logger.*\bssn\b.*\d", # SSN with numbers
|
||||
],
|
||||
@@ -390,7 +390,7 @@ class AuditValidator(BaseValidator):
|
||||
pyproject = self.project_root / "pyproject.toml"
|
||||
if pyproject.exists():
|
||||
content = pyproject.read_text()
|
||||
if "http://" in content and "https://" not in content:
|
||||
if "http://" in content and "https://" not in content: # noqa: SEC034
|
||||
self.add_error(
|
||||
"THIRD-VEND-001",
|
||||
"Only HTTPS sources allowed for packages",
|
||||
|
||||
@@ -357,9 +357,9 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_command_injection(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-012: Check for command injection vulnerabilities"""
|
||||
patterns = [
|
||||
(r"subprocess.*shell\s*=\s*True", "shell=True in subprocess"), # noqa: SEC-012
|
||||
(r"os\.system\s*\(", "os.system()"), # noqa: SEC-012
|
||||
(r"os\.popen\s*\(", "os.popen()"), # noqa: SEC-012
|
||||
(r"subprocess.*shell\s*=\s*True", "shell=True in subprocess"), # noqa: SEC012
|
||||
(r"os\.system\s*\(", "os.system()"), # noqa: SEC012
|
||||
(r"os\.popen\s*\(", "os.popen()"), # noqa: SEC012
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -514,7 +514,7 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_https_enforcement(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-034: Check for HTTP instead of HTTPS"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r"http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|\$)", line):
|
||||
if re.search(r"http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|\$)", line): # noqa: SEC034
|
||||
if self._is_noqa_suppressed(line, "SEC-034") or "example.com" in line or "schemas" in line:
|
||||
continue
|
||||
if "http://www.w3.org" in line:
|
||||
@@ -527,7 +527,7 @@ class SecurityValidator(BaseValidator):
|
||||
line_number=i,
|
||||
message="HTTP URL found - use HTTPS for security",
|
||||
context=line.strip()[:80],
|
||||
suggestion="Replace http:// with https://",
|
||||
suggestion="Replace http:// with https://", # noqa: SEC034
|
||||
)
|
||||
|
||||
def _check_timeout_configuration(self, file_path: Path, content: str, lines: list[str]):
|
||||
@@ -648,7 +648,7 @@ class SecurityValidator(BaseValidator):
|
||||
"""SEC-047: Check for disabled certificate verification"""
|
||||
patterns = [
|
||||
(r"verify\s*=\s*False", "SSL verification disabled"),
|
||||
(r"CERT_NONE", "Certificate verification disabled"),
|
||||
(r"CERT_NONE", "Certificate verification disabled"), # noqa: SEC047
|
||||
(r"check_hostname\s*=\s*False", "Hostname verification disabled"),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user