Problema
O endpoint de submissão de benchmark não tem autenticação, nem token compartilhado, nem rate limit, e não
valida o formato de body.result — o único teste é "string não vazia". A página pública /bench pega as
100 linhas mais recentes e faz JSON.parse(row.result) sem try/catch, seguido de iteração em
parsed.tasks.
Por que é explorável
Uma única submissão com result: "x" faz o JSON.parse lançar. Uma com result: "{}" faz
parsed.tasks ser undefined e o for...of lançar. Como a consulta é ordenada por timeCreated desc
com limit 100, a linha ruim permanece no conjunto e a página fica quebrada até alguém apagar a linha
diretamente no banco.
Evidência
packages/console/app/src/routes/bench/submission.ts:14-31:
export async function POST(event: APIEvent) {
const dict = i18n(localeFromRequest(event.request))
const body = (await event.request.json()) as SubmissionBody
if (!body.model || !body.agent || !body.result) {
return Response.json({ error: dict["bench.submission.error.allFieldsRequired"] }, { status: 400 })
}
await Database.use((tx) =>
tx.insert(BenchmarkTable).values({
id: Identifier.create("benchmark"),
model: body.model,
agent: body.agent,
result: body.result, // <-- string arbitraria
}),
)
return Response.json({ success: true }, { status: 200 })
}
packages/console/app/src/routes/bench/index.tsx:15-31 — a leitura sem proteção:
const rows = await Database.use((tx) =>
tx.select().from(BenchmarkTable).orderBy(desc(BenchmarkTable.timeCreated)).limit(100),
)
return rows.map((row) => {
const parsed = JSON.parse(row.result) as BenchmarkResult // <-- sem try/catch
const taskScores: Record<string, number> = {}
for (const t of parsed.tasks) { // <-- parsed.tasks pode ser undefined
taskScores[t.task.id] = t.averageScore
}
...
})
packages/console/app/src/routes/bench/[id].tsx:81-88 — o mesmo padrão na página de detalhe.
Impacto
- Negação de serviço persistente de uma página pública com uma única requisição.
- Poluição do leaderboard: qualquer pessoa insere resultados arbitrários de qualquer modelo e agente,
que aparecem como dados oficiais do projeto em /bench.
- Crescimento não limitado da
BenchmarkTable: o campo result não tem teto de tamanho na rota.
Sugestão de correção
- Exigir credencial no endpoint — um token de CI comparado com
safeEqual, que já existe em
packages/console/core/src/util/crypto.ts — ou mover a ingestão para um job autenticado.
- Validar
body.result com um schema zod do formato BenchmarkResult antes do insert, recusando o que
não casar.
- Blindar a leitura: envolver o
JSON.parse em try/catch e descartar linhas inválidas em vez de
quebrar a página.
- Adicionar limite de tamanho ao campo
result e rate limit por IP.
Critérios de aceite
Labels sugeridas: security, media
Achado da auditoria estatica de seguranca do commit 0465a5d. Relatorio completo em docs/security-audit/relatorio-auditoria-seguranca.pdf.
Problema
O endpoint de submissão de benchmark não tem autenticação, nem token compartilhado, nem rate limit, e não
valida o formato de
body.result— o único teste é "string não vazia". A página pública/benchpega as100 linhas mais recentes e faz
JSON.parse(row.result)semtry/catch, seguido de iteração emparsed.tasks.Por que é explorável
Uma única submissão com
result: "x"faz oJSON.parselançar. Uma comresult: "{}"fazparsed.tasksserundefinede ofor...oflançar. Como a consulta é ordenada portimeCreated desccom
limit 100, a linha ruim permanece no conjunto e a página fica quebrada até alguém apagar a linhadiretamente no banco.
Evidência
packages/console/app/src/routes/bench/submission.ts:14-31:packages/console/app/src/routes/bench/index.tsx:15-31— a leitura sem proteção:packages/console/app/src/routes/bench/[id].tsx:81-88— o mesmo padrão na página de detalhe.Impacto
que aparecem como dados oficiais do projeto em
/bench.BenchmarkTable: o camporesultnão tem teto de tamanho na rota.Sugestão de correção
safeEqual, que já existe empackages/console/core/src/util/crypto.ts— ou mover a ingestão para um job autenticado.body.resultcom um schema zod do formatoBenchmarkResultantes do insert, recusando o quenão casar.
JSON.parseemtry/catche descartar linhas inválidas em vez dequebrar a página.
resulte rate limit por IP.Critérios de aceite
POST /bench/submissionexige credencial e responde 401 sem elabody.resulté validado por schema zod (taskspresente e array) antes do insertbench/index.tsxebench/[id].tsxtoleram linha inválida sem quebrar a renderizaçãoresultinválido é recusada/benchLabels sugeridas: security, media
Achado da auditoria estatica de seguranca do commit
0465a5d. Relatorio completo emdocs/security-audit/relatorio-auditoria-seguranca.pdf.