Commit Graph

21 Commits

Author SHA1 Message Date
bahdotsh
0ffc20dece fix(executor)!: pass explicit build context to Docker image builds
It turns out that build_image_inner was deriving the Docker build
context from dockerfile.parent(), which is *wrong* when the
Dockerfile lives in a subdirectory of the action root. An action
with runs.image: subdir/Dockerfile would get subdir/ as its build
context instead of the action root, silently breaking every COPY
instruction that references files outside that subdirectory.

The fix is straightforward: add an explicit context_dir parameter
to the ContainerRuntime::build_image trait so callers tell us what
the context is instead of us guessing from the Dockerfile path.
The DockerBuild path in engine.rs now passes &action_dir, and the
Docker inner implementation computes the Dockerfile path relative
to context_dir via strip_prefix instead of just using file_name().

While at it, add a warning log when shlex::split fails to parse
with.args (unmatched quotes). Previously this silently fell back
to naive whitespace splitting, which is the kind of thing that
makes you stare at container logs for an hour wondering why your
quoted argument got split into three pieces.
2026-04-02 00:51:32 +05:30
bahdotsh
894b71b0a0 fix(executor): fix broken args parsing, empty dockerfile path, and silent entrypoint drop
Three correctness bugs found during review of the Docker action
execution path:

1. with.args was being split on whitespace like a caveman. An
   argument like "hello world" would turn into two separate args,
   which is *not* how GitHub Actions works. Use shlex::split() for
   proper shell-word parsing, with a whitespace fallback for
   malformed input that shlex chokes on.

2. sanitize_dockerfile_rel() happily accepted empty strings. Feed
   it "" or "docker://" and it would produce an empty path, which
   then joins to a directory instead of a file. The subsequent
   docker build would fail with a confusing error. Let's just
   reject empty paths upfront.

3. SecureEmulationRuntime silently swallowed the entrypoint
   override without telling anyone. If you're running in secure
   emulation mode and your action specifies runs.entrypoint, you
   deserve to know it's being ignored — not left wondering why
   your action isn't doing what you expect.
2026-04-02 00:45:05 +05:30
bahdotsh
ec360b2c2f fix(executor): wire up runs.entrypoint, runs.args, and fix local Docker dispatch
The previous commits got the NativeDocker path working for remote
actions, but left several holes that a code review correctly
identified. Let's fix them all.

First, local Docker actions (uses: ./my-action with a Dockerfile)
were *still* returning PreparedAction::Image instead of NativeDocker.
Same class of bug we just fixed for remote actions, hiding one
function call away. They now go through NativeDocker and parse the
local action.yml for entrypoint/args.

Second, runs.entrypoint and runs.args from action.yml were being
completely ignored. Docker actions that declare their entrypoint in
action.yml (which is, you know, *a lot of them*) would silently
use the wrong entrypoint. Add an entrypoint parameter to the
ContainerRuntime trait and thread it through all four implementations:
Docker sets Config.entrypoint, Podman passes --entrypoint, and the
emulation runtimes accept-and-ignore it.

Third, with.args from workflow steps (uses: docker://alpine with
args: "echo hello") was not being passed as container CMD. It now
overrides runs.args when present, matching GitHub Actions behavior.

While at it:
- Extract sanitize_dockerfile_rel into a real function instead of
  having the tests duplicate the logic and test their own copy.
  Testing a copy of your code instead of the actual code is not
  what I'd call confidence-inspiring.
- Add canonicalize() defense-in-depth after Dockerfile path
  resolution to catch symlink escapes.
- Document the build_image_inner context directory invariant.
2026-04-02 00:38:46 +05:30
bahdotsh
aecf6f68f3 fix(executor): fix macOS entrypoint hang, path traversal, and silent emulation pass
Three bugs in the Docker action execution path from the previous
commit:

1. The macOS emulation entrypoint override (`bash -l -c`) was applied
*unconditionally*, even when cmd was empty (NativeDocker path). That
means Docker actions running on macOS emu images would get bash with
no argument — which either hangs forever or exits immediately. The
image's real ENTRYPOINT gets discarded either way. This is not great.

Fix: capture `has_cmd` before cmd_vec is moved into the config, only
apply the bash wrapper when there's actually a command to wrap.

2. The `dockerfile_rel` extracted from action.yml's `runs.image` was
not sanitized after stripping the `docker://` prefix. A malicious
action.yml with `docker:///etc/shadow` or `../../sensitive` would
escape the action directory via Path::join's absolute-path behavior
or dotdot traversal.

Fix: strip leading slashes and reject any path containing `..`.

3. Emulation mode returned exit_code 0 for Docker actions it *didn't
actually run*. Users got a green checkmark for actions that were
silently skipped. Confusion ensues.

Fix: return exit_code 1 with a clear stderr message explaining the
action was not executed and needs --runtime docker.

