rules

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 12, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package rules provides the built-in lint rules bundled with decolint. See linter.Rule for the fields a rule declares.

To add a new rule, declare a linter.Rule value in a new file in this package and register it in RegisterRules.

Index

Constants

This section is empty.

Variables

View Source
var IDDirMismatch = &linter.Rule{
	ID:          "id-dir-mismatch",
	Description: `disallow a Feature's or Template's "id" that does not match the name of its containing directory`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Feature, linter.Template},
	Paths:       []string{"/id"},
	Check:       checkIDDirMismatch,
}

IDDirMismatch reports a Feature's or Template's "id" property when it does not match the name of the directory containing its metadata file, per the Dev Container Features/Templates convention.

View Source
var InvalidSemver = &linter.Rule{
	ID:          "invalid-semver",
	Description: `disallow a Feature's or Template's "version" that is not a valid semantic version`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Feature, linter.Template},
	Paths:       []string{"/version"},
	Check:       checkInvalidSemver,
}

InvalidSemver reports a Feature's or Template's "version" property when its value is not a valid semantic version, per the Dev Container Features/Templates specification, which requires "version" to follow the semver.org format.

View Source
var MissingBuildDockerfile = &linter.Rule{
	ID:          "missing-build-dockerfile",
	Description: `disallow a devcontainer.json "build" object that is missing "dockerfile"`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{"/build"},
	Check:       checkMissingBuildDockerfile,
}

MissingBuildDockerfile reports a devcontainer.json "build" object that does not set "dockerfile", leaving no way to know which Dockerfile to build.

View Source
var MissingComposeService = &linter.Rule{
	ID:          "missing-compose-service",
	Description: `disallow a devcontainer.json that sets "dockerComposeFile" without "service"`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{""},
	Check:       checkMissingComposeService,
}

MissingComposeService reports a devcontainer.json that sets "dockerComposeFile" without also setting "service", leaving the tool no way to know which compose service to attach to.

View Source
var MissingContainerDef = &linter.Rule{
	ID:          "missing-container-def",
	Description: `disallow a devcontainer.json that defines none of "image", "build", or "dockerComposeFile"`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{""},
	Check:       checkMissingContainerDef,
}

MissingContainerDef reports a devcontainer.json that defines none of "image", "build", or "dockerComposeFile", leaving no way to build a container.

View Source
var MissingRequiredProps = &linter.Rule{
	ID:          "missing-required-props",
	Description: `disallow a Feature's or Template's metadata that is missing a required property ("id", "version", or "name")`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Feature, linter.Template},
	Paths:       []string{""},
	Check:       checkMissingRequiredProps,
}

MissingRequiredProps reports a Feature's or Template's metadata when it is missing a required property ("id", "version", or "name").

View Source
var MissingWorkspaceMountFolder = &linter.Rule{
	ID:          "missing-workspace-mount-folder",
	Description: `disallow a devcontainer.json using "image" or "build" that sets only one of "workspaceMount" or "workspaceFolder"`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{""},
	Check:       checkMissingWorkspaceMountFolder,
}

MissingWorkspaceMountFolder reports a devcontainer.json that uses "image" or "build" and sets only one of "workspaceMount" or "workspaceFolder", leaving the tool unable to tell where the overridden mount lands inside the container.

View Source
var NoAppPort = &linter.Rule{
	ID:          "no-app-port",
	Description: `disallow the legacy "appPort" property in favor of "forwardPorts"`,
	Category:    linter.CategoryStyle,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{"/appPort"},
	Check:       checkNoAppPort,
}

NoAppPort reports the legacy "appPort" property. It only supports statically publishing ports at container-creation time; "forwardPorts" is the modern replacement and forwards ports dynamically without requiring the container to be recreated.

