Skip to content

Commit 54f4348

Browse files
committed
feat(popup): inline 'local only' trust signal on credential-bearing rows
The privacy data-flow panel (v4.12.0) explained the storage-locality guarantee, but it ships off-by-default — a user pasting a BYO API key into the schema-overview editor had no visible reassurance about where the key lives. A small green .so-key-trust-local chip now surfaces on the aiSummaryApiKey + aiSummaryEndpoint rows (the AI endpoint URL can embed a key as a query param, so it gets the same treatment). Tooltip enumerates the guarantees: - chrome.storage.local is origin-scoped and never synced to a Google account - never sent to Astra Deck servers - redacted from the bug-report bundle (Diagnostics -> Save) via BUG_REPORT_REDACTED_KEYS New TRUST_SIGNAL_LOCAL_ONLY_KEYS is a strict subset of BUG_REPORT_REDACTED_KEYS so the chip's 'redacted from bundle' claim stays true; pinned at test time so adding a key to one set without the other fails CI. Public default URLs (Cobalt instance, alternative frontend, custom CSS) are redacted from bundles but don't get the chip — the 'local only' reassurance is specifically about secrets. CSS adds the green-tone variant to the existing .so-key-profile-badge pattern (matches the v4.16.0 risk-badge palette: amber for gated, red for unavailable, green for trusted). Pinned by a new 'v4.47.0 schema-overview rows for credential- bearing keys carry an inline local only trust signal' hardening test asserting the set declaration, the subset relationship with BUG_REPORT_REDACTED_KEYS, the well-known BYO-key entries, the row-builder hook, and the CSS variant. Existing NEW-1 test anchor hardened from indexOf('BUG_REPORT_REDACTED_KEYS') to a regex search so future comments referencing the symbol can't fool the slice window. Backlog item retired from RESEARCH_FEATURE_PLAN.md. 570/570 JS tests pass (+1 new); npm run check clean.
1 parent 7702cef commit 54f4348

5 files changed

Lines changed: 133 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,35 @@ All notable changes to Astra Deck are documented here. Versions are listed newes
66

77
## [Unreleased]
88

9+
- **Inline "local only" trust signal on credential-bearing schema-overview
10+
rows.** The privacy data-flow panel (v4.12.0) explained the
11+
storage-locality guarantee, but it ships off-by-default — a user
12+
pasting a BYO API key into the schema-overview editor had no visible
13+
reassurance about where the key lives. A small green `.so-key-trust-local`
14+
chip now surfaces on the `aiSummaryApiKey` + `aiSummaryEndpoint` rows
15+
(the AI endpoint URL can embed a key as a query param, so it gets the
16+
same treatment). Tooltip enumerates the guarantees:
17+
`chrome.storage.local` is origin-scoped and never synced to a Google
18+
account, never sent to Astra Deck servers, and the value is redacted
19+
from the bug-report bundle (Diagnostics → Save) via
20+
`BUG_REPORT_REDACTED_KEYS`. New `TRUST_SIGNAL_LOCAL_ONLY_KEYS` is a
21+
strict subset of `BUG_REPORT_REDACTED_KEYS` so the chip's "redacted
22+
from bundle" claim stays true; pinned at test time so adding a key
23+
to one set without the other fails CI. Public default URLs (Cobalt
24+
instance, alternative frontend, custom CSS) are redacted from
25+
bundles but don't get the chip — the "local only" reassurance is
26+
specifically about secrets. CSS adds the green-tone variant to the
27+
existing `.so-key-profile-badge` pattern (matches the v4.16.0 risk-
28+
badge palette: amber for gated, red for unavailable, green for
29+
trusted). Pinned by a new `v4.47.0 — schema-overview rows for
30+
credential-bearing keys carry an inline "local only" trust signal`
31+
hardening test asserting the set declaration, the subset relationship
32+
with BUG_REPORT_REDACTED_KEYS, the well-known BYO-key entries, the
33+
row-builder hook, and the CSS variant. Existing NEW-1 test anchor
34+
hardened from `indexOf('BUG_REPORT_REDACTED_KEYS')` to a regex
35+
search so future comments referencing the symbol can't fool the
36+
slice window. 570/570 JS tests pass (+1 new).
37+
938
- **Audit pass: three real bugs fixed in the just-shipped Pass-3 batch.**
1039
Deep audit of the autonomous-loop's recent ships caught two real
1140
defects + one polish gap before they reached users:

RESEARCH_FEATURE_PLAN.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,6 @@ Each item carries: priority, complexity, why, evidence, touches, acceptance, ver
148148

149149
### Polish and competitive parity
150150

151-
- **P2 / S — Inline trust signal on AI provider rows**
152-
- "Key stored locally only" inline on the `aiSummaryApiKey` row (currently only in privacy-data-flow panel which is off by default).
153151

154152
- **P3 / S — Wheel-seek (NF9)**
155153
- Touches: `ytkit.js` near `volumeWheelMode`; new `wheelSeek` schema entry.

extension/popup.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,17 @@ body {
11321132
border-color: rgba(245, 87, 87, 0.36);
11331133
}
11341134

1135+
/* v4.47.0: inline trust signal on credential-bearing rows. Same
1136+
compact-pill geometry as the gated + unavailable chips, but a
1137+
reassuring green tone — this is positive trust info, not a
1138+
warning. Tooltip enumerates the local-storage + redaction
1139+
guarantees. WCAG AA contrast against the row background. */
1140+
.schema-overview-list .so-key-profile-badge.so-key-trust-local {
1141+
background: rgba(78, 200, 165, 0.14);
1142+
color: #6fe0bc;
1143+
border-color: rgba(78, 200, 165, 0.36);
1144+
}
1145+
11351146
/* v4.47.0 NEW-6: per-key reset affordance. Only rendered when the
11361147
schema entry has a defaultValue AND the current setting value
11371148
differs from it. Compact circular button with the ↺ glyph — wraps

extension/popup.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,6 +1895,26 @@ function buildSchemaOverviewKeyRow(entry, settings) {
18951895
}
18961896
}
18971897

1898+
// v4.47.0: inline trust signal on credential-bearing rows. The
1899+
// privacy data-flow panel (v4.12.0) already explains the "stored
1900+
// locally only" guarantee, but that panel is off by default — a
1901+
// user pasting an API key into the schema-overview editor has no
1902+
// visible reassurance about where the key lives. A small green
1903+
// "local only" chip on the row makes the trust boundary visible
1904+
// at the pasting moment, not buried in an opt-in panel. Tooltip
1905+
// expands: bug-report bundles redact the value via NEW-1's
1906+
// BUG_REPORT_REDACTED_KEYS list, and chrome.storage.local is
1907+
// origin-scoped (never synced to a Google account).
1908+
if (TRUST_SIGNAL_LOCAL_ONLY_KEYS.has(entry.key)) {
1909+
const trustChip = document.createElement('span');
1910+
trustChip.className = 'so-key-profile-badge so-key-trust-local';
1911+
trustChip.textContent = 'local only';
1912+
trustChip.title = 'This value is stored in chrome.storage.local on this device. '
1913+
+ 'It is never synced to a Google account, never sent to Astra Deck servers, '
1914+
+ 'and is redacted from the bug-report bundle (Diagnostics → Save).';
1915+
row.appendChild(trustChip);
1916+
}
1917+
18981918
if (entry.type === 'boolean') {
18991919
const on = settings[entry.key] === true;
19001920
const btn = document.createElement('button');
@@ -2319,6 +2339,19 @@ const BUG_REPORT_REDACTED_KEYS = Object.freeze([
23192339
'alternativeFrontendInstance',
23202340
]);
23212341

2342+
// v4.47.0: schema-overview rows for these keys carry an inline
2343+
// "local only" trust signal chip. The set is a strict subset of
2344+
// BUG_REPORT_REDACTED_KEYS — only the truly credential-bearing
2345+
// keys (BYO API keys + the AI endpoint URL, which can embed a
2346+
// key as a query param). Public default URLs (Cobalt instance,
2347+
// alternative frontend, custom CSS) are redacted from bundles
2348+
// but don't need a trust chip on the editor row because the
2349+
// "local only" reassurance is specifically about secrets.
2350+
const TRUST_SIGNAL_LOCAL_ONLY_KEYS = new Set([
2351+
'aiSummaryApiKey',
2352+
'aiSummaryEndpoint',
2353+
]);
2354+
23222355
// v4.47.0 NEW-1: redact in place so the bug-report bundle never ships
23232356
// a user's BYO API key, a self-hosted endpoint, or pasted custom CSS.
23242357
// Returns a NEW object — callers must NOT use the input afterwards.

tests/hardening.test.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8146,6 +8146,61 @@ test('v4.47.0 NEW-7 — SW lifecycle ring records sw-start into chrome.storage.s
81468146
'bug-report bundle payload must include swLifecycle (shorthand property)');
81478147
});
81488148

8149+
test('v4.47.0 — schema-overview rows for credential-bearing keys carry an inline "local only" trust signal', () => {
8150+
// The privacy data-flow panel (v4.12.0) explains the "stored
8151+
// locally only" guarantee, but that panel is off by default —
8152+
// a user pasting an API key into the schema-overview editor
8153+
// had no visible reassurance about where the key lives. A
8154+
// small green chip on the row makes the trust boundary visible
8155+
// at the pasting moment. Implementation invariants pinned here:
8156+
//
8157+
// 1. TRUST_SIGNAL_LOCAL_ONLY_KEYS is a strict subset of
8158+
// BUG_REPORT_REDACTED_KEYS — every key with a trust chip
8159+
// must also be redacted from the bug-report bundle, otherwise
8160+
// the chip's "redacted from bundle" claim is a lie.
8161+
// 2. The chip uses the existing profile-badge geometry +
8162+
// so-key-trust-local class variant.
8163+
// 3. CSS declares the variant.
8164+
8165+
assert.match(popupSource, /const\s+TRUST_SIGNAL_LOCAL_ONLY_KEYS\s*=\s*new Set\(/,
8166+
'popup.js must declare TRUST_SIGNAL_LOCAL_ONLY_KEYS');
8167+
8168+
// Extract both sets and assert the subset relationship at
8169+
// source-level so future additions to one are caught against
8170+
// the other.
8171+
const trustListMatch = popupSource.match(/TRUST_SIGNAL_LOCAL_ONLY_KEYS\s*=\s*new Set\(\[([^\]]+)\]/);
8172+
assert.ok(trustListMatch, 'TRUST_SIGNAL_LOCAL_ONLY_KEYS must initialize from an array literal');
8173+
const trustKeys = trustListMatch[1].match(/'[^']+'/g) || [];
8174+
const redactListMatch = popupSource.match(/BUG_REPORT_REDACTED_KEYS\s*=\s*Object\.freeze\(\[([^\]]+)\]/);
8175+
assert.ok(redactListMatch, 'BUG_REPORT_REDACTED_KEYS must initialize from a frozen array literal');
8176+
const redactKeys = new Set((redactListMatch[1].match(/'[^']+'/g) || []));
8177+
for (const trustKey of trustKeys) {
8178+
assert.ok(redactKeys.has(trustKey),
8179+
`${trustKey} must also be in BUG_REPORT_REDACTED_KEYS so the chip's "redacted from bundle" claim is true`);
8180+
}
8181+
// Each of the well-known BYO-key fields must carry the trust chip.
8182+
for (const required of ["'aiSummaryApiKey'", "'aiSummaryEndpoint'"]) {
8183+
assert.ok(trustKeys.includes(required),
8184+
`${required} must be in TRUST_SIGNAL_LOCAL_ONLY_KEYS so the trust chip surfaces on its row`);
8185+
}
8186+
8187+
// Row builder consults the set and applies the chip.
8188+
const rowStart = popupSource.indexOf('function buildSchemaOverviewKeyRow');
8189+
const rowEnd = popupSource.indexOf('return row;', rowStart);
8190+
const rowBlock = popupSource.slice(rowStart, rowEnd);
8191+
assert.match(rowBlock, /TRUST_SIGNAL_LOCAL_ONLY_KEYS\.has\(entry\.key\)/,
8192+
'row builder must check TRUST_SIGNAL_LOCAL_ONLY_KEYS.has(entry.key) before rendering the chip');
8193+
assert.match(rowBlock, /so-key-trust-local/,
8194+
'row builder must apply the .so-key-trust-local class on the chip');
8195+
8196+
// CSS variant exists.
8197+
const popupCss = fs.readFileSync(
8198+
path.join(__dirname, '..', 'extension', 'popup.css'), 'utf8'
8199+
);
8200+
assert.match(popupCss, /\.so-key-profile-badge\.so-key-trust-local\s*\{/,
8201+
'popup.css must declare the .so-key-trust-local variant');
8202+
});
8203+
81498204
test('v4.47.0 NEW-6 — per-key Reset button on schema-overview rows whose value differs from default', () => {
81508205
// NEW-6: a user who has changed one setting to a breaking value
81518206
// currently has to either remember the default or hit global
@@ -8347,7 +8402,11 @@ test('v4.47.0 NEW-1 — bug-report bundle redacts BYO keys/endpoints/CSS and inc
83478402
// 1. BUG_REPORT_REDACTED_KEYS exists and lists every sensitive key.
83488403
assert.match(popupSource, /const\s+BUG_REPORT_REDACTED_KEYS\s*=\s*Object\.freeze\(\[/,
83498404
'popup.js must declare BUG_REPORT_REDACTED_KEYS as a frozen array');
8350-
const listStart = popupSource.indexOf('BUG_REPORT_REDACTED_KEYS');
8405+
// Anchor on the declaration itself (not a passing mention in a
8406+
// comment elsewhere) — a later comment referenced the symbol
8407+
// name and a bare indexOf would otherwise slice from the wrong
8408+
// spot.
8409+
const listStart = popupSource.search(/const\s+BUG_REPORT_REDACTED_KEYS\s*=/);
83518410
const listBlock = popupSource.slice(listStart, listStart + 600);
83528411
for (const key of ['aiSummaryApiKey', 'aiSummaryEndpoint', 'customCssCode',
83538412
'downloadCobaltInstance', 'alternativeFrontendInstance']) {

0 commit comments

Comments
 (0)