Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,51 @@ concurrency:
cancel-in-progress: true

jobs:
lint-test:
test-matrix:
name: test (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: ['22.13.0', '24.x']
env:
OPENCLAW_SCHEDULER_REF: ac9ea643a8efc68e9f81c8d93125467ca62140b5
steps:
- uses: actions/checkout@v5
- name: Check out pinned openclaw-scheduler
uses: actions/checkout@v5
with:
repository: amittell/openclaw-scheduler
ref: ac9ea643a8efc68e9f81c8d93125467ca62140b5
path: openclaw-scheduler
- name: Configure scheduler fixture path
run: echo "SCHEDULER_PATH=$RUNNER_TEMP/openclaw-scheduler" >> "$GITHUB_ENV"
- uses: actions/setup-node@v5
with:
node-version: '22'
node-version: ${{ matrix.node-version }}
cache: npm
cache-dependency-path: |
package-lock.json
openclaw-scheduler/package-lock.json
- name: Move scheduler fixture outside the agentcli source tree
run: mv openclaw-scheduler "$SCHEDULER_PATH"
- run: npm ci
- name: Install and verify pinned scheduler runtime
run: |
cd "$SCHEDULER_PATH"
test "$(git rev-parse HEAD)" = "$OPENCLAW_SCHEDULER_REF"
npm ci
node bin/openclaw-scheduler.js --json capabilities > /dev/null
- run: npm run lint
- run: npm test

lint-test:
name: lint-test
needs: test-matrix
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Require every Node matrix job to pass
env:
MATRIX_RESULT: ${{ needs.test-matrix.result }}
run: test "$MATRIX_RESULT" = success
27 changes: 25 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,48 @@ on:
tags: ['v*']

permissions:
contents: write
id-token: write
contents: read

jobs:
test:
runs-on: ubuntu-latest
env:
OPENCLAW_SCHEDULER_REF: ac9ea643a8efc68e9f81c8d93125467ca62140b5
steps:
- uses: actions/checkout@v5
- name: Check out pinned openclaw-scheduler
uses: actions/checkout@v5
with:
repository: amittell/openclaw-scheduler
ref: ac9ea643a8efc68e9f81c8d93125467ca62140b5
path: openclaw-scheduler
- name: Configure scheduler fixture path
run: echo "SCHEDULER_PATH=$RUNNER_TEMP/openclaw-scheduler" >> "$GITHUB_ENV"
- uses: actions/setup-node@v5
with:
node-version: '24'
cache: npm
cache-dependency-path: |
package-lock.json
openclaw-scheduler/package-lock.json
- name: Move scheduler fixture outside the agentcli source tree
run: mv openclaw-scheduler "$SCHEDULER_PATH"
- run: npm ci
- name: Install and verify pinned scheduler runtime
run: |
cd "$SCHEDULER_PATH"
test "$(git rev-parse HEAD)" = "$OPENCLAW_SCHEDULER_REF"
npm ci
node bin/openclaw-scheduler.js --json capabilities > /dev/null
- run: npm run lint
- run: npm test

publish:
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
Expand Down
24 changes: 21 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 0.4.0 (2026-07-11)

- security: manual approvals now bind the canonical manifest and complete effective execution configuration, enforce `approver_scope` and `timeout_s`, reject unexpected unsigned records, and fail without writing a grant when signing fails
- security: approval checks now run before proof commands, provider calls, sandbox probes, credential materialization, signing, and all other live side effects
- security: `exec --dry-run` is now a static preview that performs no proof, provider, network, sandbox, signing, evidence, verification, or audit side effects
- security: JWT, detached-signature, and certificate authorization proofs require cryptographic verification and canonical manifest binding; `verify.required: false` no longer permits presence-only or claims-only success
- security: evidence uses a complete versioned canonical payload, persists the verification envelope, binds manifest and effective execution metadata, and detects cross-execution transplantation
- security: sandbox, allowed-path, and network restrictions fail closed when unavailable; child processes inherit only a small operational allowlist and require every other ambient variable to be explicitly declared or provider-materialized
- security: identity providers validate configuration before network access, enforce delegation and handoff capabilities, use safer endpoint and file handling, and clean up materialized credentials across failure paths
- validation: v0.2 nested objects reject unknown fields, provider-specific structural validation runs during manifest validation, and the default schema output is JSON Schema Draft 2020-12 with `--legacy` opt-in
- CLI and JSON-RPC: strict flag parsing rejects unknown, duplicate, missing-value, and misplaced flags; RPC responses use stable result/error envelopes and add read-only targets, paths, audit, approvals, and registry discovery methods
- execution: disabled tasks and branches are skipped by `agentcli run`; audit identifiers are collision-resistant and malformed audit lines are skipped with warnings
- conversion and merge: v0.1 conversion maps unverifiable legacy attestations to `method: "none"`; merge preserves same-version semantics and v0.2 profile collections, rejects mixed manifest versions, and detects conflicting profile definitions
- scheduler: live capability values override static fallback values, handoff v3 preserves governed approval and output fields, auto-reject jobs compile disabled, and apply refuses inline `shell.env` or `shell.stdin`
- examples: repaired invalid runtime timeout placement and fail-closed proof and credential-cache declarations; all published JSON examples are validated in the test suite
- maintenance: minimum Node version is now 22.13.0 and CI also tests Node 24 with a pinned `openclaw-scheduler` integration checkout
- dependencies: pinned patched `brace-expansion` and `flatted` releases; `npm audit` reports no known vulnerabilities

## 0.3.2 (2026-04-21)

- fix: `verifyApprovalSignature` now performs a tamper check against the stored `signature.signed_payload`. Previously the canonical payload was rebuilt from the current grant but then discarded; post-sign edits to `approver`, `reason`, `expires_at`, or `task_hash` fields in `approvals.ndjson` would not be detected (the ssh provider only checks signature-against-signed_payload). The rebuilt payload is now compared to `signature.signed_payload` and divergence returns `verified: false` with reason "grant fields do not match signed payload (possible tampering)"
Expand All @@ -16,8 +34,8 @@

## 0.3.0 (2026-04-21)

- local approval gate enforcement in `agentcli exec`: tasks with `approval.policy: "manual"` refuse to execute unless a matching, unconsumed, unrevoked, unexpired approval record is present (`error_type: approval_required`)
- `approval.policy: "auto-reject"` refuses execution even when an approval record exists (`error_type: approval_auto_rejected`)
- local approval gate enforcement in `agentcli exec`: tasks with `approval.policy: "manual"` refuse to execute unless a matching, unconsumed, unrevoked, unexpired approval record is present (detailed `code: approval_required`; closed `error_type: validation_error`)
- `approval.policy: "auto-reject"` refuses execution even when an approval record exists (detailed `code: approval_auto_rejected`; closed `error_type: validation_error`)
- approval grants are bound to a canonical task hash over `{workflow_id, task_id, shell.program, shell.args, shell.cwd, identity.ref, approval.policy, approval.risk_level}`; drift in any of those fields invalidates prior approvals
- `--dry-run` bypasses the approval gate (no approval consumed, no gate enforced)
- successful gated executions include `approval_used: {approval_id, approver, reason, risk_level, granted_at, expires_at, signature_verified, signature: {method, key_fingerprint}}` in both the result payload and the audit record
Expand All @@ -28,7 +46,7 @@
- new `--approval-id <id>` flag on `exec` to target a specific pending grant when more than one matches
- new append-only state file at `~/.agentcli/state/approvals.ndjson` (grant, consume, revoke events); path exposed by `agentcli paths`
- new module `src/approvals.js` exports `grantApproval`, `listApprovals`, `findValidApproval`, `consumeApproval`, `revokeApproval`, `computeTaskApprovalHash`, `approvalPolicyRequiresApproval`, `approvalPolicyAutoRejects`, `verifyApprovalSignature`
- approval signature verification reuses existing ssh allowed-signers chain (`~/.agentcli/state/allowed_signers`); tampered grants are refused (`error_type: approval_signature_invalid`)
- approval signature verification reuses existing ssh allowed-signers chain (`~/.agentcli/state/allowed_signers`); tampered grants are refused with detailed `code: approval_signature_invalid` and closed `error_type: validation_error`
- scope: local single-machine enforcement only; durable multi-actor cron-triggered approvals remain owned by openclaw-scheduler

## 0.2.2 (2026-04-08)
Expand Down
18 changes: 15 additions & 3 deletions MANIFEST-QUICK-REF.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,24 @@ Copy-paste patterns for common agentcli manifests.
| `delivery.channel` | optional | `telegram`, etc. |
| `delivery.to` | optional | Channel-specific target (chat ID) |
| `reliability.overlap_policy` | optional | `skip`, `queue`, `allow` |
| `runtime.timeout_ms` | optional | Local or backend execution timeout in milliseconds |
| `verify.shell` | optional | Post-completion verification command |
| `verify.required` | optional | When `true`, requires `public_key` or `jwks_uri` for jwt proofs |
| `authorization_proof_profiles[].verify.required` | optional | Verification policy; every non-`none` proof is verified regardless |
| `authorization.request.include` | optional | Array of include fields for OPA request (`actor`, `step_up`) |
| `subject.attributes` | optional | Actor metadata object (`org_id`, `on_behalf_of_user_id`, `delegation_grant_id`, `run_id`, `agent_id`, `verification_ref`, `verification_level`) |
| `authorization_proof_profiles[].jwks_uri` | optional | JWKS endpoint URI for JWT key discovery and caching |
| `authorization_proof_profiles[].public_key` | optional | Inline public key for JWT verification |

## Safety rules

- `agentcli exec --dry-run` is a static preview. It performs no approval consumption, proof command, provider call, sandbox probe, credential materialization, signing, evidence, postcondition, or audit write.
- Manual grants are single-use and bind the canonical manifest plus the full effective execution configuration. Approver scope and timeout are enforced. Unexpected unsigned grants are rejected.
- `jwt`, `detached-signature`, and `certificate` proofs must verify cryptographically and bind the canonical manifest. Use `method: "none"` for an intentionally unverifiable declaration.
- Requested sandbox or network restrictions fail closed if the local host cannot enforce them.
- Child processes inherit only a small operational allowlist. Every other ambient variable requires explicit `shell.env` declaration or identity-provider materialization.
- `agentcli run` skips disabled tasks and their branches.
- Scheduler apply rejects inline `shell.env` and `shell.stdin`; durable credentials belong in runtime identity providers.

## Session targets

- **shell**: Runs a command. Fast, predictable. Use for scripts and pipelines.
Expand Down Expand Up @@ -151,13 +162,14 @@ agentcli apply manifest.json --db scheduler.db --scheduler-prefix ./scheduler --
agentcli apply manifest.json --db scheduler.db --scheduler-prefix ./scheduler --adopt-by name
agentcli exec manifest.json task-id # Run a task locally
agentcli exec manifest.json task-id --approval-id <id> # Target a specific pending approval
agentcli schema manifest # Machine-readable schema
agentcli schema manifest # Draft 2020-12 JSON Schema
agentcli schema manifest --legacy # Legacy agentcli descriptor
agentcli describe commands --json # All CLI commands
```

## Approvals (local gate)

Tasks with `approval.policy: "manual"` refuse to run via `agentcli exec` unless a matching, unconsumed approval record exists. Grants are ssh-signed, single-use, and bound to the exact task hash (`workflow_id`, `task_id`, `shell.program`, `shell.args`, `shell.cwd`, `identity.ref`, policy, risk level). `--dry-run` bypasses the gate.
Tasks with `approval.policy: "manual"` refuse to run via `agentcli exec` unless a matching, unconsumed approval record exists. Grants are signed by default, single-use, constrained by `approver_scope` and `timeout_s`, and bound to the canonical manifest and complete effective execution configuration. Unexpected unsigned grants fail; `--signer none` is the explicit unsigned mode. `--dry-run` is static and does not consume or enforce the gate.

```bash
agentcli approve manifest.json task-id --by alex --reason "tuesday deploy" --ttl-s 3600
Expand Down
Loading