View Source
var NoBindMount = &linter.Rule{
	ID:          "no-bind-mount",
	Description: `disallow "bind" type entries in "mounts", which GitHub Codespaces silently ignores except for the Docker socket`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Platforms:   []linter.Platform{linter.PlatformCodespaces},
	Paths:       []string{"/mounts/*"},
	Check:       checkNoBindMount,
}

NoBindMount reports "mounts" entries that use the "bind" mount type. The Dev Container spec allows bind mounts, but GitHub Codespaces silently ignores them, except for a mount whose source is the Docker socket, so other bind mounts have no effect there.

View Source
var NoCapAddAll = &linter.Rule{
	ID:          "no-cap-add-all",
	Description: `disallow granting all Linux capabilities via an "ALL" entry in the "capAdd" property, or a "--cap-add=ALL" entry in a devcontainer.json's "runArgs"`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer, linter.Feature},
	Paths:       []string{"/capAdd/*", "/runArgs"},
	Check:       checkNoCapAddAll,
}

NoCapAddAll reports a devcontainer.json or devcontainer-feature.json that grants every Linux capability to the container, either via an "ALL" entry in the "capAdd" property or, in a devcontainer.json, a "--cap-add=ALL" entry in "runArgs". Granting all capabilities gives the container far more privilege than most workloads need, which is a significant security risk.

View Source
var NoDockerSocketMount = &linter.Rule{
	ID:          "no-docker-socket-mount",
	Description: `disallow bind-mounting the host's Docker socket via a devcontainer.json's "mounts" or "runArgs", which grants the container root-equivalent control over the host`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{"/mounts/*", "/runArgs/*"},
	Check:       checkNoDockerSocketMount,
}

NoDockerSocketMount reports a devcontainer.json that bind-mounts the host's Docker daemon socket into the container, either via a "mounts" entry or a "-v"/"--volume"/"--mount" entry in "runArgs". Anything with access to the socket can control the host's Docker daemon, which is effectively root-equivalent access to the host.

View Source
var NoHostPortFormat = &linter.Rule{
	ID:          "no-host-port-format",
	Description: `disallow "host:port" entries in "forwardPorts" and "portsAttributes", which GitHub Codespaces does not support`,
	Category:    linter.CategoryCorrectness,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Platforms:   []linter.Platform{linter.PlatformCodespaces},
	Paths:       []string{"/forwardPorts/*", "/portsAttributes/*"},
	Check:       checkNoHostPortFormat,
}

NoHostPortFormat reports "forwardPorts" entries and "portsAttributes" keys written in "host:port" format. The Dev Container spec allows that format, but GitHub Codespaces only supports a bare port number in either property.

View Source
var NoImageLatest = &linter.Rule{
	ID:          "no-image-latest",
	Description: `disallow container images without an explicit tag or with the "latest" tag`,
	Category:    linter.CategoryReproducibility,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{"/image"},
	Check:       checkNoImageLatest,
}

NoImageLatest reports the "image" property when it references a container image without an explicit tag or with the "latest" tag. Such references are not reproducible: the image they resolve to changes over time.

View Source
var NoPrivilegedContainer = &linter.Rule{
	ID:          "no-privileged-container",
	Description: `disallow running the container in privileged mode via the "privileged" property or a "--privileged" entry in "runArgs"`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer, linter.Feature},
	Paths:       []string{"/privileged", "/runArgs/*"},
	Check:       checkNoPrivilegedContainer,
}

NoPrivilegedContainer reports a devcontainer.json or devcontainer-feature.json that runs the container in privileged mode, either via the "privileged" property or, in a devcontainer.json, a "--privileged" entry in "runArgs". Privileged mode disables the container's isolation from the host, which is a significant security risk.

View Source
var NoSeccompOverride = &linter.Rule{
	ID:          "no-seccomp-override",
	Description: `disallow overriding the container runtime's default seccomp profile via a devcontainer.json's or Feature's "securityOpt" property, or a "--security-opt seccomp=..." entry in a devcontainer.json's "runArgs"`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer, linter.Feature},
	Paths:       []string{"/securityOpt/*", "/runArgs/*"},
	Check:       checkNoSeccompOverride,
}

