SecurityTxt (SecurityTxt v1.0.0)
View SourceParse, validate, and serialize RFC 9116 security.txt files in Elixir.
The package has no runtime dependencies. Parsing returns structured diagnostics, convenience accessors for registered fields, and OpenPGP cleartext detection. Serialization builds canonical unsigned documents from validated keyword options.
Quick start
expires =
DateTime.utc_now()
|> DateTime.add(2 * 365 * 24 * 60 * 60, :second)
|> DateTime.to_iso8601()
result =
SecurityTxt.parse("""
Contact: https://example.com/report
Expires: #{expires}
Policy: https://example.com/security-policy
Preferred-Languages: en, tr
""")
result.valid
#=> true
result.contact
#=> ["https://example.com/report"]
Enum.map(result.recommendations, & &1.code)
#=> ["long_expiry", "not_signed"]Serialize
expires =
DateTime.utc_now()
|> DateTime.add(2 * 365 * 24 * 60 * 60, :second)
|> DateTime.to_iso8601()
SecurityTxt.serialize(
comments: ["Security contact for example.com"],
contact: ["mailto:security@example.com", "https://example.com/report"],
expires: expires,
canonical: "https://example.com/.well-known/security.txt",
csaf: "https://example.com/.well-known/csaf/provider-metadata.json",
encryption: "openpgp4fpr:0123456789ABCDEF",
policy: "https://example.com/security-policy",
preferred_languages: ["en", "tr"]
)See the README for the full API, diagnostic codes, and scope.
Summary
Types
A diagnostic produced while processing a security.txt file.
A parsed RFC 9116 field.
The public result returned by the parser.
Functions
Parses and validates a complete security.txt string.
Builds a validated unsigned security.txt document.
Types
@type diagnostic() :: %{ code: String.t(), message: String.t(), line: pos_integer() | nil }
A diagnostic produced while processing a security.txt file.
@type field() :: %{name: String.t(), value: String.t(), line: pos_integer()}
A parsed RFC 9116 field.
@type result() :: %{ valid: boolean(), errors: [diagnostic()], recommendations: [diagnostic()], notifications: [diagnostic()], fields: [field()], signed: boolean(), contact: [String.t()], expires: String.t() | nil, acknowledgments: [String.t()], canonical: [String.t()], csaf: [String.t()], encryption: [String.t()], hiring: [String.t()], policy: [String.t()], preferred_languages: [String.t()] }
The public result returned by the parser.
Functions
Parses and validates a complete security.txt string.
Parsing malformed input does not raise. Input over 32,768 UTF-8 bytes is rejected before fields are parsed. The result includes source-order fields, convenience accessors, and diagnostics separated by severity.
Examples
expires =
DateTime.utc_now()
|> DateTime.add(2 * 365 * 24 * 60 * 60, :second)
|> DateTime.to_iso8601()
SecurityTxt.parse("""
Contact: https://example.com/report
Expires: #{expires}
Policy: https://example.com/security-policy
Preferred-Languages: en, tr
""")
Builds a validated unsigned security.txt document.
:contact and :expires are required. URI and language fields accept a
string or non-empty string list; :comments accepts a non-empty string list.
An :expires value may be a current RFC 3339 string or a DateTime.
The output uses LF line endings, canonical field order, and one trailing
newline. Invalid options raise ArgumentError.
Examples
expires =
DateTime.utc_now()
|> DateTime.add(2 * 365 * 24 * 60 * 60, :second)
|> DateTime.to_iso8601()
SecurityTxt.serialize(
comments: ["Security contact for example.com"],
contact: ["mailto:security@example.com", "https://example.com/report"],
expires: expires,
canonical: "https://example.com/.well-known/security.txt",
csaf: "https://example.com/.well-known/csaf/provider-metadata.json",
encryption: "openpgp4fpr:0123456789ABCDEF",
policy: "https://example.com/security-policy",
preferred_languages: ["en", "tr"]
)