While at it, add tests for all three fixes: NativeDocker variant
construction, dockerfile path sanitization (6 cases), and emulation
empty-cmd failure behavior.
2026-04-01 23:56:52 +05:30
bahdotsh
59efb5e108 fix(executor): actually execute Docker-based GitHub Actions instead of emulating them
Third-party GitHub Actions that use Docker (like super-linter) were
silently passing without ever *actually running*. The engine would
resolve the action, pick a Docker image, and then... run
`echo 'Would execute GitHub action: ...'` inside it. Every single
time. Regardless of runtime mode. Confusion ensues.

It turns out there were two separate failures conspiring here:

1. `prepare_action()` would error out on `ActionType::DockerBuild`
   with "not yet supported", fall back to `determine_action_image()`,
   and cheerfully return `node:20-slim` for super-linter. This is
   not great.

2. The `PreparedAction::Image` execution branch had three sub-paths
   for is_docker, is_local, and everything else — and *all three*
   just ran echo commands. The image was resolved correctly and then
   completely ignored.

The fix has several parts:

- Add a `NativeDocker` variant to `PreparedAction` that means "run
  this image with its built-in ENTRYPOINT, no command override."
  Docker registry actions and DockerBuild actions both use this.

- Implement DockerBuild properly: clone the repo, resolve the
  Dockerfile path from action.yml, build it, return the tag.
  Uses the existing `shallow_clone` and `runtime.build_image`.

- Fix `build_image_inner` to tar the *full context directory*
  instead of just the Dockerfile. The old code had `_context_dir`
  sitting right there, computed and unused. COPY instructions in
  Dockerfiles need the context, obviously.

- Allow empty `cmd` in `run_container` to mean "use the image's
  default ENTRYPOINT/CMD". The Docker impl now sets `config.cmd =
  None` when cmd is empty. Podman already handled this correctly.

The existing `PreparedAction::Image` path with all its special-cased
action handling (actions-rs, checkout, etc.) is completely untouched.

Closes #59
2026-04-01 23:43:27 +05:30
bahdotsh
b49276a026 fix: stop hard-exiting on unreadable directory and add #[must_use] to ContainerOutput
The validate subcommand was calling std::process::exit(1) when a
directory couldn't be read, which is a rather aggressive response
to a permission error. Especially when the code four lines above
handles a *missing* path by setting validation_failed and moving
on to the next one. Consistency is nice. Let's have some.

Split the match from the method chain (because continue is a
statement, not an expression, and Rust has opinions about that)
and replaced the exit(1) with the same continue pattern.

While at it, slap #[must_use] on ContainerOutput so the compiler
will yell at anyone who discards a run_container result without
checking exit_code. All current callers already bind it, so this
is purely forward-looking — but the kind of bug it prevents is
the silent-misexecution kind, and those are nobody's favorite.
2026-04-01 19:28:15 +05:30
bahdotsh
422a035c40 test: add tests for review fixes and clean up dead code
The previous commit fixed a bunch of bugs but left a few loose
ends. The next_job() function still had a redundant bounds check
that previous_job() already had cleaned up — the .filter() call
makes the inner `if workflow_idx >= self.workflows.len()` dead
code. Let's not leave half-finished refactors lying around.

While at it, add tests for the three behavioral changes that
*really* should have had tests from the start: emulation runtime
returning Ok on non-zero exit codes, log processor not panicking
on multi-byte UTF-8 near bracket boundaries, and step validator
correctly rejecting steps with only a name field.

Also fix formatting (cargo fmt) and a clippy warning about items
defined after the test module.
2026-04-01 19:08:44 +05:30
bahdotsh
aa3366a797 fix: correct multiple bugs found during full codebase review
It turns out that build_image_inner() in docker.rs was calling
.elapsed() on a SystemTime to compute the tar mtime. That gives
you "seconds since modification" — which is *not* what mtime
means. Mtime is seconds since the Unix epoch. The fix is
.duration_since(UNIX_EPOCH) like a normal person would use.

While at it, the docker logs() call was passing None for options,
which means it wasn't actually requesting stdout or stderr. So
we were collecting logs from a stream that might not have any.
Explicitly set stdout: true and stderr: true.

The emulation runtime had a fun behavioral mismatch with Docker
and Podman: it returned Err on non-zero exit codes, swallowing
all stdout/stderr output. Docker and Podman return Ok with the
exit code and let the caller decide what to do. The engine
already handles non-zero exit codes in the Ok path, so the
emulation was just silently eating useful output for no reason.

The UI had a bounds check in next_job() that was mysteriously
absent from previous_job() — the kind of inconsistency that
waits patiently for someone to hit a stale workflow index and
get a panic. Added the same .filter() guard.

String slicing in the log processor wasn't checking char
boundaries, which is fine until someone's log contains a
multi-byte UTF-8 character before a bracket. Added
is_char_boundary() checks.

Step validation was accepting steps with only a 'name' field
and no 'uses' or 'run', which is not a valid step in GitHub
Actions. Fixed the validation to require at least one of the
two fields that actually *do* something.

Replaced .expect() calls on directory reads in main.rs with
proper error handling. Panicking because a directory isn't
readable is not great user experience.
2026-04-01 18:59:31 +05:30
bahdotsh
b1cc74639c version fix 2025-09-05 08:22:15 +05:30
bahdotsh
51a655f07b version fixes 2025-08-28 12:56:05 +05:30
bahdotsh
f49ccd70d9 fix(runtime): remove unnecessary borrow in Windows taskkill command
- Fix clippy needless_borrows_for_generic_args warning
- Change &pid.to_string() to pid.to_string() for taskkill /PID argument
- Ensure clippy passes with -D warnings on Windows builds
2025-08-27 15:45:58 +05:30
bahdotsh
f1ca411281 feat(runtime): add dtolnay/rust-toolchain action support
- Add emulation support for dtolnay/rust-toolchain@ actions
- Include Rust and Cargo availability checks for dtolnay toolchain action
- Improve action detection logging for dtolnay Rust toolchain

Related to #49
2025-08-21 22:28:12 +05:30
bahdotsh
8005cbb7ee feat: Add .gitignore support for file copying
- Add ignore crate dependency to executor and runtime crates
- Implement gitignore-aware file copying in engine.rs and emulation.rs
- Support for .gitignore patterns, whitelist rules, and default ignore patterns
- Maintain backward compatibility with projects without .gitignore files
- Add proper error handling and debug logging for ignored files

This ensures that files marked in .gitignore are not copied to containers
or emulation workspaces, improving performance and security.
2025-08-21 15:27:00 +05:30
bahdotsh
960f7486a2 Release 0.7.0
wrkflw@0.7.0
wrkflw-evaluator@0.7.0
wrkflw-executor@0.7.0
wrkflw-github@0.7.0
wrkflw-gitlab@0.7.0
wrkflw-logging@0.7.0
wrkflw-matrix@0.7.0
wrkflw-models@0.7.0
wrkflw-parser@0.7.0
wrkflw-runtime@0.7.0
wrkflw-ui@0.7.0
wrkflw-utils@0.7.0
wrkflw-validators@0.7.0

Generated by cargo-workspaces
2025-08-13 18:07:11 +05:30
bahdotsh
460357d9fe feat: Add comprehensive sandboxing for secure emulation mode
Security Features:
- Implement secure emulation runtime with command sandboxing
- Add command validation, filtering, and dangerous pattern detection
- Block harmful commands like 'rm -rf /', 'sudo', 'dd', etc.
- Add resource limits (CPU, memory, execution time, process count)
- Implement filesystem isolation and access controls
- Add environment variable sanitization
- Support shell operators (&&, ||, |, ;) with proper parsing

New Runtime Mode:
- Add 'secure-emulation' runtime option to CLI
- Update UI to support new runtime mode with green security indicator
- Mark legacy 'emulation' mode as unsafe in help text
- Default to secure mode for local development safety

Documentation:
- Create comprehensive security documentation (README_SECURITY.md)
- Update main README with security mode information
- Add example workflows demonstrating safe vs dangerous commands
- Include migration guide and best practices

Testing:
- Add comprehensive test suite for sandbox functionality
- Include security demo workflows for testing
- Test dangerous command blocking and safe command execution
- Verify resource limits and timeout functionality

Code Quality:
- Fix all clippy warnings with proper struct initialization
- Add proper error handling and user-friendly security messages
- Implement comprehensive logging for security events
- Follow Rust best practices throughout

This addresses security concerns by preventing accidental harmful
commands while maintaining full compatibility with legitimate CI/CD
workflows. Users can now safely run untrusted workflows locally
without risk to their host system.
2025-08-13 14:30:51 +05:30
bahdotsh
58de01e69f docs(readme): add per-crate READMEs and enhance wrkflw crate README 2025-08-12 15:09:38 +05:30
bahdotsh
537bf2f9d1 chore: bump version to 0.6.0
- Updated workspace version from 0.5.0 to 0.6.0
- Updated all internal crate dependencies to 0.6.0
- Verified all tests pass and builds succeed
2025-08-09 17:46:09 +05:30
bahdotsh
f0b6633cb8 renamed 2025-08-09 17:03:03 +05:30
bahdotsh
1cc3bf98b6 feat: bump version to 0.5.0 for podman support 2025-08-09 15:24:49 +05:30
bahdotsh
00fa569add fix: fixed the issues in viewing step details in non verbose mode 2025-05-02 15:45:51 +05:30
bahdotsh
470132c5bf Refactor: Migrate modules to workspace crates
- Extracted functionality from the `src/` directory into individual crates within the `crates/` directory. This improves modularity, organization, and separation of concerns.
- Migrated modules include: models, evaluator, ui, gitlab, utils, logging, github, matrix, executor, runtime, parser, and validators.
- Removed the original source files and directories from `src/` after successful migration.
- This change sets the stage for better code management and potentially independent development/versioning of workspace members.
2025-05-02 12:53:41 +05:30