Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
161 commits
Select commit Hold shift + click to select a range
c45473d
fix: render SQL Server table functions as inline TVFs and list wareho…
kvlonge Nov 7, 2026
bc86f06
feat(lint): warn when SQL lint is unavailable for a dialect
kvlonge Nov 7, 2026
15b863e
fix(sqlserver): quote schema names in EXEC, end MERGE with a semicolo…
kvlonge Nov 7, 2026
57b0c20
refactor(analysis): read B101 spellings from a recorded table instead…
kvlonge Nov 7, 2026
7edf70d
feat(lint): keep L007 out of every exit decision and show it in compi…
kvlonge Nov 7, 2026
2fb4547
fix(lint): keep L007 out of rule exceptions and the plan rules gate
kvlonge Nov 7, 2026
3572408
docs(rules): name plan and build, not format, in the L007 exit note
kvlonge Nov 7, 2026
c03cec2
Merge remote-tracking branch 'origin/main' into wip/b101-function-cat…
kvlonge Nov 7, 2026
bc89e18
Merge branch 'main' into feat/lint-unavailable-warning
kvlonge Nov 7, 2026
ff7e1b9
Merge branch 'main' into fix/clickhouse-recall-sqlserver-tvf
kvlonge Nov 7, 2026
47c7a6b
test(orchestration): count the store statements a wide catalog listin…
kvlonge Nov 7, 2026
c00a9da
refactor(analysis): share diagnostic location and the catalog's optio…
kvlonge Nov 7, 2026
5aea666
Merge branch 'main' into feat/lint-unavailable-warning
kvlonge Nov 7, 2026
b620a2f
Merge branch 'main' into fix/clickhouse-recall-sqlserver-tvf
kvlonge Nov 7, 2026
355219b
Merge branch 'main' into test/catalog-statement-count
kvlonge Nov 7, 2026
2436dc7
Merge branch 'main' into refactor/b101-recorded-spellings
kvlonge Nov 7, 2026
3cd6dff
feat(contracts): prove dynamic pivots from SQLQuack parse trees
kvlonge Nov 8, 2026
db2387f
fix(contracts): refuse pivot SQL nested deeper than 512 levels before…
kvlonge Nov 8, 2026
630b2d8
wip: read rule facts with SQLQuack
kvlonge Nov 8, 2026
d9e86d0
wip: locate cast types and Snowflake date parts without a generator
kvlonge Nov 8, 2026
27e4cb4
Merge remote-tracking branch 'origin/feat/lint-unavailable-warning' i…
kvlonge Nov 8, 2026
a2909a3
wip: report built-in rules unavailable for unparsed dialects
kvlonge Nov 8, 2026
7a85582
wip: e2e for built-in rules on unparsed dialects
kvlonge Nov 8, 2026
4fad955
chore(deps): bump sqlquack to a9dde6a0
kvlonge Nov 8, 2026
adc7984
wip: docs and tests for built-in rules on unparsed dialects
kvlonge Nov 8, 2026
088bd97
wip: format tests
kvlonge Nov 8, 2026
8b2a9d3
fix(rules): refuse rule facts for statements deeper than 512 levels
kvlonge Nov 8, 2026
d9fa004
fix(rules): anchor nested comparisons and Snowflake LAST_DAY date parts
kvlonge Nov 8, 2026
449de8d
feat(scenario): map scenario column types with SQLQuack's type parsers
kvlonge Nov 8, 2026
7655ef3
fix(scenario): map SQL Server date, time, uuid and money types locally
kvlonge Nov 8, 2026
5ad2d42
Merge branch 'main' into feat/sqlquack-type-and-local-sql
kvlonge Nov 8, 2026
4637fb4
chore(fensu): allow the cast-target walk next to the other JSON tree …
kvlonge Nov 8, 2026
0352b44
refactor(rules): name the date-part and unavailable-rule loops
kvlonge Nov 8, 2026
e9221bc
Merge remote-tracking branch 'origin/main' into feat/rule-facts-sqlquack
kvlonge Nov 8, 2026
5f253c1
docs(analysis): keep the warehouse type binding's doc comment to one …
kvlonge Nov 8, 2026
28a28c5
Merge remote-tracking branch 'origin/main' into feat/sqlquack-type-an…
kvlonge Nov 8, 2026
2d146f5
feat(types): normalize adapter types without Polyglot
kvlonge Nov 8, 2026
d2e1fb8
fix(ui): stop a burst of control API requests on every live refresh
kvlonge Nov 8, 2026
9c09e54
fix(types): read only shipped dialects and ClickHouse nested parameters
kvlonge Nov 8, 2026
e0638cd
refactor(rules): read the shared-parser dialect from one lookup
kvlonge Nov 8, 2026
cb84e52
Merge main into the sqlquack bump
kvlonge Nov 8, 2026
fea889c
chore(deps): bump sqlquack to 19c51fa2
kvlonge Nov 8, 2026
183b210
build(deps): pin SQLQuack before its Snowflake grammar checks
kvlonge Nov 8, 2026
36342ee
Merge branch 'feat/sqlquack-type-and-local-sql' into feat/sqlquack-ty…
kvlonge Nov 8, 2026
7d1ef9a
fix(rules): read relation markers as tables in custom-rule syntax trees
kvlonge Nov 8, 2026
fd5faf5
Merge branch 'main' into fix/ui-refresh-burst
kvlonge Nov 8, 2026
de3cdc8
Merge fix/sql-tree-placeholder-relations (#1370)
kvlonge Nov 8, 2026
2d2eaa5
Merge feat/rule-facts-sqlquack (#1361)
kvlonge Nov 8, 2026
2a8cfa2
Merge feat/sqlquack-type-normalization (#1366)
kvlonge Nov 8, 2026
cff088f
Merge chore/sqlquack-rev-bump (#1368)
kvlonge Nov 8, 2026
a1e6b0f
build(deps): keep Windows windows-sys edges at main's resolution
kvlonge Nov 8, 2026
9d01621
docs(types): keep the ClickHouse type parameter doc comment to one line
kvlonge Nov 8, 2026
1e4aa20
feat(ui): wire the lineage and changes pages to the live control API
kvlonge Nov 8, 2026
0793704
feat(server): page the security audit newest first with actor names
kvlonge Nov 8, 2026
eb7c880
feat(ui): wire the audit log to the security audit read
kvlonge Nov 8, 2026
96ad1ba
feat(server): list people and role holdings for administrators
kvlonge Nov 8, 2026
5fa1089
feat(ui): wire the people page to the people and role reads
kvlonge Nov 8, 2026
118d6d3
fix(server): count no active sessions for a disabled person
kvlonge Nov 8, 2026
19ba7be
Merge the SQLQuack parser train into the pivot proof
kvlonge Nov 8, 2026
d27158a
fix(contracts): read relation markers as relations in dynamic pivot p…
kvlonge Nov 8, 2026
9b3282c
refactor(analysis): share the statement depth guard between pivot pro…
kvlonge Nov 8, 2026
87b8bd9
fix(contracts): keep non-dependency markers out of pivot relations
kvlonge Nov 8, 2026
42579ef
fix(types): spell ClickHouse Nullable and BINARY as the engine does
kvlonge Nov 8, 2026
6a32d49
Merge main into the SQLQuack parser train
kvlonge Nov 8, 2026
c9fa2ad
Merge main into test/catalog-statement-count
kvlonge Nov 8, 2026
1b1d0b3
Merge branch 'main' into fix/ui-refresh-burst
kvlonge Nov 8, 2026
7c64f85
Merge the SQLQuack parser train into the pivot proof
kvlonge Nov 8, 2026
5494e24
feat(server): let people read a target's location health
kvlonge Nov 8, 2026
fd39408
feat(ui): show live location health on the Locations page
kvlonge Nov 8, 2026
8dc27f1
fix(server): limit a target's health read to its own observations
kvlonge Nov 8, 2026
1f04a86
feat(server): let people read live platform problems
kvlonge Nov 8, 2026
8929fae
feat(ui): show live platform problems in the alert banner
kvlonge Nov 8, 2026
58de3d6
docs(ui): keep the locations doc comment on listLocations
kvlonge Nov 8, 2026
35f7a3a
feat(server): let role administrators read a target's role definitions
kvlonge Nov 8, 2026
a49d346
feat(ui): show role definitions on the Roles pages
kvlonge Nov 8, 2026
d220b21
feat(ui): show warehouse sync status in the top bar
kvlonge Nov 8, 2026
b12327f
fix(ui): stop rereading warehouse sync after a refused read
kvlonge Nov 8, 2026
03b49ca
fix(rules): wrap Snowflake table functions in TABLE( ) before parsing
kvlonge Nov 8, 2026
a84c6ea
Merge origin/main into the platform and admin slices
kvlonge Nov 8, 2026
689deba
fix(rules): read Snowflake table function markers inside TABLE( ) in …
kvlonge Nov 8, 2026
5974f71
feat(ui): show catalog node detail from the control API and finish li…
kvlonge Nov 8, 2026
a1d030b
Merge remote-tracking branch 'origin/main' into fix/ui-refresh-burst
kvlonge Nov 8, 2026
f729c4c
fix(ui): ask for refused node SQL once until reload
kvlonge Nov 8, 2026
5b2b272
fix(rules): keep columns after a wrapped table function in rule trees
kvlonge Nov 8, 2026
001d78c
fix(ui): wrap SQL previews and drop the kind prefix from SQL file labels
kvlonge Nov 8, 2026
2994b2a
feat: publish column lineage and show it on catalog node pages
kvlonge Nov 8, 2026
2c59909
fix(ui): show full names and transform kinds in column lineage
kvlonge Nov 8, 2026
2ff5a9e
refactor: move column lineage to its own change
kvlonge Nov 8, 2026
c8e186b
Merge branch 'main' into test/catalog-statement-count
kvlonge Nov 8, 2026
2cd8757
feat: show per-node run history and selecting jobs on catalog node pages
kvlonge Nov 8, 2026
b5f41d4
feat(orchestration): gzip location snapshot publishes
kvlonge Nov 8, 2026
e9a8dde
feat: show run events, statements, SQL and output to people
kvlonge Nov 8, 2026
bff5f8c
feat: require run.prioritise for runs above priority 0
kvlonge Nov 8, 2026
d10bd28
fix(orchestration): report the server's refusal of a snapshot too lar…
kvlonge Nov 8, 2026
19a4005
fix(orchestration): bound decompressed publish bodies at 320 MiB
kvlonge Nov 8, 2026
32abf51
fix: keep run cancellation independent of run.prioritise
kvlonge Nov 8, 2026
58abe6f
test: wait for every trigger to reach the latest due time after sched…
kvlonge Nov 8, 2026
6abe6a0
test: count ticks scheduled after their own evaluation as off schedule
kvlonge Nov 8, 2026
e8df6e4
ci: run the full Verify matrix only on pull requests labelled full-ve…
kvlonge Nov 8, 2026
deed388
Merge feat/gzip-location-publish into the combined delivery
kvlonge Nov 8, 2026
0ba6b92
Merge fix/scheduler-fault-catch-up into the combined delivery
kvlonge Nov 8, 2026
b072d05
Merge test/catalog-statement-count into the combined delivery
kvlonge Nov 8, 2026
6f4ec9c
Merge fix/clickhouse-recall-sqlserver-tvf into the combined delivery
kvlonge Nov 8, 2026
6639e64
Merge chore/sqlquack-parser-train into the combined delivery
kvlonge Nov 8, 2026
7fc70b1
Merge refactor/b101-recorded-spellings into the combined delivery
kvlonge Nov 8, 2026
bfd5cfc
Merge feat/pivot-sqlquack into the combined delivery
kvlonge Nov 8, 2026
30914da
Merge feat/run-prioritise into the combined delivery
kvlonge Nov 8, 2026
ffe352d
Merge feat/ui-warehouse-sync into the combined delivery
kvlonge Nov 8, 2026
24aba79
Merge fix/ui-refresh-burst into the combined delivery
kvlonge Nov 8, 2026
ef60acc
feat: run ad-hoc sqb commands from the web UI
kvlonge Nov 8, 2026
05cd1ab
ci: keep the Verify aggregate on always()
kvlonge Nov 8, 2026
096dbdb
Merge feat/adhoc-commands into the combined delivery
kvlonge Nov 8, 2026
5f82f19
refactor(orchestration): page security audit facts beside the audit w…
kvlonge Nov 8, 2026
aeed1ce
test(ci): prove a fast-core or label-only Verify run cannot satisfy t…
kvlonge Nov 8, 2026
6c4c7f1
feat: publish column lineage and show it on catalog node pages
kvlonge Nov 8, 2026
ac8c917
Merge origin/main into the combined delivery
kvlonge Nov 8, 2026
304af88
Merge feat/ui-run-reads into the combined delivery
kvlonge Nov 8, 2026
1826b8c
fix(lineage): publish snapshot column lineage from native edge records
kvlonge Nov 8, 2026
f757fa2
fix(ui): find an ad-hoc run launched while a runs listing is in flight
kvlonge Nov 8, 2026
13fc786
Merge wip/column-lineage-native into the combined delivery
kvlonge Nov 8, 2026
0aaf702
feat: run test, seed, audit and freshness ad-hoc commands on workers
kvlonge Nov 8, 2026
2d16d24
fix: run every ad-hoc verb from the worker's verified project
kvlonge Nov 9, 2026
5f37d98
fix(ui): show the approved logos and favicon from the website
kvlonge Nov 9, 2026
3828720
Merge remote-tracking branch 'origin/wip/ui-branding' into wip/combin…
kvlonge Nov 9, 2026
e57245a
ci: run the UI checks when the website logos or favicon change
kvlonge Nov 9, 2026
fb73e67
Merge remote-tracking branch 'origin/main' into wip/combined-next-local
kvlonge Nov 9, 2026
334c6b3
Merge remote-tracking branch 'origin/feat/adhoc-verbs' into wip/combi…
kvlonge Nov 9, 2026
333c627
style: move the checks-only outcome note into its doc comment
kvlonge Nov 9, 2026
7d80e87
feat(ui): show the Overview from live runs, checks, models and deploys
kvlonge Nov 9, 2026
0bd8914
Merge remote-tracking branch 'origin/wip/ui-overview' into wip/combin…
kvlonge Nov 9, 2026
69e9de5
feat(ui): tell a partial viewer why the Overview is empty
kvlonge Nov 9, 2026
5d846c1
fix(ui): show Overview errors and drop a stale partial-view message
kvlonge Nov 9, 2026
fafaae1
Merge remote-tracking branch 'origin/wip/ui-overview' into wip/combin…
kvlonge Nov 9, 2026
fbcf13c
feat(ui): show renames, old names and clean-ups on the Maintenance page
kvlonge Nov 9, 2026
728345e
Merge remote-tracking branch 'origin/wip/ui-maintenance' into wip/com…
kvlonge Nov 9, 2026
620cbe6
fix: regenerate the TypeScript control client for ad-hoc run commands
kvlonge Nov 9, 2026
28ba06f
ci: check the generated control client in make check-ci
kvlonge Nov 9, 2026
787a0e6
feat(ui): give and remove roles and disable people from the admin pages
kvlonge Nov 9, 2026
3d5eb48
Merge remote-tracking branch 'origin/wip/ui-admin-actions' into wip/c…
kvlonge Nov 9, 2026
0a69cf7
feat(ui): test a location's warehouse connection from the Locations page
kvlonge Nov 9, 2026
3ed5eed
Merge commit '0a69cf70c44839c47cccd834c443c4d946042d79' into wip/comb…
kvlonge Nov 9, 2026
6739902
Merge remote-tracking branch 'origin/main' into wip/combined-next-local
kvlonge Nov 9, 2026
1d34a8a
feat(ui): location detail and scheduler status from the server's heal…
kvlonge Nov 9, 2026
9b1b6c8
fix(server): count copied grants in the maintenance read instead of r…
kvlonge Nov 9, 2026
d331a85
Merge commit '1d34a8a95699290cd173b568ff066f32715f5320' into wip/comb…
kvlonge Nov 9, 2026
809b672
Merge commit '9b1b6c8facec573083f3af41062a986f4ebb711a' into wip/comb…
kvlonge Nov 9, 2026
9534b3c
feat(ui): backfill a job over a cursor window from its launch form
kvlonge Nov 9, 2026
8c7ca57
fix(ui): show a plain-text server error only when it is plain text
kvlonge Nov 9, 2026
b7c2dfd
Merge commit '8c7ca570e3e666f5c19dcc6d1f14ab86a593e7f2' into wip/comb…
kvlonge Nov 9, 2026
0c33ae1
fix(server): answer framework 5xx rejections with the generic JSON error
kvlonge Nov 9, 2026
0f34182
feat(runs): show a failed node's error text and file on the run page
kvlonge Nov 9, 2026
fcc1d6d
fix(worker): tell people to upgrade the server when it refuses a newe…
kvlonge Nov 9, 2026
289510d
refactor: keep node error text redaction in core and within structure…
kvlonge Nov 9, 2026
6ada30a
feat(jobs): show a job's settings, group order and critical nodes, an…
kvlonge Nov 9, 2026
5d8dc93
fix(redaction): redact glued keys, SQL passwords, cut digit passwords…
kvlonge Nov 9, 2026
bdacec2
fix(redaction): keep only digit ports in cut URIs and cover IDENTIFIE…
kvlonge Nov 9, 2026
2ecd551
Merge branch 'wip/job-detail'
kvlonge Nov 9, 2026
3191f27
Merge remote-tracking branch 'origin/wip/redaction-gaps' into feat/ui…
kvlonge Nov 9, 2026
f912c79
fix(runs): record rewrite_history on runs and withhold deferral paths…
kvlonge Nov 9, 2026
f9108d1
Merge remote-tracking branch 'origin/wip/job-detail' into feat/ui-nod…
kvlonge Nov 9, 2026
803689c
refactor(server): read a job's published snapshot through the stored …
kvlonge Nov 9, 2026
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
163 changes: 163 additions & 0 deletions clients/typescript/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/jobs/{job_key}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["readPersonJob"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/maintenance": {
parameters: {
query?: never;
Expand Down Expand Up @@ -578,6 +594,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/nodes/{node_key}/error": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["readPersonRunNodeError"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/output": {
parameters: {
query?: never;
Expand Down Expand Up @@ -1479,6 +1511,31 @@ export interface components {
failed: components["schemas"]["Table"][];
failure?: string | null;
};
/** @description One job or group as declared, for a person who may list the target; free-form setting values are withheld. */
JobDetailRecord: {
concurrency_groups: string[];
/** @description Selected nodes whose failure stops a group. */
critical_nodes: string[];
description: string;
job_key: string;
kind: components["schemas"]["RunKind"];
/** @description The groups this job belongs to. */
member_of: string[];
/** @description A group's member jobs in run order; empty for a job. */
members: components["schemas"]["JobMember"][];
selection: string[];
/** @description Command settings; `vars` and a `defer_*` value that is not a target name show as `[REDACTED]`, and text values pass through secret redaction. */
settings: {
[key: string]: unknown;
};
/** Format: uuid */
snapshot_id: string;
};
JobMember: {
job_key: string;
/** @description Members this one waits for, from lineage and from the group's `after`. */
waits_for: string[];
};
/** @description A job or group of the current published snapshot. Settings are left out of person reads. */
JobRecord: {
concurrency_groups: string[];
Expand All @@ -1492,6 +1549,8 @@ export interface components {
};
/** @description A trusted operator names a published job; execution inputs are never supplied by the caller. */
JobRunRequest: {
/** @description Rebuild every selected model from scratch; it rewrites history, as a window does. */
full_refresh?: boolean;
job_key: string;
/** Format: uuid */
location_id: string;
Expand Down Expand Up @@ -2011,6 +2070,8 @@ export interface components {
node_key: string;
} | {
error_code?: string | null;
/** @description Redacted and bounded on the way in, whoever sends it; absent from older workers. */
error_message?: string | null;
/** @enum {string} */
kind: "node_finished";
node_key: string;
Expand Down Expand Up @@ -2114,6 +2175,15 @@ export interface components {
};
/** @enum {string} */
RunKind: "job" | "group" | "adhoc";
/** @description A node's outcome error in one run: redacted, bounded text and the project file of the node, never its SQL. */
RunNodeFailure: {
error_code?: string | null;
/** @description Null when the worker reported none or the run's history has been pruned. */
message?: string | null;
node_key: string;
/** @description Relative to the project root, with `/` separators; null when the run's snapshot is gone. */
path?: string | null;
};
RunNodeRecord: {
/** Format: date-time */
ended_at?: string | null;
Expand Down Expand Up @@ -3687,6 +3757,52 @@ export interface operations {
};
};
};
readPersonJob: {
parameters: {
query?: never;
header?: never;
path: {
location_id: string;
target: string;
job_key: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["JobDetailRecord"];
};
};
/** @description Session rejected */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Needs view on every current node of the target */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorBody"];
};
};
/** @description No published job by that name */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
readPersonMaintenance: {
parameters: {
query?: {
Expand Down Expand Up @@ -4071,6 +4187,53 @@ export interface operations {
};
};
};
readPersonRunNodeError: {
parameters: {
query?: never;
header?: never;
path: {
location_id: string;
target: string;
run_id: string;
node_key: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["RunNodeFailure"];
};
};
/** @description Session rejected */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Needs view.evidence on the node */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorBody"];
};
};
/** @description Node not found in this run */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
readPersonRunOutput: {
parameters: {
query?: never;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ pub(crate) fn control_admin(namespace: &Namespace) -> Result<ControlAdmin, Contr
reason,
priority,
window,
full_refresh: false,
}));
}
if action.as_deref() == Some("cancel-run") {
Expand Down
28 changes: 24 additions & 4 deletions crates/sqlbuild-core/src/redaction/_helpers/patterns.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! The four secret patterns, compiled once per process.
//! The secret patterns, compiled once per process.

use std::sync::LazyLock;

use regex::{Regex, RegexBuilder};
use regex::{Captures, Regex, RegexBuilder};

use crate::redaction::constants::{
AUTHORIZATION_PATTERN, BEARER_PATTERN, PATTERN_SIZE_LIMIT, SECRET_VALUE_PATTERN,
URI_CREDENTIAL_PATTERN,
AUTHORIZATION_PATTERN, BARE_USERINFO_PATTERN, BEARER_PATTERN, DDL_PASSWORD_PATTERN,
PATTERN_SIZE_LIMIT, SECRET_VALUE_PATTERN, URI_CREDENTIAL_PATTERN,
};

pub(crate) static AUTHORIZATION: LazyLock<Result<Regex, regex::Error>> =
Expand All @@ -15,8 +15,12 @@ pub(crate) static BEARER: LazyLock<Result<Regex, regex::Error>> =
LazyLock::new(|| compiled(BEARER_PATTERN));
pub(crate) static SECRET_VALUE: LazyLock<Result<Regex, regex::Error>> =
LazyLock::new(|| compiled(SECRET_VALUE_PATTERN));
pub(crate) static DDL_PASSWORD: LazyLock<Result<Regex, regex::Error>> =
LazyLock::new(|| compiled(DDL_PASSWORD_PATTERN));
pub(crate) static URI_CREDENTIAL: LazyLock<Result<Regex, regex::Error>> =
LazyLock::new(|| compiled(URI_CREDENTIAL_PATTERN));
static BARE_USERINFO: LazyLock<Result<Regex, regex::Error>> =
LazyLock::new(|| compiled(BARE_USERINFO_PATTERN));

fn compiled(pattern: &str) -> Result<Regex, regex::Error> {
RegexBuilder::new(pattern)
Expand All @@ -35,3 +39,19 @@ pub(crate) fn substituted(
Err(_) => text.to_owned(),
}
}

/// A port is all digits and the authority ends after it (`/`, `?`, `#`); anything else may be a password.
pub(crate) fn bare_userinfo_redacted(text: &str) -> String {
match BARE_USERINFO.as_ref() {
Ok(regex) => regex
.replace_all(text, |found: &Captures<'_>| {
if !found[3].is_empty() && found[2].bytes().all(|byte| byte.is_ascii_digit()) {
found[0].to_owned()
} else {
format!("{}[REDACTED]{}", &found[1], &found[3])
}
})
.into_owned(),
Err(_) => text.to_owned(),
}
}
25 changes: 20 additions & 5 deletions crates/sqlbuild-core/src/redaction/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,36 @@ pub(crate) const SECRET_FIELD_PARTS: [&str; 11] = [
"secret",
"token",
];
/// A key may be glued to a prefix, as in `db_password` or `PGPASSWORD`; `pwd` stands alone so `OLDPWD` and `cwd` keep their paths.
pub(crate) const SECRET_VALUE_PATTERN: &str = concat!(
r"(?i)\b(api[-_]?key|access[-_]?key|private[-_]?key|authorization|",
r"(?i)\b([a-z0-9_.-]*?(?:api[-_]?key|access[-_]?key|private[-_]?key|authorization|",
r"client[-_]?secret|refresh[-_]?token|",
r"password|passwd|pwd|secret|token|dsn|connection_string)",
r"password|passwd|secret|token|dsn|connection_string)|pwd)",
r#"(\s*[=:]\s*)("[^"\r\n]{0,512}"|'[^'\r\n]{0,512}'|[^\s,;]{1,512})"#,
);
pub(crate) const AUTHORIZATION_PATTERN: &str = concat!(
r"(?i)\b(authorization)(\s*[=:]\s*)(?:basic|bearer)\s+",
r#"("[^"\r\n]{1,512}"|'[^'\r\n]{1,512}'|[^\s,;]{1,512})"#,
);
pub(crate) const BEARER_PATTERN: &str = r"(?i)\bbearer\s+[a-z0-9._~+/=-]{1,512}";
pub(crate) const URI_CREDENTIAL_PATTERN: &str =
r"(?i)\b([a-z][a-z0-9+.-]*://[^\s:/@]+:)([^\s@]+)(@)";
/// The password runs to the last `@` before the host, so an unencoded `@` inside it is covered.
pub(crate) const URI_CREDENTIAL_PATTERN: &str = concat!(
r"(?i)\b([a-z][a-z0-9+.-]*://[^\s:/@]+:)([^\s]+?)",
r#"(@[^\s@/?#,;'"]*)([\s/?#,;'"]|$)"#,
);
/// SQL that sets a password: `PASSWORD '…'` (quoted only, so prose such as "password authentication" stays) and `IDENTIFIED [WITH plugin] BY …`.
pub(crate) const DDL_PASSWORD_PATTERN: &str = concat!(
r#"(?i)\b(password)(\s+)("[^"\r\n]{0,512}"|'[^'\r\n]{0,512}')|"#,
r#"\b(identified\s+(?:with\s+\w+\s+)?by)(\s+)("[^"\r\n]{0,512}"|'[^'\r\n]{0,512}'|[^\s,;]{1,512})"#,
);
pub(crate) const DDL_REPLACEMENT: &str = "${1}${2}${4}${5}[REDACTED]";
pub(crate) const PAIR_REPLACEMENT: &str = "${1}${2}[REDACTED]";
pub(crate) const BEARER_REPLACEMENT: &str = "Bearer [REDACTED]";
pub(crate) const URI_REPLACEMENT: &str = "${1}[REDACTED]${3}";
pub(crate) const URI_REPLACEMENT: &str = "${1}[REDACTED]${3}${4}";
/// Compiled size allowance for the bounded Unicode repetitions above.
pub(crate) const PATTERN_SIZE_LIMIT: usize = 1 << 28;
/// `scheme://name:value` with no `@` after it, as in a cut connection string; group 3 ends the authority.
pub(crate) const BARE_USERINFO_PATTERN: &str =
r#"(?i)\b([a-z][a-z0-9+.-]*://[^\s:/@'"]+:)([^\s/?#@'"]+)([/?#]?)"#;
/// The most characters of error text kept after redaction.
pub(crate) const ERROR_TEXT_CHARS: usize = 2000;
1 change: 1 addition & 0 deletions crates/sqlbuild-core/src/redaction/main/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod redact_error_text;
pub mod redact_text;
pub mod secret_field_name;
17 changes: 17 additions & 0 deletions crates/sqlbuild-core/src/redaction/main/redact_error_text.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! `redact_error_text`: error text redacted before it is cut, so a cut can never expose part of a secret.

use crate::redaction::_helpers::patterns::bare_userinfo_redacted;
use crate::redaction::constants::ERROR_TEXT_CHARS;
use crate::redaction::main::redact_text::redact_text;

/// Named secrets and URI passwords redacted, even where a warehouse cut the URI before its host, then at most 2,000 characters; a second pass changes nothing.
#[must_use]
pub fn redact_error_text(text: &str) -> String {
let redacted = bare_userinfo_redacted(&redact_text(text));
if redacted.chars().count() <= ERROR_TEXT_CHARS {
return redacted;
}
let mut kept: String = redacted.chars().take(ERROR_TEXT_CHARS - 1).collect();
kept.push('…');
kept
}
11 changes: 7 additions & 4 deletions crates/sqlbuild-core/src/redaction/main/redact_text.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
//! `redact_text`: obvious named secrets and URI passwords removed from diagnostic text.

use crate::redaction::_helpers::patterns::{
AUTHORIZATION, BEARER, SECRET_VALUE, URI_CREDENTIAL, substituted,
AUTHORIZATION, BEARER, DDL_PASSWORD, SECRET_VALUE, URI_CREDENTIAL, substituted,
};
use crate::redaction::constants::{
BEARER_REPLACEMENT, DDL_REPLACEMENT, PAIR_REPLACEMENT, URI_REPLACEMENT,
};
use crate::redaction::constants::{BEARER_REPLACEMENT, PAIR_REPLACEMENT, URI_REPLACEMENT};

/// Authorization headers, bearer tokens, `name=value` secrets, then URI passwords redacted.
/// Authorization headers, bearer tokens, `name=value` secrets, SQL passwords, then URI passwords redacted.
#[must_use]
pub fn redact_text(value: &str) -> String {
let authorization: String = substituted(&AUTHORIZATION, value, PAIR_REPLACEMENT);
let bearer: String = substituted(&BEARER, &authorization, BEARER_REPLACEMENT);
let named: String = substituted(&SECRET_VALUE, &bearer, PAIR_REPLACEMENT);
substituted(&URI_CREDENTIAL, &named, URI_REPLACEMENT)
let statement: String = substituted(&DDL_PASSWORD, &named, DDL_REPLACEMENT);
substituted(&URI_CREDENTIAL, &statement, URI_REPLACEMENT)
}
Loading
Loading