Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ __pycache__/
.pytest_cache/
.ruff_cache/
.venv/
uv.lock
.DS_Store
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ALFRED

![Version](https://img.shields.io/badge/version-0.5.0-008080) ![Python](https://img.shields.io/badge/python-3.10%2B-008080) ![Node](https://img.shields.io/badge/node-18%2B-008080) ![License](https://img.shields.io/badge/license-MIT-008080)
![Version](https://img.shields.io/badge/version-0.5.0-008080) ![Python](https://img.shields.io/badge/python-3.10%2B-008080) ![Node](https://img.shields.io/badge/node-20%2B-008080) ![License](https://img.shields.io/badge/license-MIT-008080)

**A**gentic **L**atex **f**or **R**esearch, **E**diting, and **D**rafting.

Expand Down Expand Up @@ -47,7 +47,7 @@ Reports are written to `capabilities/reports/`. Review records are saved to `rev
### Prerequisites

- **Python 3.10+**
- **Node.js 18+**
- **Node.js 20+**
- **TeX Live** (basic install works for most templates — includes `latexmk` and `biber`)
- **[uv](https://docs.astral.sh/uv/)** (used by the launcher for venv setup)
- Optional: `latexdiff` for track-changes PDFs (`brew install latexdiff` on macOS)
Expand Down Expand Up @@ -148,6 +148,35 @@ files can depend on packages outside a basic TeX installation.
Upstream provenance and refresh links are recorded in
[`templates/SOURCES.md`](templates/SOURCES.md).

## Security

ALFRED runs an LLM agent with access to your local filesystem and shell.
These mitigations are in place, but they are **defense-in-depth, not a
sandbox**:

- **Command denylist** — network-exfiltration binaries (`curl`, `wget`,
`nc`, `ssh`, etc.) and env-exposure commands (`env`, `printenv`) are
blocked, including when wrapped in `bash -c`. The agent can still run
arbitrary commands through other interpreters.
- **Environment scrubbing** — credential-shaped environment variables
(`*_API_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, and AWS keys) are
stripped from the agent's arbitrary `command` tool. Fixed workflow
tools (build, sync, cite) inherit the full environment because they
run hardcoded trusted scripts.
- **SSRF protection** — `web_fetch` validates URLs against
internal/private address ranges and manually follows redirects with
per-hop validation.
- **Build isolation** — `latexmk` runs with `-norc` to prevent
`.latexmkrc` Perl code execution from untrusted paper directories.

**Recommendations:**

- Do not run ALFRED with highly privileged credentials or on sensitive
infrastructure against untrusted paper directories.
- Use dedicated, low-privilege API keys where possible.
- Review `paper.yaml` and any `.latexmkrc` files before opening papers
from untrusted sources.

## Development

Requires [Task](https://taskfile.dev) for running dev commands.
Expand All @@ -169,7 +198,7 @@ There are several great projects bringing AI to academic writing — each with a
| **Approach** | Agent-first — you talk, it writes | Editor with inline AI | Desktop editor with AI agents | Chrome extension + web app | Chrome extension for Overleaf |
| **Autonomy** | Full — writes sections, builds, searches, cites | Inline edits, agent-assisted citations | AI-assisted editing | Copilot suggestions | Multi-agent patches |
| **LLM support** | Any (Claude, GPT, Gemini, Mistral, local, OpenRouter) | GPT only | Any (Claude, GPT, Gemini, DeepSeek, local) | Locked to their API | Configurable |
| **Runs locally** | Yes — nothing leaves your machine | No (cloud) | Yes | No (cloud) | Cloud (self-host option) |
| **Runs locally** | Yes — server is local; content is sent to configured LLM provider and optional search APIs | No (cloud) | Yes | No (cloud) | Cloud (self-host option) |
| **Research workflows** | Lit review, claim verification, peer review, source analysis | Lit search, citations | No | Citation search (ArXiv) | Literature retrieval |
| **PDF preview** | Live auto-reload | Yes | Yes | Via Overleaf | Via Overleaf |
| **Conference templates** | 12 built-in formats | Yes | No | Yes | Via Overleaf |
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "alfred"
version = "0.4.0"
version = "0.5.0"
Comment thread
mkultraWasHere marked this conversation as resolved.
description = "Agentic LaTeX for Research, Editing, and Drafting"
license = "MIT"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fi
echo "=== Building document ==="
cd "$PROJECT_ROOT"
mkdir -p build
latexmk -pdf -interaction=nonstopmode -outdir=build main.tex
latexmk -norc -pdf -interaction=nonstopmode -outdir=build main.tex

EXIT_CODE=$?
if [[ $EXIT_CODE -eq 0 ]]; then
Expand Down
3 changes: 2 additions & 1 deletion scripts/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def diff(project_root: str, rev: str = "HEAD") -> int:
f.write(result.stdout)

# Step 4: Copy supporting files needed for compilation
for item in ["bibliography.bib", ".latexmkrc", "styles", "figures", "data"]:
for item in ["bibliography.bib", "styles", "figures", "data"]:
src = os.path.join(project_root, item)
dst = os.path.join(diff_dir, item)
if os.path.isdir(src):
Expand All @@ -170,6 +170,7 @@ def diff(project_root: str, rev: str = "HEAD") -> int:
result = _run(
[
"latexmk",
"-norc",
"-pdf",
"-interaction=nonstopmode",
"-outdir=build",
Expand Down
2 changes: 1 addition & 1 deletion scripts/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ echo ""

# Check sync status (paper.yaml sections vs main.tex \input lines)
echo "--- Checking sync status ---"
YAML_SECTIONS=$(python3 -c "import yaml; m=yaml.safe_load(open('$PROJECT_ROOT/paper.yaml')); print('\n'.join(s['slug'] for s in m.get('sections',[])))" 2>/dev/null)
YAML_SECTIONS=$(python3 -c "import sys,yaml; m=yaml.safe_load(open(sys.argv[1])); print('\n'.join(s['slug'] for s in m.get('sections',[])))" "$PROJECT_ROOT/paper.yaml" 2>/dev/null)
TEX_SECTIONS=$(sed -n 's/.*\\input{section\/\([^}]*\)}/\1/p' "$PROJECT_ROOT/main.tex" 2>/dev/null)
if [[ "$YAML_SECTIONS" != "$TEX_SECTIONS" ]]; then
echo " WARNING: paper.yaml and main.tex sections are out of sync"
Expand Down
Loading
Loading