.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_versionmajor lower than1are rejected withOLF_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_versionis always the current application version -olf_versionis 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 theolf_versionof the original import source.tieris alwayscommunity- premium use cases cannot be exported to.olfformat; only a community clone of a premium use case may be exported, and it exports ascommunitytier.license_key_idis not exported - instance-specific database reference with no meaning outside the originating deployment.source_use_case_idis not exported - instance-specific database reference.- Collection / folder assignments are not exported - collection membership is not portable between deployments.
visibilityis exported as-is - the exporting instance's visibility setting travels with the file. The importing user may adjust visibility after import.created_byis normalised to a display name - the UUID of the original author is never included in the export.exported_atis set at export time - the current UTC timestamp is written to this field on every export regardless of previous values.imported_atreflects 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_atis exported asnull.
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.
- YAML parse - Parse the file using
ruamel.yamlin YAML 1.2 mode. Before callingyaml.load(), apply the pre-construction anchor/alias scan specified in YAML Parser Requirements: callyaml.compose(io.BytesIO(raw_bytes))to obtain the raw node tree and walk it for anyAliasNode. Reject withOLF_SCHEMA_INVALIDif any anchor or alias node is found. Only after a clean node-tree walk mayyaml.load()be called. - Version check - Validate
olf_versionimmediately after parse. A missing, malformed, higher major version, or lower major version must be rejected with error codeOLF_VERSION_UNSUPPORTED. At v1.0 GA, no prior major format version exists; anyolf_versionmajor other than1is 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). - Schema validation - Validate the document against the schema for the declared
olf_version. Reject withOLF_SCHEMA_INVALIDif invalid. Schema validation includes: structural field validation against all constraints in this specification; post-construction anchor/alias verification walk (see YAML Parser Requirements);sequencevalue uniqueness across all entries inlog_events; and template security validation for eachlog_eventsentry (Stage 1 pre-parse string scan and Stage 2 AST inspection as specified in Template Security). - Tier check - Reject any document with
tierother thancommunitywith error codeOLF_INVALID_TIER. - UUID conflict resolution - Preserve the
idfrom 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. - Persistence - Write to the database. Set
license_key_idandsource_use_case_idto null; preservelast_edited_atandvisibilityfrom the file; setimported_atto the current UTC timestamp. Do not storeexported_ator the file'simported_atvalue - these are informational fields only.olf_versionis not stored per use case in the database (see Export Rules). Theidfield 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. Ifrun_countorrun_delay_msare absent from the imported document, the application defaults apply (run_count: 1,run_delay_ms: 1). The importing user may adjustvisibilityafter 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
.olfentries per batch ZIP. This count is verified from the ZIP central directory before any entry content is read. - Maximum 1 MB uncompressed per
.olffile, 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.
- Reject any entry whose name contains
..or starts with/. Return HTTP 400 (OLF_PACKAGE_INVALID). - Verify the archive contains exactly one
.licfile namedlicense.licand exactly one.olf-premiumfile. Return HTTP 400 (OLF_PACKAGE_INVALID) if either is absent, if the.licfile has any other name, or if unexpected entries are present. - Verify the
license.licEd25519 signature (Activation Flow Step 3a) before extracting or reading any content from the.olf-premiumfile. A failed signature check at this step returns HTTP 422 (OLF_ACTIVATION_FAILED) - notOLF_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:
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.jsonor matchusecases/<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 withOLF_PACKAGE_INVALID. manifest.jsonmust be present as an entry. A.olf-premiumarchive without amanifest.jsonentry must be rejected withOLF_PACKAGE_INVALIDbefore any other entry is read.- Reject any entry name containing
..or starting with/. - Maximum 1 MB for
manifest.json(uncompressed). Reject withOLF_PACKAGE_INVALIDbefore reading any other entry if this limit is exceeded. - Maximum 500
.encentries per bundle. - Maximum 50 MB uncompressed per
.encfile. - 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_ididentifies the content pack and is the same across all customer bundles for the same pack.bundle_idis a unique UUIDv4 generated per purchase. No two customers share abundle_id. It identifies this specific customer's encrypted bundle.issued_atis the timestamp at which Keygen.sh issued the license for this purchase.kdf_info_fieldsis informational documentation only. The implementation MUST NOT read this field to determine the HKDFinfoparameter. Theinfoparameter is alwayslicense_key_fingerprint.encode('utf-8') + pack_id.encode('utf-8')regardless of this field's value. Treatingkdf_info_fieldsas normative would allow a tampered bundle to redirect the key derivation. After Step 0 JSON parsing, implementations must not referencekdf_info_fieldsfor 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_signatureis thesigvalue fromlicense.lic(see Activation Package).payload_checksumskeys are bare UUIDs - the.encfilename without theusecases/prefix and without the.encextension (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/:
- Verify the uncompressed size of the
.encfile is at least 28 bytes (12-byte nonce + 16-byte GCM authentication tag). Abort withOLF_ACTIVATION_FAILEDif smaller. - Compute SHA-256 of the raw encrypted file bytes. Compare against the corresponding entry in
payload_checksumsusing constant-time comparison. Abort on mismatch. - Read
nonce = data[:12],ciphertext_and_tag = data[12:]. - 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. |