Skip to main content

SkillSec V2

SkillSec scans Skill content, signs its results, retains recoverable versions and publishes the selected version to SkillFS. Built-in scanning runs locally without model calls. The Rust CLI uses agent-sec-cli skillsec and sends operations to one root daemon. skill-ledger remains a compatibility alias with the same subcommands, options, output and exit codes. Existing Hook adapters and scripts can keep using it; new commands should use skillsec. Capability IDs, SKILL_LEDGER_* environment variables, RPC methods and stored data paths are unchanged.

This page covers the V2 core and its Hook adapters. The Skill Ledger guide retains the V1 reference and host-specific matching and policy controls. The adapters retain their existing languages, defaults and enablement; their CLI calls use the Rust daemon.

Installation boundary​

The normal released-component route is sudo anolisa --install-mode system install sec-core, followed by the Alinux RPM alternative described in installation. These routes select published artifacts; they do not select this unreleased V2 migration branch. Do not assume that installing a release supplies SkillSec V2.

For a V2 RPM, build this branch with the repository recipe on Linux. The full recipe also builds the unchanged sandbox and plugin packages and needs Node.js 22.14 or later, Rust 1.93 or later, RPM tools, systemd RPM macros, a C compiler, pkg-config and OpenSSL development headers:

./scripts/rpm-build.sh agent-sec-core-v2
sudo yum install ./scripts/rpmbuild/RPMS/x86_64/agent-sec-cli-*.rpm

Use the matching architecture directory on aarch64. Install the core CLI package for this phase; the metapackage also pulls Agent integrations. Use plugin packages from the same revision as this Hook migration; updating the CLI alone does not update already loaded plugins. V1 and V2 CLI packages use the same package name and executable paths, so installing V2 replaces V1. Stop V1 writers and retain a matching backup first, as described under deployment rollback below.

For a core-only source installation, use Linux and Rust 1.93 or later. From the repository root:

make -C src/agent-sec-core build-cli-v2
sudo make -C src/agent-sec-core install-core-v2

This produces src/agent-sec-core/target/v2/bin/agent-sec-cli and agent-sec-daemon. The core binaries require no Python Ledger runtime. install-core-v2 installs both binaries into /usr/bin, the system unit into /usr/lib/systemd/system, and an initial 0600 configuration into /etc/agent-sec/skillsec.json. Reinstallation preserves an existing configuration; RPM uses %config(noreplace). Installation does not enable Agent Hooks. When the service starts, background scanning of discovered Skills may initialize its signing key.

The V2 systemd unit is agent-sec-core.service; agent-sec-daemon is the executable/process name. V1 used the same unit name as a user service; V2 runs it as a system service.

On a host with a running systemd manager, RPM enables and starts the root system service during initial installation and V1-to-V2 upgrades. Review its configuration before installing, then verify that the daemon is available:

sudo systemctl status agent-sec-core.service
agent-sec-cli skillsec status

If the RPM was installed into an image or container without a systemd manager, or the service was later stopped or disabled, start it explicitly:

sudo systemctl enable --now agent-sec-core.service

Use system systemctl, without --user. The unit creates runtime/state/log directories, keeps the key and logs private, and grants CAP_DAC_OVERRIDE, CAP_CHOWN, CAP_FOWNER and CAP_SYS_PTRACE to the daemon. The first three support SkillSec directory access and recovery; CAP_SYS_PTRACE supports process discovery reads of protected procfs executable and namespace links. It retains NoNewPrivileges, SystemCallFilter=@system-service, native syscall architecture, MemoryDenyWriteExecute and kernel protections, while keeping HOME, /tmp, system Skill directories and shared mounts accessible for scanning, metadata publication and rollback. It does not grant CAP_SYS_ADMIN to the daemon. Custom SkillFS mounts must be visible to the service. In-place SkillFS backing setup requires kernel support for open_tree and move_mount, and access to /proc/self/fd. Unsupported or denied mount operations fail closed. The daemon does not need an additional mount capability.

Use an isolated deployment for evaluation: V1 and V2 must never write the same Skill metadata concurrently. Implementation and actual Linux delivery acceptance are tracked separately in the migration document.

