Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,24 @@ function normalizeContent(content) {
// prefix class already caps it at 255 characters and excludes whitespace and
// `,`, and what was actually sent is the honest thing to record.
//
// #722's allowlist reservation is closed, verbatim, permanently (#736). An
// allowlist cannot make the marker trustworthy: `data:application/pdf;base64,
// <stripped>` typed into an ordinary message passes this regex byte-for-byte
// untouched, because `<` is outside the payload class `[A-Za-z0-9+/=_-]+`
// (which also requires at least one character), so a consumer that trusted a
// structured-looking marker could already be fed a byte-identical forgery by
// the wire, allowlisted mediatype or not. The mediatype class does close
// one channel: it excludes all whitespace and `,`, so a marker can never
// carry a newline, carriage return, tab, or a comma to splice a log line
// or a CSV cell. That class is negated, though, not an allowlist, so it
// admits every other control character. ESC survives into `content_text`:
// `data:evil<ESC>[31mX;base64,QUFB` strips to
// `data:evil<ESC>[31mX;base64,<stripped>`, ESC intact, and the default
// `table` query-output format does not escape it before writing it to the
// terminal. Neutralizing terminal escapes is a `content_text` rendering
// concern for every column, not something an allowlist on this one
// mediatype would fix; it is open, tracked in #752.
//
// The capture group only changes what the replacement does. It must not change
// what matches: the prefix class stays `[^\s,]{0,255}?` so a `data:` cannot
// splice onto an unrelated `;base64,` across prose, a comma, or 255 characters.
Expand All @@ -1169,6 +1187,16 @@ function normalizeContent(content) {
// length cap on `content_text` is a deliberate open question (#718) and is
// not addressed here.
const BASE64_DATA_URI = /data:([^\s,]{0,255}?);base64,[A-Za-z0-9+/=_-]+/g

// The empty-mediatype case (`data:;base64,...`) falls back to this rather
// than echoing an empty string or resolving it the way RFC 2397 does (an
// omitted mediatype means `text/plain;charset=US-ASCII`). Kept as shipped,
// deliberately, after re-examination (#736): the RFC default answers "what
// would a browser render this as", not "what did the row actually see", and
// a search for it is no more discriminating than this sentinel. The
// accepted cost: this collides indistinguishably with a genuine
// `application/octet-stream` payload, and a literal search for
// `data:;base64` no longer finds the row. Low stakes either way.
const UNKNOWN_MEDIATYPE = 'application/octet-stream'

/** @param {string | undefined} text */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Key columns:

- `session_id`, `conversation_id`, `message_id`, `message_index`, `part_id`, `part_index`: stable identity. `session_id` is the always-present session key (group/scope on it); `conversation_id` is a nullable thread within a session (a Codex thread; null for Claude).
- `provider`, `model`, `role`, `part_type`, `content_text`: normalized provider/message content fields. `part_type` is HypAware's own vocabulary, NOT the provider's wire name: `text`, `reasoning`, `tool_call`, `tool_result`, `image`, `fallback`. Tool calls are `part_type='tool_call'`: Anthropic's `tool_use` matches no row and returns a silently empty result. `role` is `user` / `assistant` / `tool` / `system` / `developer`.
- `content_text` never carries a raw base64 payload: an inline `data:<mediatype>;base64,<payload>` is stripped and replaced with a marker. Two generations exist. Rows written before PR #732 always read `data:image;base64,<stripped>` (a fixed, invalid-mediatype sentinel, even for a non-image payload); rows written after it echo the real wire mediatype, e.g. `data:application/pdf;base64,<stripped>`. To find "a payload was stripped here" across both generations, match the stable substring `;base64,<stripped>`, not either full marker. The marker is not authenticated: this substring is ordinary text, so a message that merely contains it also matches. Treat a hit as "probably stripped", not as proof.
- `tool_name`, `tool_call_id`, `tool_args`, `status`: tool-call/result joins and sparse status such as `finish_reason`.
- `attributes` (JSON): request settings, usage, propagated `dev_run_id`, and gateway diagnostics under `attributes.gateway`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Key columns:

- `session_id`, `conversation_id`, `message_id`, `message_index`, `part_id`, `part_index`: stable identity. `session_id` is the always-present session key (group/scope on it); `conversation_id` is a nullable thread within a session (a Codex thread; null for Claude).
- `provider`, `model`, `role`, `part_type`, `content_text`: normalized provider/message content fields. `part_type` is HypAware's own vocabulary, NOT the provider's wire name: `text`, `reasoning`, `tool_call`, `tool_result`, `image`, `fallback`. Tool calls are `part_type='tool_call'`: Anthropic's `tool_use` matches no row and returns a silently empty result. `role` is `user` / `assistant` / `tool` / `system` / `developer`.
- `content_text` never carries a raw base64 payload: an inline `data:<mediatype>;base64,<payload>` is stripped and replaced with a marker. Two generations exist. Rows written before PR #732 always read `data:image;base64,<stripped>` (a fixed, invalid-mediatype sentinel, even for a non-image payload); rows written after it echo the real wire mediatype, e.g. `data:application/pdf;base64,<stripped>`. To find "a payload was stripped here" across both generations, match the stable substring `;base64,<stripped>`, not either full marker. The marker is not authenticated: this substring is ordinary text, so a message that merely contains it also matches. Treat a hit as "probably stripped", not as proof.
- `tool_name`, `tool_call_id`, `tool_args`, `status`: tool-call/result joins and sparse status such as `finish_reason`.
- `attributes` (JSON): request settings, usage, propagated `dev_run_id`, and gateway diagnostics under `attributes.gateway`.

Expand Down
Loading