SBOB specification — v0.0.4
- Document
- Software Bill of Behavior
- Version
- 0.0.4
- Stage
- Proposed
Contents
Abstract
A Software Bill of Behavior (SBOB) is a signed, declarative document that states what a piece of software is intended to do at runtime — specifically, which Linux capabilities it requires, which files it expects to open, which child processes it spawns, which network destinations it accepts/establishes.
1. Introduction
1.1 What an SBOB is for
The SBOB expresses, in machine-readable and human-readable form, the prescriptive intent of the software author: the set of behaviors the software is designed to exhibit.
The quality of an SBOB can be judged by:
- its ability to distinguish malicious from intentional behavior
- its transferability (portability) across linux-based systems
- its compatibility with standard gitOps tooling
- its usability by humans (time to first detection)
1.1.1 Kernel vs User-Space: Universal Applicability
The SBOB is designed to make runtime security more achievable, not to replace existing approaches such as the Linux Security Modules.
To serve all application languages, an SBOB is based on the lowest common denominator: the Linux Kernel and for high-compatibility, we will rely on the ABI or other long-term-stable elements.
1.2 Relationship to existing standards
| Other artifact | What it covers | Relationship to SBOB |
|---|---|---|
| SBOM (CycloneDX, SPDX) | static composition (packages, versions, licences) | complementary — solves the question of ingredients |
| RBOM (in-toto,rapidford) | execution-aware refinement of SBOM | complementary — solves the question of reachability |
| VEX | Vendor declared exploitability of a CVE | complementary — solves the question of CVE relevance |
ContainerProfile | runtime-observed behavior captured by an in-cluster operator | Raw ingredient, this document shows how to abstract it into an SBOB |
| Seccomp / AppArmor profile | kernel-enforced syscall and resource policy | adjacent — an SBoB MAY transpile into a seccomp profile |
1.3 Conformance language
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.
1.4 Generalized rules: the portable set
Cell legend: Tool 1 cells abbreviate the rule’s CEL predicate, prefixed by its event type; the verbatim expressions are in the shipped rule set (default-rules.yaml, chart tag kubescape-operator-1.41.0-duckling16). native = shipped; generated = emitted from the SBOB by tooling; deny-list = expressible only as an explicit deny of the observed value; N/A = no equivalent found in the indexed sources.
1.4.1 Rules that read the SBOB
| Generalized rule | SBOB section | Tool 1 | Tool 2 | Tool 3 | Tool 4 |
|---|---|---|---|---|---|
| Process execution | execs[].path (key process-execution) | exec: !was_executed | not proc.exepath in (sbob_execs) | process[] {path, action: allow} | deny-list: data.pathname!=<path> |
| Process arguments | execs[].args (key process-arguments) | exec: !was_executed_with_args | proc.cmdline regex <pattern> | N/A | N/A |
| File access | opens[].path (key file-access) | open: !was_path_opened | not fd.name pmatch (sbob_opens) | file[] {filter, recursive} monitor list | deny-list: data.pathname!=<path> |
| Domain communication | egress[].dnsNames (key domain-communication) | dns: !is_domain_in_egress | N/A | address group per name, egress allow | N/A |
| Egress traffic | egress[] (key egress-traffic) | network: OUTGOING && !in_egress | outbound and not fd.sip in (sbob_egress) | egress[] {selector, ports, action: allow} | deny-list: security_socket_connect |
| Ingress traffic | ingress[] (key ingress-traffic) | network: HOST && !in_ingress | inbound and not fd.cip in (sbob_ingress) | ingress[] {selector, ports, action: allow} | N/A |
1.4.2 Signature rules; the SBOB allowlists who may perform them
These rules fire without an SBOB. The SBOB section for them is rulePolicies: per rule, the processes (processAllowed, by comm) or the whole container (containerAllowed) that are intended to perform the action. The engine implements the detection; the SBOB supplies the exemption.
| Generalized rule | SBOB section | Tool 1 | Tool 2 | Tool 3 | Tool 4 |
|---|---|---|---|---|---|
| Binary not in container image | rulePolicies.binary-not-in-image | exec: upperlayer && !was_executed | native Drop and execute new binary in container | native zero-drift | N/A |
| Execution from memory | rulePolicies.execution-from-memory | exec: exepath ~ memfd | native Fileless execution via memfd_create | N/A | native TRC-105 |
| Execution from shared memory | rulePolicies.execution-from-shared-memory | exec: exepath ~ /dev/shm | native Execution from /dev/shm | generated: process[] {path: /dev/shm/*, action: deny} | N/A |
| Sensitive file access | rulePolicies.sensitive-file-access | open: path ~ /etc/shadow | native Read sensitive file untrusted | generated: file[] {filter: /etc/shadow, behavior: block_access} | N/A |
| Link over sensitive file | rulePolicies.link-over-sensitive-file | symlink/hardlink: oldPath ~ shadow | native Create Symlink Over Sensitive Files, Create Hardlink Over Sensitive Files | N/A | N/A |
| Service-account token access | rulePolicies.service-account-token-access | open: path ~ sa token | N/A | N/A | native TRC-108 |
| Procfs environment read | rulePolicies.procfs-environment-read | open: path ~ /proc/*/environ | native Read environment variable from /proc files | N/A | N/A |
| Kubernetes API access | rulePolicies.kubernetes-api-access | exec/network: kubectl, API addr | native Contact K8S API Server From Container | N/A | N/A |
| Module load | rulePolicies.module-load | kmod: init_module | native Linux Kernel Module Injection Detected | N/A | native TRC-1017 |
| eBPF program load | rulePolicies.ebpf-program-load | bpf: cmd == 5 | native BPF Program Not Profiled | N/A | N/A |
| Namespace change | rulePolicies.namespace-change | unshare: pcomm != runc | native Change namespace privileges via unshare | N/A | N/A |
| Process tracing | rulePolicies.process-tracing | ptrace: true | native PTRACE attached to process, PTRACE anti-debug attempt | N/A | native TRC-103, TRC-102 |
| Dynamic linker hook | rulePolicies.dynamic-linker-hook | exec/open: ld hook | N/A | N/A | native TRC-107 |
| SSH connection | rulePolicies.ssh-connection | ssh: dstPort not in [22] | native Disallowed SSH Connection Non Standard Port | partial: suspicious process ssh | N/A |
| Cgroup release_agent write | rulePolicies.cgroup-release-agent-write | N/A | native Detect release_agent File Container Escapes | N/A | native TRC-1010 |
| Stdio to socket | rulePolicies.stdio-to-socket | N/A | native Redirect STDOUT/STDIN to Network Connection in Container | N/A | native TRC-101 |
1.4.3 rulePolicies in the SBOB
spec:
rulePolicies:
file-access:
processAllowed: [logrotate]
service-account-token-access:
containerAllowed: true
module-load:
processAllowed: [modprobe]
2. Conformance
A conformant SBOB document is a (set of) YAML document(s) that:
- Has the envelope described in §4.1, with
apiVersion,kind,metadata, andspecpopulated. - Describes exactly one Pod with all ContainerTypes.
- Populates at least spec.imageID, plus any of the structural sections (§4.3 through §4.8) that apply.
- If any field uses a wildcard or pattern, that pattern conforms to §5.
- Distinguishes absent fields (NULL) from explicit-empty fields (NONE) per §5.4 in its YAML form, even when the underlying language binding collapses the two.
A conformant verifier:
- Matches the SBOB name using a label on the pod
- Implements every match step in §6 against every section of §4 that the document populates.
- Treats absent fields (NULL — non-deterministic, implementation-defined posture) and explicit empty-collection fields (NONE — declared zero-activity, hard violation on first observation) differently (§5.4).
- Reports its NULL-handling posture (deny / log-only / learn-mode) at startup so operators know what their absent-field workloads will encounter.
- Emits drift events keyed by the generalized rule names of §1.4, carrying the engine-specific rule ID alongside the name.
3. Document structure
An SBOB document is a single YAML stream containing one Kubernetes-style resource.
kind: ContainerProfile
metadata:
name: <name-unique-to-environment> # mandatory; the pod label binds to this value
namespace: <namespace>
spec:
architectures: [...]
imageID: <oci-image-with-digest>
capabilities: [...]
endpoints: [...]
execs: [...]
opens: [...]
ingress: [...]
egress: [...]
rulePolicies: { ... }
One document describes one container. A pod with several containers carries one ContainerProfile per container, each bound by its own label value.
4. Schema reference
4.1 Envelope
| Field | Type | Required | Description |
|---|---|---|---|
| apiVersion | string | required | |
| kind | string | required | |
| metadata.name | string | required | A short, stable name for the container. |
| metadata.namespace | string | required | Namespace of the workload the profile binds to. |
| metadata.annotations | map | optional | |
| spec.architectures | list of strings | optional | Linux architecture tokens for which this SBOB applies (e.g. amd64, arm64). |
| spec.imageID | string | required | The OCI image reference, ideally pinned by digest. |
| spec.* | sections | optional | The behavioural sections listed in §4.2. |
4.2 Sections
All sections sit directly under spec. There is no per-container list; init and ephemeral containers get their own ContainerProfile documents.
| Field | Type | Required | Description |
|---|---|---|---|
| capabilities | list of strings | optional | See §4.3. |
| endpoints | list of objects | optional | See §4.4. |
| execs | list of objects | optional | See §4.5. |
| opens | list of objects | optional | See §4.6. |
| ingress | list of objects | optional | See §4.7. highly recommended |
| egress | list of objects | optional | See §4.7. highly recommended |
| rulePolicies | map | optional | See §4.8. |
4.3 capabilities
A list of Linux capabilities that the workload is permitted to hold. Each entry MUST be a token from the kernel’s capabilities(7)
manual page, in upper-case (e.g. CAP_NET_BIND_SERVICE).
capabilities: [CAP_SETGID, CAP_SETPCAP, CAP_SETUID]
A verifier MUST emit a violation if the live process holds any capability not listed. The absence of the field carries a different meaning from explicit empty (capabilities: []) — see §5.4.
4.4 endpoints
A list of HTTP-level endpoints the workload is expected to expose (direction: inbound) or call (direction: outbound).
| Field | Type | Description | |
|---|---|---|---|
| endpoint | string | Pattern of the form :<port>/<path>. Each component supports wildcards per §5. | |
| methods | list of strings | HTTP verbs (GET, POST, …) the endpoint accepts or sends. | |
| headers | map or null | Map of <header-name>: <expected-value-pattern>. | |
| direction | enum | inbound or outbound. |
endpoints:
- endpoint: ":0/api/data" # any port, exact path
methods: [GET, POST]
headers: null # explicit, no extra headers expected
direction: outbound
The desired degree of specificity in methods and headers is under debate
4.5 execs
A list of processes that the workload is permitted to spawn (via execve).
| Field | Type | Required | Description |
|---|---|---|---|
| path | string | required | Absolute filesystem path of the executable. Wildcards per §5. |
| args | list of strings | optional | Argument vector. Each entry is either a literal token or one of the exec-arg wildcard tokens defined in §5: ⋯ (DynamicIdentifier) matches exactly one argument position in a path, ⋯⋯ (ExecArgsWildcard) matches zero-or-more consecutive arguments. * is not an argument wildcard — it matches literally inside args. A token containing ⋯ is split on / and ⋯ MUST equal a whole segment; a prefix that stops mid-segment (/a/b/elasticsearch⋯, --config=⋯) matches nothing and is rejected by bobctl validate. |
execs:
- path: /usr/sbin/apache2 # exact installation path
args: [/usr/sbin/apache2, '⋯⋯'] # anchored argv[0] then any tail
- path: /⋯/apache2 # relative installation path
args: [/⋯/apache2, '⋯⋯']
4.6 opens
A list of file paths the workload is permitted to open, with the open-flag combinations under which it is permitted to open them.
| Field | Type | Required | Description |
|---|---|---|---|
| path | string | required | Filesystem path, absolute. Wildcards per §5: ⋯ (U+22EF, DynamicIdentifier) matches exactly one segment; * (WildcardIdentifier) matches zero-or-more segments mid-path and one-or-more segments when trailing. |
| flags | list of strings | required | Subset of O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND, O_CLOEXEC, O_NONBLOCK, O_DIRECTORY, O_NOFOLLOW, O_PATH. See Kernel documentation |
opens:
- flags: [O_RDONLY, O_WRONLY, O_CREAT]
path: /var/log/apache2/* # any descendant (1+ segments under, never the bare dir)
- flags: [O_RDONLY]
path: /etc/resolv.conf # exact
- flags: [O_RDONLY, O_WRONLY]
path: /opt/*/vendor/app/node/* # mid-path * is 0+ segments (§5.1), trailing * is 1+ segments
- flags: [O_RDONLY, O_WRONLY]
path: /opt/⋯/vendor/app/node/* # mid-path ⋯ is 1 segments (§5.1), trailing * is 1+ segments
4.7 egress and ingress
Two parallel lists the workload is permitted to exchange traffic with:
- egress[] — destinations the workload is permitted to dial (it initiates the connection)
- ingress[] — peers the workload is permitted to accept connections from (the peer initiates)
Both lists carry entries of the same shape (table below) and are evaluated by symmetric verifier paths.
The two stanzas mirror the Kubernetes NetworkPolicy
model as closely as the runtime view allows: the same two directions, the same podSelector / namespaceSelector / ipBlock peer vocabulary, and the same ports[] shape. An SBOB SHOULD be expressible as a NetworkPolicy and a NetworkPolicy SHOULD be expressible as the observed-intent half of an SBOB, so that a declared profile and an enforced policy can be diffed against one another. Where the two diverge, the divergence is stated: an SBOB additionally carries dnsNames (a query name the workload resolved, which NetworkPolicy cannot express) and records observed addresses rather than admission-time selectors.
| Field | Type | Required | Description |
|---|---|---|---|
| identifier | string | optional | Free-form short name to refer to this entry in rulePolicies or in cross-document references. |
| type | enum | optional | external (default) or internal. |
| ipAddresses | list of strings | optional | New in v0.0.2. Each entry is one of: an IPv4/IPv6 literal, a CIDR (10.0.0.0/8, 2001:db8::/32), or the * sentinel meaning “any IP” (sugar for 0.0.0.0/0+::/0). The verifier matches the live observed IP against each entry per §5.7; an observation that matches ANY entry passes. Empty list [] is NONE per §5.4 (no IP traffic intended). |
| dnsNames | list of strings | optional | Set of DNS names (FQDN form with trailing dot RECOMMENDED) the verifier accepts. Each entry MAY use the wildcard tokens defined in §5.8: leading *.<suffix> (RFC 4592, exactly one label), mid <a>.⋯.<b> (DynamicIdentifier, exactly one label), or trailing <prefix>.* (one or more labels). Strings without these tokens are byte-equality. Replaces the deprecated single dns field. |
| podSelector | label selector | optional | Cluster-internal traffic: matches pods bearing these labels. |
| namespaceSelector | label selector | optional | Cluster-internal traffic: matches pods in namespaces bearing these labels. |
| ports[] | list of objects | required | Each entry: name (<protocol>-<port> e.g. TCP-443), protocol (TCP/UDP/SCTP), port (uint16, nullable per §5.4). |
spec:
egress:
- identifier: stripe-api
type: external
ipAddresses: ["162.0.217.171/32"]
dnsNames: ["*.api.stripe.com."]
ports:
- {name: TCP-443, protocol: TCP, port: 443}
- identifier: cluster-dns
type: internal
namespaceSelector:
matchLabels: {kubernetes.io/metadata.name: kube-system}
podSelector:
matchLabels: {k8s-app: kube-dns}
ports:
- {name: UDP-53, protocol: UDP, port: 53}
ingress:
- identifier: load-balancer-health
type: internal
ipAddresses: ["10.244.0.0/16"] # TBD should there be a K8S-SVC-CIDR etc placeholder
ports:
- {name: TCP-8080, protocol: TCP, port: 8080}
- identifier: prometheus-scrape
type: internal
namespaceSelector:
matchLabels: {kubernetes.io/metadata.name: monitoring}
podSelector:
matchLabels: {app.kubernetes.io/name: prometheus}
ports:
- {name: TCP-9090, protocol: TCP, port: 9090}
4.8 rulePolicies
A map keyed by a generalized rule name (§1.4, kebab case) whose value says who is intended to perform that action. It is the SBOB’s allowlist for the signature rules of §1.4.2; the rules of §1.4.1 read their own sections and ignore it.
Key: the generalized rule name (namespace-change, module-load, …). The verifier maps it to its engine rule ID and carries both in the drift event.
Value:
| Field | Type | Description |
|---|---|---|
| processAllowed | list of strings | Process comm names that ARE intended to perform the action. |
| containerAllowed | bool | true: any process in the container may perform the action. false is identical to omitting the entry. |
rulePolicies:
namespace-change:
processAllowed: [runc] # only runc may unshare
file-access:
processAllowed: [logrotate]
An entry for a rule the engine does not honour (§1.4.3) has no effect and MUST NOT be treated as a deny.
5. Pattern and wildcard semantics
Relevant to abstract exact kernel events into a semantic and portable description of intent.
5.1 Path wildcards * and ⋯
Two wildcard tokens are defined for path components.
⋯ (U+22EF) — exactly one segment. A single Unicode codepoint, NOT three ASCII periods. Matches one full path segment (zero-or-more characters that do not contain /). Spelt distinctly to avoid colliding with accidential use of globs.
* (PathWildcard) — variable arity, position-dependent.
- When
*appears mid-path (i.e. there are more segments after it), it matches zero or more consecutive segments. - When
*appears trailing (last segment of the pattern), it matches one or more remaining segments — never zero.
| Pattern | Matches | Does not match |
|---|---|---|
/etc/⋯ | /etc/passwd, /etc/hosts | /etc/, /etc/ssh/sshd_config, /etc/ssl/certs/ca.pem |
/etc/* (trailing) | /etc/passwd, /etc/ssh/sshd_config | /etc, /etc/ |
/a/*/b (mid) | /a/b, /a/x/b, /a/x/y/b | /a/x/c |
/a/⋯/b | /a/x/b | /a/b, /a/x/y/b |
5.3 Exec-argument wildcards
The * and ⋯ tokens of §5.1 are path wildcards. An exec entry’s args vector uses
⋯⋯(WildcardExecs) — zero or more arguments. Absorbs a run of consecutive argv entries, including none:[/bin/foo, '⋯⋯']matches/bin/foowith any tail (or none), whereas[/bin/foo]matches only the bare invocation with no arguments.
* is not an argument wildcard — inside args it is a literal asterisk.
5.4 Absent (NULL) vs explicit-empty (NONE)
TBD
5.6 Port wildcards
Ports appear in two contexts curretly
endpoints[].endpoint string — for http, use the integer 0 to express the intent of
ANY PORT.:0/api/dataegress[].ports[].port / ingress[].ports[].port — an absent
port(null) means ANY PORT; an omittedportsstanza means any port on that peer. A populatedports[]matches only its literal (protocol, port) entries. The RFC 6335 numeric-0sentinel does NOT apply at this field.
5.7 IP address matching
Matches an observed IP against an ipAddresses[] list using these forms in order. Each entry is one of:
| Form | Example | Semantics |
|---|---|---|
| IPv4 / IPv6 literal | 162.0.217.171, 2001:db8::1 | Byte-equality on the parsed IP. The textual canonicalisation is the verifier’s responsibility (e.g. 2001:db8::1 and 2001:0db8:0000:0000:0000:0000:0000:0001 MUST compare equal). |
| CIDR | 10.0.0.0/8, 2001:db8::/32 | The verifier parses the entry with net.ParseCIDR (or equivalent) once and stores the *net.IPNet; matches via IPNet.Contains(observedIP). |
| * | * | 0.0.0.0/0 (RFC 4632, all IPv4) and ::/0 (RFC 4291, all IPv6). Matches any observed IP. |
5.8 DNS name matching
Matches an observed DNS name (from the workload’s own getaddrinfo / res_query event see §8.2) against the
dnsNames[]
| Form | Position | Semantics | Example | Matches | Doesn’t |
|---|---|---|---|---|---|
| Literal | — | byte-equality after trailing-dot normalisation | api.stripe.com. | api.stripe.com., api.stripe.com | v1.api.stripe.com. |
*.<suffix> | leading | RFC 4592 — exactly one label before <suffix> | *.example.com. | api.example.com., webhook.example.com. | v1.api.example.com., example.com. (apex), .example.com. (empty label) |
<a>.⋯.<b> | mid | DynamicIdentifier — exactly one label between <a> and <b> | svc.⋯.kubernetes.io. | svc.kube-system.kubernetes.io. | svc.kubernetes.io., svc.a.b.kubernetes.io. |
<prefix>.* | trailing | WildcardIdentifier — one or more labels after <prefix> (never zero) | mycorp.com.* | mycorp.com.api., mycorp.com.api.v1. | mycorp.com. (apex match — zero rejected) |
Rationale for the token choices
- Leading
*= exactly one label is RFC 4592 - Mid-label
*is non-standard — bind/coredns reject it, cilium uses regex, Calico has its own glob form. v0.0.2 deliberately uses the project’s existing⋯token for the mid position, so the wire format never claims RFC 4592 compliance for a non-standard shape and producers’ intent is unambiguous. - Trailing
*= one-or-more matches the path semantic in §5.1 — a trailing*MUST consume at least one label, never zero, somycorp.com.*does not silently allow access to the bare apex.
Apex matching
*.example.com. does NOT match the bare apex example.com. — same defensive rule as path-side trailing-*. Producers that intend the apex MUST list example.com. as a separate dnsNames entry. Same rule for <prefix>.* trailing form.
Empty / ** rejection
A dnsNames[i] containing ** (recursive wildcard) MUST be rejected
List semantics
The verifier matches each observed name against EVERY entry in dnsNames; an observation that matches ANY entry passes. Entries are unordered. A NONE list (dnsNames: []) per §5.4 means “no DNS traffic intended” — any DNS observation is a hard violation.
6. Verifier algorithm
WIP
7. Examples
7.1 Minimal example
The smallest legal SBoB:
kind: ContainerProfile
metadata:
name: payment-app
namespace: shop
annotations: {kubescape.io/managed-by: User}
spec:
imageID: ghcr.io/billofbehavior/payment-app@sha256:f6c18b0...
7.2 Full example with wildcards
kind: ContainerProfile
metadata:
name: payment-app
namespace: shop
annotations:
kubescape.io/managed-by: User
sbob.io/spec-version: "0.0.4"
spec:
architectures: [amd64]
imageID: ghcr.io/billofbehavior/payment-app@sha256:f6c18b0...
capabilities: [CAP_SETGID, CAP_SETPCAP, CAP_SETUID]
endpoints:
- endpoint: ":0/api/data" # any port, exact path
methods: [GET, POST]
headers: null # explicit no extra headers
direction: outbound
execs:
path: /usr/sbin/apache2 # intended exact path
- args: [/usr/sbin/apache2, '⋯⋯'] # zero-or-more trailing args
- path: /usr/lib/postgresql/⋯/bin/postgres # version number does NOT matter
args: ["postgres"]
- path: /usr/lib/postgresql/16/bin/initdb # version number DOES matter
args: ["initdb"]
opens:
- flags: [O_RDONLY, O_WRONLY, O_CREAT]
path: /var/log/apache2/* # any descendant (1+ segments under, never the bare dir)
- flags: [O_RDONLY]
path: /etc/resolv.conf # exact
- flags: [O_RDONLY, O_WRONLY]
path: /opt/*/vendor/app/node/* # mid * is 0+ segments, trailing * is 1+ segments
egress:
- identifier: stripe-api
type: external
ipAddresses: # v0.0.2 plural form, supersedes singular ipAddress
- "162.0.217.171" # IPv4 literal
- "2001:db8::/32" # IPv6 CIDR (§5.7)
dnsNames: # supersedes the deprecated single dns field
- api.stripe.com. # literal FQDN
- "*.api.stripe.com." # RFC 4592 leading-* — exactly one label (§5.8)
ports:
- {name: TCP-443, protocol: TCP, port: 443}
- identifier: cluster-dns
type: internal
namespaceSelector:
matchLabels: {kubernetes.io/metadata.name: kube-system}
podSelector:
matchLabels: {k8s-app: kube-dns}
ports:
- {name: UDP-53, protocol: UDP, port: 53}
- identifier: kube-svc-resolver # demonstrates the mid-label ⋯ form
type: internal
dnsNames:
- "svc.⋯.cluster.local." # leaves NAMESPACE label open to user
ports:
- {name: UDP-53, protocol: UDP, port: 53}
rulePolicies:
namespace-change: # §1.4.2 (kubescape R1006, Falco "Change namespace privileges via unshare")
processAllowed: [runc] # only runc may unshare
module-load: # §1.4.2 (kubescape R1002, Falco "Linux Kernel Module Injection Detected", Tracee TRC-1017)
containerAllowed: false # same as omitting the entry; shown for clarity
8. Signatures
WIP
9. Security considerations
10. Open issues
- Generalisation of rules: names and verbs
- Notations for common network-masks such as
{pod_cidr}to be discoverable/replaceable by bobctl - Implementation and example of bundling and signing of bundles
- Support for pod-lifecycle
- Guidance on
useful syscalls
11. References
- RFC 2119, Key words for use in RFCs to Indicate Requirement Levels. https://www.rfc-editor.org/rfc/rfc2119
- RFC 8174, Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. https://www.rfc-editor.org/rfc/rfc8174
capabilities(7), Linux manual page. https://man7.org/linux/man-pages/man7/capabilities.7.htmlopen(2), Linux manual page. https://man7.org/linux/man-pages/man2/open.2.html- RFC 6335, Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registry — note on port
0. https://www.rfc-editor.org/rfc/rfc6335 - kubescape
ContainerProfileCRD, schema source. https://github.com/kubescape/storage - SBoB stack-profile extension, draft v0.0.3.
spec-stackprofile-v0.0.1
Appendix A. Change log
| Date | Version | Note |
|---|---|---|
| 2026-05-04 | 0.0.1 | Initial draft. |
| 2026-09-20 | 0.0.4 | Document syntax. One document per container, flat spec (no containers[]), imageID at top level, metadata.namespace plus a provenance annotation, one binding label on the pod. §4.8 renamed rulePolicies, keyed by generalized rule name (§1.4), values processAllowed / containerAllowed. §4.5 whole-segment ⋯ rule stated. §1.4 generalized rules added. §5.6 port semantics stated. §6 reads ingress[] / egress[] from the same document. |
| 2026-05-16 | 0.0.3 | Spec ↔ code alignment pass. §4.1 kind spelling corrected to NetworkNeighborhood (American — matches CRD, tooling, CI). §4.2 container-type field removed; replaced with a description of the kubescape PodSpec-style three-list layout (spec.containers[] / spec.initContainers[] / spec.ephemeralContainers[]). §4.4 headers entry sharpened with an explicit TODO about the json.RawMessage + omitempty binding collapsing NULL/NONE — workaround (headersIntent enum or custom unmarshaller) deferred to v0.0.2. §4.5 execs entry gains a TODO about admission-time enforcement of path REQUIRED (current CRD tags it opt for binding reasons). §4.8 rewritten to match the in-code rulePolicies field name + engine-rule-ID key + boolean containerAllowed value; the canonical-action-verb registry from Appendix B is retained as a v0.0.2 goal rather than a current normative wire format. §5.6 split into two rules: endpoint-string :0 wildcard (unchanged), and a new explicit rule that NetworkNeighbor.ports[].port uses null-pointer = “any port” (the RFC 6335 numeric-0 sentinel does NOT apply at this field). §5.3 exec-argument wildcards defined: ⋯⋯ (ExecArgsWildcard) matches zero-or-more args, ⋯ (DynamicIdentifier) exactly one; * is opens/path-only and matches literally inside args. Removed a non-normative subsection that did not reflect the implementation. |
| 2026-05-10 | 0.0.2 | Network wildcards landed. §5.7 IP matching promoted from TODO to normative — ipAddresses []string (new plural) accepts IPv4/IPv6 literals, CIDRs (net.ParseCIDR + IPNet.Contains), and * sentinel for any. Singular ipAddress deprecated, kept for back-compat. §5.8 DNS matching: leading *.<suffix> (RFC 4592, exactly one label), mid <a>.⋯.<b> (DynamicIdentifier, exactly one), trailing <prefix>.* (one or more, never zero). ** recursive form rejected — reserved for v0.0.3. §4.7 retitled “egress and ingress” — both directions are first-class, share the same NetworkNeighbor shape, and consume the same matcher implementation; example extended with two ingress[] entries (CIDR-based LB-probe + selector-based prometheus-scrape). §6.2 algorithm step renamed “network neighbour check” with direction-aware routing (pktType=='OUTGOING'→egress[], pktType=='INCOMING'→ingress[]) and a new net.ingress_unexpected drift verb. §7.2 example demonstrates IPv6 CIDR + leading-* DNS + mid-⋯ Kubernetes-service-FQDN form. |