V2_CARGO_TARGET_DIR selects a shared Cargo build cache; V2_BIN_BUILD_DIR selects the binary staging directory. Pass the same staging override to build and install. To validate an existing Linux build's staged installation and preservation of configuration:

bash src/agent-sec-core/tests/packaging/test-skillsec-install.sh

This staging check does not start systemd or establish live installation acceptance.

For a foreground development daemon, first create a root-owned runtime directory with mode 0755 and an empty root-owned state directory with mode 0700. Supply the absolute socket through agent-sec-daemon serve --socket /run/agent-sec-core/daemon.sock and optionally supply --skillsec-config /etc/agent-sec/skillsec.json. The process must run as root. Its runtime directory must already exist; it creates the private state directory if absent.

Agent Hook integration​

Restart the Agent after installing the matching plugin or changing its trusted launch environment. Keep AGENT_SEC_DAEMON_SOCKET consistent with the daemon when using a non-default socket. Before loading a Skill, run agent-sec-cli skillsec status and scan it explicitly if startup has not established V2 trust; a V1 key or manifest does not establish V2 trust.

HostQueryInitialization on a matched Skill callDefault policy
CodexcheckIdempotent init --no-baselineask, delivered as a warning at UserPromptSubmit
QodercheckNoneask
Qwen CodeshowIdempotent init --no-baselineask
Cosh-NGshowIdempotent init --no-baselineask
OpenClawshowIdempotent init --no-baselineask
HermesshowNoneobserve

The four initializing adapters ask the daemon on each matched invocation. They do not inspect HOME key files, scan Skills, reset an unsafe key, or rotate keys. OpenClaw does not initialize at plugin registration or retain a process-wide readiness result. Disabled and unmatched Hooks do not invoke the CLI. Qoder and Hermes retain their read-only behavior; initialize trust explicitly for these hosts if startup has not already done so. Host approval applies to the current invocation and does not write a Ledger decision.

check consumers accept a valid risk result with exit code 1; they do not mistake it for a transport failure. show consumers require exit code 0 and a valid exposure summary, then use message to apply their existing policy. An active warn version, or an explicit manual decision, can have a null message. hidden is enforced by SkillFS exposure; the Hook does not independently deny direct filesystem access based on that field. An unmanaged Skill is outside this managed exposure path.

A missing CLI, unavailable daemon, timeout or invalid response produces a diagnostic. Codex, Cosh-NG, Qwen Code, OpenClaw and Hermes retain fail-open execution-error behavior; Qoder applies its configured policy to an error as before. Failure to initialize stops that invocation before check/show; it does not fall back to Python. The diagnostic does not copy raw CLI stderr.

Cosh-NG allows 10 seconds for the complete SkillSec Hook: up to 3 seconds for initialization, 5 seconds for show, and 2 seconds of startup/processing margin. The capabilities environment view continues to report the 5-second query budget, not the host manifest deadline.

Adapter-to-daemon tests and native Agent acceptance are separate. Current evidence and remaining host limitations are recorded in the migration document.

System configuration and trust​

The daemon reads root-owned /etc/agent-sec/skillsec.json; --skillsec-config selects another absolute file. Parent directories must be trusted, the file must not be group/world writable, and symlinks or multiply linked configuration files are rejected. A missing default file uses the following defaults; a malformed or unsafe file fails startup.

{
"stateDir": "/var/lib/agent-sec/skillsec",
"enableDefaultSkillDirs": true,
"managedSkillDirs": []
}

Default directories are enabled even when an existing configuration omits enableDefaultSkillDirs. The daemon authorizes these locations at startup:

LocationDiscovery
/usr/share/anolisa/skills/*, /usr/local/share/anolisa/skills/*Direct Skill children
<home>/.local/share/anolisa/skills/*ANOLISA user installations
<home>/.openclaw/skills/*, <home>/.copilot-shell/skills/*Direct Skill children
<home>/.hermes/skills/**Recursive Skills, including the root if it has SKILL.md
<home>/.qoder/skills/*, <home>/.codex/skills/*, <home>/.agents/skills/*, <home>/.qwen/skills/*Direct Skill children

<home> is the absolute HOME field from each local /etc/passwd record, including root and service accounts. Duplicate homes are merged; invalid homes, /, symlink homes and existing non-directories such as /dev/null are skipped with diagnostics. Missing homes retain their scope. Account changes take effect after a daemon restart. NSS-only accounts and nonstandard XDG_DATA_HOME, CODEX_HOME, QWEN_HOME or project locations require explicit configuration or authenticated SkillFS integration; caller environment variables cannot extend authorization.

managedSkillDirs adds administrator-authorized absolute paths:

EntryAuthorized and discovered Skills
/srv/skills/demoThis exact Skill
/srv/skills/*Direct, non-hidden Skill children
/srv/skills/**Recursive Skills, including the root if it has SKILL.md

Edit the existing file with sudoedit /etc/agent-sec/skillsec.json, preserving other settings, and apply changes with sudo systemctl restart agent-sec-core.service. Setting enableDefaultSkillDirs to false disables only default directories; explicit configuration and authenticated SkillFS mounts retain their scope. An empty explicit list does not disable enabled defaults. Disabling scope does not remove existing Skill metadata or reset trust. General glob syntax and ~ are unsupported. Discovery skips hidden descendants and never follows symlinks. All local users retain access to all managed Skills; this does not introduce a user ACL.

init, scan --all, check --all and status discover through the daemon, combining defaults and managedSkillDirs. Explicit caller roots are checked before Skill I/O; one out-of-range root rejects the entire request with PermissionDenied. init --no-baseline and single-path requests do not discover directories.

Startup scans run asynchronously in the existing worker, alternating with ready SkillFS notifications; the socket does not wait for scans. Mounted Skills come from authenticated notifications and exact configured identities, which go directly through the authenticated resolver, never a FUSE walk. New Skills under existing standard roots are authorized immediately and discovered on the next aggregate command or daemon restart. There is no directory watcher or scan triggered by ordinary Skill invocation. Host Hook policies continue to control invocation; installation alone does not make all direct filesystem reads block.

One default-discovery pass visits at most 10,000 directory entries and adds at most 1,024 new Skill identities. These limits do not truncate explicit configuration. A directory failure or exhausted budget retains completed discoveries and returns discoveryErrors entries with root and error, exit code 1 and non-healthy status. Startup logs incomplete discovery and scans the successful selection. Discovery deadlines remain fatal errors.

Successful scan and certify, including an unchanged-content no-op, remember the canonical exact Skill path in the daemon's active configuration file. Ordinary directories already covered by a managedSkillDirs pattern need no duplicate entry. SkillFS paths always receive an exact entry so startup can resolve them without traversing FUSE. These entries also grant ordinary-directory access: removing a SkillFS mount or disabling default-directory discovery does not revoke a saved path. Remove its managedSkillDirs entry and restart the daemon to revoke that authorization. A save failure appears in warnings and does not discard the scan result, findings or activation outcome. Correct the configuration error and retry the scan to persist discovery.

Startup scans, scan --all and the init baseline skip authorized host Skills directly under /usr/share/anolisa/skills or /usr/local/share/anolisa/skills when the daemon cannot write their Ledger location, including read-only mounts. Results contain status: skipped, reasonCode: readonly_system_skill, persisted: false. Startup does not activate a skipped Skill. This is not a safety verdict or certification. A root-writable mode-0555 directory still scans. Explicit scans, user-directory write failures, SkillFS failures and other storage errors remain errors. Skipped Skills need a writable managed copy to establish trust.

The state directory is root-owned 0700; signing-key.pk8 is 0600. All local users may operate all managed Skills in phase one. Ownership is not an authorization ACL. Key rotation is unsupported for all users. The key signs daemon-computed records, not arbitrary caller-provided bytes.

V1 history, encrypted keys and keyrings are not imported. New manifests bind the canonical absolute Skill identity. Moving a Skill to another identity requires establishing trust there. Different Skills with the same directory name retain separate identities.

Core workflow​

Run these commands as the ordinary user whose Skill is being managed. init --no-baseline initializes only the shared key. scan also initializes a missing key; neither operation silently replaces a damaged key.

agent-sec-cli skillsec init --no-baseline
agent-sec-cli skillsec list-scanners
agent-sec-cli skillsec analyze /path/to/skill
agent-sec-cli skillsec scan /path/to/skill
agent-sec-cli skillsec check /path/to/skill
agent-sec-cli skillsec show /path/to/skill
agent-sec-cli skillsec audit /path/to/skill --verify-snapshots
agent-sec-cli skillsec status --verbose

analyze requires an authorized Skill path but remains read-only and does not initialize keys or write a Ledger. The default built-ins are code-scanner and static-scanner. scan --scanners code-scanner,static-scanner selects them explicitly. Changed content creates a version; unchanged content fills missing scan results. scan --force replaces scan results for unchanged content without inventing a content version.

The six integrity states are:

StateMeaning
noneContent has no authenticated scan verdict yet
passAuthenticated content has a passing scan result
warnAuthenticated content has warning findings
denyAuthenticated content has denying findings
driftedCurrent content differs from the authenticated record
tamperedLedger metadata, signature or version binding fails authentication

Execution errors and unmanaged diagnostics are not additional integrity states. A successful scan means the scan completed, not that its security verdict is pass.

Before the first key initialization, checking a Skill with no Ledger returns none and exit 0; auditing its empty history also succeeds. Neither query creates keys or metadata. Existing Ledger artifacts with missing or invalid keys do not qualify as an empty history.

External findings, decisions and recovery​

Import an external JSON findings report through the CLI. Custom skill, cli or api scanner entries are import-only; the root daemon never executes their configured commands.

agent-sec-cli skillsec certify /path/to/skill --findings /path/to/findings.json --scanner skill-vetter
agent-sec-cli skillsec decide /path/to/skill --action allow --reason 'Reviewed this version'
agent-sec-cli skillsec decide /path/to/skill --action always_allow
agent-sec-cli skillsec decide /path/to/skill --action block
agent-sec-cli skillsec decide /path/to/skill --action rollback --version v000001
agent-sec-cli skillsec decide /path/to/skill --clear
agent-sec-cli skillsec export /path/to/skill --version v000001 --output /path/to/empty-export
agent-sec-cli skillsec activate /path/to/skill

allow approves the selected version; always_allow persists the broader manual approval. block hides exposure. rollback restores the selected authenticated snapshot and records the decision; --clear removes the manual decision. Rollback restores files to the Skill directory owner so that its user can edit them afterward; privileged file mode bits are stripped.

Export writes snapshot/, manifest.json and findings.json to an empty caller-owned directory. The CLI creates missing export directories as the caller with mode 0700 and leaves existing directory permissions unchanged. It refuses a nonempty destination. --delete-findings deletes the imported local report only after successful certification and confirmation that the report has not changed. It reports findingsDeleted: true on deletion. Failed cleanup returns findingsDeleted: false and findingsDeleteError while preserving the committed certification and its success exit code. Deletion uses the CLI caller's permissions. Failed or malformed daemon responses never delete input.

show exposes active, pending or hidden selection. With pass_warn_only, an eligible authenticated pass or warn version may be exposed automatically; unsafe latest content may retain an eligible previous version or a pending view. Manual decisions affect that selection. activation.activationPending=true means Ledger work committed but publication did not complete. Inspect show and retry activate after repairing the reported cause. Daemon restart reconciles supported interrupted publication and rollback states. No durable notification queue is promised.

Per-Skill rollback recovery and validation remain part of normal operations. Startup uses configured discovery, so a Skill that is no longer discoverable is not guaranteed automatic recovery; retry an explicit authorized operation after restoring its path. There is no separate private registry for global recovery coverage.

SkillFS binding​

SkillFS uses the same public socket as ordinary CLI calls. Its notify handshake and messages retain their existing HMAC contract; ordinary V2 JSON remains strict. The public socket must be root-owned 0666 under a root-owned 0755 directory. SkillFS also checks ancestor safety and the actual kernel peer UID. The permission change does not permit plaintext fallback or bypass HMAC.

Configure explicit canonical/backing mappings in the daemon:

{
"stateDir": "/var/lib/agent-sec/skillsec",
"managedSkillDirs": ["/home/alice/.openclaw/skills/demo"],
"skillfs": {
"authKeyFile": "/etc/agent-sec/skillfs-hmac.key",
"mounts": [{
"controlSocket": "/run/user/1000/skillfs/control.sock",
"canonicalRoot": "/home/alice/.openclaw/skills",
"liveRoot": "/home/alice/.openclaw/live-skills",
"peerUid": 1000
}]
}
}

Use a separate random HMAC secret, 32–4096 bytes, never the signing key. The daemon's copy must be root-owned 0600. Provision the same bytes in a separate 0600 file owned by the SkillFS user; do not make the daemon's private file world-readable. For this UID-1000 example, set SkillFS notify socket_path to /run/agent-sec-core/daemon.sock and its auth_key_file to that private user copy. Its control socket remains UID-1000 owned, parent 0700/socket 0600; set control trusted_peer_uid=0 and trusted_peer_key_file to the same private copy. Use HMAC authentication; leave trusted_peer_exe unset because executable authentication is a separate, mutually exclusive mode. Configure SkillFS activation and its backing root following the SkillFS runtime reference, using the V2 daemon endpoint and peer identity above instead of that document's older examples.

Both processes must see the configured absolute paths and the same backing filesystem objects. A container deployment therefore needs matching shared-volume paths. Canonical and live roots may be identical for one ordinary non-in-place mount; otherwise they must be disjoint and must not overlap other configured mappings. Resolver failure does not fall back to scanning the FUSE view. A mismatched directory inode is rejected instead of certifying replacement content.

Notifications coalesce per Skill, then trigger scan followed by activation. A notify acknowledgment confirms queue admission only. status includes skillfs queue/running/processed/failed counters when configured. Check those counters, public audit and actual FUSE reads to distinguish admission, completed processing and visible publication. Startup reschedules exact mount Skills saved in configuration.

Output, audit and limits​

The CLI prints business JSON to stdout. check returns 1 for deny, tampered or execution error; completed scan/certify and complete analyze return 0 even for risk findings. Incomplete analyze returns 1; invalid analyze input returns 2. Transport failures go to stderr and return 1. Always inspect verdict and activation fields as well as the process exit code.

Use --socket or AGENT_SEC_DAEMON_SOCKET for an explicit endpoint. SkillSec defaults to 60 seconds; --timeout-ms is capped at 120 seconds server-side. A timeout does not establish whether a write committed. Read status/history before retrying a mutation. Oversized responses return ResponseTooLarge and operationMayHaveCommitted=true, rather than truncated data.

Public audit uses the shared Action Runtime and the private /var/log/agent-sec event store. Its projection contains operation, counts, verdict, version and error class; it omits source code, raw findings, local paths, manual reasons and keys. Full business data remains in the CLI response. Built-in scans are bounded to 2,000 files, 50 MiB and depth 32; findings import is limited to 2 MiB. Coverage failures remain visible instead of certifying partial content.

System key and deployment rollback​

V2 initializes one system signing key and reuses it. Key rotation is unsupported: rotate-keys and init --force-keys fail before directory discovery, scanning or key changes; direct RPC requests are also rejected. Ordinary init --no-baseline remains idempotent and does not replace an unsafe or corrupt existing key.

There is no private managed-skills.json registry or key-rotation.json transaction. Files left by unreleased implementations are not read, migrated or deleted. Do not manually delete the signing key to simulate rotation: existing signed history depends on it.

Before changing an existing deployment, stop its daemon and SkillFS writers, retain its matching binary/configuration, and back up private key/state plus each Skill's content and .skill-meta. Use copies for migration evaluation. To return to V1, stop V2 and restore the matching V1 files, keys, configuration and metadata before starting V1/SkillFS. Do not point V1 at V2 manifests or mix restored old metadata with modified source content. Keeping a binary alone is not a state rollback, and starting both daemons is not an upgrade strategy.