Skip to content

[pull] main from tinacms:main#189

Merged
pull[bot] merged 2 commits intocode:mainfrom
tinacms:main
Apr 21, 2026
Merged

[pull] main from tinacms:main#189
pull[bot] merged 2 commits intocode:mainfrom
tinacms:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Apr 21, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

KahaMason and others added 2 commits April 21, 2026 16:53
## Summary

Adds Playwright integration tests for #6646's three acceptance criteria
at the HTTP boundary:

1. Path-traversal rejection on GraphQL mutations and `/media` routes
2. `/media` upload → list → delete round-trip
3. Concurrent mutation consistency under contention

All tests run against the real TinaCMS dev server via the existing `api`
Playwright project — no mocks, no stubs.

**Closes #6646 — part of epic #6464.**

## At a glance

| | |
|---|---|
| New tests | 23 |
| New files | 5 |
| Modified files | 7 |
| Files deleted | 1 |
| Local run (all platforms) | 37 passed · 1 fixme |
| CI runtime | ~12s |

## Tests added

### `tests/api/security.spec.ts` — 21 tests (20 passing + 1 fixme)

Every vector runs against all three GraphQL mutations
(create/update/delete) and the three `/media` routes
(upload/list/delete), except `backslash traversal` which is
`graphqlOnly` (its media-side behaviour depends on Win32 path
semantics). Each rejection must:

1. Error on the wire (GraphQL `errors` or HTTP 4xx/5xx)
2. Not leak server-side filesystem paths in the response
3. Not have written a file — `assertNoEscapeWrite` guards plausible
landing targets

| Vector | createDocument | updateDocument | deleteDocument | media
upload | media list | media delete |
|---|:-:|:-:|:-:|:-:|:-:|:-:|
| relative `../` traversal | ✅ | ✅ | ✅ | 403 | 403 | 403 |
| URL-encoded traversal | ✅ | ✅ | ✅ | 403 | 403 | 403 |
| null byte | ✅ | ✅ | ✅ | 500 | 200 no-op | 🟡 fixme |
| backslash traversal | ✅ | ✅ | ✅ | — | — | — |

🟡 `test.fixme` — one combination marked as expected-fail pending a
follow-up change. A finding reported privately to the TinaCMS team per
[SECURITY.md](../SECURITY.md); details intentionally omitted here.

`—` backslash × media is skipped entirely — `\` is only a path separator
on Win32, so media tests would be platform-specific. GraphQL tests for
backslash run on every platform via the character-allowlist regex.

### `tests/api/media.spec.ts` — 1 test

| Test | Covers |
|---|---|
| `media — upload → list → delete round-trip` | AC2 lifecycle chained in
one test; each phase verifies the previous phase's side effect |

### `tests/api/concurrency.spec.ts` — 1 test

| Test | Covers |
|---|---|
| `concurrent updates on the same document all resolve without data
loss` | 8 parallel `updateDocument` calls on the same doc. Asserts (1)
all resolve without errors, (2) final read matches one of the sent
titles, (3) the `title` index has exactly one edge across all 8 titles —
catches stale-index regressions an `eq: finalTitle` query would miss.
Directly satisfies the AC's "concurrent mutation test demonstrates
correct locking behaviour". |

## Supporting changes

| File | | Purpose |
|---|:-:|---|
| `playwright/tina-playwright/utils/graphql.ts` | 🆕 | Mutation strings +
helpers + `CollectionName` union |
| `playwright/tina-playwright/utils/media.ts` | 🆕 | `uploadMedia` /
`listMedia` / `deleteMedia` / `encodeMediaPath` |
| `playwright/tina-playwright/utils/deleteDocument.ts` | 🗑️ | Subsumed
by `utils/graphql.ts` |
| `playwright/tina-playwright/fixtures/test-content.ts` | ✏️ |
`mediaCleanup` fixture; both cleanups check-before-delete |
| `playwright/tina-playwright/fixtures/api-context.ts` | ✏️ | Removed
hard-coded `Content-Type` (was clobbering multipart boundaries) |
| `playwright/tina-playwright/tina/config.js` | ✏️ | Added `media.tina`
config — uploads were 500'ing without it |
| `playwright/tina-playwright/tests/api/pagination.spec.ts` | ✏️ |
Filter queries to seed titles — fixes pre-existing flakiness exposed by
any parallel content-creating test |
| `playwright/tina-playwright/.gitignore` | ✏️ | Defensive rule —
transient `playwright-*` artefacts in `content/*/` and `public/uploads/`
can't be accidentally staged if a test process is killed mid-run |
| `playwright/tina-playwright/README.md` | ✏️ | New `Projects` / `API
test suite` / `Shared infrastructure` sections |
| `AGENTS.md` (repo root) | ✏️ | New `Security & Disclosure` section so
future AI agents automatically adhere to SECURITY.md |
…om MediaStore (#6682)

## Summary

- Fixes a crash (React Error #31: "Objects are not valid as React
children") when inserting an image from the Media Library into an image
field or MDX rich-text editor, when using a custom `MediaStore` that
doesn't implement a `parse()` method (e.g., `GitHubMediaStore` for
self-hosted setups)
- The image field plugin's `onChange` fallback now extracts `media.src`
instead of passing the full Media object as the field value
- The MDX image toolbar button now normalizes `media.src` via `parse()`
or falls back to `media.src` before calling `insertImg()`

## Root Cause

The `parse()` method is **not part of the `MediaStore` interface**
(`packages/tinacms/src/toolkit/core/media.ts:65-100`), so custom stores
don't implement it. When `cms.media.store.parse` is `undefined`, the
fallback in `image-field-plugin.tsx` was passing the entire `Media`
object `{id, src, filename, directory, type, thumbnails}` as the field
value. React then tried to render this object as a child, causing Error
#31.

## Test plan

- [x] Unit tests added covering both buggy (regression) and fixed code
paths (12 vitest tests)
- [x] Integration test script verifying all scenarios (16 assertions)
- [x] Manual browser test: unpatched = white screen crash, patched =
image inserts correctly
- [ ] Verify existing tests still pass (`pnpm test` in
`packages/tinacms`)
- [ ] Test with default `TinaMediaStore` (should work as before since
`parse()` is still called when available)

Closes #6679

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Apr 21, 2026
@pull pull Bot added the ⤵️ pull label Apr 21, 2026
@pull pull Bot merged commit d998884 into code:main Apr 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants