Credential

security

Credential

Definition

A credential is a digital artifact that proves an entity's claimed identity and authorizes access to protected resources. Credentials serve as the foundational bridge between authentication (proving who you are) and authorization (determining what you can do). Without properly managed credentials, no security boundary can be trusted.

In the context of the Prismatic Platform, credentials govern three critical domains: platform infrastructure access, user session management, and OSINT tool authentication for intelligence-gathering operations. The platform's SEAL doctrine mandates that credentials never appear in source code, are never logged, and are always injected at runtime through environment variables or secret management systems.

Overview

Credential Types

Credentials exist in many forms, each with distinct security properties, lifecycle characteristics, and appropriate use cases. Understanding these differences is essential for selecting the right credential type for each security boundary.

Passwords

The oldest and most familiar credential type. A shared secret known only to the user and the verifying system. Passwords are stored as cryptographic hashes (bcrypt, argon2) -- never in plaintext. Their primary weakness is human behavior: users choose weak passwords, reuse them across services, and fall victim to phishing. Password-based authentication should always be combined with a second factor.

Tokens

Short-lived digital artifacts issued after successful authentication. JWT (JSON Web Tokens) are the most common form, carrying encoded claims about the bearer's identity and permissions. Tokens are self-contained -- the verifier needs only the signing key, not a database lookup. Session tokens, refresh tokens, and OAuth access tokens all fall in this category. Their short lifespan limits the damage window if compromised.

API Keys

Static strings that identify and authenticate a calling application or service. Unlike tokens, API keys typically do not expire automatically and must be rotated manually or through automated rotation policies. They are the primary credential type for OSINT tool integrations in the Prismatic Platform -- services like Shodan, VirusTotal, and Hunter.io all require API keys for programmatic access.

Certificates

Cryptographic documents that bind a public key to an identity, verified by a trusted Certificate Authority (CA). TLS certificates secure HTTPS connections, client certificates authenticate services in mutual TLS (mTLS) configurations, and code-signing certificates verify software provenance. Certificates have fixed expiration dates and can be revoked through Certificate Revocation Lists (CRL) or the Online Certificate Status Protocol (OCSP).

SSH Keys

Asymmetric key pairs used for server access and Git operations. The private key never leaves the holder's machine; the public key is distributed to servers that should grant access. SSH keys provide strong authentication without transmitting secrets over the network. Key rotation and access revocation require removing the public key from authorized hosts.

Erlang Distribution Cookies

A platform-specific credential type. The Erlang runtime uses a shared cookie to authenticate nodes in a distributed cluster. All nodes sharing the same cookie can communicate freely. This cookie must be treated with the same care as any other secret -- if leaked, an attacker can join the cluster and execute arbitrary code on any connected node.

Credential Properties Comparison

