Repository: kubernetes-sigs/security-profiles-operator
Pull request #2141 (2024-03-01 21:54:09)

Add End-to-End Tests for Spoc
#### What type of PR is this?

/kind cleanup

#### What this PR does / why we need it:

I have a series of (work-in-progress) BPF recorder improvements sitting at https://github.com/kubernetes-sigs/security-profiles-operator/compare/main...mhils:security-profiles-operator:wip that I'd like to upstream. I'd like to kick this off with end-to-end tests for spoc so that we have more confidence in further PRs. :)

#### Which issue(s) this PR fixes:

refs (but not fixes) https://github.com/kubernetes-sigs/security-profiles-operator/issues/718

#### Does this PR have test?

Yes.

#### Does this PR introduce a user-facing change?

```release-note
NONE
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/2141#" title="Author self-approved">mhils</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [pjbgf](https://github.com/pjbgf) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["pjbgf"]} -->
k8s-ci-robot: Hi @mhils. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/2141?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) Report
> Merging [#2141](https://app.codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/2141?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) (82a24da) into [main](https://app.codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/11d77f49d652b2b077eaeb9a0ef9eff5f24b6a8d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubernetes-sigs) (11d77f4) will **decrease** coverage by `4.55%`.
> Report is 60 commits behind head on main.
> The diff coverage is `n/a`.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2141      +/-   ##
==========================================
- Coverage   45.50%   40.95%   -4.55%     
==========================================
  Files          79      102      +23     
  Lines        7782    15106    +7324     
==========================================
+ Hits         3541     6187    +2646     
- Misses       4099     8482    +4383     
- Partials      142      437     +295     
```

</details>
mhils: I've rebased onto main and made sure that tests are passing locally. Please let me know if this needs anything else! 😃 
saschagrunert: /ok-to-test
k8s-ci-robot: @mhils: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-security-profiles-operator-test-e2e | c25df59fd96491a7d832e1dc8657a763a410ff49 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/2141/pull-security-profiles-operator-test-e2e/1765686594657849344) | true | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=2141). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amhils). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
mhils: /retest

Patch:
diff --git a/Makefile b/Makefile
index a5b3fd9be..e6d1e9896 100644
--- a/Makefile
+++ b/Makefile
@@ -461,13 +461,17 @@ test-unit: $(BUILD_DIR) ## Run the unit tests
 test-e2e: ## Run the end-to-end tests
 	CGO_LDFLAGS= \
 	E2E_SKIP_FLAKY_TESTS=true \
-	$(GO) test -parallel 1 -timeout 60m -count=1 ./test/... -v
+	$(GO) test -parallel 1 -timeout 60m -count=1 ./test -v
 
 .PHONY: test-flaky-e2e
 test-flaky-e2e: ## Only run the flaky end-to-end tests
 	CGO_LDFLAGS= \
 	E2E_SKIP_FLAKY_TESTS=false \
-	$(GO) test -parallel 1 -timeout 20m -count=1 ./test/... -v -testify.m '^(TestSecurityProfilesOperator_Flaky)$$'
+	$(GO) test -parallel 1 -timeout 20m -count=1 ./test -v -testify.m '^(TestSecurityProfilesOperator_Flaky)$$'
+
+.PHONY: test-spoc-e2e
+test-spoc-e2e: build/spoc
+	$(GO) test -v ./test/spoc
 
 # Generate CRD manifests
 manifests: $(BUILD_DIR)/kubernetes-split-yaml $(BUILD_DIR)/kustomize
