"""Enum-like constants for the auditor domain. Kept as plain module-level strings so SQL filters and JSON payloads match exactly. No Python `Enum` class — same shape as the rest of the codebase (`leaver_blocked.models`, `relationships.constants`). """ from __future__ import annotations # ── MFA providers ────────────────────────────────────────────────────── MFA_PROVIDER_GOOGLE = "google" MFA_PROVIDER_FEISHU = "feishu" MFA_PROVIDER_ATLASSIAN = "atlassian" MFA_PROVIDER_GITHUB = "github" ALL_MFA_PROVIDERS = ( MFA_PROVIDER_GOOGLE, MFA_PROVIDER_FEISHU, MFA_PROVIDER_ATLASSIAN, MFA_PROVIDER_GITHUB, ) # ── Privileged review cycle status ───────────────────────────────────── PRIV_CYCLE_IN_PROGRESS = "in_progress" PRIV_CYCLE_COMPLETED = "completed" PRIV_CYCLE_CANCELLED = "cancelled" ALL_PRIV_CYCLE_STATUSES = ( PRIV_CYCLE_IN_PROGRESS, PRIV_CYCLE_COMPLETED, PRIV_CYCLE_CANCELLED, ) # ── Privileged role holder decision ──────────────────────────────────── PRIV_DECISION_PENDING = "pending" PRIV_DECISION_KEEP = "keep" PRIV_DECISION_REVOKE = "revoke" ALL_PRIV_DECISIONS = ( PRIV_DECISION_PENDING, PRIV_DECISION_KEEP, PRIV_DECISION_REVOKE, ) # ── Privileged role scopes ───────────────────────────────────────────── PRIV_SCOPE_HELIX = "helix" PRIV_SCOPE_GOOGLE = "google" PRIV_SCOPE_FEISHU = "feishu" PRIV_SCOPE_ATLASSIAN = "atlassian" PRIV_SCOPE_AWS = "aws" PRIV_SCOPE_GITHUB = "github" ALL_PRIV_SCOPES = ( PRIV_SCOPE_HELIX, PRIV_SCOPE_GOOGLE, PRIV_SCOPE_FEISHU, PRIV_SCOPE_ATLASSIAN, PRIV_SCOPE_AWS, PRIV_SCOPE_GITHUB, ) # ── Public exposure findings ─────────────────────────────────────────── EXPOSURE_SOURCE_AWS_SG = "aws_security_group" EXPOSURE_SOURCE_AWS_S3 = "aws_s3" EXPOSURE_SOURCE_ATLASSIAN = "atlassian_public_page" ALL_EXPOSURE_SOURCES = ( EXPOSURE_SOURCE_AWS_SG, EXPOSURE_SOURCE_AWS_S3, EXPOSURE_SOURCE_ATLASSIAN, ) EXPOSURE_TYPE_INGRESS_ANY = "ingress_0_0_0_0_any_port" EXPOSURE_TYPE_INGRESS_SENSITIVE = "ingress_0_0_0_0_sensitive_port" EXPOSURE_TYPE_S3_PUBLIC_ACL = "s3_public_acl" EXPOSURE_TYPE_S3_PUBLIC_POLICY = "s3_public_bucket_policy" EXPOSURE_TYPE_ATLASSIAN_PUBLIC_PAGE = "atlassian_anyone_can_view" EXPOSURE_SEVERITY_LOW = "low" EXPOSURE_SEVERITY_MEDIUM = "medium" EXPOSURE_SEVERITY_HIGH = "high" EXPOSURE_SEVERITY_CRITICAL = "critical" ALL_EXPOSURE_SEVERITIES = ( EXPOSURE_SEVERITY_LOW, EXPOSURE_SEVERITY_MEDIUM, EXPOSURE_SEVERITY_HIGH, EXPOSURE_SEVERITY_CRITICAL, ) EXPOSURE_STATUS_OPEN = "open" EXPOSURE_STATUS_RESOLVED = "resolved" EXPOSURE_STATUS_ACCEPTED_RISK = "accepted_risk" ALL_EXPOSURE_STATUSES = ( EXPOSURE_STATUS_OPEN, EXPOSURE_STATUS_RESOLVED, EXPOSURE_STATUS_ACCEPTED_RISK, ) # Sensitive ports — anything bound to 0.0.0.0/0 on these is critical. SENSITIVE_PORTS = frozenset({ 22, # SSH 23, # Telnet 3306, # MySQL 3389, # RDP 5432, # PostgreSQL 6379, # Redis 9200, # Elasticsearch 27017, # MongoDB 11211, # Memcached 5984, # CouchDB 8086, # InfluxDB 1433, # MSSQL 5601, # Kibana 9092, # Kafka 2181, # Zookeeper 2375, # Docker daemon 10250, # kubelet }) # ── Secret rotation ──────────────────────────────────────────────────── SECRET_KIND_API_KEY = "api_key" SECRET_KIND_CLOUD_ACCESS_KEY = "cloud_access_key" SECRET_KIND_SSL_CERT = "ssl_cert" SECRET_KIND_DB_PASSWORD = "db_password" SECRET_KIND_OAUTH_CLIENT_SECRET = "oauth_client_secret" ALL_SECRET_KINDS = ( SECRET_KIND_API_KEY, SECRET_KIND_CLOUD_ACCESS_KEY, SECRET_KIND_SSL_CERT, SECRET_KIND_DB_PASSWORD, SECRET_KIND_OAUTH_CLIENT_SECRET, ) SECRET_SOURCE_HELIX_APIKEY = "helix_apikey" SECRET_SOURCE_AWS = "aws" SECRET_SOURCE_GCP = "gcp" SECRET_SOURCE_AZURE = "azure" SECRET_SOURCE_ALIYUN = "aliyun" SECRET_SOURCE_TENCENT = "tencent" SECRET_SOURCE_AWS_ACM = "aws_acm" SECRET_SOURCE_MANUAL = "manual" SECRET_STATUS_CURRENT = "current" SECRET_STATUS_DUE_SOON = "due_soon" SECRET_STATUS_OVERDUE = "overdue" SECRET_STATUS_ROTATED = "rotated" # Default rotation periods (days) by secret kind. DEFAULT_ROTATION_PERIOD_DAYS = { SECRET_KIND_API_KEY: 90, SECRET_KIND_CLOUD_ACCESS_KEY: 90, SECRET_KIND_SSL_CERT: 365, SECRET_KIND_DB_PASSWORD: 90, SECRET_KIND_OAUTH_CLIENT_SECRET: 180, } # Threshold (days) for due_soon vs current. ROTATION_DUE_SOON_THRESHOLD_DAYS = 14 # ── Signin events ────────────────────────────────────────────────────── SIGNIN_PROVIDER_GOOGLE = "google" SIGNIN_PROVIDER_FEISHU = "feishu" SIGNIN_PROVIDER_HELIX = "helix" SIGNIN_PROVIDER_GITHUB = "github" ALL_SIGNIN_PROVIDERS = ( SIGNIN_PROVIDER_GOOGLE, SIGNIN_PROVIDER_FEISHU, SIGNIN_PROVIDER_HELIX, SIGNIN_PROVIDER_GITHUB, ) SIGNIN_STATUS_SUCCESS = "success" SIGNIN_STATUS_FAILURE = "failure" SIGNIN_STATUS_MFA_REQUIRED = "mfa_required" SIGNIN_STATUS_BLOCKED = "blocked" ANOMALY_LEVEL_NORMAL = "normal" ANOMALY_LEVEL_LOW = "low" ANOMALY_LEVEL_MEDIUM = "medium" ANOMALY_LEVEL_HIGH = "high" ANOMALY_LEVEL_CRITICAL = "critical" ALL_ANOMALY_LEVELS = ( ANOMALY_LEVEL_NORMAL, ANOMALY_LEVEL_LOW, ANOMALY_LEVEL_MEDIUM, ANOMALY_LEVEL_HIGH, ANOMALY_LEVEL_CRITICAL, ) # Anomaly score band → anomaly level. def anomaly_level_for_score(score: int) -> str: if score >= 80: return ANOMALY_LEVEL_CRITICAL if score >= 60: return ANOMALY_LEVEL_HIGH if score >= 40: return ANOMALY_LEVEL_MEDIUM if score >= 20: return ANOMALY_LEVEL_LOW return ANOMALY_LEVEL_NORMAL # Common reason codes for signin anomalies — populated as a # JSON list under ``anomaly_reasons``. ANOMALY_REASON_NEW_COUNTRY = "new_country" ANOMALY_REASON_UNUSUAL_HOUR = "unusual_hour" ANOMALY_REASON_RAPID_FAILED_ATTEMPTS = "rapid_failed_attempts" ANOMALY_REASON_LOGIN_AFTER_OFFBOARD = "login_after_offboard" ANOMALY_REASON_NEW_DEVICE = "new_device" ANOMALY_REASON_IMPOSSIBLE_TRAVEL = "impossible_travel" ANOMALY_REASON_KNOWN_BAD_IP = "known_bad_ip" # Scripted / automated client (curl, python-requests, headless Chrome). # Used to be reported as ``new_device``, which sent auditors chasing a # device-fingerprint story for what is really a scripted-access signal. ANOMALY_REASON_HEADLESS_UA = "headless_ua" ALL_ANOMALY_REASONS = ( ANOMALY_REASON_NEW_COUNTRY, ANOMALY_REASON_UNUSUAL_HOUR, ANOMALY_REASON_RAPID_FAILED_ATTEMPTS, ANOMALY_REASON_LOGIN_AFTER_OFFBOARD, ANOMALY_REASON_NEW_DEVICE, ANOMALY_REASON_IMPOSSIBLE_TRAVEL, ANOMALY_REASON_KNOWN_BAD_IP, ANOMALY_REASON_HEADLESS_UA, ) # ── PAT / MCP audit ──────────────────────────────────────────────────── PAT_KIND_GITHUB_PAT = "github_pat" PAT_KIND_GITHUB_FINEGRAINED = "github_finegrained" PAT_KIND_MCP_TOKEN = "mcp_token" PAT_KIND_OAUTH_GRANT = "oauth_grant" PAT_KIND_HELIX_API_KEY = "helix_api_key" PAT_KIND_HELIX_PAT = "helix_pat" PAT_KIND_ATLASSIAN_API_TOKEN = "atlassian_api_token" ALL_PAT_KINDS = ( PAT_KIND_GITHUB_PAT, PAT_KIND_GITHUB_FINEGRAINED, PAT_KIND_MCP_TOKEN, PAT_KIND_OAUTH_GRANT, PAT_KIND_HELIX_API_KEY, PAT_KIND_HELIX_PAT, ) PAT_SOURCE_GITHUB = "github" PAT_SOURCE_HELIX_APIKEY = "helix_apikey" PAT_SOURCE_HELIX_OAUTH = "helix_oauth" PAT_SOURCE_HELIX_PAT = "helix_pat" PAT_SOURCE_ATLASSIAN = "atlassian" PAT_STATUS_ACTIVE = "active" PAT_STATUS_REVOKED = "revoked" PAT_STATUS_EXPIRED = "expired" PAT_STATUS_DORMANT = "dormant" # Risk flag tags — populated as JSON list under ``risk_flags``. PAT_RISK_OVER_SCOPED = "over_scoped" PAT_RISK_NO_EXPIRY = "no_expiry" PAT_RISK_DORMANT = "dormant" PAT_RISK_OWNER_LEFT = "owner_left" PAT_RISK_BROAD_SCOPE_ADMIN = "broad_admin_scope" # Days without use to count as dormant (also overrides status to # 'dormant' on the next sync). PAT_DORMANT_DAYS = 90 # Token scope strings considered admin-grade — over-scoping risk. PAT_BROAD_ADMIN_SCOPES = frozenset({ "admin:org", "site_admin", "org:write", "delete_repo", "admin:enterprise", "manage_runners:org", }) # ── Cert + ICP expiry ────────────────────────────────────────────────── CERT_KIND_SSL = "ssl_cert" CERT_KIND_DOMAIN = "domain" CERT_KIND_ICP = "icp_filing" ALL_CERT_KINDS = ( CERT_KIND_SSL, CERT_KIND_DOMAIN, CERT_KIND_ICP, ) CERT_SOURCE_AWS_ACM = "aws_acm" CERT_SOURCE_MANUAL = "manual" CERT_SOURCE_ALIYUN_DOMAIN = "aliyun_domain" CERT_SOURCE_TENCENT_DOMAIN = "tencent_domain" CERT_ALERT_NONE = "none" CERT_ALERT_90D = "90d" CERT_ALERT_60D = "60d" CERT_ALERT_30D = "30d" CERT_ALERT_EXPIRED = "expired" ALL_CERT_ALERT_LEVELS = ( CERT_ALERT_NONE, CERT_ALERT_90D, CERT_ALERT_60D, CERT_ALERT_30D, CERT_ALERT_EXPIRED, ) CERT_STATUS_ACTIVE = "active" CERT_STATUS_EXPIRED = "expired" CERT_STATUS_RENEWED = "renewed" CERT_STATUS_RETIRED = "retired" def cert_alert_level_for_days(days_until_expiry: int) -> str: if days_until_expiry < 0: return CERT_ALERT_EXPIRED if days_until_expiry <= 30: return CERT_ALERT_30D if days_until_expiry <= 60: return CERT_ALERT_60D if days_until_expiry <= 90: return CERT_ALERT_90D return CERT_ALERT_NONE # ── Risk score levels ────────────────────────────────────────────────── RISK_LEVEL_LOW = "low" RISK_LEVEL_MEDIUM = "medium" RISK_LEVEL_HIGH = "high" RISK_LEVEL_CRITICAL = "critical" ALL_RISK_LEVELS = ( RISK_LEVEL_LOW, RISK_LEVEL_MEDIUM, RISK_LEVEL_HIGH, RISK_LEVEL_CRITICAL, ) def risk_level_for_score(score: int) -> str: if score >= 80: return RISK_LEVEL_CRITICAL if score >= 60: return RISK_LEVEL_HIGH if score >= 30: return RISK_LEVEL_MEDIUM return RISK_LEVEL_LOW # Risk factor weights — each factor contributes up to its cap. # Order: factor_name → (weight, cap) RISK_FACTOR_WEIGHTS = { "mfa_missing_provider": (15, 30), # missing MFA on >=1 provider "anomalous_signin_event": (10, 30), # any anomaly_level>=high "overdue_secret": (8, 20), # overdue secrets owned "dormant_pat": (5, 10), # PAT not used >= 90d "over_scoped_pat": (10, 20), # PAT with broad admin scope "owner_left_pat": (20, 20), # PAT belongs to a leaver "post_offboard_signin": (30, 30), # signed in after off-boarding "expired_cert_owned": (5, 10), # cert/ICP expired and theirs } # ── Evidence pack ────────────────────────────────────────────────────── EVIDENCE_STATUS_PENDING = "pending" EVIDENCE_STATUS_GENERATING = "generating" EVIDENCE_STATUS_COMPLETED = "completed" EVIDENCE_STATUS_FAILED = "failed" ALL_EVIDENCE_STATUSES = ( EVIDENCE_STATUS_PENDING, EVIDENCE_STATUS_GENERATING, EVIDENCE_STATUS_COMPLETED, EVIDENCE_STATUS_FAILED, ) EVIDENCE_MODULE_MFA = "mfa" EVIDENCE_MODULE_PRIVILEGED_REVIEW = "privileged_review" EVIDENCE_MODULE_PUBLIC_EXPOSURE = "public_exposure" EVIDENCE_MODULE_SECRET_ROTATION = "secret_rotation" EVIDENCE_MODULE_SIGNIN_ANOMALY = "signin_anomaly" EVIDENCE_MODULE_PAT_AUDIT = "pat_audit" EVIDENCE_MODULE_CERT_EXPIRY = "cert_expiry" EVIDENCE_MODULE_RISK_SCORE = "risk_score" # Access grants + the authority behind them. The eight modules above are # all security-DETECTION outputs (MFA enrolment, exposed endpoints, stale # secrets…); none of them answers "who had access to X, and who approved # it" — the CC6.x questions an external auditor actually opens with. This # module renders the frozen evidence bundles produced in the pack's # period, so the pack carries the same rows, hashes and citations the # forensics page shows. EVIDENCE_MODULE_ACCESS_EVIDENCE = "access_evidence" ALL_EVIDENCE_MODULES = ( EVIDENCE_MODULE_MFA, EVIDENCE_MODULE_PRIVILEGED_REVIEW, EVIDENCE_MODULE_PUBLIC_EXPOSURE, EVIDENCE_MODULE_SECRET_ROTATION, EVIDENCE_MODULE_SIGNIN_ANOMALY, EVIDENCE_MODULE_PAT_AUDIT, EVIDENCE_MODULE_CERT_EXPIRY, EVIDENCE_MODULE_RISK_SCORE, EVIDENCE_MODULE_ACCESS_EVIDENCE, ) # ── Unified findings (cross-control worklist) ────────────────────────── FINDING_STATUS_OPEN = "open" FINDING_STATUS_ACKNOWLEDGED = "acknowledged" FINDING_STATUS_ACCEPTED_RISK = "accepted_risk" FINDING_STATUS_RESOLVED = "resolved" ALL_FINDING_STATUSES = ( FINDING_STATUS_OPEN, FINDING_STATUS_ACKNOWLEDGED, FINDING_STATUS_ACCEPTED_RISK, FINDING_STATUS_RESOLVED, ) # Statuses that still demand attention — the worklist / posture-fail set. FINDING_OPEN_STATUSES = ( FINDING_STATUS_OPEN, FINDING_STATUS_ACKNOWLEDGED, ) # Decisions an operator can take on a finding. FINDING_DECISION_ACKNOWLEDGE = "acknowledge" FINDING_DECISION_ACCEPT_RISK = "accept_risk" FINDING_DECISION_RESOLVE = "resolve" FINDING_DECISION_REOPEN = "reopen" ALL_FINDING_DECISIONS = ( FINDING_DECISION_ACKNOWLEDGE, FINDING_DECISION_ACCEPT_RISK, FINDING_DECISION_RESOLVE, FINDING_DECISION_REOPEN, ) FINDING_SEVERITY_LOW = "low" FINDING_SEVERITY_MEDIUM = "medium" FINDING_SEVERITY_HIGH = "high" FINDING_SEVERITY_CRITICAL = "critical" ALL_FINDING_SEVERITIES = ( FINDING_SEVERITY_LOW, FINDING_SEVERITY_MEDIUM, FINDING_SEVERITY_HIGH, FINDING_SEVERITY_CRITICAL, ) # Sort weight so the worklist orders critical-first. FINDING_SEVERITY_RANK = { FINDING_SEVERITY_CRITICAL: 4, FINDING_SEVERITY_HIGH: 3, FINDING_SEVERITY_MEDIUM: 2, FINDING_SEVERITY_LOW: 1, } # Subject types — what a finding points at. FINDING_SUBJECT_PERSON = "person" FINDING_SUBJECT_RESOURCE = "resource" FINDING_SUBJECT_SECRET = "secret" FINDING_SUBJECT_TOKEN = "token" FINDING_SUBJECT_CERT = "cert" # Finding types per control (also the audit-action suffix). FINDING_TYPE_MFA_NOT_ENROLLED = "mfa_not_enrolled" FINDING_TYPE_SECRET_OVERDUE = "secret_overdue" FINDING_TYPE_CERT_EXPIRING = "cert_expiring" FINDING_TYPE_PAT_RISK = "pat_risk" FINDING_TYPE_PUBLIC_EXPOSURE = "public_exposure" FINDING_TYPE_SIGNIN_ANOMALY = "signin_anomaly" # How long completed evidence packs stay on disk before retention sweep. EVIDENCE_RETENTION_DAYS = 365 # Evidence pack on-disk root (relative to /var/helix/evidence in # production, /tmp/helix-evidence locally). EVIDENCE_PACK_ROOT_ENV = "HELIX_EVIDENCE_PACK_ROOT" EVIDENCE_PACK_DEFAULT_ROOT = "/tmp/helix-evidence"