You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cross-cutting prerequisite for the v0.2.0 write phase. Sibling of #109. Subset of #13.
Every destructive call (add_*, update_*, delete_*, reset_*, move_*) should leave a structured trace on stderr — independent of --verbose — so that a user can reconstruct after the fact what was changed, against which resource, and what the KAS-API response was. Today there is no record beyond shell history.
Scope
A shared helper in internal/cli/ (or internal/audit/) that emits one line per dispatched write action with at minimum:
timestamp (RFC 3339 UTC),
resolved login (KAS account),
KAS action name (add_mailaccount, delete_dns_settings, …),
target identifier (mail login, DNS record id, FTP login, …),
outcome (success | failure:<fault_code>),
request-correlating field where the KAS API returns one (e.g. record_id from add_dns_settings).
Default output format: structured key-value on stderr (logfmt-ish) — easy to grep, no JSON-parser dependency for users.
A --audit-log <path> flag (and KAS_AUDIT_LOG env var) appends the same record as JSON Lines to a file when set; the file is created with mode 0600.
Records are emitted after the SOAP call returns, regardless of success — failed writes are arguably more important to log than successful ones.
Secrets (auth_data, KasFloodDelay tokens, passwords passed as parameters like mail_password) are never logged. Pre-flight redaction list lives next to the helper.
Relationship to existing logging
--verbose / -v today dumps SOAP actions on stderr for debugging. The audit log is orthogonal: it is always on for write actions, opinionated about the schema, and survives --quiet (if/when that flag exists). Read actions continue to produce no audit record.
Out of scope
A separate read-side audit log.
Remote sinks (syslog, journald, HTTP). The file path is enough; users can pipe stderr or tail -F the file.
The actual write endpoints — those live in the per-resource issues spawned from Write operations #13.
Status (codebase verified against main @ ceca090, 2026-05-17)
Helper + flag + env var + redaction + tests + docs landed in PR #165 (feat(cli): structured write-action audit log, merged ceca090) and are verified present in internal/cli/audit.go:
cli.WriteAudit: always-on logfmt line on stderr (independent of --verbose); cli.OpenAuditFile appends JSON Lines, file created 0600 with an explicit re-Chmod (Windows-aware, mirrors the session store).
Global --audit-log flag + KAS_AUDIT_LOG env (cli.AuditLogPath, flag wins).
cli.RedactParams: explicit secret-key set (auth_data, kas_auth_data, *password, session, token) plus a password/passwd/secret/token/auth_data substring rule; tests assert no secret value reaches stderr or the JSON-Lines file.
Three boxes are not closable in isolation: no #13 write command exists yet (so nothing emits a record end-to-end, and the redaction list cannot be exhaustively matched against per-endpoint write parameters that are not documented/wired). They transfer to the first write-endpoint PR, which calls WriteAudit after dispatch and will carry Closes #131.
Parent: #13. Depends on #109 (shares the destructive-command dispatch path) — #109 infrastructure landed in PR #164.
Closed by PR #167 (first #13 write endpoint, merged 1b56d1a): the mail-forward write slice (mail forwards add/update/delete) exercises this end-to-end through the shared cli.runWriteE runner. The deferred end-to-end / Closes boxes are now satisfied.
Cross-cutting prerequisite for the v0.2.0 write phase. Sibling of #109. Subset of #13.
Every destructive call (
add_*,update_*,delete_*,reset_*,move_*) should leave a structured trace on stderr — independent of--verbose— so that a user can reconstruct after the fact what was changed, against which resource, and what the KAS-API response was. Today there is no record beyond shell history.Scope
internal/cli/(orinternal/audit/) that emits one line per dispatched write action with at minimum:login(KAS account),add_mailaccount,delete_dns_settings, …),success|failure:<fault_code>),record_idfromadd_dns_settings).logfmt-ish) — easy to grep, no JSON-parser dependency for users.--audit-log <path>flag (andKAS_AUDIT_LOGenv var) appends the same record as JSON Lines to a file when set; the file is created with mode0600.auth_data,KasFloodDelaytokens, passwords passed as parameters likemail_password) are never logged. Pre-flight redaction list lives next to the helper.Relationship to existing logging
--verbose/-vtoday dumps SOAP actions on stderr for debugging. The audit log is orthogonal: it is always on for write actions, opinionated about the schema, and survives--quiet(if/when that flag exists). Read actions continue to produce no audit record.Out of scope
tail -Fthe file.Status (codebase verified against
main@ceca090, 2026-05-17)Helper + flag + env var + redaction + tests + docs landed in PR #165 (
feat(cli): structured write-action audit log, mergedceca090) and are verified present ininternal/cli/audit.go:cli.AuditRecord(TimeRFC 3339 nano UTC,Login,Action,Target,Outcome,Fields) +cli.OutcomeFor(success/failure:<kas_code>viaapi.AsError/failure).cli.WriteAudit: always-onlogfmtline on stderr (independent of--verbose);cli.OpenAuditFileappends JSON Lines, file created0600with an explicit re-Chmod(Windows-aware, mirrors the session store).--audit-logflag +KAS_AUDIT_LOGenv (cli.AuditLogPath, flag wins).cli.RedactParams: explicit secret-key set (auth_data,kas_auth_data,*password,session,token) plus apassword/passwd/secret/token/auth_datasubstring rule; tests assert no secret value reaches stderr or the JSON-Lines file.0600/ flag-env precedence. Docs:docs/usage/destructive-writes.md"Audit log" section; CHANGELOG### Added.Three boxes are not closable in isolation: no #13 write command exists yet (so nothing emits a record end-to-end, and the redaction list cannot be exhaustively matched against per-endpoint write parameters that are not documented/wired). They transfer to the first write-endpoint PR, which calls
WriteAuditafter dispatch and will carryCloses #131.Acceptance
internal/cli/(orinternal/audit/) — PR feat(cli): structured write-action audit log #165 (internal/cli/audit.go).cli.runWriteEemitscli.WriteAuditwithcli.OutcomeForafter every mailforward write dispatch (andoutcome=dry-runon--dry-run).local_part/domain_part/target_*/mail_forward) carry no secrets and pass through the sameRedactParamsnet — PR feat(mail): mailforward write endpoints — add / update / delete #167. Exhaustive per-endpoint coverage remains incremental per future Write operations #13 PR.### Addedentry — PR feat(cli): structured write-action audit log #165.Closes #<n>— PR feat(mail): mailforward write endpoints — add / update / delete #167 carriedCloses #131(merged1b56d1a).Parent: #13. Depends on #109 (shares the destructive-command dispatch path) — #109 infrastructure landed in PR #164.
Closed by PR #167 (first #13 write endpoint, merged
1b56d1a): the mail-forward write slice (mail forwards add/update/delete) exercises this end-to-end through the sharedcli.runWriteErunner. The deferred end-to-end /Closesboxes are now satisfied.