saglitzsecure-mcp
by HalidSaglam
README.md
# SaglitzSecure
A static security scanner that needs no parser: it works over web/backend
source code and Apple platform configuration (`Info.plist`), and ties every
finding to file + line evidence and to a dated, primary-sourced knowledge
base. It is used both from the command line and as an MCP server — you can
connect it to Claude and say "run a security analysis on this project", or
run it as a gatekeeper in a CI/CD pipeline.
The distinguishing claim: **no advice is produced without a source.** If a
topic is not in the knowledge base the tool stays quiet and lists the nearest
covered topics; plausible-looking but invented advice is decidedly worse than
no advice at all.
## One package
Everything ships as [`saglitzsecure`](./packages/saglitzsecure): the CLI, the MCP
server, the rule engine, the knowledge base and the skills.
Up to 0.3.0 the rule engine was published separately as
`@saglitz/security-core`, so npm showed two entries for one product. Since 0.4.0
it is a subpath of the one package:
```js
import { allRules, runRules, toSarif } from 'saglitzsecure/core';
```
That entry point carries the finding model, the 15-rule engine and the
JSON/Markdown/SARIF writers, and nothing about the CLI or MCP — the same
boundary the separate package used to draw, now enforced by
`packages/saglitzsecure/tests/core-boundary.test.ts` and by the release
preflight rather than by npm. The rules need no parser: the engine's package
budget is `zod` alone, and today it spends none of it. Its node builtins are
allowlisted too — `node:crypto`, `node:fs/promises`, `node:path` and nothing
else — so the part of the tool that reads your source cannot spawn a process or
open a socket.
`@saglitz/security-core` is retired. If you depend on it, drop it and depend on
`saglitzsecure`, importing from `saglitzsecure/core`.
## Installation and use
### CLI
```bash
npx saglitzsecure scan .
npx saglitzsecure scan . --target apple --format sarif --fail-on high
npx saglitzsecure explain apple/ats-arbitrary-loads
npx saglitzsecure knowledge --freshness
```
Exit codes: `0` below the threshold · `1` threshold exceeded · `2` tool error
(invalid argument, unreadable directory, nothing audited). "The scanner
crashed" and "the code is clean" never give the same signal.
### MCP
```bash
claude mcp add saglitzsecure -- npx -y --package=saglitzsecure saglitzsecure-mcp
```
Because the package name (`saglitzsecure`) and the MCP server's bin name
(`saglitzsecure-mcp`) differ, `npx` is told explicitly with `--package` which
package to install; without `--package`, a bare `npx -y saglitzsecure-mcp`
looks for a DIFFERENT (non-existent) package carrying exactly that name in the
npm registry and fails. The server exposes eight tools: `scan_project`,
`explain_finding`, `security_guide`, `knowledge_freshness`, and the four that
make up a remediation loop — `remediation_plan`, `verify_fixes`,
`threat_model`, `deep_review_protocol` — see
[`packages/saglitzsecure/README.md`](./packages/saglitzsecure/README.md) for
the detail and for how the loop is used.
### Claude Code plugin (skills + MCP server in one install)
The MCP command above gives Claude the eight tools. It does **not** give it the
four skills, which are what decide *when* each tool is called, in which order,
and what may not be said about the result — Claude Code never reads inside an
npm tarball, so a skill shipped only in the package reaches nobody. The
supported route for skills is a plugin, and this repository is itself the
marketplace that serves it:
```
/plugin marketplace add HalidSaglam/saglitzsecure
/plugin install saglitzsecure@saglitzsecure
```
Both commands are run inside Claude Code. From the CLI the same thing reads
`claude plugin marketplace add HalidSaglam/saglitzsecure`. Installing the plugin brings
both halves: the four skills, namespaced as `/saglitzsecure:security-audit`,
`/saglitzsecure:secure-by-default`, `/saglitzsecure:threat-modeling` and
`/saglitzsecure:remediation`, and the MCP server above, declared inline in the
plugin manifest — so `claude mcp add` is not needed on top. If the install
summary says `Run /reload-plugins to activate.`, run it.
Installed as a plugin, the eight tools are named
`mcp__plugin_saglitzsecure_saglitzsecure__scan_project` and so on; added by hand
with `claude mcp add saglitzsecure`, they are `mcp__saglitzsecure__scan_project`.
Pick one route, or write permission rules that cover both prefixes.
To try the plugin from a local clone without a marketplace at all:
```bash
claude --plugin-dir /path/to/this/repository
```
## What this release does not do
This section exists so that you read a "clean" scan knowing what it does NOT
mean — the most dangerous failure mode of a security tool is not crashing, it
is making a place it never looked at appear examined:
- **No data-flow (taint) analysis.** Chains reaching from an input to a
dangerous sink are not evaluated; the rules work at the single-file,
single-pattern level.
- **No dependency CVE matching.** Whether a lock file (`package-lock.json`
and friends) EXISTS is checked; the known vulnerabilities of the packages
inside it are not scanned.
- **No live target testing.** The tool makes no network request; it only
reads the files in the repository as text. Security headers added by a CDN
or a reverse proxy cannot be seen — `header/*` findings reflect only the
configuration in the repository.
- **Only the file types the discovery layer recognises are scanned.**
Web/backend source (certain extensions) and Apple platform configuration
(`Info.plist`) are read; sources in other languages such as Python, Go,
Ruby, PHP and Java are NOT scanned at all in this release. The rules are
language-independent, but the discovery layer never gets those files to the
rules — **a project producing no findings does not mean "clean" when that
project is written in an unsupported language.**
Findings cover test and fixture files too: this release cannot deliberately
tell a knowingly insecure example apart from a real vulnerability. Set your CI
thresholds (`--fail-on`) accordingly, or narrow the scope with
`.saglitzsecureignore`.
Every report ends with a **coverage statement**: what was looked at, what
could not be, under which assumption. That statement is not a document
separate from the tool, it is a mandatory part of every scan.
### Excluding files from a scan
By default nothing is excluded — every file is scanned, your own test and
fixture directories included. Narrowing the scope is your choice and it is
never silent: everything you exclude appears in the coverage statement with a
count and the patterns.
- **`.saglitzsecureignore`** — a file at the scan root, in `.gitignore` syntax
(comments, `!` negation, the trailing `/` for directories, wildcards). It is
committed to the repository and shared across the team.
- **`--exclude <pattern>`** — a repeatable CLI flag for one-off use; it can
override a rule in `.saglitzsecureignore`.
For the detail and examples see the
[package README](./packages/saglitzsecure/README.md#excluding-files-from-a-scan).
## Repository layout
```
packages/
saglitzsecure/ the one published package
src/ CLI (cli.ts) + MCP server (mcp.ts) + scan orchestration
src/core/ the rule engine, finding model and report writers,
exported as `saglitzsecure/core`
knowledge/ an 11-section, dated, primary-sourced knowledge base
skills/ Claude skills (security-audit, secure-by-default, threat-modeling, remediation)
.claude-plugin/ plugin.json (the plugin, incl. the MCP server it configures on
install) + marketplace.json (this repo as a marketplace)
scripts/ core-boundary.mjs (the engine's import boundary, read by both the
release gate and a test) + preflight-release.mjs (the release gate)
.github/workflows/
ci.yml runs the build, the typecheck and the suite on Node 20 and
22 and then the release gate verbatim; audit.yml runs npm audit
daily, for advisories that land while the repository is quiet
```
## Building and publishing
```bash
npm ci
npm run build # tsc --build — compiles src/ to dist/
npm run typecheck # src AND tests; vitest strips types without checking them
npm test # vitest run
```
`typecheck` is its own script rather than part of the build because `tsc --build`
compiles `src` alone. A test is only executed, never typechecked, by vitest — so
without this step a test could construct a `Finding` missing a required field, or
read a property no longer on the type, and still pass. What it then asserted about
was a shape the product does not produce.
If you are publishing from this repository, see `RELEASING.md` — the sequence,
the gates it runs, and the npm-token traps learned the hard way are there.
## License
MIT — see [`LICENSE`](./LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues