Skip to content

fix(cat): you could not read the Cat's answer on your own private post - #820

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/cat-reply-visible-to-asker
Aug 28, 2026
Merged

fix(cat): you could not read the Cat's answer on your own private post#820
github-actions[bot] merged 1 commit into
mainfrom
fix/cat-reply-visible-to-asker

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

Ask the Cat under a private post and the answer is written correctly, with the parent's visibility — a private thread should stay private. But the reply is authored by the Cat, and the rule for a private event is actor_id = auth.uid(). So the answer to your own question was visible to exactly one account, and it was not yours.

Measured in production on post 5c3ad8ef:

reply author visibility
425a3d00 cat private "Each person would owe 1,400 CHF"
52cc045c mao private "@cat what is this"
3f9372ba mao public "awdaw"

The author could see two of them. The missing one was the answer they had asked for. Nothing errors — the thread just renders without it, which reads as the Cat having ignored you.

The rule added

A private event is also visible to the author of the private post it replies to.

Scoped to a private parent deliberately. The looser version — "the parent's author sees any private reply" — is a real privacy regression on public posts, where anyone may reply and someone may write a private note to themselves under a stranger's post. That note stays theirs.

With a private parent the set of possible repliers is closed: to reply to a private post you must first be able to read it, and the policy allows only its author. The one other writer is the Cat, whose worker uses the service role.

Verified against production, rolled back

before after
replies the author sees 2 of 3 3
replies a different user sees 1 (the public one)
private events leaked to them 0

The check runs in a SECURITY DEFINER function because a policy on timeline_events that queries timeline_events re-enters itself — "infinite recursion detected in policy", which is how my first attempt failed. It answers only "does the caller own this private parent" and returns no row content.

Not keyed on the Cat's id or on metadata.is_cat_reply: either would put a second definition of who the Cat is into SQL, to drift against config/cat-identity.ts.

Gated

The failure is silent, so timeline_policy_allows_own_thread() reports false against the policy in production today and true after this migration. A future rewrite that drops the clause turns the nightly run red instead of quietly breaking the feature again.

🤖 Generated with Claude Code

https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5

Ask the Cat under a private post and the answer is written, correctly,
with the parent's visibility — a private thread should stay private. But
the reply is authored by the CAT, and the rule for a private event is
`actor_id = auth.uid()`. So the answer to your own question was visible
to exactly one account, and it was not yours.

Measured in production 2026-08-28 on post 5c3ad8ef. Three replies exist:

  425a3d00  actor=cat   private   "Each person would owe 1,400 CHF"
  52cc045c  actor=mao   private   "@cat what is this"
  3f9372ba  actor=mao   public    "awdaw"

The author could see two of them. The missing one was the answer they
had asked for. Nothing errors; the thread renders without it, which
reads as the Cat having ignored you.

THE RULE ADDED: a private event is also visible to the author of the
private post it replies to.

Scoped to a private PARENT deliberately. The looser version — "the
parent's author sees any private reply" — is a real privacy regression
on PUBLIC posts, where anyone may reply and someone may write a private
note to themselves under a stranger's post. That note stays theirs.

With a private parent the set of possible repliers is closed: to reply
to a private post you must first be able to read it, and the policy
allows only its author. The one other writer is the Cat, whose worker
uses the service role. So this exposes your own replies and the Cat's
answers on your own private posts, and nothing else. Verified: a
different user still sees one reply on that thread (the public one) and
zero private events that are not theirs.

The check runs in a SECURITY DEFINER function because a policy on
timeline_events that queries timeline_events re-enters itself —
"infinite recursion detected in policy for relation timeline_events",
which is how the first attempt failed. It answers only "does the caller
own this private parent" and returns no row content.

Not keyed on the Cat's id or on metadata.is_cat_reply: either would put
a second definition of who the Cat is into SQL, to drift against
config/cat-identity.ts. The property that matters is structural.

GATED, because the failure is silent — the Cat answers, the row exists,
and the thread renders without it. timeline_policy_allows_own_thread()
reports false against the policy in production today and true after this
migration, so a future rewrite that drops the clause turns the nightly
run red instead of quietly breaking the feature again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5
@github-actions
github-actions Bot merged commit 0da9694 into main Aug 28, 2026
8 checks passed
@github-actions
github-actions Bot deleted the fix/cat-reply-visible-to-asker branch August 28, 2026 20:23
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.

1 participant