TypeScopeExpirationRevocabilityStorageRotation Complexity
PasswordUser identityNone (manual change)ImmediateHashed (bcrypt/argon2)Low
API KeyService accessConfigurableImmediateEnv var / secrets managerMedium
JWT TokenSession + claimsShort-lived (15m-24h)Blocklist requiredClient-sideAutomatic
TLS CertificateServer/service identity90 days (Let's Encrypt)CRL/OCSPFile system / cert storeMedium
SSH KeyServer accessNone (manual)Key removal from hosts~/.ssh/Medium
OAuth2 TokenDelegated accessShort-livedToken revocation endpointSecure storageAutomatic
Erlang CookieCluster node authNoneCookie regenerationFile / env varHigh (cluster restart)
Client CertificateService-to-serviceConfigurableCRL/OCSPCert storeMedium

Technical Deep Dive

SEAL Doctrine: No Hardcoded Secrets

The Prismatic Platform enforces the SEAL (Security Enforcement Absolute Lock) doctrine, which absolutely prohibits hardcoded credentials in any form. This is not advisory -- it is enforced by pre-commit hooks that scan staged files for credential patterns and block commits containing potential secrets.

Banned patterns under SEAL:

# BANNED: Hardcoded API key
api_key = "sk-1234567890abcdef"      # Pre-commit hook BLOCKS this

# BANNED: Credentials in configuration files
config :my_app, :shodan_key, "abc123" # BLOCKS - use System.get_env/1

# BANNED: Secrets in string interpolation
fragment("password = '#{password}'")  # BLOCKS - SQL injection + credential exposure

# APPROVED: Environment variable injection
api_key = System.get_env("SHODAN_API_KEY")  # Runtime injection

# APPROVED: Required env with clear failure
api_key = System.fetch_env!("SHODAN_API_KEY")  # Fails fast if missing

# APPROVED: Application config from runtime.exs
api_key = Application.get_env(:prismatic_osint_sources, :shodan_api_key)

Environment Variables and Runtime Injection

All credentials in the Prismatic Platform flow through config/runtime.exs, which reads environment variables at application startup. This ensures credentials are never compiled into the release binary and can differ between environments (development, staging, production) without code changes.

# config/runtime.exs - credential injection pattern
import Config

if config_env() == :prod do
  # Database credentials - required, fail fast if missing
  config :prismatic, Prismatic.Repo,
    url: System.fetch_env!("DATABASE_URL"),
    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

  # OSINT tool credentials - optional, degrade gracefully
  config :prismatic_osint_sources,
    shodan_api_key: System.get_env("SHODAN_API_KEY"),
    virustotal_api_key: System.get_env("VT_API_KEY"),
    hunter_api_key: System.get_env("HUNTER_API_KEY")

  # Secret key base - required for session security
  config :prismatic_web, PrismaticWeb.Endpoint,
    secret_key_base: System.fetch_env!("SECRET_KEY_BASE")
end

Vault Integration Pattern

For production environments requiring centralized secret management, the platform supports integration with external vault systems. The pattern retrieves credentials at startup and caches them in application configuration, with periodic refresh for rotation support.

defmodule Prismatic.Credentials.VaultProvider do
  @moduledoc """
  Retrieves credentials from external vault systems at runtime.
  Supports HashiCorp Vault, AWS Secrets Manager, and Fly.io secrets.
  """

  require Logger

  @spec fetch(String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
  def fetch(secret_path, opts \\ []) do
    provider = Keyword.get(opts, :provider, :env)
    timeout = Keyword.get(opts, :timeout, 5_000)

    case provider do
      :env ->
        case System.get_env(secret_path) do
          nil -> {:error, :not_configured}
          value -> {:ok, value}
        end

      :fly_secrets ->
        # Fly.io injects secrets as environment variables
        case System.get_env(secret_path) do
          nil -> {:error, :fly_secret_not_found}
          value -> {:ok, value}
        end

      :vault ->
        fetch_from_vault(secret_path, timeout)
    end
  end

  defp fetch_from_vault(path, timeout) do
    vault_addr = System.get_env("VAULT_ADDR")
    vault_token = System.get_env("VAULT_TOKEN")

    if is_nil(vault_addr) or is_nil(vault_token) do
      {:error, :vault_not_configured}
    else
      # HTTP request to vault with timeout
      case Req.get("#{vault_addr}/v1/secret/data/#{path}",
             headers: [{"X-Vault-Token", vault_token}],
             receive_timeout: timeout) do
        {:ok, %{status: 200, body: %{"data" => %{"data" => data}}}} ->
          {:ok, data}

        {:ok, %{status: status}} ->
          Logger.warning("Vault returned status #{status} for path #{path}")
          {:error, {:vault_error, status}}

        {:error, reason} ->
          Logger.error("Vault connection failed: #{inspect(reason)}")
          {:error, {:connection_failed, reason}}
      end
    end
  end
end

Credential Lifecycle

graph TD
    A[Generation] -->|Strong entropy| B[Secure Storage]
    B -->|Env vars / Vault| C[Runtime Injection]
    C -->|config/runtime.exs| D[Active Use]
    D -->|Telemetry events| E{Monitoring}
    E -->|Anomaly detected| F[Investigation]
    E -->|Rotation schedule| G[Rotation]
    E -->|Compromise confirmed| H[Revocation]
    F -->|Confirmed safe| D
    F -->|Confirmed compromised| H
    G -->|New credential generated| A
    H -->|Immediate invalidation| I[Audit Log]
    I -->|Post-mortem| J[Policy Update]
    J -->|Strengthened controls| A

    style A fill:#1e3a5f,stroke:#3b82f6,color:#fff
    style B fill:#1e3a5f,stroke:#3b82f6,color:#fff
    style C fill:#1e3a5f,stroke:#3b82f6,color:#fff
    style D fill:#065f46,stroke:#10b981,color:#fff
    style E fill:#713f12,stroke:#f59e0b,color:#fff
    style F fill:#713f12,stroke:#f59e0b,color:#fff
    style G fill:#1e3a5f,stroke:#3b82f6,color:#fff
    style H fill:#7f1d1d,stroke:#ef4444,color:#fff
    style I fill:#4c1d95,stroke:#8b5cf6,color:#fff
    style J fill:#4c1d95,stroke:#8b5cf6,color:#fff

Authentication Flow with Credentials

sequenceDiagram
    participant User
    participant Browser
    participant Phoenix as Phoenix Endpoint
    participant Auth as PrismaticAuth
    participant Session as Session Store
    participant OSINT as OSINT Sources

    User->>Browser: Submit login form
    Browser->>Phoenix: POST /auth/login (username, password)
    Phoenix->>Auth: verify_credentials(username, password)
    Auth->>Auth: bcrypt_verify(password, stored_hash)

    alt Valid Credentials
        Auth-->>Phoenix: {:ok, user}
        Phoenix->>Session: create_session(user, metadata)
        Session-->>Phoenix: session_token
        Phoenix-->>Browser: Set-Cookie: _prismatic_session=token
        Browser-->>User: Redirect to /dashboard
    else Invalid Credentials
        Auth-->>Phoenix: {:error, :invalid_credentials}
        Phoenix-->>Browser: 401 + error message
        Browser-->>User: Invalid credentials
    end

    Note over Browser,OSINT: Subsequent API calls use session token

    Browser->>Phoenix: GET /api/osint/execute (Cookie: session)
    Phoenix->>Auth: validate_session(session_token)
    Auth-->>Phoenix: {:ok, user}
    Phoenix->>OSINT: execute_tool(tool, input, api_key_from_env)
    OSINT-->>Phoenix: {:ok, results}
    Phoenix-->>Browser: JSON response

Usage in Prismatic Platform

OSINT API Credentials

The Prismatic Platform integrates with 157+ OSINT tools, many requiring API key authentication. Each tool's requires_auth configuration determines whether credentials are needed at runtime. Tools check for their specific credential and return structured errors when credentials are missing, rather than failing silently.

Tool CategoryAuth MethodCredential TypeEnvironment Variable
ShodanAPI KeyString (32 char)SHODAN_API_KEY
VirusTotalAPI KeyString (64 char)VT_API_KEY
Hunter.ioAPI KeyStringHUNTER_API_KEY
Czech ARESNoneN/APublic API
Companies HouseAPI KeyBasic AuthCH_API_KEY
SEC EDGARUser-AgentEmail headerConfig
Have I Been PwnedAPI KeyStringHIBP_API_KEY
CensysAPI ID + SecretPairCENSYS_API_ID / CENSYS_API_SECRET
SecurityTrailsAPI KeyStringSECURITYTRAILS_API_KEY

The OSINT toolbox UI (/hub/osint/tools) displays authentication status per tool, showing which tools are configured and ready for use versus which require credential configuration.

SEAL Enforcement in Practice

The platform enforces credential security through multiple layers:

  1. Pre-commit hooks (Phase 9: Security scan) -- grep-based detection of credential patterns in staged files. Commits containing strings that match API key, password, or token patterns are blocked.

  2. .gitignore exclusions -- Common credential file patterns (.env, *.pem, *.key, credentials.json) are excluded from version control.

  3. CI/CD validation -- The mix check.doctrines task validates SEAL compliance across the entire codebase, detecting hardcoded secrets that might have bypassed pre-commit checks.

  4. Runtime validation -- The platform validates credential format and liveness at startup, failing fast with clear error messages when required credentials are missing or malformed.

Perimeter EASM Credential Assessment

The Perimeter External Attack Surface Management module assesses target organizations' credential exposure as part of security ratings. The assessment checks for:

  • Exposed admin interfaces with default credentials
  • API keys visible in client-side JavaScript
  • Leaked credentials in public code repositories
  • Credential stuffing vulnerability indicators
  • Certificate misconfigurations and expirations

Color Team Credential Monitoring

The Color Team's Blue Team blue-auth-sentinel agent monitors authentication events for credential abuse patterns: brute force attempts, credential stuffing, token replay, and privilege escalation via compromised credentials. Detected patterns enter the Color Team pipeline for assessment and containment.

Code Examples

Safe Credential Retrieval

defmodule PrismaticSecurity.CredentialManager do
  @moduledoc """
  Manages credential lifecycle: storage, retrieval, rotation, and
  compromise detection. Credentials are never logged, never stored
  in plain text, and never included in error messages.

  Enforces SEAL doctrine: no hardcoded secrets, environment variable
  injection only, structured error handling for missing credentials.
  """

  require Logger

  @type credential_status :: :active | :expired | :revoked | :compromised
  @type credential_type :: :api_key | :password | :token | :certificate | :ssh_key

  @type credential :: %{
    id: String.t(),
    type: credential_type(),
    scope: String.t(),
    created_at: DateTime.t(),
    expires_at: DateTime.t() | nil,
    last_used: DateTime.t() | nil,
    status: credential_status()
  }

  @spec fetch_credential(credential_type(), String.t()) ::
          {:ok, String.t()} | {:error, atom()}
  def fetch_credential(:env_var, identifier) do
    case System.get_env(identifier) do
      nil ->
        Logger.warning("Credential not found",
          credential_type: :env_var,
          identifier: redact_identifier(identifier)
        )
        {:error, :not_found}

      "" ->
        {:error, :empty_value}

      value ->
        {:ok, value}
    end
  end

  def fetch_credential(:application_env, identifier) do
    # Use String.to_existing_atom to prevent atom table exhaustion (ZERO doctrine)
    atom_key =
      try do
        String.to_existing_atom(identifier)
      rescue
        ArgumentError -> nil
      end

    case atom_key && Application.get_env(:prismatic, atom_key) do
      nil -> {:error, :not_found}
      value -> {:ok, value}
    end
  end

  def fetch_credential(type, _identifier) do
    Logger.warning("Unsupported credential type requested",
      credential_type: type
    )
    {:error, :unsupported_type}
  end

  @spec validate_credential(String.t() | nil, credential_type()) ::
          {:ok, credential()} | {:error, atom()}
  def validate_credential(nil, _type), do: {:error, :not_empty}
  def validate_credential("", _type), do: {:error, :not_empty}

  def validate_credential(value, expected_type) do
    with :ok <- check_minimum_length(value, expected_type),
         :ok <- check_not_placeholder(value),
         :ok <- check_format(value, expected_type) do
      {:ok, %{
        id: hash_id(value),
        type: expected_type,
        scope: "*",
        created_at: DateTime.utc_now(),
        expires_at: nil,
        last_used: nil,
        status: :active
      }}
    end
  end

  @spec check_for_exposure(String.t()) :: :safe | {:exposed, String.t()}
  def check_for_exposure(content) do
    patterns = [
      {~r/password\s*[=:]\s*["'][^"']+["']/, "Password in assignment"},
      {~r/api[_-]?key\s*[=:]\s*["'][^"']+["']/, "API key in assignment"},
      {~r/secret\s*[=:]\s*["'][^"']+["']/, "Secret in assignment"},
      {~r/sk-[a-zA-Z0-9]{20,}/, "Potential API secret key"},
      {~r/-----BEGIN (RSA |EC )?PRIVATE KEY-----/, "Private key material"}
    ]

    case Enum.find(patterns, fn {pattern, _} -> Regex.match?(pattern, content) end) do
      nil -> :safe
      {_, description} -> {:exposed, description}
    end
  end

  # Private helpers

  defp check_minimum_length(value, type) do
    min = minimum_length(type)
    if String.length(value) >= min, do: :ok, else: {:error, :too_short}
  end

  defp check_not_placeholder(value) do
    placeholders = ~w(changeme password123 xxx todo replace_me your_key_here
                      CHANGE_ME INSERT_KEY_HERE example test)
    if String.downcase(value) in placeholders, do: {:error, :placeholder}, else: :ok
  end

  defp check_format(value, :api_key) do
    if Regex.match?(~r/^[a-zA-Z0-9_\-]{16,}$/, value), do: :ok, else: {:error, :invalid_format}
  end

  defp check_format(_value, _type), do: :ok

  defp minimum_length(:api_key), do: 16
  defp minimum_length(:password), do: 12
  defp minimum_length(:token), do: 32
  defp minimum_length(:certificate), do: 64
  defp minimum_length(_), do: 8

  defp hash_id(value) do
    :crypto.hash(:sha256, value)
    |> Base.encode16(case: :lower)
    |> String.slice(0, 16)
  end

  defp redact_identifier(id) do
    # Show only first 4 chars to aid debugging without exposing full name
    String.slice(id, 0, 4) <> "****"
  end
end

OSINT Tool Credential Check Pattern

defmodule PrismaticOsintSources.CredentialCheck do
  @moduledoc """
  Validates OSINT tool credentials before execution.
  Returns structured errors when credentials are missing,
  enabling graceful degradation in the toolbox UI.
  """

  @spec check_tool_auth(map()) :: :ok | {:error, :auth_required, String.t()}
  def check_tool_auth(%{requires_auth: false}), do: :ok

  def check_tool_auth(%{requires_auth: true, slug: slug} = tool) do
    env_var = tool[:auth_env_var] || default_env_var(slug)

    case System.get_env(env_var) do
      nil ->
        {:error, :auth_required,
         "Tool '#{slug}' requires credential in #{env_var}"}

      "" ->
        {:error, :auth_required,
         "Tool '#{slug}' has empty credential in #{env_var}"}

      _value ->
        :ok
    end
  end

  defp default_env_var(slug) do
    slug
    |> String.upcase()
    |> String.replace("-", "_")
    |> Kernel.<>("_API_KEY")
  end
end

Best Practices

Credential Generation

  • Use cryptographically secure random number generators (crypto:strong_rand_bytes/1 in Erlang, SecureRandom in other languages)
  • API keys should be at least 32 characters of high-entropy randomness
  • Passwords must meet minimum complexity requirements enforced at the boundary
  • Never derive credentials from predictable inputs (timestamps, sequential IDs, usernames)

Storage and Transmission

  • Store passwords only as salted cryptographic hashes (bcrypt with cost factor >= 12, or argon2id)
  • Transmit credentials only over encrypted channels (TLS 1.2+)
  • Store API keys in environment variables or dedicated secret management systems, never in source code
  • Use short-lived tokens for session management; long-lived credentials for service-to-service authentication only when rotation is automated

Rotation and Revocation

  • Rotate all credentials on a defined schedule (90 days for API keys, 30 days for service passwords)
  • Implement zero-downtime rotation: new credential active before old credential is revoked
  • Maintain audit logs of all credential operations (creation, use, rotation, revocation)
  • Automate rotation where possible -- manual rotation introduces human error and drift

Monitoring and Incident Response

  • Log all authentication attempts (successes and failures) with structured metadata
  • Alert on anomalous credential usage (geographic anomalies, frequency spikes, scope violations)
  • Maintain a credential injection response playbook: immediate revocation, scope assessment, forensic analysis
  • Never include credential values in log output, error messages, or stack traces

Common Mistakes

MistakeRisk LevelImpactCorrect Approach
Hardcoding API keys in source codeCriticalKey exposed to all repo viewers, persists in git historyUse environment variables via System.get_env/1
Logging credential valuesCriticalCredentials exposed in log aggregation systemsLog credential metadata only (type, scope, last 4 chars)
Using String.to_atom/1 for credential identifiersHighAtom table exhaustion DoS (ZERO doctrine violation)Use String.to_existing_atom/1 or allowlists
No credential rotation policyHighCompromised credentials remain valid indefinitelyImplement automated rotation on defined schedule
Storing passwords in plaintextCriticalDirect credential theft from any data breachUse bcrypt/argon2 hashing with unique salts
Sharing credentials across environmentsHighStaging compromise leads to production breachUnique credentials per environment
Using bare rescue in credential handlingMediumError details swallowed, debugging impossibleCatch specific exceptions (ZERO doctrine)
No rate limiting on authentication endpointsHighBrute force and credential stuffing attacks succeedImplement exponential backoff and lockout
Transmitting credentials over HTTPCriticalNetwork sniffing captures credentials in transitEnforce TLS for all credential transmission
Committing .env files to version controlCriticalAll environment secrets exposed in repoAdd .env to .gitignore, use .env.example for templates
Using the same API key for all OSINT toolsMediumSingle key compromise affects all tool integrationsUnique keys per service with minimal scope
No credential validation at startupMediumApplication starts with invalid/missing credentials, fails laterValidate required credentials in runtime.exs with fetch_env!/1
  • Authentication -- the process of verifying identity using credentials
  • Authorization -- determining permitted actions after credential verification
  • API Key -- a specific credential type for programmatic service access
  • JWT -- JSON Web Token, a self-contained credential carrying encoded claims
  • OAuth -- delegated authorization framework using token credentials
  • Secret -- any sensitive value requiring protection, including credentials
  • Encryption -- cryptographic protection for credentials in storage and transit
  • Injection -- attack vector that can expose or bypass credential checks
  • SEAL -- Security Enforcement Absolute Lock doctrine governing credential handling
  • Credential Management -- lifecycle practices for credential security
  • Credential Stuffing -- automated attack using stolen credential databases
  • Cipher Suite -- TLS configuration protecting credential transmission
  • Certificate -- cryptographic credential binding identity to public keys
  • Token -- short-lived credential issued after successful authentication
  • Access Control -- security framework enforcing credential-based permissions

See Also

  • Livebooks: livebooks/domains/security_compliance/ -- credential security labs and interactive exercises
  • Perimeter EASM: Credential exposure assessment in external security ratings
  • Color Team: Blue Team blue-auth-sentinel agent for credential abuse detection
  • OSINT Toolbox: /hub/osint/tools -- credential configuration status per tool
  • SEAL Doctrine: Pre-commit hook Phase 9 security scan for credential leaks
  • Platform Configuration: config/runtime.exs -- canonical credential injection point
  • Production Secrets: Fly.io secrets management for production credential storage

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