moon-securitytxt

RFC 9116 security.txt parser, validator, generator and audit toolkit for MoonBit.

security-txt
securitytxt
rfc9116
parser
validator
audit
moonbit
moon add moyunijieshi1/moon-securitytxt@0.1.0-dev
Download zip
Version
0.1.0-dev
License
Apache-2.0
Last updated
4 days ago
Downloads
3
README

#moon-securitytxt

Module: moyunijieshi1/moon-securitytxt Version: 0.1.0-dev Status: GitHub development Repository: https://github.com/moyunijieshi1/moon-securitytxt Mooncakes: not published Maintainer: 谭海杰 <wx20061011@qq.com>

A pure-MoonBit RFC 9116 security.txt toolkit: parser, validator, generator, freshness and audit. GitHub development package moyunijieshi1/moon-securitytxt, version 0.1.0-dev.

#What it does

  • Parse security.txt documents byte-accurately: comments, blank lines, CRLF, UTF-8 (with BOM), all eight standard RFC 9116 fields plus extension fields, and OpenPGP cleartext-signature envelopes — with line/column/byte positions for every field and every error.
  • Validate against RFC 9116 constraints: required Contact and Expires, singleton cardinality, URI formats, HTTPS for web URIs, RFC 3339 date-times and language tags. validate returns the first error; validate_all collects all.
  • Check freshness with an explicit now timestamp: expiry, seconds-until-expiry and the 30-day "expires soon" window.
  • Generate valid documents with a builder that runs the validator before returning.
  • Audit for advisory findings (expired/expiring documents, missing optional fields, canonical mismatches, duplicate singletons, unknown extensions and unsigned documents).
  • CLI (securitytxt-tool) with parse, validate, fresh, generate, audit and stats commands, plus --version/--help.

#Field overview

FieldCardinalityPurpose
ContactOne or moreOrdered reporting channels; the first is preferred
ExpiresExactly oneTime after which the document is stale
Preferred-LanguagesZero or oneEqually preferred report languages
Other standard fieldsOptional, repeatablePolicy, keys, acknowledgments, hiring and canonical locations
Extension fieldsOptional, repeatableForward-compatible registered or private information

#What it does not do (by design)

  • No HTTP client, DNS, TLS, PGP signature/key verification, certificate validation, file access or security scanning. The library never fetches anything and can never be assembled into a scanner.
  • Signature handling stops at envelope detection: documents are reported as SignedUnverified, never verified.
  • A security.txt document grants no permission for security testing. See docs/security.md.

#Quick start

let text =
"Contact: mailto:security@example.com\n" +
"Expires: 2027-01-01T00:00:00Z\n" +
"Preferred-Languages: en, de\n"

match parse_security_txt(text) {
Ok(doc) =>
match validate(doc) {
Ok(_) => println("valid; preferred contact: \{doc.preferred_contact().unwrap()}")
Err(err) => println("invalid: \{err.to_string()}")
}
Err(err) => println("parse error: \{err.to_string()}")
}

CLI (input via --text; the tool never opens files):

moon run ./cmd/securitytxt-tool --target wasm-gc -- validate --text "Contact: mailto:a@example.com`nExpires: 2027-01-01T00:00:00Z`n" moon run ./cmd/securitytxt-tool --target wasm-gc -- fresh --text "..." --now 2026-08-13T00:00:00Z

#Project layout

PathContents
*.mbtCore library (parser, validator, serializer, audit, ...)
test_*.mbtNamed tests, property grids and truncation-fuzz suite
cmd/securitytxt-tool/CLI executable package
examples/Runnable examples (parse, validate, freshness, generate, audit)
docs/Architecture, API, testing, security, limitations, CLI
scripts/verify_all.ps1, count_code.py

#Verification

powershell -File scripts/verify_all.ps1

Runs moon clean, moon fmt --check, check+test on wasm-gc, js and native, CLI smoke tests, all examples, code metrics and moon package --list, ending with All verification steps passed.

#Tests

102 named tests plus deterministic property and truncation cases (see docs/testing.md). All inputs — truncated, mutated or malformed — return Ok or a structured Err; the parser never crashes.

#License

Apache-2.0. See LICENSE and THIRD_PARTY_NOTICES.md (the RFC 9116 Appendix A.1 example is reproduced as a test fixture).

#Project status

Maintained by 谭海杰 (moyunijieshi1, wx20061011@qq.com) at https://github.com/moyunijieshi1/moon-securitytxt. The module remains a 0.1.0-dev development release and has not been published to Mooncakes.

#
SecurityTxtError

pub(all) suberror SecurityTxtError {
SecurityTxtError(SecurityTxtErrorStage, SecurityTxtErrorKind, Int, Int, Int, String)
}

A structured security.txt error: stage, kind and exact position.

#
SecurityTxtError::byte_offset

fn SecurityTxtError::byte_offset(self : SecurityTxtError) -> Int

0-based byte offset into the raw input, -1 when unknown.

#
SecurityTxtError::column

fn SecurityTxtError::column(self : SecurityTxtError) -> Int

1-based column number, 0 when unknown.

#
SecurityTxtError::kind

The kind of this error.

#
SecurityTxtError::line

fn SecurityTxtError::line(self : SecurityTxtError) -> Int

1-based line number, 0 when unknown.

#
SecurityTxtError::message

fn SecurityTxtError::message(self : SecurityTxtError) -> String

Human-readable description of the violation.

#
SecurityTxtError::stage

The processing stage of this error.

#
SecurityTxtError::to_string

fn SecurityTxtError::to_string(self : SecurityTxtError) -> String

Render the error as a single readable line.

#
AuditFinding

pub struct AuditFinding {
kind : AuditFindingKind
severity : AuditSeverity
message : String
line : Int
} derive(Eq,
Debug
)

One advisory finding with an optional source line (0 when n/a).

#
AuditFinding::kind

Finding kind.

#
AuditFinding::line

fn AuditFinding::line(self : AuditFinding) -> Int

Source line (0 when the finding is document-wide).

#
AuditFinding::message

fn AuditFinding::message(self : AuditFinding) -> String

Human-readable description.

#
AuditFinding::severity

fn AuditFinding::severity(self : AuditFinding) -> AuditSeverity

Finding severity.

#
AuditFindingKind

pub(all) enum AuditFindingKind {
Expired
ExpiresSoon
NoPolicy
NoCanonical
CanonicalMismatch
InsecureWebUri
DuplicateSingletonField
UnknownExtension
NoPreferredLanguages
InvalidContext
Unsigned
} derive(Eq,
Debug
)

The kinds of advisory findings the audit layer can produce.

#
AuditFindingKind::label

fn AuditFindingKind::label(self : AuditFindingKind) -> String

Stable kebab-case label, useful for CLI output.

#
AuditSeverity

pub(all) enum AuditSeverity {
Info
Warning
} derive(Eq,
Debug
)

Severity of a finding. Warning flags actively risky states.

#
AuditSeverity::label

fn AuditSeverity::label(self : AuditSeverity) -> String

Stable severity label.

#
DateTime

pub struct DateTime {
year : Int
month : Int
day : Int
hour : Int
minute : Int
second : Int
nanos : Int64
offset_minutes : Int
} derive(Eq,
Debug
)

A civil date-time with a UTC offset (0 for Z); epoch math uses the UTC instant.

#
DateTime::after

fn DateTime::after(self : DateTime, other : DateTime) -> Bool

True when this instant is strictly after other.

#
DateTime::before

fn DateTime::before(self : DateTime, other : DateTime) -> Bool

True when this instant is strictly before other.

#
DateTime::day

fn DateTime::day(self : DateTime) -> Int

Day component (1-31).

#
DateTime::equals

fn DateTime::equals(self : DateTime, other : DateTime) -> Bool

True when both instants coincide.

#
DateTime::format_rfc3339

fn DateTime::format_rfc3339(self : DateTime) -> String

Format as canonical RFC 3339: date, time, optional fraction, Z or ±HH:MM.

#
DateTime::hour

fn DateTime::hour(self : DateTime) -> Int

Hour component (0-23).

#
DateTime::minute

fn DateTime::minute(self : DateTime) -> Int

Minute component (0-59).

