Skip to content

Phase 2: security hardening — token user-binding, credential marker, log caps, domain enforcement (supersedes #67) - #99

Merged
ifahimreza merged 4 commits into
mainfrom
improve/phase2-security
Aug 16, 2026
Merged

Phase 2: security hardening — token user-binding, credential marker, log caps, domain enforcement (supersedes #67)#99
ifahimreza merged 4 commits into
mainfrom
improve/phase2-security

Conversation

@ifahimreza

Copy link
Copy Markdown
Contributor

Supersedes #67, which GitHub auto-closed when its base branch (improve/phase1-correctness, #66) was deleted on merge. Same branch, same work, brought up to date with main. Closes #63.

What

Phase 2 of the 2026-07 architecture review:

  • Approval tokens bound to the previewing user — cross-agent redemption closed. With several agents on one site (separate users, separate application passwords), a token previewed by one can no longer be confirmed by another.
  • Credential scoping keys on an immutable stored UUID marker, rename-proof; legacy keys migrate on sight.
  • Separate log GC caps for denials vs executed mutations.
  • Opt-in domain-drift enforcement (default stays warn-only).

Bringing it up to date

The branch was 3.5 weeks stale and main has moved a long way. Three conflicts, all resolved by keeping both sides:

  • denial_reason() — this branch adds a domain-drift branch, main (from fix(guardrails): hold a wrapped destructive confirm to the args it previewed (#89) #90) adds a capability branch. Both belong. Ordered capability → domain → tier, mirroring the order the merged permission() actually checks them in (paused → capability → disabled → domain → tier).
  • Saddle_Integrations — this branch's collision registry vs main's target_keys()/canonical() helpers from the wrapper-bind fix. Purely additive on both sides.
  • tests/approval-test.php — this branch's user-mismatch test vs main's. Additive.

Also fixed a pre-existing phpcs error this branch shipped with: a non-Yoda comparison on the new user-binding check. Not from the merge — it had simply never been linted against the current ruleset.

Note on what it means alongside #90

Saddle_Approval::gate() now binds a token to action + target + bind + user. #90 added the bind (every argument the preview showed); this adds the user. Those are complementary, and together they close both halves of "a confirm that isn't the preview it claims to be".

Testing

No CI in this repo, so "green" means the commands above were run locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S

ifahimreza and others added 4 commits July 24, 2026 01:48
…llisions

- Saddle_Approval::gate now logs a confirmed destructive execution even when
  the executor returns WP_Error — a partial mutation with no audit trail is
  worse than a noisy one. The entry carries a "FAILED after confirmation"
  marker plus the error message.
- Saddle_Integrations::wrap surfaces genuine wrapper-name collisions via
  _doing_it_wrong instead of silently dropping the partner tool; its own
  wrappers are tracked so idempotent re-runs stay silent.

Part of Phase 1 (correctness) of the 2026-07 architecture review
(plugpressco/saddle-pro#39).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133CZhoFPY6BBChDDGEa22Q
…it log caps, domain enforcement

- Approval tokens are bound to the previewing user: with several agents on
  one site, agent A's preview can no longer be confirmed by agent B
  (saddle_token_user_mismatch).
- Saddle-issued app passwords are recorded by UUID in user meta
  (saddle_issued_credentials); credential scoping, the XML-RPC block, and
  client revoke/rotate/list key on that immutable marker instead of the
  user-editable display name. Legacy prefix-named keys migrate on sight.
- Saddle_Log GC caps denials (300) and executed mutations (1000) as
  separate filterable buckets, batch raised to 500, so denial noise can
  never evict change history.
- Opt-in domain-drift enforcement (saddle_enforce_tier_domain, default
  off): write/admin abilities refuse on a migrated/cloned domain until the
  owner re-confirms the tier; exposed via GET/POST /settings.

Phase 2 of the 2026-07 architecture review (#63).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133CZhoFPY6BBChDDGEa22Q
# Conflicts:
#	includes/class-saddle-capabilities.php
#	includes/class-saddle-integrations.php
#	tests/approval-test.php
phpcs flagged it after the merge; WPCS wants the call on the left.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S
@ifahimreza
ifahimreza merged commit 59b2003 into main Aug 16, 2026
1 of 7 checks passed
@ifahimreza
ifahimreza deleted the improve/phase2-security branch August 16, 2026 04:47
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.

Security hardening: approval-token user binding, wrapper bind, annotation fail-safety, credential marker, log caps

1 participant