#18: Audit-log Gitea MCP mutating actions with execution profile metadata #29

Merged
sysadmin merged 1 commits from feat/issue-18-audit-log-mutating-actions into master 2026-07-01 21:33:22 -05:00
Owner

Closes #18. Roadmap #10.

What

Durable, opt-in audit logging for every mutating Gitea MCP action — so an operator can see which execution profile and authenticated Gitea user performed (or was blocked from / failed) each mutation.

How

  • New gitea_audit.py (pure, no network):
    • Recursive secret redaction — dict keys containing token/password/secret/authorization/auth, and string value runs starting token /Basic /Bearer [REDACTED].
    • build_event(...) → record with timestamp (ISO-8601 UTC), action, action_type, result, remote, server, repository, issue_number, pr_number, profile_name, audit_label, authenticated_username, target_branch, head_sha, reason, redacted request_metadata.
    • write_event(...) → append-only JSON Lines sink; never raises.
  • mcp_server.py wiring:
    • _audited context manager for simple mutations; _audit_pr_result decorator for the gated review/merge tools (reads their own result dict — no extra API calls).
    • Wired into: create_issue, create_pr, edit_pr, close_issue, commit_files, delete_branch, create_label, set_issue_labels, mark_issue (label/unlabel), gitea_submit_pr_review, gitea_merge_pr.
    • Outcomes classified allowed/blocked/failed/succeeded. Blocked and failed actions are logged, not just successes.

Safety / behavior

  • Off by default. Records only when GITEA_AUDIT_LOG is set. Unset ⇒ every audit path short-circuits: no records, no extra API calls → existing tool behavior and API call sequences unchanged.
  • Auditing never raises (sink writes best-effort); tokens/Authorization material never written.
  • No Jenkins/Ops/deploy behavior; no new gating (that's #14–#16, already merged).

Tests / checks

  • tests/test_audit.py — 19 cases: redaction, event build (injected clock), sink append/no-op/bad-path, per-tool success/failure/blocked records, secret-free output, off-by-default no-op (asserts single API call), audit-failure-never-breaks-action.
  • Full suite: 212 passed, 0 failures, 0 errors (JUnit XML — harness swallows pytest stdout on multi-file runs).
  • py_compile clean; no linters configured in repo.

Docs

README env table + audit note (#18), .env.example placeholder for GITEA_AUDIT_LOG.

Intentionally untouched

  • No standalone "comment issue" tool exists in the server, so that action category is N/A here.
  • Retry/backoff env config (#28) not documented in this PR — out of scope.

⚠️ Authored by me — do not self-merge. Needs review by another author.

Closes #18. Roadmap #10. ## What Durable, opt-in audit logging for every mutating Gitea MCP action — so an operator can see **which execution profile and authenticated Gitea user** performed (or was blocked from / failed) each mutation. ## How - **New `gitea_audit.py`** (pure, no network): - Recursive secret redaction — dict keys containing `token`/`password`/`secret`/`authorization`/`auth`, and string value runs starting `token `/`Basic `/`Bearer ` → `[REDACTED]`. - `build_event(...)` → record with `timestamp` (ISO-8601 UTC), `action`, `action_type`, `result`, `remote`, `server`, `repository`, `issue_number`, `pr_number`, `profile_name`, `audit_label`, `authenticated_username`, `target_branch`, `head_sha`, `reason`, redacted `request_metadata`. - `write_event(...)` → append-only **JSON Lines** sink; never raises. - **`mcp_server.py`** wiring: - `_audited` context manager for simple mutations; `_audit_pr_result` decorator for the gated review/merge tools (reads their own result dict — no extra API calls). - Wired into: `create_issue`, `create_pr`, `edit_pr`, `close_issue`, `commit_files`, `delete_branch`, `create_label`, `set_issue_labels`, `mark_issue` (label/unlabel), `gitea_submit_pr_review`, `gitea_merge_pr`. - Outcomes classified `allowed`/`blocked`/`failed`/`succeeded`. **Blocked and failed** actions are logged, not just successes. ## Safety / behavior - **Off by default.** Records only when `GITEA_AUDIT_LOG` is set. Unset ⇒ every audit path short-circuits: **no records, no extra API calls** → existing tool behavior and API call sequences unchanged. - Auditing **never raises** (sink writes best-effort); tokens/Authorization material never written. - No Jenkins/Ops/deploy behavior; no new gating (that's #14–#16, already merged). ## Tests / checks - `tests/test_audit.py` — 19 cases: redaction, event build (injected clock), sink append/no-op/bad-path, per-tool success/failure/blocked records, secret-free output, off-by-default no-op (asserts single API call), audit-failure-never-breaks-action. - Full suite: **212 passed, 0 failures, 0 errors** (JUnit XML — harness swallows pytest stdout on multi-file runs). - `py_compile` clean; no linters configured in repo. ## Docs README env table + audit note (#18), `.env.example` placeholder for `GITEA_AUDIT_LOG`. ## Intentionally untouched - No standalone "comment issue" tool exists in the server, so that action category is N/A here. - Retry/backoff env config (#28) not documented in this PR — out of scope. --- ⚠️ Authored by me — do **not** self-merge. Needs review by another author.
jcwalker3 added 1 commit 2026-07-01 21:21:24 -05:00
Add durable, opt-in audit logging for every mutating Gitea MCP action so an
operator can see which execution profile and authenticated Gitea user
performed (or was blocked from / failed) each mutation.

- New gitea_audit.py: pure, no-network module — recursive secret redaction
  (token/password/authorization keys; token/Basic/Bearer value runs),
  build_event (timestamp, action, result, profile, audit label, authenticated
  username, repo, issue/PR, target branch, head SHA, redacted request
  metadata), and an append-only JSON Lines sink.
- mcp_server.py: _audit helper + _audited context manager (simple mutations)
  and an _audit_pr_result decorator (gated review/merge tools, reading their
  own result dict) wired into create_issue, create_pr, edit_pr, close_issue,
  commit_files, delete_branch, create_label, set_issue_labels, mark_issue
  (label/unlabel), gitea_submit_pr_review, and gitea_merge_pr.
- Outcomes recorded as allowed/blocked/failed/succeeded; blocked and failed
  eligibility checks are logged, not just successes.

Off by default: records are written only when GITEA_AUDIT_LOG is set. When it
is unset every audit path short-circuits — no records, no extra API calls — so
existing tool behaviour and API call sequences are unchanged. Auditing never
raises; sink writes are best-effort. Tokens are never written.

Docs: README env table + audit note, .env.example placeholder.
Tests: tests/test_audit.py (19 cases) — redaction, event build, sink writes,
per-tool success/failure/blocked records, secret-free output, off-by-default
no-op, and audit-failure-never-breaks-action.

Closes #18

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sysadmin merged commit d4251c5c47 into master 2026-07-01 21:33:22 -05:00
sysadmin deleted branch feat/issue-18-audit-log-mutating-actions 2026-07-01 21:33:22 -05:00
Sign in to join this conversation.