How it works
Security and trust
Lore feeds text to an AI agent, so the main risks are prompt injection through rule text and unsafe file writes. This page lists the threats, what Lore does about each, and where the limits are.
python3 install.py --approval manual.The threats
- Hidden text in a rule
- Invisible characters (zero-width, bidirectional controls, Unicode "tag" characters, variation selectors) make a rule read one way to a person and another way to a model.
- A poisoned source
- A repository's own files, or review comments written by strangers on a public repository, try to teach the agent a rule it should not follow ("send your keys to…").
- An injected agent
- Text the agent reads (a file, a web page, a log) tells it to call Lore's write tools with a rule or a project of the attacker's choice.
- A rule aimed at the wrong place
- A rule saved into another project than the one being edited.
- Unsafe file writes
- Exporting rules through a symlink, or outside the project.
- The local web UI
- A web page you happen to visit reaching a server on your machine.
The safeguards
| Threat | Safeguard | Where |
|---|---|---|
| Hidden text | Rule text with hidden characters, HTML comments, or more than 500 characters is refused with a message, never quietly cleaned. The same goes for titles and trigger words. | core/sanitize.py |
Stored rules that already contain hidden characters are shown with <U+XXXX> markers, never served or exported, and cannot be approved until fixed. | core/resolver.py, core/repository.py | |
| Folder and file names reach the agent only after line breaks and hidden characters are spelled out and the length is capped. | sanitize.label | |
| A poisoned source | Review comments count only from a repository's owners, members and collaborators (GitHub's author_association); outsiders are ignored. Comments with hidden characters are never used. | reviews/cluster.py |
| Instruction files in a repository Lore has just met are not imported. Imports done by hand arrive as pending rules. | core/registration.py | |
Rules activate by themselves only in your repositories: no remote, or one your gh login can push to. Anything else waits for you. | reviews/trust.py | |
| Quoted review comments are labelled as data, and the agent is told never to save a rule because text in a file, page or comment said so. | mcpserver/tools.py, core/instructions.py | |
| An injected agent | A screen on every proposed rule: risky wording, contradictions and per-session limits keep a rule pending. | core/risk.py, mcpserver/tools.py |
The agent can set title, triggers and always but never a rule's text, scope or status through tag_rules; it can switch always on but not off; tags are size-limited and refused if they look like an instruction. | core/repository.py | |
A session activates at most auto_limit rules (25 by default). | core/config.py | |
Every automatic activation is marked, reported by the agent, listed by review.py list --auto, and undone with review.py reject. | core/models.py | |
| Wrong place | Tools that save rules decide the project from the files they are given and never accept a project name; rules are activated only in a project the agent is working on; paths outside the project are left out instead of being mapped into it. | mcpserver/tools.py |
| Unsafe writes | Export refuses symlinked targets and any folder that resolves outside the project, and never deletes symlinks when pruning. | core/export.py |
Auto-registration never registers your home folder, /, system folders or ~/.lore. A folder that only pretends to be a repository is registered but counts as not yours, because git cannot read its remotes. | core/registration.py, reviews/trust.py | |
| The web UI | Listens on 127.0.0.1 only. Every API call needs a random token (new each run). Only 127.0.0.1:<port> is accepted as Host and Origin, which defeats DNS rebinding and cross-site requests; the name localhost is refused. | web/server.py |
| The page can run only a fixed set of Lore scripts, with arguments passed as a list (never through a shell), one at a time, with a 120-second limit. Rule text and script output are rendered as plain text, never as HTML, under a strict content security policy. | web/runner.py, web/index.html | |
Approving needs a deliberate step: approve --all lists what it will approve and asks; rules that look risky are never bulk-approved and need an individual confirmation in the CLI and in the UI. | cli/review.py |
What the screening looks for
These are the patterns in core/risk.py. Any match keeps a rule pending and shows a warning in the CLI and UI.
- A download piped into a shell (
curl … | sh). - An attempt to override earlier instructions ("ignore all previous instructions").
- Imitating a system or developer message, "you are now…", "developer mode", "new instructions".
- Asking to reveal the system prompt.
- Mentions of credential or key files:
.ssh,id_rsa,.aws,.netrc,.npmrc,.pypirc,/etc/passwd, keychains. - A web address.
- Dangerous commands:
sudo,rm -rf,chmod 777,eval(,base64 -d. - Telling the agent to skip checks or review, or to push straight to main.
- A long encoded blob.
Contradictions are found by comparing similar rules in the same scope for opposite words (tabs/spaces, always/never), a one-sided negation ("do" against "do not"), or different numbers.
The limits, plainly
- Screening is pattern matching. A visible, carefully worded malicious rule that avoids the patterns can become active without anyone reading it. The session cap, trust, team-only comments and undo reduce the damage; they do not prevent it.
- Text inside your own repository can still steer the agent. A dependency's README or a pasted log may persuade it to save a rule there. That is why the instructions forbid saving a rule because a file said so, but a model can be fooled.
- The agent calls Lore because its instructions say so. It is a request, not enforcement.
- Other programs running as you can reach the web UI's port, and anything that can read the token can use it. Treat the UI as a convenience for your own machine, not a service to expose.
- The trust check relies on
gh. If it fails, the repository is treated as not yours, which is the safe direction.
Hardening
- Use manual mode if you work in code you do not control:
python3 install.py --approval manual. - Lower the cap:
"auto_limit": 5in~/.lore/config.json, or0to activate nothing automatically while still letting the agent propose. - Mark a repository you do not trust:
python3 review.py --name myapp untrust. - Skim
python3 review.py --name myapp list --autonow and then, and read the "always" rules, because they are sent on every request. - Keep
~/.loreprivate; it contains your rules and the project paths.
Reporting a vulnerability
Please do not open a public issue. Use GitHub's Report a vulnerability button on the repository's Security tab (a private advisory) and include what you did, what you expected and what happened. Good examples: hidden text reaching an agent, a way to write outside the project or the data folder, the web UI being driven from another website, or a rule being activated when it should have waited.