Documentation
¶
Overview ¶
Package sqlsafe implements the guarded SQL execution contract for sshx: fail-closed statement classification, policy gates, backup planning, and PostgreSQL remote command assembly. It never connects anywhere itself; it only analyzes SQL text and builds commands for the SSH execution layer.
Index ¶
- Constants
- func BackupPath(dir, database, table string, kind BackupKind) string
- func CheckPolicy(cls *Classification, opts Options) error
- func NormalizeEngine(engine string) string
- func ParseBooleanOutput(output string) (bool, error)
- func ParseChangesOutput(output string) (int64, bool)
- func ParseCommandTag(output string) (rows int64, ok bool)
- func ParseExplainRows(output string) (int64, error)
- func RedactForAudit(sql string) string
- func RestoreHint(plan BackupPlan, path string) string
- func RestoreHintFor(engine string, plan BackupPlan, path string) string
- func ValidateBackupDir(dir string) error
- func ValidateContainerName(name string) error
- func ValidateDatabaseName(name string) error
- func ValidateSQLitePath(path string) error
- func ValidateTableIdent(table string) error
- func WrapSudoStdin(command string) string
- type BackupKind
- type BackupPlan
- type BlockedError
- type Class
- type Classification
- type Conn
- func (c Conn) ExecuteCommand(stmt string) RemoteCommand
- func (c Conn) ExecuteReadCommand(stmt string) RemoteCommand
- func (c Conn) ExecuteWithBackupCommand(stmt, table, where, path string, kind BackupKind) (RemoteCommand, error)
- func (c Conn) ExplainCommand(stmt string) RemoteCommand
- func (c Conn) NeedsPasswordLine() bool
- func (c Conn) RelatedEffectsCommand(table, verb string) (RemoteCommand, error)
- type CredSource
- type Credentials
- type Options
- type RemoteCommand
- type SQLExecutor
- type SQLiteConn
- func (c SQLiteConn) ExecuteCommand(stmt string) RemoteCommand
- func (c SQLiteConn) ExecuteReadCommand(stmt string) RemoteCommand
- func (c SQLiteConn) ExecuteWithBackupCommand(stmt, table, where, path string, kind BackupKind) (RemoteCommand, error)
- func (c SQLiteConn) ExplainCommand(stmt string) RemoteCommand
- func (c SQLiteConn) NeedsPasswordLine() bool
- func (c SQLiteConn) RelatedEffectsCommand(table, verb string) (RemoteCommand, error)
Constants ¶
const ( EnginePostgres = "postgres" EngineSQLite = "sqlite" )
Engine names accepted by sshx sql.
const DefaultBackupDir = ".sshx/sql-backups"
DefaultBackupDir is where remote backups land, relative to the SSH user's home directory (the working directory of a fresh SSH exec session).
const DefaultRowThreshold = 1000
DefaultRowThreshold bounds row-level backups: estimates above it switch to a full table dump.
Variables ¶
This section is empty.
Functions ¶
func BackupPath ¶
func BackupPath(dir, database, table string, kind BackupKind) string
BackupPath builds a unique remote backup file path under dir (default DefaultBackupDir, relative to the SSH user's home). The generated path only contains shell- and \copy-safe bytes.
func CheckPolicy ¶
func CheckPolicy(cls *Classification, opts Options) error
CheckPolicy enforces the execution gates on a classified statement. It returns a *BlockedError describing the first violated gate, or nil.
func NormalizeEngine ¶ added in v0.5.0
NormalizeEngine maps a user-supplied --engine value to a canonical name. An empty value is postgres, matching the historical default.
func ParseBooleanOutput ¶
ParseBooleanOutput parses the terse 0/1 result emitted by catalog preflight queries.
func ParseChangesOutput ¶ added in v0.5.0
ParseChangesOutput reads the last integer line emitted by SELECT changes().
func ParseCommandTag ¶
ParseCommandTag scans psql output (last line first) for a DML command tag and returns the affected/copied row count.
func ParseExplainRows ¶
ParseExplainRows extracts the top-level plan row estimate from EXPLAIN (FORMAT JSON) output.
func RedactForAudit ¶
RedactForAudit removes comments and literal values while retaining enough SQL structure for an audit record to remain useful. Exact-statement correlation is provided separately by a SHA-256 digest.
func RestoreHint ¶
func RestoreHint(plan BackupPlan, path string) string
RestoreHint documents how to restore from the backup artifact.
func RestoreHintFor ¶ added in v0.5.0
func RestoreHintFor(engine string, plan BackupPlan, path string) string
RestoreHintFor is the engine-aware restore hint used in JSON results.
func ValidateBackupDir ¶
ValidateBackupDir rejects control characters that could escape psql meta-command or shell line boundaries.
func ValidateContainerName ¶
ValidateContainerName bounds the docker container reference used in argv.
func ValidateDatabaseName ¶
func ValidateSQLitePath ¶ added in v0.5.0
ValidateSQLitePath accepts an absolute filesystem path and rejects URI forms, relative paths, parent segments, and characters that would break the file: URI or the sqlite3 script we generate.
func ValidateTableIdent ¶
ValidateTableIdent accepts plain or quoted, optionally schema-qualified table identifiers and rejects anything that could break out of the SQL or psql meta-command context the name is embedded into. Fail-closed.
func WrapSudoStdin ¶ added in v0.8.0
WrapSudoStdin runs command under `sudo -S` with an empty prompt. The caller must prepend the sudo password plus a newline to stdin; sudo consumes that first line and the original command sees the rest. The password never enters argv. sh -c preserves pipelines, mkdir prefixes, and PGPASSWORD readers.
Types ¶
type BackupKind ¶
type BackupKind string
BackupKind names the backup strategy chosen for one statement.
const ( // BackupNone means no pre-change backup is taken. BackupNone BackupKind = "none" // BackupRows snapshots exactly the affected rows to a CSV file before the // change using the statement's own WHERE clause. BackupRows BackupKind = "rows" // BackupTable snapshots the whole target table to CSV before the change. BackupTable BackupKind = "table" // BackupFile snapshots the whole SQLite database file with .backup. BackupFile BackupKind = "file" )
type BackupPlan ¶
type BackupPlan struct {
Kind BackupKind `json:"kind"`
Table string `json:"table,omitempty"`
Reason string `json:"reason"`
}
BackupPlan describes the pre-change backup decided for one statement.
func DecideBackup ¶
func DecideBackup(cls *Classification, estimatedRows int64, opts Options) (BackupPlan, error)
DecideBackup chooses the backup strategy for a classified statement given the EXPLAIN row estimate (pass a negative estimate when unknown, e.g. for a local dry-run plan). It is fail-closed: when a backup is required but the target table could not be extracted, it returns a *BlockedError.
func DecideSQLiteBackup ¶ added in v0.5.0
func DecideSQLiteBackup(cls *Classification, opts Options) (BackupPlan, error)
DecideSQLiteBackup chooses a SQLite backup. L1 never uses row-level CSV: bounded single-table DML snapshots the table; everything else that needs a backup snapshots the whole database file (always available).
type BlockedError ¶
type BlockedError struct {
Reason string
}
BlockedError wraps every classification/policy rejection so callers can map it to the stable "blocked" error kind.
func (*BlockedError) Error ¶
func (e *BlockedError) Error() string
type Class ¶
type Class string
Class is the coarse risk class of one SQL statement.
const ( // ClassRead statements never mutate data (SELECT, SHOW, EXPLAIN, ...). ClassRead Class = "read" // ClassDML statements mutate rows (INSERT, UPDATE, DELETE, MERGE). ClassDML Class = "dml" // ClassDDL statements mutate schema or run privileged maintenance and // always require --force. ClassDDL Class = "ddl" // ClassBlocked statements are never executed by sshx sql. ClassBlocked Class = "blocked" )
type Classification ¶
type Classification struct {
// Statement is the single trimmed statement without a trailing semicolon.
Statement string `json:"statement"`
Class Class `json:"class"`
Verb string `json:"verb"`
// Table is the primary target table (DML and destructive DDL). Empty when
// not applicable or not extractable.
Table string `json:"table,omitempty"`
// HasWhere reports a top-level WHERE clause (UPDATE/DELETE).
HasWhere bool `json:"has_where"`
// WhereClause is the original text of the top-level WHERE condition
// (excluding the WHERE keyword and any trailing RETURNING clause).
WhereClause string `json:"-"`
// ComplexSource marks UPDATE ... FROM, DELETE ... USING, MERGE, and
// CTE-wrapped DML whose affected row set cannot be reproduced by a simple
// SELECT; these always take a table-level backup.
ComplexSource bool `json:"complex_source,omitempty"`
// Destructive marks DDL that destroys data (DROP TABLE, TRUNCATE) and
// therefore requires a table backup in addition to --force.
Destructive bool `json:"destructive,omitempty"`
// MayAffectRelated marks syntax such as TRUNCATE ... CASCADE whose effects
// are known to extend beyond the primary target table.
MayAffectRelated bool `json:"may_affect_related,omitempty"`
}
Classification is the fail-closed analysis of exactly one SQL statement.
func Classify ¶
func Classify(sql string) (*Classification, error)
Classify analyzes sql and returns the classification of the single statement it contains. It is fail-closed: multiple statements, lexing errors, and unrecognized statement heads are all rejected.
func ClassifyFor ¶ added in v0.5.0
func ClassifyFor(engine, sql string) (*Classification, error)
ClassifyFor dispatches statement analysis to the engine-specific classifier.
func ClassifySQLite ¶ added in v0.5.0
func ClassifySQLite(sql string) (*Classification, error)
ClassifySQLite analyzes sql under the SQLite dialect. It shares the fail-closed lexer with Classify but uses a SQLite verb table and blocks sqlite3 dot-commands, ATTACH, load_extension, and writable PRAGMA forms.
type Conn ¶
type Conn struct {
Database string
User string // database role, not the SSH user
Host string // database host as seen from the remote machine
Port string
PasswordStdin bool
Docker string // container name/ID; empty = clients run on the host
}
Conn describes how the remote psql process reaches PostgreSQL on the target host. Credentials are never placed in argv: when PasswordStdin is set, the generated command reads PGPASSWORD from the first stdin line. When Docker is set, the database clients run inside the container via `docker exec -i` (the password crosses only via env passthrough, not argv).
Conn implements SQLExecutor.
func (Conn) ExecuteCommand ¶
func (c Conn) ExecuteCommand(stmt string) RemoteCommand
ExecuteCommand runs the statement itself. Command tags (e.g. "UPDATE 3") stay on stdout so the affected row count can be parsed afterwards.
func (Conn) ExecuteReadCommand ¶
func (c Conn) ExecuteReadCommand(stmt string) RemoteCommand
ExecuteReadCommand enforces PostgreSQL's transaction-level read-only mode so SELECT-invoked functions cannot mutate persistent database state.
func (Conn) ExecuteWithBackupCommand ¶
func (c Conn) ExecuteWithBackupCommand(stmt, table, where, path string, kind BackupKind) (RemoteCommand, error)
ExecuteWithBackupCommand locks the target against concurrent writers, snapshots its preimage, and executes the mutation in one PostgreSQL transaction. A failed mutation rolls back while the already-written CSV remains available for diagnosis or restore.
func (Conn) ExplainCommand ¶
func (c Conn) ExplainCommand(stmt string) RemoteCommand
ExplainCommand runs EXPLAIN (FORMAT JSON) for stmt and prints the bare JSON plan (-q -A -t) so the row estimate can be parsed deterministically.
func (Conn) NeedsPasswordLine ¶
NeedsPasswordLine reports whether the command expects a leading PGPASSWORD line on stdin before the payload.
func (Conn) RelatedEffectsCommand ¶
func (c Conn) RelatedEffectsCommand(table, verb string) (RemoteCommand, error)
RelatedEffectsCommand checks whether a mutation can invoke user triggers or cascading referential actions outside its direct target table.
type CredSource ¶
type CredSource struct {
// Kind is "docker" (read the container's environment via docker inspect)
// or "env-file" (read a KEY=VALUE file on the remote host).
Kind string
// Container is the docker container name or ID (Kind == "docker").
Container string
// Path is the remote env file path (Kind == "env-file").
Path string
}
CredSource describes where database credentials live on the remote host. Production databases frequently keep credentials in a container's environment or an env file next to the deployment instead of any local keyring; sshx resolves them remotely at execution time and never asks the operator to copy secrets around.
func ParseCredSource ¶
func ParseCredSource(spec string) (CredSource, error)
ParseCredSource parses a --db-cred-from specification. Supported forms: "docker:<container>" and "env-file:<remote path>".
func (CredSource) ExtractionCommand ¶
func (s CredSource) ExtractionCommand() (string, error)
ExtractionCommand returns the remote command that prints the credential environment, one KEY=VALUE per line. The command itself contains no secret; its stdout does and must never be logged or audited.
func (CredSource) String ¶
func (s CredSource) String() string
String returns the canonical spec form (also used as the cache identity).
type Credentials ¶
type Credentials struct {
User string `json:"user,omitempty"`
Password string `json:"password"`
Database string `json:"database,omitempty"`
Host string `json:"host,omitempty"`
Port string `json:"port,omitempty"`
}
Credentials is the outcome of resolving a CredSource. Every field is optional except Password; explicit CLI flags always win over these values.
func ParseCredOutput ¶
func ParseCredOutput(output string) (Credentials, error)
ParseCredOutput extracts credentials from KEY=VALUE lines (docker inspect env output or an env file). Discrete keys win over a connection URL. The error never embeds raw output, which may contain unrelated secrets.
type Options ¶
type Options struct {
// Force allows DDL statements (still never the hard-blocked ones).
Force bool
// AllowFullTable allows UPDATE/DELETE without a top-level WHERE clause.
AllowFullTable bool
// NoBackup disables pre-change backups. It requires Force.
NoBackup bool
// RowThreshold is the estimated-row boundary between a row-level CSV
// backup and a full table dump. Zero uses DefaultRowThreshold.
RowThreshold int64
}
Options carries the caller-controlled safety levers for one execution.
type RemoteCommand ¶
RemoteCommand is one fully assembled remote invocation: the shell command for the SSH exec channel plus the stdin payload (SQL text or psql meta-commands). The caller prepends the password line when needed.
type SQLExecutor ¶ added in v0.5.0
type SQLExecutor interface {
NeedsPasswordLine() bool
ExplainCommand(stmt string) RemoteCommand
ExecuteCommand(stmt string) RemoteCommand
ExecuteReadCommand(stmt string) RemoteCommand
ExecuteWithBackupCommand(stmt, table, where, path string, kind BackupKind) (RemoteCommand, error)
RelatedEffectsCommand(table, verb string) (RemoteCommand, error)
}
SQLExecutor assembles remote client commands for one engine. sshx embeds no database driver: every method returns a command for the SSH exec channel.
type SQLiteConn ¶ added in v0.5.0
type SQLiteConn struct {
Path string
}
SQLiteConn describes how the remote sqlite3 process opens one database file. There is no password, role, or network endpoint: identity is the file path.
func (SQLiteConn) ExecuteCommand ¶ added in v0.5.0
func (c SQLiteConn) ExecuteCommand(stmt string) RemoteCommand
ExecuteCommand runs the statement and prints sqlite3's changes() count so DML affected-row reporting stays machine-readable.
func (SQLiteConn) ExecuteReadCommand ¶ added in v0.5.0
func (c SQLiteConn) ExecuteReadCommand(stmt string) RemoteCommand
ExecuteReadCommand opens the file URI in read-only mode so writes fail.
func (SQLiteConn) ExecuteWithBackupCommand ¶ added in v0.5.0
func (c SQLiteConn) ExecuteWithBackupCommand(stmt, table, where, path string, kind BackupKind) (RemoteCommand, error)
ExecuteWithBackupCommand locks the database with BEGIN IMMEDIATE, snapshots either the target table (CSV) or the whole file (.backup), then mutates.
func (SQLiteConn) ExplainCommand ¶ added in v0.5.0
func (c SQLiteConn) ExplainCommand(stmt string) RemoteCommand
ExplainCommand runs EXPLAIN QUERY PLAN. SQLite has no JSON plan-row estimate; callers store the text and do not parse a row count.
func (SQLiteConn) NeedsPasswordLine ¶ added in v0.5.0
func (c SQLiteConn) NeedsPasswordLine() bool
NeedsPasswordLine is always false: SQLite file access uses OS permissions.
func (SQLiteConn) RelatedEffectsCommand ¶ added in v0.5.0
func (c SQLiteConn) RelatedEffectsCommand(table, verb string) (RemoteCommand, error)
RelatedEffectsCommand reports whether a mutation may fire triggers or cascading foreign keys outside the copied table CSV.