#
DateTime::month

fn DateTime::month(self : DateTime) -> Int

Month component (1-12).

#
DateTime::nanos

fn DateTime::nanos(self : DateTime) -> Int64

Fractional seconds in nanoseconds (0-999999999).

#
DateTime::offset_minutes

fn DateTime::offset_minutes(self : DateTime) -> Int

UTC offset in minutes (0 for Z).

#
DateTime::second

fn DateTime::second(self : DateTime) -> Int

Second component (0-60; 60 is the leap second).

#
DateTime::to_epoch_nanos

fn DateTime::to_epoch_nanos(self : DateTime) -> Int64

This instant as nanoseconds since the Unix epoch (1970-01-01T00:00:00Z).

#
DateTime::year

fn DateTime::year(self : DateTime) -> Int

Year component.

#
ExtensionField

pub struct ExtensionField {
name : String
value : String
} derive(Eq,
Debug
)

An unknown (extension) field: RFC 9116 fields are extensible.

#
Limits

pub struct Limits {
max_input_bytes : Int
max_lines : Int
max_line_bytes : Int
max_fields : Int
max_field_value_bytes : Int
} derive(Eq,
Debug
)

Resource limits applied while parsing.

#
Limits::custom

fn Limits::custom(max_input_bytes : Int, max_lines : Int, max_line_bytes : Int, max_fields : Int, max_field_value_bytes : Int) -> Limits

Build custom limits. Negative values fall back to the default.

#
Limits::default

fn Limits::default() -> Limits

Conservative defaults suitable for untrusted input.

#
Limits::max_field_value_bytes

fn Limits::max_field_value_bytes(self : Limits) -> Int

Maximum length of one field value in bytes.

#
Limits::max_fields

fn Limits::max_fields(self : Limits) -> Int

Maximum number of field lines (standard and extension combined).

#
Limits::max_input_bytes

fn Limits::max_input_bytes(self : Limits) -> Int

Maximum input size in bytes.

#
Limits::max_line_bytes

fn Limits::max_line_bytes(self : Limits) -> Int

Maximum length of one physical line in bytes (excluding the line break).

#
Limits::max_lines

fn Limits::max_lines(self : Limits) -> Int

Maximum number of physical lines.

#
Limits::permissive

fn Limits::permissive() -> Limits

Relaxed limits for controlled environments.

#
Limits::strict

fn Limits::strict() -> Limits

Tighter limits for constrained or high-risk callers.

#
SecurityField

pub(all) enum SecurityField {
Contact(String)
Expires(String)
Canonical(String)
Encryption(String)
Acknowledgments(String)
Policy(String)
Hiring(String)
PreferredLanguages(String)
Extension(String, String)
} derive(Eq,
Debug
)

A parsed field: standard fields carry raw values; unknown fields become Extension.

#
SecurityField::is_singleton

fn SecurityField::is_singleton(self : SecurityField) -> Bool

True for fields that MUST NOT appear more than once per RFC 9116.

#
SecurityField::is_standard

fn SecurityField::is_standard(self : SecurityField) -> Bool

True for the nine standard RFC 9116 fields.

#
SecurityField::name

fn SecurityField::name(self : SecurityField) -> String

Canonical field name for this field; for extensions, the name as written.

#
SecurityField::value

fn SecurityField::value(self : SecurityField) -> String

Raw value string for this field.

#
SecurityFieldEntry

pub struct SecurityFieldEntry {
field : SecurityField
line : Int
byte_offset : Int
} derive(Eq,
Debug
)

An ordered field record with its position in the source file.

#
SecurityTxt

pub struct SecurityTxt {
entries : Array[SecurityFieldEntry]
comments : Array[String]
signature : SignatureState
armor_headers : Array[String]
line_count : Int
byte_count : Int
} derive(
Debug
)

A parsed security.txt document; field order is preserved.

#
SecurityTxt::acknowledgments

fn SecurityTxt::acknowledgments(self : SecurityTxt) -> Array[String]

All Acknowledgments values in original order.

#
SecurityTxt::armor_headers

fn SecurityTxt::armor_headers(self : SecurityTxt) -> Array[String]

