Skip to content

Commit ca583d0

Browse files
eastmadcclaude
andcommitted
fix(findings): widen title column from VARCHAR(255) to VARCHAR(512) for nested-extract paths
Credentials-detector formats title as f"Hardcoded credential in {path}"; on deeply-nested firmware extracts (e.g. RespArray V1.12 with paths through .tar.xz_extract → .gzip_extract → .gzip_extract → etc/.../delegates.xml, ~290 chars), the per-finding flush triggered StringDataRightTruncationError on findings.title and rolled back the entire /security/audit transaction — no findings persisted even though every scanner ran successfully (~12 min of work lost). Widening to 512 matches the existing file_path column width. Migration: hand-written using the 1f6c72decc84 precedent (autogenerate is currently blocked by an orthogonal model-registration gap — hardware_firmware referenced via FK from SbomVulnerability but not exported via app/models/__init__.py; fixing that registration is out of scope for this fix per Rule #19). Verified post-rebuild: DB column width = 512, Finding mapper width = 512, alembic head = d4a7c8b6e2f1. Source: .planning/intake/gui-smoke-bugs-2026-04-24.md Bug #3 (HIGH). Rule #20: rebuilt backend+worker (class-shape change to ORM mapper). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a1c5d54 commit ca583d0

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""widen_findings_title_to_512
2+
3+
Revision ID: d4a7c8b6e2f1
4+
Revises: c3f8a1b9e4d2
5+
Create Date: 2026-04-25 00:00:00.000000
6+
7+
The Finding.title column was created as VARCHAR(255) but the
8+
credentials-detector formats title as
9+
``f"Hardcoded credential in {path}"`` — on deeply-nested firmware
10+
extracts (e.g. ``/zImage-restore.tar.xz_extract/.../etc/.../delegates.xml``)
11+
the path alone reaches ~290 chars. The whole /security/audit pipeline
12+
runs to completion, then the per-finding flush triggers
13+
``StringDataRightTruncationError`` and rolls back the entire transaction
14+
— no findings persist even though every scanner ran successfully.
15+
16+
Widening to 512 matches the existing ``file_path`` column width and is
17+
a metadata-only ALTER in PostgreSQL (no lock storm, non-blocking).
18+
19+
Note: alembic autogenerate is currently blocked by an orthogonal
20+
model-registration gap (``hardware_firmware`` model is referenced via
21+
FK from ``SbomVulnerability`` but not exported via ``app/models/__init__.py``);
22+
this revision was hand-written using the
23+
``1f6c72decc84_widen_analysis_cache_operation_to_512`` precedent.
24+
"""
25+
from typing import Sequence, Union
26+
27+
from alembic import op
28+
import sqlalchemy as sa
29+
30+
31+
# revision identifiers, used by Alembic.
32+
revision: str = 'd4a7c8b6e2f1'
33+
down_revision: Union[str, None] = 'c3f8a1b9e4d2'
34+
branch_labels: Union[str, Sequence[str], None] = None
35+
depends_on: Union[str, Sequence[str], None] = None
36+
37+
38+
def upgrade() -> None:
39+
op.alter_column(
40+
"findings",
41+
"title",
42+
existing_type=sa.String(length=255),
43+
type_=sa.String(length=512),
44+
existing_nullable=False,
45+
)
46+
47+
48+
def downgrade() -> None:
49+
# Truncates any rows whose title > 255 chars. Irreversible for
50+
# those rows (original value not recoverable). Kept for completeness.
51+
op.execute(
52+
"UPDATE findings SET title = LEFT(title, 255) "
53+
"WHERE length(title) > 255"
54+
)
55+
op.alter_column(
56+
"findings",
57+
"title",
58+
existing_type=sa.String(length=512),
59+
type_=sa.String(length=255),
60+
existing_nullable=False,
61+
)

backend/app/models/finding.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Finding(Base):
3030
nullable=True,
3131
index=True,
3232
)
33-
title: Mapped[str] = mapped_column(String(255), nullable=False)
33+
title: Mapped[str] = mapped_column(String(512), nullable=False)
3434
severity: Mapped[str] = mapped_column(String(20), nullable=False)
3535
description: Mapped[str | None] = mapped_column(Text)
3636
evidence: Mapped[str | None] = mapped_column(Text)

0 commit comments

Comments
 (0)