Skill Security

Mayros enforces an 18-layer security architecture for semantic skills. Skills run in a WASM sandbox with no access to the file system, network, or process. Every graph operation is namespace-scoped, rate-limited, and audit-logged.

Security layers overview

#LayerComponent
1Static scanner16 rules + anti-evasion preprocessing
2Enrichment sanitizerUnicode normalization + injection detection
3QuickJS WASM sandboxIsolated execution with resource limits
4Namespace isolationAll queries forced to ${ns}: prefix
5Tool allowlistIntersection model across active skills
6Permission resolverPer-operation permission checks
7Rate limiterSliding window per skill (calls/minute)
8Query limitsPer-skill + global cap
9Write limitsPer-sandbox session cap
10Enrichment timeout2s Promise.race on skill enrichment
11Path traversal protectionReject .. + isPathInside()
12Verify-then-promoteTemp extract, verify hashes, atomic promote
13Hot-reload securityAtomic swap, manifest validation, downgrade block
14Circuit breaker3-state with exponential backoff
15Audit loggingSkill name + operation on all sandbox writes
16No default assertion engineskill_assert fails without declared engine
17Per-request skill trackingresolveCurrentSkill() round-robin
18Integrity hashingSHA-256 of skill directory contents

Static scanner

The scanner (src/security/skill-scanner.ts) runs 16 rules against skill source code before any execution.

Line rules (12)

Rule IDSeverityDetects
dangerous-execcriticalexec, spawn, execSync with child_process context
dynamic-code-executioncriticaleval(), new Function()
crypto-miningcriticalstratum+tcp, coinhive, xmrig patterns
suspicious-networkwarnWebSocket to non-standard ports
semantic-unbounded-querywarnpatternQuery without limit
semantic-unproven-assertionwarnrequireProof: false
bracket-property-execcritical["exec"], ["spawn"], ["eval"]
dynamic-requirecriticalrequire() with non-literal argument
global-this-accesswarnglobalThis[ bracket access
process-env-bracketcriticalprocess["env"] with network context
dynamic-importcriticalimport() with non-literal argument

Source rules (4)

Rule IDSeverityDetects
potential-exfiltrationwarnreadFile + fetch/http.request
obfuscated-codewarnHex sequences (\x?? x6+)
obfuscated-codewarnLarge base64 + atob/Buffer.from
env-harvestingcriticalprocess.env + network send

Anti-evasion preprocessing

Before scanning, source code is preprocessed to defeat common evasion techniques:

  1. stripComments() — removes // and /* */ while preserving string literals
  2. joinSplitStatements() — tracks paren balance across lines (catches eval\n(...))
  3. countNetParens() — counts open/close parens outside string literals

A skill with any critical finding is rejected before sandbox instantiation.

Enrichment sanitizer

The sanitizer (enrichment-sanitizer.ts) prevents prompt injection via skill enrichment data.

Unicode normalization

Before pattern matching, all strings undergo:

  1. NFC normalization — canonical decomposition + composition
  2. Zero-width character stripping — removes \u200B, \u200C, \u200D, \u2060, \uFEFF, \u00AD
  3. Homoglyph mapping — Cyrillic and Greek lookalikes mapped to ASCII (e.g., Cyrillic \u0410 to A)
  4. Fullwidth collapseU+FF01U+FF5E mapped to ASCII 0x210x7E

Injection patterns (11)

The sanitizer detects and blocks these injection patterns:

  • ignore/disregard previous instructions
  • you are/act as/pretend to be
  • system:override/system message
  • execute the following/run bash
  • new instructions/override instructions
  • important: you must/ignore
  • curl/wget/bash/eval commands
  • rm -rf
  • do not follow the system/developer
  • developer message
  • HTML-like tags: <system>, <assistant>, <developer>

Depth limits

LimitValuePurpose
MAX_DEPTH4Maximum nesting depth
MAX_ARRAY_LENGTH50Maximum array elements
MAX_STRING_LENGTH512Maximum string value length
MAX_ENRICHMENT_CHARS4096Total output cap

Output wrapping

Sanitized enrichment is wrapped in explicit tags for the LLM:

xml
<skill-enrichment type="data">
{
  "key": "value"
}
</skill-enrichment>

QuickJS WASM sandbox

Skills execute inside QuickJS WASM ([email protected]) with no access to:

  • File system (fs)
  • Network (net, fetch, http)
  • Process (process, child_process)
  • Module system (require, import)
  • Timers (setTimeout, setInterval)
  • Workers (Worker)
  • Dynamic code (eval — harmless in WASM context)

Host functions (7 total)

Only these functions are exposed to sandboxed code:

graphClient (4 methods):

MethodSecurity layers
createTripleWrite limit check + namespace enforcement + audit log
deleteTripleWrite limit check + audit log
listTriplesNamespace enforcement on subject/predicate
patternQueryNamespace enforcement on subject/predicate

logger (3 methods): info, warn, error — prefixed with [skill:name]

Resource limits

LimitRangeDefault
memoryLimitBytes1MB–256MB8MB
maxStackSizeBytes64KB–8MB512KB
executionTimeoutMs100–60,00010,000
maxWritesPerSession50

The timeout is enforced via shouldInterruptAfterDeadline — the QuickJS runtime checks the deadline during execution and interrupts if exceeded.

Permission resolver

The PermissionResolver checks whether operations are allowed for a skill.

Operation kinds

typescript
type OperationKind =
  | "graph:read"
  | "graph:write"
  | "proofs:request"
  | "proofs:verify"
  | "proofs:publish"
  | "memory:recall"
  | "memory:remember";

Tool-to-permission mapping

ToolRequired permissions
skill_graph_querygraph:read
skill_assertgraph:write
skill_verify_assertiongraph:read, proofs:verify
skill_request_zk_proofproofs:request
skill_verify_zk_proofproofs:verify
skill_memory_contextmemory:recall

Tool allowlist (intersection model)

When multiple skills are active, a tool is allowed only if all active skills include it in their allowedTools. This prevents a permissive skill from opening access for a restrictive one.

DEFAULT_ALLOWED_TOOLS = [
  "skill_graph_query", "skill_assert", "skill_verify_assertion",
  "skill_request_zk_proof", "skill_verify_zk_proof", "skill_memory_context",
  "hub_search", "hub_verify", "mesh_request_knowledge"
]
  • If a skill omits allowedTools, the default restrictive list is applied
  • ["*"] is an escape hatch for unrestricted tool access
  • The 6 semantic tools are always allowed when permissions match

Hot-reload security

When skills are reloaded at runtime:

  1. Atomic swap: new maps built in temp variables, then swapped in
  2. Manifest validation: validateManifest() runs on every reload
  3. Downgrade block: rejects reloads where allowedTools were removed
  4. Diff logging: diffManifests() logs changes to allowedTools, permissions, assertions, and maxQueries