OpenPGP armor headers seen in the signed envelope (e.g. Hash: SHA256).

#
SecurityTxt::byte_count

fn SecurityTxt::byte_count(self : SecurityTxt) -> Int

Size of the original input in bytes.

#
SecurityTxt::canonical

fn SecurityTxt::canonical(self : SecurityTxt) -> String?

Raw Canonical value, when present.

#
SecurityTxt::canonicals

fn SecurityTxt::canonicals(self : SecurityTxt) -> Array[String]

All Canonical values in original order.

#
SecurityTxt::comments

fn SecurityTxt::comments(self : SecurityTxt) -> Array[String]

Comment text lines (without the leading #), in original order.

#
SecurityTxt::contacts

fn SecurityTxt::contacts(self : SecurityTxt) -> Array[String]

All Contact values in original order.

#
SecurityTxt::encryption

fn SecurityTxt::encryption(self : SecurityTxt) -> String?

Raw Encryption value, when present.

#
SecurityTxt::entries

Ordered field records with positions.

#
SecurityTxt::expires

fn SecurityTxt::expires(self : SecurityTxt) -> Result[DateTime, SecurityTxtError]

Parsed Expires as a DateTime; fails SecurityTxtError when absent or malformed.

#
SecurityTxt::expires_value

fn SecurityTxt::expires_value(self : SecurityTxt) -> String?

Raw Expires value, when present.

#
SecurityTxt::extension_field_count

fn SecurityTxt::extension_field_count(self : SecurityTxt) -> Int

Number of extension fields.

#
SecurityTxt::extensions

fn SecurityTxt::extensions(self : SecurityTxt) -> Array[ExtensionField]

All extension fields in original order.

#
SecurityTxt::field_count

fn SecurityTxt::field_count(self : SecurityTxt) -> Int

Total number of fields, standard and extension.

#
SecurityTxt::fields

All fields in original document order.

#
SecurityTxt::hiring

fn SecurityTxt::hiring(self : SecurityTxt) -> String?

Raw Hiring value, when present.

#
SecurityTxt::is_signed

fn SecurityTxt::is_signed(self : SecurityTxt) -> Bool

True when the input carried an OpenPGP signed envelope.

#
SecurityTxt::line_count

fn SecurityTxt::line_count(self : SecurityTxt) -> Int

Number of physical lines in the original input.

#
SecurityTxt::policy

fn SecurityTxt::policy(self : SecurityTxt) -> String?

Raw Policy value, when present.

#
SecurityTxt::preferred_contact

fn SecurityTxt::preferred_contact(self : SecurityTxt) -> String?

The preferred contact: the first Contact, exactly as written (no scheme ranking).

#
SecurityTxt::preferred_languages

fn SecurityTxt::preferred_languages(self : SecurityTxt) -> Array[String]

Preferred-Languages split into trimmed tags, in listed order.

#
SecurityTxt::preferred_languages_value

fn SecurityTxt::preferred_languages_value(self : SecurityTxt) -> String?

Raw Preferred-Languages value, when present.

#
SecurityTxt::signature_state

fn SecurityTxt::signature_state(self : SecurityTxt) -> SignatureState

Signature state of the document.

#
SecurityTxt::standard_field_count

fn SecurityTxt::standard_field_count(self : SecurityTxt) -> Int

Number of standard RFC 9116 fields.

#
SecurityTxtBuilder

pub struct SecurityTxtBuilder {
contacts : Array[String]
expires : DateTime?
canonical : String?
encryption : String?
acknowledgments : Array[String]
policy : String?
hiring : String?
preferred_languages : Array[String]?
extensions : Array[ExtensionField]
comments : Array[String]
}

Builder for generating valid security.txt documents.

#
SecurityTxtBuilder::acknowledgments

fn SecurityTxtBuilder::acknowledgments(self : SecurityTxtBuilder, uri : String) -> SecurityTxtBuilder

Add an Acknowledgments field.

#
SecurityTxtBuilder::build

Build and validate; fixed deterministic field order; errors are returned as-is.

#
SecurityTxtBuilder::canonical

fn SecurityTxtBuilder::canonical(self : SecurityTxtBuilder, uri : String) -> SecurityTxtBuilder

Set the Canonical field.

#
SecurityTxtBuilder::comment

fn SecurityTxtBuilder::comment(self : SecurityTxtBuilder, text : String) -> SecurityTxtBuilder

Add a comment line (without the leading #).

#
SecurityTxtBuilder::contact

fn SecurityTxtBuilder::contact(self : SecurityTxtBuilder, uri : String) -> SecurityTxtBuilder

Add a Contact field. Multiple calls preserve call order.

#
SecurityTxtBuilder::encryption

fn SecurityTxtBuilder::encryption(self : SecurityTxtBuilder, uri : String) -> SecurityTxtBuilder

Set the Encryption field.

#
SecurityTxtBuilder::expires

Set the Expires field from a DateTime (formatted as RFC 3339).

#
SecurityTxtBuilder::extension

fn SecurityTxtBuilder::extension(self : SecurityTxtBuilder, name : String, value : String) -> SecurityTxtBuilder

Add an extension field.

#
SecurityTxtBuilder::hiring

fn SecurityTxtBuilder::hiring(self : SecurityTxtBuilder, uri : String) -> SecurityTxtBuilder

Set the Hiring field.

#
SecurityTxtBuilder::policy

fn SecurityTxtBuilder::policy(self : SecurityTxtBuilder, uri : String) -> SecurityTxtBuilder

Set the Policy field.

#
SecurityTxtBuilder::preferred_languages

fn SecurityTxtBuilder::preferred_languages(self : SecurityTxtBuilder, tags : Array[String]) -> SecurityTxtBuilder

Set Preferred-Languages; tags are joined with , .

#
SecurityTxtContext

pub struct SecurityTxtContext {
retrieval_uri : String?
content_type : String?
} derive(Eq,
Debug
)

How a document was retrieved; both fields optional — absent values skip checks.

#
SecurityTxtContext::content_type

fn SecurityTxtContext::content_type(self : SecurityTxtContext) -> String?

The Content-Type header value as provided by the caller.

#
SecurityTxtContext::retrieval_uri

fn SecurityTxtContext::retrieval_uri(self : SecurityTxtContext) -> String?

The retrieval URI as provided by the caller.

#
SecurityTxtErrorKind

pub(all) enum SecurityTxtErrorKind {
InvalidLine
MissingColon
EmptyFieldName
EmptyFieldValue
InvalidUtf8
InvalidUri
InvalidScheme
InvalidDateTime
InvalidLanguage
DuplicateExpires
DuplicatePreferredLanguages
DuplicateField
MissingContact
MissingExpires
ContextMismatch
InvalidContext
LimitExceeded
InvalidSignatureEnvelope
} derive(Eq,
Debug
)

The concrete error kind, one distinct kind per violation.

#
SecurityTxtErrorKind::to_string

fn SecurityTxtErrorKind::to_string(self : SecurityTxtErrorKind) -> String

Render the error kind name.

#
SecurityTxtErrorStage

pub(all) enum SecurityTxtErrorStage {
Input
Line
FieldName
FieldValue
Uri
DateTime
Language
SignatureEnvelope
Validation
Limit
} derive(Eq,
Debug
)

The processing stage an error was produced in.

#
SecurityTxtErrorStage::to_string

fn SecurityTxtErrorStage::to_string(self : SecurityTxtErrorStage) -> String

Render the error stage name.

#
SignatureState

pub(all) enum SignatureState {
Unsigned
SignedUnverified
} derive(Eq,
Debug
)

Signature state: the OpenPGP envelope is extracted but never verified.

#
SignedPayload

pub struct SignedPayload {
armor_headers : Array[String]
cleartext_lines : Array[String]
cleartext_offsets : Array[Int]
}

Result of scanning an input for a signed envelope.

#
SignedPayload::armor_headers

fn SignedPayload::armor_headers(self : SignedPayload) -> Array[String]

Armor headers from the envelope (for example Hash: SHA256).

#
SignedPayload::cleartext_lines

fn SignedPayload::cleartext_lines(self : SignedPayload) -> Array[String]

Dash-unescaped cleartext lines.

#
SignedPayload::cleartext_offsets

fn SignedPayload::cleartext_offsets(self : SignedPayload) -> Array[Int]

Byte offsets of the cleartext lines in the original input.

#
EXPIRES_SOON_SECONDS

let EXPIRES_SOON_SECONDS : Int64

Seconds before expiry that count as ExpiresSoon (project-defined).

#
RFC3339_MAX_FRACTIONAL_DIGITS

let RFC3339_MAX_FRACTIONAL_DIGITS : Int

Maximal fractional digits accepted (nanosecond precision).

#
audit

fn audit(document : SecurityTxt, now : DateTime, context : SecurityTxtContext) -> Array[AuditFinding]

Audit a parsed document against now and a retrieval context; findings are deterministic.

#
audit_finding

fn audit_finding(kind : AuditFindingKind, severity : AuditSeverity, message : String, line : Int) -> AuditFinding

Construct a finding.

#
char_utf8_len

fn char_utf8_len(c : Char) -> Int

#
check_field_value

fn check_field_value(field : SecurityField, line : Int, byte_offset : Int) -> Result[Unit, SecurityTxtError]

Validate one field value against its RFC 9116 constraints. line and byte_offset position the field in the original input; they are 0 for generated documents.

#
check_language_tag

fn check_language_tag(tag : String) -> Result[Unit, SecurityTxtError]

Validate a language tag against the project-defined RFC 5646 subset: a 2-8 alpha primary subtag (or private-use x), optional --separated 1-8 alphanumeric subtags, at most 35 characters. No registry lookup is performed.

#
check_uri

fn check_uri(value : String) -> Result[Unit, SecurityTxtError]

Minimal absolute-URI syntax check: scheme present, non-empty remainder, no control characters or spaces. Scheme policy (e.g. the https requirement on Encryption) is checked separately via check_uri_scheme.

#
check_uri_scheme

fn check_uri_scheme(value : String, allowed : Array[String], field_name : String, line : Int, byte_offset : Int) -> Result[Unit, SecurityTxtError]

Enforce a per-field scheme allow-list; InvalidScheme on violation.

#
contains_uri_illegal

fn contains_uri_illegal(value : String) -> Bool

True when the value contains control characters (C0 except HTAB, or DEL) or literal spaces, which are not permitted in a URI.

#
days_from_civil

fn days_from_civil(year : Int, month : Int, day : Int) -> Int64

Days from 1970-01-01 to the civil date (Howard Hinnant's days_from_civil).

#
days_in_month

fn days_in_month(year : Int, month : Int) -> Int

Days in the given month, respecting leap years (proleptic Gregorian).

#
div_floor_i64

fn div_floor_i64(a : Int64, b : Int64) -> Int64

Floor division for Int64 (truncation breaks negative durations).

#
empty_context

fn empty_context() -> SecurityTxtContext

An empty context: no retrieval information available.

#
encryption_allowed_schemes

fn encryption_allowed_schemes() -> Array[String]

Common schemes demonstrated by RFC 9116 for Encryption. This is informational: RFC 9116 also demonstrates dns: and does not define a closed allow-list. Validation only forbids insecure http: web URIs.

#
extension_field

fn extension_field(name : String, value : String) -> ExtensionField

Construct an extension field record.

#
extract_cleartext

fn extract_cleartext(input : String) -> Result[String?, SecurityTxtError]

Extract the cleartext body of a signed message (joined with LF). No verification.

#
is_expired

fn is_expired(expires : DateTime, now : DateTime) -> Bool

True when this instant has passed relative to now.

#
is_leap_year

fn is_leap_year(year : Int) -> Bool

Proleptic Gregorian leap year test.

#
is_standard_field_name

fn is_standard_field_name(name : String) -> Bool

True when the name (any case) is one of the nine standard fields.

#
library_version

fn library_version() -> String

Library version string, used by the CLI's --version.

#
make_utc

fn make_utc(year : Int, month : Int, day : Int, hour : Int, minute : Int, second : Int) -> Result[DateTime, SecurityTxtError]

Make a UTC date-time without fractional seconds. Range-checked.

#
make_utc_nanos

fn make_utc_nanos(year : Int, month : Int, day : Int, hour : Int, minute : Int, second : Int, nanos : Int64) -> Result[DateTime, SecurityTxtError]

Make a UTC date-time with fractional seconds as nanoseconds.

#
new_builder

fn new_builder() -> SecurityTxtBuilder

Start a new builder.

#
parse_rfc3339

fn parse_rfc3339(value : String) -> Result[DateTime, SecurityTxtError]

Parse the RFC 3339 date-time production used by RFC 9116.

#
parse_security_txt

fn parse_security_txt(input : String) -> Result[SecurityTxt, SecurityTxtError]

Parse a security.txt document with default limits.

#
parse_security_txt_bytes

fn parse_security_txt_bytes(input : Bytes, limits : Limits) -> Result[SecurityTxt, SecurityTxtError]

Parse raw bytes with limits; invalid UTF-8 reports InvalidUtf8 with byte offsets.

#
parse_security_txt_with_limits

fn parse_security_txt_with_limits(input : String, limits : Limits) -> Result[SecurityTxt, SecurityTxtError]

Parse a security.txt document with explicit limits.

#
parse_standard_field

fn parse_standard_field(name : String, value : String) -> SecurityField?

Map a case-insensitive name to the matching standard field, or None.

#
security_field_entry

fn security_field_entry(field : SecurityField, line : Int, byte_offset : Int) -> SecurityFieldEntry

Construct an ordered field record.

#
security_txt

fn security_txt(entries : Array[SecurityFieldEntry], comments : Array[String], signature : SignatureState, armor_headers : Array[String], line_count : Int, byte_count : Int) -> SecurityTxt

Construct a document. Prefer parse_security_txt or the generator; for tests.

#
security_txt_context

fn security_txt_context(retrieval_uri : String?, content_type : String?) -> SecurityTxtContext

Build a retrieval context.

#
security_txt_error

fn security_txt_error(stage : SecurityTxtErrorStage, kind : SecurityTxtErrorKind, line : Int, column : Int, byte_offset : Int, message : String) -> SecurityTxtError

Construct a security.txt error; line/column 1-based, byte_offset 0-based.

#
serialize_security_txt

fn serialize_security_txt(document : SecurityTxt) -> String

Serialize to canonical text: comments then fields in stored order, LF line endings.

#
split_lines_text

fn split_lines_text(input : String) -> (Array[String], Array[Int])

Split text into LF lines, stripping one CR, with byte offsets; no trailing line.

#
split_preferred_languages

fn split_preferred_languages(value : String) -> Array[String]

Split a Preferred-Languages value into trimmed tags.

#
split_signed_envelope

fn split_signed_envelope(lines : Array[String], offsets : Array[Int]) -> Result[SignedPayload?, SecurityTxtError]

Split a signed envelope: Ok(None)=unsigned, Ok(Some(payload))=complete, Err=malformed.

#
standard_field_name

fn standard_field_name(name : String) -> String?

Look up a case-insensitive field name; returns the canonical name.

#
time_until_expiry

fn time_until_expiry(expires : DateTime, now : DateTime) -> Int64

Seconds from now until expiry; negative when already expired.

#
uri_scheme

fn uri_scheme(value : String) -> String?

Lower-case URI scheme of a value with a scheme-like prefix (ALPHA *(ALPHA / DIGIT / "+" / "-" / ".") ":").

#
utf8_byte_len

fn utf8_byte_len(s : String) -> Int

#
validate

fn validate(document : SecurityTxt) -> Result[Unit, SecurityTxtError]

Validate a document; fails with the first error found.

#
validate_all

fn validate_all(document : SecurityTxt) -> Array[SecurityTxtError]

Collect every RFC violation in a document; empty when valid. Deterministic order.

#
validate_context

fn validate_context(context : SecurityTxtContext) -> Result[Unit, SecurityTxtError]

Validate the retrieval context: HTTPS, well-known path, text/plain; charset=utf-8.

#
validate_retrieval_context

fn validate_retrieval_context(document : SecurityTxt, context : SecurityTxtContext) -> Result[Unit, SecurityTxtError]

Compare the retrieval URI with all Canonical fields: one must match exactly.