Skip to content

fix(daemon): log CLI-triggered ban/unban/allow at INFO (#45)#46

Open
evertramos wants to merge 1 commit into
devfrom
fix/issue-45-cli-action-log
Open

fix(daemon): log CLI-triggered ban/unban/allow at INFO (#45)#46
evertramos wants to merge 1 commit into
devfrom
fix/issue-45-cli-action-log

Conversation

@evertramos

Copy link
Copy Markdown
Owner

What & why

Closes #45

Manual ezyshield ban / unban / allow via the control socket wrote to bans_active + audit_log but produced no slog line. Operators tailing journalctl -u ezyshield couldn't confirm CLI actions or tell a manual ban apart from a pipeline-triggered one. The pipeline path already logs msg="daemon: action" at INFO (internal/daemon/daemon.go:571); this PR gives the socket handlers the equivalent line so a single grep pattern catches every state change.

Acceptance (per issue):

  • INFO line with op={ban|unban|allow|dry_ban}, ip, ttl, reason, source=cli after each successful CLI verb.
  • Message string identical to the pipeline path ("daemon: action") — only source=cli discriminates.
  • Only the happy path logs; existing ERROR logs cover failures.
  • Additive only — no schema, wire, or automatic-path changes.

Changes

  • internal/daemon/socket.go: three slog.InfoContext(ctx, "daemon: action", ...) calls at the tail of handleBan, handleUnban, handleAllow.
  • handleBan guards the log behind a stored flag so the fallback ERROR branch (RecordManualBan failure) does not falsely emit a success line.
  • handleBan reuses its local op variable so op="dry_ban" when policy.Armed=false.
  • handleAllow reuses the same ttl handed to store.AuditOp so the log and audit row agree (0 for permanent, computed duration otherwise).
  • handleUnban emits ttl=0, reason=req.Reason (empty from today's CLI — issue says leave as-is; do not invent a placeholder).
  • internal/daemon/socket_test.go: new file. Captures slog via a text handler attached to slog.Default() (matches the package pattern — slog.Default() is already how the socket handlers dispatch, and no other daemon test needed a scoped logger). Asserts one INFO line per verb with the expected attributes, including the dry_ban and permanent-allow variants.

Tests

  • Unit tests added/updated (5 new tests in internal/daemon/socket_test.go)
  • New parser/rule? fixture added in fixtures/ — N/A
  • Parser change? fuzz test present — N/A
  • go test -race ./... green locally on Go 1.26 (see note below on lint)

Note on local lint: this environment ships a golangci-lint built against Go 1.24, which refuses the repo's go 1.26 module directive. go vet ./... and gofmt -l are clean; CI's own toolchain will run the full lint.

Security review (per docs/SECURITY-REVIEW.md)

  • §1 Input handling (hostile logs): N/A — no parser or log-collector change. The reason attribute is operator-supplied text from the socket request; it already flowed into audit_log unchanged prior to this PR, so no new exposure surface. It is logged at INFO because the automatic path already treats reason as INFO-safe (daemon.go:571).
  • §2 Decision engine (lock-out / false-ban): N/A — no change to allowlist, threshold, strike, or rule logic. Socket handlers still enforce the same allowlist upstream.
  • §3 Privilege separation / enforcer: N/A — no enforcer or privileged-path change.
  • §4 Secrets: OK — req.Reason and op are not secret material. No token, URL, or credential reaches these attributes; the socket request struct has no secret fields. secret_leak_test.go in internal/config and internal/ai cover the paths that do handle secrets and are untouched.
  • §5 AI / prompt-injection boundary: N/A — CLI path does not touch the AI provider.
  • §6 Control surfaces (socket/dashboard): OK — no wire-format, no new verb, no new listener. The control socket still binds unix-only with 0660 group ownership; only the log output on an existing verb changed.
  • §7 Plugins: N/A.
  • §8 Edge / external APIs: N/A.
  • §9 Dependencies / supply chain: OK — no new import; log/slog, net, bytes, encoding/json, strings, testing are already used across the daemon package.
  • §10 Logging / audit / fail-safe: IMPROVEMENT — this PR closes the exact gap §10 warns about ("every state-changing action writes an append-only audit record" combined with the operator-visibility expectation). handleBan guards the INFO emission on stored so a failed RecordManualBan + failed AuditOp does not produce a misleading "action" line while the audit-fallback ERROR log already exists. handleUnban / handleAllow return early on primary store-write failure via the existing error paths, so the tail INFO is only reached in the happy path. Reason and IP are the same values already persisted to audit_log; no new secret-exposure surface.

Self-assessment: Does this change give an attacker a step toward lock-out / injection / privilege-escalation / secret-exfil / evasion? No. The change is additive, only touches the log sink on three already-audited paths, adds no new import, no new interface, and no new attacker-controlled surface. An attacker with socket access already had full ban/unban/allow power before this PR; the only difference is that their actions are now visible to the operator on stdout/journal.

Checklist

  • Follows AGENTS.md Hard Rules (no new listeners, allowlist supremacy, dry-run default, secrets out of code)
  • No hardening systemd directive removed
  • Docs updated — N/A (behavior is additive-only; no user-facing flag or schema change)
  • New dependency justified — none added

🤖 Generated with Claude Code

Manual actions via the control socket wrote to bans_active + audit_log
but produced no slog line. Operators tailing journalctl -u ezyshield
saw no confirmation of CLI verbs and could not distinguish a manual ban
from an automatic (pipeline-triggered) one.

Adds three additive slog.InfoContext calls at the tail of handleBan /
handleUnban / handleAllow, each reusing the pipeline path's message
"daemon: action" so a single grep still catches every state change.
source="cli" discriminates the CLI path from the automatic
rules/AI path.

- handleBan: op is "ban" or "dry_ban" (when policy.Armed=false), ttl is
  the parsed duration (0 for permanent), reason is the operator-supplied
  string or "manual ban via CLI" if empty. The line is emitted only
  after the primary store write succeeds — the audit-fallback ERROR
  branch stays as-is (§10: no false confirmation of a lost write).
- handleUnban: op="unban", ttl=0, reason empty (CLI doesn't send one).
- handleAllow: op="allow", ttl matches the value passed to AuditOp so
  the log and the audit_log agree.

No schema change, no socket wire-format change, no change to the
automatic path's message string. Additive only.

Tests: internal/daemon/socket_test.go captures slog via a text-handler
attached to slog.Default() (matches the existing package pattern) and
asserts one INFO line per verb with the expected op/ip/ttl/source
attributes, plus the dry_ban and permanent-allow variants.

Security review notes: §10 improvement — closes a gap where CLI state
changes were invisible to journal-based monitoring. No new attacker
surface: reason is already stored in audit_log with the same shape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 6, 2026 01:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR successfully addresses issue #45 by adding INFO-level logging for CLI-triggered ban/unban/allow operations. The implementation is well-designed with proper error handling, comprehensive test coverage, and consistent attribute naming that matches the existing pipeline logging pattern.

Key strengths:

  • Correctly guards INFO logging behind the stored flag in handleBan to prevent false success indicators when writes fail
  • Consistent message format across all three handlers enables unified grep patterns for operational monitoring
  • Comprehensive test suite covers all scenarios including dry-run mode and permanent vs. temporary operations
  • Additive-only changes with no modifications to existing behavior or security boundaries

The changes are ready to merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants