diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 6e7153b..b501f5a 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -56,7 +56,7 @@ {"id":"ifcurl-7mg","title":"auth.py: token appears in git process arguments, visible in process list","description":"inject_token() embeds the authentication token into the HTTPS remote URL as 'https://\u003ctoken\u003e@host/path'. This URL is passed directly to git clone/fetch as a command-line argument, which means the token is visible in the process list (ps aux) while the git command runs. On a shared server this is a security leak. The standard mitigation is to use GIT_ASKPASS or a credential helper that supplies the token via stdin/environment rather than via the URL. Should be documented as a known limitation at minimum.","status":"closed","priority":3,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T10:41:29Z","created_by":"Bruno Postle","updated_at":"2026-04-24T15:52:13Z","started_at":"2026-04-24T14:47:34Z","closed_at":"2026-04-24T15:52:13Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-z4f","title":"render.py: concurrent requests each spawn full CPU-count process pools","description":"render() calls ifcopenshell.geom.iterator(settings, model, multiprocessing.cpu_count(), ...). In a multi-request service scenario, N concurrent renders each spawn cpu_count worker processes, so a 16-core machine handling 4 simultaneous requests spawns 64 processes. This can saturate the machine and is effectively a DoS amplifier. Consider capping the worker count (e.g. max 2 or 4 per request), or using a shared process pool at the service level.","status":"closed","priority":3,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T10:41:16Z","created_by":"Bruno Postle","updated_at":"2026-04-24T15:52:14Z","started_at":"2026-04-24T14:47:54Z","closed_at":"2026-04-24T15:52:14Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-5or","title":"README.md: library usage example doesn't show temp file pattern for ifcopenshell.open()","description":"The 'Use as a library' section shows 'model = ifcopenshell.open(...) # write ifc_bytes to tmp file first' with a comment but no actual code. ifcopenshell.open() requires a file path, not bytes. A new user following the README will be stuck. The example should show the tempfile.mkstemp pattern used in _load_model() in service.py, or at least reference that function.","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T10:41:04Z","created_by":"Bruno Postle","updated_at":"2026-04-24T15:52:14Z","started_at":"2026-04-24T14:47:55Z","closed_at":"2026-04-24T15:52:14Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} -{"id":"ifcurl-dyy","title":"viewer.html: BCF export from viewer omits component GUIDs — undocumented","description":"The viewer's client-side BCF export cannot include component GUIDs because it has no access to the IFC model's resolved GlobalIds. The service POST /bcf endpoint resolves the selector server-side and produces proper BCF with \u003cSelection\u003e or \u003cVisibility/Exceptions\u003e elements. This discrepancy is not documented anywhere. The viewer's BCF button should either show a note ('GUIDs not resolved — use /bcf API for full component selection') or redirect to the service endpoint when available.","status":"open","priority":3,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-04-24T10:40:49Z","created_by":"Bruno Postle","updated_at":"2026-04-24T10:40:49Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"id":"ifcurl-dyy","title":"viewer.html: BCF export from viewer omits component GUIDs — undocumented","description":"The viewer's client-side BCF export cannot include component GUIDs because it has no access to the IFC model's resolved GlobalIds. The service POST /bcf endpoint resolves the selector server-side and produces proper BCF with \u003cSelection\u003e or \u003cVisibility/Exceptions\u003e elements. This discrepancy is not documented anywhere. The viewer's BCF button should either show a note ('GUIDs not resolved — use /bcf API for full component selection') or redirect to the service endpoint when available.","status":"in_progress","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T10:40:49Z","created_by":"Bruno Postle","updated_at":"2026-04-25T11:32:57Z","started_at":"2026-04-25T11:32:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-tq8","title":"No tests for ifcurl cache CLI subcommands","description":"The 'ifcurl cache list', 'ifcurl cache prune', and 'ifcurl cache clear' subcommands in __main__.py have no automated test coverage. _repo_cache_entries(), _evict_if_needed(), and _get_max_cache_bytes() have unit tests in test_git.py, but the CLI layer (_cmd_cache) is untested. Should add tests that exercise the argparse wiring, the output formatting, and the prune logic via subprocess or by calling _cmd_cache() directly with a fake args namespace.","status":"open","priority":3,"issue_type":"task","owner":"bruno@postle.net","created_at":"2026-04-24T10:40:45Z","created_by":"Bruno Postle","updated_at":"2026-04-24T10:40:45Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-ovi","title":"dev/compose.yml uses Forgejo v10, but go.patch targets v13","description":"dev/compose.yml pulls codeberg.org/forgejo/forgejo:10 for local development. The go.patch and ifc_url.go were written and tested against the v13 branch. Running the dev environment with v10 won't have the patched binary or the correct template slots. The compose file should either use the patched v13 build, or document that the stock container is for testing the service/viewer in isolation only, not for testing the Go patch.","status":"closed","priority":3,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-04-24T10:40:35Z","created_by":"Bruno Postle","updated_at":"2026-04-25T09:05:41Z","started_at":"2026-04-25T09:05:21Z","closed_at":"2026-04-25T09:05:41Z","close_reason":"Updated image to v13 and added comment clarifying stock container doesn't include the IFC patch","dependency_count":0,"dependent_count":0,"comment_count":0} {"id":"ifcurl-j35","title":"git.py: silent failure on mutable ref fetch serves stale data without warning","description":"In _open_remote(), when fetching for a mutable ref (branch/HEAD) fails — network error, auth failure, server down — the exception is caught and ignored ('pass # offline — use cached data'). The caller receives stale data with no indication anything went wrong. The render then proceeds, potentially showing outdated geometry. At minimum a log.warning should be emitted. Ideally the service response should include a header (e.g. X-Ifcurl-Cache: stale) so Forgejo or the browser viewer can surface a warning to the user.","status":"closed","priority":3,"issue_type":"bug","owner":"bruno@postle.net","created_at":"2026-04-24T10:40:31Z","created_by":"Bruno Postle","updated_at":"2026-04-25T08:05:18Z","closed_at":"2026-04-25T08:05:18Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/forgejo/README.md b/forgejo/README.md index 77dcdca..5f1f879 100644 --- a/forgejo/README.md +++ b/forgejo/README.md @@ -258,6 +258,12 @@ location /render_diff { proxy_set_header Host $host; proxy_read_timeout 120s; } + +location /bcf { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_read_timeout 120s; +} ``` Without this proxy the "View in 3D" button (Case 1/2) still works — it opens diff --git a/forgejo/custom/public/assets/viewer.html b/forgejo/custom/public/assets/viewer.html index 364eb29..e0e2752 100644 --- a/forgejo/custom/public/assets/viewer.html +++ b/forgejo/custom/public/assets/viewer.html @@ -570,7 +570,11 @@ } // ----------------------------------------------------------------------- - // BCF 2.1 export — build zip client-side and trigger download. + // BCF 2.1 export. + // With a selector: delegates to POST /bcf so the service resolves component + // GUIDs server-side. Fails visibly if the service is unreachable — no + // silent fallback to a GUIDless file. + // Without a selector: builds the zip client-side (camera + clips only). // ----------------------------------------------------------------------- async function generateBcf(title, comment) { if (threeCamera) syncCameraUrl(threeCamera); @@ -590,6 +594,30 @@ const selector = qs.get("selector") || ""; const visibility = qs.get("visibility") || "highlight"; + if (selector) { + try { + const resp = await fetch("/bcf", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url: src, title: title || "IFC View", comment }), + }); + if (!resp.ok) { + const detail = await resp.text().catch(() => resp.statusText); + statusEl.textContent = `BCF export failed: ${detail}`; + return; + } + const blob = await resp.blob(); + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = "view.bcf"; + a.click(); + URL.revokeObjectURL(a.href); + } catch (_) { + statusEl.textContent = "BCF export requires the ifcurl service — /bcf not reachable"; + } + return; + } + const topicGuid = crypto.randomUUID(); const vpGuid = cam ? crypto.randomUUID() : null; const now = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");