System Weaknesses

security

System Weaknesses

Definition

A system weakness is a flaw, deficiency, or suboptimal condition in a software system's design, implementation, configuration, or operational procedure that could potentially be exploited to violate the system's security properties (confidentiality, integrity, or availability). Weaknesses exist on a spectrum from theoretical (a design pattern that could be exploited under specific conditions) to practical (a coding error that creates an immediately exploitable vulnerability).

The distinction between weakness, vulnerability, and exploit is critical for prioritization. A weakness is a general flaw category (e.g., insufficient input validation). A vulnerability is a specific, confirmed instance of a weakness in a particular system (e.g., SQL injection in the login endpoint). An exploit is a concrete technique that leverages a vulnerability to achieve unauthorized access. Weakness identification focuses on the broadest category, seeking to eliminate entire classes of potential vulnerabilities through architectural decisions, coding standards, and automated detection.

In Elixir/OTP systems, the BEAM VM's process isolation, immutable data structures, and pattern matching provide inherent protection against many weakness categories common in other platforms. However, BEAM systems introduce their own weakness patterns related to atom exhaustion, distribution protocol security, ETS access control, and the unique interaction between Erlang's trust model and modern web application security requirements.

Historical Context and Evolution

The formal study of system weaknesses began with the Orange Book (TCSEC) in 1983, which classified computer systems by their security capabilities. The Common Criteria (ISO 15408) expanded this into a comprehensive framework for security evaluation. MITRE's Common Weakness Enumeration (CWE), launched in 2006, created the first systematic taxonomy of software weaknesses, cataloging over 900 distinct weakness types organized into hierarchical categories.

The OWASP Top 10, first published in 2003, provided a practitioner-focused ranking of the most critical web application security risks. Its evolution from injection-focused early versions to the 2021 edition's emphasis on design flaws and supply chain risks reflects the maturation of the field from reactive vulnerability patching to proactive weakness prevention.

Threat modeling methodologies evolved in parallel. Microsoft's STRIDE (Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege) provided a systematic framework for identifying threats. PASTA (Process for Attack Simulation and Threat Analysis) added risk-centric analysis with attacker profiling. Attack trees, introduced by Bruce Schneier, formalized hierarchical threat decomposition.

The Prismatic Platform's security approach synthesizes these traditions through its 6-team Color-Team Security Operations, where 20 specialized security agents conduct continuous adversarial simulation, boundary exploration, and constructive verification.

Platform Context

The Prismatic Platform treats weakness identification as a continuous, automated process rather than a periodic manual activity. Multiple platform subsystems contribute to weakness detection and remediation.

The Color-Team Security Operations deploys 20 agents across 6 color teams (Gray, Red, Blue, Purple, White, Black) for adversarial-defensive security synthesis. The Gray team explores boundary conditions and specification gaps. The Red team simulates epistemic attacks against the platform's reasoning. The Blue team maintains defensive posture through signal aggregation and drift detection. The Purple team synthesizes Red and Blue findings into closure assessments. The White team provides constructive verification through formal proofs. The Black team models theoretical threats at maximum abstraction.

The Prismatic Perimeter EASM module extends weakness detection to the external attack surface, assessing security ratings (A-F grades, 300-900 numeric scores), identifying exposed assets, and evaluating compliance with NIS2 and ZKB regulations.

The Quality Floor Guardian monitors for code-level weakness patterns across 13 quality domains, blocking commits that introduce known vulnerability patterns such as unsafe map access, missing type specifications, or hardcoded credentials.

The 13-layer Trinity Gate requires that every security claim passes structural consistency, logical consistency, and formal necessity checks before being accepted, preventing false certainty about security posture.

Weakness Taxonomy

CWE Categories Relevant to Elixir/OTP

The Common Weakness Enumeration organizes weaknesses into hierarchical categories. Several categories are particularly relevant to Elixir/OTP systems.

CWE-20: Improper Input Validation remains the most prevalent weakness category. While Elixir's pattern matching and Ecto changesets provide strong input validation primitives, weaknesses arise when validation is incomplete, inconsistent across endpoints, or bypassed by internal calls that skip the validation layer.

CWE-200: Exposure of Sensitive Information encompasses information leakage through error messages, logs, and debugging interfaces. Elixir's default error pages in development mode expose stack traces and variable bindings. The BEAM's :observer and distribution protocol can expose internal system state if not properly secured in production.

CWE-400: Uncontrolled Resource Consumption covers denial-of-service through resource exhaustion. In BEAM systems, this manifests as atom table exhaustion (converting user input to atoms), process creation storms (unbounded Task.async), ETS table growth (unbounded cache without eviction), and message queue buildup (slow consumers in producer-consumer patterns).