NoSeccompOverride reports a devcontainer.json or devcontainer-feature.json that overrides the container runtime's default seccomp profile, either via the "securityOpt" property or, in a devcontainer.json, a "--security-opt seccomp=..." entry in "runArgs". Unlike NoSeccompUnconfined, which only flags disabling seccomp entirely, this rule flags any override, including a custom profile, since it replaces the runtime's vetted default. It is off by default because many projects legitimately ship a custom profile.

View Source
var NoSeccompUnconfined = &linter.Rule{
	ID:          "no-seccomp-unconfined",
	Description: `disallow disabling seccomp confinement via a devcontainer.json's or Feature's "securityOpt" property, or a "--security-opt seccomp=unconfined" entry in a devcontainer.json's "runArgs"`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer, linter.Feature},
	Paths:       []string{"/securityOpt/*", "/runArgs"},
	Check:       checkNoSeccompUnconfined,
}

NoSeccompUnconfined reports a devcontainer.json or devcontainer-feature.json that disables seccomp confinement, either via the "securityOpt" property or, in a devcontainer.json, a "--security-opt seccomp=unconfined" entry in "runArgs". Running unconfined removes a key layer of kernel-level syscall filtering that isolates the container from the host.

View Source
var PinExtensionVersion = &linter.Rule{
	ID:          "pin-extension-version",
	Description: `disallow a "customizations.vscode.extensions" entry without an explicit pinned version`,
	Category:    linter.CategoryReproducibility,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Platforms:   []linter.Platform{linter.PlatformVSCode, linter.PlatformCodespaces},
	Paths:       []string{"/customizations/vscode/extensions/*"},
	Check:       checkPinExtensionVersion,
}

PinExtensionVersion reports a "customizations.vscode.extensions" entry that does not pin an explicit version (e.g. "publisher.name@1.2.3"). Without a pinned version, the VS Code Dev Containers extension and GitHub Codespaces always install the latest published version, which is not reproducible.

View Source
var PinFeatureVersion = &linter.Rule{
	ID:          "pin-feature-version",
	Description: `disallow a Feature reference without an explicit version or with the "latest" version`,
	Category:    linter.CategoryReproducibility,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{"/features"},
	Check:       checkPinFeatureVersion,
}

PinFeatureVersion reports a "features" entry whose key references an OCI Feature without an explicit version tag or with the "latest" tag. Such references are not reproducible: the Feature they resolve to changes over time. Local path Features (e.g. "./my-feature") and direct tarball URIs (e.g. "https://.../devcontainer-feature.tgz") have no version tag to pin and are not checked.

View Source
var PinImageDigest = &linter.Rule{
	ID:          "pin-image-digest",
	Description: `disallow an "image" property that does not pin the image by content digest (e.g. "image@sha256:...")`,
	Category:    linter.CategoryReproducibility,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{"/image"},
	Check:       checkPinImageDigest,
}

PinImageDigest reports the "image" property when it references a container image without a content digest (e.g. "ubuntu@sha256:..."). Unlike NoImageLatest, which only flags a missing or "latest" tag, this rule flags any reference that isn't pinned by digest, since even a fixed tag can later be reassigned to point at a different image. It is off by default because digest-pinning every image is a heavier requirement than most projects want.

View Source
var RequireCapDropAll = &linter.Rule{
	ID:          "require-cap-drop-all",
	Description: `require an "ALL" entry in a devcontainer.json's "--cap-drop=ALL" entry in "runArgs", dropping every Linux capability`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{""},
	Check:       checkRequireCapDropAll,
}

RequireCapDropAll reports a devcontainer.json that does not drop all Linux capabilities, either via an "ALL" entry in the "capDrop" property or a "--cap-drop=ALL" entry in "runArgs". Dropping every capability and adding back only what's needed (e.g. via "capAdd") follows the principle of least privilege. It is off by default because most configs don't set it and enabling it by default would be noisy.

