Repository navigation
Security model
Mestre Yoda reads repositories it did not write. That is the whole threat model: a hostile or merely broken repository should not be able to make the tool execute code, escape its root, exhaust memory, or leak what it found.
It never executes project code. No file from the analyzed repository is imported, evaluated, spawned, or passed to a shell. Every file is read as text and matched against patterns.
It never runs a package manager. Dependencies are read by parsing
package.json as data. There is no npm install, no lockfile resolution, no
registry call.
It never writes to the analyzed project. There is no write API in 0.1. The inspector opens files read-only and the planner returns tasks.
It never makes a network call. The catalog ships inside the package. Schema
$id values look like URLs but are never fetched.
The inspector is the only component that touches foreign files, so its limits are part of the contract rather than an implementation detail.
| Limit | Value | What it prevents |
|---|---|---|
| Files walked | 5000 | An unbounded walk on a monorepo or a generated tree |
| Bytes per file | 512 KiB | Reading a vendored blob or a generated bundle |
| Bytes per corpus | 8 MiB | Unpredictable peak memory |
| Symbolic links | refused | Escaping the requested root |
| Binary content | dropped | Matching patterns against nonsense |
Exceeding the file limit is an error, not a truncation:
Error: Project inventory exceeds 5000 files.
Failing loudly matters more than finishing. A truncated walk would produce a report that looks complete and quietly missed half the repository.
The size limits behave differently: a file over 512 KiB is skipped silently, and
once the corpus reaches 8 MiB no further files are read. Compare
inventory.inspectedTextFiles against inventory.files to see when that
happened.
Every symlink is recorded in inventory.skippedSymlinks and never followed.
The root itself is checked with lstat and rejected if it is a link or not a
directory:
Error: Project root must be a real directory.
This closes the obvious escape. A repository containing evil -> /etc gets that
path listed in the inventory and nothing read through it.
The practical cost is that a repository whose source lives behind a symlink
inspects as nearly empty. That is why skippedSymlinks is in the output rather
than dropped: the surprising result comes with its explanation attached.
.git, .idea, .next, .vscode, bin, coverage, dist, node_modules,
and obj.
Build output and installed dependencies are not evidence about source, and
node_modules alone would blow the file limit on most repositories.
Worth knowing: bin is skipped for .NET build output, so source that lives
under a directory named bin is invisible to detection.
A profile manifest references its own policy, recipe, and gate files by relative
path. Each reference is checked against
^(policies|recipes|gates)/[a-z0-9-]+\.json$ before the file is opened:
Error: Profile <id> has an unsafe reference.
The check is a pattern match rather than resolve-and-compare, so there is no
window in which a crafted path is opened and then judged. ../../../etc/passwd
fails on the pattern.
Success payloads are about the project, so some of it is project data by definition. Two fields deserve attention before a report leaves the machine that produced it.
inspect.result.root is the resolved absolute path. On a CI runner that is a
build path, and on a laptop it contains a username.
inventory.skippedSymlinks contains relative paths from the repository, which
is repository content.
Nothing else in a success payload carries file contents. Signals are booleans, and rules carry text from the catalog rather than from the project.
Local paths, stack traces, and project content. The failure envelope is deliberately thin:
{
"status": "failure",
"error": {
"code": "INVALID_REQUEST",
"summary": "Unknown Golden Path profile: missing-profile."
}
}A test in tests/cli.test.mjs asserts the summary contains no stack frame, so a
regression that starts leaking internals fails the build.
Three facts, each asserted by a test that reads the source as text:
-
protocol.mjscontainsmutations: false; -
evaluator.mjscontainsreadOnly: true; -
evaluator.mjscontainseffects: Object.freeze([]).
tests/boundary.test.mjs fails the build if any of them disappears. That is a
crude check, and it is crude on purpose: it survives refactors that behavioral
tests would let through, because it asserts the guarantee still exists in the
code rather than that one code path happened to produce the right value.
The same file asserts the package declares no dependencies of any kind, which is the supply-chain half of the story. A tool that reads other people's repositories with zero transitive dependencies has a very short list of things that can go wrong in its own tree.
plan.effects is always empty and plan.readOnly is always true. Task text is
written for humans.
The failure mode to avoid is a consumer parsing "Add versioned migrations and validate them before deployment." into a filesystem operation. Task strings are
prose from the catalog, they change with a patch release, and nothing validates
them as commands. A future protocol version will declare typed operations
explicitly, and until then, treating the text as instructions for a person is
the only supported reading.
Report privately to the maintainers rather than in a public issue. Do not include credentials, customer code, private repository contents, or production data. See SECURITY.md.
Repository · Releases · Issues · MIT license
Normative contracts live in
docs/. Where
this wiki disagrees with the repository, the repository is correct.
Getting started
Reference
The catalog
Using it
Project