Skip to content

.olf Format Specification

Draft

This specification is at draft status for v0.1 Alpha. Field definitions and schema are stable for implementation purposes. The specification reaches stable status at v1.0 GA. Breaking changes before v1.0 GA will be announced via the project GitHub Discussions.


Overview

The .olf (openLogForge use case) format is the portable, human-readable format for openLogForge use cases. It is an open community standard - any tool can read, write, or validate .olf files using this specification.

A .olf file is a single YAML 1.2 document. One file = one use case. The format is designed for:

  • Human authoring and review (readable in any text editor, diffs clearly on GitHub)
  • Portability across openLogForge deployments
  • Third-party tooling: validators, converters, IDE plugins, CI integrations
  • Community sharing via GitHub pull requests

The .olf-premium variant is the encrypted distribution format for premium content packs. Its decrypted payload follows the same YAML schema as community use cases. Creation of .olf-premium bundles is restricted to the openLogForge project.

Community format integrity

Community .olf files carry no envelope-level integrity check (no checksums or signatures). This is intentional: the format is fully open, human-readable, and visible in its entirety - the content is its own integrity signal. Implementations that require tamper evidence for community files must manage that out-of-band (e.g., git commit signatures on the community repository). The .olf-premium format provides cryptographic integrity via payload_checksums because its content is encrypted and cannot be inspected visually.


Versioning

A single olf_version field in the YAML document controls format compatibility. It versions both the document structure and the use case schema.

Field Location Current value
olf_version Top-level field in every .olf document "1.0"

The olf_version field must be a string matching the format "<major>.<minor>" where <major> and <minor> are non-negative integers (e.g. "1.0", "1.2", "2.0"). Any other format is OLF_VERSION_UNSUPPORTED.

The olf_version major number tracks the application major version. They are not independently versioned - olf_version: "1.x" means the file is compatible with any app v1.x release. Breaking format changes only occur at an app major version boundary (v1.x → v2.0).

Import behaviour is defined by the Compatibility Policy:

  • Same major - import normally; unknown fields from a higher minor are ignored with a warning.
  • Higher major - hard stop: "This use case requires a newer version of openLogForge."
  • Lower major - at v1.0 GA, no prior major format version exists. Files with an olf_version major lower than 1 are rejected with OLF_VERSION_UNSUPPORTED. A migration path will be documented when a v2.0 release introduces a new format major; until then no migration applies.

Unknown fields at any version are ignored with a warning, never a hard failure. Warnings are surfaced to the caller in a warnings string array in the import API response body alongside the success result, and are also logged at WARNING level in the application log.


Community Use Case (.olf)

Document Structure

A .olf file is a single YAML 1.2 document with the following schema:

olf_version: "1.0"

id: "550e8400-e29b-41d4-a716-446655440000"
name: "SSH Brute Force - Failed Logins"
description: |
  Simulates a sequence of failed SSH authentication attempts from a single
  source IP. Maps to MITRE ATT&CK T1110.001 (Brute Force: Password Guessing).
tier: community
visibility: public_readonly
created_by: "Jane Smith"
created_at: "2026-06-06T12:00:00Z"
last_edited_at: "2026-06-06T12:00:00Z"
exported_at: "2026-06-13T10:00:00Z"
imported_at: null

classification:
  mitre_tactics:
    - TA0006
  mitre_techniques:
    - T1110.001
  tags:
    - linux
    - ssh
    - brute-force

log_source:
  category: os
  platform: linux

run_count: 5
run_delay_ms: 500

log_events:
  - sequence: 1
    format: syslog_rfc5424
    template: |
      <34>1 {{timestamp}} {{hostname}} sshd 1234 - - Failed password for invalid user {{username}} from {{src_ip}} port {{src_port}} ssh2
    variables:
      timestamp:
        type: timestamp
        default: "2026-06-06T12:00:00Z"
        source: generated
      hostname:
        type: hostname
        default: "web-prod-01"
        source: user_override
      username:
        type: string
        default: "admin"
        source: user_override
      src_ip:
        type: ip
        default: "192.168.1.100"
        source: generated
      src_port:
        type: integer
        default: 54321
        min: 1024
        max: 65535
        source: generated
    delay_ms: 0
    repeat: 5
    comment: "Repeat 5 times to simulate a burst of failed attempts"

Top-Level Fields

Field Type Required Description
olf_version string Yes Format version. Must be "1.0" for this revision.
id string (UUIDv4) Yes Globally unique identifier for this use case. Must match [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}. Any other value is OLF_SCHEMA_INVALID.
name string Yes Human-readable name. Minimum 1 character. Max 120 characters. Whitespace-only strings are rejected with OLF_SCHEMA_INVALID.
description string (Markdown) No Description of the simulated scenario. CommonMark is rendered in the UI. Max 10,000 characters.
tier enum Yes Always community in a .olf file. See Export Rules.
visibility enum Yes public_readonly, public_collaborative, or private. Must be present in .olf files; a missing field is rejected with OLF_SCHEMA_INVALID on import. The UI defaults to public_readonly when creating a new use case.
created_by string Yes Display name of the original author. Minimum 1 character (whitespace-only strings are rejected with OLF_SCHEMA_INVALID). Max 255 characters. Normalised to display name on export - never a UUID.
created_at string (ISO 8601 UTC) Yes Creation timestamp.
last_edited_at string (ISO 8601 UTC) Yes Timestamp of the last content edit. Preserved on import.
exported_at string (ISO 8601 UTC) Yes Timestamp when this file was exported. Informational only; not stored on import.
imported_at string (ISO 8601 UTC) or null No Timestamp when this use case was last imported into the exporting instance. null if the use case was created natively and never imported. Informational only; not stored on import.
classification object No MITRE ATT&CK and tag metadata. See below.
log_source object No Log source metadata. See below.
run_count integer No Number of times to execute the full log_events sequence. The forger repeats all events from the first entry to the last run_count times. Default: 1. Minimum: 1. Maximum: 1000. A value greater than 1 is the primary mechanism for correlation rule tuning (e.g. run_count: 5 combined with run_delay_ms: 500 simulates five failed logins within 2.5 seconds to trigger a brute-force rule).
run_delay_ms integer No Milliseconds to wait between successive runs. Only applied when run_count > 1; ignored on the first run and after the last run. Default: 1. Minimum: 1. Maximum: 300000 (5 minutes).
log_events LogEvent[] Yes Ordered array of log events. Minimum 1. Maximum 500.

Timestamp fields (created_at, last_edited_at, exported_at, imported_at) must be UTC timestamps in the format YYYY-MM-DDTHH:MM:SS[.sss]Z (ISO 8601 with Z suffix). Millisecond precision is optional; both "2026-06-06T12:00:00Z" and "2026-06-06T12:00:00.000Z" are valid. Formats using a +00:00 UTC offset, date-only strings, and ISO 8601 basic (compact) format are not accepted; a non-conforming timestamp is rejected with OLF_SCHEMA_INVALID.

