Skip to content

R0001 R0007 R1001 R1004: fall back to event.exepath in AP lookup#32

Merged
slashben merged 2 commits intomainfrom
naut-i004-i011-r1001-r1004-r0001-exepath-fallback
May 4, 2026
Merged

R0001 R0007 R1001 R1004: fall back to event.exepath in AP lookup#32
slashben merged 2 commits intomainfrom
naut-i004-i011-r1001-r1004-r0001-exepath-fallback

Conversation

@slashben
Copy link
Copy Markdown
Contributor

@slashben slashben commented May 4, 2026

Summary

Patches R0001, R0007, R1001, R1004 to also check event.exepath against the AP when the original parse.get_exec_path(event.args, event.comm) lookup misses. Closes the symmetric-with-recorder gap left by node-agent PR #800: the recorder prefers kernel-authoritative event.exepath, so the rule's lookup key should match.

Why

parse.get_exec_path(event.args, event.comm) returns argv[0] verbatim. For two real-world patterns, that disagrees with the path the AP recorder stores:

  • Relative argv[0]: a process invoked from a working directory (e.g. ./python from a _work/_tool/Python/<ver>/x64/bin cwd). AP records the resolved absolute path (post-PR #800, kernel-authoritative). Rule looks up ./python → no match → fires.
  • Empty argv[0] (fexecve / AT_EMPTY_PATH): modern libpam invokes unix_chkpwd via fexecve, which surfaces as argv[0] == "". PR #800 fixes the recorder to fall back to exepath; this PR mirrors the fix on the rule side.

Plus argv[0] spoofing as a quiet detection gap covered for free by the same change.

Approach

Per-rule expression patch — no helper changes. Each rule keeps the existing parse.get_exec_path(...) lookup and adds a second ap.was_executed(..., event.exepath) lookup with an empty-string guard. Suppresses if either lookup matches; fires only when both miss.

!ap.was_executed(event.containerId, parse.get_exec_path(event.args, event.comm))
  && (event.exepath == "" || !ap.was_executed(event.containerId, event.exepath))

The empty-string guard avoids false suppression for legacy AP entries with Path: "" (pre-#800 unix_chkpwd recordings — separate I013 issue).

Why not change parse.get_exec_path?

Considered, but signature changes propagate across every rule call site and test. Operational burden too high for the value. The per-rule patch is three lines of CEL across three YAMLs.

Test plan

  • go test ./pkg/rules/r0001-unexpected-process-launched/...
  • go test ./pkg/rules/r1001-exec-binary-not-in-base-image/...
  • go test ./pkg/rules/r1004-exec-from-mount/...
  • New per-rule sub-tests cover: relative argv[0] (./python), empty argv[0] (fexecve), empty exepath fallback, both-miss-still-fires.

References

parse.get_exec_path(event.args, event.comm) returns argv[0] verbatim,
which disagrees with the kernel-authoritative event.exepath that the
recorder stores in the AP for two real-world patterns:

- Relative argv[0] (e.g. "./python") — executes from cwd, AP records
  the resolved absolute path.
- Empty argv[0] from fexecve / AT_EMPTY_PATH (e.g. modern libpam ->
  unix_chkpwd) — AP records the resolved exepath.

In both cases the rule's lookup key (argv[0]) does not match the AP's
storage key (exepath), so the rule fires on a legitimate, profiled exec.

This patch adds a second AP lookup using event.exepath, with an empty-
string guard so legacy AP entries with Path: "" can't false-suppress.
The rule fires only when both lookups miss.

Mirrors the recorder-side fix in node-agent PR #800.
Signed-off-by: Ben H <ben@armosec.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

This PR extends three detection rules (R0001, R1001, R1004) to support event.exepath as a fallback when matching against application profile execution records. Each rule's expression is updated to additionally check whether event.exepath is empty or previously executed, and comprehensive tests validate the new fallback behavior across multiple event scenarios.

Changes

Rule Expression Updates & Test Coverage

Layer / File(s) Summary
Rule Expression Updates
pkg/rules/r0001-unexpected-process-launched/unexpected-process-launched.yaml, pkg/rules/r1001-exec-binary-not-in-base-image/exec-binary-not-in-base-image.yaml, pkg/rules/r1004-exec-from-mount/exec-from-mount.yaml
R0001, R1001, and R1004 rule expressions are expanded to check event.exepath as a fallback. Each rule now includes a condition (event.exepath == "" || !ap.was_executed(event.containerId, event.exepath)) to suppress alerts when the exepath was already seen in the application profile, in addition to existing argv[0]-derived path checks.
Test Coverage for Exepath Fallback
pkg/rules/r0001-unexpected-process-launched/rule_test.go, pkg/rules/r1001-exec-binary-not-in-base-image/rule_test.go, pkg/rules/r1004-exec-from-mount/rule_test.go
Three new table-driven tests (TestR0001ExepathFallback, TestR1001ExepathFallback, TestR1004ExepathFallback) validate rule triggering behavior across scenarios where argv[0] and exepath differ, ensuring exepath suppresses alerts when present in the application profile, and that empty exepath does not pollute matching logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • matthyx

Poem

🐰 A rabbit hops through three rules today,
With exepath fallback leading the way—
When argv fades, exepath shall shine,
Profile matches keep the alerts aligned!
✨ Hop, test, and launch with pride! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding event.exepath fallback to application profile (AP) lookup across three rules (R0001, R1001, R1004).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch naut-i004-i011-r1001-r1004-r0001-exepath-fallback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/rules/r0001-unexpected-process-launched/rule_test.go (1)

182-198: ⚡ Quick win

Add a direct regression case for legacy AP Path: "" with empty exepath.

Current “empty exepath guard” coverage does not directly assert the exact legacy-empty-path scenario called out in the PR rationale. Adding one table row would lock this down and prevent regressions.

Proposed test-case addition
@@
 		{
 			name: "empty exepath fallback guard — argv[0] match suppresses",
@@
 			description:   "exepath='' must not poll the AP; argv[0] '/usr/bin/foo' alone suffices to suppress",
 		},
+		{
+			name: "empty exepath does not suppress via legacy empty-path AP entry",
+			event: &utils.StructEvent{
+				Args:        []string{"./foo"},
+				Comm:        "foo",
+				Container:   "test",
+				ContainerID: "test",
+				EventType:   utils.ExecveEventType,
+				ExePath:     "",
+				Pcomm:       "bash",
+				Pid:         1234,
+			},
+			profileExecs: []v1beta1.ExecCalls{
+				{Path: "", Args: []string{"./foo"}},
+			},
+			expectTrigger: true,
+			description:   "guard should skip AP lookup on empty exepath; legacy empty-path AP entry must not suppress",
+		},
 		{
 			name: "both miss — rule still fires",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/rules/r0001-unexpected-process-launched/rule_test.go` around lines 182 -
198, Add a regression table test row that covers the legacy policy-agent
empty-path case: create a case where the event has ExePath: "" and Args:
[]string{"/usr/bin/foo"} (Comm "foo", Pcomm "bash", EventType
utils.ExecveEventType, etc.) and profileExecs contains a legacy ExecCalls entry
with Path: "" and Args: []string{"/usr/bin/foo"}; set expectTrigger to false and
give it a descriptive name like "legacy empty path guard — argv[0] match
suppresses" so the test ensures empty exepath + legacy Path:"" is handled as a
suppressing match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/rules/r0001-unexpected-process-launched/rule_test.go`:
- Around line 182-198: Add a regression table test row that covers the legacy
policy-agent empty-path case: create a case where the event has ExePath: "" and
Args: []string{"/usr/bin/foo"} (Comm "foo", Pcomm "bash", EventType
utils.ExecveEventType, etc.) and profileExecs contains a legacy ExecCalls entry
with Path: "" and Args: []string{"/usr/bin/foo"}; set expectTrigger to false and
give it a descriptive name like "legacy empty path guard — argv[0] match
suppresses" so the test ensures empty exepath + legacy Path:"" is handled as a
suppressing match.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4fc2f298-67a2-4523-8f96-33cef865be09

📥 Commits

Reviewing files that changed from the base of the PR and between 7db50ed and e2a6952.

📒 Files selected for processing (6)
  • pkg/rules/r0001-unexpected-process-launched/rule_test.go
  • pkg/rules/r0001-unexpected-process-launched/unexpected-process-launched.yaml
  • pkg/rules/r1001-exec-binary-not-in-base-image/exec-binary-not-in-base-image.yaml
  • pkg/rules/r1001-exec-binary-not-in-base-image/rule_test.go
  • pkg/rules/r1004-exec-from-mount/exec-from-mount.yaml
  • pkg/rules/r1004-exec-from-mount/rule_test.go

@matthyx
Copy link
Copy Markdown
Contributor

matthyx commented May 4, 2026

as I said, just check R0007 for safety

Mirrors the same fix applied to R0001/R1001/R1004 in this branch:
parse.get_exec_path(event.args, event.comm) returns argv[0] verbatim,
which disagrees with the kernel-authoritative event.exepath that the
recorder stores in the AP. The exec branch of R0007 has the same
shape, so apply the same per-rule patch with empty-exepath guard.

Suggested by Matthias on PR review.
Signed-off-by: Ben H <ben@armosec.io>
@slashben
Copy link
Copy Markdown
Contributor Author

slashben commented May 4, 2026

Good call — same pattern in R0007's exec branch ((event.comm == 'kubectl' || event.exepath.endsWith('/kubectl')) && !ap.was_executed(event.containerId, parse.get_exec_path(event.args, event.comm))). A ./kubectl invocation from a directory containing kubectl would FP — the endsWith guard passes, the AP lookup against ./kubectl misses even when the absolute path is recorded.

Pushed feb98df extending the same per-rule patch to R0007 with new sub-tests covering the relative-argv[0], fexecve, empty-exepath, and both-miss cases. Tests green locally.

@slashben slashben changed the title R0001 R1001 R1004: fall back to event.exepath in AP lookup R0001 R0007 R1001 R1004: fall back to event.exepath in AP lookup May 4, 2026
Copy link
Copy Markdown
Contributor

@matthyx matthyx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks Ben

@slashben slashben merged commit b1bea54 into main May 4, 2026
4 checks passed
@slashben slashben deleted the naut-i004-i011-r1001-r1004-r0001-exepath-fallback branch May 4, 2026 07:18
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