Skip to content

Feat/detection hardening - #220

Open
Ar9av wants to merge 10 commits into
mainfrom
feat/detection-hardening
Open

Feat/detection hardening#220
Ar9av wants to merge 10 commits into
mainfrom
feat/detection-hardening

Conversation

@Ar9av

@Ar9av Ar9av commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Ar9av added 10 commits July 10, 2026 14:44
semantic_guard_v2.py prepended the prismor package directory to sys.path
(a v1 relic). That made the PEP-420 namespace shims prismor/openai,
prismor/crewai, prismor/langchain and prismor/browser_use importable as
top-level modules, so `import openai` (or crewai/langchain/browser_use)
resolved to the adapter shim and hijacked sys.modules — breaking the real
SDK for every in-process framework adapter.

The sibling heuristic import is absolute and resolves through the installed
prismor namespace, so the sys.path.insert is unnecessary. Remove it and add
a regression test asserting the prismor package dir never leaks onto sys.path.
… the hook boundary

Extend the hook-dispatch decision from binary allow/block to honor the
finding action: step_up emits a Claude/Copilot 'ask' (inline human approval),
modify rewrites tool input via a named transform (hookSpecificOutput.updatedInput).
Unsupported verdicts fail closed to block, never a silent allow. Adds a transform
registry (sandbox, cloak) and end-to-end tests.
…dentity binding)

Each enrolled device holds an Ed25519 keypair and signs every receipt over a
canonical {hash, ts, identity} payload — binding the immutable chain hash to the
receipt's device/agent/human-principal identity and timestamp. Records carry
signature/signing_pubkey/signing_key_id/signing_alg; the pubkey is registered at
enrollment for control-plane verification + trusted-on-first-use pinning.
Optional cryptography extra; degrades to the hash chain when absent.
…agents

New prismor/runtime/intent.py capture_intent synthesizes the session's
intent-scoped rules from a goal + the agent's tool names (idempotent per session,
never raises). Wired as a goal= param on guard_agent/guard_tools/guard_controller
across all four SDK adapters, so evaluate_tool_call's scoped enforcement now
applies task-alignment to deployed framework agents, not just hook agents.
…cement

Session-scoped control over how tools are combined. Tools classify as red
(untrusted-content ingest) or blue (critical action); the first call from the
second category in a session is blocked before it executes, from the session's
prior tool-use history. Terminal and non-overridable when enforcing; opt-in and
observe-first by default.

- trifecta.py: classifier (explicit map -> built-in defaults -> event-type/
  finding inference) + per-session CategoryLedger (persisted like _TaintStore)
- policy_engine.py: crossover check in evaluate() emits a lethal_trifecta
  finding; rule id + category added to the non-overridable floor
- default_policy.yaml: settings.tool_categories block (disabled/observe default)
- examples/lethal-trifecta: demo.sh (5 scenarios via evaluate_tool_call) and
  hook_demo.sh (real hook-dispatch, red->blue returns exit 2)
- tests/test_trifecta.py: 11 tests (classifier, ledger, end-to-end enforce/observe)
…en sets

Replaces the fixed red/blue model with arbitrary, org-defined tags and forbidden
tag combinations (settings.tool_tags). A tool carries one or more tags; a session
may not COMPLETE any `incompatible` tag set. Red/blue becomes the default rule
[untrusted_content, critical_action]; N-tag rules (e.g. a 3-tag combination) work
the same way.

- trifecta.py: classify_tool_tags -> set of tags; TagLedger tracks first-seen
  tag+tool per session; completes() is index-aware so an idempotent pre-pass
  (analyze_events) re-recording the current call's tag doesn't mask a real
  completion.
- policy_engine.py: block emits on the call that completes a forbidden set
- default_policy.yaml: settings.tool_tags (tags map + incompatible list)
- demo.py: adds a custom 3-tag scenario; tests cover 2-tag, 3-tag, observe
Four independent detection improvements, each opt-in or fallback-only so no
input that is classified a given way today changes classification.

1. Homoglyph / invisible-character evasion rescan (policy_engine)

   Every rule pattern matches literal ASCII, and the existing confusable check
   covered only command/path/url — never prompt or combined_text, which carry
   the untrusted-content trust class. So `іgnore previous instructions`
   (Cyrillic і) or `r<ZWSP>m -rf /` evaded both.

   The raw scan runs first and unchanged; only on a miss, and only when the
   value is non-ASCII, is an NFKC + confusable-folded copy re-scanned. Evidence
   stays the original bytes and findings gain `evasion: unicode_obfuscation`.
   Folds are memoized per event (previously recomputed per rule). Notably this
   routes zero-width `rm -rf /` to destructive-command, which sits in the
   non-overridable floor, so it now blocks rather than only warning.

2. Fail-open in the semantic guard's verdict parsing (semantic_guard_v2)

   `re.search(r"\{[^{}]*\}")` matched the innermost object, so a verdict with
   any nested value — {"risk_score": 0.92, "category": "credential_exfil",
   "detail": {...}} — was read as risk_score 0.0, i.e. clean. Replaced with a
   brace-balancing extractor that tracks string literals, plus tolerance for a
   single wrapper key.

3. Optional per-rule `condition:` expression (policy_engine, policy_schema)

   Rules may name pattern groups and combine them: "patterns and not
   benign_context", "2 of (a, b, c)", "any of (x, y)". Parsed once into a
   validated AST — no eval, node whitelist — so no policy layer, including a
   signed org overlay, can smuggle in code execution.

   A condition can only ever narrow, so it is refused outright on the
   non-overridable floor; otherwise an overlay could ship
   `condition: "patterns and never_matches"` as a disable in disguise. A
   malformed condition is dropped while patterns are kept, so a typo can never
   silently turn a rule off.

4. pnpm / yarn lockfile coverage (deps, policy_engine)

   The transitive post-install CVE scan read package-lock.json only, leaving
   pnpm and yarn workspaces with no transitive coverage. Adds parsers for
   pnpm v6/v9 and yarn v1/Berry, unioned via read_js_lockfiles_full — all three
   resolve from the same registry, so OSV treats them as one npm ecosystem.
   Bun stays excluded; its lockfile is binary.

Also surfaces real coverage in `prismor doctor`: how many rules can actually
block, and which optional layers are live. A stock install reports 21/70 rules
blocking with semantic guard, tool tags and sandbox all off.

62 new tests. Suite goes 831 -> 893 passing with no change to the 8 pre-existing
failures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant