audit-logging-for-configuration-changes · diff

v1.2.0 to v1.3.0

144 added, 28 removed. Audit A to A.

---
name: audit-logging-for-configuration-changes
- description: Immutable audit logging engine enforcing SEC Reg SCI and FINRA Rule 3110
- compliance for algorithmic trading configuration changes.
+ description: >-
+ Tamper-evident, hash-chained audit trail for changes to trading-system
+ configuration and risk-control parameters — recording who changed what, from
+ which value to which, why, and in which environment; retaining rejected
+ attempts as supervisory evidence; and verifying an archived chain for edits,
+ middle deletions, reordering and gaps.
domain: deployment-ops
subdomain: regulatory
tags:
- compliance
- audit-logging
- - sec-reg-sci
+ - change-management
+ - tamper-evident
- finra-3110
- risk-controls
brokers_frameworks:
- generic
- version: "1.2.0"
+ version: "1.3.0"
author: System
license: MIT
---
## When to Use
- Use this skill to track modifications to live trading algorithms, risk parameters, and system configurations. FINRA Rule 3110 (Supervision) and Regulatory Notice 15-09 require a documented change-management process for algorithmic trading code and risk-control parameter settings, including records of approvals and reasoning. SEC Rule 17a-4 imposes WORM-style retention for broker-dealer records. SEC Regulation SCI applies *directly* only to "SCI entities" (SROs, certain ATSs, plan processors, certain exempt clearing agencies); a 2023 SEC proposal would extend it to certain large broker-dealers. Firms not mandated by SCI nonetheless commonly adopt SCI-style controls voluntarily.
+ Use this skill when changes to live trading algorithms, risk-control parameters, or
+ system configuration need to leave an evidence trail rather than a shrug. The engine
+ intercepts a change request, checks that it names a parameter, an authenticated
+ principal and a reason, and emits a canonical JSON record chained by SHA-256 to the
+ record before it.
- This engine intercepts configuration updates, validates that a justification and authenticated principal are present, and formats the change into a compliance-ready, tamper-evident JSON audit log. Use it for both approved changes and rejected attempts (a failed change attempt is itself a supervisory event).
+ It is worth being precise about *whose* obligation this serves, because "the
+ regulators require an audit log" is the kind of claim that is usually stated too
+ broadly:
+ - **FINRA member broker-dealers** are subject to Rule 3110 (Supervision): written
+ supervisory procedures under 3110(b)(1), a record of supervisory designations kept
+ at least three years under 3110(b)(6)(B), and internal inspection reports kept at
+ least three years under 3110(c)(2).
+ - **FINRA Regulatory Notice 15-09** (March 2015) is *guidance*, not a rule. It offers
+ "suggested effective practices" that "complement, rather than supplant, obligations
+ firms have under existing or future rules", among them "a development and change
+ management process that tracks the development of new trading code or material
+ changes to existing code", including "a review of test results and a set of approval
+ protocols". It does not require that the *reason* for a change be documented.
+ - **US broker-dealers with market access** are subject to SEA Rule 15c3-5, and this is
+ the actual authority behind the `justification` field. SEC Division of Trading and
+ Markets FAQ No. 18 says that where a threshold is raised in accordance with
+ supervisory procedures, "the reasons for any such modification should be
+ appropriately documented and retained as part of the broker-dealer's books and
+ records."
+ - **SEC Rule 17a-4** governs preservation of the resulting records. Since the 2022
+ amendments (Release 34-96034; effective 3 January 2023, compliance date 3 May 2023
+ for broker-dealers) a system may satisfy 17a-4(f) by **either** WORM **or** an
+ audit-trail arrangement that permits recreation of an original record if it is
+ modified or deleted, capturing the date and time of each creation, modification or
+ deletion and the identity of the person responsible. WORM is no longer the only
+ permitted route.
+ - **SEC Regulation SCI** applies only to "SCI entities" — SROs, certain ATSs, plan
+ processors and certain exempt clearing agencies. The March 2023 proposal to extend it
+ to certain large broker-dealers and SBSDRs (88 FR 23146) was **formally withdrawn**
+ on 12 June 2025. Do not describe SCI-style controls as SCI compliance if the firm is
+ outside the perimeter.
+
+ Everyone else — a proprietary trader, a fund's internal system, an individual running
+ an algorithm — has no configuration-audit rule pointed at them and should use this as
+ operational hygiene.
+
## When NOT to Use
- - For routine application logs that are not configuration changes (use `structured-logging-for-post-incident-forensics` instead).
- - For order/fill audit trails (use `data-lineage-tracking-for-audit-and-debugging` or venue CAT reporting tooling).
- - As a substitute for the downstream WORM/SIEM storage layer — this engine produces records; immutability is enforced by the sink.
+ - **As an authorisation gate.** This engine records that a change was requested; it
+ does not decide who may make it. Maker-checker approval and RBAC belong upstream —
+ see `risk-control-configuration-change-approval-workflow`.
+ - **As the system of record.** The chain lives in process memory and is tamper-*evident*,
+ not immutable. Persist the emitted lines and publish the chain head to storage the
+ trading host cannot rewrite.
+ - For routine application logs that are not configuration changes — use
+ `structured-logging-for-post-incident-forensics`.
+ - For order/fill audit trails — use `data-lineage-tracking-for-audit-and-debugging` or
+ venue CAT reporting tooling.
+ - For manual overrides of a risk control that is actively rejecting orders — that is an
+ emergency bypass with its own evidence requirements, see
+ `risk-control-bypass-audit-logging`.
## Prerequisites
- - Python 3.9+
- - A centralized, append-only log storage system (e.g., Splunk, Datadog, or an AWS S3 Object Lock WORM bucket) to receive the generated log lines.
- - An authenticated caller context so `user_id` is always populated (e.g., SSO/JWT).
+ - Python 3.9+ (standard library only).
+ - A single `ConfigurationAuditLogger` instance per audited configuration domain. The
+ sequence number and chain are per-instance; two instances produce two independent
+ chains that cannot be interleaved into one.
+ - An authenticated caller context so `user_id` is a verified principal. Never accept
+ `user_id` from an unverified request body — a forged principal is worse than a
+ missing one, because it looks like evidence.
+ - Append-only storage for the emitted lines (S3 Object Lock, Splunk with data-integrity
+ controls, or a 17a-4(f)-compliant recordkeeping system), plus somewhere to publish the
+ chain head that the trading host cannot rewrite.
+ - The firm's own justification policy. `MIN_JUSTIFICATION_LENGTH` defaults to 5 and has
+ **no regulatory basis**; override it via `min_justification_chars`.
## Workflow
- 1. **Intercept Update**: A trader or automated system attempts to modify a configuration (e.g., increasing `max_order_size` from 100 to 500). Every UI, CLI, or API path that mutates a config parameter must route through `ConfigurationAuditLogger.process_change_request`.
- 2. **Validation**: The logger verifies that:
- - `justification` is present and at least `MIN_JUSTIFICATION_LENGTH` characters after stripping whitespace.
- - `user_id` is a non-empty, authenticated principal.
- - `old_value` and `new_value` are actually different (no-op changes are recorded but not approved).
- 3. **Record Construction**: The engine builds a `ConfigChangeRecord` with a high-precision UTC timestamp, the originating `environment`, a monotonically increasing `sequence_number`, and a SHA-256 `record_hash` chained to the previous record's hash via `prev_hash`.
- 4. **Log Emission**: Approved records are emitted at INFO as `AUDIT_LOG_ENTRY`; rejected attempts are emitted at WARNING as `AUDIT_LOG_REJECTED`. Both serialize to canonical (sorted-key) JSON for deterministic SIEM ingestion.
- 5. **Integrity Verification**: On examination, recompute each `record_hash` from the serialized fields and walk the `prev_hash` chain; the first record whose recomputed hash differs, or whose `prev_hash` does not match the prior `record_hash`, marks the boundary of tampering or a gap.
+ 1. **Intercept the update.** Every UI, CLI, or API path that mutates an audited
+ parameter routes through `ConfigurationAuditLogger.process_change_request`. Direct
+ database writes and flat-file edits are backdoors that leave the firm blind at
+ examination; close them rather than documenting them.
+ 2. **Validate.** In order, the logger requires a non-blank `parameter_name` (a record
+ that cannot name what changed proves nothing), a `justification` of at least
+ `min_justification_chars` after stripping, a non-blank `user_id`, and `old_value !=
+ new_value`. The first failure becomes the `rejection_reason`; the constants
+ `REASON_PARAMETER_NAME`, `REASON_JUSTIFICATION`, `REASON_USER_ID` and `REASON_NO_OP`
+ let callers branch without matching prose.
+ 3. **Record and chain.** Under one lock, the engine assigns a monotonic
+ `sequence_number`, a high-precision UTC `timestamp_utc`, the originating
+ `environment`, and a SHA-256 `record_hash` over the canonical JSON of every other
+ field including `prev_hash`. Order the records by `sequence_number`, never by
+ `timestamp_utc` — the wall clock can step backwards under an NTP correction.
+ 4. **Emit.** Approved records go out at INFO as `AUDIT_LOG_ENTRY`, rejected attempts at
+ WARNING as `AUDIT_LOG_REJECTED`, both as sorted-key JSON for deterministic SIEM
+ ingestion. Emission happens inside the lock so log order matches chain order.
+ 5. **Commit only on approval.** Apply the underlying change when `is_approved` is
+ `True`. When it is `False`, the change must not be applied — but the record is still
+ emitted and retained, because a rejected attempt is itself a supervisory event.
+ 6. **Publish the chain head.** Write `chain_head_hash` to storage the trading process
+ cannot alter. This is the only thing that reveals truncation of the newest records,
+ and it is what NIST SP 800-92 §3.1 means by protecting the digest.
+ 7. **Verify at examination.** Rebuild records from the archived lines with
+ `ConfigChangeRecord.from_json`, then call `verify_chain(records)`. It returns
+ `(True, None)` or `(False, reason)` naming the first record that fails and why —
+ content edited, link broken, or sequence out of order. Compare the last
+ `record_hash` against the externally published head. Use
+ `verify_chain(window, expect_genesis=False)` to check a slice of a longer chain;
+ that proves internal consistency only and says nothing about earlier records.
## Common Pitfalls
- - **Silent Failures**: Allowing configurations to be updated via a database or flat file directly without passing through an application-layer audit logger, leaving the firm blind during an examination.
- - **Missing Justifications**: Logging that a risk limit was changed without recording *why* or *who authorized it*.
- - **Missing Principals**: Accepting a change request without an authenticated `user_id`, producing a record that cannot support supervisory attribution.
- - **Dropping Rejected Attempts**: Logging only approved changes; a rejected attempt (e.g., missing justification) is itself auditable evidence of attempted unauthorized change.
- - **Non-Serializable Values**: Config values that are not JSON-serializable (sets, datetimes, custom objects) must not silently drop the audit record — `to_json` uses `default=str` to guarantee emission.
- - **Assuming JSON = Immutable**: JSON is a format, not a tamper control. True immutability comes from the WORM/SIEM sink; the hash chain provides *detection* of tampering, not prevention.
+ - **Treating the length floor as a rule.** `MIN_JUSTIFICATION_LENGTH = 5` is an
+ engineering default with no regulatory basis. A character count cannot judge whether
+ a justification is adequate; it only catches `"ok"`. Adequacy is a human review
+ question, and citing FINRA for the number is a claim the Notice does not support.
+ - **Claiming Reg SCI compliance outside the SCI perimeter.** Reg SCI binds SCI entities.
+ The 2023 proposal to extend it to large broker-dealers was withdrawn in June 2025;
+ a system description that still says "SCI-mandated" is now wrong.
+ - **Assuming WORM is the only permitted sink.** Since the 2022 amendments, 17a-4(f)
+ accepts an audit-trail arrangement as an alternative to WORM. Designing as though
+ WORM were mandatory can force an unnecessary second recordkeeping system.
+ - **Assuming JSON or a hash means immutable.** The hash chain provides *detection*.
+ Anything that can rewrite the whole log can recompute the whole chain; prevention is
+ the storage layer's job.
+ - **Believing the chain detects truncation.** Deleting the newest N records leaves a
+ chain that verifies perfectly. Only an externally held chain head exposes it.
+ - **Sharing one logger across processes, or building one per request.** The sequence and
+ chain are per-instance and in-memory. Two instances make two chains; a per-request
+ instance makes a chain of length one and proves nothing about ordering.
+ - **Ordering the audit trail by timestamp.** Use `sequence_number`. NTP corrections and
+ DST-naive local times both reorder a timestamp-sorted trail.
+ - **Dropping rejected attempts.** Logging only successful changes hides exactly the
+ events — a missing justification, an unauthenticated principal — that a supervisor
+ needs to see.
+ - **Letting an exotic config value drop the record.** Values that are not JSON-native
+ are coerced to their string form, and a comparison that raises is treated as a real
+ change rather than propagating. Prefer JSON-native values anyway: the string form of
+ an unordered container is not stable between processes.
## Verification
- Run `python scripts/test_config_change_audit_logger.py` (or `python -m unittest discover -s skills/audit-logging-for-configuration-changes/scripts`) to confirm that the engine: rejects un-justified / missing-principal / no-op changes; emits monotonic sequence numbers; chains `prev_hash` to the prior `record_hash`; produces deterministic sorted-key JSON; and survives non-serializable config values.
+ Run `python -m unittest discover -s skills/audit-logging-for-configuration-changes/scripts`
+ to confirm the engine: rejects requests missing a parameter name, justification or
+ principal, and no-op changes; honours a configured justification floor; emits monotonic
+ sequence numbers and chains `prev_hash` to the prior `record_hash`; detects an in-place
+ edit, a middle deletion, reordering, and an edit whose own hash was recomputed;
+ verifies a chain rebuilt from emitted JSON and rejects an archive line with an extra or
+ missing field; keeps `chain_head_hash` in step with the last record; assigns
+ gap-free sequence numbers under eight concurrent writer threads; and records rather
+ than drops config values that cannot be rendered or compared. Then work through
+ `assets/checklist.md`.
## Related Skills
- - `audit-logging-for-configuration-changes`
- `risk-control-configuration-change-approval-workflow`
- `risk-control-bypass-audit-logging`
- `configuration-drift-detection-across-environments`
+ - `structured-logging-for-post-incident-forensics`
- `record-retention-periods-by-jurisdiction`