feat(zapscript): harden execution pipeline#656
Conversation
…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.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (15)
go.modpkg/api/methods/run.gopkg/api/middleware/ipfilter.gopkg/api/server.gopkg/config/config.gopkg/config/config_test.gopkg/config/configservice.gopkg/service/scan_behavior_test.gopkg/zapscript/commands.gopkg/zapscript/http.gopkg/zapscript/http_test.gopkg/zapscript/input.gopkg/zapscript/input_test.gopkg/zapscript/utils.gopkg/zapscript/utils_test.go
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (4)
go.modpkg/api/middleware/ipfilter_test.gopkg/zapscript/http_test.gopkg/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
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.
Summary
allow_execute,allow_run,allow_file) to prevent substring matchingblock_commandsconfig field to disable specific ZapScript commands by nameallow_httpURL allowlist forhttp.get/http.postcommands (empty = all allowed)combos(desktop default, blocks single char keys) andunrestricted(embedded default), with configurableallow/blocklists and a default desktop block list for dangerous key combosallow_runto parse ZapScript per-command viaCommand.String(), preventing chained command bypass. REST run endpoints bypassallowed_ipswhenallow_runis configuredImplements security-hardening-phase1.md item 1 (all sub-items).
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Tests
Chores