CenterForDigitalHumanities / CenterForDigitalHumanities/TPEN-services
PUT page preserves user-supplied `creator` on existing-line updates
- Dominant language
- JavaScript
- Stars
- 2
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
The page PUT route lets a client override `creator` on an existing-line update. Authenticated callers can attribute a saved line to any agent IRI they choose. New lines are unaffected.
## Where
`page/index.js:119-123`
```js
const line = item.id?.startsWith?.('http')
? new Line(item)
: Line.build(projectId, pageId, item, user.agent.split('/').pop())
line.creator ??= user.agent.split('/').pop()
```
For the `http`-id branch, `new Line(item)` reads `creator` from input via destructuring (`classes/Line/Line.js:16-29`). The `??=` on the next line only fills when nullish, so an input-supplied `creator` is preserved. The `Line.build` branch is safe — its destructure (`Line.js:32-36`) drops `creator` from input and uses the function-arg from `user.agent`.
`#saveLineToRerum` then writes the line to RERUM via `creator: await fetchUserAgent(this.creator)`. `fetchUserAgent` (`utilities/shared.js:343-358`) returns any string starting with `http` verbatim, so the supplied IRI flows through unchanged.
## Reproduction
PUT a page with an existing-line update whose body carries a fake `creator`:
```http
PUT /project/{projectId}/page/{pageId}
Authorization: Bearer
Content-Type: application/json
{
"items": [
{
"id": "https://store.rerum.io/v1/id/",
"body": [{ "type": "TextualBody", "value": "x", "format": "text/plain" }],
"target": { /* unchanged */ },
"creator": "https://store.rerum.io/v1/id/SOMEONE_ELSE"
}
]
}
```
Returns 200. The new RERUM version of that line records `creator: https://store.rerum.io/v1/id/SOMEONE_ELSE`, not the authenticated user's agent.
## Impact
- Audit/provenance via `__rerum.generatedBy` is intact (RERUM stamps that from auth), so the underlying "who actually wrote this version" is recoverable.
- The body-level `creator` is what consumers read for attribution display ("authored by X"). Anything that surfaces creator in the UI shows the wrong agent.
- Authentication is required, so this isn't anonymous spoofing — but a token holder can attribute lines to other users, which has obvious abuse and audit-trail implications.
- `screenContentMiddleware` / `hasSuspiciousPageData` don't normalize `creator`; the common_keys list checks for script injection in label/value/text/etc., not identity fields.
## Suggested fix
Strip `creator` from input on the existing-line branch and force it from auth, mirroring how the page-level creator is set at `page/index.js:95`:
```js
const line = item.id?.startsWith?.('http')
? new Line({ ...item, creator: undefined })
: Line.build(projectId, pageId, item, user.agent.split('/').pop())
line.creator = user.agent.split('/').pop()
```
(Or hard-assign instead of `??=` and document that `creator` is server-controlled, then strip in the constructor as a defense-in-depth.)
Worth a similar audit on the `PUT /line/:lineId` and `PATCH` routes — `Object.assign(line, req.body)` at `line/index.js:130` has the same shape and likely the same exposure.
## Found via
`/static-review` of [TPEN-Prompts#4](https://github.com/CenterForDigitalHumanities/TPEN-Prompts/pull/4). The prompts UI accepts JSON pasted from an LLM and submits it via this PUT; an LLM that hallucinates a `creator` field would silently misattribute lines. The right defense is here, not in the client.
Contributor guide
Research direction
Start at the page PUT route in page/index.js:119-123, then read the Line constructor and build method in classes/Line/Line.js:16-36. Check how fetchUserAgent in utilities/shared.js:343-358 handles the existing-line update. Done means an input-supplied creator cannot replace the authenticated user's agent on the saved line.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- api, backend, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100