diff --git a/go.mod b/go.mod
index 832dc714d..0439fd172 100644
--- a/go.mod
+++ b/go.mod
@@ -132,7 +132,7 @@ require (
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/go-chi/chi v4.1.2+incompatible // indirect
 	github.com/go-ini/ini v1.67.0 // indirect
-	github.com/go-jose/go-jose/v3 v3.0.2 // indirect
+	github.com/go-jose/go-jose/v3 v3.0.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-openapi/analysis v0.22.0 // indirect
 	github.com/go-openapi/errors v0.21.1 // indirect
@@ -262,7 +262,7 @@ require (
 	google.golang.org/api v0.159.0 // indirect
 	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
-	gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
+	gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
diff --git a/go.sum b/go.sum
index b170ff0df..4319632aa 100644
--- a/go.sum
+++ b/go.sum
@@ -286,8 +286,8 @@ github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyN
 github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
-github.com/go-jose/go-jose/v3 v3.0.2 h1:2Edjn8Nrb44UvTdp84KU0bBPs1cO7noRCybtS3eJEUQ=
-github.com/go-jose/go-jose/v3 v3.0.2/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
+github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
+github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
 github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -955,8 +955,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4U=
-gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
+gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
+gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/vendor/github.com/go-jose/go-jose/v3/CHANGELOG.md b/vendor/github.com/go-jose/go-jose/v3/CHANGELOG.md
index 7ae6cff94..ce2a54ebf 100644
--- a/vendor/github.com/go-jose/go-jose/v3/CHANGELOG.md
+++ b/vendor/github.com/go-jose/go-jose/v3/CHANGELOG.md
@@ -1,3 +1,56 @@
+# v4.0.1
+
+## Fixed
+
+ - An attacker could send a JWE containing compressed data that used large
+   amounts of memory and CPU when decompressed by `Decrypt` or `DecryptMulti`.
+   Those functions now return an error if the decompressed data would exceed
+   250kB or 10x the compressed size (whichever is larger). Thanks to
+   Enze Wang@Alioth and Jianjun Chen@Zhongguancun Lab (@zer0yu and @chenjj)
+   for reporting.
+
+# v4.0.0
+
+This release makes some breaking changes in order to more thoroughly
+address the vulnerabilities discussed in [Three New Attacks Against JSON Web
+Tokens][1], "Sign/encrypt confusion", "Billion hash attack", and "Polyglot
+token".
+
+## Changed
+
+ - Limit JWT encryption types (exclude password or public key types) (#78)
+ - Enforce minimum length for HMAC keys (#85)
+ - jwt: match any audience in a list, rather than requiring all audiences (#81)
+ - jwt: accept only Compact Serialization (#75)
+ - jws: Add expected algorithms for signatures (#74)
+ - Require specifying expected algorithms for ParseEncrypted,
+   ParseSigned, ParseDetached, jwt.ParseEncrypted, jwt.ParseSigned,
+   jwt.ParseSignedAndEncrypted (#69, #74)
+   - Usually there is a small, known set of appropriate algorithms for a program
+     to use and it's a mistake to allow unexpected algorithms. For instance the
+     "billion hash attack" relies in part on programs accepting the PBES2
+     encryption algorithm and doing the necessary work even if they weren't
+     specifically configured to allow PBES2.
+ - Revert "Strip padding off base64 strings" (#82)
+  - The specs require base64url encoding without padding.
+ - Minimum supported Go version is now 1.21
+
+## Added
+
+ - ParseSignedCompact, ParseSignedJSON, ParseEncryptedCompact, ParseEncryptedJSON.
+   - These allow parsing a specific serialization, as opposed to ParseSigned and
+     ParseEncrypted, which try to automatically detect which serialization was
+     provided. It's common to require a specific serialization for a specific
+     protocol - for instance JWT requires Compact serialization.
+
+[1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
+
+# v3.0.3
+
+## Fixed
+
+ - Limit decompression output size to prevent a DoS. Backport from v4.0.1.
+
 # v3.0.2
 
 ## Fixed
diff --git a/vendor/github.com/go-jose/go-jose/v3/README.md b/vendor/github.com/go-jose/go-jose/v3/README.md
index 57da65700..282cd9e13 100644
--- a/vendor/github.com/go-jose/go-jose/v3/README.md
+++ b/vendor/github.com/go-jose/go-jose/v3/README.md
@@ -1,18 +1,22 @@
 # Go JOSE
 
-[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v3.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v3)
-[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v3/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v3/jwt)
-[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
-[![test](https://img.shields.io/github/checks-status/go-jose/go-jose/v3)](https://github.com/go-jose/go-jose/actions)
+### Versions
+
+[Version 4](https://github.com/go-jose/go-jose)
+([branch](https://github.com/go-jose/go-jose/),
+[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
+
+    import "github.com/go-jose/go-jose/v4"
+
+The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
+are deprecated.
+
+### Summary
 
 Package jose aims to provide an implementation of the Javascript Object Signing
 and Encryption set of standards. This includes support for JSON Web Encryption,
 JSON Web Signature, and JSON Web Token standards.
 
-**Help Wanted!** If you'd like to help us develop this library please reach
-out to css (at) css.bio. While I'm still working on keeping this maintained,
-I have limited time for in-depth development and could use some additional help.
-
 **Disclaimer**: This library contains encryption software that is subject to
 the U.S. Export Administration Regulations. You may not export, re-export,
 transfer or download this code or any part of it in violation of any United
@@ -39,17 +43,6 @@ of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/curren
 This is to avoid differences in interpretation of messages between go-jose and
 libraries in other languages.
 
-### Versions
-
-[Version 3](https://github.com/go-jose/go-jose)
-([branch](https://github.com/go-jose/go-jose/tree/v3),
-[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v3), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
-
-    import "github.com/go-jose/go-jose/v3"
-
-The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
-are still useable but not actively developed anymore. 
-
 ### Supported algorithms
 
 See below for a table of supported algorithms. Algorithm identifiers match
diff --git a/vendor/github.com/go-jose/go-jose/v3/crypter.go b/vendor/github.com/go-jose/go-jose/v3/crypter.go
index 506d3b7b8..8870e8905 100644
--- a/vendor/github.com/go-jose/go-jose/v3/crypter.go
+++ b/vendor/github.com/go-jose/go-jose/v3/crypter.go
@@ -440,6 +440,9 @@ func (ctx *genericEncrypter) Options() EncrypterOptions {
 //
 // Note that ed25519 is only available for signatures, not encryption, so is
 // not an option here.
+//
+// Automatically decompresses plaintext, but returns an error if the decompressed
+// data would be >250kB or >10x the size of the compressed data, whichever is larger.
 func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
 	headers := obj.mergedHeaders(nil)
 
@@ -511,6 +514,9 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
 //
 // The decryptionKey argument must have one of the types allowed for the
 // decryptionKey argument of Decrypt().
+//
+// Automatically decompresses plaintext, but returns an error if the decompressed
+// data would be >250kB or >3x the size of the compressed data, whichever is larger.
 func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
 	globalHeaders := obj.mergedHeaders(nil)
 
diff --git a/vendor/github.com/go-jose/go-jose/v3/encoding.go b/vendor/github.com/go-jose/go-jose/v3/encoding.go
index 62f8b8ad3..9f07cfdcb 100644
--- a/vendor/github.com/go-jose/go-jose/v3/encoding.go
+++ b/vendor/github.com/go-jose/go-jose/v3/encoding.go
@@ -21,6 +21,7 @@ import (
 	"compress/flate"
 	"encoding/base64"
 	"encoding/binary"
+	"fmt"
 	"io"
 	"math/big"
 	"strings"
@@ -85,7 +86,7 @@ func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
 	}
 }
 
-// Compress with DEFLATE
+// deflate compresses the input.
 func deflate(input []byte) ([]byte, error) {
 	output := new(bytes.Buffer)
 
@@ -97,15 +98,27 @@ func deflate(input []byte) ([]byte, error) {
 	return output.Bytes(), err
 }
 
-// Decompress with DEFLATE
+// inflate decompresses the input.
+//
+// Errors if the decompressed data would be >250kB or >10x the size of the
+// compressed data, whichever is larger.
 func inflate(input []byte) ([]byte, error) {
 	output := new(bytes.Buffer)
 	reader := flate.NewReader(bytes.NewBuffer(input))
 
-	_, err := io.Copy(output, reader)
-	if err != nil {
+	maxCompressedSize := 10 * int64(len(input))
+	if maxCompressedSize < 250000 {
+		maxCompressedSize = 250000
+	}
+
+	limit := maxCompressedSize + 1
+	n, err := io.CopyN(output, reader, limit)
+	if err != nil && err != io.EOF {
 		return nil, err
 	}
+	if n == limit {
+		return nil, fmt.Errorf("uncompressed data would be too large (>%d bytes)", maxCompressedSize)
+	}
 
 	err = reader.Close()
 	return output.Bytes(), err
diff --git a/vendor/gopkg.in/go-jose/go-jose.v2/CHANGELOG.md b/vendor/gopkg.in/go-jose/go-jose.v2/CHANGELOG.md
new file mode 100644
index 000000000..8e6e91323
--- /dev/null
+++ b/vendor/gopkg.in/go-jose/go-jose.v2/CHANGELOG.md
@@ -0,0 +1,84 @@
+# v4.0.1
+
+## Fixed
+
+ - An attacker could send a JWE containing compressed data that used large
+   amounts of memory and CPU when decompressed by `Decrypt` or `DecryptMulti`.
+   Those functions now return an error if the decompressed data would exceed
+   250kB or 10x the compressed size (whichever is larger). Thanks to
+   Enze Wang@Alioth and Jianjun Chen@Zhongguancun Lab (@zer0yu and @chenjj)
+   for reporting.
+
+# v4.0.0
+
+This release makes some breaking changes in order to more thoroughly
+address the vulnerabilities discussed in [Three New Attacks Against JSON Web
+Tokens][1], "Sign/encrypt confusion", "Billion hash attack", and "Polyglot
+token".
+
+## Changed
+
+ - Limit JWT encryption types (exclude password or public key types) (#78)
+ - Enforce minimum length for HMAC keys (#85)
+ - jwt: match any audience in a list, rather than requiring all audiences (#81)
+ - jwt: accept only Compact Serialization (#75)
+ - jws: Add expected algorithms for signatures (#74)
+ - Require specifying expected algorithms for ParseEncrypted,
+   ParseSigned, ParseDetached, jwt.ParseEncrypted, jwt.ParseSigned,
+   jwt.ParseSignedAndEncrypted (#69, #74)
+   - Usually there is a small, known set of appropriate algorithms for a program
+     to use and it's a mistake to allow unexpected algorithms. For instance the
+     "billion hash attack" relies in part on programs accepting the PBES2
+     encryption algorithm and doing the necessary work even if they weren't
+     specifically configured to allow PBES2.
+ - Revert "Strip padding off base64 strings" (#82)
+  - The specs require base64url encoding without padding.
+ - Minimum supported Go version is now 1.21
+
+## Added
+
+ - ParseSignedCompact, ParseSignedJSON, ParseEncryptedCompact, ParseEncryptedJSON.
+   - These allow parsing a specific serialization, as opposed to ParseSigned and
+     ParseEncrypted, which try to automatically detect which serialization was
+     provided. It's common to require a specific serialization for a specific
+     protocol - for instance JWT requires Compact serialization.
+
+[1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
+
+# v3.0.3
+
+## Fixed
+
+ - Limit decompression output size to prevent a DoS. Backport from v4.0.1.
+
+# v3.0.2
+
+## Fixed
+
+ - DecryptMulti: handle decompression error (#19)
+
+## Changed
+
+ - jwe/CompactSerialize: improve performance (#67)
+ - Increase the default number of PBKDF2 iterations to 600k (#48)
+ - Return the proper algorithm for ECDSA keys (#45)
+
+## Added
+
+ - Add Thumbprint support for opaque signers (#38)
+
+# v3.0.1
+
+## Fixed
+
+ - Security issue: an attacker specifying a large "p2c" value can cause
+   JSONWebEncryption.Decrypt and JSONWebEncryption.DecryptMulti to consume large
+   amounts of CPU, causing a DoS. Thanks to Matt Schwager (@mschwager) for the
+   disclosure and to Tom Tervoort for originally publishing the category of attack.
+   https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
+
+# v2.6.3
+
+## Fixed
+
+ - Limit decompression output size to prevent a DoS. Backport from v4.0.1.
diff --git a/vendor/gopkg.in/go-jose/go-jose.v2/README.md b/vendor/gopkg.in/go-jose/go-jose.v2/README.md
index 46b02d61d..b877f412c 100644
--- a/vendor/gopkg.in/go-jose/go-jose.v2/README.md
+++ b/vendor/gopkg.in/go-jose/go-jose.v2/README.md
@@ -1,118 +1,4 @@
-# Go JOSE 
+# go-jose v2
 
-[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v1)
-[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2)
-[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
-[![build](https://travis-ci.org/go-jose/go-jose.svg?branch=v2)](https://travis-ci.org/go-jose/go-jose)
-[![coverage](https://coveralls.io/repos/github/go-jose/go-jose/badge.svg?branch=v2)](https://coveralls.io/r/go-jose/go-jose)
-
-Package jose aims to provide an implementation of the Javascript Object Signing
-and Encryption set of standards. This includes support for JSON Web Encryption,
-JSON Web Signature, and JSON Web Token standards.
-
-**Disclaimer**: This library contains encryption software that is subject to
-the U.S. Export Administration Regulations. You may not export, re-export,
-transfer or download this code or any part of it in violation of any United
-States law, directive or regulation. In particular this software may not be
-exported or re-exported in any form or on any media to Iran, North Sudan,
-Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
-US maintained blocked list.
-
-## Overview
-
-The implementation follows the
-[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516),
-[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
-[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519).
-Tables of supported algorithms are shown below. The library supports both
-the compact and full serialization formats, and has optional support for
-multiple recipients. It also comes with a small command-line utility
-([`jose-util`](https://github.com/go-jose/go-jose/tree/v2/jose-util))
-for dealing with JOSE messages in a shell.
-
-**Note**: We use a forked version of the `encoding/json` package from the Go
-standard library which uses case-sensitive matching for member names (instead
-of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
-This is to avoid differences in interpretation of messages between go-jose and
-libraries in other languages.
-
-### Versions
-
-We use [gopkg.in](https://gopkg.in) for versioning.
-
-[Version 2](https://gopkg.in/go-jose/go-jose.v2)
-([branch](https://github.com/go-jose/go-jose/tree/v2),
-[doc](https://godoc.org/gopkg.in/go-jose/go-jose.v2)) is the current version:
-
-    import "gopkg.in/go-jose/go-jose.v2"
-
-The old `v1` branch ([go-jose.v1](https://gopkg.in/go-jose/go-jose.v1)) will
-still receive backported bug fixes and security fixes, but otherwise
-development is frozen. All new feature development takes place on the `v2`
-branch. Version 2 also contains additional sub-packages such as the
-[jwt](https://godoc.org/gopkg.in/go-jose/go-jose.v2/jwt) implementation
-contributed by [@shaxbee](https://github.com/shaxbee).
-
-### Supported algorithms
-
-See below for a table of supported algorithms. Algorithm identifiers match
-the names in the [JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
-standard where possible. The Godoc reference has a list of constants.
-
- Key encryption             | Algorithm identifier(s)
- :------------------------- | :------------------------------
- RSA-PKCS#1v1.5             | RSA1_5
- RSA-OAEP                   | RSA-OAEP, RSA-OAEP-256
- AES key wrap               | A128KW, A192KW, A256KW
- AES-GCM key wrap           | A128GCMKW, A192GCMKW, A256GCMKW
- ECDH-ES + AES key wrap     | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
- ECDH-ES (direct)           | ECDH-ES<sup>1</sup>
- Direct encryption          | dir<sup>1</sup>
-
-<sup>1. Not supported in multi-recipient mode</sup>
-
- Signing / MAC              | Algorithm identifier(s)
- :------------------------- | :------------------------------
- RSASSA-PKCS#1v1.5          | RS256, RS384, RS512
- RSASSA-PSS                 | PS256, PS384, PS512
- HMAC                       | HS256, HS384, HS512
- ECDSA                      | ES256, ES384, ES512
- Ed25519                    | EdDSA<sup>2</sup>
-
-<sup>2. Only available in version 2 of the package</sup>
-
- Content encryption         | Algorithm identifier(s)
- :------------------------- | :------------------------------
- AES-CBC+HMAC               | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
- AES-GCM                    | A128GCM, A192GCM, A256GCM 
-
- Compression                | Algorithm identifiers(s)
- :------------------------- | -------------------------------
- DEFLATE (RFC 1951)         | DEF
-
-### Supported key types
-
-See below for a table of supported key types. These are understood by the
-library, and can be passed to corresponding functions such as `NewEncrypter` or
-`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
-allows attaching a key id.
-
- Algorithm(s)               | Corresponding types
- :------------------------- | -------------------------------
- RSA                        | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
- ECDH, ECDSA                | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
- EdDSA<sup>1</sup>          | [ed25519.PublicKey](https://godoc.org/golang.org/x/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/golang.org/x/crypto/ed25519#PrivateKey)
- AES, HMAC                  | []byte
-
-<sup>1. Only available in version 2 of the package</sup>
-
-## Examples
-
-[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v1)
-[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2)
-
-Examples can be found in the Godoc
-reference for this package. The
-[`jose-util`](https://github.com/go-jose/go-jose/tree/v2/jose-util)
-subdirectory also contains a small command-line utility which might be useful
-as an example.
+Version 2 of this library is no longer supported. [Please use v4
+instead](https://pkg.go.dev/github.com/go-jose/go-jose/v4).
diff --git a/vendor/gopkg.in/go-jose/go-jose.v2/asymmetric.go b/vendor/gopkg.in/go-jose/go-jose.v2/asymmetric.go
index 3ca79cc26..43f9ce2fc 100644
--- a/vendor/gopkg.in/go-jose/go-jose.v2/asymmetric.go
+++ b/vendor/gopkg.in/go-jose/go-jose.v2/asymmetric.go
@@ -285,6 +285,9 @@ func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm
 
 	switch alg {
 	case RS256, RS384, RS512:
+		// TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the
+		// random parameter is legacy and ignored, and it can be nil.
+		// https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1
 		out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed)
 	case PS256, PS384, PS512:
 		out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
diff --git a/vendor/gopkg.in/go-jose/go-jose.v2/crypter.go b/vendor/gopkg.in/go-jose/go-jose.v2/crypter.go
index 73aab0fab..0ae2e5eba 100644
--- a/vendor/gopkg.in/go-jose/go-jose.v2/crypter.go
+++ b/vendor/gopkg.in/go-jose/go-jose.v2/crypter.go
@@ -406,6 +406,9 @@ func (ctx *genericEncrypter) Options() EncrypterOptions {
 // Decrypt and validate the object and return the plaintext. Note that this
 // function does not support multi-recipient, if you desire multi-recipient
 // decryption use DecryptMulti instead.
+//
+// Automatically decompresses plaintext, but returns an error if the decompressed
+// data would be >250kB or >10x the size of the compressed data, whichever is larger.
 func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
 	headers := obj.mergedHeaders(nil)
 
@@ -470,6 +473,9 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
 // with support for multiple recipients. It returns the index of the recipient
 // for which the decryption was successful, the merged headers for that recipient,
 // and the plaintext.
+//
+// Automatically decompresses plaintext, but returns an error if the decompressed
+// data would be >250kB or >3x the size of the compressed data, whichever is larger.
 func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
 	globalHeaders := obj.mergedHeaders(nil)
 
diff --git a/vendor/gopkg.in/go-jose/go-jose.v2/encoding.go b/vendor/gopkg.in/go-jose/go-jose.v2/encoding.go
index 40b688b3d..636f6c8f5 100644
--- a/vendor/gopkg.in/go-jose/go-jose.v2/encoding.go
+++ b/vendor/gopkg.in/go-jose/go-jose.v2/encoding.go
@@ -21,6 +21,7 @@ import (
 	"compress/flate"
 	"encoding/base64"
 	"encoding/binary"
+	"fmt"
 	"io"
 	"math/big"
 	"strings"
@@ -85,7 +86,7 @@ func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
 	}
 }
 
-// Compress with DEFLATE
+// deflate compresses the input.
 func deflate(input []byte) ([]byte, error) {
 	output := new(bytes.Buffer)
 
@@ -97,15 +98,27 @@ func deflate(input []byte) ([]byte, error) {
 	return output.Bytes(), err
 }
 
-// Decompress with DEFLATE
+// inflate decompresses the input.
+//
+// Errors if the decompressed data would be >250kB or >10x the size of the
+// compressed data, whichever is larger.
 func inflate(input []byte) ([]byte, error) {
 	output := new(bytes.Buffer)
 	reader := flate.NewReader(bytes.NewBuffer(input))
 
-	_, err := io.Copy(output, reader)
-	if err != nil {
+	maxCompressedSize := 10 * int64(len(input))
+	if maxCompressedSize < 250000 {
+		maxCompressedSize = 250000
+	}
+
+	limit := maxCompressedSize + 1
+	n, err := io.CopyN(output, reader, limit)
+	if err != nil && err != io.EOF {
 		return nil, err
 	}
+	if n == limit {
+		return nil, fmt.Errorf("uncompressed data would be too large (>%d bytes)", maxCompressedSize)
+	}
 
 	err = reader.Close()
 	return output.Bytes(), err
diff --git a/vendor/gopkg.in/go-jose/go-jose.v2/symmetric.go b/vendor/gopkg.in/go-jose/go-jose.v2/symmetric.go
index 2b8076f5f..52c8b62bf 100644
--- a/vendor/gopkg.in/go-jose/go-jose.v2/symmetric.go
+++ b/vendor/gopkg.in/go-jose/go-jose.v2/symmetric.go
@@ -402,6 +402,11 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
 		if p2c <= 0 {
 			return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: must be a positive integer")
 		}
+		if p2c > 1000000 {
+			// An unauthenticated attacker can set a high P2C value. Set an upper limit to avoid
+			// DoS attacks.
+			return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: too high")
+		}
 
 		// salt is UTF8(Alg) || 0x00 || Salt Input
 		alg := headers.getAlgorithm()
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 1d9e0769c..29fbcc4c7 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -471,7 +471,7 @@ github.com/go-chi/chi/middleware
 # github.com/go-ini/ini v1.67.0
 ## explicit
 github.com/go-ini/ini
-# github.com/go-jose/go-jose/v3 v3.0.2
+# github.com/go-jose/go-jose/v3 v3.0.3
 ## explicit; go 1.12
 github.com/go-jose/go-jose/v3
 github.com/go-jose/go-jose/v3/cipher
@@ -1600,7 +1600,7 @@ google.golang.org/protobuf/types/known/durationpb
 google.golang.org/protobuf/types/known/structpb
 google.golang.org/protobuf/types/known/timestamppb
 google.golang.org/protobuf/types/pluginpb
-# gopkg.in/go-jose/go-jose.v2 v2.6.1
+# gopkg.in/go-jose/go-jose.v2 v2.6.3
 ## explicit
 gopkg.in/go-jose/go-jose.v2
 gopkg.in/go-jose/go-jose.v2/cipher


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 77f57b724..afc091461 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -33,10 +33,7 @@ jobs:
         with:
           go-version: ${{ env.GO_VERSION }}
       - name: Install dependencies
-        run: |
-          sudo apt update
-          sudo apt install -y libseccomp-dev libelf-dev libapparmor-dev
-          sudo hack/install-libbpf.sh
+        run: sudo hack/install-packages
       - run: make test-unit
       - uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab
         with:
@@ -166,6 +163,18 @@ jobs:
           $RUN "echo \"export E2E_TEST_FLAKY_TESTS_ONLY=true\" >> /vagrant/hack/ci/env-flatcar.sh"
           $RUN hack/ci/e2e-flatcar-dev-container.sh
 
+  e2e-spoc:
+    runs-on: ubuntu-22.04
+    timeout-minutes: 90
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3.5.2
+      - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
+        with:
+          go-version: ${{ env.GO_VERSION }}
+      - name: Install dependencies
+        run: sudo hack/install-packages
+      - run: make test-spoc-e2e
+
   baseprofiles:
     needs: image
     runs-on: ubuntu-22.04
diff --git a/test/spoc/.gitignore b/test/spoc/.gitignore
new file mode 100644
index 000000000..d92338fbb
--- /dev/null
+++ b/test/spoc/.gitignore
@@ -0,0 +1,2 @@
+demobinary
+demobinary-child
diff --git a/test/spoc/demobinary.go b/test/spoc/demobinary.go
new file mode 100644
index 000000000..f99e077ac
--- /dev/null
+++ b/test/spoc/demobinary.go
@@ -0,0 +1,142 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+// #cgo LDFLAGS: -ldl
+// #include <dlfcn.h>
+import "C"
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"os/exec"
+	"strings"
+	"syscall"
+	"time"
+)
+
+const LogPrefixEnvVar = "LOGPREFIX"
+
+// Demo binary to exercise various capabilities that may be restricted by seccomp/apparmor.
+func main() {
+	log.SetPrefix(fmt.Sprintf("%s[pid:%d] ", os.Getenv(LogPrefixEnvVar), os.Getpid()))
+	log.SetFlags(log.Lshortfile)
+	log.Println("⏩", os.Args)
+
+	fileWrite := flag.String("file-write", "", "write file (e.g. /dev/null)")
+	fileRead := flag.String("file-read", "", "read file (e.g. /dev/null)")
+	fileSymlink := flag.String("file-symlink", "", "Create symlink using the following syntax: OLD:NEW")
+	netTCP := flag.Bool("net-tcp", false, "spawn a tcp server")
+	netUDP := flag.Bool("net-udp", false, "spawn a udp server")
+	netIcmp := flag.Bool("net-icmp", false, "open an icmp socket, exercise NET_RAW capability.")
+	library := flag.String("load-library", "", "load a shared library")
+	sleep := flag.Int("sleep", 0, "sleep N seconds before exiting.")
+	crash := flag.Bool("crash", false, "crash instead of exiting.")
+
+	flag.Parse()
+
+	subprocess := flag.Args()
+
+	if *fileWrite != "" {
+		const fileMode = 0o666
+		err := os.WriteFile(*fileWrite, []byte{}, fileMode)
+		if err != nil {
+			log.Fatal("❌ Error creating file:", err)
+		}
+		log.Println("✅ File write successful:", *fileWrite)
+		// make file writable for other users so that sudo/non-sudo testing works.
+		err = os.Chmod(*fileWrite, fileMode)
+		if err != nil {
+			log.Fatal("❌ Error setting file permissions:", err)
+		}
+	}
+	if *fileSymlink != "" {
+		oldname, newname, found := strings.Cut(*fileSymlink, ":")
+		if !found {
+			log.Fatal("❌ Symlink syntax: OLD:NEW")
+		}
+		err := os.Symlink(oldname, newname)
+		if err != nil {
+			log.Fatal("❌ Error creating symlink:", err)
+		}
+		log.Println("✅ Symlink created:", newname, "->", oldname)
+	}
+	if *fileRead != "" {
+		_, err := os.ReadFile(*fileRead)
+		if err != nil {
+			log.Fatal("❌ Error reading file:", err)
+		}
+		log.Println("✅ File read successful:", *fileRead)
+	}
+	if *netTCP {
+		listener, err := net.Listen("tcp", ":0")
+		if err != nil {
+			log.Fatal("❌ Error starting TCP server:", err)
+		}
+		log.Println("✅ TCP server spawned:", listener.Addr())
+		defer listener.Close()
+	}
+	if *netUDP {
+		server, err := net.ListenPacket("udp", ":0")
+		if err != nil {
+			//nolint:gocritic  // gocritic is terminally confused here.
+			log.Fatal("❌ Error starting UDP server:", err)
+		}
+		log.Println("✅ UDP server spawned:", server.LocalAddr())
+		defer server.Close()
+	}
+	if *netIcmp {
+		fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP)
+		if err != nil {
+			log.Fatal("❌ Error opening ICMP socket:", err)
+		}
+		log.Println("✅ ICMP socket opened: fd", fd)
+		defer syscall.Close(fd)
+	}
+	if len(subprocess) > 0 {
+		cmd := exec.Command(subprocess[0], subprocess[1:]...)
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		cmd.Env = append(os.Environ(), "LOGPREFIX=\t"+os.Getenv(LogPrefixEnvVar))
+		err := cmd.Run()
+		if err != nil {
+			log.Fatal("❌ Error running subprocess:", err)
+		}
+		log.Println("✅ Subprocess ran successfully:", subprocess)
+	}
+	if *library != "" {
+		if handle := C.dlopen(C.CString(*library), C.RTLD_NOW); handle == nil {
+			log.Fatal("❌ Error loading library: ", C.GoString(C.dlerror()))
+		}
+		log.Println("✅ Library loaded successfully:", *library)
+	}
+	if *sleep > 0 {
+		log.Println("⏳ Sleeping for", *sleep, "seconds...")
+		time.Sleep(time.Duration(*sleep) * time.Second)
+	}
+	if *crash {
+		log.Println("🫡  Terminating with SIGKILL...")
+		err := syscall.Kill(syscall.Getpid(), syscall.SIGKILL)
+		if err != nil {
+			log.Fatal("❌ Error sending SIGKILL:", err)
+		}
+	}
+	log.Println("⭐️ Success.")
+}
diff --git a/test/spoc/e2e_spoc_test.go b/test/spoc/e2e_spoc_test.go
new file mode 100644
index 000000000..e63615ccd
--- /dev/null
+++ b/test/spoc/e2e_spoc_test.go
@@ -0,0 +1,120 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main_test
+
+import (
+	"os"
+	"os/exec"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/release-utils/util"
+	"sigs.k8s.io/yaml"
+
+	apparmorprofileapi "sigs.k8s.io/security-profiles-operator/api/apparmorprofile/v1alpha1"
+	seccompprofileapi "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1beta1"
+)
+
+//nolint:paralleltest // should not run in parallel
+func TestSpoc(t *testing.T) {
+	cmd := exec.Command("go", "build", "demobinary.go")
+	err := cmd.Run()
+	require.Nil(t, err, "failed to build demobinary.go")
+	err = util.CopyFileLocal("demobinary", "demobinary-child", true)
+	require.Nil(t, err)
+	err = os.Chmod("demobinary-child", 0o700)
+	require.Nil(t, err)
+
+	t.Run("record", recordTest)
+}
+
+func recordTest(t *testing.T) {
+	t.Run("AppArmor", recordAppArmorTest)
+	t.Run("Seccomp", recordSeccompTest)
+}
+
+func recordAppArmorTest(t *testing.T) {
+	t.Run("files", func(t *testing.T) {
+		profile := recordAppArmor(t, "--file-read", "../../README.md", "--file-write", "/dev/null")
+		// TODO: This is still wrong - it should be an absolute path.
+		require.Contains(t, *profile.Filesystem.ReadOnlyPaths, "../../README.md")
+		require.Contains(t, *profile.Filesystem.WriteOnlyPaths, "/dev/null")
+
+		profile = recordAppArmor(t, "--file-read", "/dev/null", "--file-write", "/dev/null")
+		require.Contains(t, *profile.Filesystem.ReadWritePaths, "/dev/null")
+	})
+	t.Run("sockets", func(t *testing.T) {
+		profile := recordAppArmor(t, "--net-tcp")
+		require.True(t, *profile.Network.Protocols.AllowTCP)
+		profile = recordAppArmor(t, "--net-udp")
+		require.True(t, *profile.Network.Protocols.AllowUDP)
+		profile = recordAppArmor(t, "--net-icmp")
+		require.True(t, *profile.Network.AllowRaw)
+	})
+
+	t.Run("subprocess", func(t *testing.T) {
+		profile := recordAppArmor(t, "./demobinary-child", "--file-read", "/dev/null")
+		require.Contains(t, (*profile.Executable.AllowedExecutables)[0], "/demobinary-child")
+		// TODO: requires child process tracing
+		// require.Contains(t, *profile.Filesystem.ReadOnlyPaths, "/dev/null")
+
+		profile = recordAppArmor(t, "./demobinary", "--file-read", "/dev/null")
+		require.Contains(t, (*profile.Executable.AllowedExecutables)[0], "/demobinary")
+		// TODO: requires child process tracing
+		// require.Contains(t, *profile.Filesystem.ReadOnlyPaths, "/dev/null")
+	})
+}
+
+func recordSeccompTest(t *testing.T) {
+	profile := recordSeccomp(t, "--net-tcp")
+	require.Contains(t, profile.Syscalls[0].Names, "listen")
+}
+
+func runSpoc(t *testing.T, args ...string) []byte {
+	t.Helper()
+	args = append([]string{"../../build/spoc"}, args...)
+	cmd := exec.Command("sudo", args...)
+	cmd.Stderr = os.Stderr
+	out, err := cmd.Output()
+	require.Nil(t, err, "failed to run spoc")
+	return out
+}
+
+func record(t *testing.T, typ string, profile client.Object, args ...string) {
+	t.Helper()
+	args = append([]string{
+		"record", "-t", typ, "-o", "/dev/stdout", "--no-base-syscalls", "./demobinary",
+	}, args...)
+	content := runSpoc(t, args...)
+	err := yaml.Unmarshal(content, &profile)
+	require.Nil(t, err, "failed to parse yaml")
+}
+
+func recordAppArmor(t *testing.T, args ...string) apparmorprofileapi.AppArmorAbstract {
+	t.Helper()
+	profile := apparmorprofileapi.AppArmorProfile{}
+	record(t, "apparmor", &profile, args...)
+	return profile.Spec.Abstract
+}
+
+func recordSeccomp(t *testing.T, args ...string) seccompprofileapi.SeccompProfileSpec {
+	t.Helper()
+	profile := seccompprofileapi.SeccompProfile{}
+	record(t, "seccomp", &profile, args...)
+	return profile.Spec
+}