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
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ Errors go to stderr and return a non-zero exit code. With `--json`, errors also
## Commands

```text
brainpod detect [<path>] [--rules <rules.toml>]
brainpod detect [<path>] [--rules <rules.toml>] [--with-env [--with-source]]
brainpod describe [<command>...]
brainpod describe resource [<kind>]
brainpod login
Expand Down Expand Up @@ -271,11 +271,19 @@ Inspect a directory without credentials, network access, subprocesses, or execut
```sh
brainpod detect .
brainpod detect ./services --json
brainpod detect . --with-env --json
brainpod detect . --with-env --with-source --json
brainpod detect . --rules ./detection.toml --json
```

The result contains sorted project paths, manifest names, languages and version declarations, priority-ranked detections with evidence and `uses`/`provides` framework roles, workspace members, diagnostics, and scan statistics. Optional `runtimes` records Deno declarations separately from JavaScript/TypeScript versions; `workspaces` records each manager’s ecosystem, source, and members, while `workspaceMembers` remains their union. Framework versions are attached to dependency evidence. JavaScript versions describe Node.js; TypeScript versions describe the compiler. C# versions distinguish `language` (LangVersion), `target` (TFM), `runtime`, and `sdk`; Java distinguishes compiler `source`/`target`/`release`, toolchains, build-JDK metadata, and pins. Requirements and pins retain their source and kind: these are declarations, not a claim about installed versions. Missing versions remain unknown.

Add `--with-env` for name-only environment declarations from dotenv files (including examples/templates), Compose, Dockerfile `ENV` and `ARG` (build-scoped), conventional Kubernetes manifests, GitHub Actions workflow/job/step `env` maps, and deployment manifests of other hosting platforms: Heroku/Dokku `app.json`, Scalingo `scalingo.json`, `heroku.yml`, Render `render.yaml`, Fly.io `fly.toml`, DigitalOcean `.do/app.yaml`/`.do/deploy.template.yaml`, Vercel `vercel.json`/`vercel.toml`, Netlify `netlify.toml`, Google App Engine `app.yaml` and AWS Copilot `copilot/*/manifest.yml`. Platform records add optional `required`, `secret`, `generated`, `hasDefault` (a literal value exists; never the value) and `scope` (`runtime`, `build`, `build-and-runtime`) fields only where the manifest states them, and database/service/secret wiring as a `reference`.

`--with-env` reads only manifest, config and deployment files, never application source. Add `--with-source` (requires `--with-env`) to also **read application source files** (JavaScript/TypeScript, Vue/Svelte/Astro, Python, Ruby, Go, Rust, PHP, Java/Kotlin, C#, Elixir; existing directory exclusions and the 1 MiB limit apply) and search them literally for a cataloged list of platform-injected names (for example `VERCEL_URL`, `RENDER_GIT_COMMIT`, `HEROKU_SLUG_COMMIT`, `CF_PAGES_URL`) and framework prefixes (`NEXT_PUBLIC_*`, `VITE_*`, `REACT_APP_*`, `NUXT_PUBLIC_*`, `EXPO_PUBLIC_*`, …). This is not a general read-site scanner: arbitrary `process.env` reads are not inferred. Each `(project, name)` becomes one `code-reference` record with `origin`, `provider`, `scope` and up to 20 file/line occurrences, never code text. On the pinned corpus `--with-source` reads about 40 times as many files as `--with-env` alone: 277,283 files / 1.77 GB versus 6,822 files / 15.1 MB.

JSON adds `environment`; text shows names, source/format/location, project ownership, unresolved references and the optional fields. Values are never exposed, loaded or evaluated. References are not followed, and declarations are not a claim about usage or installed/effective values. Without the flag, output and read counts are unchanged. See [supported syntax, per-platform mapping, code-reference patterns, privacy/scoping limits and offline validation](tests/detect-env.md).

Built-in coverage includes Laravel, Symfony, CakePHP, CodeIgniter, Composer; Go modules/workspaces and Gin/Echo/Fiber; Cargo workspaces, axum, Rocket, Actix; Bundler, Rails, Sinatra, Hanami; Python requirements, PEP 621, Poetry, Pipenv, uv workspaces, Django, FastAPI, Flask, Starlette, Celery; npm/pnpm and Deno workspaces and catalogs, Fresh, Oak, Hono, Next.js, Nuxt, Astro, SvelteKit, Remix, Angular, Express, React, Vue, Vite; C#/.NET projects and solutions, ASP.NET Core/classic, Aspire, FastEndpoints; Java/JVM Maven and Gradle projects, Spring/Spring Boot, Quarkus, Micronaut; and standalone source files in all nine languages. Rust workspace dependency aliases and inherited `rust-version` are understood. Ancestor runtime pin/toolchain files are included with provenance. Poetry names, literal gemspec metadata, and Rails component dependencies are recognized. pnpm catalog references retain the original declaration and add the locally resolved constraint with its YAML source; unresolved references produce diagnostics.

Deno supports `deno.json`/`deno.jsonc` (comments and trailing commas), Deno-only, hybrid, and npm-only workspace members, globs/exclusions, and shared imports with member alias overrides. Registry targets (`npm:`, `jsr:`) and supported `deno.land/x`/`esm.sh` URLs supply dependency evidence without downloading modules. Local relative import maps are bounded and cannot escape the scan root or follow symlinks. `.deno-version`, `.tool-versions` and npm `engines.deno` retain explicit runtime version provenance; a JSR package’s `version` is never treated as a Deno runtime version. Peer and development dependency constraints remain separate evidence. pnpm-only membership is not inferred as Deno membership. Scoped import-map overrides and non-relative/remote import-map files are diagnosed, not fetched or guessed; source imports, lockfiles, Docker/CI version hints, and tasks are not evaluated.
Expand Down
14 changes: 13 additions & 1 deletion src/detect/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub(super) enum Resolver {
#[serde(default, deny_unknown_fields)]
pub(super) struct CatalogSpec {
manifests: Vec<Manifest>,
environment: Vec<super::environment::Spec>,
code_references: super::environment::references::Spec,
scan: Scan,
families: BTreeMap<String, String>,
inheritance: Vec<Inheritance>,
Expand Down Expand Up @@ -249,6 +251,8 @@ struct WorkspaceField {

pub(super) struct Catalog {
spec: CatalogSpec,
pub environment: super::environment::Catalog,
pub references: super::environment::references::Catalog,
exact: HashMap<String, Vec<usize>>,
wildcard: Vec<(FilePattern, usize)>,
extensions: HashMap<String, Vec<(FilePattern, usize)>>,
Expand All @@ -260,6 +264,8 @@ pub(super) struct Catalog {
impl CatalogSpec {
pub fn extend(&mut self, extra: Self) {
self.manifests.extend(extra.manifests);
self.environment.extend(extra.environment);
self.code_references.extend(extra.code_references);
self.scan.ignore.extend(extra.scan.ignore);
self.scan.prune.extend(extra.scan.prune);
self.families.extend(extra.families);
Expand All @@ -271,8 +277,14 @@ impl CatalogSpec {
}

impl Catalog {
pub fn new(spec: CatalogSpec) -> Result<Self> {
pub fn new(mut spec: CatalogSpec) -> Result<Self> {
let environment = super::environment::Catalog::new(&spec.environment)?;
let references = super::environment::references::Catalog::new(std::mem::take(
&mut spec.code_references,
))?;
let mut catalog = Self {
environment,
references,
spec,
exact: HashMap::new(),
wildcard: Vec::new(),
Expand Down
Loading
Loading