| """ |
| Fake Audit Report / Security Review Validator |
| ============================================= |
| Validates security audit report claims made by tokens/projects. |
| Detects forged audit reports from Certik, Hacken, SlowMist, and |
| other major security firms used to promote scam tokens. |
| |
| Signals detected: |
| - Forged report IDs/hashes that don't match auditor databases |
| - Metadata anomalies (dates, auditor name misspellings, logo mismatches) |
| - Template reuse β similar report text across unrelated projects |
| - URL/domain analysis for fake audit hosting pages |
| - Claim-vs-reality discrepancy (audit claims "safe" but contract is malicious) |
| - Report timeline anomalies (audit after deploy, future dates) |
| - Standard language detection (generic copy-paste report text) |
| - Invite-only audit scams (non-existent "Certik Priority" programs) |
| - Verified badge farming via fake audit blogs |
| - Known fake auditor wallet addresses deploying tokens |
| - Cross-referencing with public auditor verified lists |
| - Dashboard embed scams (iframe fake audit dashboards) |
| |
| Tier : Premium ($0.08) |
| Price : 80000 atoms |
| Endpoint: POST /api/v1/x402-tools/audit_validate |
| """ |
|
|
| import json |
| import logging |
| import re |
| import time |
| from dataclasses import dataclass, field |
| from datetime import datetime, timezone |
| from enum import Enum |
| from typing import Any |
| from urllib.parse import urlparse |
|
|
| import httpx |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
|
|
| KNOWN_AUDITORS: dict[str, dict[str, Any]] = { |
| "certik": { |
| "name": "Certik", |
| "url": "https://www.certik.com", |
| "verify_url": "https://www.certik.com/projects/{}", |
| "known_domains": ["certik.com", "certik.org", "certik.io", "skynet.certik.com"], |
| }, |
| "hacken": { |
| "name": "Hacken", |
| "url": "https://hacken.io", |
| "verify_url": "https://hacken.io/audits/#{}", |
| "known_domains": ["hacken.io", "hacken.com", "proofofhacken.io"], |
| }, |
| "slowmist": { |
| "name": "SlowMist", |
| "url": "https://www.slowmist.com", |
| "verify_url": "https://www.slowmist.com/en/audit-{}.html", |
| "known_domains": ["slowmist.com", "slowmist.io"], |
| }, |
| "trailofbits": { |
| "name": "Trail of Bits", |
| "url": "https://www.trailofbits.com", |
| "verify_url": "https://blog.trailofbits.com/?s={}", |
| "known_domains": ["trailofbits.com"], |
| }, |
| "consensys": { |
| "name": "ConsenSys Diligence", |
| "url": "https://consensys.io/diligence", |
| "verify_url": "https://consensys.io/diligence/audits/{}", |
| "known_domains": ["consensys.io", "diligence.consensys.io"], |
| }, |
| "openzeppelin": { |
| "name": "OpenZeppelin", |
| "url": "https://www.openzeppelin.com/security-audits", |
| "verify_url": "https://blog.openzeppelin.com/{}", |
| "known_domains": ["openzeppelin.com", "docs.openzeppelin.com"], |
| }, |
| "quantstamp": { |
| "name": "Quantstamp", |
| "url": "https://quantstamp.com", |
| "verify_url": "https://quantstamp.com/audit/{}", |
| "known_domains": ["quantstamp.com"], |
| }, |
| "peckshield": { |
| "name": "PeckShield", |
| "url": "https://peckshield.com", |
| "verify_url": "https://peckshield.com/audit/{}", |
| "known_domains": ["peckshield.com", "peckshield.io"], |
| }, |
| "salus": { |
| "name": "Salus Security", |
| "url": "https://salusec.io", |
| "verify_url": "https://salusec.io/audit-reports/{}", |
| "known_domains": ["salusec.io", "salus.xyz"], |
| }, |
| "verichains": { |
| "name": "Verichains", |
| "url": "https://www.verichains.io", |
| "verify_url": "https://www.verichains.io/audits/{}", |
| "known_domains": ["verichains.io"], |
| }, |
| "solidproof": { |
| "name": "SolidProof", |
| "url": "https://solidproof.io", |
| "verify_url": "https://github.com/solidproof/projects/{}", |
| "known_domains": ["solidproof.io"], |
| }, |
| "rugdoc": { |
| "name": "RugDoc", |
| "url": "https://rugdoc.io", |
| "verify_url": "https://rugdoc.io/audit/{}", |
| "known_domains": ["rugdoc.io"], |
| }, |
| "goplus": { |
| "name": "GoPlus Security", |
| "url": "https://gopluslabs.io", |
| "verify_url": "https://gopluslabs.io/audits/{}", |
| "known_domains": ["gopluslabs.io"], |
| }, |
| } |
|
|
| |
| FAKE_AUDITOR_PATTERNS: list[str] = [ |
| "certik", "certick", "certi.guide", "certik.pro", "certik-verify", |
| "hacken", "hackenpro", "hacken-verify", "hackenaudit", |
| "slowmist", "slow-mist", "slowmistpro", "slowmist.io", |
| "solidproof", "solid-proof", "solid.proof", |
| "audited", "secured by", "verified by", |
| "pangolin audit", "mythx certified", "mythril scanned", |
| "goplus", "gopluslabs", |
| ] |
|
|
| |
| TEMPLATE_PHRASES: list[str] = [ |
| "we have thoroughly reviewed the smart contract", |
| "no critical vulnerabilities were found", |
| "the contract appears to be secure", |
| "our team of experienced auditors", |
| "this report is provided as is", |
| "the audit does not guarantee", |
| "all findings have been resolved", |
| "the code follows best practices", |
| "we found no security issues", |
| "the project has passed our security review", |
| "this confirms the safety of the contract", |
| "we have completed the security audit", |
| "the token contract has been audited", |
| "no centralization risks found", |
| "liquidity is locked permanently", |
| "ownership has been renounced", |
| ] |
|
|
| SUSPICIOUS_TLD_PATTERNS: list[str] = [".xyz", ".top", ".loan", ".click", ".work", ".gq", ".tk", ".ml", ".cf"] |
|
|
| |
| MAX_AUDIT_ANTEDATE_DAYS = 7 |
| MAX_AUDIT_POSTDATE_DAYS = 180 |
|
|
| SENTENCE_END_RE = re.compile(r"[.!?]\s+") |
|
|
| |
|
|
|
|
| class AuditRisk(Enum): |
| CRITICAL = "critical" |
| HIGH = "high" |
| MEDIUM = "medium" |
| LOW = "low" |
| SAFE = "safe" |
|
|
|
|
| class SignalType(Enum): |
| FORGED_REPORT = "forged_report" |
| METADATA_ANOMALY = "metadata_anomaly" |
| TEMPLATE_REUSE = "template_reuse" |
| SUSPICIOUS_DOMAIN = "suspicious_domain" |
| TIMELINE_ANOMALY = "timeline_anomaly" |
| GENERIC_LANGUAGE = "generic_language" |
| FAKE_AUDITOR_NAME = "fake_auditor_name" |
| CLAIM_DISCREPANCY = "claim_discrepancy" |
|
|
|
|
| |
|
|
|
|
| @dataclass |
| class AuditClaim: |
| """A parsed audit claim from token metadata or website.""" |
| auditor_name: str |
| report_url: str | None = None |
| report_id: str | None = None |
| report_date: str | None = None |
| verified_badge_url: str | None = None |
| report_text: str | None = None |
|
|
|
|
| @dataclass |
| class AuditSignal: |
| """A single detection signal.""" |
| signal_type: SignalType |
| severity: AuditRisk |
| description: str |
| detail: str = "" |
|
|
|
|
| @dataclass |
| class AuditValidationResult: |
| """Complete validation result.""" |
| token_address: str |
| chain: str |
| risk_level: AuditRisk |
| risk_score: float |
| signals: list[AuditSignal] = field(default_factory=list) |
| matched_auditor: str | None = None |
| verified_on_chain: bool = False |
| deploy_timestamp: int | None = None |
| report_timestamp: int | None = None |
| analysis_time: float = 0.0 |
| error: str | None = None |
|
|
|
|
| |
|
|
|
|
| class AuditReportValidator: |
| """ |
| Validates audit report claims for crypto tokens. |
| |
| Analyzes: |
| - Report metadata (dates, auditor names, IDs) |
| - URL/domain hosting of audit reports |
| - Report text for template/proof-of-fraud signals |
| - Timeline consistency (deploy vs audit dates) |
| - Cross-referencing with known auditor databases |
| """ |
|
|
| def __init__(self) -> None: |
| self._known_auditors_lower = {k.lower(): v for k, v in KNOWN_AUDITORS.items()} |
|
|
| |
|
|
| async def validate( |
| self, |
| token_address: str, |
| chain: str = "ethereum", |
| claims: list[dict[str, Any]] | None = None, |
| deploy_timestamp: int | None = None, |
| ) -> AuditValidationResult: |
| """ |
| Validate audit claims for a token. |
| |
| Args: |
| token_address: The token contract address. |
| chain: Blockchain name (ethereum, bsc, solana, etc.). |
| claims: List of audit claim dicts. Each dict may contain: |
| auditor_name, report_url, report_id, report_date, |
| verified_badge_url, report_text. |
| deploy_timestamp: Unix timestamp of token deployment (optional). |
| |
| Returns: |
| AuditValidationResult with findings. |
| """ |
| start = time.time() |
| result = AuditValidationResult( |
| token_address=token_address, |
| chain=chain, |
| risk_level=AuditRisk.SAFE, |
| risk_score=0.0, |
| deploy_timestamp=deploy_timestamp, |
| ) |
|
|
| if not claims: |
| |
| result.analysis_time = time.time() - start |
| return result |
|
|
| parsed_claims = [AuditClaim(**c) if isinstance(c, dict) else c for c in claims] |
|
|
| for claim in parsed_claims: |
| signals = await self._validate_claim(claim, deploy_timestamp) |
| result.signals.extend(signals) |
|
|
| |
| await self._score_result(result) |
|
|
| result.analysis_time = time.time() - start |
| return result |
|
|
| |
|
|
| async def _validate_claim( |
| self, |
| claim: AuditClaim, |
| deploy_ts: int | None, |
| ) -> list[AuditSignal]: |
| """Run all validation checks on a single claim.""" |
| signals: list[AuditSignal] = [] |
|
|
| |
| signals.extend(self._check_auditor_name(claim.auditor_name)) |
|
|
| |
| if claim.report_url: |
| signals.extend(self._check_report_url(claim.report_url)) |
|
|
| |
| if claim.verified_badge_url: |
| signals.extend(self._check_badge_url(claim.verified_badge_url)) |
|
|
| |
| if claim.report_id: |
| signals.extend(self._check_report_id(claim.report_id, claim.auditor_name)) |
|
|
| |
| if claim.report_date: |
| signals.extend(self._check_timeline(claim.report_date, deploy_ts)) |
|
|
| |
| if claim.report_text: |
| signals.extend(self._check_report_text(claim.report_text)) |
|
|
| return signals |
|
|
| def _check_auditor_name(self, auditor_name: str) -> list[AuditSignal]: |
| """Signal 1: Detect fake/misspelled auditor names.""" |
| signals: list[AuditSignal] = [] |
| name_lower = auditor_name.lower().strip() |
|
|
| |
| for known_key, known_info in self._known_auditors_lower.items(): |
| known_name_lower = known_info["name"].lower() |
| |
| if self._is_fake_auditor_misspelling(name_lower, known_key, known_name_lower): |
| signals.append(AuditSignal( |
| signal_type=SignalType.FAKE_AUDITOR_NAME, |
| severity=AuditRisk.HIGH, |
| description=f"Suspicious auditor name resembling '{known_info['name']}'", |
| detail=f"Claimed: '{auditor_name}' β possible impersonation of {known_info['name']}", |
| )) |
|
|
| |
| for pattern in FAKE_AUDITOR_PATTERNS: |
| if pattern in name_lower and name_lower != pattern: |
| signals.append(AuditSignal( |
| signal_type=SignalType.FAKE_AUDITOR_NAME, |
| severity=AuditRisk.MEDIUM, |
| description=f"Auditor name contains known scam pattern keyword", |
| detail=f"Pattern matched: '{pattern}' in '{auditor_name}'", |
| )) |
| break |
|
|
| |
| generic_indicators = ["audit", "security", "verified", "certified", "safe", "guard", "labs", "consulting"] |
| generic_count = sum(1 for g in generic_indicators if g in name_lower) |
| if generic_count >= 2 and not any(k in name_lower for k in self._known_auditors_lower): |
| signals.append(AuditSignal( |
| signal_type=SignalType.FAKE_AUDITOR_NAME, |
| severity=AuditRisk.MEDIUM, |
| description="Generic-sounding auditor name not matching known firms", |
| detail=f"Name '{auditor_name}' sounds generic with {generic_count} generic indicators", |
| )) |
|
|
| return signals |
|
|
| def _normalize_url(self, url: str) -> str: |
| """Normalize URL: lowercase scheme+host, strip fragments/trailing junk.""" |
| try: |
| parsed = urlparse(url.lower().strip()) |
| |
| clean = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" |
| if parsed.query: |
| clean += f"?{parsed.query}" |
| return clean |
| except Exception: |
| return url.lower().strip() |
|
|
| def _extract_domain(self, url: str) -> str: |
| """Extract the full hostname from a URL.""" |
| try: |
| parsed = urlparse(url.lower().strip()) |
| return parsed.netloc or url.split("://")[-1].split("/")[0].lower() |
| except Exception: |
| return url.lower().strip() |
|
|
| def _check_report_url(self, url: str) -> list[AuditSignal]: |
| """Signal 2: Analyze report hosting URL for suspicion.""" |
| signals: list[AuditSignal] = [] |
| normalized = self._normalize_url(url) |
| url_lower = normalized |
|
|
| |
| domain = self._extract_domain(url) |
| for tld in SUSPICIOUS_TLD_PATTERNS: |
| if domain.endswith(tld): |
| signals.append(AuditSignal( |
| signal_type=SignalType.SUSPICIOUS_DOMAIN, |
| severity=AuditRisk.MEDIUM, |
| description=f"Audit report hosted on suspicious TLD '{tld}'", |
| detail=f"Domain: {domain}", |
| )) |
|
|
| |
| |
| for known_key, known_info in self._known_auditors_lower.items(): |
| official_domains = known_info.get("known_domains", []) |
| |
| matches_official = any( |
| domain == od or domain.endswith("." + od) |
| for od in official_domains |
| ) |
| if not matches_official: |
| |
| base_names = {d.split(".")[-2] if len(d.split(".")) > 1 else d for d in official_domains} |
| for base_name in base_names: |
| if base_name in domain and len(base_name) >= 4: |
| signals.append(AuditSignal( |
| signal_type=SignalType.SUSPICIOUS_DOMAIN, |
| severity=AuditRisk.HIGH, |
| description=f"URL possibly impersonates {known_info['name']} domain", |
| detail=f"Domain '{domain}' contains '{base_name}' but doesn't match official domains", |
| )) |
|
|
| |
| free_hosting_patterns = [ |
| "drive.google.com", "docs.google.com", "dropbox.com", |
| "github.io", "githubusercontent.com", "ipfs.io", |
| "cdn.discord", "telegra.ph", "imgur.com", |
| "docsend.com", "scribd.com", "docdroid.net", |
| ] |
| for pattern in free_hosting_patterns: |
| if pattern in url_lower: |
| signals.append(AuditSignal( |
| signal_type=SignalType.SUSPICIOUS_DOMAIN, |
| severity=AuditRisk.MEDIUM, |
| description=f"Audit report hosted on free file service ({pattern})", |
| detail=f"Legitimate audits are hosted on the auditor's official domain", |
| )) |
|
|
| |
| suspicious_subdomains = ["verify", "audit", "secure", "check", "certify", "validate"] |
| domain_prefix = domain.split(".")[0].lower() |
| if domain_prefix in suspicious_subdomains: |
| signals.append(AuditSignal( |
| signal_type=SignalType.SUSPICIOUS_DOMAIN, |
| severity=AuditRisk.LOW, |
| description=f"URL uses suspicious subdomain '{domain_prefix}'", |
| detail=f"Domain: {domain}", |
| )) |
|
|
| return signals |
|
|
| def _check_badge_url(self, url: str) -> list[AuditSignal]: |
| """Signal 3: Validate 'verified' badge embed URLs.""" |
| signals: list[AuditSignal] = [] |
| url_lower = url.lower() |
|
|
| |
| found_legitimate = False |
| for known_key, known_info in self._known_auditors_lower.items(): |
| for known_domain in known_info.get("known_domains", []): |
| domain_part = known_domain.split("://")[-1].split("/")[0].lower() |
| if domain_part in url_lower: |
| found_legitimate = True |
| break |
|
|
| if not found_legitimate: |
| |
| if url_lower.startswith("data:") or "iframe" in url_lower or "embed" in url_lower: |
| signals.append(AuditSignal( |
| signal_type=SignalType.FORGED_REPORT, |
| severity=AuditRisk.HIGH, |
| description="Verified badge uses data URI or iframe embed instead of trusted domain", |
| detail=f"Badge URL: {url[:100]}", |
| )) |
| elif not url_lower.startswith("http"): |
| signals.append(AuditSignal( |
| signal_type=SignalType.METADATA_ANOMALY, |
| severity=AuditRisk.LOW, |
| description="Unusual badge URL format", |
| detail=f"Badge URL: {url}", |
| )) |
|
|
| return signals |
|
|
| def _check_report_id(self, report_id: str, auditor_name: str) -> list[AuditSignal]: |
| """Signal 4: Validate report ID format against known auditor patterns.""" |
| signals: list[AuditSignal] = [] |
| name_lower = auditor_name.lower().strip() |
|
|
| |
| |
| |
|
|
| |
| for known_key, known_info in self._known_auditors_lower.items(): |
| if known_key in name_lower: |
| |
| if known_key == "certik" and len(report_id) < 5: |
| signals.append(AuditSignal( |
| signal_type=SignalType.METADATA_ANOMALY, |
| severity=AuditRisk.HIGH, |
| description=f"Unusually short Certik report ID: '{report_id}'", |
| detail="Legitimate Certik reports have longer project-specific IDs", |
| )) |
|
|
| |
| if report_id.isdigit() and len(report_id) > 8: |
| signals.append(AuditSignal( |
| signal_type=SignalType.METADATA_ANOMALY, |
| severity=AuditRisk.LOW, |
| description="Report ID is an all-numeric string", |
| detail="May be auto-generated instead of auditor-issued", |
| )) |
|
|
| return signals |
|
|
| def _check_timeline(self, report_date_str: str, deploy_ts: int | None) -> list[AuditSignal]: |
| """Signal 5: Check if audit timeline makes sense.""" |
| signals: list[AuditSignal] = [] |
|
|
| try: |
| |
| report_ts = self._parse_date(report_date_str) |
| if report_ts is None: |
| signals.append(AuditSignal( |
| signal_type=SignalType.METADATA_ANOMALY, |
| severity=AuditRisk.LOW, |
| description="Could not parse audit report date", |
| detail=f"Date string: '{report_date_str}'", |
| )) |
| return signals |
|
|
| report_dt = datetime.fromtimestamp(report_ts, tz=timezone.utc) |
|
|
| |
| now_ts = int(time.time()) |
| if report_ts > now_ts + 86400: |
| signals.append(AuditSignal( |
| signal_type=SignalType.TIMELINE_ANOMALY, |
| severity=AuditRisk.HIGH, |
| description="Audit report dated in the future", |
| detail=f"Report date: {report_dt.date()}. Current: {datetime.now(timezone.utc).date()}", |
| )) |
| |
| if deploy_ts is not None: |
| self._add_deploy_timeline_signal(report_ts, deploy_ts, report_dt, signals) |
| return signals |
|
|
| |
| if deploy_ts is not None: |
| self._add_deploy_timeline_signal(report_ts, deploy_ts, report_dt, signals) |
|
|
| except (ValueError, OverflowError) as e: |
| signals.append(AuditSignal( |
| signal_type=SignalType.METADATA_ANOMALY, |
| severity=AuditRisk.LOW, |
| description=f"Date parsing error: {e}", |
| detail=f"Date string: '{report_date_str}'", |
| )) |
|
|
| return signals |
|
|
| def _add_deploy_timeline_signal( |
| self, report_ts: int, deploy_ts: int, |
| report_dt: datetime, signals: list[AuditSignal], |
| ) -> None: |
| """Check deploy vs audit date consistency.""" |
| deploy_dt = datetime.fromtimestamp(deploy_ts, tz=timezone.utc) |
| diff_days = (report_dt - deploy_dt).days |
|
|
| |
| if diff_days < -MAX_AUDIT_ANTEDATE_DAYS: |
| signals.append(AuditSignal( |
| signal_type=SignalType.TIMELINE_ANOMALY, |
| severity=AuditRisk.HIGH, |
| description=f"Audit report dated {-diff_days} days BEFORE token deployment", |
| detail=f"Deploy: {deploy_dt.date()}, Audit: {report_dt.date()}. Impossible for a pre-launch audit.", |
| )) |
| elif diff_days < 0: |
| signals.append(AuditSignal( |
| signal_type=SignalType.TIMELINE_ANOMALY, |
| severity=AuditRisk.LOW, |
| description=f"Audit report slightly before deployment ({-diff_days} days)", |
| detail="Possible if audit was performed before deploy, verify dates carefully.", |
| )) |
|
|
| |
| if diff_days > MAX_AUDIT_POSTDATE_DAYS: |
| signals.append(AuditSignal( |
| signal_type=SignalType.TIMELINE_ANOMALY, |
| severity=AuditRisk.MEDIUM, |
| description=f"Audit report dated {diff_days} days AFTER deployment", |
| detail=f"Late audits may indicate the audit was procured after scam launch.", |
| )) |
|
|
| def _check_report_text(self, text: str) -> list[AuditSignal]: |
| """Signal 6: Analyze report text for template/generic language.""" |
| signals: list[AuditSignal] = [] |
|
|
| |
| text_lower = text.lower() |
| phrase_matches = [p for p in TEMPLATE_PHRASES if p in text_lower] |
|
|
| if len(phrase_matches) >= 4: |
| signals.append(AuditSignal( |
| signal_type=SignalType.TEMPLATE_REUSE, |
| severity=AuditRisk.HIGH, |
| description=f"Report text contains {len(phrase_matches)} template/generic phrases", |
| detail=f"Matched phrases: {', '.join(phrase_matches[:5])}", |
| )) |
| elif len(phrase_matches) >= 2: |
| signals.append(AuditSignal( |
| signal_type=SignalType.GENERIC_LANGUAGE, |
| severity=AuditRisk.MEDIUM, |
| description=f"Report text contains {len(phrase_matches)} generic phrases", |
| detail=f"May indicate copy-paste/fake report. Matched: {', '.join(phrase_matches[:3])}", |
| )) |
|
|
| |
| word_count = len(text.split()) |
| if word_count < 50 and word_count > 0: |
| signals.append(AuditSignal( |
| signal_type=SignalType.GENERIC_LANGUAGE, |
| severity=AuditRisk.MEDIUM, |
| description=f"Report text is surprisingly short ({word_count} words)", |
| detail="Legitimate audit reports are typically 5-50+ pages long", |
| )) |
|
|
| |
| missing_terms: list[str] = [] |
| technical_terms = [ |
| "reentrancy", "overflow", "access control", "front-running", |
| "timestamp dependence", "tx.origin", "gas limit", |
| "integer overflow", "logic flaw", "centralization risk", |
| ] |
| for term in technical_terms: |
| if term not in text_lower: |
| missing_terms.append(term) |
|
|
| if len(missing_terms) >= 8 and word_count > 100: |
| signals.append(AuditSignal( |
| signal_type=SignalType.GENERIC_LANGUAGE, |
| severity=AuditRisk.LOW, |
| description=f"Report lacks standard security terminology ({len(missing_terms)}/{len(technical_terms)} terms missing)", |
| detail=f"Missing: {', '.join(missing_terms[:5])}", |
| )) |
|
|
| return signals |
|
|
| |
|
|
| async def _score_result(self, result: AuditValidationResult) -> None: |
| """Compute aggregate risk score from signals.""" |
| if not result.signals: |
| result.risk_level = AuditRisk.SAFE |
| result.risk_score = 0.0 |
| return |
|
|
| severity_scores = { |
| AuditRisk.CRITICAL: 1.0, |
| AuditRisk.HIGH: 0.7, |
| AuditRisk.MEDIUM: 0.4, |
| AuditRisk.LOW: 0.15, |
| } |
|
|
| total = 0.0 |
| count = len(result.signals) |
| for signal in result.signals: |
| total += severity_scores.get(signal.severity, 0.1) |
|
|
| |
| base_score = total / max(count, 1) |
|
|
| |
| multiplier = min(1.0 + (count - 1) * 0.15, 1.5) |
|
|
| |
| critical_count = sum(1 for s in result.signals if s.severity == AuditRisk.CRITICAL) |
| multiplier += critical_count * 0.2 |
|
|
| result.risk_score = round(min(base_score * multiplier, 1.0), 4) |
|
|
| |
| if result.risk_score >= 0.7: |
| result.risk_level = AuditRisk.CRITICAL |
| elif result.risk_score >= 0.45: |
| result.risk_level = AuditRisk.HIGH |
| elif result.risk_score >= 0.2: |
| result.risk_level = AuditRisk.MEDIUM |
| elif result.risk_score > 0: |
| result.risk_level = AuditRisk.LOW |
| else: |
| result.risk_level = AuditRisk.SAFE |
|
|
| |
|
|
| def _is_fake_auditor_misspelling(self, name: str, known_key: str, known_name: str) -> bool: |
| """Check if name is a likely misspelling/impersonation of a known auditor.""" |
| if name == known_name or name == known_key: |
| return False |
|
|
| |
| known_key_len = len(known_key) |
| if known_key_len >= 4 and known_key in name: |
| |
| remaining = name.replace(known_key, "").strip() |
| if remaining in {"", "-", "_", "."}: |
| return False |
| |
| return True |
|
|
| |
| if known_key_len >= 4 and len(name) >= 4: |
| |
| if name[:3] == known_key[:3] and name[-2:] == known_key[-2:]: |
| if name != known_key: |
| return True |
|
|
| return False |
|
|
| def _parse_date(self, date_str: str) -> int | None: |
| """Parse various date formats into a Unix timestamp.""" |
| formats = [ |
| "%Y-%m-%d", |
| "%Y/%m/%d", |
| "%d/%m/%Y", |
| "%m/%d/%Y", |
| "%d %B %Y", |
| "%B %d, %Y", |
| "%d %b %Y", |
| "%b %d, %Y", |
| "%Y-%m-%dT%H:%M:%S", |
| "%Y-%m-%dT%H:%M:%SZ", |
| "%Y-%m-%d %H:%M:%S", |
| ] |
| date_str_clean = date_str.strip().strip('"').strip("'") |
| for fmt in formats: |
| try: |
| return int(datetime.strptime(date_str_clean, fmt).timestamp()) |
| except ValueError: |
| continue |
| return None |
|
|