View Source
var RequireNoNewPrivileges = &linter.Rule{
	ID:          "require-no-new-privileges",
	Description: `require "no-new-privileges" to be set via a devcontainer.json's "securityOpt" property, or a "--security-opt no-new-privileges..." entry in "runArgs"`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{""},
	Check:       checkRequireNoNewPrivileges,
}

RequireNoNewPrivileges reports a devcontainer.json that does not set "no-new-privileges", either via the "securityOpt" property or a "--security-opt no-new-privileges..." entry in "runArgs". Without it, processes in the container can gain additional privileges through setuid/setgid binaries. It is off by default because most configs don't set it and enabling it by default would be noisy.

View Source
var RequireNonRoot = &linter.Rule{
	ID:          "require-non-root",
	Description: `require "remoteUser" or, if unset, "containerUser" to be set to a non-root user`,
	Category:    linter.CategorySecurity,
	FileTypes:   []linter.FileType{linter.Devcontainer},
	Paths:       []string{""},
	Check:       checkRequireNonRoot,
}

RequireNonRoot reports a devcontainer.json that does not clearly configure a non-root user. Per the devcontainer.json spec, "remoteUser" is the user any lifecycle script and remote editor/IDE server or terminal session runs as, defaulting to "containerUser" (and, ultimately, the image's own default user) when unset. Both properties are therefore consulted: "remoteUser" is checked first, falling back to "containerUser" only when "remoteUser" is unset. It is off by default because most configs don't set either property and enabling it by default would be noisy.

Functions

func RegisterRules

func RegisterRules(l *linter.Linter, platforms []linter.Platform, overrides Overrides) error

RegisterRules registers the built-in rules whose target platform matches platforms on l, in a deterministic order, at their default severities, unless overrides names a rule's ID or its category, in which case that severity is used instead (see Overrides.SeverityFor).

A rule is registered if it declares no target platforms (applies to all platforms), or if any of the platforms it targets is in platforms. If platforms is empty, only rules with no target platforms are registered.

RegisterRules returns an error if overrides.Rules contains a key that does not match any built-in rule ID, or if overrides.Categories contains a key that does not name a category. An override for a rule that exists but is filtered out by platforms is not an error: overriding a platform-scoped rule that hasn't been enabled is a legitimate no-op, not a typo.

Types

type Overrides added in v0.1.0

type Overrides struct {
	// Categories maps a category name (see linter.ParseCategory), matched case-insensitively, to
	// the severity every rule in that category is registered at, unless Rules overrides that rule
	// individually. A category severity also applies to rules that are off by default.
	Categories map[string]linter.Severity
	// Rules maps a rule ID to the severity that rule is registered at. It takes precedence over
	// Categories.
	Rules map[string]linter.Severity
}

Overrides carries the user-supplied severity overrides that RegisterRules applies on top of the built-in defaults.

func (Overrides) SeverityFor added in v0.1.0

func (o Overrides) SeverityFor(reg Registration) linter.Severity

SeverityFor returns the severity reg is registered at under o: the per-rule override if present, otherwise the override for the rule's category, otherwise reg's default severity.

type Registration added in v0.0.2

type Registration struct {
	// Rule is the built-in rule.
	Rule *linter.Rule
	// DefaultSeverity is the severity Rule is registered at unless overridden. It is derived from
	// Rule.Category (see categoryDefaultSeverities), not set per rule.
	DefaultSeverity linter.Severity
}

Registration pairs a built-in rule with the severity it's registered at by default.

func Builtin added in v0.0.2

func Builtin() []Registration

Builtin returns the built-in rules and their default severities, in the same deterministic order as RegisterRules uses. It is a copy of the internal registry; callers may not mutate the built-in rules through it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL