Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions backend/routes/pods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,27 @@ router.get('/:podId/external-links', auth, async (req: AuthReq, res: Res) => {

router.get('/:id/children', auth, async (req: AuthReq, res: Res) => {
try {
const children = await Pod.find({ parentPod: req.params?.id }).populate('createdBy', 'username profilePicture').populate('members', 'username profilePicture').sort({ name: 1 });
return res.json(children);
const parentId = req.params?.id || '';
// Children listing reveals pod existence + names + members under a
// parent. Gate by the parent's visibility so a non-member can't
// enumerate someone else's nested workspace structure. The full
// results array is then filtered to children the caller can also
// see — matches the principle from PR #375 (no leak via fan-out
// queries either).
const parent = await Pod.findById(parentId).select('_id members type').lean();
if (!parent) return res.status(404).json({ msg: 'Pod not found' });
const canViewParent = await DMService.canViewPod(req.user?.id, parent);
if (!canViewParent) return res.status(403).json({ msg: 'Not authorized to view child pods' });
const children = await Pod.find({ parentPod: parentId })
.populate('createdBy', 'username profilePicture')
.populate('members', 'username profilePicture')
.sort({ name: 1 });
const visibleChildren = [];
for (const child of children) {
// eslint-disable-next-line no-await-in-loop
if (await DMService.canViewPod(req.user?.id, child)) visibleChildren.push(child);
}
return res.json(visibleChildren);
} catch (err) {
console.error('Error fetching child pods:', (err as Error).message);
return res.status(500).json({ msg: 'Server error' });
Expand Down
13 changes: 13 additions & 0 deletions backend/routes/summaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const { AgentInstallation } = require('../models/AgentRegistry');
const dailyDigestService = require('../services/dailyDigestService');
// eslint-disable-next-line global-require
const auth = require('../middleware/auth');
// eslint-disable-next-line global-require
const Pod = require('../models/Pod');
// eslint-disable-next-line global-require
const DMService = require('../services/dmService');

interface AuthReq {
user?: { id: string };
Expand Down Expand Up @@ -177,6 +181,15 @@ router.get('/all-posts', auth, async (_req: AuthReq, res: Res) => {
router.get('/pod/:podId', auth, async (req: AuthReq, res: Res) => {
try {
const { podId } = req.params || {};
// Pod-scoped summaries inherit pod visibility — non-members of personal
// pods (agent-room / agent-dm / agent-admin) must not learn pod
// existence or its summarized content. Parity with the /announcements
// + /files + /external-links gates on the pods router.
const pod = await Pod.findById(podId).select('_id members type').lean();
if (!pod) return res.status(404).json({ error: 'Pod not found' });
const userId = req.userId || req.user?.id;
const canView = await DMService.canViewPod(userId, pod);
if (!canView) return res.status(403).json({ error: 'Not authorized to view this pod summary' });
const podSummary = await ChatSummarizerService.getLatestPodSummary(podId);
res.json(podSummary);
} catch (error) {
Expand Down
Loading