{"record":{"id":"f145305fe4ae2e8d","repo":"Crosstalk-Solutions/project-nomad","slug":"guard-warnings-join","errorCode":null,"errorMessage":"guard.warnings.join(' ')","messagePattern":"guard\\.warnings\\.join\\(' '\\)","errorType":"http","errorClass":null,"httpStatus":409,"severity":"warning","filePath":"admin/app/controllers/system_controller.ts","lineNumber":407,"sourceCode":"        if (duplicateHostPorts.length) {\n            return response.status(422).send({\n                success: false,\n                message: `Duplicate host port(s): ${duplicateHostPorts.join(', ')}. Each host port can map to only one container.`,\n            })\n        }\n\n        // Security guardrails: hard-block dangerous bind mounts / malformed images regardless of\n        // force; surface overridable warnings (risky paths, untrusted/moving-tag images) unless forced.\n        const guard = evaluateCustomApp({ image: payload.image, volumes: payload.volumes })\n        if (guard.blocked.length) {\n            return response.status(422).send({\n                success: false,\n                message: guard.blocked.join(' '),\n                blocked: guard.blocked,\n            })\n        }\n        if (!payload.force && guard.warnings.length) {\n            return response.status(409).send({\n                success: false,\n                message: guard.warnings.join(' '),\n                warnings: guard.warnings,\n            })\n        }\n\n        // Advisory preflight: surface port conflicts before creating the record so a failed\n        // install doesn't leave a phantom card. The user can re-submit with force=true to override.\n        if (!payload.force && hostPorts.length) {\n            const { conflicts } = await this.dockerService.checkPortConflicts(hostPorts)\n            if (conflicts.length) {\n                return response.status(409).send({\n                    success: false,\n                    message: `Port conflict: ${conflicts\n                        .map((c) => `${c.port} (in use by ${c.usedBy})`)\n                        .join(', ')}.`,\n                    portConflicts: conflicts,\n                })","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/controllers/system_controller.ts#L389-L425","documentation":"A 409 from createCustomApp when evaluateCustomApp produced warnings (risky host paths, untrusted or moving-tag images like :latest) and the request did not set force=true. Unlike blocks, warnings are overridable.","triggerScenarios":"POST create-custom-app without force=true while specifying an image with a mutable tag (latest) or volumes touching risky paths (e.g. /home, /etc subpaths).","commonSituations":"Installing a community image tagged :latest, or mounting broad host directories that risk data exposure.","solutions":["Review guard.warnings in the response body","Pin the image to an immutable tag/digest or narrow the mounted path, then re-submit","If the risk is accepted, re-submit the identical request with force: true"],"exampleFix":"// before\n{ image: 'someapp:latest', ... }\n// after\n{ image: 'someapp:1.2.3', ... }","handlingStrategy":"validation","validationCode":"if (image.endsWith(':latest')) requireForceConfirmation();","typeGuard":null,"tryCatchPattern":"try { await create(payload); } catch (e) { if (e.status === 409 && e.warnings) confirmForce(e.warnings); }","preventionTips":["Prefer pinned tags over latest","Handle 409+warnings by asking the user to confirm force"],"tags":["security","warnings","force-override","docker"],"backgroundTag":"image-tag-warning-force-required","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}