Fix manifest push 500: use explicit SELECT instead of LastInsertId

SQLite's last_insert_rowid() only updates on actual INSERTs, not
ON CONFLICT DO UPDATE. When pushing a second tag for an existing
manifest digest, the upsert fires the conflict branch (no new row),
so LastInsertId() returns a stale ID from a previous insert. This
caused manifest_blobs and tags to reference the wrong manifest,
producing a 500 on the PUT manifest response.

Replace LastInsertId() with a SELECT id WHERE repository_id AND
digest query within the same transaction.

Security: manifest_blobs and tag foreign keys now always reference
the correct manifest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 12:35:39 -07:00
parent 885bf4bd56
commit 61b8c2fcef
2 changed files with 101 additions and 4 deletions

View File

@@ -92,8 +92,8 @@ func (d *DB) PushManifest(p PushManifestParams) error {
}
// Step b: insert or update manifest.
// Use INSERT OR REPLACE on the UNIQUE(repository_id, digest) constraint.
result, err := tx.Exec(
// Use INSERT ... ON CONFLICT DO UPDATE on the UNIQUE(repository_id, digest) constraint.
_, err = tx.Exec(
`INSERT INTO manifests (repository_id, digest, media_type, content, size)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(repository_id, digest) DO UPDATE SET
@@ -106,10 +106,22 @@ func (d *DB) PushManifest(p PushManifestParams) error {
_ = tx.Rollback()
return fmt.Errorf("db: insert manifest: %w", err)
}
manifestID, err := result.LastInsertId()
// Retrieve the manifest ID by querying the row directly. We cannot use
// result.LastInsertId() here because SQLite's last_insert_rowid() is
// unreliable after an ON CONFLICT DO UPDATE — it returns the rowid of
// the most recent *insert* in the connection, not the upserted row.
// When the conflict branch fires (no new row inserted), the stale
// last_insert_rowid from a previous insert is returned, causing
// manifest_blobs and tags to reference the wrong manifest.
var manifestID int64
err = tx.QueryRow(
`SELECT id FROM manifests WHERE repository_id = ? AND digest = ?`,
repoID, p.Digest,
).Scan(&manifestID)
if err != nil {
_ = tx.Rollback()
return fmt.Errorf("db: manifest last insert id: %w", err)
return fmt.Errorf("db: get manifest id after upsert: %w", err)
}
// Step c: populate manifest_blobs join table.