Local-first paper integrity checks: hallucinated citations, AI-text, plagiarism, venue guidelines.
Runs on your machine from its package; the steps are below.

Not assessed yet.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx aegis-integrity
MCP client config
{
"mcpServers": {
"aegis-academic-integrity-checker": {
"command": "uvx",
"args": [
"aegis-integrity"
]
}
}
}
Real CLI output on a synthetic sample paper (docs/demo/sample_paper.txt). Both DOIs in it are invented, and AEGIS flags them.
Open-source, offline, bias-aware academic integrity analysis. Documents are processed entirely on your own hardware and never uploaded anywhere. Citation checks may query Crossref/OpenAlex with reference metadata (titles, authors, DOIs) when online verification is enabled. Analyzes plagiarism, AI-generated content, citation hallucinations, ghostwriting, predatory references, and essay mill patterns in a single pipeline -- plus an experimental token-distribution heuristic for LLM watermark research. Plagiarism detection is corpus-based, not a live web/database crawl: AEGIS compares your document against a corpus of papers you supply (your own prior works, a downloaded reference set, etc. -- see Building a Corpus Index). With no corpus loaded, plagiarism modules correctly report "no prior works loaded" rather than silently finding nothing to flag -- that isn't a scan failure. Results are a supporting signal for human review, not a determination of misconduct.
Pick the way you want to use AEGIS. All three run on your own computer.
1. In your browser (easiest). Install once, then open the web app:
pip install "aegis-integrity[ml]"
aegis ui
Drag in a PDF, Word, LaTeX or text file, choose Full check, Private / offline,
References only or Style & formatting, and get a plain-language report:
an overall rating, what to look at, every reference with its verification
result, and a downloadable full report. A Compare two papers tab and a
My comparison library tab cover self-plagiarism and building the corpus.
On Windows, install.bat sets everything up and start-aegis.bat opens the
web app with a double-click.
2. Inside Claude Code. Install the package with the mcp extra, then add the plugin:
pip install "aegis-integrity[mcp,ml]"
/plugin marketplace add sunilgentyala/aegis-integrity
/plugin install aegis-integrity@aegis-integrity
Then ask in plain words ("check references in ~/drafts/paper.pdf", "is this
ready for an Elsevier journal?"), or use the /integrity-check and
/submission-check skills.
3. In Claude Desktop. Install the package as in option 2, download
aegis-integrity-<version>.mcpb from the
Releases page and
open it. In the extension settings, choose the Python where AEGIS is
installed. Any other MCP client can run the aegis-mcp command directly.
Not sure what works on your machine? Run aegis doctor. It lists every
capability, whether it's ready, and the one command that fixes it.
aegis doctor --warm-up downloads the AI models ahead of time so your first
full check doesn't wait on them.
aegis-mcp exposes seven tools to Claude Code, Claude Desktop and any MCP client.
| Tool | What it does | Example prompt |
|---|---|---|
aegis_analyze_paper | Full integrity pipeline on a PDF/DOCX/TEX/TXT | "Run a full integrity check on ~/drafts/paper.pdf" |
aegis_check_citations | Verify references against Crossref (finds hallucinated DOIs) | "Check the references in paper.docx" |
aegis_check_guidelines | Offline style pass vs IEEE, ACM, BCS, IET, ISACA, Elsevier | "Is this ready for an Elsevier journal?" |
aegis_compare_papers | Overlap between two papers (conference vs journal version) | "Compare v1.docx and v2.docx" |
aegis_index_add | Add a paper to your local comparison corpus | "Add Smith2025.pdf to my corpus" |
aegis_index_summary | List the corpus | "What is in my AEGIS corpus?" |
aegis_status | Which checks are ready and how to fix the rest | "Is AEGIS set up correctly?" |
AEGIS is local-first and the MCP server runs over stdio. Documents never leave your machine; only reference metadata goes to Crossref/OpenAlex.
.pdf, .docx, .tex and .txt files up to 50 MB (AEGIS_MAX_FILE_MB).AEGIS_ALLOWED_ROOTS to confine every tool to specific folders (recommended when an agent drives AEGIS)../.env.bandit, a blocking pip-audit, and CodeQL; Dependabot is on.Report vulnerabilities privately: see SECURITY.md.
If AEGIS helped you, a star on GitHub helps other researchers find it. Bug reports, sample papers that break it and pull requests are worth more: see the open issues labelled good first issue.
Every major integrity tool has blind spots. AEGIS v3.3 aims to close fourteen of them simultaneously.
Based on each vendor's public documentation and pricing pages as of August 2026. "Not public" means the capability isn't documented publicly by that vendor -- not a confirmed absence. Corrections welcome.
| Gap | AEGIS v3.3 | Turnitin | iThenticate | CopyLeaks | GPTZero | Originality.ai |
|---|---|---|---|---|---|---|
| Open-source / self-hostable | Yes | No | No | No | No | No |
| Citation hallucination detection | Yes | Not public | Not public | Not public | Not public | Not public |
| IEEE/ACM/Elsevier/IET/IETE/BCS -scoped venue-claim + duplicate-title check | Yes (metadata-only via Crossref, free) | Yes (full-text, paid Similarity Check membership) | Yes (full-text, paid Similarity Check membership) | Not public | Not public | Not public |
| LLM watermark token-distribution heuristic (experimental, keyless) | Yes | Not public | Not public | Not public | Not public | Not public |
| Citation network analysis (cartels, predatory) | Yes | Not public | Not public | Not public | Not public | Not public |
| ESL / non-native bias calibration (15 languages) | Partial (experimental) | Not public | Not public | Not public | Not public | Not public |
| Paragraph-level AI scoring | Yes | Not public | Not public | Yes | Yes | Partial |
| Semantic / paraphrase plagiarism (SBERT) | Yes | Partial | Not public | Partial | Not public | Not public |
| Stylometric ghostwriting detection (Burrows' Delta) | Yes | Not public | Not public | Not public | Not public | Not public |
| Self-plagiarism against open corpus | Yes | Not public | Paid | Not public | Not public | Not public |
| Batch classroom / essay mill detection | Yes | Not public | Not public | Not public | Not public | Not public |
| Semantic coherence AI-polish detection | Yes | Not public | Not public | Not public | Not public | Not public |
| OpenAlex journal quality integration | Yes | Not public | Not public | Not public | Not public | Not public |
| Fully explainable per-sentence reports | Yes | Not public | Not public | Partial | Partial | Not public |
| Mathematical formula checking (equation numbering, dangling references, notation) | Yes | Not public | Not public | Not public | Not public | Not public |
| Grammar & language convention checking (contractions, US/UK spelling, agreement) | Yes | Not public | Not public | Not public | Not public | Not public |
| Per-venue publisher guideline compliance (IEEE/ACM/BCS/IET/ISACA/Elsevier, checked separately) | Yes | Not public | Not public | Not public | Not public | Not public |
| Offline / air-gapped operation | Yes | No | No | No | No | No |
| REST API + CLI (free) | Yes | No | Paid | Paid | Paid | Paid |
| Pricing model | $0.00 (self-hosted) | Institutional (not public) | Institutional (not public) | Paid (self-serve) | Paid (self-serve) | Paid (self-serve) |
Resolves every DOI via the Crossref REST API and cross-checks author, year, and title. A peer-reviewed study found ChatGPT fabricated up to 55% of references depending on model version (Walters & Wilder, Scientific Reports, 2023).
Verdicts: VALID | MISMATCH | HALLUCINATED | UNRESOLVABLE | NO_DOI
AEGIS ships two distinct capabilities here, and they should not be confused:
WatermarkMode.EXPERIMENTAL):
a keyless statistic loosely modeled on the shape of the Kirchenbauer (2023) green-list z-test
and Zhao et al. (2023) entropy/rank-skew analysis. It does not have access to any real
LLM provider's watermark key, seeding scheme, or tokenizer -- the "green list" it tests
against is fabricated locally as a statistical null, not recovered from any actual
deployment. It can report STATISTICAL_ANOMALY or NO_STATISTICAL_ANOMALY, never a
definitive watermark claim, and it never affects the overall integrity risk score.WatermarkMode.VERIFIED_SCHEME, opt-in): for when the real
scheme, tokenizer, and key are actually known and supplied. AEGIS does not currently
implement a real scheme's verifier, so this mode reports UNSUPPORTED_CONFIGURATION
rather than silently falling back to the heuristic above.See Watermark Detection: Capabilities and Limitations below before relying on any watermark output.
Analyzes the full reference list for structural anomalies that single-citation DOI checking misses:
Targets the bias documented by Liang et al. (Stanford, 2023): GPT detectors misclassified more than half of non-native-authored TOEFL essays as AI-generated, one detector flagging up to 98%, while native-English essays were scored accurately. Defines per-language threshold multipliers for 15 languages, but this part is experimental: the language is detected from the text, which is "en" for nearly all English submissions, so the non-English multipliers rarely fire (see the v3.1.1 changelog note). Do not treat AEGIS as having solved ESL false positives; keep a human in the loop. Paragraph-level scoring pinpoints injected AI sections rather than giving one document-level verdict.
Signals: GPT-2 perplexity, burstiness, cross-perplexity ratio, stylometric ensemble, GPT-4/GPT-5-era lexical-tell density (v2.5 -- transition/elevation vocabulary disproportionately common in ChatGPT/GPT-4/GPT-5-family output, which GPT-2 perplexity alone under-detects against fluent frontier-model text).
Detects AI-polished text that passes perplexity filters because it was post-processed by a humanizer. Targets the "too smooth to be human" signature:
SBERT dense retrieval + CrossEncoder reranking catches concept-level paraphrase where no exact words are shared. Traditional BM25/TF-IDF-only tools miss this entirely.
Model: paraphrase-MiniLM-L6-v2 (80 MB, CPU-friendly). Index: FAISS IndexFlatIP.
Requires a corpus you load first (--corpus) -- there is no built-in web/database
crawl to compare against.
Dual index: word 3-gram (verbatim copy) and character 5-gram (obfuscation via typos or character substitution). 128 MinHash permutations; sub-linear query time over large corpora via LSH banding. Also requires a loaded corpus -- same scope note as above.
60-dimensional feature vector per segment (10 scalar + 50 function-word dimensions). Segments with Burrows' Delta > 0.40 from the document baseline are flagged as potential ghostwritten sections. Catches professional essay mills that mix human and AI writing.
Three-layer detection: character 5-gram Jaccard (verbatim), word 3-gram Jaccard (near-verbatim), SBERT cosine >= 0.88 (cross-language paraphrase recycling). Risk levels follow COPE text recycling guidelines (15% / 30% thresholds).
Detects essay mill operations and shared AI source documents by analyzing a set of submissions simultaneously:
Scopes citation and duplicate-submission checking to six publishers authors most commonly ask about: IEEE, ACM, Elsevier, IET, IETE, BCS. No outside tool (this one included) can query those publishers' actual full-text plagiarism databases -- Crossref's Similarity Check corpus that backs Turnitin/iThenticate is restricted to paying member organizations, and Scopus/IEEE Xplore's public APIs are metadata- or abstract-only even with a key. What this module does instead, entirely via free Crossref metadata:
Configurable via --target-publishers IEEE,ACM,... (CLI) or
PipelineConfig.venue_target_publishers (Python API); defaults to all six.
Checks the structural integrity of numbered equations: consecutive numbering
(catches duplicates, gaps, and out-of-order numbers), dangling in-text references
to equation numbers that don't exist (a common leftover from renumbering during
revision), orphaned equations that are numbered but never referenced, and a set of
notation conventions sourced from actual publisher style manuals -- exponential
notation (5E03 vs. 5×10³), decimal leading zeros, percentage-range formatting,
and doubly-parenthesised references. Equations are extracted from LaTeX source
(\begin{equation}/align/eqnarray/...), from Word's native OMML math XML
(python-docx doesn't expose this at all -- Paragraph.text silently skips every
equation in a .docx), or via text-pattern heuristics for PDF/TXT. Pure Python,
no ML dependency. This is a compliance/quality signal, not a misconduct signal --
it never affects overall_risk.
An offline, dependency-light grammar/usage checker: contraction detection
("don't" in formal text), US/UK spelling-consistency detection across 30+ word
pairs, subject/verb agreement heuristics ("the data is" vs. "the data are"),
common usage errors ("comprised of", "could of", "less samples" vs. "fewer samples", decade/acronym apostrophe misuse), and readability metrics. Runs fully
in-process on regex + optional spaCy POS tagging (already an AEGIS dependency) --
no Java runtime, no external grammar service, no new hard dependency. Also a
compliance/quality signal, never part of overall_risk.
Runs the math and grammar findings above against six publishing bodies' own sourced style guidance, checked SEPARATELY rather than one generic merged rule set -- so a document that's fine by ACM's conventions but violates an IEEE-specific one (or vice versa) is visible per venue instead of averaged away:
| Venue | Sourced from | Distinguishing rule this catches |
|---|---|---|
| IEEE | IEEE Editorial Style Manual for Authors (2024) | American spelling, no contractions, "(n)" equation citation, serial comma |
| ACM | ACM Formatting/Reference Guide (Chicago Manual of Style base) | American spelling, numeric-bracket citations, serial comma |
| BCS | The Computer Journal (OUP) General Instructions | No contraction/spelling rule published -- reported as inferred, not asserted |
| IET | IET Research Journals Author Guide | Bare "(1)" equation references (not "Eq. (1)"); scientific notation, not 5E03 |
| ISACA | ISACA Journal Article Submission Guidelines | Third person required ("avoid 'I' or 'you'"); 2,000-3,000 word target; endnotes, not numeric brackets |
| Elsevier | Elsevier Guide for Authors / CRediT / Highlights / Declaration of Competing Interest policy pages | Either US or UK spelling accepted, just not mixed; requires a CRediT authorship statement, a Declaration of Competing Interest, and a Data Availability Statement; Highlights capped at 3-5 bullets x 85 characters |
Results are PASS / NEEDS_REVIEW / NOT_ENOUGH_DATA -- advisory, never FAIL.
These are style conventions, not academic-integrity findings; AEGIS does not
claim to be a venue's editorial desk. Run via aegis guidelines paper.pdf --venues IEEE,ACM,BCS,IET,ISACA,ELSEVIER (fast, no ML models at all) or opt in from aegis analyze ... --guidelines all.
submission (PDF / DOCX / TEX / TXT)
|
v
DocumentParser -- PyMuPDF / python-docx / TexSoup / striprtf
|
┌────┴──────────────────────────────────────────────────────────────┐
│ AEGISPipeline v3.0 │
│ │
│ NGramDetector word 3-gram + char 5-gram MinHash LSH │
│ SemanticDetector SBERT + FAISS + CrossEncoder reranker │
│ AIContentDetector[v2.5] GPT-2 perplexity+burstiness+ESL+tell │
│ CitationIntegrityDetector Crossref REST API (DOI resolution) │
│ StylometricAnalyzer Burrows' Delta; 60-dim feature vector │
│ SelfPlagiarismDetector SBERT + n-gram vs. prior works │
│ LLMWatermarkDetector [v2.1] experimental, no real key │
│ CitationNetworkAnalyzer[v2] self-cite inflation; OpenAlex │
│ SemanticCoherenceAnalyzer[v2] discourse connectors; uniformity │
│ BatchAnalyzer [v2] classroom-level essay mill detection │
│ TargetPublisherVerifier[v2.4] IEEE/ACM/Elsevier/IET/IETE/BCS │
│ MathFormulaChecker [v3.0] equation numbering/refs/notation │
│ GrammarLanguageChecker[v3.0] contractions/spelling/agreement │
│ GuidelineComplianceChecker[v3.0] IEEE/ACM/BCS/IET/ISACA/Elsevier │
└────────────────────────────┬──────────────────────────────────────┘
|
AnalysisReport
|
HTML report
(self-contained,
offline-viewable)
Minimal (no ML models -- citation, stylometric, watermark, coherence, math, grammar, and per-venue guideline compliance; all pure-Python):
pip install -e .
Full (all 14 detectors):
pip install -e ".[ml,nlp,bib]"
python -m spacy download en_core_web_sm
Docker (recommended for production / air-gapped environments):
docker compose up --build
# Web app at http://localhost:8000/ ; API at the same address (bound to localhost only by default)
# Swagger UI at http://localhost:8000/docs
The container runs as a non-root user and its healthcheck needs no extra
tools. By default the compose file only publishes the API on the host's
loopback interface. Before exposing it beyond localhost (a different host
binding, a reverse proxy, etc.), set AEGIS_API_KEY -- otherwise every
route except /health is unauthenticated.
Windows one-click:
install.bat
# Full analysis (all 14 detectors):
aegis analyze paper.pdf --html report.html
# Fast, offline-only scan: math + grammar + per-venue guideline compliance,
# checked SEPARATELY for each requested venue -- no ML models at all:
aegis guidelines paper.pdf --venues IEEE,ACM,BCS,IET,ISACA,ELSEVIER --html guidelines.html
# Fold guideline compliance into the full analysis instead:
aegis analyze paper.pdf --guidelines all --html report.html
# Disable the experimental watermark heuristic entirely:
aegis analyze paper.pdf --watermark-mode disabled
# Against a reference corpus:
aegis analyze paper.pdf --corpus ./prior_papers/ --html report.html
# Self-plagiarism check against own prior publications:
aegis analyze paper.pdf --prior-works ./my_previous_papers/ --html report.html
# Pairwise comparison (conference vs. journal version):
aegis compare conference_draft.pdf journal_submission.pdf
# Batch / classroom analysis (essay mill detection):
aegis batch ./submissions/ --html batch_report.html
# Build a persistent index for a large corpus:
aegis index build ./corpus_dir/ --index-dir ./aegis_index/
aegis analyze paper.pdf --index-dir ./aegis_index/ --html report.html
# Start the REST API server:
aegis serve --host 0.0.0.0 --port 8000
from aegis.core.pipeline import AEGISPipeline, PipelineConfig
from aegis.detectors.watermark_detector import WatermarkMode
cfg = PipelineConfig(
citation_email="you@university.edu",
run_watermark_detector=True, # v2.0
watermark_mode=WatermarkMode.EXPERIMENTAL, # default; never affects overall_risk
run_citation_network=True, # v2.0
run_coherence_analyzer=True, # v2.0
)
pipeline = AEGISPipeline(config=cfg)
# Load a reference corpus (optional)
pipeline.load_corpus([("Smith2023", open("smith2023.txt").read())])
# Load your own prior publications (optional)
pipeline.load_prior_works([("My2022Conf", open("my2022.txt").read())])
report = pipeline.analyze("submission.pdf")
print(report.overall_risk) # LOW | MEDIUM | HIGH | CRITICAL
# v2.0 new fields
print(report.watermark_result) # WatermarkResult
print(report.citation_network_result)# CitationNetworkResult
print(report.coherence_result) # CoherenceResult
# v3.0 new fields -- compliance/quality signals, never part of overall_risk
print(report.math_result) # MathAnalysisResult
print(report.grammar_result) # GrammarAnalysisResult
print(report.guideline_results) # {"IEEE": GuidelineComplianceResult, ...}
# (empty unless PipelineConfig.guideline_venues is set)
from aegis.report.generator import ReportGenerator
gen = ReportGenerator("./reports")
gen.generate_html(report)
# Batch classroom analysis (v2.0)
from aegis.detectors.batch_analyzer import BatchAnalyzer
analyzer = BatchAnalyzer()
batch = analyzer.analyze(
doc_names=["alice.pdf", "bob.pdf", "carol.pdf"],
doc_texts=[text_alice, text_bob, text_carol],
ai_scores=[0.72, 0.69, 0.71],
)
print(batch.overall_risk) # CRITICAL if essay mill detected
print(batch.suspicious_pairs)
# Upload for analysis:
curl -X POST http://localhost:8000/analyze \
-F "file=@paper.pdf" -F "format=json"
# Add to reference corpus:
curl -X POST http://localhost:8000/corpus/add \
-F "file=@reference.pdf" -F "label=Smith2023"
# Build search index:
curl -X POST http://localhost:8000/corpus/build
# Pairwise comparison:
curl -X POST http://localhost:8000/compare \
-F "file_a=@journal.pdf" -F "file_b=@conference.pdf"
# Batch classroom analysis:
curl -X POST http://localhost:8000/batch \
-F "files=@a.pdf" -F "files=@b.pdf" -F "files=@c.pdf"
detector_status distinguishes "this detector found nothing" from "this detector
didn't run" -- a 0.0 score alone is ambiguous between a disabled detector, a
missing corpus, and a genuinely clean result. citation_summary.assessment is
"INCONCLUSIVE" (rather than a confident risk level) when fewer than 5
references were detected or verification coverage is below 80% -- a single
low-confidence reference should never read as "100% of citations are fabricated."
{
"overall_risk": "HIGH",
"scores": {
"plagiarism": 0.12,
"ai_content": 0.71,
"citation_issue_rate": 0.22,
"style_inconsistency": 0.08,
"self_recycling_pct": 4.2
},
"flags": ["AI content detected: AI_LIKELY (score=0.71)", "..."],
"network_activity": {
"document_content_transmitted": false,
"citation_check_mode": "online",
"citation_network_mode": "online",
"external_services_contacted": ["Crossref", "OpenAlex"]
},
"detector_status": {
"ngram": {"status": "completed", "reason": null},
"semantic": {"status": "disabled", "reason": null},
"ai_content": {"status": "completed", "reason": null},
"citation": {"status": "completed", "reason": null},
"self_plagiarism": {"status": "unavailable", "reason": "no prior works loaded"},
"...": "one entry per detector -- completed | disabled | unavailable | failed"
},
"ai_detection": {
"document_verdict": "AI_LIKELY",
"ai_fraction": 0.62,
"paragraph_scores": [...]
},
"citation_summary": {
"total_references": 22,
"references_with_identifier": 20,
"references_verified": 19,
"verification_coverage": 0.864,
"assessment": "ASSESSED",
"risk_level": "MEDIUM"
},
"citation_integrity": [...],
"citation_network": {
"self_citation_rate": 0.08,
"predatory_journal_count": 0,
"missing_doi_rate": 0.14,
"flags": []
},
"watermark": {
"mode": "experimental",
"status": "completed",
"verdict": "NO_STATISTICAL_ANOMALY",
"evidence_status": "experimental",
"affects_overall_risk": false,
"tokens_evaluated": 842,
"z_score": 0.41,
"confidence": 0.0,
"limitations": ["This is a keyless heuristic...", "..."]
},
"coherence": {
"verdict": "AI_POLISHED",
"ensemble_score": 0.63,
"discourse_connector_density": 5.2,
"sentence_length_cv": 0.29
},
"stylometric": {...},
"self_plagiarism": {...}
}
Copy .env.example to .env and set:
| Variable | Default | Description |
|---|---|---|
AEGIS_INDEX_DIR | ./aegis_index | Persistent FAISS + MinHash index directory |
AEGIS_REPORT_DIR | ./aegis_reports | Output directory for JSON/HTML reports |
AEGIS_DEVICE | cpu | PyTorch device (cpu, cuda, mps) |
AEGIS_CITATION_EMAIL | aegis-check@example.com | Email for Crossref polite-pool |
AEGIS_API_KEY | unset | If set, the REST API requires a matching X-API-Key header on every route except /health. Unset means no authentication -- only expose the API to a trusted network in that case. |
AEGIS_MAX_UPLOAD_MB | 50 | Maximum upload size (MB) accepted by /analyze, /compare, and /corpus/add; larger uploads get HTTP 413. |
AEGIS_MAX_CONCURRENT_JOBS | 2 | Maximum concurrent /analyze requests; additional requests get HTTP 503 instead of queuing. |
All settings can also be passed as PipelineConfig arguments in the Python API.
AEGIS's watermark analysis is experimental by default and does not affect the overall integrity risk score. Before relying on any watermark output, understand:
STATISTICAL_ANOMALY verdict means
the heuristic's own null was exceeded -- it does not mean a watermark was found. Only a
VERIFIED_SCHEME run against a real, correctly configured scheme (not currently
implemented in AEGIS) could support that claim.INSUFFICIENT_TEXT --
short excerpts are not evaluated at all.CRITICAL.pip install pytest
pytest tests/ -v
The test suite runs without network calls or ML model downloads.
1. Local-first processing. AEGIS never uploads your manuscript anywhere -- analysis runs entirely on your own machine. Citation checks may query Crossref/OpenAlex with reference metadata (titles, authors, DOIs), never the document itself, and only when online verification is enabled.
2. Reduced false-positive bias against international researchers. Liang et al. (Stanford, 2023) found GPT detectors misclassified more than half of non-native-authored TOEFL essays as AI-generated. AEGIS applies per-language calibration across 15 languages to reduce this bias.
3. Closes gaps not publicly documented elsewhere. Citation cartels, essay mills, and AI-polished text that passes perplexity filters are not publicly documented as covered by mainstream tools. AEGIS also includes an experimental, informational-only LLM watermark heuristic not commonly found in open-source alternatives -- see its capabilities and limitations.
4. Explainable, not a black box. Every flag cites the exact sentence, the source it was matched against, and the metric that triggered it, so a human reviewer can verify or dismiss it -- rather than a single opaque score. AEGIS results are a supporting signal for human review, not a determination of misconduct.
5. Built for research institutions. REST API for LMS integration, Docker for air-gapped deployment, batch mode for classroom scanning, persistent indices for journal editorial systems.
6. Free, forever. MIT license. No per-submission fees, no seat licenses, no vendor lock-in -- commercial tools require an institutional license or paid API credits. AEGIS costs compute time only.
A prebuilt container image is published to the GitHub Container Registry:
docker pull ghcr.io/sunilgentyala/aegis-integrity:latest
docker run --rm -p 8000:8000 -v aegis-data:/data ghcr.io/sunilgentyala/aegis-integrity
If you use AEGIS in your research, please cite the software:
@software{gentyala2026aegis,
author = {Gentyala, Sunil},
title = {{AEGIS}: Offline, Bias-Aware Academic Integrity Checker},
year = {2026},
version = {3.3.2},
url = {https://github.com/sunilgentyala/aegis-integrity}
}
Machine-readable metadata is in CITATION.cff; GitHub shows it under "Cite this repository".
MIT License. See LICENSE.
Sunil Gentyala Independent Research | HCL America Inc., Dallas TX, USA
| Credential | Detail |
|---|---|
| IEEE Senior Member | Institute of Electrical and Electronics Engineers |
| CISM | Certified Information Security Manager (ISACA) |
| ISACA | Information Systems Audit and Control Association |
Contact: sunil.gentyala@ieee.org GitHub: sunilgentyala LinkedIn: linkedin.com/in/sunil-gentyala Website: sunilgentyala.github.io/aegis-integrity
Packaging release for the official MCP Registry. No detection behaviour changes.
mcp is now a core dependency and a new aegis-integrity command starts the
MCP server, so uvx aegis-integrity works. aegis-mcp is unchanged.Security hardening release. No detection behaviour changes.
.pdf, .docx, .tex
and .txt files up to AEGIS_MAX_FILE_MB (default 50). New
AEGIS_ALLOWED_ROOTS confines tools to listed folders; prior_works_dir
is validated the same way. Paths are passed as absolute paths so a file
named -x.pdf cannot be read as a CLI option../.env
(only AEGIS_ENV_FILE or ~/.aegis/.env). .dockerignore excludes
.env, key files and virtualenvs.SECURITY.md, Dependabot, CodeQL, blocking pip-audit,
least-privilege workflow token, 10 regression tests.PipelineConfig.run_ieee_plagiarism turns it off.aegis ui opens a browser app at http://127.0.0.1:8765/
(also served at / by aegis serve and Docker). Drag-and-drop checking
with four modes (Full, Private/offline, References only, Style &
formatting), publisher-guideline selection, a plain-language verdict,
score tiles that say "Not run" instead of showing a misleading 0%, a
reference table sorted problems-first, a "which checks ran" panel, a
plain-English "internet use" summary, the full HTML report in a
sandboxed viewer, and JSON/HTML downloads. Also Compare two papers and
My comparison library tabs. Single self-contained page: no CDN, fonts
or trackers, so it works offline; all document text is inserted with
textContent, never as HTML.aegis doctor): lists every capability (document reading,
corpus, paraphrase and AI detection, non-native-English calibration,
Crossref, spaCy, MCP) as Ready / Limited / Not installed, with the exact
command that fixes each. --warm-up pre-downloads the models; --plain
for scripts. The same data backs GET /status, the web app's status
panel and the MCP aegis_status tool.aegis-mcp server (was a
root-level script with hardcoded C:\Gitrepos paths that only worked on
one machine); a Claude Code plugin + marketplace (claude-plugin/,
.claude-plugin/marketplace.json) with integrity-check,
submission-check and aegis-setup skills; and a Claude Desktop
extension (mcpb/). aegis_mcp.py remains as a backward-compatible
launcher for existing configs./analyze accepts offline=true (no network calls at all),
guidelines=, include_html=true, and toggles for every detector.
/health reports auth_required and no longer exposes the server's
index path to unauthenticated callers./analyze, /compare and /corpus/add ran
CPU-bound work directly on the async event loop, so one analysis froze
every other request (including /health) for minutes. That work now runs
in a thread pool.10.1016/j. + newline + ebiom...) was cut to 10.1016/j
and reported HALLUCINATED; running page headers ("... Control 115
(2026) ...") inside the reference list were read as publication years;
author lists or page ranges guessed as titles produced MISMATCH even when
the reference contained the real title; and an online-first year was
called a mismatch with the print year. Wrapped DOIs are rejoined (page
numbers and following words are not), repeated header/footer lines are
stripped from references, a title is accepted when the resolved title
appears in the reference text, and any of Crossref's print/online/issued
years is accepted. A one-year difference Crossref can't explain (it often
stores only the online-first date) is now a note on a verified reference,
not a MISMATCH; larger gaps still are. The first year of a page range
("(2022) 2049-2065") is no longer read as the publication year. On the
test paper: 12 -> 17 references verified, 1 -> 0 false HALLUCINATED,
6 -> 0 false MISMATCH..env file created by install.bat was never
read by the CLI or API; ./.env and ~/.aegis/.env are now loaded, and
aegis analyze --email honours AEGIS_CITATION_EMAIL.~/.aegis/{index,reports} so the
CLI, web app and MCP server share one library. An existing
./aegis_index / ./aegis_reports in the working directory, or
AEGIS_INDEX_DIR / AEGIS_REPORT_DIR, still take precedence.ParsedDocument.body_text
(References/Bibliography sections excluded); AI detection and citation
verification are unaffected.DocumentParser. _extract_year took the first 4-digit "19xx"/"20xx"-shaped number
anywhere in a reference's raw text as its claimed publication year. That
picks up a page/article number that happens to look like a year ("..., p. 1947, 2025." claimed 1947, not 2025) or a conference's event year
instead of its proceedings' actual publication year ("...CRITIS 2016), ... 2017." claimed 2016, not 2017). Now strips a trailing DOI, ignores
any match immediately preceded by a page/volume/issue label or a
page-range dash, and takes the last surviving candidate.ReportGenerator.generate_json,
aegis analyze --output/-o, aegis guidelines --output/-o, aegis batch --json, and the corresponding MCP tool JSON output paths). The
JSON file duplicated the HTML report's data in a form nobody read; the
self-contained HTML report is now the only report file AEGIS writes.
The REST API's GET /analyze?format=json machine-readable response is
unaffected -- that's a live API contract, not a report file./works/{doi} call and a secondary /agency call; the /agency call
hits Crossref's rate limit far more easily on reference-heavy papers,
and a 429/5xx response there fell through the same code path as a
genuine 404 (DOI unregistered), turning a transient rate limit into a
false HALLUCINATED verdict against real, resolvable DOIs. Non-404
failures on the agency check are now reported UNAVAILABLE, not
HALLUCINATED.claimed_publisher() gained a
claimed_publishers_all() counterpart and the mismatch check now passes
if the resolved publisher is any of the venues actually claimed.GPT_TELL_PHRASES
(the lexical-tell signal, 25% ensemble weight) mixed idiosyncratic AI
catchphrases ("delve into", "tapestry of", "testament to") with ordinary
formal-register connectives ("furthermore", "moreover", "in conclusion",
"robust", "leverage") that are standard vocabulary in every academic-
writing curriculum -- exactly what ESL writing courses teach as formal
transition words. Counting both tiers equally meant this signal could
disproportionately penalize careful, formal (often non-native) academic
prose, undermining the detector's own ESL bias correction
(ESL_THRESHOLD_MULTIPLIER). Split into GPT_TELL_PHRASES_STRONG (full
weight) and GPT_TELL_PHRASES_WEAK (WEAK_TELL_WEIGHT = 0.35) in
aegis.detectors.ai_detector; _gpt_tell_density now weights hits by
tier instead of counting every phrase equally.ESL_THRESHOLD_MULTIPLIER's non-English
entries are effectively unreachable in practice -- the calibration keys
off langdetect(text), which (correctly) detects the language the
text is written in, not the author's native language, so it returns
"en" for essentially every real English-language submission,
including grammatically non-native-influenced English. Verified
directly: langdetect("This paper propose a novel method for is more efficient...") still returns "en". A real fix needs a genuine L2-
English-style heuristic (e.g. article/preposition error density), which
is a new detector, not a quick tweak -- left as a scoped follow-up
rather than guessed at without a labeled corpus to validate against.aegis.guidelines.profiles.GUIDELINE_PROFILES["ELSEVIER"]), sourced from
Elsevier's own Guide for Authors / CRediT / Highlights / Declaration of
Competing Interest policy pages and cross-checked against a real
published ScienceDirect article. Unlike the other five venues, Elsevier
explicitly accepts either US or UK spelling and only flags a document
that mixes the two -- the checker's spelling rule was extended with a new
"EITHER" variant to represent this correctly instead of forcing a false
single-target verdict. Four new structural checks run only for venues
that require them (Elsevier, for now): a CRediT authorship contribution
statement, a Declaration of Competing Interest, a Data Availability
Statement, and a Highlights section capped at 3-5 bullets of 85
characters each.X-API-Key check (aegis.api.app.require_api_key) -- it compared the
header to AEGIS_API_KEY with plain !=, which short-circuits on the
first mismatching character and so takes measurably longer to reject a
key that matches more leading characters, in principle letting an
attacker recover the key one character at a time. Now uses
hmac.compare_digest.transformers (RCE via unsafe deserialization,
CVE-2024-11394/11392), torch (torch.load RCE even with
weights_only=True, CVE-2025-32434), PyMuPDF (path traversal in
embed-extract, CVE-2026-3029), requests (Session cert-verification
bypass, CVE-2024-35195), jinja2 (sandbox breakout via the |attr
filter, CVE-2024-56201/56326/CVE-2025-27516), python-multipart (DoS/
ReDoS, CVE-2024-53981/24762), and an explicit starlette floor (DoS via
unbounded multipart-field buffering and blocking event-loop rollover,
CVE-2024-47874/CVE-2025-54121) rather than relying on FastAPI's own
looser transitive floor. See requirements.txt / setup.py for the
per-package citations.aegis.detectors.math_formula).
Equation numbering (duplicates/gaps/out-of-order), dangling in-text
references to equations that don't exist, orphaned equations never
referenced, and notation conventions (exponential notation, decimal
leading zeros, percentage-range formatting) sourced from IEEE/IET style
manuals. Extracts equations from LaTeX source, from Word's native OMML
math XML (previously invisible to AEGIS entirely -- python-docx's
Paragraph.text silently skips every .docx equation), or via
text-pattern heuristics for PDF/TXT. Pure Python, no ML dependency.aegis.detectors.grammar). Contractions, US/UK spelling-consistency
detection across 30+ word pairs, subject/verb agreement heuristics,
common usage errors, and readability metrics -- fully offline via regex
aegis.guidelines). Runs the math/grammar findings against IEEE, ACM,
BCS, IET, and ISACA's own sourced style guidance separately (not one
merged rule set) via aegis guidelines <file> --venues ... or aegis analyze ... --guidelines all. Every rule cites its source; results are
advisory (PASS/NEEDS_REVIEW/NOT_ENOUGH_DATA), never FAIL --
these are style conventions, not misconduct findings, and are never
factored into overall_risk.publisher_registry.classify_publisher / claimed_publisher) used bare
substring containment on short, generic venue keywords ("iet", "iete",
"acm", "bcs"). This false-matched inside unrelated words -- a reference
mentioning a "quiet cooling system" was classified as claiming IET, one
mentioning noise being "quieted" was classified as claiming IETE, and a
"pacman-style scheduling" reference was classified as claiming ACM --
each producing a spurious VENUE_MISMATCH flag with no real venue claim
present. Matching now requires the keyword not be glued to a letter/digit
on either side (whole-word/phrase match), eliminating the collision
while leaving genuine "IEEE Trans...", "Proc. ACM...", "IET
Communications" matches unaffected.GPT_TELL_PHRASES) covering
transition/elevation vocabulary disproportionately common in
ChatGPT/GPT-4/GPT-5-family output ("delve into", "underscores", "pivotal
role", "leverage", "in conclusion", ...), reported per-paragraph as
gpt_tell_density and folded into the ensemble score at a modest, fixed
weight alongside perplexity/burstiness/stylometrics -- a hit is a
stylistic tell, not proof of AI authorship, consistent with this
project's existing no-overclaiming stance (see watermark detector).venue_verification to every report: per-venue
verified-citation counts, VENUE_MISMATCH flags when a reference claims
one of these six venues but its DOI resolves elsewhere, and a
duplicate/prior-publication search scoped per venue via Crossref
(member id for IEEE/ACM/Elsevier/IET; DOI-prefix + container-title
match for IETE/BCS, which don't hold independent Crossref membership).
Configurable via --target-publishers (CLI) / venue_target_publishers
(Python API); enabled by default, adds no new required dependency (reuses
the existing requests + Crossref integration).Follow-up audit fixes from testing against real manuscripts, plus CI/security hardening. Second consecutive minor bump for behavioral fixes, not a patch:
\n, but paragraph-level detectors (AI content, n-gram) split on
\n\n+ to find paragraph boundaries -- every DOCX submission silently
collapsed into one giant "paragraph," disabling paragraph-level AI/n-gram
detection entirely for that format. Also now extracts table cell text,
previously dropped completely.overall_risk to CRITICAL. Citation
findings now only influence risk once there's an adequate sample
(>=5 references, >=80% verification coverage); below that, the report
says "assessment": "INCONCLUSIVE" instead of a false-confidence verdict.
UNRESOLVABLE verdicts (network/parse failures) no longer count toward
citation_score either -- only confirmed HALLUCINATED/MISMATCH do.10.48550/*
prefix, etc.) previously only got a generic "not independently verified"
pass-through after the Crossref-404/agency-check fix in v2.2.0. AEGIS now
queries DataCite's own REST API for real title/author/year metadata and
runs the same comparison used for Crossref results.network_activity stating exactly which
services (if any) were contacted for that specific run.detector_status in every report: each of the 9 detectors reports
completed / disabled / unavailable / failed with a reason. A 0.0
score used to be ambiguous between "ran and found nothing," "was disabled,"
"had no corpus to compare against," and "raised an exception that got
logged and silently swallowed" -- these are now distinguishable.usedforsecurity=False (bandit B324).ruff check (pinned to the version actually run
against this codebase, not "latest" -- a ruff minor release changing
default rules would otherwise break this gate with no code change),
bandit, and a Docker build + healthcheck smoke test. pip-audit runs
for visibility but doesn't block on transitive-dependency CVEs.thinc>=8.3.12) no
longer publishes a build for it -- the full [ml,nlp,bib] install is no
longer installable on 3.9 regardless of anything in this project.
python_requires is now >=3.10.Behavioral and security fixes from an independent audit -- not documentation-only, so this is a minor version bump rather than a patch:
10.48550/* prefix) always 404
there even when valid. Now checks the DOI's registration agency first and
returns NOT_FOUND_IN_CROSSREF instead of HALLUCINATED when appropriate.
Timeouts/429/5xx now return UNAVAILABLE rather than being folded into a
verdict about the citation.CorpusIndexer reloaded document metadata
after a restart but not the actual document text, so build_indices() would
silently rebuild from an empty corpus and drop every previously-indexed
document. Document text is now persisted and reloaded correctly./corpus/add + /corpus/build wrote to the same
directory /analyze deserialized via pickle.load, an arbitrary-code-
execution risk if that directory were ever writable by an untrusted party.AEGIS_API_KEY header auth (all
routes except /health), AEGIS_MAX_UPLOAD_MB (default 50MB), and
AEGIS_MAX_CONCURRENT_JOBS (default 2, returns 503 instead of queuing
unboundedly).
API version now reports aegis.__version__ instead of a hardcoded 1.0.0."aegis_version": "2.1.0" and a stale
footer version instead of using aegis.__version__. source_breakdown keys
(document labels) were interpolated into HTML unescaped. citation_network
and coherence detector results were produced by the pipeline but never
appeared in the JSON report or HTML output -- both are now included.aegis batch CLI command and POST /batch API endpoint. Both were
documented in the README and on the GitHub Pages site already, but neither
existed -- aegis batch returned "Error: No such command 'batch'". Both now
wire in the existing (previously untested) BatchAnalyzer detector.curl, which the image never
installed, so it always failed. Replaced with a Python-based check. Added a
non-root user, removed a silently-swallowed model-download failure, stopped
installing dependencies twice (requirements.txt + editable install
overlapped almost entirely), pinned the base image to a content digest, and
added CPU/memory limits + localhost-only port binding to docker-compose.yml.overall_risk to CRITICAL; it now
never affects the risk score in experimental mode and is capped at +1 level even in a
hypothetical validated-scheme modeWatermarkMode (disabled / experimental / verified_scheme) and --watermark-mode CLI flagSTATISTICAL_ANOMALY / NO_STATISTICAL_ANOMALY / etc.;
the old definitive WATERMARKED verdict is goneaegis batch CLI command