Skip to content
Merged
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
12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions internal/app/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,8 +345,10 @@ func (m *Model) startSQLStream(query string) tea.Cmd {
messages = append(messages, history...)
messages = append(messages, llm.Message{Role: roleUser, Content: query})

ctx, cancel := context.WithCancel(context.Background())

//nolint:gosec // cancel stored in CancelFn, called on ctrl+c
ctx, cancel := context.WithCancel(
context.Background(),
)
streamCh, err := client.ChatStream(ctx, messages)
if err != nil {
return sqlStreamStartedMsg{
Expand Down
11 changes: 9 additions & 2 deletions internal/app/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ func TestCancellationDuringSQLGeneration(t *testing.T) {
m := newTestModel(t)
m.openChat()

_, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
_ = ctx // only the cancel func is needed; ctx is cleaned up via t.Cleanup
m.chat.CurrentQuery = testQuestion
m.chat.Streaming = true
m.chat.StreamingSQL = true
Expand Down Expand Up @@ -150,7 +152,9 @@ func TestCancellationDuringAnswerStreaming(t *testing.T) {
m := newTestModel(t)
m.openChat()

_, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
_ = ctx
m.chat.CurrentQuery = testQuestion
m.chat.Streaming = true
m.chat.StreamingSQL = false // stage 2
Expand Down Expand Up @@ -240,6 +244,7 @@ func TestNoSpinnerAfterCancellation(t *testing.T) {
m.openChat()

_, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
m.chat.Streaming = true
m.chat.StreamingSQL = true
m.chat.CancelFn = cancel
Expand Down Expand Up @@ -274,6 +279,7 @@ func TestLateSQLChunkAfterCancellationIsDropped(t *testing.T) {
m.openChat()

_, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
m.chat.Streaming = true
m.chat.StreamingSQL = true
m.chat.CancelFn = cancel
Expand Down Expand Up @@ -310,6 +316,7 @@ func TestLateChatChunkAfterCancellationIsDropped(t *testing.T) {
m.openChat()

_, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
m.chat.Streaming = true
m.chat.CancelFn = cancel
m.chat.Messages = []chatMessage{
Expand Down
9 changes: 7 additions & 2 deletions internal/app/extraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,10 @@ func (m *Model) startExtractionOverlay(
sp := spinner.New(spinner.WithSpinner(spinner.Dot))
sp.Style = appStyles.AccentText()

ctx, cancel := context.WithCancel(context.Background())
//nolint:gosec // cancel stored in ex.CancelFn, called on extraction close
ctx, cancel := context.WithCancel(
context.Background(),
)

// Text extraction only applies to PDFs and text files; skip for images.
hasText := !extract.IsImageMIME(mime)
Expand Down Expand Up @@ -935,7 +938,9 @@ func (m *Model) rerunLLMExtraction() tea.Cmd {

// Replace a cancelled context so the rerun has a live one.
if ex.ctx.Err() != nil {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel( //nolint:gosec // cancel stored in ex.CancelFn, called on extraction close
context.Background(),
)
ex.ctx = ctx
ex.CancelFn = cancel
}
Expand Down
11 changes: 11 additions & 0 deletions internal/app/extraction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func newExtractionModel(t *testing.T, steps map[extractionStep]stepStatus) *Mode

m := newTestModel(t)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
ex := &extractionLogState{
ID: nextExtractionID.Add(1),
ctx: ctx,
Expand Down Expand Up @@ -913,6 +914,7 @@ func TestForeground_SwapsCurrentToBackground(t *testing.T) {

// Create a new foreground extraction.
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
m.ex.extraction = &extractionLogState{
ID: nextExtractionID.Add(1),
Filename: "second.pdf",
Expand Down Expand Up @@ -1051,6 +1053,7 @@ func TestMultipleBgExtractions(t *testing.T) {

// Create and background second.
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
m.ex.extraction = &extractionLogState{
ID: nextExtractionID.Add(1),
Filename: "b.pdf",
Expand Down Expand Up @@ -1088,6 +1091,7 @@ func TestStartExtraction_AutoBackgroundsExisting(t *testing.T) {
// This tests the backgrounding logic directly.
m.backgroundExtraction()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
m.ex.extraction = &extractionLogState{
ID: nextExtractionID.Add(1),
Filename: "new.pdf",
Expand Down Expand Up @@ -1134,6 +1138,7 @@ func TestCtrlQ_CancelsAllBgExtractions(t *testing.T) {

// Create another foreground extraction.
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
m.ex.extraction = &extractionLogState{
ID: nextExtractionID.Add(1),
Filename: "fg2.pdf",
Expand Down Expand Up @@ -1817,6 +1822,7 @@ func TestDispatch_VendorCrossReference(t *testing.T) {
require.NoError(t, sdb.Stage(ops))

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
defer cancel()
ex := &extractionLogState{
ID: nextExtractionID.Add(1),
Expand Down Expand Up @@ -1885,6 +1891,7 @@ func TestDispatch_InvalidProjectIDShowsError(t *testing.T) {
require.NoError(t, sdb.Stage(ops))

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
defer cancel()
ex := &extractionLogState{
ID: nextExtractionID.Add(1),
Expand Down Expand Up @@ -1950,6 +1957,7 @@ func TestDispatch_OffsetCrossReference(t *testing.T) {
require.NoError(t, sdb.Stage(ops))

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
defer cancel()
ex := &extractionLogState{
ID: nextExtractionID.Add(1),
Expand Down Expand Up @@ -2032,6 +2040,7 @@ func TestDispatch_DuplicateVendorDedup(t *testing.T) {
require.NoError(t, sdb.Stage(ops))

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
defer cancel()
ex := &extractionLogState{
ID: nextExtractionID.Add(1),
Expand Down Expand Up @@ -2102,6 +2111,7 @@ func TestDispatch_DuplicateApplianceDedup(t *testing.T) {
require.NoError(t, sdb.Stage(ops))

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
defer cancel()
ex := &extractionLogState{
ID: nextExtractionID.Add(1),
Expand Down Expand Up @@ -2163,6 +2173,7 @@ func TestDispatch_TransactionRollbackOnFailure(t *testing.T) {
require.NoError(t, sdb.Stage(ops))

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
defer cancel()
ex := &extractionLogState{
ID: nextExtractionID.Add(1),
Expand Down
5 changes: 3 additions & 2 deletions internal/app/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -879,8 +879,9 @@ func columnWidths(
// Content exceeds terminal width — apply Max constraints.
widths := make([]int, columnCount)
for i, w := range natural {
if specs[i].Max > 0 && w > specs[i].Max {
w = specs[i].Max
if specs[i].Max > 0 && //nolint:gosec // specs and natural have equal length (columnCount)
w > specs[i].Max { //nolint:gosec // same bounds
w = specs[i].Max //nolint:gosec // same bounds
}
widths[i] = w
}
Expand Down
4 changes: 2 additions & 2 deletions internal/extract/ocr.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func ocrPDF(ctx context.Context, data []byte, maxPages int) (string, []byte, err
defer os.RemoveAll(tmpDir) //nolint:errcheck // best-effort cleanup

pdfPath := filepath.Join(tmpDir, "input.pdf")
if err := os.WriteFile(pdfPath, data, 0o600); err != nil {
if err := os.WriteFile(pdfPath, data, 0o600); err != nil { //nolint:gosec // path is tmpDir + constant filename
return "", nil, fmt.Errorf("write temp pdf: %w", err)
}

Expand Down Expand Up @@ -271,7 +271,7 @@ func ocrImage(ctx context.Context, data []byte) (string, []byte, error) {
defer os.RemoveAll(tmpDir) //nolint:errcheck // best-effort cleanup

imgPath := filepath.Join(tmpDir, "input")
if err := os.WriteFile(imgPath, data, 0o600); err != nil {
if err := os.WriteFile(imgPath, data, 0o600); err != nil { //nolint:gosec // path is tmpDir + constant filename
return "", nil, fmt.Errorf("write temp image: %w", err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/extract/ocr_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func BenchmarkOcrPage(b *testing.B) {

tmpDir := b.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
if err := os.WriteFile(pdfPath, data, 0o600); err != nil {
if err := os.WriteFile(pdfPath, data, 0o600); err != nil { //nolint:gosec // path is tmpDir + constant filename
b.Fatal(err)
}

Expand Down
54 changes: 45 additions & 9 deletions internal/extract/ocr_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,11 @@ func TestPdfPageCount_ValidPDF(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
require.NoError(t, os.WriteFile(pdfPath, data, 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, data, 0o600),
)

count, err := pdfPageCount(context.Background(), pdfPath)
require.NoError(t, err)
Expand All @@ -290,7 +294,11 @@ func TestPdfPageCount_InvalidPDF(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "corrupt.pdf")
require.NoError(t, os.WriteFile(pdfPath, []byte("corrupt data"), 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, []byte("corrupt data"), 0o600),
)

_, err := pdfPageCount(context.Background(), pdfPath)
assert.Error(t, err)
Expand All @@ -309,7 +317,11 @@ func TestPdfPageCount_ContextCancelled(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
require.NoError(t, os.WriteFile(pdfPath, data, 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, data, 0o600),
)

ctx, cancel := context.WithCancel(context.Background())
cancel()
Expand All @@ -335,7 +347,11 @@ func TestOcrPage_ValidPDF(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
require.NoError(t, os.WriteFile(pdfPath, data, 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, data, 0o600),
)

result := ocrPage(context.Background(), pdfPath, 1, nil)
require.NoError(t, result.err)
Expand All @@ -351,7 +367,11 @@ func TestOcrPage_InvalidPDF(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "corrupt.pdf")
require.NoError(t, os.WriteFile(pdfPath, []byte("corrupt data"), 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, []byte("corrupt data"), 0o600),
)

result := ocrPage(context.Background(), pdfPath, 1, nil)
assert.Error(t, result.err)
Expand All @@ -370,7 +390,11 @@ func TestOcrPage_ContextCancelled(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
require.NoError(t, os.WriteFile(pdfPath, data, 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, data, 0o600),
)

ctx, cancel := context.WithCancel(context.Background())
cancel()
Expand Down Expand Up @@ -812,7 +836,11 @@ func TestOcrPDFPages_ValidPDF(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
require.NoError(t, os.WriteFile(pdfPath, data, 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, data, 0o600),
)

pageCount, err := pdfPageCount(context.Background(), pdfPath)
require.NoError(t, err)
Expand Down Expand Up @@ -844,7 +872,11 @@ func TestOcrPDFPages_ContextCancelled(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
require.NoError(t, os.WriteFile(pdfPath, data, 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, data, 0o600),
)

ctx, cancel := context.WithCancel(context.Background())
cancel()
Expand All @@ -867,7 +899,11 @@ func TestOcrPDFPages_ProgressReporting(t *testing.T) {

tmpDir := t.TempDir()
pdfPath := filepath.Join(tmpDir, "input.pdf")
require.NoError(t, os.WriteFile(pdfPath, data, 0o600))
//nolint:gosec // path is tmpDir + constant filename
require.NoError(
t,
os.WriteFile(pdfPath, data, 0o600),
)

pageDone := make(chan struct{}, 2)
results := ocrPDFPages(context.Background(), pdfPath, 1, nil, pageDone)
Expand Down
4 changes: 2 additions & 2 deletions internal/extract/ocr_progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func ocrImageWithProgress(ctx context.Context, data []byte, ch chan<- ExtractPro
defer os.RemoveAll(tmpDir) //nolint:errcheck // best-effort cleanup

imgPath := filepath.Join(tmpDir, "input.png")
if err := os.WriteFile(imgPath, data, 0o600); err != nil {
if err := os.WriteFile(imgPath, data, 0o600); err != nil { //nolint:gosec // path is tmpDir + constant filename
ch <- ExtractProgress{Err: fmt.Errorf("write temp image: %w", err), Done: true}
return
}
Expand Down Expand Up @@ -118,7 +118,7 @@ func ocrPDFWithProgress(
defer os.RemoveAll(tmpDir) //nolint:errcheck // best-effort cleanup

pdfPath := filepath.Join(tmpDir, "input.pdf")
if err := os.WriteFile(pdfPath, data, 0o600); err != nil {
if err := os.WriteFile(pdfPath, data, 0o600); err != nil { //nolint:gosec // path is tmpDir + constant filename
ch <- ExtractProgress{Err: fmt.Errorf("write temp pdf: %w", err), Done: true}
return
}
Expand Down
2 changes: 1 addition & 1 deletion internal/extract/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func extractPDF(ctx context.Context, data []byte) (string, error) {
defer os.RemoveAll(tmpDir) //nolint:errcheck // best-effort cleanup

pdfPath := filepath.Join(tmpDir, "input.pdf")
if err := os.WriteFile(pdfPath, data, 0o600); err != nil {
if err := os.WriteFile(pdfPath, data, 0o600); err != nil { //nolint:gosec // path is tmpDir + constant filename
return "", fmt.Errorf("write temp pdf: %w", err)
}

Expand Down
8 changes: 8 additions & 0 deletions osv-scanner.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ reason = "minor TLS 1.3 info disclosure requiring network-local attacker; defaul
id = "GO-2026-4342"
reason = "archive/zip DoS via crafted ZIP filenames; micasa never opens or processes ZIP archives"

[[IgnoredVulns]]
id = "GO-2026-4601"
reason = "url.Parse IPv6 host parsing flaw; only caller is isLoopbackURL which parses the user's own llm.base_url config value, not attacker-supplied input"

[[IgnoredVulns]]
id = "GO-2026-4602"
reason = "os.ReadDir directory traversal via Root-constrained File; micasa uses plain os.ReadDir on a local cache dir, never uses os.OpenRoot"

# ollama server-side vulnerabilities: micasa is a client that makes HTTP
# requests to an Ollama server; it does not embed, host, or run the Ollama
# server, GGUF parser, or model-serving endpoints. These code paths are
Expand Down
Loading