- H12: session and remember-me cookies get http_only + secure (prod-only). Cookie can no longer be read via document.cookie (XSS exfil defense) and the Secure flag is set in production via config/prod.exs. - L2: 404 tracker uses EXPIRE … NX so a sustained probe can't keep refreshing the 60s window and dodge the threshold ban. - L5: vault only reads CLOAK_KEY when :env == :prod — a developer with a prod env var set in their shell won't accidentally encrypt local data with the production key. - L6: health endpoint no longer leaks the app version. - L8: Preseem.dismiss_insight/2 + InsightsLive uses it — dismissing now requires the insight to belong to the user's org (closes IDOR). - L10: SidebarCollapse JS hook stores its click handler and removes it in destroyed(); listeners no longer accumulate across LV navigation. - L11: WebMCP navigate tool rejects anything that isn't a same-origin absolute path (blocks javascript:, data:, off-site URLs).
59 lines
1.5 KiB
Elixir
59 lines
1.5 KiB
Elixir
defmodule Towerops.Vault do
|
|
@moduledoc """
|
|
Encryption vault for sensitive data using Cloak.
|
|
|
|
Encrypts data using AES-256-GCM encryption. The encryption key is loaded
|
|
from the CLOAK_KEY environment variable (base64-encoded).
|
|
|
|
## Usage
|
|
|
|
Fields that use this vault are automatically encrypted/decrypted by Ecto.
|
|
See `Towerops.Encrypted.Binary` for the custom Ecto type.
|
|
|
|
## Key Generation
|
|
|
|
Generate a new encryption key:
|
|
|
|
openssl rand -base64 32
|
|
|
|
Store this key securely in 1Password and set it as the CLOAK_KEY
|
|
environment variable in production.
|
|
"""
|
|
use Cloak.Vault, otp_app: :towerops
|
|
|
|
@impl GenServer
|
|
def init(config) do
|
|
# In dev/test, use the key from config files so a developer who happens
|
|
# to have CLOAK_KEY set in their shell (e.g., from a prod-deploy env)
|
|
# doesn't accidentally encrypt local data with the production key.
|
|
# In production (runtime.exs), the env var override is required.
|
|
config =
|
|
if Application.get_env(:towerops, :env, :prod) == :prod and System.get_env("CLOAK_KEY") do
|
|
Keyword.put(config, :ciphers,
|
|
default: {
|
|
Cloak.Ciphers.AES.GCM,
|
|
tag: "AES.GCM.V1", key: decode_env!("CLOAK_KEY")
|
|
}
|
|
)
|
|
else
|
|
config
|
|
end
|
|
|
|
{:ok, config}
|
|
end
|
|
|
|
defp decode_env!(var) do
|
|
var
|
|
|> System.get_env()
|
|
|> case do
|
|
nil ->
|
|
raise """
|
|
Environment variable #{var} is missing.
|
|
Generate a key with: openssl rand -base64 32
|
|
"""
|
|
|
|
value ->
|
|
Base.decode64!(value)
|
|
end
|
|
end
|
|
end
|