CWE-862: Missing Authorization involves operations that should require authorization but do not check it. In Phoenix LiveView applications, a common weakness is failing to verify authorization in handle_event callbacks, assuming that UI-level restrictions are sufficient.

BEAM-Specific Weakness Patterns

The BEAM VM introduces unique weakness patterns not found in other platforms.

defmodule Prismatic.Security.WeaknessDetector do
  @moduledoc """
  Static and runtime detection of BEAM-specific weakness patterns.
  Identifies atom exhaustion risks, distribution protocol exposures,
  ETS access control gaps, and other BEAM-unique vulnerability classes.
  Emits telemetry events for integration with security monitoring.
  """

  require Logger

  @type weakness :: %{
          category: String.t(),
          cwe: String.t(),
          severity: :critical | :high | :medium | :low | :info,
          description: String.t(),
          location: String.t() | nil,
          remediation: String.t()
        }

  @type scan_result :: %{
          weaknesses: [weakness()],
          scanned_at: DateTime.t(),
          duration_ms: non_neg_integer(),
          modules_scanned: non_neg_integer()
        }

  @spec scan_atom_exhaustion() :: [weakness()]
  def scan_atom_exhaustion do
    atom_count = :erlang.system_info(:atom_count)
    atom_limit = :erlang.system_info(:atom_limit)
    usage_pct = atom_count / atom_limit * 100

    cond do
      usage_pct > 80 ->
        [%{
          category: "resource_exhaustion",
          cwe: "CWE-400",
          severity: :critical,
          description: "Atom table at #{Float.round(usage_pct, 1)}% capacity " <>
            "(#{atom_count}/#{atom_limit}). VM crash imminent if growth continues.",
          location: nil,
          remediation: "Audit all String.to_atom/1 calls. Replace with " <>
            "String.to_existing_atom/1 for user input. Review dynamic module creation."
        }]

      usage_pct > 50 ->
        [%{
          category: "resource_exhaustion",
          cwe: "CWE-400",
          severity: :high,
          description: "Atom table at #{Float.round(usage_pct, 1)}% capacity. " <>
            "Investigate sources of atom creation.",
          location: nil,
          remediation: "Enable atom counting with :persistent_term to track " <>
            "growth sources. Review dynamic module loading patterns."
        }]

      true ->
        []
    end
  end

  @spec scan_distribution_security() :: [weakness()]
  def scan_distribution_security do
    weaknesses = []

    weaknesses =
      if Node.alive?() do
        cookie = Node.get_cookie()

        cookie_weakness =
          if cookie == :nocookie do
            [%{
              category: "authentication",
              cwe: "CWE-287",
              severity: :critical,
              description: "Erlang distribution running with default cookie. " <>
                "Any node can connect and execute arbitrary code.",
              location: "runtime:distribution",
              remediation: "Set a strong, unique cookie via RELEASE_COOKIE " <>
                "environment variable or vm.args."
            }]
          else
            []
          end

        epmd_weakness =
          case :net_adm.names() do
            {:ok, names} when length(names) > 1 ->
              [%{
                category: "information_disclosure",
                cwe: "CWE-200",
                severity: :medium,
                description: "EPMD exposes #{length(names)} registered node names. " <>
                  "Node names may reveal infrastructure topology.",
                location: "runtime:epmd",
                remediation: "Restrict EPMD to localhost. Consider using " <>
                  "alternative node discovery (libcluster with Kubernetes)."
              }]

            _ ->
              []
          end

        cookie_weakness ++ epmd_weakness
      else
        weaknesses
      end

    weaknesses
  end

  @spec scan_ets_access_control() :: [weakness()]
  def scan_ets_access_control do
    :ets.all()
    |> Enum.flat_map(fn table ->
      info = :ets.info(table)

      if info != :undefined do
        protection = Keyword.get(info, :protection, :protected)
        name = Keyword.get(info, :name, table)

        if protection == :public do
          [%{
            category: "access_control",
            cwe: "CWE-862",
            severity: :medium,
            description: "ETS table '#{name}' has public access. Any process " <>
              "can read and write to this table.",
            location: "ets:#{name}",
            remediation: "Change to :protected access unless public write " <>
              "is explicitly required. Use a GenServer wrapper for controlled access."
          }]
        else
          []
        end
      else
        []
      end
    end)
  end

  @spec full_scan() :: scan_result()
  def full_scan do
    start_time = System.monotonic_time(:millisecond)

    weaknesses =
      scan_atom_exhaustion() ++
        scan_distribution_security() ++
        scan_ets_access_control()

    duration = System.monotonic_time(:millisecond) - start_time

    result = %{
      weaknesses: weaknesses,
      scanned_at: DateTime.utc_now(),
      duration_ms: duration,
      modules_scanned: length(:code.all_loaded())
    }

    :telemetry.execute(
      [:prismatic, :security, :weakness_scan],
      %{
        weakness_count: length(weaknesses),
        duration_ms: duration,
        critical: Enum.count(weaknesses, &(&1.severity == :critical)),
        high: Enum.count(weaknesses, &(&1.severity == :high))
      },
      %{}
    )

    Logger.info(
      "Weakness scan complete: #{length(weaknesses)} findings in #{duration}ms"
    )

    result
  end
