{"record":{"id":"8be7e042ab41fe9f","repo":"hashicorp/nomad","slug":"invalid-security-opt-q","errorCode":null,"errorMessage":"invalid security_opt: %q","messagePattern":"invalid security_opt: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/docker/driver.go","lineNumber":914,"sourceCode":"\t// Empty string maps to `rprivate` for backwards compatibility in restored\n\t// older tasks, where mount propagation will not be present.\n\t\"\":                                     \"rprivate\",\n\tnstructs.VolumeMountPropagationPrivate: \"rprivate\",\n\tnstructs.VolumeMountPropagationHostToTask:    \"rslave\",\n\tnstructs.VolumeMountPropagationBidirectional: \"rshared\",\n}\n\n// takes a local seccomp daemon, reads the file contents for sending to the daemon\n// this code modified slightly from the docker CLI code\n// https://github.com/docker/cli/blob/8ef8547eb6934b28497d309d21e280bcd25145f5/cli/command/container/opts.go#L840\nfunc parseSecurityOpts(securityOpts []string) ([]string, error) {\n\tfor key, opt := range securityOpts {\n\t\tcon := strings.SplitN(opt, \"=\", 2)\n\t\tif len(con) == 1 && con[0] != \"no-new-privileges\" {\n\t\t\tif strings.Contains(opt, \":\") {\n\t\t\t\tcon = strings.SplitN(opt, \":\", 2)\n\t\t\t} else {\n\t\t\t\treturn securityOpts, fmt.Errorf(\"invalid security_opt: %q\", opt)\n\t\t\t}\n\t\t}\n\t\tif con[0] == \"seccomp\" && con[1] != \"unconfined\" {\n\t\t\tf, err := os.ReadFile(con[1])\n\t\t\tif err != nil {\n\t\t\t\treturn securityOpts, fmt.Errorf(\"opening seccomp profile (%s) failed: %v\", con[1], err)\n\t\t\t}\n\t\t\tb := bytes.NewBuffer(nil)\n\t\t\tif err := json.Compact(b, f); err != nil {\n\t\t\t\treturn securityOpts, fmt.Errorf(\"compacting json for seccomp profile (%s) failed: %v\", con[1], err)\n\t\t\t}\n\t\t\tsecurityOpts[key] = fmt.Sprintf(\"seccomp=%s\", b.Bytes())\n\t\t}\n\t}\n\n\treturn securityOpts, nil\n}\n","sourceCodeStart":896,"sourceCodeEnd":932,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/drivers/docker/driver.go#L896-L932","documentation":"parseSecurityOpts validates the task's security_opt entries before passing them to the Docker daemon. Each option must be either key=value, the bare token 'no-new-privileges', or contain a ':' separator. An option in none of those forms is rejected here so the daemon never sees malformed security options.","triggerScenarios":"config.security_opt contains a bare token without '=' or ':' (other than no-new-privileges), e.g. \"seccomp\" alone, \"apparmor\", or a misspelled flag like \"no new privileges\". Raised in createContainerConfig.","commonSituations":"Hand-writing security opts copied from docker CLI docs incorrectly (docker accepts some bare flags the parser here doesn't), forgetting the '=profile' part of seccomp=..., or whitespace mangling in HCL arrays.","solutions":["Use key=value form, e.g. \"seccomp=unconfined\" or \"apparmor=docker-default\".","Use colon form where applicable, e.g. \"label:disable\".","For privilege escalation control, use the exact token \"no-new-privileges\".","Remove options that carry no value if they are not needed."],"exampleFix":"// before\nconfig { security_opt = [\"seccomp\"] }\n// after\nconfig { security_opt = [\"seccomp=unconfined\"] }","handlingStrategy":"validation","validationCode":"func validSecurityOpt(opt string) bool {\n\tif opt == \"no-new-privileges\" { return true }\n\treturn strings.Contains(opt, \"=\") || strings.Contains(opt, \":\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always write security opts as key=value or key:value","Keep a reviewed allowlist of security_opt values","Lint job specs for bare tokens like 'seccomp' or 'apparmor'"],"tags":["docker","security","config-parsing"],"backgroundTag":"invalid-security-opt","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}