hashicorp/nomad · error
invalid security_opt: %q
Error message
invalid security_opt: %q
What it means
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.
Source
Thrown at drivers/docker/driver.go:914
// Empty string maps to `rprivate` for backwards compatibility in restored
// older tasks, where mount propagation will not be present.
"": "rprivate",
nstructs.VolumeMountPropagationPrivate: "rprivate",
nstructs.VolumeMountPropagationHostToTask: "rslave",
nstructs.VolumeMountPropagationBidirectional: "rshared",
}
// takes a local seccomp daemon, reads the file contents for sending to the daemon
// this code modified slightly from the docker CLI code
// https://github.com/docker/cli/blob/8ef8547eb6934b28497d309d21e280bcd25145f5/cli/command/container/opts.go#L840
func parseSecurityOpts(securityOpts []string) ([]string, error) {
for key, opt := range securityOpts {
con := strings.SplitN(opt, "=", 2)
if len(con) == 1 && con[0] != "no-new-privileges" {
if strings.Contains(opt, ":") {
con = strings.SplitN(opt, ":", 2)
} else {
return securityOpts, fmt.Errorf("invalid security_opt: %q", opt)
}
}
if con[0] == "seccomp" && con[1] != "unconfined" {
f, err := os.ReadFile(con[1])
if err != nil {
return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
}
b := bytes.NewBuffer(nil)
if err := json.Compact(b, f); err != nil {
return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
}
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
}
}
return securityOpts, nil
}
View on GitHub (pinned to 482b49bf1a)
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.
Example fix
// before
config { security_opt = ["seccomp"] }
// after
config { security_opt = ["seccomp=unconfined"] } Defensive patterns
Strategy: validation
Validate before calling
func validSecurityOpt(opt string) bool {
if opt == "no-new-privileges" { return true }
return strings.Contains(opt, "=") || strings.Contains(opt, ":")
} Prevention
- 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'
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- running container as ContainerAdmin is unsafe; change the co
- invalid docker volume %q: %v
- volumes are not enabled; cannot mount host path: %q
- opening seccomp profile (%s) failed: %v
- requested runtime %q is not allowed
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/8be7e042ab41fe9f.
Report an issue: GitHub.