Classification Fields

Field Type Required Description
mitre_tactics string[] No MITRE ATT&CK tactic IDs (e.g. TA0001). No format validation is applied; any string is accepted.
mitre_techniques string[] No MITRE ATT&CK technique IDs (e.g. T1059.001). No format validation is applied; any string is accepted.
tags string[] No Arbitrary labels for filtering (e.g. linux, brute-force). Each tag must match [a-z0-9][a-z0-9-]*, max 64 characters. Max 50 tags.

Log Source Fields

Field Type Required Description
category enum No os, application, security_device, network_device, or cloud.
platform string No Specific platform within the category (e.g. linux, palo_alto). Free-form string. Max 100 characters.

Log Event Fields

Each entry in log_events has the following fields:

Field Type Required Description
sequence integer Yes 1-based label for this event. Minimum value: 1. Maximum value: 65535. Must be unique within the use case; gaps are permitted (e.g. 1, 5, 10 is valid). Uniqueness is enforced at schema validation time (Import Rules Step 3). The forger processes events in array order - sequence is for uniqueness checking and human labelling only, not sort order.
format enum Yes One of: cef, leef, json, syslog_rfc3164, syslog_rfc5424, windows_evtxml, custom. Use custom when the template does not conform to any named format; no format-specific validation is applied and the template is rendered as a free-form string.
template string Yes Log string with {{variable_name}} placeholders. Max 65,536 characters. Rendered using a sandboxed Jinja2 environment (variable substitution only - see Template Security).
variables object No Variable definitions keyed by name. Max 64 variables per event. Variable names must match [A-Za-z_][A-Za-z0-9_]*. See Variable Schema.
delay_ms integer No Milliseconds to wait after each send of this event, including after the final repetition. 0 means no delay. Default: 0. Minimum: 0. Maximum: 300000 (5 minutes). The next event in the sequence starts immediately after the wait expires. A non-zero delay_ms on the last event introduces a trailing wait before the session completes. If repeat: 0 (event is skipped), delay_ms does not fire.
repeat integer No Number of times to send this event. 0 skips the event entirely. Default: 1. Minimum: 0. Maximum: 128.
comment string No Internal note visible only in the editor. Max 2,000 characters. Stored in the database and included in .olf export. Not passed to the forger engine.

No format-specific structural validation is applied to the template field by the import pipeline for any format value, including named formats. The format field is metadata for UI display and third-party tooling only. A conforming implementation must not reject a template based on its format value during import.

Maximum session wall-clock time

Forger engine implementations must enforce a maximum total session wall-clock time of 24 hours, regardless of delay_ms values, repeat counts, run_count, and run_delay_ms declared in the use case. The cap applies to the entire session including all runs and all inter-run delays. When the limit is reached the forger terminates the session and reports a timeout error to the user. This cap is independent of use case parameters and prevents a single session from holding a slot indefinitely (a potential DoS against the OLF_MAX_SESSIONS cap). A use case whose parameters sum to more than 24 hours of total time is valid to import and edit; only the forger enforces the runtime cap.

Variable Schema

Variables are defined as a map keyed by variable name. Each variable has the following fields:

Field Type Required Description
type enum Yes Data type: string, ip, ipv6, hostname, timestamp, integer, uuid, enum, mac_address, hash, or username. ipv4 is accepted on import as an alias for ip, kept symmetric with ipv6; the exporter always writes ip.
default any Yes Default value used if not overridden at send time. Must be a valid instance of the declared type. Type-specific constraints: type: ip - valid IPv4 address in dotted-decimal notation (IPv6 not accepted); type: ipv6 - valid IPv6 address (any notation accepted by standard IPv6 parsing, including :: compression); type: integer - must satisfy min/max if specified; type: enum - must be one of the declared values; type: timestamp - format depends on format: YYYY-MM-DDTHH:MM:SS[.sss]Z (ISO 8601) when format is rfc5424 or unset; Mon D HH:MM:SS (e.g. Jun 27 00:00:00) when format is rfc3164 - matching whichever style the generator will actually render, so the file stays human-readable; type: uuid - must match [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}; type: hostname - non-empty string, max 253 characters; type: string - any UTF-8 string, max 256 characters; type: mac_address - must match [0-9a-f]{2}(:[0-9a-f]{2}){5} (lowercase hex, colon-separated); type: username - non-empty string, max 64 characters; type: hash - lowercase hex string whose length matches algorithm (32 chars for md5, 40 for sha1, 64 for sha256). Render-time use of default depends on source; see the source values table below.
source enum No Render-time behaviour. Default: user_override. See below.
min integer No Minimum value. Only valid for type: integer. Specifying min on any other type is a validation error (OLF_SCHEMA_INVALID).
max integer No Maximum value. Only valid for type: integer. Specifying max on any other type is a validation error (OLF_SCHEMA_INVALID).
values string[] Yes (if type: enum) List of allowed values. Required when type is enum. Minimum 1 item. Maximum 256 items. Values must be unique within the list. Each value max 256 characters.
algorithm enum No Hash algorithm. Only valid for type: hash. One of md5, sha1, sha256. Default: sha256. Specifying algorithm on any other type is a validation error (OLF_SCHEMA_INVALID).
format enum No Render-time timestamp style. Only valid for type: timestamp. One of rfc3164 (legacy syslog, e.g. Jun 27 14:30:00) or rfc5424 (ISO 8601). Default: rfc5424. Specifying format on any other type is a validation error (OLF_SCHEMA_INVALID).
offset_seconds integer No Seconds to offset the generated timestamp from send time. Only valid for type: timestamp. Negative = past, positive = future (e.g. -7200 = 2 hours ago). Default: 0. Specifying offset_seconds on any other type is a validation error (OLF_SCHEMA_INVALID).
cidr string No Subnet to constrain a generated IPv4 address to (e.g. 192.168.0.0/16). Only valid for type: ip. Mutually exclusive in practice with range (both may be present; cidr takes precedence at render time). Specifying cidr on any other type is a validation error (OLF_SCHEMA_INVALID).
range string No Start-end range to constrain a generated IPv4 address to (e.g. 10.0.0.1-10.0.0.254). Only valid for type: ip. Specifying range on any other type is a validation error (OLF_SCHEMA_INVALID).

If both min and max are specified, max must be greater than or equal to min. A max < min combination is a validation error (OLF_SCHEMA_INVALID).

Template Security

