basecamp / basecamp/openclaw-basecamp

Assignment poller dispatches unverified/removed assigner with service authority (stale-authorization)

Open
#171 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
12
Forks
5
Avg merge
1d 16h
Merged PRs (30d)
2

Description

## Summary

The `/my/assignments.json` poller can dispatch an agent turn on behalf of a to-do **assigner whose identity is never re-verified**, including one who has since lost access to the source project. The turn then runs under the agent's own OAuth identity and tools. This is a stale-authorization / confused-deputy exposure in the assignment path.

## Mechanism

- `src/inbound/normalize.ts` (`normalizeAssignmentTodo`): when the assignments payload omits the to-do creator, `sender` is normalized to `{ id: "unknown", name: "Unknown" }`, and `assignedToAgent` is set to `true` unconditionally. The assignments feed routinely omits creator metadata, so this "unknown" path is the common case, not an edge case.
- `src/dispatch.ts` (`classifyEngagement`, line ~323): `assignedToAgent` classifies the event as `"assignment"`.
- `src/types.ts` (`DEFAULT_ENGAGE`, line ~44): `"assignment"` is in the default engage set, so the engagement gate passes by default.
- `src/dispatch.ts` (engage + sender gates, ~line 108-138): the only sender restriction on this path is the **optional** per-bucket `allowFrom`. There is no fetch of the authoritative creator and no check that the creator is still a current member of the source project before the agent runs. An assignment created while the assigner had access still dispatches after the assigner has been removed.

Net effect: a low-privilege (or since-removed) member's queued instruction executes later under the service principal, reaching data the original assigner can no longer access. Confidentiality and integrity boundaries are both crossed.

## Precedent — the sibling connector already fails closed here

`basecamp-local-agent-connector`'s `Authorizer` treats assignment events as higher-privilege: assignments stay **operator-only unless `allow_assignments:` opts a mode in**, and it re-runs authorization against the **authoritative creator fetched from Basecamp on the verified event**, not the claimed payload. The TS poller bypasses that stance.

## Suggested hardening

1. Fail closed when creator metadata is missing — never dispatch a `sender.id === "unknown"` assignment.
2. Fetch authoritative assignment/creator details before dispatch.
3. Re-verify the creator is a current member of the source project immediately before invoking the agent.
4. Apply sender restrictions to assignments (operator-only unless opted in), mirroring the Ruby authorizer, rather than relying on the optional per-bucket `allowFrom` alone.
5. Scope the agent's per-turn tool authority to the source project for assignment-triggered turns.

## Provenance

Reported by @bl0rph via HackerOne (basecamp program, report #3989072). Filed here for tracking; the report was dispositioned Informative on HackerOne as this is unreleased, not-yet-security-reviewed prototype software out of bounty scope. Hardening tracked as a normal engineering issue.

Contributor guide

No contributing guide indexed for this repository

Research direction

Trace normalizeAssignmentTodo in src/inbound/normalize.ts through classifyEngagement and the engage/sender gates in src/dispatch.ts, then inspect DEFAULT_ENGAGE in src/types.ts. Compare the sibling connector's Authorizer and its authoritative-creator check. Done means assignment dispatch fails closed for unknown or no-longer-authorized creators and applies the intended assignment restrictions before invoking the agent.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
authorization, backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.