end

Threat Modeling Methodologies

STRIDE Analysis for Elixir/OTP

The STRIDE methodology systematically examines each component for six threat categories.

Spoofing threats involve impersonating another entity. In Elixir/OTP systems, spoofing risks include Erlang node impersonation via stolen distribution cookies, Phoenix session forgery through weak secret key bases, and LiveView channel hijacking through token manipulation. Mitigation requires strong cookie management, secure session configuration, and token verification on every channel join.

Tampering threats involve unauthorized data modification. Risks include ETS table modification by unauthorized processes (when tables are :public), message interception in distribution protocol (unencrypted by default), and request parameter manipulation in Phoenix controllers. Mitigation uses :protected ETS access, TLS for distribution, and comprehensive input validation.

Repudiation threats involve denying actions performed. Risks include missing audit trails for administrative operations, inadequate logging of security-relevant events, and timestamp manipulation. Mitigation requires immutable audit logging, cryptographic log integrity, and NTP synchronization.

Information Disclosure threats involve unauthorized data access. Risks include stack trace exposure in error responses, BEAM introspection tool access in production, and verbose logging of sensitive data. Mitigation requires production error handlers, secured :observer access, and log sanitization.

Denial of Service threats involve resource exhaustion. BEAM-specific risks include atom table exhaustion, process creation storms, message queue buildup, and ETS memory exhaustion. Mitigation uses bounded resource creation, backpressure mechanisms, and circuit breakers.

Elevation of Privilege threats involve gaining unauthorized capabilities. Risks include missing authorization checks in LiveView event handlers, process impersonation through PID guessing, and module code injection through dynamic compilation. Mitigation requires consistent authorization enforcement, process registry-based addressing, and restricted code compilation.

Attack Tree Analysis

Attack trees decompose high-level threats into hierarchical sequences of attacker actions. For the Prismatic Platform, the top-level goal "Compromise Platform Integrity" decomposes into branches: exploit application vulnerability, compromise supply chain, social engineer operator, exploit infrastructure weakness, and abuse legitimate functionality.

Each branch further decomposes until reaching atomic attack steps that can be individually assessed for feasibility, cost, and detectability. This decomposition drives the platform's security investment -- resources are allocated to mitigate the most feasible and impactful attack paths.

Defense in Depth Architecture

The Prismatic Platform implements defense in depth through multiple overlapping security layers.

Layer 1: Network Perimeter

Fly.io's infrastructure provides DDoS protection, TLS termination, and network-level access control. The Prismatic Perimeter module monitors the external attack surface for exposed services and misconfigurations.

Layer 2: Application Gateway

Phoenix's Plug pipeline enforces authentication, authorization, rate limiting, CORS policy, and request validation before any business logic executes. The API gateway on port 4004 adds OpenApiSpex schema validation for all API requests.

Layer 3: Process Isolation

The BEAM VM's process isolation ensures that a compromised process cannot directly access another process's memory. This provides inherent containment that limits the blast radius of exploitation.

Layer 4: Input Validation

Ecto changesets, custom validators, and schema-driven validation (OpenApiSpex) ensure that all data entering the system conforms to expected formats and constraints. The platform's forbidden patterns enforcement blocks common vulnerability patterns at commit time.

Layer 5: Monitoring and Detection

The Quality Floor Guardian, Color-Team agents, and telemetry-based anomaly detection provide continuous monitoring for weakness exploitation indicators. The Autoheal system can automatically respond to detected anomalies.

Automated Weakness Detection Pipeline

The Prismatic Platform automates weakness detection through a multi-phase pipeline integrated with the development lifecycle.

