Skip to content

docs(commonly): correct attachment-read authorization - #11

Open
lilyshen0722 wants to merge 1 commit into
mainfrom
docs/attachment-read-acl
Open

docs(commonly): correct attachment-read authorization#11
lilyshen0722 wants to merge 1 commit into
mainfrom
docs/attachment-read-acl

Conversation

@lilyshen0722

Copy link
Copy Markdown

Summary

  • Correct the readAttachment docstring to distinguish pod-membership reads from AgentInstallation-gated writes.
  • Document that unscoped avatar files are public.

Verification

  • git diff --check
  • Reviewed the raw attachment authorization path on Commonly main: runtime-token reads go through canReadAttachment, while agent writes require an active AgentInstallation.

No runtime behavior changes.

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Approve — this is strictly better than what it replaces, and I verified all three claims against origin/main rather than taking them from the thread. One refinement below, non-blocking.

What I checked

The old text — "the uploads route uses the same pod-membership ACL as agent writes" — is false in both directions, which is why this PR exists. The replacement:

"Un-scoped files (avatars) are public" — exact. backend/routes/uploads.ts:313:

if (!meta?.podId) return true; // un-scoped: public (avatars etc.)

"a different predicate from the write path, which requires an active AgentInstallation" — confirmed, and this is the load-bearing half. The two paths genuinely don't share a predicate:

// WRITE — routes/agentsRuntime.ts
router.post('/pods/:podId/uploads', agentRuntimeAuth, uploadSingle('file'), ...
  if (!ensurePodMatch(req.agentInstallations || req.agentInstallation, podId, req.agentAuthorizedPodIds))
    return res.status(403)

// READ — routes/uploads.ts:318
const agentUserId = req.agentUser?._id?.toString();
if (agentUserId && await canReadAttachment(fileName, agentUserId)) return true;

canReadAttachment never consults AgentInstallation at all. So an agent can hold read access to a file in a pod it has no active installation for. That asymmetry is exactly what the old docstring flattened, and naming it is the main value here.

Refinement — canReadAttachment is wider than "pod membership"

The new text summarizes it as "authorize the runtime token via pod membership (canReadAttachment)". Naming the function is the right call — a reader can go look. But the one-line gloss covers one of five grant surfaces (backend/services/attachmentAccess.ts:96-160):

:108   fileDoc.uploadedBy === userId          →  owner. no pod involved at all
:115   File.podId + Pod.members               →  pod membership          ← the one named
:129   any User.profilePicture references it  →  ANY authenticated caller
:140   a referencing post with no podId       →  ANY authenticated caller
:91    a referencing PG message               →  membership of that message's pod

Two of these matter for an agent specifically:

  • The owner path is the common agent case. An agent uploads a deliverable, then reads it back. The grant comes from uploadedBy, not membership — so the docstring names the wrong reason for the read most agents will actually do.
  • The public-post path widens it past the pod. A pod-scoped file also referenced by a global post is readable by any authenticated caller. Someone reasoning "pod-scoped ⇒ pod members only" from this docstring would get that wrong.

This isn't the old defect — that one overclaimed a guarantee. This one under-describes a permissive predicate, which fails safe for a caller reasoning about their own access and unsafe for one reasoning about who else can read. Suggested one-line change:

   * Fetch an attachment. Pod-scoped files authorize the runtime token via
   * `canReadAttachment` — owner, pod membership, or a public post/profile
   * reference — note this is a different predicate from the write path,
   * which requires an active AgentInstallation. Un-scoped files (avatars)
   * are public.

Same length, and it stops the summary from being narrower than the function it names.

Notes

  • mergeStateStatus reads UNSTABLE — that's non-required checks pending, not a failure. Doesn't block.
  • Doc-only, no runtime change; git diff --check clean, confirmed.
  • Method note against my own read: my first pass at authorizePodFile came from the working tree and showed no agent-token path at all — which would have been a serious finding. The tree predates openclaw#830. git merge-base --is-ancestor 7692a766 HEAD → false; everything above is read from origin/main.

Not verified

  • That agentRuntimeAuth filters AgentInstallation on status: 'active' specifically — I confirmed the write path routes through installations and the read path doesn't, which is what the docstring claims. The word "active" I took from the repo's own documented behavior, not from reading the middleware.
  • Nothing about the ?t= signed-token path, which this docstring doesn't mention and doesn't need to.

@lilyshen0722
lilyshen0722 force-pushed the docs/attachment-read-acl branch from 79ac6ee to 3fca77a Compare August 5, 2026 19:54
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