fix(security): enforce slash invocation write authority - #146
Conversation
|
Codex review: needs maintainer review before merge. Reviewed August 4, 2026, 12:01 AM ET / 04:01 UTC. ClawSweeper reviewWhat this changesThe branch adds transactional slash-command authorization and scope checks, counts persisted guest command invocations against the guest write limit, and adds SQLite/PostgreSQL migrations and regressions. Merge readinessKeep open for maintainer review: the PR appears to close a real authorization gap with strong live proof, but it deliberately makes existing guest slash-command workflows share the three-write rolling quota and may return HTTP 429 after upgrade. Priority: P1 Review scores
Verification
How this fits togetherThe slash-hook endpoint resolves a registered command, persists an invocation, then sends its callback. SQLite and PostgreSQL store logic decides whether the caller, channel, command, and guest quota are valid before either persistence or callback delivery. flowchart LR
A[Guest slash request] --> B[Slash-hook endpoint]
B --> C[Command lookup]
C --> D[Transactional authority check]
D --> E[Shared guest write quota]
E --> F[Invocation record]
F --> G[Callback delivery]
Decision needed
Why: The branch makes a deliberate user-visible quota change that existing deployments will discover at runtime, and the implementation cannot establish whether that compatibility tradeoff is intended. Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Explicitly accept the combined guest-write quota as the upgrade contract, then land the transactional authorization hardening with its cross-store regression coverage. Do we have a high-confidence way to reproduce the issue? Yes: current main’s source counts only guest messages before it persists a registered invocation, while the PR provides current-head live HTTP output showing the repaired three-successes/fourth-429 boundary. Is this the best way to solve the issue? Unclear: transactional revalidation is the narrow maintainable security fix, but maintainers must decide whether sharing the existing guest quota is the intended upgrade policy. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 601224ddee75. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
History |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
2971562 to
f350c66
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Landed as Local proof at exact reviewed head
Exact-head GitHub proof:
Caveat: the live HTTP proof used the explicitly test-only unsafe-callback build tag to reach a loopback receiver. Production callback network policy remained covered by the full test suite. |
What Problem This Solves
Follow-up to #145. Registered slash-command invocations must use the same
rolling 24-hour guest write budget as guest messages, and authorization must
remain authoritative at the persistence boundary. The previous revision still
left a PostgreSQL revocation lock race and did not directly cover all
cross-workspace and stale-authorization inputs.
The required behavior is:
scope-mismatched invocations create no invocation row and reach no callback;
What Changed
transaction that acquires the command row first may finish persistence; a
concurrent revoked_at update waits for that transaction to commit.
synchronization and pg_stat_activity blocking detection.
mismatches, forged workspace IDs, revoked commands, membership removal after
lookup, channel deletion after lookup, and unrelated-row preservation.
persistence, and no sensitive command details in the response.
slash-command HTTP path. Lookup authorization and transactional persistence
authorization remain.
messages/invocations UNION ALL.
(workspace_id, user_id, channel_id, created_at) for the invocation budget
query.
budget slot even when callback delivery later fails.
Verification
Current branch HEAD:
f350c66
Base:
origin/main at 601224d
Commands passed:
The full Go suite and PostgreSQL package used a disposable loopback
PostgreSQL 17 test instance via CLICKCLACK_POSTGRES_TEST_DSN. PostgreSQL
integration tests were live, not skipped.
Current-Head Real Behavior Proof
Behavior addressed: an authenticated guest caller submits attacker-controlled
slash-command form data to the real HTTP slash-hook endpoint. The fourth
combined guest write must be denied before callback delivery and invocation
persistence.
Real environment:
Local redacted evidence log:
/home/rev/projects/clickclack-pr-logs/pr-146-f350c667/real-behavior.log
Exact proof steps after this patch:
The disposable fixture was seeded directly into SQLite to create a guest
membership and reserved guest channel, then the command was registered
through the real HTTP API as the owner. The request path itself used the
production HTTP router and persistence/callback code.
Observed output:
Observed result: the first three registered guest attempts persisted and
reached the independent callback receiver. The fourth request was rejected
with HTTP 429; its invocation count and callback count remained unchanged.
Query-Plan Evidence
With 1,000 channels and 100,000 invocation rows, PostgreSQL changed from a
hash join plus sequential invocation scan under the old
(workspace_id, user_id, created_at) index to a nested-loop index-only scan
under (workspace_id, user_id, channel_id, created_at). Rows read before the
LIMIT fell from 2,001 to 3, and measured execution time fell from 0.299 ms to
0.085 ms in the disposable PostgreSQL 17 plan.
SQLite likewise changed from a scan of the invocation table to a covering-index
search. No message index was added.
What Was Not Tested
clickclack_e2e_unsafe_callbacks build tag so a loopback receiver could be
used. It does not prove the production public-address callback dialer;
callback SSRF/redirect/proxy policy remains covered by its dedicated tests.
than a production bearer/session credential. The actor, channel, persistence,
budget, and callback paths were real.
live PostgreSQL store package and deterministic concurrency test, not a full
deployed PostgreSQL HTTP server.
Unrelated working-tree changes were not present before this work, none were
overwritten or included, and the final repository worktree is clean.