Skip to main content

Database schema discovery

Every scan maps your application's database schema from the ORM models and migrations in the repository. You get each table (or collection) with its columns, keys, indexes and relations, which columns hold sensitive data, and which endpoints read or write the table. Vulkro reads the code only: it never connects to a database.

The schema is the code-side twin of Salesforce objects and fields. It also feeds the data-flow map: when a flow touches a table, its data classes come from that table's classified columns.

Where you see it​

  • vulkro scan --format json: the top-level schema object (left out when no schema was found). The --format ndjson summary line carries the schema counts.
  • The local console: it serves the schema of a saved scan through its local API (GET /api/scans/<id>/schema, or ?table=<id> for one table).
  • Vulkro Cloud: each code repository has a Database area with an overview, Tables, Columns (filterable by sensitivity) and a Relationships diagram of the tables and the keys between them. Every table and column opens its own record.

There is no separate vulkro schema command: the schema is part of every scan.

What it reads​

SourceWhat Vulkro reads
Prismaschema.prisma
DrizzlepgTable / mysqlTable / sqliteTable, including column helpers imported from another file
Mongooseschemas, including NestJS @Schema classes
TypeORM, MikroORMentity classes
Sequelizedefine, Model.init, sequelize-typescript, queryInterface migrations
Knexmigrations
SQL DDLCREATE TABLE, ALTER TABLE ... ADD, CREATE [UNIQUE] INDEX, CREATE TYPE ... AS ENUM, and DROP TABLE / RENAME TO applied in migration order
Djangomodels
SQLAlchemydeclarative models, Table(...), SQLModel
Alembicmigrations
Railsdb/schema.rb and ActiveRecord migrations, plus encrypts / has_secure_password in models
JPA / Hibernate@Entity classes in Java and Kotlin
GORM, entGo structs and ent schemas
EF CoreDbSet<T> and the entity classes
Laravelmigrations, plus $casts

When a table appears both as a model and in migrations, the two views merge into one entry. In a monorepo, two packages that each own a users table stay separate: a table's id is <package dir>#<name>, or just <name> at the repository root.

What each table carries​

  • Columns: name, database name, type, nullability, primary key, unique, default, enum values, and the table and column a foreign key points to.
  • Sensitivity per column: none, pii, health, financial, secret or credential, matched per whole word against the same field-name catalogue the PII rules and the data-flow map use (never by substring).
  • Protection: hashed or encrypted, when the code shows it.
  • Relations and indexes as declared in the models and migrations.
  • Hints: the soft-delete column, the tenant column, the owner column, timestamps, and which columns are hashed or encrypted.
  • Endpoints that read or write the table (read, write or read_write), with their method, path, handler and whether they need a sign-in.

A trimmed example of the JSON:

"schema": {
"tables": [{
"id": "apps/api#users",
"name": "users", "model": "User", "kind": "table",
"file": "apps/api/src/db/schema.ts", "line": 24,
"columns": [{
"name": "passwordHash", "db_name": "password_hash", "type": "text",
"primary_key": false, "unique": false,
"sensitivity": "credential", "protection": "hashed"
}],
"hints": { "tenant_column": "orgId", "hashed_columns": ["passwordHash"] },
"endpoints": [{ "method": "POST", "path": "/signup", "access": "write", "auth": "unprotected" }]
}],
"stats": { "tables": 75, "columns": 686, "sensitive_columns": 8 }
}

Schema rules​

Four rules check the schema itself. Each one is explained in full by vulkro explain <rule>.

RuleSeverityFires when
schema-plaintext-password-column (CWE-256)HighA login password column is written by code, and no write site, assigned value or model hook hashes it.
schema-plaintext-secret-column (CWE-312)MediumA token, API key or secret column is written raw, with no hash or encryption on any write site.
schema-login-identifier-not-unique (CWE-287)MediumA table that stores a password has an email or username that no model or migration makes unique, and accounts are looked up by it.
schema-tenant-column-missing (CWE-639)MediumMost tables carry a tenant column, but one holding sensitive data has neither one nor a key to a tenant-scoped table.

Two schema facts add context to findings that already exist, instead of creating new ones. They never change a finding's severity or confidence:

  • A missing-authentication finding whose endpoint reaches a table holding credentials, secrets, financial or health data is marked schema-sensitive-table-unauthenticated.
  • A mass-assignment finding whose endpoint writes role, admin, balance, owner or tenant columns is marked schema-privileged-columns.

Limits​

  • Vulkro reads models and migrations as code. It does not connect to a database, so a column added by hand in production and never written down in the repository is not seen.
  • Sensitivity is a name-based classification. A column called notes that happens to hold health data is not flagged as health data.