Skip to content

feat(rules): add container privilege and isolation rules - #78

Merged
nozaq merged 3 commits into
mainfrom
claude/rule-expansion-proposal-1401z4
Aug 1, 2026
Merged

feat(rules): add container privilege and isolation rules#78
nozaq merged 3 commits into
mainfrom
claude/rule-expansion-proposal-1401z4

Conversation

@nozaq

@nozaq nozaq commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Adds three rules for container privilege and isolation. They read runArgs through the engine's
docker run traversal, which landed in #90 and #92, so each names the flags it wants and is handed
their occurrences rather than parsing the array itself.

This branch was rebuilt on main after the groundwork it used to carry was split out and merged as
#86#92. What remains is the rules.

Rules

  • no-dangerous-cap-add — a Linux capability that lets a process act on the host rather than on
    the container, in capAdd or in a --cap-add entry. Capabilities the kernel confines to the
    container's own namespaces are deliberately absent: SYS_PTRACE and NET_ADMIN sound privileged
    but reach no further than the container does, and ALL is what no-cap-add-all reports.
  • no-host-namespace — a runArgs entry that puts the container in one of the host's
    namespaces: --network=host, --net=host, --pid=host, --ipc=host, --uts=host,
    --userns=host, --cgroupns=host.
  • no-apparmor-unconfined — AppArmor confinement turned off, the counterpart of
    no-seccomp-unconfined.

Reading a network value

no-host-namespace needs to know what a --network value names, which no rule needed before, so
dockerargs gains NetworkTarget. It reads the value as docker/cli does: a field list when some
part of it is a bare word=word pair, in which case the name field holds the network, and a
network name otherwise. So --network=name = host asks for a network of that name, while
--network=alias=web, name = host asks for the host's.

Notes

  • no-host-namespace derives its Paths from its own flag table, so the two cannot drift apart,
    and a test pins every key to a flag dockerargs.RunFlags holds.
  • Every capability no-dangerous-cap-add reports, and the effect each is reported with, is pinned
    by a list written out in the test rather than read from the rule.

Security category: 8 rules → 11.

https://claude.ai/code/session_01NnTsBGF2nTtG8AS1osuHvJ

@nozaq nozaq changed the title Add security rules for dangerous capabilities and host namespaces feat(rule): add security rules for dangerous capabilities and host namespaces Jul 28, 2026
@nozaq
nozaq force-pushed the claude/rule-expansion-proposal-1401z4 branch from 7e47a58 to 6933938 Compare July 29, 2026 19:49
no-dangerous-cap-add reports a Linux capability that lets a process act
on the host rather than on the container, in "capAdd" or in a
"--cap-add" entry. Capabilities the kernel confines to the container's
own namespaces are deliberately absent: "SYS_PTRACE" and "NET_ADMIN"
sound privileged but reach no further than the container does, and "ALL"
is what no-cap-add-all reports.

no-host-namespace reports a "runArgs" entry that puts the container in
one of the host's namespaces — "--network=host", "--pid=host",
"--ipc=host", "--uts=host", "--userns=host", "--cgroupns=host". A
network value is read as docker/cli reads it: a field list when some
part of it is a bare "word=word" pair, in which case "name" holds the
network, and a network name otherwise, so "--network=name = host" asks
for a network of that name rather than for the host's.

no-apparmor-unconfined reports AppArmor confinement turned off, the
counterpart of no-seccomp-unconfined.

The rules read a flag through the engine's "runArgs" traversal, so each
declares the flags it wants and is handed their occurrences.
no-host-namespace derives its paths from its own table, so the two
cannot drift apart, and a test pins every key to a flag the table of
"docker run" flags holds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnTsBGF2nTtG8AS1osuHvJ
@nozaq
nozaq force-pushed the claude/rule-expansion-proposal-1401z4 branch from 2e4cad6 to b0aac68 Compare August 1, 2026 11:37
@nozaq nozaq changed the title feat(rule): add security rules for dangerous capabilities and host namespaces feat(rules): add container privilege and isolation rules Aug 1, 2026
claude added 2 commits August 1, 2026 12:27
The guard asserted that every hostNamespaces key names a "docker run"
flag. It came from an earlier shape of the rule, where the lookup went
through a canonicalizing step and the paths did not: a key that was not
already canonical left the entry unreachable and the rule calling a nil
target.

Neither half of that holds now. The paths are built from the keys and
the engine matches them against the flag's canonical name, so a match
implies the key is there; and the rule checks target for nil, so a zero
entry could not panic even if one were reachable. What remains is a
failure mode TestNoHostNamespace already covers — mistyping any of the
seven keys, or writing one as a shorthand, fails it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnTsBGF2nTtG8AS1osuHvJ
The rule read node.Arg without checking it, which every runArgs-driven
rule but this one had to do anyway for its property path. A "runArgs"
that is an object rather than an array is not an argv and is traversed
as the ordinary object it is, so a member named "--network" reaches
"/runArgs/--network" carrying no flag occurrence. The rule dereferenced
nil there, and safeCheck turned that into a "rule panicked" finding.

Also make NetworkTarget read a field list Docker rejects, which its own
doc already claimed it did not. Docker refuses the flag on an unknown
field key, an address that does not parse, or a field written without a
key or a value; the value still says which network was asked for, so
reading it beats going quiet on a config that already fails to start —
the choice IsTrue makes for a boolean flag and parseMountString makes
for a spaced mount field. It was half made here: three of those four
forms already yielded the name field, and only a field without a value
returned nothing, so "--network=name=host,web" asked for the host and
was reported by no one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NnTsBGF2nTtG8AS1osuHvJ
@nozaq
nozaq merged commit 452b4aa into main Aug 1, 2026
12 checks passed
@nozaq
nozaq deleted the claude/rule-expansion-proposal-1401z4 branch August 1, 2026 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants