Back to skill

Security audit

consensus-permission-escalation-guard

Security checks for vulnerabilities and agentic risk

Overview

The skill is disclosed as a local IAM-escalation decision gate, but it allows caller-supplied policy settings and external votes to influence high-impact approval decisions without enough trusted scoping.

Treat this as Review, not proven malware. Before installing it in any workflow that can actually grant permissions, ensure policy constraints are supplied from trusted deployment configuration, not from the requester; accept external_agent votes only after authentication/signature and duplicate/freshness checks; include vote evidence in replay/idempotency decisions; pin and audit dependencies; and run it with a dedicated non-privileged state directory.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/index.mjs:151
Finding
Request-Controlled Constraints Can Disable Security Safeguards<![CDATA[ ## Vulnerability Details **File Location**: `src/index.mjs:151-161` **Vulnerability Type**: Authorization policy bypass through untrusted policy configuration **Risk Level**: High ### Vulnerable Code ```js function normalizeConstraints(c = {}) { return { require_ticket: c.require_ticket !== false, require_justification: c.require_justification !== false, require_expiry_for_temporary: c.require_expiry_for_temporary !== false, max_temporary_duration_minutes: Number.isInteger(c.max_temporary_duration_minutes) ? c.max_temporary_duration_minutes : 240, block_wildcard_permissions: c.block_wildcard_permissions !== false, production_requires_human_confirm: c.production_requires_human_confirm !== false, forbid_break_glass_without_incident: c.forbid_break_glass_without_incident !== false }; } ``` The input schema also explicitly permits callers to supply these policy settings: ```json "constraints": { "type": "object", "additionalProperties": false, "properties": { "require_ticket": { "type": "boolean", "default": true }, "require_justification": { "type": "boolean", "default": true }, "require_expiry_for_temporary": { "type": "boolean", "default": true }, "max_temporary_duration_minutes": { "type": "integer", "minimum": 1, "default": 240 }, "block_wildcard_permissions": { "type": "boolean", "default": true }, "production_requires_human_confirm": { "type": "boolean", "default": true }, "forbid_break_glass_without_incident": { "type": "boolean", "default": true } } } ``` ### Technical Analysis The security constraints are taken directly from the same request that is being evaluated. The normalization logic treats an explicit `false` value as authorization to disable the corresponding safeguard. An untrusted requester can therefore disable: - Ticket requirements - Justification requirements - Temporary-access expiration requirements - Wildcard-permission blocking - Production human-confi ...[truncated 1989 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove security-policy controls from the untrusted invocation schema. 2. Load the effective constraints from a trusted deployment configuration, authenticated board configuration, or server-controlled `opts` value. 3. If request-level overrides are necessary, only permit changes that make the policy stricter. For example: - Permit `false` to become `true`, but never `true` to become `false`. - Permit a shorter maximum duration, but never a longer one. 4. Require a separate privileged administrative authorization before accepting weaker policy settings. 5. Record the trusted policy version and policy source in every decision artifact. 6. Add tests proving that request data cannot disable wildcard blocking, ticket enforcement, expiration enforcement, production confirmation, or break-glass incident checks. 7. Fail closed when trusted policy configuration is missing or invalid. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/index.mjs:63
Finding
External Votes and Reputation Values Are Accepted Without Authentication<![CDATA[ ## Vulnerability Details **File Location**: `src/index.mjs:63-74` **Vulnerability Type**: Forged consensus identities and authorization votes **Risk Level**: High ### Vulnerable Code ```js function validateVote(v) { if (!v || typeof v !== 'object' || Array.isArray(v)) return 'external_votes item must be object'; let e = rejectUnknown(v, VOTE, 'external_votes[]'); if (e) return e; if (!isNonEmptyString(v.persona_id)) return 'external_votes[].persona_id is required'; if (!isNonEmptyString(v.name)) return 'external_votes[].name is required'; if (typeof v.reputation_before !== 'number' || v.reputation_before < 0.05 || v.reputation_before > 0.95) return 'external_votes[].reputation_before must be 0.05..0.95'; if (!['YES', 'NO', 'REWRITE'].includes(v.vote)) return 'external_votes[].vote must be YES|NO|REWRITE'; if (typeof v.confidence !== 'number' || v.confidence < 0 || v.confidence > 1) return 'external_votes[].confidence must be 0..1'; if (!Array.isArray(v.reasons) || !Array.isArray(v.red_flags) || !Array.isArray(v.suggested_edits)) return 'external_votes[].reasons/red_flags/suggested_edits must be arrays'; return null; } ``` The validated caller-controlled votes are then used directly for aggregation: ```js const votes = externalMode ? input.external_votes : makePersonaVotes(personaSet, policyFlags); const ag = aggregateVotes(votes, { method: 'WEIGHTED_APPROVAL_VOTE', approve_threshold: 0.7 }); let final_decision = DECISION_MAP(ag.final_decision); if (policyFlags.hard_flags.length) final_decision = 'BLOCK'; else if (policyFlags.rewrite_flags.length && final_decision === 'ALLOW') final_decision = 'REQUIRE_REWRITE'; ``` ### Technical Analysis External vote validation checks only field presence, data types, enumerated vote values, and numeric ranges. It does not verify that: - The stated persona exists in a trusted persona set - The vote was produced by that persona - The caller is authorized to submit external votes - `reputati ...[truncated 1927 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require each external vote to be contained in a signed envelope covering: - Board identifier - Request identifier or canonical request digest - Persona identifier - Vote - Confidence - Timestamp - Nonce or sequence number 2. Verify signatures against trusted persona keys before aggregation. 3. Resolve persona names and reputation values from trusted state rather than accepting them from request input. 4. Reject unknown, disabled, expired, and duplicate persona identifiers. 5. Bind every vote to the exact escalation content to prevent vote reuse across requests. 6. Enforce vote freshness and replay protection. 7. Restrict `external_agent` mode to authenticated and explicitly authorized callers. 8. Require a minimum number of distinct trusted voters where appropriate. 9. Add negative tests for forged identities, caller-supplied reputation inflation, duplicate personas, stale votes, cross-request replay, and invalid signatures. 10. Fail closed if trusted persona or signature state cannot be loaded. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.mjs:220
Finding
External Votes Are Omitted from the Idempotency Key<![CDATA[ ## Vulnerability Details **File Location**: `src/index.mjs:220-231` **Vulnerability Type**: Stale authorization decision replay caused by incomplete idempotency binding **Risk Level**: Medium ### Vulnerable Code ```js const idem = makeIdempotencyKey({ board_id, proposed_escalation: input.proposed_escalation, constraints, persona_set_id: input.persona_set_id || null, mode: input.mode || 'persona' }); const prior = await getDecisionByKey(board_id, idem, statePath); if (prior?.response) return prior.response; ``` The external votes that materially determine the result are selected later: ```js const votes = externalMode ? input.external_votes : makePersonaVotes(personaSet, policyFlags); const ag = aggregateVotes(votes, { method: 'WEIGHTED_APPROVAL_VOTE', approve_threshold: 0.7 }); ``` ### Technical Analysis The idempotency key includes the board, escalation, constraints, persona-set identifier, and mode, but it excludes `external_votes`. In `external_agent` mode, two submissions with different votes therefore produce the same idempotency key as long as the other fields remain unchanged. The first persisted decision is returned before the new vote set is aggregated. This makes an authorization-relevant result dependent on submission order rather than the supplied voting evidence. It can also cause a favorable historical response to remain effective after reviewers replace or correct their votes. ### Attack Path 1. An escalation request is prepared in `external_agent` mode. 2. A favorable set of external votes is submitted first. 3. The guard aggregates those votes, returns `ALLOW`, and persists the response under the generated idempotency key. 4. Reviewers subsequently submit the same escalation with corrected, rejecting, or rewrite votes. 5. Because the new votes are omitted from the idempotency key, `getDecisionByKey()` finds the earlier artifact. 6. The handler returns the cached `ALLOW` before processing the replacement votes. 7. ...[truncated 562 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Include a canonical digest of all authenticated external votes in the idempotency key. 2. Canonicalize vote ordering before hashing so equivalent vote sets produce stable keys. 3. Prefer binding idempotency to a trusted immutable vote-set identifier or governance-round identifier. 4. Include vote revisions or round numbers when votes are allowed to change. 5. Define explicit lifecycle rules for superseding decisions rather than silently returning an earlier response. 6. Store the canonical request digest and vote-set digest in the decision artifact. 7. Use atomic state operations or locking to prevent concurrent submissions from creating order-dependent decisions. 8. Add tests demonstrating that: - Identical requests and identical votes replay the same decision. - Different votes do not replay an earlier decision. - Corrected or superseding vote rounds cannot inherit stale approval. - Concurrent conflicting vote submissions fail safely or follow a deterministic trusted rule. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Known Vulnerable Dependency: fast-uri==3.1.0 — 7 advisory(ies): CVE-2026-13676 (fast-uri vulnerable to host confusion via failed IDN canonicalization); CVE-2026-18446 (fast-uri vulnerable to host confusion via backslash authority introducer); CVE-2026-75975 (fast-uri vulnerable to server-side request forgery via malformed IPv6 normalizat) +4 more

High
Category
Supply Chain
Confidence
96% confidence
Finding
fast-uri 3.1.0 is flagged for multiple URI parsing and canonicalization issues, including host confusion and SSRF-relevant edge cases. This skill is explicitly a governance/security control around IAM and permission escalation decisions, so incorrect URI parsing in any downstream network, policy, callback, or artifact-handling logic could undermine trust boundaries or route requests to attacker-controlled targets.

Known Vulnerable Dependency: esbuild==0.27.3 — 1 advisory(ies): GHSA-g7r4-m6w7-qqqr (esbuild allows arbitrary file read when running the development server on Window)

Low
Category
Supply Chain
Confidence
60% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"demo": "node --import tsx run.js --input ./examples/input.json"
  },
  "dependencies": {
    "ajv": "^8.17.1",
    "ajv-formats": "^3.0.1",
    "consensus-guard-core": "^1.1.15",
    "tsx": "^4.20.3"
Confidence
90% confidence
Finding
The package uses a caret range for the ajv dependency, which allows automatic installation of newer minor and patch releases. While common in the Node.js ecosystem, this weakens build determinism and can introduce supply-chain risk if an upstream release is compromised or contains a breaking security regression.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "ajv": "^8.17.1",
    "ajv-formats": "^3.0.1",
    "consensus-guard-core": "^1.1.15",
    "tsx": "^4.20.3"
  },
Confidence
90% confidence
Finding
The ajv-formats dependency is specified with a caret range, so future compatible releases may be pulled in without explicit review. In a security-governance skill, this increases supply-chain exposure because validation behavior is part of the trust boundary and unexpected upstream changes could affect policy enforcement.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "ajv": "^8.17.1",
    "ajv-formats": "^3.0.1",
    "consensus-guard-core": "^1.1.15",
    "tsx": "^4.20.3"
  },
  "license": "MIT",
Confidence
95% confidence
Finding
The consensus-guard-core dependency is unpinned, allowing newer minor or patch versions to be installed automatically. Because this package appears central to permission-escalation governance decisions, upstream changes or a compromised release could directly alter ALLOW/BLOCK logic, making the context more sensitive than a typical utility package.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"ajv": "^8.17.1",
    "ajv-formats": "^3.0.1",
    "consensus-guard-core": "^1.1.15",
    "tsx": "^4.20.3"
  },
  "license": "MIT",
  "engines": {
Confidence
86% confidence
Finding
The tsx dependency is also specified with a caret range, permitting unreviewed updates within the allowed semver range. Although likely used for testing and demo execution, compromised or unexpected dependency updates can still affect local execution paths and developer environments.

Vague Triggers

Low
Confidence
78% confidence
Finding
This JSON manifest/test-vector file uses the mode value "external_agent" without any accompanying natural-language constraint about when that mode should or should not be used. In manifest-like files, broad activation descriptors without explicit scope can create ambiguity about invocation conditions.

Static analysis

No suspicious patterns detected.