Skip to content

security: fix SQL-injection and path-injection (CodeQL)#794

Merged
intel352 merged 2 commits into
mainfrom
sec/fix-injection-vulns-20260529
May 29, 2026
Merged

security: fix SQL-injection and path-injection (CodeQL)#794
intel352 merged 2 commits into
mainfrom
sec/fix-injection-vulns-20260529

Conversation

@intel352
Copy link
Copy Markdown
Contributor

Summary

Fixes all open CodeQL injection alerts. No suppressions — each is a root-cause fix.


Alerts fixed

go/sql-injection — plugin/storebrowser/handler.go (alerts #60, #61)

Alert #60tableRows sort column (line 192)

  • Source: r.URL.Query().Get("sort")orderClausefmt.Sprintf(query)
  • Fix: add isValidTableName identifier-regex check (^[a-zA-Z_][a-zA-Z0-9_]*$) on sortCol before the DB column-schema lookup. This gives CodeQL a local, statically-trackable sanitizer rather than relying on the DB round-trip for safety. The existing DB schema cross-check is retained as a second layer.

Alert #61execQuery admin SQL REPL (line 260)

  • Source: r.Bodyqtx.QueryContext
  • This is an intentional admin-only read-only SQL REPL. Parameterisation is impossible because the user supplies the full SQL structure. Mitigations are: sanitizeReadOnlyQuery (SELECT-only, no ;, no SQL comments), dangerousKeywords regexp (DROP/INSERT/UPDATE/DELETE/…), and BeginTx(ReadOnly:true). Added detailed code comment documenting these layers and the design intent.

go/path-injection — module/api_v1_handler.go (alerts #56, #57, #58)

Source: req.Path (JSON body) → os.Stat / filepath.Join / os.ReadFile (lines 965, 974, 982)

  • Fix: when h.dataDir is set, resolve req.Path to an absolute path via filepath.Abs and verify it stays inside h.dataDir using strings.HasPrefix(abs+sep, clean(base)+sep). Paths escaping the data directory return 403 Forbidden. When dataDir is empty (test/minimal deployments), filepath.Clean is still applied.

go/path-injection — store/local_storage.go + store/workspace.go (alert #14)

Source: projectID (URL segment) → WorkspacePathNewLocalStorageos.MkdirAll (line 25)

  • Fix in workspace.go: StorageForProject now resolves both the workspaces base and the candidate path to absolute paths and checks containment before calling NewLocalStorage. A traversal ID like ../../etc returns an error instead of creating a directory outside the workspaces root.
  • Fix in local_storage.go: tightened the resolve method's HasPrefix containment check by appending filepath.Separator to both sides, closing an off-by-one where root /tmp/work would incorrectly allow /tmp/workevil/….

Tests added

Test File Malicious inputs
TestTableRowsSortInjection plugin/storebrowser/handler_test.go id;DROP, backtick, ../../etc/passwd, id--, id()
TestSQLQueryInjectionAttempts plugin/storebrowser/handler_test.go semicolon+DROP, -- comment, /* */ block comment, tautology SELECT
TestWorkspaceManager_StorageForProject_PathTraversal store/workspace_test.go ../../etc/passwd, ../../../tmp/evil, proj/../../../etc
TestLoadWorkflowFromPath_PathTraversal module/api_v1_test.go ../../etc/passwd, /etc/passwd, escaped traversal, ../outside
TestLoadWorkflowFromPath_AllowedPath module/api_v1_test.go path inside dataDir (positive case)

False positives noted (not dismissed — left for the lead)

module/database.go alerts #8, #9 — CodeQL traces user request body → PipelineContextresolvedParamsdb.QueryContext(ctx, query, resolvedParams...). The user-derived data flows into the args slice (bound as ? placeholders), not into the SQL string. The only dynamic SQL interpolation path (allowDynamicSQL) runs every resolved value through validateSQLIdentifier before substitution. These are false positives.


Verification

go build ./...                              # clean
go test ./plugin/storebrowser/... ./store/... ./module/... -count=1
go vet ./plugin/storebrowser/... ./store/... ./module/...
gofmt -l <touched files>                   # no output
golangci-lint run ./plugin/storebrowser/. ./store/. ./module/.   # no new issues

All pass.

🤖 Generated with Claude Code

…14)

SQL-injection fixes (alerts #60, #61 — plugin/storebrowser/handler.go):
- tableRows sort-column path: add isValidTableName identifier-regex guard on
  the `sort` query param before the DB column-schema lookup so static analysis
  has a local sanitizer it can track; update comments and nolint annotations.
- execQuery (admin read-only SQL REPL): document the mitigation layers
  (sanitizeReadOnlyQuery + dangerousKeywords regex + ReadOnly transaction)
  explicitly in code so the intentional trade-off is clear to reviewers.

Path-injection fixes (alerts #56, #57, #58 — module/api_v1_handler.go):
- loadWorkflowFromPath: when h.dataDir is configured, verify the resolved
  absolute path stays inside h.dataDir using filepath.Abs + HasPrefix(path+sep)
  before calling os.Stat / filepath.Join / os.ReadFile. Paths that escape the
  data directory receive 403 Forbidden.

Path-injection fix (alert #14 — store/workspace.go + store/local_storage.go):
- WorkspaceManager.StorageForProject: validate that the projectID (URL segment,
  user-controlled) does not produce a workspace path outside the workspaces base
  directory before calling NewLocalStorage (which calls os.MkdirAll).
- LocalStorage.resolve: tighten the HasPrefix containment check by appending a
  path separator so that a root of "/tmp/work" cannot accidentally allow
  "/tmp/workevil/..." (off-by-one in bare HasPrefix without separator).

Tests added:
- TestTableRowsSortInjection: feeds semicolons, backticks, dot-traversal, dash-
  comments, parens as sort column values; asserts 400 rejection.
- TestSQLQueryInjectionAttempts: feeds semicolon+DROP, SQL comments, block
  comments, tautology (SELECT-only passes); asserts correct status codes.
- TestWorkspaceManager_StorageForProject_PathTraversal: feeds ../../etc/passwd
  and similar traversal IDs; asserts error.
- TestLoadWorkflowFromPath_PathTraversal: feeds traversal paths to the endpoint
  with dataDir set; asserts non-201 rejection.
- TestLoadWorkflowFromPath_AllowedPath: confirms a path inside dataDir succeeds.

False-positive notes (not dismissed — left for the lead):
- database.go alerts #8, #9: user request data flows into `resolvedParams`
  (bound as ? placeholders to parameterized queries), NOT into the SQL string
  itself. Only allowDynamicSQL paths interpolate resolved values, and those are
  validated by validateSQLIdentifier. These are false positives.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread module/api_v1_handler.go Dismissed
Comment thread plugin/storebrowser/handler.go Dismissed
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 29, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:298: parsing iteration count: invalid syntax
baseline-bench.txt:358211: parsing iteration count: invalid syntax
baseline-bench.txt:622758: parsing iteration count: invalid syntax
baseline-bench.txt:915577: parsing iteration count: invalid syntax
baseline-bench.txt:1205843: parsing iteration count: invalid syntax
baseline-bench.txt:1485411: parsing iteration count: invalid syntax
benchmark-results.txt:298: parsing iteration count: invalid syntax
benchmark-results.txt:342461: parsing iteration count: invalid syntax
benchmark-results.txt:633885: parsing iteration count: invalid syntax
benchmark-results.txt:952753: parsing iteration count: invalid syntax
benchmark-results.txt:1277942: parsing iteration count: invalid syntax
benchmark-results.txt:1598823: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ benchmark-results.txt │
                            │        sec/op         │
InterpreterCreation-4                  10.35m ± 33%
ComponentLoad-4                        3.642m ±  0%
ComponentExecute-4                     1.960µ ±  1%
PoolContention/workers-1-4             1.086µ ±  4%
PoolContention/workers-2-4             1.077µ ±  2%
PoolContention/workers-4-4             1.085µ ±  1%
PoolContention/workers-8-4             1.083µ ±  1%
PoolContention/workers-16-4            1.088µ ±  2%
ComponentLifecycle-4                   3.675m ±  1%
SourceValidation-4                     2.409µ ±  0%
RegistryConcurrent-4                   818.4n ±  5%
LoaderLoadFromString-4                 3.681m ±  1%
geomean                                19.46µ

                            │ benchmark-results.txt │
                            │         B/op          │
InterpreterCreation-4                  2.027Mi ± 0%
ComponentLoad-4                        2.180Mi ± 0%
ComponentExecute-4                     1.203Ki ± 0%
PoolContention/workers-1-4             1.203Ki ± 0%
PoolContention/workers-2-4             1.203Ki ± 0%
PoolContention/workers-4-4             1.203Ki ± 0%
PoolContention/workers-8-4             1.203Ki ± 0%
PoolContention/workers-16-4            1.203Ki ± 0%
ComponentLifecycle-4                   2.183Mi ± 0%
SourceValidation-4                     1.984Ki ± 0%
RegistryConcurrent-4                   1.133Ki ± 0%
LoaderLoadFromString-4                 2.182Mi ± 0%
geomean                                15.25Ki

                            │ benchmark-results.txt │
                            │       allocs/op       │
InterpreterCreation-4                   15.68k ± 0%
ComponentLoad-4                         18.02k ± 0%
ComponentExecute-4                       25.00 ± 0%
PoolContention/workers-1-4               25.00 ± 0%
PoolContention/workers-2-4               25.00 ± 0%
PoolContention/workers-4-4               25.00 ± 0%
PoolContention/workers-8-4               25.00 ± 0%
PoolContention/workers-16-4              25.00 ± 0%
ComponentLifecycle-4                    18.07k ± 0%
SourceValidation-4                       32.00 ± 0%
RegistryConcurrent-4                     2.000 ± 0%
LoaderLoadFromString-4                  18.06k ± 0%
geomean                                  183.3

cpu: AMD EPYC 9V74 80-Core Processor                
                            │ baseline-bench.txt │
                            │       sec/op       │
InterpreterCreation-4               9.424m ± 67%
ComponentLoad-4                     3.484m ± 13%
ComponentExecute-4                  1.814µ ±  3%
PoolContention/workers-1-4          1.004µ ±  7%
PoolContention/workers-2-4          1.034µ ±  1%
PoolContention/workers-4-4          1.026µ ±  2%
PoolContention/workers-8-4          1.003µ ±  6%
PoolContention/workers-16-4         1.018µ ±  4%
ComponentLifecycle-4                3.524m ±  2%
SourceValidation-4                  2.097µ ±  0%
RegistryConcurrent-4                739.4n ±  3%
LoaderLoadFromString-4              3.542m ±  1%
geomean                             18.12µ

                            │ baseline-bench.txt │
                            │        B/op        │
InterpreterCreation-4               2.027Mi ± 0%
ComponentLoad-4                     2.180Mi ± 0%
ComponentExecute-4                  1.203Ki ± 0%
PoolContention/workers-1-4          1.203Ki ± 0%
PoolContention/workers-2-4          1.203Ki ± 0%
PoolContention/workers-4-4          1.203Ki ± 0%
PoolContention/workers-8-4          1.203Ki ± 0%
PoolContention/workers-16-4         1.203Ki ± 0%
ComponentLifecycle-4                2.183Mi ± 0%
SourceValidation-4                  1.984Ki ± 0%
RegistryConcurrent-4                1.133Ki ± 0%
LoaderLoadFromString-4              2.182Mi ± 0%
geomean                             15.25Ki

                            │ baseline-bench.txt │
                            │     allocs/op      │
InterpreterCreation-4                15.68k ± 0%
ComponentLoad-4                      18.02k ± 0%
ComponentExecute-4                    25.00 ± 0%
PoolContention/workers-1-4            25.00 ± 0%
PoolContention/workers-2-4            25.00 ± 0%
PoolContention/workers-4-4            25.00 ± 0%
PoolContention/workers-8-4            25.00 ± 0%
PoolContention/workers-16-4           25.00 ± 0%
ComponentLifecycle-4                 18.07k ± 0%
SourceValidation-4                    32.00 ± 0%
RegistryConcurrent-4                  2.000 ± 0%
LoaderLoadFromString-4               18.06k ± 0%
geomean                               183.3

pkg: github.com/GoCodeAlone/workflow/middleware
cpu: AMD EPYC 7763 64-Core Processor                
                                  │ benchmark-results.txt │
                                  │        sec/op         │
CircuitBreakerDetection-4                     288.2n ± 4%
CircuitBreakerExecution_Success-4             21.55n ± 0%
CircuitBreakerExecution_Failure-4             66.33n ± 0%
geomean                                       74.41n

                                  │ benchmark-results.txt │
                                  │         B/op          │
CircuitBreakerDetection-4                    144.0 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

                                  │ benchmark-results.txt │
                                  │       allocs/op       │
CircuitBreakerDetection-4                    1.000 ± 0%
CircuitBreakerExecution_Success-4            0.000 ± 0%
CircuitBreakerExecution_Failure-4            0.000 ± 0%
geomean                                                 ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                                  │ baseline-bench.txt │
                                  │       sec/op       │
CircuitBreakerDetection-4                  296.8n ± 5%
CircuitBreakerExecution_Success-4          22.66n ± 0%
CircuitBreakerExecution_Failure-4          70.95n ± 0%
geomean                                    78.14n

                                  │ baseline-bench.txt │
                                  │        B/op        │
CircuitBreakerDetection-4                 144.0 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │
                                  │     allocs/op      │
CircuitBreakerDetection-4                 1.000 ± 0%
CircuitBreakerExecution_Success-4         0.000 ± 0%
CircuitBreakerExecution_Failure-4         0.000 ± 0%
geomean                                              ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
cpu: AMD EPYC 7763 64-Core Processor                
                                 │ benchmark-results.txt │
                                 │        sec/op         │
IaCStateBackend_InProcess-4                 342.7n ± 14%
IaCStateBackend_GRPC-4                      9.663m ±  4%
JQTransform_Simple-4                        706.3n ± 37%
JQTransform_ObjectConstruction-4            1.579µ ±  1%
JQTransform_ArraySelect-4                   3.633µ ±  2%
JQTransform_Complex-4                       41.80µ ±  1%
JQTransform_Throughput-4                    1.922µ ±  2%
SSEPublishDelivery-4                        64.44n ±  0%
geomean                                     4.029µ

                                 │ benchmark-results.txt │
                                 │         B/op          │
IaCStateBackend_InProcess-4                 416.0 ± 0%
IaCStateBackend_GRPC-4                    5.870Mi ± 8%
JQTransform_Simple-4                      1.273Ki ± 0%
JQTransform_ObjectConstruction-4          1.773Ki ± 0%
JQTransform_ArraySelect-4                 2.625Ki ± 0%
JQTransform_Complex-4                     16.31Ki ± 0%
JQTransform_Throughput-4                  1.984Ki ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                 │ benchmark-results.txt │
                                 │       allocs/op       │
IaCStateBackend_InProcess-4                 2.000 ± 0%
IaCStateBackend_GRPC-4                     6.836k ± 0%
JQTransform_Simple-4                        10.00 ± 0%
JQTransform_ObjectConstruction-4            15.00 ± 0%
JQTransform_ArraySelect-4                   30.00 ± 0%
JQTransform_Complex-4                       328.0 ± 0%
JQTransform_Throughput-4                    17.00 ± 0%
SSEPublishDelivery-4                        0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                                 │ baseline-bench.txt │
                                 │       sec/op       │
IaCStateBackend_InProcess-4              291.3n ± 29%
IaCStateBackend_GRPC-4                   10.47m ± 11%
JQTransform_Simple-4                     665.4n ± 37%
JQTransform_ObjectConstruction-4         1.502µ ±  1%
JQTransform_ArraySelect-4                3.605µ ±  2%
JQTransform_Complex-4                    43.34µ ±  1%
JQTransform_Throughput-4                 1.827µ ±  1%
SSEPublishDelivery-4                     65.14n ±  1%
geomean                                  3.928µ

                                 │ baseline-bench.txt │
                                 │        B/op        │
IaCStateBackend_InProcess-4              416.0 ± 0%
IaCStateBackend_GRPC-4                 5.885Mi ± 9%
JQTransform_Simple-4                   1.273Ki ± 0%
JQTransform_ObjectConstruction-4       1.773Ki ± 0%
JQTransform_ArraySelect-4              2.625Ki ± 0%
JQTransform_Complex-4                  16.31Ki ± 0%
JQTransform_Throughput-4               1.984Ki ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │
                                 │     allocs/op      │
IaCStateBackend_InProcess-4              2.000 ± 0%
IaCStateBackend_GRPC-4                  6.851k ± 0%
JQTransform_Simple-4                     10.00 ± 0%
JQTransform_ObjectConstruction-4         15.00 ± 0%
JQTransform_ArraySelect-4                30.00 ± 0%
JQTransform_Complex-4                    328.0 ± 0%
JQTransform_Throughput-4                 17.00 ± 0%
SSEPublishDelivery-4                     0.000 ± 0%
geomean                                             ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
cpu: AMD EPYC 7763 64-Core Processor                
                                    │ benchmark-results.txt │
                                    │        sec/op         │
SchemaValidation_Simple-4                      1.139µ ± 15%
SchemaValidation_AllFields-4                   1.657µ ±  5%
SchemaValidation_FormatValidation-4            1.611µ ±  3%
SchemaValidation_ManySchemas-4                 1.800µ ±  4%
geomean                                        1.529µ

                                    │ benchmark-results.txt │
                                    │         B/op          │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

                                    │ benchmark-results.txt │
                                    │       allocs/op       │
SchemaValidation_Simple-4                      0.000 ± 0%
SchemaValidation_AllFields-4                   0.000 ± 0%
SchemaValidation_FormatValidation-4            0.000 ± 0%
SchemaValidation_ManySchemas-4                 0.000 ± 0%
geomean                                                   ¹
¹ summaries must be >0 to compute geomean

cpu: AMD EPYC 9V74 80-Core Processor                
                                    │ baseline-bench.txt │
                                    │       sec/op       │
SchemaValidation_Simple-4                    1.107µ ± 3%
SchemaValidation_AllFields-4                 1.625µ ± 1%
SchemaValidation_FormatValidation-4          1.587µ ± 1%
SchemaValidation_ManySchemas-4               1.588µ ± 2%
geomean                                      1.459µ

                                    │ baseline-bench.txt │
                                    │        B/op        │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │
                                    │     allocs/op      │
SchemaValidation_Simple-4                   0.000 ± 0%
SchemaValidation_AllFields-4                0.000 ± 0%
SchemaValidation_FormatValidation-4         0.000 ± 0%
SchemaValidation_ManySchemas-4              0.000 ± 0%
geomean                                                ¹
¹ summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
cpu: AMD EPYC 7763 64-Core Processor                
                                   │ benchmark-results.txt │
                                   │        sec/op         │
EventStoreAppend_InMemory-4                   1.201µ ± 25%
EventStoreAppend_SQLite-4                     1.414m ±  3%
GetTimeline_InMemory/events-10-4              13.90µ ±  3%
GetTimeline_InMemory/events-50-4              78.47µ ±  2%
GetTimeline_InMemory/events-100-4             123.6µ ± 28%
GetTimeline_InMemory/events-500-4             628.9µ ±  2%
GetTimeline_InMemory/events-1000-4            1.288m ±  1%
GetTimeline_SQLite/events-10-4                106.7µ ±  0%
GetTimeline_SQLite/events-50-4                248.0µ ±  1%
GetTimeline_SQLite/events-100-4               420.9µ ±  1%
GetTimeline_SQLite/events-500-4               1.788m ±  1%
GetTimeline_SQLite/events-1000-4              3.498m ±  0%
geomean                                       220.1µ

                                   │ benchmark-results.txt │
                                   │         B/op          │
EventStoreAppend_InMemory-4                     802.5 ± 5%
EventStoreAppend_SQLite-4                     1.985Ki ± 2%
GetTimeline_InMemory/events-10-4              7.953Ki ± 0%
GetTimeline_InMemory/events-50-4              46.62Ki ± 0%
GetTimeline_InMemory/events-100-4             94.48Ki ± 0%
GetTimeline_InMemory/events-500-4             472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4            944.3Ki ± 0%
GetTimeline_SQLite/events-10-4                16.74Ki ± 0%
GetTimeline_SQLite/events-50-4                87.14Ki ± 0%
GetTimeline_SQLite/events-100-4               175.4Ki ± 0%
GetTimeline_SQLite/events-500-4               846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4              1.639Mi ± 0%
geomean                                       67.43Ki

                                   │ benchmark-results.txt │
                                   │       allocs/op       │
EventStoreAppend_InMemory-4                     7.000 ± 0%
EventStoreAppend_SQLite-4                       53.00 ± 0%
GetTimeline_InMemory/events-10-4                125.0 ± 0%
GetTimeline_InMemory/events-50-4                653.0 ± 0%
GetTimeline_InMemory/events-100-4              1.306k ± 0%
GetTimeline_InMemory/events-500-4              6.514k ± 0%
GetTimeline_InMemory/events-1000-4             13.02k ± 0%
GetTimeline_SQLite/events-10-4                  382.0 ± 0%
GetTimeline_SQLite/events-50-4                 1.852k ± 0%
GetTimeline_SQLite/events-100-4                3.681k ± 0%
GetTimeline_SQLite/events-500-4                18.54k ± 0%
GetTimeline_SQLite/events-1000-4               37.29k ± 0%
geomean                                        1.162k

cpu: AMD EPYC 9V74 80-Core Processor                
                                   │ baseline-bench.txt │
                                   │       sec/op       │
EventStoreAppend_InMemory-4                1.095µ ± 15%
EventStoreAppend_SQLite-4                  1.080m ±  6%
GetTimeline_InMemory/events-10-4           12.84µ ±  4%
GetTimeline_InMemory/events-50-4           64.09µ ± 16%
GetTimeline_InMemory/events-100-4          111.4µ ±  1%
GetTimeline_InMemory/events-500-4          566.7µ ±  1%
GetTimeline_InMemory/events-1000-4         1.147m ±  1%
GetTimeline_SQLite/events-10-4             83.37µ ±  4%
GetTimeline_SQLite/events-50-4             219.9µ ±  1%
GetTimeline_SQLite/events-100-4            384.4µ ±  3%
GetTimeline_SQLite/events-500-4            1.683m ±  2%
GetTimeline_SQLite/events-1000-4           3.266m ±  7%
geomean                                    193.3µ

                                   │ baseline-bench.txt │
                                   │        B/op        │
EventStoreAppend_InMemory-4                  756.0 ± 5%
EventStoreAppend_SQLite-4                  1.985Ki ± 1%
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%
geomean                                    67.10Ki

                                   │ baseline-bench.txt │
                                   │     allocs/op      │
EventStoreAppend_InMemory-4                  7.000 ± 0%
EventStoreAppend_SQLite-4                    53.00 ± 0%
GetTimeline_InMemory/events-10-4             125.0 ± 0%
GetTimeline_InMemory/events-50-4             653.0 ± 0%
GetTimeline_InMemory/events-100-4           1.306k ± 0%
GetTimeline_InMemory/events-500-4           6.514k ± 0%
GetTimeline_InMemory/events-1000-4          13.02k ± 0%
GetTimeline_SQLite/events-10-4               382.0 ± 0%
GetTimeline_SQLite/events-50-4              1.852k ± 0%
GetTimeline_SQLite/events-100-4             3.681k ± 0%
GetTimeline_SQLite/events-500-4             18.54k ± 0%
GetTimeline_SQLite/events-1000-4            37.29k ± 0%
geomean                                     1.162k

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

❌ Patch coverage is 68.75000% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
module/api_v1_handler.go 62.50% 7 Missing and 2 partials ⚠️
store/workspace.go 57.14% 3 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

…ath read, revert-sensitive tests)

CRITICAL 1 — path-injection guard was dead in production:
- cmd/server/main.go: wire v1Handler.SetDataDir(*dataDir) right after
  NewV1APIHandler so the loadWorkflowFromPath containment guard is actually
  reached in the running binary (previously SetDataDir was never called).
- module/api_v1_handler.go: make containment active BY DEFAULT. If no dataDir is
  configured, fall back to the process working directory (filepath.Abs(".")) as
  the containment base rather than reading arbitrary absolute paths. Default-deny.

IMPORTANT 2 — SetDataDir now normalizes to absolute:
- SetDataDir stores filepath.Clean(filepath.Abs(dir)) so the HasPrefix compare in
  loadWorkflowFromPath is absolute-vs-absolute. A relative dataDir no longer makes
  the prefix check spuriously fail.

IMPORTANT 3 — TestLoadWorkflowFromPath_PathTraversal now revert-sensitive:
- Asserts EXACTLY http.StatusForbidden (403) for each out-of-base path — a status
  only the containment guard produces. Added TestLoadWorkflowFromPath_ContainedByDefault
  (no SetDataDir) asserting 403 for /etc/passwd via the working-dir default base.
- Revert-restore proof: with the guard disabled, /etc/passwd is read and returned
  201 with full file contents (live exploit); traversal-to-missing returns 400.
  All assertions fail without the guard; restored → pass.

IMPORTANT 4 — storebrowser sort-injection tests now prove the NEW guard:
- Added TestIsValidTableName_RejectsMalicious (direct unit test, fails to compile
  if the function is removed) and TestTableRowsSortInjection_GuardCatchesSchemaColumn,
  which creates a real column named "bad col" (passes the schema allowlist) and
  asserts the isValidTableName guard rejects it with 400 before the DB is queried.
- Revert-restore proof: with isValidTableName removed, "bad col" reaches the DB
  and the request returns 500 (unsafe); restored → 400 pass.

MINOR 5 — api_v1_handler.go no longer echoes the resolved absolute path in the
  "path not found" / "no workflow.yaml" error responses.

MINOR 6 — store/workspace.go StorageForProject rejects projectID of "", ".", "..",
  or any value containing a path separator before computing the workspace path,
  preserving project isolation.

database.go FP claim (#8, #9) confirmed by review — no change; lead to dismiss.

Re-verified: go build ./...; go test ./module/... ./plugin/storebrowser/...
./store/... ./cmd/server/...; go vet; gofmt -l (clean); golangci-lint (clean).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@intel352 intel352 merged commit 9db3730 into main May 29, 2026
21 of 22 checks passed
@intel352 intel352 deleted the sec/fix-injection-vulns-20260529 branch May 29, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants