Skip to content

feat(zapscript): harden execution pipeline#656

Merged
wizzomafizzo merged 5 commits into
mainfrom
feat/zapscript-pipeline-hardening
Apr 11, 2026
Merged

feat(zapscript): harden execution pipeline#656
wizzomafizzo merged 5 commits into
mainfrom
feat/zapscript-pipeline-hardening

Conversation

@wizzomafizzo

@wizzomafizzo wizzomafizzo commented Apr 11, 2026

Copy link
Copy Markdown
Member

Summary

  • Auto-anchor regex allowlists (allow_execute, allow_run, allow_file) to prevent substring matching
  • Remove SourceControl bypass that let launcher TOMLs skip the execute allowlist
  • Add block_commands config field to disable specific ZapScript commands by name
  • Add allow_http URL allowlist for http.get/http.post commands (empty = all allowed)
  • Add input restriction modes: combos (desktop default, blocks single char keys) and unrestricted (embedded default), with configurable allow/block lists and a default desktop block list for dangerous key combos
  • Repurpose allow_run to parse ZapScript per-command via Command.String(), preventing chained command bypass. REST run endpoints bypass allowed_ips when allow_run is configured
  • Bump go-zapscript to v0.7.0

Implements security-hardening-phase1.md item 1 (all sub-items).

Summary by CodeRabbit

  • New Features

    • HTTP URL allowlisting, command-block configuration, and input-key authorization with configurable modes and allow/block lists
    • Enhanced IP filtering for run endpoints and conditional run access
  • Bug Fixes

    • Clarified REST run denial log message
  • Refactor

    • Simplified execution permission checks and consolidated REST run routing
  • Tests

    • Expanded tests for allowlists, input restrictions, IP filtering, and HTTP/execute behaviors
  • Chores

    • Bumped go-zapscript dependency

…t, and input modes

Auto-anchor regex allowlists so patterns match the full string instead
of substrings. Remove the SourceControl bypass that let launcher TOMLs
skip the execute allowlist. Add block_commands config to disable specific
ZapScript commands by name. Add allow_http URL allowlist for http.get and
http.post commands.

Add input restriction modes: combos mode (desktop default) blocks single
character keys while allowing key combos and special keys; unrestricted
mode (embedded default) allows everything. Configurable allow list
(strict mode) and block list (replaces default desktop block list).

Repurpose allow_run to parse ZapScript and check each command
individually, preventing chained command bypass. REST run endpoints
bypass allowed_ips when allow_run is configured since the content
restriction is the security boundary.

Bump go-zapscript to v0.7.0 for Command.String() support.
@coderabbitai

coderabbitai Bot commented Apr 11, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce46270b-a9ce-4769-bc23-3c4754937882

📥 Commits

Reviewing files that changed from the base of the PR and between 15d2b98 and 3fc2109.

📒 Files selected for processing (3)
  • pkg/service/reader_manager_test.go
  • pkg/zapscript/input.go
  • pkg/zapscript/input_test.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/zapscript/input_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/zapscript/input.go

📝 Walkthrough

Walkthrough

Adds run-specific IP-filter middleware and routes; enforces per-command and per-input authorization (HTTP URL allowlist, command blocklist, input key allow/block/mode); updates config to support HTTP, block-commands, and input settings; and adds/refactors tests and minor log/message changes.

Changes

Cohort / File(s) Summary
Dependency
go.mod
Bumped github.com/ZaparooProject/go-zapscript from v0.6.0 to v0.7.0.
API middleware & routing
pkg/api/middleware/ipfilter.go, pkg/api/server.go, pkg/api/methods/run.go
Added RunIPFilterMiddleware (loopback and allow-run bypass); server run endpoints (/l/*,/r/*,/run/*) now use it; reused single run handler instance and updated run denial log text.
Configuration model & service
pkg/config/config.go, pkg/config/configservice.go, pkg/config/config_test.go
Added AllowHTTP, BlockCommands, Input config and compiled regex anchoring; per-command IsRunAllowed now parses ZapScript and validates each command; added query/setter helpers and tests for anchoring, HTTP/command/input allow/block behavior.
Zapscript runtime checks
pkg/zapscript/commands.go, pkg/zapscript/http.go, pkg/zapscript/input.go, pkg/zapscript/utils.go
Enforced command blocklist check before execution; added ErrHTTPNotAllowed and HTTP URL allowlist checks; added input key validation (modes, allow/block lists, errors); removed source-based execute restriction.
Tests: zapscript & utils
pkg/zapscript/http_test.go, pkg/zapscript/input_test.go, pkg/zapscript/utils_test.go
New tests for HTTP allowlist, comprehensive input validations across platforms/modes, and execute-allow behavior for token source control.
Tests: middleware & service
pkg/api/middleware/ipfilter_test.go, pkg/service/scan_behavior_test.go, pkg/service/reader_manager_test.go
Added tests for RunIPFilterMiddleware cases; forced input mode in scan behavior test setup; added notification-wait helper and updated reader-manager tests to use it.
Config & zapscript tests
pkg/config/..., pkg/zapscript/... (various)
Multiple new/updated unit tests validating anchoring, allow/block semantics, and input behavior across code paths.

Sequence Diagram

sequenceDiagram
    autonumber
    actor Client
    participant IPFilter as RunIPFilterMiddleware
    participant API as REST Run Handler
    participant Parser as ZapScript Parser
    participant Validator as Authorization Validator
    participant Executor as Command Executor

    Client->>IPFilter: POST /run (script)
    alt RemoteAddr is loopback or HasAllowRun = true
        IPFilter->>API: forward request
    else
        IPFilter->>API: apply IP allowlist -> allowed / 403
    end

    API->>Parser: parse script body
    Parser->>Parser: split into commands

    loop per command
        Parser->>Validator: checkAllow(command)
        alt Command blocked
            Validator-->>API: error "command blocked"
        else HTTP command
            Validator->>Validator: IsHTTPAllowed(url)
            alt URL not allowed
                Validator-->>API: ErrHTTPNotAllowed
            else allowed
                Validator->>Executor: execute HTTP
            end
        else Input command
            Validator->>Validator: checkInputKey(key)
            alt not allowed/blocked
                Validator-->>API: ErrInputNotAllowed/ErrInputBlocked
            else allowed
                Validator->>Executor: perform input
            end
        else Execute command
            Validator->>Validator: IsExecuteAllowed(execStr)
            alt not allowed
                Validator-->>API: execute denied
            else allowed
                Validator->>Executor: run command
            end
        end
    end

    Executor-->>API: command results
    API-->>Client: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hop through code with careful paws,
Anchors, lists, and guarded laws,
Keys and URLs checked tight and neat,
No rogue command can steal my treat,
A cautious carrot for every hop.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(zapscript): harden execution pipeline' clearly and concisely summarizes the primary objective of the pull request, which is to add security hardening measures to the ZapScript execution pipeline.

✏️ 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 feat/zapscript-pipeline-hardening

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

@sentry

sentry Bot commented Apr 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.27027% with 55 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/config/config.go 41.77% 42 Missing and 4 partials ⚠️
pkg/config/configservice.go 82.60% 4 Missing ⚠️
pkg/zapscript/commands.go 0.00% 1 Missing and 1 partial ⚠️
pkg/zapscript/input.go 96.29% 2 Missing ⚠️
pkg/api/methods/run.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/api/middleware/ipfilter.go`:
- Around line 91-106: Add regression tests exercising RunIPFilterMiddleware's
four branches: loopback bypass (use IsLoopbackAddr to simulate loopback
RemoteAddr and ensure next handler is called), remote bypass when hasAllowRun
returns true (mock hasAllowRun to true and verify next handler is called),
normal AllowedIPs allow (exercise NonWSIPFilterMiddleware/AllowedIPs path with a
permitted remote address and verify next handler is called), and deny when
neither applies (use a non-loopback address with hasAllowRun false and an IP not
allowed by NonWSIPFilterMiddleware and verify the filtered handler denies
access). Put these tests beside other middleware tests, mock or stub IPsProvider
used by NonWSIPFilterMiddleware and the hasAllowRun function, and assert the
correct handler is invoked for each case.

In `@pkg/zapscript/http_test.go`:
- Around line 263-278: Add a positive allow-list test for cmdHTTPPost: extend or
add a test (e.g., TestCmdHTTPPost_AllowListAllows) that uses
config.Instance.SetHTTPAllowListForTesting with the same regex
(`https://example\.com/.*`), constructs a platforms.CmdEnv with
gozapscript.ZapScriptCmdHTTPPost and an Args URL that matches the regex (e.g.,
"https://example.com/foo"), calls cmdHTTPPost(nil, env) and asserts no
ErrHTTPNotAllowed (i.e., err is nil or not ErrHTTPNotAllowed) so the allow-list
success path is covered alongside the existing deny-path test.

In `@pkg/zapscript/input_test.go`:
- Around line 253-273: The test TestCmdKeyboard_CombosAllowsSpecialKey is not
exercising the combos path because mockPlatform uses platformids.Mister (which
defaults to unrestricted); update the test to either use a desktop platform ID
(e.g., replace platformids.Mister with a desktop platform constant) or
explicitly enable combos by setting cfg.InputModeCombos = true on the
config.Instance before calling cmdKeyboard; modify the platforms.CmdEnv setup
(used by cmdKeyboard) so the env.Cfg has InputModeCombos enabled or the
mockPlatform.ID returns a desktop ID to ensure the desktop/combo branch is
executed.

In `@pkg/zapscript/input.go`:
- Around line 76-80: isSpecialKey currently treats any braced token as special
(e.g., "{a}" or "{5}"), letting single-character input bypass the combos gate;
update isSpecialKey to inspect the inner token (key[1:len-1]) and only return
true when that inner token is a multi-character combo (len(inner) > 1) or
matches the set of known named special keys (e.g., "Enter", "Ctrl", etc.),
otherwise return false; apply the same inner-token validation to the analogous
checks referenced around lines 121-133 so single-character braced tokens are
rejected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83efcca9-c258-4401-9dc8-6e74c044c316

📥 Commits

Reviewing files that changed from the base of the PR and between 39c30bf and 5fe454e.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (15)
  • go.mod
  • pkg/api/methods/run.go
  • pkg/api/middleware/ipfilter.go
  • pkg/api/server.go
  • pkg/config/config.go
  • pkg/config/config_test.go
  • pkg/config/configservice.go
  • pkg/service/scan_behavior_test.go
  • pkg/zapscript/commands.go
  • pkg/zapscript/http.go
  • pkg/zapscript/http_test.go
  • pkg/zapscript/input.go
  • pkg/zapscript/input_test.go
  • pkg/zapscript/utils.go
  • pkg/zapscript/utils_test.go

Comment thread pkg/api/middleware/ipfilter.go
Comment thread pkg/zapscript/http_test.go
Comment thread pkg/zapscript/input_test.go
Comment thread pkg/zapscript/input.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/zapscript/input_test.go`:
- Around line 163-230: Add a new test (e.g.,
TestCheckInputKey_UnknownModeDefaultsToCombos) that constructs a
config.Instance, sets an invalid input mode via SetInputModeForTesting (e.g.,
pointer to an invalid string), and then exercises checkInputKey: assert that
single-character input like "a" returns ErrInputNotAllowed and that
special/combo keys like "{f1}" and "{ctrl+q}" are allowed (no error), thereby
covering the unknown-mode fallback path in checkInputKey.
- Around line 277-296: Tighten TestCmdKeyboard_BracedSingleCharRejected to
verify the platform parser path is exercised by asserting the mock call and
error origin: after calling cmdKeyboard(env) assert mockPlatform.AssertCalled(t,
"KeyboardPress", "{a}") (or mockPlatform.AssertExpectations(t)) and assert the
returned error matches the mocked error (e.g. require.EqualError(t, err,
"unknown key") or require.ErrorIs if wrapped) so the test fails if KeyboardPress
wasn't invoked or a different error is returned; keep uses of
TestCmdKeyboard_BracedSingleCharRejected, cmdKeyboard, mockPlatform,
KeyboardPress/PressKeyboardSequence in mind when making the changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c40fb71c-0718-45f7-b494-7fbd1bedba19

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe454e and 15d2b98.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • go.mod
  • pkg/api/middleware/ipfilter_test.go
  • pkg/zapscript/http_test.go
  • pkg/zapscript/input_test.go
✅ Files skipped from review due to trivial changes (1)
  • go.mod
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/zapscript/http_test.go

Comment thread pkg/zapscript/input_test.go
Comment thread pkg/zapscript/input_test.go
The RetapAfterDelayConfirms test had a race where Go's select could
pick the re-tap scan from scanQueue before the guardDelay timer,
causing the re-tap to be misinterpreted as occurring during the delay
period. Wait for the tokens.staged.ready notification instead of using
a barrier scan to ensure the delay has been fully processed.
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