Documentation
¶
Overview ¶
Package surface is the core framework for surface mapping (PRD section 10): the language-agnostic home for surface categories and, in Fase 1, the SurfaceQuery format (codefit's own declarative format for enumerating auditable structural surface, distinct from the Semgrep-format rules used for deterministic findings — PRD section 17).
Status: SKELETON. Today it declares the surface categories. The aggregation framework and the SurfaceQuery type and runner are implemented in Fase 1. The per-language enumeration currently lives in each provider's AnalyzeSurface (ADR 0001, provisional); core/surface will host the shared machinery once a second language exists to factor it against.
Index ¶
- func Run(queries []Query, root syntax.Node, file string) []findings.SurfaceItem
- func StableID(file string, line int, category string) string
- func StampIDs(items []findings.SurfaceItem)
- func Summary(category string, enumerated int, in Integration) string
- type Category
- type Confirmation
- type Integration
- type Query
- type Verdict
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Run ¶
Run executes each query over the file and returns the union of their items with stable ids stamped. The framework is agnostic: it only orchestrates.
func StableID ¶
StableID derives a short, stable id for a surface item from (file, line, category). It is a PURE function of those three components: the enumeration stamps it on each item, and the confirmation step recomputes it to validate that a verdict refers to a real item — this is what keeps codefit stateless (it stores nothing between enumeration and confirmation, it recomputes).
func StampIDs ¶
func StampIDs(items []findings.SurfaceItem)
StampIDs fills the stable id of any item that does not yet have one. It is idempotent, so emitting boundaries (the sensor, the MCP handler) can call it without caring whether a provider already stamped.
Types ¶
type Category ¶
type Category string
Category is a class of auditable surface the agent reasons about. These match the Category field of findings.SurfaceItem.
const ( CategoryIDOR Category = "idor" // endpoints that access a resource by ID CategoryAuthz Category = "authz" // protectable handlers CategoryOverfetch Category = "overfetch" // serializations of domain objects CategoryNPlus1 Category = "nplus1" // a query call sits inside a loop (dimension db) // DB-structure surface categories (schema-only rules, dimension "db"). One // category per rule so a baseline fingerprint is distinct per rule, the same // reason a finding's fingerprint carries its rule ID. These do not participate // in the scan-all endpoint bucketing (idor/authz/overfetch) — the DB sensor is // standalone. CategoryDBFKNoIndex Category = "db-fk-no-index" // DB-001: FK with no covering index CategoryDBDupIndex Category = "db-duplicate-index" // DB-011: exact duplicate index CategoryDBMultivalued Category = "db-multivalued-column" // DB-002: multivalued (array) column // CategoryDBViewSensitiveColumn (DB-020, Phase 2.2): a VIEW's top-level // SELECT column list exposes a column/alias whose name matches a // sensitive token (the same name-heuristic vocabulary CategoryDBSensitive // Unencrypted/DB-053 already uses). Body-derived (View.Body), unlike the // structural DB-001/011/002 categories above — grouped with the name- // heuristic block below it would also be reasonable; it lives here // because it is schema-STRUCTURE-adjacent (a view definition), not a // column-type heuristic. CategoryDBViewSensitiveColumn Category = "db-view-sensitive-column" // CategoryDBPrefixRedundantIndex (DB-011's prefix-redundant half, Unit E, // Phase 2.2): an index [a] whose columns are a strict leading prefix of // another index-like column list [a,b] on the same table (a real // composite index, or the primary key treated as an implicit index, same // as DB-001/rules.go's indexLike). Distinct from CategoryDBDupIndex // (DB-011's EXACT-duplicate case, same length): the two are mutually // exclusive by construction (a strict prefix is, by definition, strictly // SHORTER than what subsumes it). Closes the gap declared at // coverage.go:34 ("Prefix-redundant indexes ... are NOT yet detected"). CategoryDBPrefixRedundantIndex Category = "db-prefix-redundant-index" // CategoryDBRoutineNoExceptionHandling (DB-031, 0.2.3 routine-body rules): // a stored procedure/function whose captured body contains NO exception- // handling construct for its dialect (T-SQL BEGIN TRY, MySQL DECLARE ... // HANDLER, PL/pgSQL EXCEPTION WHEN). This states an ABSENCE as a structural // fact, never an affirmation of a defect: whether the missing handler // matters — and whether a PRESENT handler is adequate (an empty CATCH is // "present" here yet may still be a bug) — is the agent's judgment, not // codefit's. Body-derived (Procedure.Body), read through the same bounded, // string/comment-aware token scanner discipline as DB-020, never a general // SQL parser. Gated on Body.Complete (ADR 0004/0025): a body the parser // could not prove whole is never evaluated, so an absence over unread text // is never falsely affirmed. CategoryDBRoutineNoExceptionHandling Category = "db-routine-no-exception-handling" // CategoryDBTriggerCrossTableCascade (DB-040, 0.2.3 routine-body rules): a // TRIGGER whose body performs DML (INSERT/UPDATE/DELETE) against a table // OTHER than the trigger's OWN table — a cross-table cascade. It states the // structural FACT ("this trigger writes to other table(s): X, Y") plus // whether a comment documents the write near it (documented_by_comment), // never an affirmation of a defect: whether the cascade is intentional and // correct is the AGENT's judgment. Body source is per-dialect: MySQL/T-SQL // triggers carry an inline Body scanned directly; a PostgreSQL trigger has // NO inline body (ADR 0026) — its logic lives in the executed function, // resolved via Schema.ExecutedProcedure(t), whose own Body is scanned // instead; when that resolution yields nothing (a built-in like // tsvector_update_trigger), the rule abstains for that trigger. Read through // the same bounded, string/comment-aware token scanner discipline as DB-020/ // DB-031, never a general SQL parser. Gated on the scanned body's // Body.Complete (ADR 0004/0025). CategoryDBTriggerCrossTableCascade Category = "db-trigger-cross-table-cascade" // CategoryDBTriggerExternalCall (DB-041, 0.2.3 routine-body rules): a TRIGGER // whose body invokes an EXTERNAL-EFFECTING call — one that reaches OUTSIDE the // database (a shell exec, OLE automation, email, cross-database/remote query, // async notification, or an untrusted-language routine). It states the // structural FACT ("this trigger makes external call(s): X"), never an // affirmation that the call is unsafe — the exploitability is the AGENT's // judgment. STRICT vocabulary per dialect: a plain EXECUTE/CALL of an internal // stored procedure is NOT external and does not fire (the noise a loose rule // would create). Body source is per-dialect, resolving a PostgreSQL trigger to // its executed function (ADR 0026) exactly as DB-040 does. Bounded, string/ // comment-aware token scanner; gated on Body.Complete (ADR 0004/0025). CategoryDBTriggerExternalCall Category = "db-trigger-external-call" // CategoryDBDynamicSQLInRoutine (DB-030, 0.2.3 routine-body rules): a stored // procedure or function whose body CONSTRUCTS and runs SQL at runtime from a // string (T-SQL sp_executesql / EXEC(@var), PL/pgSQL EXECUTE over a built // string / format() / quote_*, MySQL PREPARE ... FROM). It states the FACT // that dynamic SQL is built here; whether it is injectable is the AGENT's // judgment (codefit maps the surface, it does not do taint analysis). A static // EXEC/CALL of a named internal procedure is NOT dynamic SQL and does not fire. // Bounded string/comment-aware scanner; gated on Body.Complete (ADR 0004/0025). CategoryDBDynamicSQLInRoutine Category = "db-dynamic-sql-in-routine" // Name-heuristic DB categories (slice 2b) — pure surface (ADR 0017). CategoryDBFKTextType Category = "db-fk-text-type" // DB-051: FK typed as text vs a numeric/uuid key CategoryDBNoTimestamps Category = "db-no-timestamps" // DB-052: missing audit timestamps CategoryDBSensitiveUnencrypted Category = "db-sensitive-unencrypted" // DB-053: sensitive-looking column stored in the clear CategoryDBRepeatingGroups Category = "db-repeating-groups" // DB-003: repeating groups (1NF smell) )
type Confirmation ¶
type Confirmation struct {
SurfaceID string `json:"surface_id"`
Category string `json:"category"`
File string `json:"file"`
Line int `json:"line"`
Verdict Verdict `json:"verdict"`
Reasoning string `json:"reasoning"`
Confidence float64 `json:"confidence"`
Severity findings.Severity `json:"severity,omitempty"`
}
Confirmation is what the agent returns after reasoning over a surface item. It carries everything codefit needs to integrate the verdict statelessly: the surface id (revalidated by recomputation) plus the anchor (file/line/category) and the agent's judgment. Severity is optional — the agent, which understands what resource is exposed, judges it; codefit falls back to a class default.
type Integration ¶
type Integration struct {
Findings []findings.Finding
Dismissed []Confirmation
Uncertain []Confirmation
Invalid []Confirmation
}
Integration is the result of folding the agent's verdicts into the report. Each verdict lands in exactly one bucket: Findings (vulnerable, probabilistic), Dismissed (not_vulnerable, kept for traceability, no score impact), Uncertain (flagged for human review — developer autonomy), Invalid (the surface id did not recompute, so the verdict refers to nothing codefit can anchor).
func Integrate ¶
func Integrate(confs []Confirmation) Integration
Integrate folds agent verdicts into the report. It is a pure function of the confirmations passed in — codefit keeps no session. A vulnerable verdict becomes a probabilistic finding (confidence < 1.0 always) anchored to the surface item; the id is revalidated by recomputing StableID.
type Query ¶
type Query interface {
// Enumerate returns the surface items found in the file rooted at root. file
// is the project-relative path (a query may restrict itself by path, e.g.
// Next App Router handlers live in app/**/route.ts).
Enumerate(root syntax.Node, file string) []findings.SurfaceItem
}
Query enumerates the auditable structural surface of one category over a parsed file. Each category+provider implements it; the framework orchestrates queries without knowing any category (it never knows about IDOR or Next).