Implementations must render templates in a restricted Jinja2 environment that permits only {{ variable_name }} variable substitution. Block tags ({% %}), comment tags ({# #}), attribute access (.), subscript/item access ([]), call expressions, all filters (|), and Python built-in exposure must be disabled. The Jinja2 environment must be configured with an empty filter map, an empty globals map, and an empty tests map. All Jinja2 delimiter settings (block_start_string, block_end_string, variable_start_string, variable_end_string, comment_start_string, comment_end_string) must remain at their Jinja2 defaults; reconfiguring delimiters bypasses the Stage 1 pre-parse scan and the SAST checks.

Template validation is a two-stage process and must be applied both at import time (as part of schema validation, Step 3 of Import Rules) and when a use case is created or edited through the UI or API. Both code paths must use identical validation logic.

Stage 1 - Pre-parse string scan (run before Jinja2 parsing): Scan the raw template string for the {# byte sequence. A template containing {# anywhere must be rejected with OLF_SCHEMA_INVALID before proceeding to Jinja2 parsing. Jinja2 strips comment tokens before AST construction; they are invisible to AST inspection and cannot be detected any other way.

Stage 2 - AST inspection (run after Jinja2 parsing): Parse the template using the restricted Jinja2 environment to obtain the AST. If Jinja2 parsing raises TemplateSyntaxError, reject the template with OLF_SCHEMA_INVALID before proceeding. Walk the AST and reject the template with OLF_SCHEMA_INVALID if any of the following node types are present: filter expressions (Filter node, e.g. {{ var | upper }}), block tags (If, For, Block, or any non-Output body node, e.g. {% for ... %}), attribute access expressions (Getattr node, e.g. {{ var.attr }}), subscript/item access expressions (Getitem node, e.g. {{ var['key'] }}), or call expressions (Call node, e.g. {{ func() }}).

A conforming implementation must ensure that a crafted template cannot execute code on the host. Variables referenced in the template that are not declared in variables must be treated as a validation error (OLF_SCHEMA_INVALID), not rendered as empty strings.

SAST enforcement - four checks required

The CI SAST pipeline must enforce all four of the following; any one alone is insufficient: 1. Any jinja2.Environment() instantiation that is not SandboxedEnvironment is a hard failure. 2. Any SandboxedEnvironment instantiation where .filters is not explicitly cleared (.filters = {}) before use is a hard failure. 3. Any SandboxedEnvironment instantiation where .globals is not explicitly cleared (.globals = {}) before use is a hard failure. 4. Any SandboxedEnvironment instantiation where .tests is not explicitly cleared (.tests = {}) before use is a hard failure. The Senior Security Officer is responsible for ensuring all four checks are active in the Bandit plugin.

source values:

Value Behaviour
user_override Shown in the pre-flight form with the default value pre-filled. The default is rendered at send time if the operator provides no override.
static Fixed at use case definition time. The value of default is used unchanged at render time. Not shown in the pre-flight form.
generated Auto-generated at render time using a random value of the given type. Shown in the pre-flight form only if the operator chooses to override.
sequence Counter starting at 1, incremented by 1 for each repetition of this event. The counter resets to 1 when the forger begins processing a new entry in the log_events array; it does not reset between repetitions of the same entry. Not shown in the pre-flight form. Variable must declare type: integer; any other type is a validation error (OLF_SCHEMA_INVALID). default is required for schema consistency but is not used at render time; it is exempt from min/max range validation. By convention, set default to 1.

Generated Value Behavior

When source: generated the forger produces a random value at render time for each individual send. The default field is the initial value shown in the pre-flight form if the operator chooses to override.

Type Generated value
ip Random IPv4 address (any valid range; 32-bit random integer formatted as dotted-decimal).
ipv6 Random IPv6 address (128-bit random integer formatted per standard IPv6 notation).
hostname Random 8-character lowercase alphanumeric string.
timestamp Current UTC at render time. ISO 8601 with millisecond precision: YYYY-MM-DDTHH:MM:SS.sssZ.
uuid Random UUIDv4.
string Random 8-character alphanumeric string (uppercase + lowercase + digits).
integer Random integer within [min, max] if both are set; [0, 65535] if neither is set; [min, min + 65535] if only min is set; [0, max] if only max is set.
enum Random item from values using uniform distribution.
mac_address Random locally-administered unicast MAC address: 6 random octets, colon-separated lowercase hex, with the second-least-significant bit of the first octet set and the least-significant bit cleared (IEEE 802 locally-administered unicast addressing).
hash Random lowercase hex string. Length depends on algorithm: 32 characters for md5, 40 for sha1, 64 for sha256 (default). Not a hash of any input - a random value of the correct length and character set.
username Random single lowercase initial concatenated with a surname from a small fixed word list (e.g. jsmith).

Export Rules

The following rules apply when exporting use cases to a .olf file:

  • olf_version is always the current application version - olf_version is not stored per use case in the database. The exporting application always writes its current supported format version (e.g. "1.0") into the exported file, regardless of the olf_version of the original import source.
  • tier is always community - premium use cases cannot be exported to .olf format; only a community clone of a premium use case may be exported, and it exports as community tier.
  • license_key_id is not exported - instance-specific database reference with no meaning outside the originating deployment.
  • source_use_case_id is not exported - instance-specific database reference.
  • Collection / folder assignments are not exported - collection membership is not portable between deployments.
  • visibility is exported as-is - the exporting instance's visibility setting travels with the file. The importing user may adjust visibility after import.
  • created_by is normalised to a display name - the UUID of the original author is never included in the export.
  • exported_at is set at export time - the current UTC timestamp is written to this field on every export regardless of previous values.
  • imported_at reflects the stored import timestamp - if the use case was itself imported into this instance, the stored timestamp is written to this field on export. If the use case was created natively, imported_at is exported as null.

Batch Export

Exporting multiple use cases at once produces a .zip archive of individual .olf YAML files. Each file is named <uuid>.olf where <uuid> is the use case's UUIDv4 id field. This ZIP is an unstructured transport container - it carries no manifest and imposes no further format constraints beyond being a valid ZIP. The file extension is .zip, not .olf.


Import Rules

Error Codes and HTTP Status Codes

All errors from the import flow use the standard JSON error envelope: {"error": {"code": "MACHINE_READABLE_CODE", "message": "...", "details": {}}}.

Error code HTTP status When returned
OLF_SCHEMA_INVALID 422 Schema validation failed, template validation failed, or invalid field values
OLF_VERSION_UNSUPPORTED 422 olf_version is missing, malformed, or has an unsupported major version (higher or lower than the current supported major)
OLF_INVALID_TIER 422 Document tier is not community
OLF_PACKAGE_INVALID 400 ZIP archive is structurally invalid - applies to: community batch import ZIP, the outer activation package ZIP, and the inner .olf-premium ZIP (e.g., missing manifest.json entry, unexpected entry names)
OLF_ACTIVATION_FAILED 422 Premium bundle activation failed at any step
OLF_PREMIUM_VERSION_UNSUPPORTED 422 manifest.json schema_version is not "1"

File size limit: Before Step 1, reject any single-file .olf upload exceeding 1 MB (uncompressed) with HTTP 413. This check occurs before YAML parsing begins.

The following steps are mandatory and must be executed in this order. No partial state is written to the database if any step fails.

  1. YAML parse - Parse the file using ruamel.yaml in YAML 1.2 mode. Before calling yaml.load(), apply the pre-construction anchor/alias scan specified in YAML Parser Requirements: call yaml.compose(io.BytesIO(raw_bytes)) to obtain the raw node tree and walk it for any AliasNode. Reject with OLF_SCHEMA_INVALID if any anchor or alias node is found. Only after a clean node-tree walk may yaml.load() be called.
  2. Version check - Validate olf_version immediately after parse. A missing, malformed, higher major version, or lower major version must be rejected with error code OLF_VERSION_UNSUPPORTED. At v1.0 GA, no prior major format version exists; any olf_version major other than 1 is rejected. A higher minor version within the same supported major is accepted; unknown fields introduced by the newer minor are ignored with a warning (consistent with the Versioning policy).
  3. Schema validation - Validate the document against the schema for the declared olf_version. Reject with OLF_SCHEMA_INVALID if invalid. Schema validation includes: structural field validation against all constraints in this specification; post-construction anchor/alias verification walk (see YAML Parser Requirements); sequence value uniqueness across all entries in log_events; and template security validation for each log_events entry (Stage 1 pre-parse string scan and Stage 2 AST inspection as specified in Template Security).
  4. Tier check - Reject any document with tier other than community with error code OLF_INVALID_TIER.
  5. UUID conflict resolution - Preserve the id from the YAML file. If a use case with the same UUID already exists, present the user with a conflict resolution choice: skip (do not import), overwrite (replace existing), or import as new (generate a fresh UUIDv4). Silent overwriting and silent skipping are both prohibited. The mechanism for presenting this choice (interactive dialog, pre-import summary screen, or API policy parameter) is implementation-defined; the three choices listed are the minimum required set.
  6. Persistence - Write to the database. Set license_key_id and source_use_case_id to null; preserve last_edited_at and visibility from the file; set imported_at to the current UTC timestamp. Do not store exported_at or the file's imported_at value - these are informational fields only. olf_version is not stored per use case in the database (see Export Rules). The id field is stored as the primary key, or replaced with the newly generated UUIDv4 if "import as new" was chosen in Step 5. All other fields from the YAML document (name, description, tier, visibility, created_by, created_at, classification, log_source, log_events, run_count, run_delay_ms) are stored as-is. If run_count or run_delay_ms are absent from the imported document, the application defaults apply (run_count: 1, run_delay_ms: 1). The importing user may adjust visibility after import via the UI or API.

Batch Import

Importing a .zip archive applies the single-file import path to each .olf file found inside the archive. Non-.olf files inside the archive are ignored. Conflict resolution is presented per use case. If two files within the same batch share the same id UUID, the second file is treated as a conflict against the first (as if the first had already been imported); the same three conflict resolution choices apply (skip, overwrite, import as new).

ZIP processing order: Implementations must read the ZIP central directory before processing any entry content. This enables path traversal checks and entry count verification across all entries before any content is read. A one-pass streaming implementation that processes entries before inspecting the full central directory is not conforming.

Before processing any entries, reject the entire batch with HTTP 400 (OLF_PACKAGE_INVALID) if any ZIP entry name contains .. or starts with /. This path traversal check applies to all entries in the archive regardless of file extension, before any extension-based filtering. Do not process partial archives. OLF_PACKAGE_INVALID is the shared error code for any invalid ZIP-based container; it is used for both community batch imports and the premium activation package.

Batch import resource limits:

  • Maximum 100 .olf entries per batch ZIP. This count is verified from the ZIP central directory before any entry content is read.
  • Maximum 1 MB uncompressed per .olf file, enforced on bytes actually read during decompression. Abort and reject the entire batch on the first file that exceeds this limit; return HTTP 413.
  • Maximum 50 MB total uncompressed size across all entries, enforced as a running total on bytes actually read. Abort and reject the entire batch when the running total exceeds this limit; return HTTP 413.
  • Size limits are enforced on bytes actually read during decompression, not on sizes declared in the ZIP central directory metadata.

Community Repository Layout

Each use case in the openlogforge/community-usecases repository is a single .olf YAML file, organised by category subdirectory:

community-usecases/
  initial-access/
    ssh-brute-force-valid-login.olf
    rdp-password-spray.olf
  privilege-escalation/
    sudo-abuse-linux.olf
  ...

A pull request to add or update a use case changes exactly one .olf file. GitHub renders the full YAML diff. CI validates every .olf file against the olf_version: "1.0" schema on every PR.


Premium Bundle (.olf-premium)

The .olf-premium format is the encrypted distribution format for premium content packs. Creation of .olf-premium bundles is restricted to the openLogForge project.

The build pipeline is: a private git repository holds the source use case files as plain YAML 1.2 documents using the same schema as community .olf files, with tier: premium. A build script reads those files, encrypts each one, and packages the results into a .olf-premium bundle. The plaintext source is never distributed. Keeping a single schema for both formats avoids divergence in the application and the build tooling.

Activation Package

The user uploads an activation package - a ZIP archive (file extension .zip) delivered by the Lemon Squeezy / Keygen.sh purchase flow. This outer ZIP is a delivery envelope; it is not an .olf-premium file. Its contents:

license.lic              - Keygen.sh-issued signed license file (JSON)
<bundle_id>.olf-premium  - encrypted use case bundle (this spec)

license.lic is the Keygen.sh offline license file: a JSON object with two fields:

Field Description
enc Base64url-encoded license data (the signed payload bytes); no = padding
sig Base64url-encoded Ed25519 signature over the raw bytes of enc; no = padding

Base64url decoding

Both enc and sig use base64url without = padding. The same applies to hkdf_salt and license_signature in manifest.json. To decode in Python: base64.urlsafe_b64decode(value + '==') (Python ignores excess padding).

license_signature in manifest.json is the sig value from license.lic, copied into the manifest by the bundle build pipeline at generation time. It binds this specific .olf-premium bundle to this specific .lic file. A valid .lic cannot be paired with a different bundle.

Outer activation package validation (mandatory before the .olf-premium file is opened):

Implementations must read the outer ZIP central directory before processing any entry content. This enables path traversal checks and entry count verification across all entries before any content is read. A one-pass streaming implementation that processes entries before inspecting the full central directory is not conforming.

  1. Reject any entry whose name contains .. or starts with /. Return HTTP 400 (OLF_PACKAGE_INVALID).
  2. Verify the archive contains exactly one .lic file named license.lic and exactly one .olf-premium file. Return HTTP 400 (OLF_PACKAGE_INVALID) if either is absent, if the .lic file has any other name, or if unexpected entries are present.
  3. Verify the license.lic Ed25519 signature (Activation Flow Step 3a) before extracting or reading any content from the .olf-premium file. A failed signature check at this step returns HTTP 422 (OLF_ACTIVATION_FAILED) - not OLF_PACKAGE_INVALID - because it is a security check, not a format check.

Premium Archive Layout

A .olf-premium file is a ZIP archive (deflate compression) with the following structure:

manifest.json
usecases/
  <uuid>.enc
  <uuid>.enc
  ...

Archive entry constraints (enforced before any content is read):

Implementations must read the .olf-premium ZIP central directory before processing any entry content. This enables entry name validation and manifest.json presence verification without opening any entry. A one-pass streaming implementation that processes entries before inspecting the full central directory is not conforming.

  • Entry names must be exactly manifest.json or match usecases/<uuidv4>.enc (UUIDv4 regex: [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}). Any other entry name must cause rejection with OLF_PACKAGE_INVALID.
  • manifest.json must be present as an entry. A .olf-premium archive without a manifest.json entry must be rejected with OLF_PACKAGE_INVALID before any other entry is read.
  • Reject any entry name containing .. or starting with /.
  • Maximum 1 MB for manifest.json (uncompressed). Reject with OLF_PACKAGE_INVALID before reading any other entry if this limit is exceeded.
  • Maximum 500 .enc entries per bundle.
  • Maximum 50 MB uncompressed per .enc file.
  • Maximum 250 MB total uncompressed size across all entries.
  • All size limits are enforced on bytes actually read during decompression, not on sizes declared in the ZIP central directory metadata.

Rejection with HTTP 400 (OLF_PACKAGE_INVALID) must occur before any decryption is attempted.

Premium manifest.json Schema

{
  "schema_version": "1",
  "pack_id": "<uuidv4>",
  "bundle_id": "<uuidv4 - unique per customer>",
  "issued_at": "<ISO 8601 UTC>",
  "license_key_fingerprint": "<SHA-256 hex of the canonicalized license key>",
  "hkdf_salt": "<base64url - 32 CSPRNG bytes, unique per bundle>",
  "encryption": {
    "algorithm": "AES-256-GCM",
    "kdf": "HKDF-SHA256",
    "kdf_info_fields": ["license_key_fingerprint", "pack_id"]
  },
  "license_signature": "<base64url - Keygen.sh Ed25519 signature>",
  "payload_checksums": {
    "<uuid>": "<SHA-256 hex of the encrypted .enc bytes>"
  }
}

Note

payload_checksums are computed over the encrypted .enc bytes, not the plaintext. This allows tamper detection before any decryption attempt.

Manifest field notes:

  • pack_id identifies the content pack and is the same across all customer bundles for the same pack.
  • bundle_id is a unique UUIDv4 generated per purchase. No two customers share a bundle_id. It identifies this specific customer's encrypted bundle.
  • issued_at is the timestamp at which Keygen.sh issued the license for this purchase.
  • kdf_info_fields is informational documentation only. The implementation MUST NOT read this field to determine the HKDF info parameter. The info parameter is always license_key_fingerprint.encode('utf-8') + pack_id.encode('utf-8') regardless of this field's value. Treating kdf_info_fields as normative would allow a tampered bundle to redirect the key derivation. After Step 0 JSON parsing, implementations must not reference kdf_info_fields for any operational purpose; the field may be deleted from the parsed manifest object (manifest_data.pop("kdf_info_fields", None)) to enforce this at the call site and prevent accidental use in later steps.
  • license_signature is the sig value from license.lic (see Activation Package).
  • payload_checksums keys are bare UUIDs - the .enc filename without the usecases/ prefix and without the .enc extension (e.g. "550e8400-e29b-41d4-a716-446655440000", not "usecases/550e8400-...enc").

Each .enc file decrypts to a valid YAML 1.2 use case document following the community schema defined in this specification, with tier: premium. Decrypted payload validation (including olf_version check, tier check, and schema validation) is performed in Step 5 of the Activation Flow. Any failure returns OLF_ACTIVATION_FAILED.

Key derivation (HKDF-SHA256, RFC 5869):

Parameter Value
IKM Canonicalized license key bytes (key.strip().lower() + Unicode NFC)
Salt hkdf_salt from manifest.json - 32 CSPRNG bytes, base64url-decoded. Unique per bundle. The bundle build pipeline must generate a fresh hkdf_salt for every bundle; reusing a salt across bundles that share the same derived key reintroduces nonce-reuse risk for the per-file AES-256-GCM nonces.
Info license_key_fingerprint_bytes \|\| pack_id_bytes - UTF-8 encoding of both fields as written: the 64-character lowercase hex string of license_key_fingerprint (64 bytes) concatenated with the 36-character UUID string of pack_id (36 bytes). Neither field is hex-decoded or otherwise transformed before concatenation.
Output length (L) 32 bytes (256-bit key for AES-256-GCM).

The derived key is held in memory only for the duration of decryption and is discarded immediately after. It is never written to disk, logged, or returned in any API response.

.enc File Binary Layout

[12 bytes] nonce (generated per file using a CSPRNG - os.urandom(12) in Python)
[N bytes]  ciphertext + 16-byte GCM authentication tag

Using Python's cryptography library (AESGCM): the GCM authentication tag is appended to the ciphertext automatically. Write path: nonce + aesgcm.encrypt(nonce, plaintext_yaml_bytes, None). Read path: nonce = data[:12]; ciphertext_and_tag = data[12:]. Any implementation that deviates from this layout produces bundles that cannot be decrypted by the reference implementation.

schema_version Versioning

The manifest.json schema_version field is independent from the community olf_version. It versions the manifest structure only. Current value: "1". Breaking changes to the manifest schema bump this value. The application must reject any bundle with an unrecognised schema_version with error code OLF_PREMIUM_VERSION_UNSUPPORTED.

There is no minor-version tolerance for schema_version. The application accepts only the exact value "1". Any other value - including numerically higher values - must be rejected with OLF_PREMIUM_VERSION_UNSUPPORTED. This strict check is intentional: the manifest controls cryptographic operations, and silently accepting an unknown schema version is a security risk.

Design note: The two versioning strategies in this spec are intentionally asymmetric. Community olf_version is permissive (unknown minor-version fields generate a warning and are ignored) to support ecosystem extensibility and third-party tooling. schema_version is strict (exact match only) because the manifest governs key derivation and decryption - any ambiguity in its interpretation is a security boundary violation. Do not normalize these two strategies.

Activation Flow

The activation flow is the mandatory sequence of steps the application performs when a user activates an .olf-premium bundle. The order must never be rearranged.

Error handling: Any failure at any step returns HTTP 422 with error code OLF_ACTIVATION_FAILED and message "License activation failed. Verify the activation package is complete and was not corrupted in transit." The specific failed step MUST NOT appear in the API response, in any application log, or in the audit log entry. The audit log entry records: event type license_activation_failed, actor, pack_id (if parsed from manifest.json before the failure), and timestamp.

Key zeroize requirement: The derived key bytes and canonical key bytes MUST be zeroized on every exit path - both success and failure - before the activation function returns. Do not defer to garbage collection.

Post-activation persistence: This spec covers validation and decryption only. After Step 6 completes on the success path, the API handler persists the decrypted use cases as specified in PRD §3.6.1 step 3 (re-encrypted under OLF_SECRET_KEY into the template_encrypted column). OLF_SECRET_KEY is a 256-bit application-level encryption key supplied via environment variable; see PRD §3.6.1 for key management requirements. The DB commit is atomic - no use cases are written until all files pass Steps 0-6.

Instance binding

This protocol does not provide cryptographic instance binding. A valid activation package can be activated on any instance that accepts the license key. Portability between instances is governed by Keygen.sh license policy, not by this cryptographic protocol. The duplicate activation check in Step 3c prevents re-activation on the same instance; it does not prevent activation on a different instance.

Step 0 - Open archive and parse manifest

Open the .olf-premium ZIP and locate manifest.json. If the entry is absent, abort with OLF_PACKAGE_INVALID. Read and parse the content as JSON; if the JSON parse fails or any required top-level field (schema_version, pack_id, bundle_id, issued_at, license_key_fingerprint, hkdf_salt, encryption, license_signature, payload_checksums) is missing, abort with OLF_ACTIVATION_FAILED. All required fields must also have the correct JSON type: schema_version, pack_id, bundle_id, issued_at, license_key_fingerprint, hkdf_salt, and license_signature must be JSON strings; encryption must be a JSON object; payload_checksums must be a JSON object with string keys and string values. A type mismatch on any required field is OLF_ACTIVATION_FAILED. Validate schema_version from the parsed manifest: if not exactly "1", abort with OLF_PREMIUM_VERSION_UNSUPPORTED (see schema_version Versioning). Validate encryption.algorithm equals exactly "AES-256-GCM" and encryption.kdf equals exactly "HKDF-SHA256"; any other value is OLF_ACTIVATION_FAILED. After base64url decoding hkdf_salt, verify the result is exactly 32 bytes; any other length is OLF_ACTIVATION_FAILED.

Step 1 - Key canonicalization

Normalize the license key before any operation: key.strip().lower() followed by Unicode NFC normalization (unicodedata.normalize('NFC', key) in Python). Convert the result to bytearray: canonical_key = bytearray(normalized.encode('utf-8')). All subsequent steps use only this bytearray. Using bytearray (not bytes) is required so Step 6 can overwrite the value in place.

Step 2 - Fingerprint check (constant-time)

Verify license_key_fingerprint is a valid 64-character lowercase hex string before proceeding; abort with OLF_ACTIVATION_FAILED if not. Compute SHA-256(canonical_key_bytes) encoded as lowercase hex. Compare the result against manifest.json license_key_fingerprint using a constant-time comparison (hmac.compare_digest or equivalent); abort on mismatch. Both sides are compared as lowercase hex strings; the manifest field is always lowercase hex (enforced by the bundle build pipeline).

Step 3 - Signature verification

Two sub-checks, both mandatory, both must pass before Step 4 begins:

3a - Verify the Keygen.sh license file: Read license.lic from the activation package. Parse as JSON; extract enc (the license data, base64url-decoded to bytes) and sig (the Ed25519 signature, base64url-decoded to bytes). Verify using the Keygen.sh Ed25519 public key embedded in the application at build time: load the key from the named constant into an Ed25519PublicKey instance, then call keygen_public_key.verify(signature=sig, data=enc) (Python cryptography library; method signature is verify(signature: bytes, data: bytes) - first argument is the signature bytes, second is the signed data bytes; raises InvalidSignature on failure). Abort on failure.

Ed25519 public key rotation

The Keygen.sh Ed25519 public key is embedded at build time and cannot be updated at runtime. If Keygen.sh rotates their signing key, all deployed instances will reject newly issued licenses until a patch release is deployed with the updated key. The Senior Security Officer is responsible for coordinating key rotation with Keygen.sh and triggering a patch release. The embedded key must be stored in a named constant (not inline in the verification call) so that a key rotation patch changes exactly one location in the codebase.

3b - Cross-check bundle-license binding: Before comparing, base64url-decode license_signature from the manifest and verify the result is exactly 64 bytes (the Ed25519 signature length); abort with OLF_ACTIVATION_FAILED if the decoded length differs. Compare manifest.json license_signature (decoded bytes) against sig from license.lic (decoded bytes) using hmac.compare_digest. Abort on mismatch. This confirms the .olf-premium bundle was generated for this specific license file and prevents a valid .lic from being paired with a different bundle.

3c - Duplicate activation check: Verify that bundle_id from the parsed manifest does not already exist in the license_keys table as an active record. If it does, abort with OLF_ACTIVATION_FAILED. This check is placed after Step 3 to avoid a timing side-channel: Steps 1 and 2 involve only in-memory cryptographic operations; a database query here (after signature verification passes) is indistinguishable in response time from a failed checksum or schema check. This check must be backed by a database-level UNIQUE constraint on the bundle_id column; a unique constraint violation at persistence time must also be treated as OLF_ACTIVATION_FAILED. Re-activating a bundle requires explicit deactivation via DELETE /license-keys/:id first.

No bypass permitted

There is no skip path, no debug flag, and no environment variable that bypasses Step 3. Derivation (Step 4) must never precede verification (Step 3) in any code path. This invariant is enforced by a CI SAST hard gate. Additionally, Steps 0-6 must be implemented as a single encapsulated function with no externally accessible entry point between steps - Step 4 must receive an opaque proof-of-verification value that is only produced by a successful return from Step 3 (for example, a function-scoped boolean flag set only on the Step 3 success path, or a closure variable). This structural constraint ensures that a future refactor cannot create a bypass path that the SAST gate would not catch.

Step 4 - HKDF key derivation

Derive the bundle decryption key in memory only using cryptography.hazmat.primitives.kdf.hkdf.HKDF (full extract-then-expand; do NOT use HKDFExpand, which skips the extraction step and requires a uniformly random IKM):

HKDF-SHA256(IKM=bytes(canonical_key), salt=base64.urlsafe_b64decode(hkdf_salt + '=='), info=license_key_fingerprint.encode('utf-8') + pack_id.encode('utf-8'), length=32)

The info parameter is the UTF-8 encoding of both string fields as written (64 bytes of hex + 36 bytes of UUID = 100 bytes total). Do not hex-decode license_key_fingerprint before encoding; use the hex string itself.

Store the result as bytearray: derived_key = bytearray(HKDF(...).derive(bytes(canonical_key))).

SAST enforcement - HKDF info parameter

The CI SAST pipeline must enforce a fifth check in addition to the four Jinja2 checks: the info parameter passed to the HKDF call must be a hardcoded construction of license_key_fingerprint_bytes + pack_id_bytes and must never be derived from any parsed manifest field (including kdf_info_fields). Any code path where info is read from manifest_data["kdf_info_fields"] or any other parsed value is a hard CI failure. This prevents a tampered bundle from redirecting key derivation. The Senior Security Officer is responsible for ensuring this check is active in the Bandit plugin.

Step 5 - Payload decryption

Pre-check: Verify that the set of UUIDs in payload_checksums exactly matches the set of .enc filenames in usecases/. Abort with OLF_ACTIVATION_FAILED if any .enc file has no checksum entry, or if any payload_checksums entry has no corresponding .enc file.

For each .enc file in usecases/:

  1. Verify the uncompressed size of the .enc file is at least 28 bytes (12-byte nonce + 16-byte GCM authentication tag). Abort with OLF_ACTIVATION_FAILED if smaller.
  2. Compute SHA-256 of the raw encrypted file bytes. Compare against the corresponding entry in payload_checksums using constant-time comparison. Abort on mismatch.
  3. Read nonce = data[:12], ciphertext_and_tag = data[12:].
  4. Decrypt: AESGCM(derived_key).decrypt(nonce, ciphertext_and_tag, None). A GCM authentication tag failure on any file aborts the entire activation - no partial state is committed.

Post-decryption validation (after all files are decrypted): Parse each resulting YAML using ruamel.yaml in YAML 1.2 mode. Apply the full YAML parser requirements from YAML Parser Requirements, including the pre-construction anchor/alias scan before calling yaml.load(). Validate: (a) olf_version is present and a supported value - abort if not; (b) tier: premium is set - abort if not; (c) the document passes schema validation for its olf_version - abort if not. A failure on any document aborts the entire activation.

On any abort within this step, proceed immediately to Step 6 (zeroize) before returning the error. Do not defer zeroization to the success path only.

Step 6 - Zeroize

Overwrite the derived key bytes and the canonical key bytes in memory immediately, before the activation function returns. Python: derived_key[:] = b'\x00' * len(derived_key); canonical_key[:] = b'\x00' * len(canonical_key) (requires bytearray - see Step 1 and Step 4).

Zeroize scope

This step covers the named bytearray variables only. HKDF.derive() internally produces an intermediate bytes object (immutable in Python) that cannot be zeroized. This is the realistic bound of the guarantee in CPython: the bytearray buffers are overwritten in place; any transient copies the runtime made are not. For the threat model (in-memory forensics on a live server process), this is the expected limit and should be acknowledged in the implementation review.


YAML Parser Requirements

.olf files must be encoded as UTF-8. UTF-16 and UTF-32 encodings are not supported.

Implementations that read or write .olf use case files must use a YAML 1.2 compliant parser. YAML 1.1 parsers must not be used because they silently coerce bare strings such as yes, no, on, off to boolean values - a behaviour that corrupts log templates containing these strings.

Recommended parser: ruamel.yaml >= 0.15 (Python). Defaults to YAML 1.2.

For other languages, verify that your YAML library implements the YAML 1.2 specification and does not apply YAML 1.1 implicit type coercions.

YAML anchors and aliases are prohibited. A .olf document MUST NOT contain anchor definitions (&anchor) or alias references (*alias). Anchor expansion (analogous to XML "Billion Laughs") causes exponential memory growth during object construction - file size limits alone do not bound the in-memory representation, so anchor detection must occur before object construction.

Implementations must apply both of the following checks in order:

Pre-construction node scan (mandatory, run before constructing Python objects): For ruamel.yaml, call yaml.compose(io.BytesIO(raw_bytes)) to obtain the raw YAML node tree without constructing Python objects, then walk the node tree and check for any AliasNode instances (from ruamel.yaml.nodes import AliasNode). (ruamel.yaml's compose() accepts a stream, not raw bytes; wrap the bytes in io.BytesIO before passing.) The compose() step does not expand aliases, so it is not vulnerable to exponential memory growth. If any AliasNode is found, reject with OLF_SCHEMA_INVALID before calling yaml.load(). Only after a clean node-tree walk may yaml.load() be called to construct the Python document.

For other YAML 1.2 implementations, use a streaming or node-level parser to detect anchor and alias tokens or nodes before object construction. Do not parse to a fully constructed object graph and then inspect - the construction step is where alias expansion occurs.

Post-construction verification (mandatory, run after yaml.load()): Walk the constructed document object and confirm no node carries an anchor attribute and no node is an alias reference. Reject with OLF_SCHEMA_INVALID if any are found. This step provides additional assurance and catches any edge cases that the pre-construction scan did not stop.

The pre-construction scan is the primary gate: a conforming implementation MUST NOT call the YAML object constructor (yaml.load() or equivalent) on any document that contains anchor or alias nodes. Both checks are required - the pre-construction scan prevents anchor expansion by stopping construction before it begins; the post-construction walk is additional assurance that no anchor or alias node survived into the constructed object.


Changelog

Version Date Changes
olf_version: "1.0" 2026-08-04 Added cidr and range fields to the variable schema (valid only for type: ip). Same class of gap as format/offset_seconds below: these were accepted as YAML keys but silently dropped on import since the schema didn't model them, so a type: ip variable declared with cidr: 192.168.0.0/16 would import as an unconstrained fully-random IPv4 - a functional regression, not just a display one. Writer updated to round-trip both fields on export. Backward-compatible: existing files without cidr/range are unaffected.
olf_version: "1.0" 2026-08-04 Added format and offset_seconds fields to the variable schema (valid only for type: timestamp; format one of rfc3164/rfc5424, default rfc5424). Previously these were accepted as YAML keys but silently dropped on import/export since the schema didn't model them, so a declared format: rfc3164 had no effect on the generated value. default validation for type: timestamp now follows the declared format: ISO 8601 (YYYY-MM-DDTHH:MM:SS[.sss]Z) when format is rfc5424 or unset (unchanged), or Mon D HH:MM:SS (e.g. Jun 27 00:00:00) when format is rfc3164, so the default stays human-readable in whichever style the generator will actually render. Writer updated to round-trip both fields on export. Backward-compatible: existing files (none of which declare format) are unaffected.
olf_version: "1.0" 2026-08-04 type: ipv4 is now accepted on import as an alias for type: ip, normalized internally before schema validation - kept symmetric with ipv6 so hand-authored .olf files may use either spelling. The exporter is unchanged and always writes ip. Backward-compatible: existing files using ip are unaffected.
olf_version: "1.0" 2026-07-21 Added four variable types: ipv6, mac_address, hash, username. All four were already generatable by the application (source: generated) but had no .olf-legal representation, so any use case relying on them could not be exported - the writer previously fell back to a lossy string remap or, for ipv6, produced a file that failed re-import. Added algorithm field to the variable schema (valid only for type: hash; one of md5/sha1/sha256, default sha256) to declare hash length. Default-value validation added per new type: ipv6 - valid IPv6 address; mac_address - [0-9a-f]{2}(:[0-9a-f]{2}){5}; username - non-empty, max 64 chars; hash - lowercase hex string matching algorithm's length. Generated Value Behavior table updated with all four. Backward-compatible: existing .olf files are unaffected, this only legalizes types that previously could not appear in a valid file at all.
olf_version: "1.0" 2026-07-04 Added run_count and run_delay_ms top-level fields. run_count (integer, default 1, max 1000) sets how many times the full log_events sequence is repeated. run_delay_ms (integer, default 1, max 300000) sets the delay in milliseconds between successive runs. Both fields are optional and backward-compatible: existing .olf files without these fields import with application defaults. The 24-hour session wall-clock cap now explicitly covers run_count and run_delay_ms in addition to per-event delay_ms and repeat. Import Rules Step 6 updated to store both fields.
olf_version: "1.0" 2026-06-19 Cross-role correction pass (no format change). Security: Instance binding note corrected to reference Step 3c for duplicate activation check (was incorrectly referencing Step 0); license_key_fingerprint format validation reordered to occur before hmac.compare_digest in Step 2; outer activation package validation now requires reading the ZIP central directory before processing any entry content, consistent with community batch import and .olf-premium archive requirements. Architecture: Import Rules Step 1 inline yaml.compose() call corrected to include io.BytesIO(raw_bytes) wrapper (cross-reference to YAML Parser Requirements was present but inline paraphrase was incomplete); source: user_override source values table entry clarified - default value is pre-filled and rendered at send time if the operator provides no override. Developer: OLF_VERSION_UNSUPPORTED error code table description corrected to include lower major version as a trigger (Step 2 and Versioning section already specified this; table was incomplete); batch import per-file and total-batch size limit violations now explicitly specify HTTP 413, consistent with single-file import precedent.
olf_version: "1.0" 2026-06-19 Cross-role production readiness review - specification corrections (no format change). Security: Ed25519 verify() rewritten to instance method with named arguments (keygen_public_key.verify(signature=sig, data=enc)); Call node added to Stage 2 Jinja2 AST rejection list; Jinja2 delimiter settings required to remain at defaults; license_signature decoded-length check (exactly 64 bytes) added before Step 3b hmac.compare_digest; duplicate activation check moved from Step 0 to Step 3c to eliminate timing side-channel; HKDF info encoding clarified (UTF-8 of hex and UUID strings as written, 64 + 36 = 100 bytes); hkdf_salt freshness requirement added for bundle build pipeline; kdf_info_fields active discard requirement added after Step 0 parsing; zeroize scope documented (named bytearray variables only; HKDF.derive() intermediate bytes is not zeroizable). Architecture: YAML anchor defense rewritten - pre-construction compose() scan is the primary gate; post-construction walk is additional assurance (previous "authoritative enforcement gate" wording was contradictory); OLF_PACKAGE_INVALID error code description corrected to include inner .olf-premium ZIP structural failures; lower-major olf_version handling clarified (rejected with OLF_VERSION_UNSUPPORTED at v1.0 GA; no prior major format version exists, no migration path applies); Import Rules Step 2 aligned with Versioning section on lower-major behavior. Developer: ruamel.yaml compose() requires io.BytesIO wrapper (not raw bytes); TemplateSyntaxError in Stage 2 explicitly returns OLF_SCHEMA_INVALID; source: sequence default exempt from min/max range check, convention to set default: 1; visibility added to Step 6 stored-field enumeration (was missing from list despite being preserved); batch import path traversal check applies to all entries before extension filtering; Step 5 abort path must proceed to Step 6 zeroize before returning; name whitespace-only rejection documented (consistent with created_by).
olf_version: "1.0" 2026-06-19 Production readiness passes - three-pass review (no format change). Field constraints: name min 1, created_by max 255, template max 65,536, variables max 64/event, comment max 2,000, platform max 100, tags pattern + max 50, values min 1/max 256/unique/each-max-256, log_events max 500, sequence min 1/max 65535. Template security: two-stage validation (Stage 1 pre-parse {# scan; Stage 2 AST walk with Getitem, Getattr, Filter, block-tag rejection); SandboxedEnvironment.filters/globals/tests = {}; SAST checks 1-5 defined; TemplateSyntaxError is schema invalid; validation required on create/edit paths as well as import. Import rules: HTTP status codes table; single-file 1 MB limit (HTTP 413); ZIP central directory scan before any content read; batch limits (100 files, 1 MB/file, 50 MB total, streaming enforcement); OLF_PACKAGE_INVALID shared across community and premium containers; id conflict resolution mechanism noted; visibility required on import. Versioning: olf_version format regex "<major>.<minor>"; not stored per use case; exporter always writes current app version. Timestamps: Z-suffix only (YYYY-MM-DDTHH:MM:SS[.sss]Z). Variable schema: default constraints per type; min/max type restrictions; max < min error; source: sequence type restriction. Premium activation: Step 0 defined; manifest field type validation; encryption.algorithm/kdf value checks; hkdf_salt 32-byte validation; bundle_id UNIQUE constraint; Step 3 structural enforcement (opaque proof-of-verification); license_key_fingerprint lowercase hex validation; .enc minimum 28-byte check; post-decryption YAML parser requirements; OLF_SECRET_KEY defined; instance binding threat model; Ed25519 key rotation process; yaml.compose() node-tree anchor detection. Community format: no-integrity design note; olf_version/schema_version versioning asymmetry design note; batch export <uuid>.olf naming; session 24h wall-clock cap.
olf_version: "1.0" 2026-06-13 RFC-002: Replaced ZIP container with single YAML document. One file per use case. Versioning simplified to single olf_version field. manifest.json and checksum verification removed from community format. exported_at field added. Classification and log_source fields restructured as nested objects.
olf_version: "1" / schema_version: "1.0" 2026-06-06 Initial draft (ADR-001). ZIP layout with manifest.json. Superseded by RFC-002.