@shomra/agent 0.2.12 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -5
- package/ai-usage.mjs +29 -0
- package/design.mjs +299 -0
- package/guard-signals.mjs +55 -9
- package/model-refs.mjs +26 -0
- package/package.json +3 -2
- package/shomra.mjs +2360 -206
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @shomra/agent
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
agent and CI and blocks dangerous tool-calls, shell commands and
|
|
5
|
-
exfiltration *before they run* — on your machine, even offline.
|
|
3
|
+
**Adversarial assurance for AI agents**, as a local-first CLI. It sits inside
|
|
4
|
+
your coding agent and CI and blocks dangerous tool-calls, shell commands and
|
|
5
|
+
data exfiltration *before they run* — on your machine, even offline. Enrolled,
|
|
6
|
+
it also attacks your org’s own guardrails (`shomra admin redteam`) and turns
|
|
7
|
+
each breach into a false-positive-gated control (`shomra harden`). It also vets AI
|
|
6
8
|
artifacts (MCP configs, Skills, slash commands, hooks, rules files) before they
|
|
7
9
|
install. Start with a free on-machine scan — no signup.
|
|
8
10
|
|
|
@@ -10,6 +12,15 @@ Zero dependencies — Node ≥ 18 built-ins only.
|
|
|
10
12
|
|
|
11
13
|
## Install
|
|
12
14
|
|
|
15
|
+
Also available as a [Dev Container Feature](devcontainer-feature/) (the guard
|
|
16
|
+
exists before the first keystroke in Codespaces / Gitpod / a local rebuild) and a
|
|
17
|
+
[GitHub Action](action.yml).
|
|
18
|
+
|
|
19
|
+
**There is no `curl … | sh` installer, deliberately.** Shomra's own Tier-0 guard
|
|
20
|
+
blocks piping a downloaded script into a shell, and the rules block it writes
|
|
21
|
+
tells coding agents never to do it. Shipping that one-liner would be the product
|
|
22
|
+
contradicting its own control in its own README.
|
|
23
|
+
|
|
13
24
|
```bash
|
|
14
25
|
npm i -g @shomra/agent # global `shomra`
|
|
15
26
|
# or run without installing:
|
|
@@ -40,6 +51,8 @@ shomra fix .mcp.json --apply # AI-fix one artifact and write it back
|
|
|
40
51
|
shomra why .mcp.json # why each finding matters + is-it-a-false-positive
|
|
41
52
|
shomra install-precommit # block risky staged AI artifacts on git commit
|
|
42
53
|
shomra install-hook --agent claude # wire the runtime firewall into Claude Code
|
|
54
|
+
shomra rules --write # teach the agent what gets blocked, so it never writes it
|
|
55
|
+
shomra mcp install # let the agent gate its own content BEFORE writing it
|
|
43
56
|
shomra scan # discover AI tooling on this machine
|
|
44
57
|
shomra status # config + firewall health
|
|
45
58
|
shomra help # full command list
|
|
@@ -66,6 +79,18 @@ shomra gate my-skill/SKILL.md # vet ONE artifact (auto-classified from
|
|
|
66
79
|
shomra gate --all . # vet every AI artifact in the repo (the CI form)
|
|
67
80
|
```
|
|
68
81
|
|
|
82
|
+
### Starting a new agent project
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
shomra new agent triage-bot # a project that starts compliant
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Guard enforcing on every model call, an egress allowlist in code rather than in
|
|
89
|
+
the prompt, untrusted input kept out of the system prompt, secrets referenced
|
|
90
|
+
from the environment, and the gate wired into CI — from commit zero. Remediating
|
|
91
|
+
a project into this shape later means changing decisions that have already been
|
|
92
|
+
built on.
|
|
93
|
+
|
|
69
94
|
## `gate` in CI
|
|
70
95
|
|
|
71
96
|
`gate`/`gate --all` are **local-first**: real static analysis (dangerous shell,
|
|
@@ -74,14 +99,39 @@ install-lure prose) runs on-machine, so you get a genuine verdict even if the
|
|
|
74
99
|
backend is unreachable. When enrolled + reachable, your **org policy** is layered
|
|
75
100
|
on top.
|
|
76
101
|
|
|
77
|
-
**Exit codes
|
|
102
|
+
**Exit codes** (one convention across every command):
|
|
103
|
+
|
|
104
|
+
| Code | Meaning |
|
|
105
|
+
|------|---------|
|
|
106
|
+
| `0` | clean / pass |
|
|
107
|
+
| `1` | hard fail — BLOCK, vulnerable model, secret found, FAIL verdict, below `--min`, regression (also `--strict` + backend outage) |
|
|
108
|
+
| `2` | soft fail — FLAG under `--strict` (REVIEW when strict) |
|
|
109
|
+
| `3` | usage / config error — not configured, bad flags, unknown command |
|
|
78
110
|
|
|
79
111
|
**Backend outage:** by default it falls back to the on-machine verdict (org
|
|
80
112
|
policy not applied). `--strict` fails closed (exit 1) because org policy can't be
|
|
81
113
|
verified. Every backend call is bounded by `SHOMRA_API_TIMEOUT_MS` (default 30s),
|
|
82
114
|
so a job never hangs.
|
|
83
115
|
|
|
84
|
-
### GitHub Actions
|
|
116
|
+
### GitHub Actions — the reusable action
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
- uses: actions/checkout@v4
|
|
120
|
+
- uses: shomra-org/agent@v0
|
|
121
|
+
with:
|
|
122
|
+
args: check # --strict is appended unless fail-on-flag: 'false'
|
|
123
|
+
api-key: ${{ secrets.SHOMRA_API_KEY }} # optional — the gate is local-first
|
|
124
|
+
url: ${{ secrets.SHOMRA_URL }}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Wire it as a **required status check** on a protected branch and it becomes the
|
|
128
|
+
un-bypassable control on GitHub.com, which has no server-side hooks. On
|
|
129
|
+
self-hosted Git and GitHub Enterprise, `shomra install-precommit --pre-receive`
|
|
130
|
+
(below) refuses the push itself.
|
|
131
|
+
|
|
132
|
+
The hand-written workflow below still works and shows what the action does.
|
|
133
|
+
|
|
134
|
+
### GitHub Actions — by hand
|
|
85
135
|
|
|
86
136
|
```yaml
|
|
87
137
|
name: Shomra AI-artifact gate
|
|
@@ -153,6 +203,9 @@ runs local-first; with a key, your **org policy** (below) drives the verdict.
|
|
|
153
203
|
with: { sarif_file: shomra.sarif }
|
|
154
204
|
```
|
|
155
205
|
|
|
206
|
+
`shomra pr` accepts it too: bare `--sarif` writes SARIF to stdout,
|
|
207
|
+
`--sarif=shomra.sarif` writes a file alongside the normal check-run output.
|
|
208
|
+
|
|
156
209
|
### Org policy + triage on top of CI
|
|
157
210
|
|
|
158
211
|
When enrolled, the same **org policy** that governs the dashboard decides the CI
|
|
@@ -180,6 +233,25 @@ shomra-gate:
|
|
|
180
233
|
SHOMRA_URL: $SHOMRA_URL
|
|
181
234
|
```
|
|
182
235
|
|
|
236
|
+
### pre-receive (server-side, cannot be skipped)
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
shomra install-precommit --pre-receive /srv/git/your-repo.git
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
A pre-commit hook is a courtesy: it lives on the developer's machine, it is one
|
|
243
|
+
`--no-verify` away, and a machine that never ran `install-precommit` has no gate
|
|
244
|
+
at all. A pre-receive hook runs on the **server**, on every push, for every
|
|
245
|
+
developer. Same check; the difference between a reminder and a control.
|
|
246
|
+
|
|
247
|
+
It **fails closed** — the opposite of the client hook. Blocking a local commit
|
|
248
|
+
because a binary is missing is hostile; waving a push through for the same reason
|
|
249
|
+
makes deleting the binary the bypass.
|
|
250
|
+
|
|
251
|
+
Available on self-hosted Git (GitLab, Gitea, Bitbucket DC, plain bare repos) and
|
|
252
|
+
GitHub Enterprise. GitHub.com does not run server-side hooks — use the action as
|
|
253
|
+
a required status check instead.
|
|
254
|
+
|
|
183
255
|
### pre-commit (local, blocks risky artifacts before they land)
|
|
184
256
|
|
|
185
257
|
`.git/hooks/pre-commit` (or a [pre-commit](https://pre-commit.com) `local` hook):
|
|
@@ -206,6 +278,231 @@ backend, behind a short timeout + circuit breaker — so a slow or down backend
|
|
|
206
278
|
never freezes the agent. Fail-open by default; `SHOMRA_GUARD_STRICT=1` fails
|
|
207
279
|
closed on the server tier.
|
|
208
280
|
|
|
281
|
+
Three channels are screened:
|
|
282
|
+
|
|
283
|
+
| Channel | Hook | What it stops |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| **Tool call** | PreToolUse / `beforeShellExecution` | the shell command, artifact write or MCP call, before it runs |
|
|
286
|
+
| **Tool result** | PostToolUse / `afterMCPExecution` | injection, exfil sinks and hidden payloads in what a fetch/read brings *back* |
|
|
287
|
+
| **Prompt** | `UserPromptSubmit` (Claude Code) / `beforeSubmitPrompt` (Cursor) | what **you** paste, before it leaves the machine |
|
|
288
|
+
| **Plan** | `PreToolUse` on `ExitPlanMode` (Claude Code) | nothing — it *informs*. See [`shomra plan`](#shomra-plan--threat-model-what-the-agent-is-about-to-build) |
|
|
289
|
+
|
|
290
|
+
The prompt channel is the one a person controls, and the only one where the leak
|
|
291
|
+
is a paste rather than a tool call. A live credential in a prompt is refused;
|
|
292
|
+
pasted text that reads as an instruction to an agent is passed through but
|
|
293
|
+
flagged **to the model** as untrusted data rather than blocked — you meant to
|
|
294
|
+
send it, the risk is that you did not read it. Backtick-quoted payloads are
|
|
295
|
+
down-ranked, so asking *why does `<pattern>` get flagged* is never blocked.
|
|
296
|
+
`SHOMRA_PROMPT_GUARD_OFF=1` disables just this channel. Only the two vendors with
|
|
297
|
+
a documented pre-submit hook that can stop a submission are wired; the rest get
|
|
298
|
+
nothing rather than a guessed event name that would silently never fire.
|
|
299
|
+
|
|
300
|
+
## Prevention: get in front of the model
|
|
301
|
+
|
|
302
|
+
Everything above intercepts *after* the model has written something. These run
|
|
303
|
+
before it.
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
shomra design docs/rfc.md # threat-model a system that does not exist yet
|
|
307
|
+
shomra add model owner/m # vet anything before it lands on the machine
|
|
308
|
+
shomra rules --write # teach the agent what gets blocked here
|
|
309
|
+
shomra rules --check # CI: fail when the block goes stale
|
|
310
|
+
shomra mcp install # let the agent gate its own content before writing it
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### `shomra design` — threat-model the ticket, not the repo
|
|
314
|
+
|
|
315
|
+
Every other command needs an artifact. This one reads a **description** — an RFC,
|
|
316
|
+
a design doc, a Jira/Linear ticket, a PR body — and answers the only question
|
|
317
|
+
worth asking before anyone writes code: does the thing being described hand an
|
|
318
|
+
attacker a path from untrusted input to a consequence?
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
shomra design docs/rfc-042.md
|
|
322
|
+
shomra design docs/ --strict # every design doc, fail on any closed path
|
|
323
|
+
gh issue view 42 --json body -q .body | shomra design -
|
|
324
|
+
shomra design docs/rfc-042.md --checklist | gh issue comment 42 -F -
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
It uses the platform's own model: capabilities split into **sources** (untrusted
|
|
328
|
+
input, sensitive data, filesystem) and **sinks** (network egress, execution,
|
|
329
|
+
destructive action). A closed source→sink pair is an attack path. That model does
|
|
330
|
+
not care whether the capabilities came from a scan or from a sentence — here they
|
|
331
|
+
come from a sentence, and each one cites the line that evidenced it so you can
|
|
332
|
+
disagree with the machine's reading.
|
|
333
|
+
|
|
334
|
+
`--checklist` emits the conditions as a markdown task list, which is the form
|
|
335
|
+
anyone actually acts on: paste it into the ticket as acceptance criteria.
|
|
336
|
+
|
|
337
|
+
> **It reads prose, so it sees only what was written down.** There is deliberately
|
|
338
|
+
> no clean verdict. `NOT_DESCRIBED` means the document did not describe
|
|
339
|
+
> capabilities in a way this matched — it is **not** a statement that the system
|
|
340
|
+
> has none. A threat model that reads as a clean bill of health is worse than
|
|
341
|
+
> none, because it is consumed exactly when the design is still cheap to change.
|
|
342
|
+
|
|
343
|
+
Exit codes: `1` when untrusted input reaches execution or a destructive action
|
|
344
|
+
(the shape where the attacker picks the action), `2` for any other closed path
|
|
345
|
+
under `--strict`.
|
|
346
|
+
|
|
347
|
+
### `shomra plan` — threat-model what the agent is about to build
|
|
348
|
+
|
|
349
|
+
`design` reads a document a human remembered to write. Coding agents produce a
|
|
350
|
+
**plan** before every non-trivial task, constantly and automatically, and nothing
|
|
351
|
+
looks at it. Same analysis, a hundred times the frequency, zero human effort.
|
|
352
|
+
|
|
353
|
+
The loop: agent proposes a plan → Shomra threat-models it → the controls land in
|
|
354
|
+
the agent's context **before it writes line one**. The agent builds the guarded
|
|
355
|
+
version first, instead of building the unguarded one and having the firewall
|
|
356
|
+
refuse it three tool calls later.
|
|
357
|
+
|
|
358
|
+
Three ways in, deliberately redundant, strongest first:
|
|
359
|
+
|
|
360
|
+
1. **`shomra_review_plan`** — an MCP tool, so any MCP-capable agent can call it
|
|
361
|
+
mid-task with no vendor hook. Register it with `shomra mcp install`.
|
|
362
|
+
2. **The rules block asks the agent to call it.** Once the MCP server is
|
|
363
|
+
registered, `shomra rules --write` adds a *Before you implement a plan*
|
|
364
|
+
section — so `mcp install` and `rules --write` compose into a closed loop.
|
|
365
|
+
3. **A Claude Code `PreToolUse` hook on `ExitPlanMode`**, wired by
|
|
366
|
+
`install-hook`. Zero-effort, but that tool name is not in the published hook
|
|
367
|
+
docs, so it is the optional path and never the only one.
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
shomra plan plan.md # or: … | shomra plan -
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**A plan is a proposal, so the default is to inform, never refuse.** Denying a
|
|
374
|
+
plan spends a turn and tells the model only that it was wrong, not how — the
|
|
375
|
+
controls are the useful payload. Only untrusted-input-reaches-a-hard-sink
|
|
376
|
+
escalates to *ask*, and only under `SHOMRA_GUARD_STRICT=1`.
|
|
377
|
+
`SHOMRA_PLAN_GUARD_OFF=1` disables just this channel.
|
|
378
|
+
|
|
379
|
+
### `shomra corpus` — screen the index, not the retrieval
|
|
380
|
+
|
|
381
|
+
The result firewall screens what a retrieval brings *back*. Nothing screened what
|
|
382
|
+
went **in** — so a poisoned document sits in the vector store indefinitely,
|
|
383
|
+
clean-until-retrieved, and is judged for the first time at the worst possible
|
|
384
|
+
moment: as one chunk, stripped of its document, inside a request a user is
|
|
385
|
+
waiting on.
|
|
386
|
+
|
|
387
|
+
Index time wins on all three counts. The whole document is present, so a payload
|
|
388
|
+
split across paragraphs is visible. The cost is paid once per document instead of
|
|
389
|
+
once per retrieval. And a document that fails is simply never embedded — a
|
|
390
|
+
control rather than a detection.
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
shomra corpus ./kb --manifest .shomra/corpus.json
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
✗ QUARANTINE escalation.md
|
|
398
|
+
HIGH Injected instruction: "ignore all previous" (line 243 · chunk 19)
|
|
399
|
+
✗ QUARANTINE hidden.md
|
|
400
|
+
CRITICAL Invisible / bidirectional characters
|
|
401
|
+
⚠ 2 files could not be read — they are NOT covered by the result above:
|
|
402
|
+
2 × binary format — no text extractor
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Findings carry the **chunk index**, not just the line, because retrieval returns
|
|
406
|
+
chunks and the chunk is what actually reaches the model. The manifest is the
|
|
407
|
+
point of the command: feed it to your ingestion job so a quarantined document is
|
|
408
|
+
never embedded.
|
|
409
|
+
|
|
410
|
+
> **Absence accounting is load-bearing.** Real corpora are mostly PDF, DOCX and
|
|
411
|
+
> PPTX — formats this cannot read. A screen that silently skips them and prints
|
|
412
|
+
> "clean" is a lie about the majority of the corpus, so every unreadable file is
|
|
413
|
+
> counted and reported next to the verdict, and `--strict` fails on them:
|
|
414
|
+
> *we could not check it* is not *it is fine*.
|
|
415
|
+
|
|
416
|
+
Fenced code blocks are down-ranked — a docs corpus is full of examples, and an
|
|
417
|
+
example is not a live instruction. A directive in prose is the real threat and
|
|
418
|
+
survives the down-rank.
|
|
419
|
+
|
|
420
|
+
### `shomra add` — vet at acquisition, not after
|
|
421
|
+
|
|
422
|
+
`mcp add` gated one channel. An agent acquires from four, and the other three had
|
|
423
|
+
no gate at all: a skill copied out of a gist, a model pulled from the Hub, a
|
|
424
|
+
package installed because an agent suggested the name.
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
shomra add mcp files npx -y @modelcontextprotocol/server-filesystem /tmp
|
|
428
|
+
shomra add skill ./downloaded-skill # manifest AND the scripts it bundles
|
|
429
|
+
shomra add model openai-community/gpt2 # against the Model Index, before any weights download
|
|
430
|
+
shomra add package langchian --type pypi # → BLOCK: 2 edits from langchain
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
One verdict vocabulary (ALLOW / FLAG / BLOCK), one exit-code contract, `--force`
|
|
434
|
+
to override a BLOCK deliberately rather than by accident. After something lands
|
|
435
|
+
the question changes from *should we take this?* to *is it safe to remove?*,
|
|
436
|
+
which is a much worse question to be asked.
|
|
437
|
+
|
|
438
|
+
**Unknown is never clean.** An unreachable Model Index, an unscanned model, and a
|
|
439
|
+
package the AI catalog does not recognise all return **FLAG**, not ALLOW —
|
|
440
|
+
"we could not check" and "it is fine" are different answers.
|
|
441
|
+
|
|
442
|
+
**`shomra rules`** compiles what Shomra actually enforces — plus what *this repo*
|
|
443
|
+
already trips, plus your org's policy when enrolled — into the agent's own
|
|
444
|
+
context files:
|
|
445
|
+
|
|
446
|
+
| Agent | File |
|
|
447
|
+
|---|---|
|
|
448
|
+
| Claude Code | `CLAUDE.md` |
|
|
449
|
+
| Codex CLI (and the cross-vendor default) | `AGENTS.md` |
|
|
450
|
+
| Cursor | `.cursor/rules/shomra.mdc` |
|
|
451
|
+
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
452
|
+
| Gemini CLI | `GEMINI.md` |
|
|
453
|
+
| Windsurf | `.windsurfrules` |
|
|
454
|
+
| Cline | `.clinerules/shomra.md` |
|
|
455
|
+
|
|
456
|
+
It writes inside a `<!-- BEGIN SHOMRA MANAGED BLOCK -->` marker pair and **never
|
|
457
|
+
touches a line outside it**, so your own rules are safe and re-running is a
|
|
458
|
+
no-op. The block is derived, not boilerplate: sections switch on according to
|
|
459
|
+
what the repo holds (MCP configs, skills, model loads, agent-calling code), and
|
|
460
|
+
an **"Already present in this repo"** section names the findings a local gate
|
|
461
|
+
pass actually found, with paths. Commit the result and keep it honest with
|
|
462
|
+
`shomra rules --check`, which exits 1 when the block is missing or stale.
|
|
463
|
+
|
|
464
|
+
The generated block is itself an AI rules file, so `shomra rules` gates its own
|
|
465
|
+
output and refuses to write anything its own checker would block.
|
|
466
|
+
|
|
467
|
+
**`shomra mcp install`** registers Shomra *as* an MCP server with your agents, so
|
|
468
|
+
the model can call it in its own loop — most usefully `shomra_review_change`,
|
|
469
|
+
which takes proposed file content plus its intended path and returns a verdict
|
|
470
|
+
**without writing anything to disk**. A BLOCK there costs nothing; the same
|
|
471
|
+
content on disk costs a blocked tool call and a wasted turn. `shomra_rules`,
|
|
472
|
+
`shomra_check`, `shomra_explain`, `shomra_fix` and `shomra_scan_models` are
|
|
473
|
+
exposed too. `shomra mcp serve` runs the server directly (stdio JSON-RPC) if you
|
|
474
|
+
prefer to wire it by hand.
|
|
475
|
+
|
|
476
|
+
## Adopting Shomra on an existing repo
|
|
477
|
+
|
|
478
|
+
A brand-new gate on a repo with history will flag things. Three layers make
|
|
479
|
+
adoption friction-free — all of them re-grade the artifact, so a fully
|
|
480
|
+
suppressed file drops to ALLOW and never fails the build:
|
|
481
|
+
|
|
482
|
+
- **`shomra baseline`** records every current finding (line-independent
|
|
483
|
+
fingerprints) in `.shomra/baseline.json` — commit it so the whole team shares
|
|
484
|
+
it. From then on only findings introduced *after* the baseline fail; re-run it
|
|
485
|
+
to refresh after cleanups. Skip it per-run with `--no-baseline`.
|
|
486
|
+
- **`.shomraignore`** — a repo file of `path/glob` lines (skip the file) or
|
|
487
|
+
`path/glob :: title-substring` lines (skip one finding class in those files).
|
|
488
|
+
The runtime firewall honors it too, so test fixtures and detection source
|
|
489
|
+
aren't withheld. Silence a single finding inline with `// shomra-ignore` (or
|
|
490
|
+
`# shomra-ignore`) on the finding's line or the line above, or opt a whole
|
|
491
|
+
file out with `shomra-ignore-file` in its first lines (works in JSON as a
|
|
492
|
+
`"_shomra": "shomra-ignore-file"` key). `--no-suppress` ignores all of this.
|
|
493
|
+
- **`.shomra/policy.yml`** — policy-as-code, reviewed in PRs like any code:
|
|
494
|
+
|
|
495
|
+
```yaml
|
|
496
|
+
block: high # min severity that BLOCKS (critical|high|medium|low|none)
|
|
497
|
+
flag: medium # min severity that FLAGS
|
|
498
|
+
allow: # finding-title substrings to always downgrade away
|
|
499
|
+
- "IPv4 address"
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
For a local verdict the repo policy fully re-grades; when the backend
|
|
503
|
+
returned an org decision it can only make it *stricter* (worst-wins) — repo
|
|
504
|
+
config never loosens org enforcement. `--no-policy` skips it.
|
|
505
|
+
|
|
209
506
|
## Environment variables
|
|
210
507
|
|
|
211
508
|
| Var | Purpose |
|
|
@@ -214,9 +511,19 @@ closed on the server tier.
|
|
|
214
511
|
| `SHOMRA_URL` | Backend URL (overrides config) |
|
|
215
512
|
| `SHOMRA_API_TIMEOUT_MS` | Per-request backend timeout (default 30000) |
|
|
216
513
|
| `SHOMRA_AGENT` | Agent-identity handle presented to `llm-proxy` + firewall |
|
|
514
|
+
| `SHOMRA_GATE_CONCURRENCY` | Parallel backend calls in batch gate / model lookups (default 8, 1–32) |
|
|
515
|
+
| `SHOMRA_GH_TOKEN` | GitHub token for `shomra pr` (falls back to `GITHUB_TOKEN`) |
|
|
217
516
|
| `SHOMRA_GUARD_STRICT` | `1` = firewall fails closed on the server tier |
|
|
218
517
|
| `SHOMRA_GUARD_LOCAL` | `0` = disable the on-machine Tier-0 guard |
|
|
518
|
+
| `SHOMRA_GUARD_IGNORE` | Comma-separated file globs the runtime guard treats as known-safe (adds to `.shomraignore`) |
|
|
519
|
+
| `SHOMRA_GUARD_ALWAYS_ESCALATE` | `1` = send every call to the server (full telemetry, higher overhead) |
|
|
219
520
|
| `SHOMRA_GUARD_TIMEOUT_MS` | Firewall per-call server timeout (default 2000) |
|
|
220
521
|
| `SHOMRA_GUARD_BREAKER_MS` | Skip the server this long after a failure (default 30000; `0` disables) |
|
|
522
|
+
| `SHOMRA_LLM_PROXY_BASE` | Proxy base URL `install-hook` writes for Aider (default `http://127.0.0.1:4141/openai/v1`) |
|
|
523
|
+
| `SHOMRA_MODEL_GUARD` | `0` = disable the model-load screen in the PreToolUse hook |
|
|
524
|
+
| `SHOMRA_PROMPT_GUARD_OFF` | `1` = disable the prompt channel only (tool-call and tool-result guards stay on) |
|
|
525
|
+
| `SHOMRA_PLAN_GUARD_OFF` | `1` = disable the plan channel only |
|
|
526
|
+
| `SHOMRA_MODEL_CACHE` | `0` = disable the on-machine model-index verdict cache |
|
|
527
|
+
| `SHOMRA_MODEL_CACHE_TTL_MS` | Model-cache freshness window (default 7 days) |
|
|
221
528
|
|
|
222
529
|
Run `shomra help` for the full command reference.
|
package/ai-usage.mjs
CHANGED
|
@@ -60,6 +60,35 @@ const PROVIDERS = [
|
|
|
60
60
|
{ id: 'llama-cpp', label: 'llama.cpp', category: 'local-runtime', npm: ['node-llama-cpp'], py: ['llama_cpp'], call: [/\bLlama\s*\(\s*model_path\s*=/] },
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Every AI package name this catalog knows, flattened for name comparison at
|
|
65
|
+
* ACQUISITION time (`shomra add package`). A typosquat is only detectable
|
|
66
|
+
* against a list of the real names, and this catalog is already that list —
|
|
67
|
+
* maintaining a second copy is how the two drift and the check quietly stops
|
|
68
|
+
* matching the packages people actually install.
|
|
69
|
+
*
|
|
70
|
+
* `ecosystem` matters: `openai` exists on both npm and PyPI, but `crewai` is
|
|
71
|
+
* PyPI-only, so `npm i crewai` is a different and more suspicious event than
|
|
72
|
+
* `pip install crewai`.
|
|
73
|
+
*/
|
|
74
|
+
export const KNOWN_AI_PACKAGES = (() => {
|
|
75
|
+
const out = [];
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
const add = (name, ecosystem, p) => {
|
|
78
|
+
const key = `${ecosystem}:${name}`;
|
|
79
|
+
if (!name || seen.has(key)) return;
|
|
80
|
+
seen.add(key);
|
|
81
|
+
out.push({ name, ecosystem, provider: p.id, label: p.label, category: p.category });
|
|
82
|
+
};
|
|
83
|
+
for (const p of PROVIDERS) {
|
|
84
|
+
for (const n of p.npm ?? []) add(n, 'npm', p);
|
|
85
|
+
for (const n of p.npmPrefix ?? []) add(n.replace(/\/$/, ''), 'npm', p);
|
|
86
|
+
for (const n of p.py ?? []) add(n, 'pypi', p);
|
|
87
|
+
for (const n of p.pyRoot ?? []) add(n, 'pypi', p);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
})();
|
|
91
|
+
|
|
63
92
|
const MODEL_ON_LINE = /\bmodel(?:_?id|_?name)?\s*[=:]\s*['"]([A-Za-z0-9][\w.:\/-]{1,80})['"]/;
|
|
64
93
|
const MAX_CODE_LEN = 240;
|
|
65
94
|
const clipLine = (s) => (s.length > MAX_CODE_LEN ? s.slice(0, MAX_CODE_LEN) + '…' : s);
|