Back to skill

Security audit

consensus-agent-action-guard

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed action-governance guard, but its high-impact decisions can be driven by weakly validated caller input.

Review this carefully before installing in any automation path that can delete data, post publicly, send messages, or mutate business systems. It should not be the sole authority for high-risk execution unless external votes are authenticated and schema-validated, action metadata is enforced conservatively, and installs are locked with the committed package-lock file.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T09 · Insecure Skill Coding Practices

Error
Location
src/index.mjs:68
Finding
Caller-Controlled External Votes Can Bypass Local Safety Evaluation<![CDATA[ ## Vulnerability Details **File Location**: `src/index.mjs:16-17` and `src/index.mjs:68-73` **Vulnerability Type**: Untrusted decision input and authorization bypass **Risk Level**: High ### Complete Code Snippet ```js if(input.mode!==undefined && !['persona','external_agent'].includes(input.mode)) return 'mode must be persona|external_agent'; if(input.external_votes!==undefined && !Array.isArray(input.external_votes)) return 'external_votes must be array'; ``` ```js const externalMode = input.mode === 'external_agent'; const idem = makeIdempotencyKey({ board_id, proposed_action: input.proposed_action, constraints: input.constraints||{}, persona_set_id: input.persona_set_id||null }); const prior = await getDecisionByKey(board_id, idem, statePath); if (prior?.response) return prior.response; let personaSet = externalMode ? null : (input.persona_set_id ? await getPersonaSet(board_id, input.persona_set_id, statePath) : await getLatest(board_id, 'persona_set', statePath)); if (!personaSet && !externalMode) { personaSet = { persona_set_id: null, personas: [1,2,3,4,5].map((n)=>({ persona_id:`default-${n}`, name:`Default Persona ${n}`, reputation:0.5 })) }; } const votes = externalMode ? input.external_votes : makeVotes(personaSet, input.proposed_action, input.constraints || {}); const ag = aggregateVotes(votes, { method:'WEIGHTED_APPROVAL_VOTE', approve_threshold:0.7 }); ``` ### Technical Analysis When `mode` is `external_agent`, the caller-provided `external_votes` array is passed directly to `aggregateVotes()`. Local validation only confirms that the value is an array. The code does not enforce a vote schema, authenticate voter identities, verify signatures, restrict voters to an authorized set, or establish an independent quorum. External mode also bypasses `makeVotes()`, which is where this package applies its local checks for high-risk irreversible actions, sensitive-data flags, and human-confirmation requirements. Consequently, a party capab ...[truncated 1468 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define and enforce a strict schema for every external vote, including required fields, allowed vote values, confidence bounds, and numeric reputation bounds. 2. Authenticate each external voter and verify a digital signature over the board ID, action digest, vote, timestamp, and nonce. 3. Restrict accepted identities to a board-specific allowlist and reject duplicate, expired, replayed, or unknown votes. 4. Require a minimum quorum of distinct trusted voters rather than relying only on caller-supplied weights. 5. Do not trust reputation or voting weight supplied by the request. Load these values from trusted board state. 6. Apply non-bypassable local policy before aggregation. Sensitive-data, prohibited-action, and high-risk irreversible checks should be able to force `BLOCK` regardless of external consensus. 7. Bind the idempotency key to the vote set or trusted vote-artifact identifiers when external votes can affect the result. 8. Add negative tests proving that self-issued votes, unknown voters, forged weights, duplicate voters, and external votes for locally prohibited actions cannot produce `ALLOW`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.mjs:30
Finding
Security Decisions Trust Incomplete and Weakly Typed Action Metadata<![CDATA[ ## Vulnerability Details **File Location**: `src/index.mjs:5-17` and `src/index.mjs:30-36` **Vulnerability Type**: Insufficient input validation and policy bypass **Risk Level**: Medium ### Complete Code Snippet ```js const TOP = new Set(['board_id','proposed_action','constraints','persona_set_id','mode','external_votes']); const ACTION = new Set(['action_type','target','summary','irreversible','external_side_effect','risk_level']); const CONSTRAINTS = new Set(['require_human_confirm_for_irreversible','block_sensitive_exfiltration']); const err=(b,c,m,d={})=>({board_id:b,error:{code:c,message:m,details:d}}); function validate(input){ if(!input||typeof input!=='object'||Array.isArray(input)) return 'input must be object'; let e=rejectUnknown(input,TOP,'input'); if(e) return e; if(typeof input.board_id!=='string' || !input.board_id.trim()) return 'board_id is required'; if(!input.proposed_action||typeof input.proposed_action!=='object'||Array.isArray(input.proposed_action)) return 'proposed_action is required'; e=rejectUnknown(input.proposed_action,ACTION,'proposed_action'); if(e) return e; if(input.constraints!==undefined){ if(typeof input.constraints!=='object'||Array.isArray(input.constraints)) return 'constraints must be object'; e=rejectUnknown(input.constraints,CONSTRAINTS,'constraints'); if(e) return e; } if(input.mode!==undefined && !['persona','external_agent'].includes(input.mode)) return 'mode must be persona|external_agent'; if(input.external_votes!==undefined && !Array.isArray(input.external_votes)) return 'external_votes must be array'; return null; } ``` ```js function makeVotes(personaSet, action, constraints={}){ const txt = `${action.action_type||''}\n${action.summary||''}`; const flags = detectHardBlockFlags(txt); const highRisk = action.risk_level === 'high'; const irreversible = !!action.irreversible; const blockExfil = constraints.block_sensitive_exfiltration && flags.includes('SENSITIVE_DA ...[truncated 2204 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace allowlist-only property checking with a complete JSON Schema or equivalent validator. 2. Require `action_type`, `target`, `summary`, `irreversible`, `external_side_effect`, and `risk_level`. 3. Require strict boolean types for boolean fields and constraints. 4. Define an explicit enumeration for `risk_level`, normalize case before evaluation, and reject unknown values. 5. Treat missing or invalid security metadata conservatively, preferably as high risk rather than safe. 6. Include `target` and all other security-relevant fields in hard-block analysis. 7. Apply policy to `external_side_effect`; for example, require additional approval for all externally side-effecting operations. 8. Derive risk from trusted action classes and destination policy instead of relying solely on caller declarations. 9. Add tests for omitted fields, incorrect types, case variants, sensitive targets, misleading summaries, and unknown risk levels. ]]>

T08 · Insecure Dependencies

Note
Location
package.json:10
Finding
Dependency Version Ranges Contradict the Documented Reproducible-Install Trust Model<![CDATA[ ## Vulnerability Details **File Location**: `package.json:10-11`; related documentation at `SKILL.md:69-71` **Vulnerability Type**: Non-exact dependency constraints and supply-chain exposure **Risk Level**: Low ### Complete Code Snippet ```json "dependencies": { "consensus-guard-core": "^1.1.15", "tsx": "^4.20.3" } ``` The stated dependency trust model is: ```md ## Dependency trust model - `consensus-guard-core` is the first-party consensus package used in guard execution - versions are semver-pinned in `package.json` for reproducible installs - this skill does not request host-wide privileges and does not mutate other skills ``` ### Technical Analysis Caret constraints are version ranges, not exact semantic-version pins. They permit package managers to select later compatible versions. This contradicts the documentation's claim that versions are pinned in `package.json` for reproducible installation. The committed `package-lock.json` records exact resolved versions, registry URLs, and integrity hashes, which materially reduces this risk when installation is performed with `npm ci` and the lockfile is trusted. However, installations that omit, regenerate, or do not strictly honor the lockfile may resolve dependency code that was not represented by the audited project snapshot. No evidence was found that the currently locked dependencies are malicious. This finding concerns preventable supply-chain drift rather than a confirmed malicious package. ### Attack Path 1. A deployment installs the package without strictly honoring the committed lockfile, or regenerates the lockfile. 2. The caret range resolves to a newer release allowed by `package.json`. 3. The newly resolved dependency is installed even though it was not part of the audited snapshot. 4. Its code executes in the Node.js process when the Skill imports or uses the dependency. 5. If the newer package release is compromised or contains a vulnerability, it inherits the Skill proces ...[truncated 394 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace caret ranges with exact versions, for example `"consensus-guard-core": "1.1.15"`. 2. Continue committing `package-lock.json` and require `npm ci` in CI/CD and production builds. 3. Reject builds when the lockfile is missing or differs unexpectedly. 4. Review dependency updates through explicit pull requests with tests and security scanning. 5. Use automated dependency vulnerability and provenance checks. 6. Correct `SKILL.md` if ranged versions are intentional so that the documented trust model accurately reflects installation behavior. 7. Consider reducing runtime dependencies where possible; if `tsx` is only needed for development, move it to `devDependencies` and avoid loading it in production. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (5)

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
81% confidence
Finding
The lockfile pulls in fast-uri 3.1.0 via ajv, and the cited advisories indicate URI parsing and normalization flaws that can cause host confusion and potentially SSRF or policy bypass when attacker-controlled URLs are validated or canonicalized. Given this skill performs pre-execution governance for high-risk agent actions, any incorrect interpretation of URLs, hosts, or authorities in policy evaluation could directly weaken safety decisions, making this more dangerous in context than in a generic utility package.

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": {
    "consensus-guard-core": "^1.1.15",
    "tsx": "^4.20.3"
  },
  "license": "MIT",
Confidence
90% confidence
Finding
The dependency version uses a caret range (^1.1.15), which allows future non-major releases to be installed. This can introduce supply-chain risk because builds are not fully reproducible and a compromised or buggy upstream minor/patch release could be pulled in without explicit review.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "consensus-guard-core": "^1.1.15",
    "tsx": "^4.20.3"
  },
  "license": "MIT",
  "engines": {
Confidence
88% confidence
Finding
The tsx dependency is also specified with a caret range, allowing automatic adoption of later compatible releases. Although common in JavaScript projects, this weakens build determinism and increases exposure to upstream supply-chain compromise or unexpected behavior changes.

Missing User Warnings

Low
Confidence
87% confidence
Finding
The handler persists a decision artifact containing the action proposal, votes, aggregation data, and response via writeArtifact(), but this file provides no user-facing indication that these governance records will be stored. In a pre-execution governance skill, those artifacts may include sensitive operational context, so silent persistence creates a privacy and data-governance issue even if it is not directly exploitable as code execution.

Static analysis

No suspicious patterns detected.