Skip to content

feat(builtins): logrotate demo builtin (host-remediation series)#247

Draft
julesmcrt wants to merge 2 commits into
jules.macret/host-remediation/truncatefrom
jules.macret/host-remediation/logrotate
Draft

feat(builtins): logrotate demo builtin (host-remediation series)#247
julesmcrt wants to merge 2 commits into
jules.macret/host-remediation/truncatefrom
jules.macret/host-remediation/logrotate

Conversation

@julesmcrt
Copy link
Copy Markdown
Collaborator

Summary

Adds a logrotate builtin intended for the host-remediation demo. It wraps the existing AllowedPaths.Truncate capability behind a familiar command name so an agent can free disk space by emptying runaway log files via logrotate /var/log/app.log. Demo-only — not intended to merge to main.

  • Stacked on top of jules.macret/host-remediation/truncate. Re-target to main (and rebase) if/when that branch lands.
  • Hard scope cap: this builtin only truncates files. There is no rename-based rotation, no compression, no config parsing, no post-rotate scripts. The -k N flag is recorded for -v reporting only — the sandbox has no Rename capability today, so prior copies are not retained. This trade-off is documented in the package comment, SHELL_FEATURES.md, and the --help output.

Flags

Flag Behavior
-h, --help Print usage to stdout, exit 0
-s SIZE Skip files smaller than SIZE. Accepts K/M/G/T (1024-based, leading letter case-insensitive) or KB/MB/GB/TB (1000-based)
-k N Informational. Real logrotate(8) retains N rotated copies; here it is only echoed under -v
-v Per-file line: logrotate: PATH: truncated N bytes (keep=K) or ... below threshold N, skipping

Wiring

  • builtins/logrotate/logrotate.go — implementation
  • interp/register_builtins.go — command registration
  • analysis/symbols_builtins.go — per-command symbol allowlist (context.Context, errors.New, math.MaxInt64, strconv.ParseInt)
  • SHELL_FEATURES.md — user-facing feature line

Sandbox-safety properties

  • All file access routes through callCtx.Truncate and callCtx.StatFile; no direct os filesystem calls.
  • No write of arbitrary bytes — only ftruncate to size 0.
  • Numeric inputs (-s, -k) bounds-checked; parseSize rejects overflow via MaxInt64 / mult guard.

Test plan

  • go build ./... — clean
  • go test ./builtins/... ./interp/... ./analysis/... -timeout 120s — all green
  • 10 new scenario tests under tests/scenarios/cmd/logrotate/ (basic, threshold above/below, verbose truncated/skipped, help, errors: missing operand / invalid size / missing target) — all pass via go test ./tests/ -run TestShellScenarios/cmd/logrotate
  • End-to-end smoke test: created a tmp log, ran logrotate -v -k 3 -s 10 <path> through cmd/rshell --allowed-paths $TMPDIR, confirmed the file was truncated to 0 and the verbose line matched expectation
  • go run ./cmd/rshellhelp logrotate prints the registered help text
  • Bash comparison suite (RSHELL_BASH_TEST=1 go test ./tests/ -run TestShellScenariosAgainstBash) — N/A; every logrotate scenario sets skip_assert_against_bash: true because logrotate is not present in the bash comparison image and would always fail

Wraps the existing AllowedPaths Truncate capability behind a logrotate-
shaped command intended for agent-driven disk remediation. Supports -h,
-s SIZE (threshold), -k N (informational only — sandbox has no rename
capability), and -v. Not a substitute for logrotate(8): no config, no
rename-based rotation, no compression, no post-rotate scripts.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@julesmcrt
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee95f8bc94

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread builtins/logrotate/logrotate.go Outdated
Logrotate previously called callCtx.StatFile to gate -s and then
callCtx.Truncate, which reopens the path separately — a concurrent
swap of a smaller file under the same path between the two calls
could fool the threshold check.

Add Sandbox.TruncateIfLarger, which opens the path with O_WRONLY,
fstats the resulting fd, and ftruncates only when the fd's size is
at least minSize. Stat and truncate share a single fd, so the size
used for the threshold decision is the size of the inode that is
actually truncated. Existing safety properties of Sandbox.Truncate
(IsRegular check, write-symlink rejection, EINVAL on negative size)
are preserved.

Expose the primitive on CallContext, wire it up in interp's two
CallContext construction sites, and refactor logrotate to drop the
separate StatFile call.

Addresses Codex review on PR #247.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@julesmcrt
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b1b308344

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// on the same fd. This closes the path-stat to path-truncate
// TOCTOU window — the size used for the threshold decision is
// the size of the inode that will actually be truncated.
sizeBefore, truncated, err := callCtx.TruncateIfLarger(ctx, file, threshold, 0, false)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid truncating logrotate config operands

When an existing script or agent uses the standard logrotate invocation and passes a config file, this line treats that operand as the log to empty and truncates it. The upstream logrotate(8) synopsis takes config_file [config_file2 ...] operands, so with AllowedPaths covering /etc or a staged config directory, logrotate /etc/logrotate.conf would destroy the configuration instead of parsing it; consider renaming this demo command or refusing config-like operands unless an explicit destructive mode is requested. See: https://man7.org/linux/man-pages/man8/logrotate.8.html

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Claude Opus 4.7] Acknowledged but deferring — this PR is demo-only (won't merge to main) and the destructive-by-default behavior is intentional to keep agent ergonomics close to truncate -s 0 <file>. The logrotate(8) name collision is real; the production resolution would be either a required --in-place opt-in flag (default = dry-run) or a rename to truncate-log. Leaving the thread open so the trade-off is visible.

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.

1 participant