dir := filepath.Dir(modelPath) // attacker-controlled
drawioPath := filepath.Join(dir, "architecture.drawio")
statePath := filepath.Join(dir, ".bausteinsicht-sync")
Security Review — 2026-03-01
Changelog
| Date | Changes |
|---|---|
2026-03-01 (initial) |
Initial full security review at commit |
2026-03-01 (fixes) |
Fixed SEC-002 (predictable temp file), SEC-003 (data race), SEC-009 (Go 1.23.0 → 1.24.0), SEC-010 (x/sys v0.13.0 → v0.41.0). Merged as PR #77. |
2026-03-01 (tooling) |
Added Makefile with |
2026-03-03 (sub-cells) |
Updated SEC-008 assessment: |
2026-03-05 (nilaway + review) |
Fixed SEC-012 (nil panics from |
2026-03-30 (full review) |
Full security review at current main. Verified fixes: SEC-004 (now 0600), SEC-007 (MaxElementDepth=50), SEC-011 ( |
2026-04-27 (typo-squatting) |
Fixed SEC-018: Go module path, |
2026-04-27 (govulncheck) |
Fixed SEC-017: govulncheck v1.1.4 (Go 1.22.2) could not analyze |
2026-04-27 (path traversal) |
Fixed SEC-015: view keys used directly in export filenames without sanitization. Added |
2026-04-27 (batch fix) |
Fixed SEC-001 (path traversal via CLI flags — reject |
Scope
Full security review of the Bausteinsicht repository at commit 992d7c1 (main branch, 2026-03-01).
Reviewed areas:
-
All Go source files in
cmd/bausteinsicht/(CLI layer) -
All Go source files in
internal/(drawio, model, sync, watcher packages) -
All direct and transitive dependencies (
go.mod/go.sum) -
Dependency vulnerability scan via
govulncheckand OSV database
Threat Model
Bausteinsicht is a local CLI tool that reads/writes files on the user’s workstation. The trust boundary is:
-
Trusted input: CLI flags provided by the operator
-
Semi-trusted input: JSONC model files and draw.io XML files (may be shared via Git between team members)
-
No network exposure: No HTTP server, no outbound API calls, no TLS server
The primary risk scenario is a malicious or crafted model/drawio file shared via a repository that causes unexpected behavior when another user runs bausteinsicht sync.
Summary
| ID | Severity | Issue | Status |
|---|---|---|---|
SEC-001 |
Medium |
No path boundary check on |
Fixed (reject |
SEC-002 |
Low-Medium |
Predictable temp file name in |
Fixed (PR #77) |
SEC-003 |
Low-Medium |
Data race on |
Fixed (PR #77) |
SEC-004 |
Low |
File permissions hardcoded to |
Fixed (init.go now uses 0600) |
SEC-005 |
Low |
|
Fixed (error on multiple files) |
SEC-006 |
Low |
No file size limit on JSONC model parsing (memory exhaustion DoS) |
Fixed (10 MB limit) |
SEC-007 |
Low |
No recursion depth limit on |
Fixed (MaxElementDepth=50) |
SEC-008 |
Low |
|
Fixed (attribute quoting) |
SEC-009 |
Medium |
Go toolchain 1.23.0 has 25 known stdlib vulnerabilities |
Fixed (PR #77, upgraded to 1.24.0) |
SEC-010 |
Low |
|
Fixed (PR #77, upgraded to v0.41.0) |
SEC-011 |
Low |
Unhandled |
Fixed ( |
SEC-015 |
Medium |
View key used directly in export filenames without path traversal sanitization |
Fixed (SafeViewKey) |
SEC-016 |
Low |
|
Fixed (reject |
SEC-017 |
Info |
|
Fixed (updated to v1.3.0) |
SEC-012 |
Low |
Potential nil panics from unchecked |
Fixed (#235) |
SEC-013 |
Low |
|
Fixed (strips dots/slashes) |
SEC-014 |
Medium |
Dockerfile downloads and pipes install scripts (golangci-lint, Claude Code, human CLI) without checksum verification |
Deferred (upstream projects don’t publish checksums) |
SEC-018 |
Medium |
Typo-squatting risk: Go module path, README |
Fixed |
Findings
SEC-001: Path Traversal via CLI Flags (Medium)
Affected files:
-
cmd/bausteinsicht/sync.go:27-28 -
cmd/bausteinsicht/watch.go:29-30 -
cmd/bausteinsicht/validate.go:34 -
cmd/bausteinsicht/add_element.go:40 -
cmd/bausteinsicht/add_relationship.go:34 -
internal/model/loader.go:13-14 -
internal/drawio/template.go:27-33
Description:
The --model and --template CLI flags accept arbitrary filesystem paths without validation.
No call to filepath.Clean or boundary check exists.
More critically, write targets are derived from the model path:
Supplying --model /tmp/../../some/path/x.jsonc causes writes at attacker-chosen locations.
Risk: For a local CLI tool where the operator provides their own flags, this is acceptable. If the CLI is ever wrapped in automation (CI/CD, LLM agent pipelines), this becomes exploitable.
Tool correlation: Also flagged by gosec as G304 (CWE-22) in loader.go:14, template.go:28, state.go:44, state.go:100.
Recommendation:
After resolving the user-supplied path with filepath.Abs, verify the result is rooted at an allowed base directory.
At minimum, document that paths are fully trusted and the tool must not be run with untrusted flag values.
SEC-002: Predictable Temp File in model.Save (Low-Medium) — FIXED
Affected file: internal/model/loader.go:32-38
Fix applied in PR #77: Replaced path + ".tmp" with os.CreateTemp(dir, ".model-tmp-*"), aligning with the safe pattern used in SaveDocument and SaveState.
SEC-003: Data Race in Watcher Debounce (Low-Medium) — FIXED
Affected file: internal/watcher/watcher.go:99-108
Fix applied in PR #77: Added captured := lastFile before time.AfterFunc closure to capture the variable by value instead of by reference. Verified with go test -race ./….
SEC-004: World-Readable File Permissions (Low) — FIXED
Fix verified 2026-03-30: init.go now uses 0600 for both model and template files. model.Save uses atomic temp file with restricted permissions.
SEC-005: AutoDetect Confused Deputy (Low)
Affected file: internal/model/loader.go:56-65
func AutoDetect(dir string) (string, error) {
matches, err := filepath.Glob(filepath.Join(dir, "*.jsonc"))
...
return matches[0], nil // first alphabetical match
}
Description:
When multiple .jsonc files exist, the tool silently picks the first alphabetically.
A malicious file named aaa-evil.jsonc placed in the project directory would be used over architecture.jsonc.
Recommendation:
If more than one .jsonc is found and --model was not specified, return an error requiring explicit selection.
SEC-006: No File Size Limit on JSONC Parsing (Low)
Affected file: internal/model/loader.go:13-24
Description:
model.Load reads the entire file into memory with os.ReadFile without a size check.
A crafted multi-gigabyte .jsonc file could exhaust memory.
Update (2026-03-30): StripJSONC now handles block comments (/* … */). The size limit issue remains open.
Recommendation: Add a maximum file size check (e.g., reject files over 10 MB).
SEC-007: Unbounded Recursion on Element Hierarchy (Low) — FIXED
Fix verified 2026-03-30: MaxElementDepth = 50 constant in resolve.go:11 is enforced in flattenInto, validateElement, and reverse.go (maxReverseDepth). Tests cover the depth limit.
SEC-008: stripTags Incorrect Parsing (Low — reduced)
Affected file: internal/drawio/label.go:141-155
Description:
The stripTags fallback function uses a simple state machine that treats any > as a tag closer.
If a draw.io label contains > inside an HTML attribute value (e.g., <font color="a>b">),
the parser exits the "inTag" state prematurely, leaking attribute content into the output.
This only affects the fallback path in ParseLabel (when the label does not start with <b>).
Update (2026-03-03): With the sub-cell architecture (#232), new elements use plain-text value attributes on child <mxCell> elements instead of HTML labels. ParseLabel is now only invoked as a backward-compatible fallback for older draw.io files that lack sub-cells. This significantly reduces the attack surface of stripTags — it will only be hit when reading legacy elements.
Recommendation:
Either use a proper HTML tokenizer or add attribute-aware parsing for the > inside quotes case.
Lower priority now that ParseLabel is a fallback path only.
SEC-009: Outdated Go Toolchain (Medium) — FIXED
Previous version: Go 1.23.0
Fixed version: Go 1.24.0
Fix applied in PR #77: Updated go.mod to go 1.24.0. All 25 stdlib vulnerabilities resolved.
Confirmed by govulncheck — no vulnerable symbols called by project code.
SEC-010: Outdated golang.org/x/sys (Low) — FIXED
Previous version: v0.13.0
Fixed version: v0.41.0
Fix applied in PR #77: Updated via go get golang.org/x/sys@latest && go mod tidy.
SEC-011: Unhandled Error in model.Save Cleanup (Low) — FIXED
Fix verified 2026-03-30: loader.go:115 now uses _ = tmp.Close() to explicitly discard the error.
SEC-012: Potential Nil Panics from Unchecked GetPage() (Low) — FIXED
Fix applied in #235: requirePage test helpers and strings.Cut refactor. Nil checks added.
SEC-015: View Key Path Traversal in Export Filenames (Medium) — FIXED
Added 2026-03-30, fixed 2026-04-27
Affected files:
-
cmd/bausteinsicht/export_diagram.go:115—outPath := filepath.Join(outputDir, key+"."+ext) -
cmd/bausteinsicht/export_table.go:75—filename = viewKey + "-elements." + tableFormat -
internal/export/export.go:53-54—OutputFileName(viewKey, format)
Description:
View keys from the JSONC model were used directly in output filenames without sanitization.
A malicious model file could define a view key containing path separators or .. sequences:
{
"views": {
"../../../tmp/pwned": { "title": "Malicious View" }
}
}
When exported with --output ./docs, this would write to ./docs/../../../tmp/pwned.puml.
Fix:
Added export.SafeViewKey() which normalizes backslashes to forward slashes and applies filepath.Base() to strip directory components. Applied at all three export paths (export.go, export_diagram.go, export_table.go). The malicious key above now safely resolves to pwned.puml in the output directory.
SEC-016: No Output Directory Boundary Check (Low) — NEW
Added 2026-03-30
Affected files:
-
cmd/bausteinsicht/export_diagram.go:34—--outputflag -
cmd/bausteinsicht/export_table.go:35—--outputflag -
cmd/bausteinsicht/export.go:42—--outputflag
Description:
The --output flag accepts arbitrary paths including absolute paths and paths with .. sequences.
Combined with SEC-015 (view key traversal), this expands the write surface.
Risk: Low for direct CLI usage (operator controls their own flags). Medium if the CLI is wrapped in automation with untrusted input.
Recommendation:
Document that --output is a fully trusted CLI flag. Optionally validate that the path is relative.
SEC-017: govulncheck Internal Error (Info) — FIXED
Added 2026-03-30, fixed 2026-04-27
Description:
govulncheck ./… failed with:
internal error: package "golang.org/x/sys/unix" without types was imported from "github.com/fsnotify/fsnotify/internal"
This prevented automated vulnerability scanning.
Root cause:
govulncheck v1.1.4 (built with Go 1.22.2) was too old to analyze the internal package structure of fsnotify v1.9.0. The type analysis engine crashed before reaching the vulnerability scan.
Fix:
Updated govulncheck to v1.3.0 (Go 1.25.9) via go install golang.org/x/vuln/cmd/govulncheck@latest. Makefile and Dockerfile already used @latest — the issue was a stale local binary. Scan now passes: 0 vulnerabilities in called code.
SEC-018: Typo-Squatting in Go Module Path and Documentation (Medium) — Fixed
Added 2026-04-27
Description:
The Go module path was registered as github.com/docToolchain/Bauteinsicht (missing "s"), which does not match the actual repository name Bausteinsicht. The README’s go install command and the landing page buttons also used misspelled URLs and/or the wrong GitHub owner (rdmueller instead of docToolchain).
An attacker could register the misspelled path github.com/docToolchain/Bauteinsicht and serve a malicious Go module to anyone following the go install instructions.
Risk:
Medium. Users copying the go install command from the README would fetch from a path that could be claimed by a third party.
Fix:
Renamed the Go module to github.com/docToolchain/Bausteinsicht across the entire codebase (go.mod, all imports, schema URLs, documentation). Corrected landing page URLs to docToolchain/Bausteinsicht.
Not Vulnerable
The following attack vectors were explicitly tested and confirmed not exploitable:
| Attack Vector | Reason |
|---|---|
XXE (XML External Entity) |
|
Billion Laughs / XML Bomb |
Entity expansion does not occur with |
HTML Injection (forward sync) |
User-controlled model strings pass through |
JSON Deserialization Attack |
Go’s |
Command Injection |
|
Privilege Escalation |
No setuid, no capability use, no privileged file paths accessed. |
Supply Chain (Dependencies) |
|
Dependency Status (verified 2026-03-30)
All dependencies at latest versions. No known CVEs. go mod verify — PASS.
| Module | Version | CVEs |
|---|---|---|
|
v1.6.0 |
None |
|
v1.10.2 |
None |
|
v1.9.0 |
None |
|
v1.0.10 |
None |
|
v0.41.0 |
None |
Go stdlib |
1.24.0 |
None called |
Automated Tool Results
Latest run (2026-03-30)
| Tool | Version | Result | Notes |
|---|---|---|---|
|
go1.24.0 |
PASS |
No findings |
|
latest |
PASS |
No findings |
|
latest |
0 issues |
All previous findings resolved or suppressed with justified |
|
latest |
PASS |
No findings (SEC-012 fixes verified) |
|
v1.3.0 |
PASS |
0 vulnerabilities in called code (SEC-017 fixed) |
|
— |
Not installed |
Not available outside devcontainer; no secrets found via manual grep |
|
go1.24.0 |
PASS |
No data races detected |
Prioritized Action Items
Completed
SEC-002 (PR #77), SEC-003 (PR #77), SEC-009 (PR #77), SEC-010 (PR #77), SEC-012 (#235). SEC-004, SEC-007, SEC-011 verified fixed 2026-03-30 (see individual findings above).
Remaining Open
| Priority | ID | Action |
|---|---|---|
Medium |
SEC-014 |
Deferred: upstream projects (Claude CLI, Kiro, human CLI) don’t publish checksums. Revisit when checksum support becomes available. |
Next Review
The next security review should:
-
Revisit SEC-014 (Dockerfile checksum verification) — check if upstream projects now publish checksums
-
Check for new CVEs in dependencies (govulncheck now functional — SEC-017 fixed)
-
Verify
gitleaksruns in CI (currently only available in devcontainer) -
Review any new CLI commands or features added since this review
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.