Videos.duplicate() copies createdAt and public, drops password → shareable-link quota bypass and public exposure of password-protected caps
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 58/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- mysql, typescript
Línea de trabajo
Comienza en packages/web-backend/src/Videos/index.ts y sigue duplicate() hasta create() en VideosRepo.ts; inspecciona Video.ts y las columnas de vídeo en packages/database/schema.ts. Determina el comportamiento previsto de password/public para los duplicados y verifica después que los identificadores de origen y las marcas de tiempo no lleguen al insert, que la protección no se debilite silenciosamente y que las pruebas de salida de duplicados cubran la regresión.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
Videos.duplicate() spreads the full source video into repo.create, which lets two
unrelated columns leak through unintended paths: createdAt/updatedAt are forged to the
original video's timestamp instead of getting a fresh one, and public is copied while
password is silently dropped.
Root cause
packages/web-backend/src/Videos/index.ts:413-425:
yield* repo.create(
{
...video,
source: publishedKeys.size > 0 ? { type: "desktopMP4" } : video.source,
metadata: Option.map(video.metadata, (metadata) => { ... }),
},
{ id: newVideoId },
);
video is a Video.Video instance (packages/web-domain/src/Video.ts:27-58), a
Schema.Class whose fields including createdAt, updatedAt, and public are own
enumerable instance properties. password is deliberately excluded from this class
(comment at line 26: "Purposefully doesn't include password as this is a public class"), so
it never rides along in the spread.
CreateVideoInput (packages/web-backend/src/Videos/VideosRepo.ts:11-14) is typed as
Omit<Schema.Type<typeof Video.Video>, "id" | "createdAt" | "updatedAt"> & {...}, but this
Omit only affects the compiler TypeScript does not run excess-property checks against a
spread, so createdAt/updatedAt pass through unflagged at both compile time and runtime.
create() (VideosRepo.ts:106-131) explicitly overrides nine keys
(id, orgId, bucket, storageIntegrationId, metadata, transcriptionStatus,
folderId, width, height, duration) before calling
db.insert(Db.videos).values([{ ...data, ... }]) but not createdAt, updatedAt, or
public. Both createdAt/updatedAt columns are .defaultNow()
(packages/database/schema.ts:439,446), which Drizzle/MySQL only apply when the key is
absent from the insert here it's present with the original video's value, so the default
never fires. password (schema.ts:448) is nullable with no default and is never a key on
data at all, so it comes out NULL.
Consequence 1: shareable-link quota bypass
SHAREABLE_LINK_LIMIT_ENFORCED_FROM = 2026-08-19T00:00:00.000Z
(packages/web-domain/src/Video.ts:22-24), and
apps/web/lib/shareable-link-quota.ts:32 exempts any video with
createdAt < SHAREABLE_LINK_LIMIT_ENFORCED_FROM from the free-tier 25-link/month cap,
permanently. Duplicating any video created before 2026-08-19 produces a new video row that
keeps the original createdAt, so the duplicate is also permanently quota-exempt. A free-tier
user can duplicate one old cap as many times as they like and get unlimited uncounted
shareable links.
Consequence 2: access-control regression
Duplicating a public: true + password-protected cap copies public: true forward but drops
the password (comes out NULL). The result is a public cap with no password, silently
created by a completely ordinary "duplicate" action no attacker or malicious intent required.
Consequence 3 (minor): dashboard sort
Duplicates keep the original's createdAt, so if the dashboard orders by creation date they
sort next to the original instead of appearing at the top, effectively burying themselves.
Existing work
None found. The quota feature landed via PR #2138 ("Shareable link limits", merged
2026-08-20) and never touched Videos/index.ts/duplicate(). No issue or PR references this
interaction, and there's no test coverage for Videos.duplicate()'s output fields at all.
Fix
- In
repo.create, build the insert payload by enumerating the columns that should carry over
from the source video, instead of spreading the wholeVideo.Videoinstance. Never let
id/createdAt/updatedAtreach the insert from the source object. - Decide deliberately whether a duplicate should inherit password protection; if not, force
public: falseon duplicates of password-protected videos (or carry the password forward
explicitly but silently dropping it while keepingpublic: trueis the wrong default
either way).
- Lenguaje dominante
- Rust
- Estrellas
- 22.8k
- Forks
- 2k
- Merge medio
- 10 h 40 min
- PR fusionados (30 d)
- 89
Preparar el entorno
- Incluye un Dockerfile o un archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de CapSoftware/Cap
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
CapSoftware/Cap#2384 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
CapSoftware/Cap#2305 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
CapSoftware/Cap#1714 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
CapSoftware/Cap#2409 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 64/100
CapSoftware/Cap#2408 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de CapSoftware/Cap
Issues similares
-
bug CLI exec tool-calls
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
-
maintainer-needed p2 triaged ui windows
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Los mantenedores suelen responder en 1 día
-
ai_p2
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
ClickHouse/ClickHouse#123351 ·
Los mantenedores suelen responder en 1 día
-
documentation
Dificultad 1/5 Menos de una hora Aptitud para principiantes 92/100
github/copilot-sdk#2804 · 1 comentario ·
Los mantenedores suelen responder en 1 día