diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 297ca4c..ecf87b8 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -6,6 +6,31 @@ Console tool for CNC program transfer and file management. Fanuc FOCAS base, exp ## Current Milestone +**v0.7 Build Hardening** (v0.7.0) +Status: 🚧 In Progress +Phases: 0 of 2 complete + +Theme: Formalizzare pipeline build sicura β€” obfuscation Release (ConfuserEx), license bypass Debug, fix review findings v0.6 (ISS-012..017). + +| Phase | Name | Plans | Status | Completed | +|-------|------|-------|--------|-----------| +| 17 | build-pipeline | TBD | Not started | - | +| 18 | review-fixes | TBD | Not started | - | + +### Phase 17: build-pipeline + +Focus: ConfuserEx Release obfuscation pipeline, Debug license bypass (`#if !DEBUG`), `tools/` gitignore, docs build process. +Plans: TBD (defined during /paul:plan) + +### Phase 18: review-fixes + +Focus: Chiudere review findings v0.6 β€” ISS-012 (Fagor ReadActiveProgram), ISS-013 (FanucProgram coupling), ISS-014 (ResolvePath NRE), ISS-015 (file encoding UTF-8 BOM), ISS-016 (validator path fallback), ISS-017 (Ping swallow log). +Plans: TBD (defined during /paul:plan) + +--- + +## Past Milestones + **v0.1 Multi-CN Release** (v0.1.0) Status: βœ… Complete Phases: 5 of 5 complete @@ -227,4 +252,4 @@ Completed: 2026-05-18 - [x] 16-02: Fagor fixup post-review (validator extension fix, Γ± escape, lock tests Siemens/Heidenhain, factory test) --- -*Roadmap updated: 2026-05-25 β€” v0.6 milestone complete; Phase 16 (fagor-ftp) 16-01 + 16-02 complete.* +*Roadmap updated: 2026-05-25 β€” v0.7 Build Hardening milestone created; Phases 17 (build-pipeline) + 18 (review-fixes) scaffolded.* diff --git a/.paul/STATE.md b/.paul/STATE.md index ef201e1..ae48b28 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,15 +5,15 @@ See: .paul/PROJECT.md (updated 2026-05-18) **Core value:** Machinists transfer CNC programs to/from any controller without manual steps or internet -**Current focus:** v0.6 Fagor Support complete; ready for v0.7 planning +**Current focus:** v0.7 Build Hardening β€” formalize obfuscation pipeline + close v0.6 review findings ## Current Position -Milestone: v0.6 Fagor Support β€” **Complete** -Phase: 16 of 16 (fagor-ftp) β€” Complete -Plan: 16-02 complete -Status: Milestone complete β€” ready for v0.7 planning -Last activity: 2026-05-25 β€” Phase 16-02 fixup complete (post-review); v0.6 milestone complete +Milestone: v0.7 Build Hardening β€” **In Progress** +Phase: 17 of 18 (build-pipeline) β€” Not started +Plan: Not started +Status: Ready to plan Phase 17 +Last activity: 2026-05-25 β€” Milestone v0.7 created (phases 17 build-pipeline, 18 review-fixes) Progress: - Milestone v0.1: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 100% (complete) @@ -22,14 +22,15 @@ Progress: - Milestone v0.4: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 100% (complete) - Milestone v0.5: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 100% (complete) - Milestone v0.6: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 100% (complete) -- Phase 15: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 100% -- Phase 16: [β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ] 100% +- Milestone v0.7: [β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘] 0% +- Phase 17: [β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘] 0% +- Phase 18: [β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘] 0% ## Loop Position ``` PLAN ──▢ APPLY ──▢ UNIFY - βœ“ βœ“ βœ“ [Loop complete β€” v0.6 milestone complete] + β—‹ β—‹ β—‹ [Ready for Phase 17 PLAN] ``` ## What Was Built (v0.6 Fagor Support β€” complete) @@ -214,10 +215,17 @@ Key open items: None β€” v0.5 complete. ### Git State -Last commit: `60ce1c9` β€” feat(15-logging): NLog structured logging β€” all output through NLog (v0.5 complete) +Last commit: `68c5a55` β€” chore(paul): close v0.6 Fagor Support milestone, log ISS-012..017 LicenseGenerator repo: commit `2ef68dd` β€” feat: initial LicenseGenerator Branch: main +Uncommitted (pre-Phase 17 work, to fold into 17-01): +- Program.cs β€” `#if !DEBUG` wrap license check +- NcProgramManager.csproj β€” Obfuscate target (Release-only) +- NcProgramManager.crproj β€” ConfuserEx config +- tools/ConfuserEx/ β€” CLI 1.6.0 (vendored; consider gitignore) +- 4 test files β€” `result.Error` β†’ `string.Join("; ", result.Errors)` fix + ## Codebase Mapped Date: 2026-05-18 @@ -225,9 +233,9 @@ Documents: `.paul/codebase/` (7 files β€” STACK, ARCHITECTURE, STRUCTURE, CONVEN ## Session Continuity -Last session: 2026-05-18 -Stopped at: v0.5 milestone complete β€” Phase 15 both plans done -Next action: /paul:milestone for v0.6 +Last session: 2026-05-25 +Stopped at: Milestone v0.7 Build Hardening created (phases 17 + 18 scaffolded) +Next action: /paul:plan for Phase 17 (build-pipeline) Resume file: .paul/ROADMAP.md --- diff --git a/.paul/paul.json b/.paul/paul.json index 3be91ee..3b6a00c 100644 --- a/.paul/paul.json +++ b/.paul/paul.json @@ -1,15 +1,15 @@ { "name": "NcProgramManager", - "version": "0.6.0", + "version": "0.7.0", "milestone": { - "name": "v0.6 Fagor Support", - "version": "0.6.0", - "status": "complete" + "name": "v0.7 Build Hardening", + "version": "0.7.0", + "status": "in_progress" }, "phase": { - "number": 16, - "name": "fagor-ftp", - "status": "complete" + "number": 17, + "name": "build-pipeline", + "status": "not_started" }, "loop": { "plan": null, @@ -17,7 +17,7 @@ }, "timestamps": { "created_at": "2026-05-13T00:00:00Z", - "updated_at": "2026-05-25T00:00:00Z" + "updated_at": "2026-05-25T21:40:00Z" }, "satellite": { "groom": true diff --git a/BUG_ANALYSIS.md b/BUG_ANALYSIS.md new file mode 100644 index 0000000..198e82a --- /dev/null +++ b/BUG_ANALYSIS.md @@ -0,0 +1,120 @@ +# Analisi Bug & Problemi - nc_program_manager + +Data: 2026-05-25 +Scope: progetto `nc_program_manager` (esclusi test, obj/, Properties/) + +## πŸ”΄ Critici (security) + +### FTP plaintext - credenziali esposte (no TLS) +- `Cnc/Fagor/FagorFtpClient.cs:60` - `ftp://` URI con NetworkCredential, nessun supporto SSL/TLS +- `Cnc/Mitsubishi/MitsubishiFtpClient.cs:60` - stesso pattern +- `Cnc/Siemens/SiemensFtpClient.cs:60` - stesso pattern + +**Fix**: usare `FtpWebRequest.EnableSsl = true` o migrare a `ftps://` / SFTP. Se rete trusted, documentare requirement esplicito. + +### LSV2 credenziali cleartext su TCP raw +- `Cnc/Heidenhain/Lsv2Client.cs:105` - username+password concatenati con null terminator, inviati non cifrati su socket TCP. + +**Fix**: documentare come limitazione protocollo LSV2 oppure incapsulare in TLS tunnel. + +### Path traversal - no validation prima FTP delete +- `Cnc/Fagor/FagorMachine.cs:121` - `DeleteProgramAsync` passa path utente direttamente a `_ftp.DeleteFile()` +- `Cnc/Mitsubishi/MitsubishiMachine.cs:117` - stesso pattern +- `Cnc/Siemens/SiemensMachine.cs:117` - stesso pattern + +**Fix**: validare path (no `..`, no path absoluti fuori dir consentita) prima di chiamata FTP. + +### License bypass in DEBUG build +- `Program.cs:47` - check licenza saltato in DEBUG conditional compile. + +**Fix**: rimuovere `#if DEBUG` skip, o se necessario per dev limitare a `[Conditional]` log non funzionale. + +### License read exception swallowed +- `Licensing/LicenseFile.cs:20` - errori lettura file licenza silenziosamente catturati, bypassano errori. + +**Fix**: log + rethrow o ritorno result con error info. + +--- + +## 🟠 Bug funzionali + +### Program.Replace() return ignorato +- `FanucProgram.cs:176` - `Program.Replace(...)` chiamato senza assegnazione. String immutable, nessun effetto. + +**Fix**: `Program = Program.Replace(...)`. + +### Off-by-one in IsoProgramFormatter (commenti ISO) +- `Cnc/Fanuc/IsoProgramFormatter.cs:19` - `if (closePar > 0)` deve essere `>= 0` per gestire commento a inizio riga +- `Cnc/Fanuc/IsoProgramFormatter.cs:41` - `if (openPar > 0 && closePar > openPar)` deve essere `openPar >= 0` + +**Fix**: cambiare `> 0` in `>= 0` quando si controlla risultato `IndexOf`. + +### FanucDialectDetector - comparison string inconsistente +- `Cnc/Fanuc/FanucDialectDetector.cs:71` - `if (trimmed == "0" || t == " 0")` usa `t` non trimmato a destra mentre `trimmed` a sinistra. Edge case mancato. + +**Fix**: uniformare uso di `trimmed` o documentare intent. + +--- + +## 🟑 NullReferenceException + +### ArgParser - Substring su null +- `ArgParser.cs:23` - `arguments.Find(...)` ritorna `null` se argomento `-azione=` mancante, poi `.Substring(8)` solleva NRE. + +**Fix**: null check prima di Substring, o usare `?.Substring(8)` con default. + +### FanucProgram - field Program nullable usato senza check +- `FanucProgram.cs:116` - `Standardize()` chiama `Program.IndexOf` / `Program.Replace` senza guardia null +- `FanucProgram.cs:188` - `ToString()` concatena `Program`, `ToolOffsetData`, `WorkZeroOffsetData` potenzialmente null + +**Fix**: null check all'ingresso metodi o inizializzazione a `string.Empty` nel costruttore. + +--- + +## 🟑 Resource leak / robustness + +### FanucMachine - buffer access fragile +- `Cnc/Fanuc/FanucMachine.cs:407` - `buffer[len-1]` su `num` ritornato da FOCAS user-controlled. Se `len == UploadBufferSize + 1` raggiunge boundary. + +**Fix**: clamp `len` su `Math.Min(len, buffer.Length)`. + +### Lsv2Client - TcpClient/Stream non disposti se Login fallisce +- `Cnc/Heidenhain/Lsv2Client.cs:43` - `_stream` assegnato, poi `Login()` puΓ² throware; manca try/finally per cleanup. + +**Fix**: wrap connect+login in try/catch con dispose esplicito su exception. + +### Lsv2Client - ReadFrame ritorna dati parziali su EOF +- `Cnc/Heidenhain/Lsv2Client.cs:163` - loop esce su `n == 0` (EOF/disconnect) ma processa buffer incompleto, ritornando frame parziale. + +**Fix**: throw o ritorno error se `n == 0` prima di lunghezza attesa. + +--- + +## 🟒 Nit + +### DateTime.Now per timestamp +- `Cnc/Models/CncError.cs:17` - `DateTime.Now` soggetto a drift/DST. Usare `DateTime.UtcNow` o `DateTimeOffset.UtcNow`. + +--- + +## Riepilogo + +| SeveritΓ  | Count | +|----------|-------| +| Critici security | 8 | +| Bug funzionali | 4 | +| NRE | 3 | +| Resource/robustness | 3 | +| Nit | 1 | +| **Totale** | **19** | + +## PrioritΓ  fix consigliata + +1. TLS su FTP + LSV2 (o documenta requirement rete trusted) +2. Path validation prima delete FTP +3. Rimuovi DEBUG bypass licenza +4. Null checks su `FanucProgram.Program` e `ArgParser` +5. Fix off-by-one `IsoProgramFormatter` +6. Fix `FanucProgram.Replace()` assignment +7. Dispose pattern `Lsv2Client` +8. Logging exception `LicenseFile` diff --git a/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs index 9b15d73..6a07334 100644 --- a/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs +++ b/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs @@ -120,7 +120,7 @@ namespace NcProgramManager.Tests.Integration using (var machine = new FagorMachine(config)) { var result = machine.ConnectAsync().GetAwaiter().GetResult(); - Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error); + Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors)); } } } diff --git a/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs index 50f2457..035225f 100644 --- a/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs +++ b/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs @@ -94,7 +94,7 @@ namespace NcProgramManager.Tests.Integration using (var machine = new HeidenhainMachine(config)) { var result = machine.ConnectAsync().GetAwaiter().GetResult(); - Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error); + Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors)); } } } diff --git a/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs index 78a4aa0..54fc035 100644 --- a/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs +++ b/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs @@ -111,7 +111,7 @@ namespace NcProgramManager.Tests.Integration using (var machine = new MitsubishiMachine(config)) { var result = machine.ConnectAsync().GetAwaiter().GetResult(); - Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error); + Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors)); } } } diff --git a/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs index 918e85b..a07d0c4 100644 --- a/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs +++ b/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs @@ -119,7 +119,7 @@ namespace NcProgramManager.Tests.Integration using (var machine = new SiemensMachine(config)) { var result = machine.ConnectAsync().GetAwaiter().GetResult(); - Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error); + Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors)); } } } diff --git a/NcProgramManager.csproj b/NcProgramManager.csproj index fb608d8..de1b25a 100755 --- a/NcProgramManager.csproj +++ b/NcProgramManager.csproj @@ -163,4 +163,8 @@ + + + + \ No newline at end of file diff --git a/Program.cs b/Program.cs index 9e246c7..774e1a3 100755 --- a/Program.cs +++ b/Program.cs @@ -35,6 +35,7 @@ namespace NcProgramManager return 0; } +#if !DEBUG string licFingerprint = new MachineFingerprint().GetFingerprint(); string licBase64; string licPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "license.lic"); @@ -45,6 +46,9 @@ namespace NcProgramManager VediErrore(-100, inputArgs.debugMode); return -1; } +#else + _log.Info("DEBUG build - licenza non richiesta"); +#endif bool needsIp = inputArgs.manufacturer != CncManufacturer.Fanuc || inputArgs.nodoHssb == -1; if (inputArgs.azione == "" || (needsIp && string.IsNullOrEmpty(inputArgs.ip)) || inputArgs.pathLocaleProgramma == "")