Database schema discovery
Every scan maps your application's database schema from the ORM models and migrations in the repository. Vulkro reads code only and never connects to a database.
vulkro scan . --format json # see the top-level "schema" object
There is no separate schema command: the schema is part of every scan.
Where you see it
- JSON output: the top-level
schemaobject (absent when no schema is found). The NDJSON summary line carries the counts. - Local console: the schema of a saved scan (
GET /api/scans/<id>/schema). - Vulkro Cloud: each code repository has a Database area with Tables, Columns (filterable by sensitivity) and a Relationships diagram.
The schema also feeds the data-flow map: a flow that touches a table takes its data classes from that table's columns.
What it reads
| Stack | Sources |
|---|---|
| JavaScript / TypeScript | Prisma, Drizzle, Mongoose (including NestJS @Schema), TypeORM, MikroORM, Sequelize, Knex migrations |
| Python | Django models, SQLAlchemy and SQLModel, Alembic migrations |
| Java | JPA / Hibernate @Entity classes |
| Go | GORM structs, ent schemas |
| Other | SQL DDL migrations, Rails db/schema.rb and migrations, EF Core, Laravel migrations |
A table found in both models and migrations becomes one entry. In a monorepo, a table's id is <package dir>#<name>, so two packages with a users table stay separate.
What each table carries
- Columns: type, nullability, keys, unique, default, enum values, foreign keys.
- Sensitivity:
none,pii,health,financial,secretorcredential, matched on whole words in the column name. - Protection:
hashedorencrypted, when the code shows it. - Hints: soft-delete, tenant, owner and timestamp columns.
- Endpoints that read or write the table, with method, path and whether they need sign-in.
Schema rules
| Rule | Severity | Fires when |
|---|---|---|
schema-plaintext-password-column | High | A password column is written with no hashing on any write path. |
schema-plaintext-secret-column | Medium | A token, API key or secret column is written raw. |
schema-login-identifier-not-unique | Medium | The email or username used to look up accounts is not unique. |
schema-tenant-column-missing | Medium | Most tables have a tenant column, but a sensitive one has none and no key to a tenant-scoped table. |
Two schema facts add context to existing findings without changing severity or confidence: schema-sensitive-table-unauthenticated (a missing-auth finding whose endpoint reaches sensitive data) and schema-privileged-columns (a mass-assignment finding that writes role, admin, balance, owner or tenant columns).
Limits
A column added by hand in production and never written in the repository is not seen. Sensitivity is name-based: a column called notes that holds health data is not flagged.
See also: Secrets