Feat/detection hardening - #220
Open
Ar9av wants to merge 10 commits into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.