defmodule Prismatic.Security.WeaknessPipeline do
  @moduledoc """
  Orchestrates automated weakness detection across multiple analysis
  phases: static analysis (Credo, Dialyzer), dependency audit
  (mix deps.audit), runtime scanning, and configuration review.
  Integrates with pre-commit hooks and CI/CD gates.
  """

  require Logger

  @type phase :: :static_analysis | :dependency_audit | :runtime_scan | :config_review
  @type pipeline_result :: %{
          phases: %{phase() => phase_result()},
          overall_status: :pass | :warn | :fail,
          total_weaknesses: non_neg_integer(),
          critical_count: non_neg_integer(),
          started_at: DateTime.t(),
          completed_at: DateTime.t()
        }
  @type phase_result :: %{
          status: :pass | :warn | :fail,
          weaknesses: [Prismatic.Security.WeaknessDetector.weakness()],
          duration_ms: non_neg_integer()
        }

  @spec run(keyword()) :: pipeline_result()
  def run(opts \\ []) do
    phases = Keyword.get(opts, :phases, [:static_analysis, :dependency_audit,
                                         :runtime_scan, :config_review])
    started_at = DateTime.utc_now()

    phase_results =
      phases
      |> Enum.map(fn phase ->
        start_time = System.monotonic_time(:millisecond)
        weaknesses = execute_phase(phase)
        duration = System.monotonic_time(:millisecond) - start_time

        status =
          cond do
            Enum.any?(weaknesses, &(&1.severity == :critical)) -> :fail
            Enum.any?(weaknesses, &(&1.severity == :high)) -> :warn
            true -> :pass
          end

        {phase, %{status: status, weaknesses: weaknesses, duration_ms: duration}}
      end)
      |> Map.new()

    all_weaknesses =
      phase_results
      |> Map.values()
      |> Enum.flat_map(& &1.weaknesses)

    critical_count = Enum.count(all_weaknesses, &(&1.severity == :critical))

    overall =
      cond do
        critical_count > 0 -> :fail
        Enum.any?(Map.values(phase_results), &(&1.status == :warn)) -> :warn
        true -> :pass
      end

    result = %{
      phases: phase_results,
      overall_status: overall,
      total_weaknesses: length(all_weaknesses),
      critical_count: critical_count,
      started_at: started_at,
      completed_at: DateTime.utc_now()
    }

    :telemetry.execute(
      [:prismatic, :security, :pipeline_complete],
      %{
        total: result.total_weaknesses,
        critical: result.critical_count,
        status: result.overall_status
      },
      %{phases: phases}
    )

    result
  end

  defp execute_phase(:static_analysis) do
    Logger.info("Running static analysis weakness detection")
    []
  end

  defp execute_phase(:dependency_audit) do
    Logger.info("Running dependency vulnerability audit")
    []
  end

  defp execute_phase(:runtime_scan) do
    Logger.info("Running runtime weakness scan")
    scan = Prismatic.Security.WeaknessDetector.full_scan()
    scan.weaknesses
  end

  defp execute_phase(:config_review) do
    Logger.info("Running configuration weakness review")
    []
  end
end

Pre-Commit Integration

The platform's 11-phase pre-commit hook includes security-focused checks: Phase 8 validates templates for XSS vulnerabilities, Phase 9 runs forbidden pattern detection (blocking hardcoded credentials, unsafe function references), and Phase 10 verifies design consistency. The NO MERCY enforcement ensures that no security weakness bypass is possible -- --no-verify is absolutely forbidden.

Weakness Remediation Patterns

When weaknesses are identified, the platform applies structured remediation following the mandatory regression test protocol.

Every weakness fix must: identify the root cause and failure mode, create regression tests that would have caught the weakness, verify tests fail with the vulnerable code, apply the fix, verify tests pass with the fixed code, and report completion with the standard regression test report format.

This protocol ensures that every weakness fix permanently prevents the weakness from recurring, building an ever-expanding regression test suite that serves as a living security specification.

Continuous Improvement Through Color-Team Operations

The Color-Team Security Operations provide continuous weakness discovery through adversarial-defensive synthesis. The Gray team surfaces boundary conditions and specification gaps that may harbor latent weaknesses. The Red team simulates attacks against these boundaries to determine exploitability. The Blue team develops defenses and detection capabilities. The Purple team ensures closure by verifying that Red findings are addressed by Blue defenses. The White team formally proves that defenses hold. The Black team models theoretical worst-case scenarios to prepare for novel attack techniques.

This continuous cycle ensures that the platform's weakness posture improves with every iteration, transforming security from a periodic audit into a continuous improvement process.

Further Reading

  • MITRE CWE: https://cwe.mitre.org/
  • OWASP Top 10 (2021): https://owasp.org/Top10/
  • Shostack, Adam. "Threat Modeling: Designing for Security." Wiley, 2014.
  • NIST SP 800-30: Guide for Conducting Risk Assessments.
  • Erlang/OTP security documentation: https://www.erlang.org/doc/apps/ssl/ssl_distribution

Connect & Contribute

Created by Tomas Korcak (korczis) | Open Source under GHL