SecretShield
Local secret detection for VS Code. SecretShield finds exposed credentials, private keys and service-account files, classifies them into three security phases, highlights them in the editor, checks how sensitive files are treated by Git, and installs a Git pre-push hook that blocks pushes containing restricted secrets.
All detection runs locally. SecretShield never uploads source code, never sends files to an LLM, never executes or imports the code it scans, and collects no telemetry. Secret values are masked everywhere (diagnostics, tree, hover, logs, hook output, audit log).
Contents
- Development setup
- Using SecretShield
- The three phases
- How detection works
- Git ignore inspection
- Pre-push hook
- Overrides and suppressions
- Configuration
- Custom rules
- Known false positives and limitations
- Security and privacy guarantees
- Packaging a VSIX
- Manual testing checklist
Development setup
Requirements: Node.js 18+ (20/22 recommended), Git 2.30+, VS Code 1.90+.
npm install
npm run build # bundles dist/extension.js and dist/secretshield-cli.js (esbuild)
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm test # build + unit/integration tests (node:test, temporary Git repos)
npm run validate # package.json contribution checks + activation smoke test (mock VS Code API)
Launch in the Extension Development Host
- Open this folder in VS Code.
- Press F5 (configuration "Run SecretShield (Extension Development Host)"). It builds with
node scripts/build.mjs and opens a second VS Code window with the extension loaded.
- In that window open any folder/repository to test (use a throwaway repository, not production code).
Using SecretShield
| Action |
How |
| Scan current file |
Command Palette → SecretShield: Scan Current File (files are also scanned on open, on save and while typing) |
| Scan workspace |
SecretShield: Scan Workspace (also runs at startup when secretshield.scanWorkspace is on). Shows progress and can be cancelled. |
| Rescan |
SecretShield: Rescan Everything (Clear Cache) |
| Overview panel |
Activity bar → SecretShield shield icon → Overview: summary tiles per phase (click to filter), search by file / rule / author, minimum-severity filter, group by phase / file / author / severity, and one card per finding with severity colour, location link, author ("👤 who introduced it"), masked value and actions (Open, Move to env, Git-ignore, How to fix, ⋯ Ignore this line / Suppress). A push-protection strip shows each repository's hook state with Install hook, Update and Dry run buttons. Filters are remembered. |
| Push protection panel |
Same container → Push Protection (collapsed by default; details per repository) (hook state per repository, policy, last push result, actions). Repositories are found when the opened folder is inside a repo or contains repos in sub-folders (up to 4 levels deep). |
| Status bar |
$(shield) N = unresolved findings (red background if a restricted secret is present). Click to focus the Findings view. |
| Hover |
Hover a squiggle: rule, phase, severity, confidence, classification, risk, masked value, Git status, who introduced it, remediation, fingerprint |
| Who introduced it |
For files in a repository, git blame attributes each finding's line to its author, date and commit ("introduced by"); unsaved/uncommitted lines say so. Phase 3 file findings show who first added the file ("file added by"). The tree shows the author's name next to each finding, and the push report adds a by line with the author of the offending commit. Only local Git metadata is used. |
| Quick fixes |
Light bulb on a finding: remediation guide, move to environment variable (JS/TS/Python string literals), add file to .gitignore, suppress on this line, suppress in .secretshield.json |
| Staged changes |
SecretShield: Scan Staged Changes |
| Push dry run |
SecretShield: Check Outgoing Commits (Dry-Run Push Gate): same logic as the hook, against your upstream |
Squiggle colours: red (Error) = confirmed/likely Phase 2 restricted secret with confidence ≥ blockMinConfidence; yellow (Warning) = general exposure, lower-confidence restricted findings and Phase 3 hygiene issues; Information = low-signal review items (e.g. credential flows); Hint = recognised placeholders (only when reportPlaceholders is on).
The three phases
| Phase |
Meaning |
Examples |
Push gate |
| 1 — General Exposure |
Potential secrets and suspicious credential patterns |
API_KEY = "…", password: "…", Authorization headers, JWTs, URLs with user:password@, credential query strings, Google API keys, Stripe test keys, commented-out credentials |
Warning |
| 2 — Highly Restricted |
Material that grants privileged access |
PEM/OpenSSH/PuTTY private keys, GCP service-account JSON, AWS access/secret keys, GitHub/GitLab tokens, Slack/Stripe-live/OpenAI/Anthropic/npm/PyPI/SendGrid/Vault/Databricks/Shopify/Docker tokens, Azure storage keys, DB admin credentials, production-named credentials, signing/JWT/encryption secrets, lines marked secretshield:restricted, credentials in restrictedPaths |
Blocks (when confidence ≥ blockMinConfidence) |
| 3 — File Exposure & Hygiene |
Is a sensitive file protected from version control? |
.env*, *.pem, *.key, *.p12/*.pfx/*.jks, id_rsa, service-account / credentials JSON, .aws/credentials, .kube/config, .netrc, .git-credentials, Terraform state, custom restrictedPaths |
Warn (or block with gitignoreViolationPolicy: "block") |
Every finding carries: phase, severity, confidence (0–1), risk score (0–100), classification, file, line/column, rule id, remediation, masked value and a stable fingerprint.
Classification
confirmed: recognised credential format that passed structural validation (PEM body, JWT header, token checksum shape)
likely: sensitive variable/property name combined with a credential-like literal value
review: suspicious; needs a human (e.g. the value contains "example"/"test", or a credential flow without a literal)
placeholder: reliably recognised placeholder (changeme, ${VAR}, <your-token>, xxxx, AWS …EXAMPLE keys); hidden by default
How detection works
For each file SecretShield:
- Detects the file kind (JS, TS, Python, JSON/JSONC, YAML, TOML, INI/properties,
.env, shell/PowerShell, Dockerfile, XML; a generic analyzer covers Go, Java, C#, Ruby, PHP, Terraform…).
- Runs format rules (regex + structural validators): PEM blocks are validated for matching armor and real base64 key material, JWTs are decoded, URLs are parsed for credentials (admin DB users escalate to Phase 2), entropy checks reject low-randomness matches.
- Runs structural document checks: any JSON/YAML object with
type: service_account + client_email + a valid PEM private_key is a service-account key, whatever the filename. A file named service-account.json without key material is not reported.
- Runs a language-aware analyzer that tokenises the file without executing it and correlates names with values: declarations, assignments, nested object/dict properties (reported as
config.auth.token), member and subscript assignment (os.environ["API_KEY"] = …), keyword arguments, type-annotated assignments, static string concatenation, aliasing through another variable (k = "…"; API_KEY = k), hardcoded fallbacks (process.env.X || "…", os.getenv("X", "…")), header setters (headers.set("Authorization", …)), comparisons (if password == "…"), and credential flows with no literal (SERVICE_ACCOUNT_KEY = load_config()["private_key"] → review/info only).
- Evaluates each candidate: identifier words (
passwordHash, tokenizer, tokenUrl, getPassword, publicKey are not secrets), values (placeholders, references, prose, key names like "access_token", paths and URLs without credentials are excluded), entropy, syntax and test-path context adjust confidence; names marking production, admin, AWS or signing material escalate to Phase 2.
- De-duplicates overlapping findings (most specific wins), applies escalation, fixture downgrades, suppressions and the confidence threshold.
The rule engine is data-driven (src/core/rules.ts): each rule declares id, name, description, phase, severity, confidence, file kinds, detection and validation strategy, remediation and whether it can block a push. New analyzers plug into src/core/engine.ts by returning Candidates.
Git ignore inspection (Phase 3)
For sensitive files (by name, or because they contain a Phase 2 secret) SecretShield asks Git itself:
| Question |
Git command |
| Tracked? |
git ls-files -z --full-name -- <paths> |
| Ignored, and by which rule? |
git check-ignore -v -z --no-index --stdin: honours nested .gitignore, .git/info/exclude, core.excludesFile (global) and !negation rules; --no-index also evaluates tracked files |
| Staged? |
git diff-index --cached --name-only HEAD (empty tree when there is no commit yet) |
| In history? |
git log --all -1 -- <path> (for untracked files) |
| Trusted fixture? |
secretshield.trustedFixturePatterns |
Findings: tracked despite an ignore rule (ignore rules do not apply to tracked files), tracked sensitive file, staged, not ignored (could be committed by git add .), only in history. An ignored file that contains a secret is still reported by Phases 1/2, with one deliberate exception: files that Git ignores and does not track or stage (a local .env, a service-account key kept out of the repo) are the intended place for local secrets, so their content findings are hidden in the editor (secretshield.quietIgnoredFiles, on by default). The moment such a file is tracked, staged or no longer ignored, its findings come back, and the push gate scans it like any other file if it is ever committed. Template files (.env.example, *.sample, *.template) are expected to be committed and only get Phase 3 findings if they contain a restricted secret. Content-dependent files (.npmrc, .pypirc, *.tfvars, kubeconfig, *.pem certificates…) are only flagged when they contain credential findings.
Pre-push hook
What it does
When you run git push (terminal, VS Code Source Control, or any client that runs Git hooks):
- Git passes
<local ref> <local sha> <remote ref> <remote sha> lines to the hook.
- For each ref the outgoing commits are resolved:
- existing branch:
git rev-list <local> ^<remote> (also correct for force-pushes);
- new branch, or remote SHA not available locally: commits not reachable from any remote-tracking branch of that remote (
--not --remotes=<remote>);
- branch deletions are skipped; multiple refs are combined and de-duplicated.
- For every outgoing commit the changed files are listed (
git diff-tree -r -M), new blobs are read with git cat-file --batch, and each full blob is scanned (so multi-line PEM blocks and JSON structure are understood). Findings are kept only if they touch lines added by that commit (git diff -U0 old new). Renames without content changes add nothing; pre-existing secrets already on the remote are not re-reported.
- Merge commits only report lines new relative to every parent (evil-merge/conflict-resolution content); content from the merged branch is reported on its own commits.
- A secret added in one commit and deleted in a later one is still blocked: it is in the history being pushed (the report says "removed in a later commit").
- Paths matching
excludePatterns (e.g. dist/) are still checked for Phase 2 secrets. Binary files are only checked by name (Phase 3). Files over maxFileSizeKB are listed as not scanned.
- Restricted findings (Phase 2, confidence ≥
blockMinConfidence, blockRestrictedPushes on) block the push with a non-zero exit code. Phase 1 and lower-confidence findings are printed as warnings. Sensitive files added in outgoing commits warn, or block with gitignoreViolationPolicy: "block".
- Output shows file, line, column, commit, rule, phase, severity, confidence, masked value, remediation and the fingerprint, never the secret.
The working tree is never scanned as a substitute for outgoing content. Staged-but-uncommitted changes are not part of a push (use Scan Staged Changes).
When a push is blocked from VS Code, the Git error appears as usual and SecretShield shows a notification with the reason, file/line, phase, remediation and an Open Finding button (it watches .git/secretshield/last-block.json).
Installation (safe and reversible)
Limitations of client-side hooks
Client-side hooks are a safety net, not an enforcement boundary:
git push --no-verify skips pre-push hooks; other clients/tools may not run hooks; anyone can edit .git/hooks, delete the scanner or change core.hooksPath.
- Pushes with more than
prePushHook.maxCommits commits are scanned partially (reported).
- If the remote SHA is unknown locally (force-push without fetching), all commits not on a known remote-tracking branch are scanned, which can re-report content that is already on the remote.
- Content detection has false positives/negatives; binary files are checked by name only.
Organisations needing mandatory enforcement must also use server-side controls: GitHub push protection / secret scanning, GitLab secret push protection, Bitbucket secret scanning, or pre-receive hooks on your Git server.
Overrides and suppressions
Overrides (for a blocked push) are disabled by default (secretshield.allowLocalOverrides: false). A committed .secretshield.json with "allowLocalOverrides": false disables them for everyone regardless of personal settings. When allowed, an override is always explicit and audited. There is no hidden bypass:
- SecretShield: Approve One-Time Push Override…: asks for a reason (≥ 10 characters), shows the exact findings, and writes a token valid for 15 minutes that covers exactly the fingerprints of the last blocked push; it is consumed by the next push.
- or
SECRETSHIELD_OVERRIDE_REASON="reason, 10+ chars" git push from a terminal.
Every block and override is appended to .git/secretshield/audit.log (JSON lines: time, action, reason, git user, remote, rule ids, paths, lines, commits, fingerprints; no secret values).
Suppressions (for reviewed false positives) are narrowly scoped and always need a reason:
{
"allowLocalOverrides": false,
"restrictedPaths": ["deploy/prod/**"],
"trustedFixturePatterns": ["test/fixtures/**"],
"suppressions": [
{ "fingerprint": "3f1c9a0d2b7e8c4f5a61", "ruleId": "pem.private-key", "path": "test/fixtures/fake-key.txt", "reason": "Synthetic key used by parser tests", "addedBy": "ramiz", "addedAt": "2026-11-03" },
{ "ruleId": "ctx.hardcoded-password", "path": "docs/examples/*.md", "reason": "Documentation examples" }
]
}
Fingerprints are truncated SHA-256 hashes over rule id, path, key name and a SHA-256 of the value. They contain no value, but like any hash of a weak password they could be brute-forced by someone who already has the file.
Configuration
| Setting |
Default |
Description |
secretshield.enabled |
true |
Enable scanning in the editor |
secretshield.scanOnOpen / scanOnSave / scanOnType |
true |
When to scan documents |
secretshield.debounceMs |
400 |
Delay before re-scanning after an edit |
secretshield.scanWorkspace |
true |
Scan the workspace at startup |
secretshield.maxWorkspaceFiles |
20000 |
File limit per workspace folder |
secretshield.maxConcurrentScans |
8 |
Parallel file scans |
secretshield.maxFileSizeKB |
2048 |
Skip larger files (editor and push gate) |
secretshield.excludePatterns |
node_modules, .git, dist, build, .venv, venv, __pycache__, out, *.min.js, *.map |
Skipped by workspace scans; the push gate still checks them for Phase 2 |
secretshield.followSymlinks |
false |
Follow symlinks in workspace scans (targets outside the folder are never scanned) |
secretshield.blockRestrictedPushes |
true |
Block pushes with restricted findings |
secretshield.blockMinConfidence |
0.75 |
Minimum confidence for a restricted finding to block |
secretshield.minConfidence |
0.35 |
Findings below this are not reported |
secretshield.warnOnGeneralExposure |
true |
Show Phase 1 in the editor and as push warnings |
secretshield.reportPlaceholders |
false |
Show recognised placeholders as hints |
secretshield.inspectGitIgnore |
true |
Run Phase 3 checks |
secretshield.blame |
true |
Show who introduced each finding (git blame: author, e-mail, date, commit) in hover, tree and push report; up to 300 files per workspace scan |
secretshield.quietIgnoredFiles |
true |
Hide content findings in files that Git ignores and does not track or stage (local .env files, service-account keys kept out of the repo). They reappear as soon as the file is tracked, staged or un-ignored; templates like .env.example are always reported; the push gate is unaffected |
secretshield.gitignoreViolationPolicy |
"warn" |
warn or block when outgoing commits add sensitive files |
secretshield.prePushHook.enabled |
true |
Offer installation and keep hooks updated; when false installed hooks print a notice and skip scanning |
secretshield.prePushHook.failClosed |
true |
Block the push if the scan cannot run |
secretshield.prePushHook.maxCommits |
2000 |
Max outgoing commits scanned per push |
secretshield.allowLocalOverrides |
false |
Allow explicit, audited one-time overrides |
secretshield.trustedFixturePatterns |
[] |
Phase 1 findings there become information; Phase 2 is never downgraded |
secretshield.restrictedPaths |
[] |
Extra sensitive-file globs; credentials inside are promoted to Phase 2 |
secretshield.suppressions.allowRestricted |
false |
Honour inline suppressions on Phase 2 findings |
secretshield.customRules |
[] |
Custom regex rules (below) |
All settings are validated; invalid values fall back to defaults and are reported in the SecretShield output channel. .secretshield.json in the repository root can add restrictedPaths, trustedFixturePatterns, excludePatterns, customRules, suppressions, allowRestrictedSuppressions, and can only tighten allowLocalOverrides (false wins), blockRestrictedPushes (true wins) and gitignoreViolationPolicy (block wins). The extension respects git.path if Git is installed in a non-standard location. In untrusted workspaces, workspace-level values of customRules, excludePatterns, allowLocalOverrides and suppressions.allowRestricted are ignored and hooks cannot be installed.
Custom rules
"secretshield.customRules": [
{
"id": "custom.acme-service-token",
"name": "ACME service token",
"pattern": "\\bacme_svc_[A-Za-z0-9]{32}\\b",
"flags": "",
"secretGroup": 0,
"phase": 2,
"severity": "critical",
"confidence": 0.9,
"fileGlobs": ["**/*.{js,ts,py,yaml,env}"],
"canBlockPush": true,
"remediation": "Revoke the token in the ACME console and use the vault path secret/acme."
}
]
id must start with custom.; pattern is a JavaScript regular expression (max 2000 chars, must not match the empty string); flags may contain i m s u; secretGroup selects the capture group holding the secret; phase is 1 or 2. Invalid rules are skipped and reported in the output channel. The same array can live in .secretshield.json (shared with the team and used by the hook).
Known false positives and limitations
- Generic names with random-looking values (e.g.
session_token = "<uuid>" in fixtures) are reported as Phase 1; use fixture paths or suppressions.
- Values containing "example", "test", "dummy", "demo"… are reported with reduced confidence (
review) rather than hidden, because real secrets sometimes contain such words.
- Twilio-style
SK… keys, Databricks dapi… and other short-prefix formats can match hashes; they carry entropy checks but may still need suppressions.
- To keep noise low, some value shapes are treated as names, not secrets, which can hide a weak real credential of that shape: camelCase/snake_case/namespaced identifiers containing a credential word (
"FlowPasswordRequired", "password_too_weak"), dotted identifiers ("settings.auth.token"), a single lowercase word of ≤ 16 letters for non-password fields, Title-case labels ("Access Token"), URNs, CSS selectors, values containing ${…}/{{…}} interpolation, non-ASCII text, and URLs on RFC 2606 documentation domains (example.com, *.test, *.invalid). Format-based rules (tokens, keys) are not affected by these filters.
- Benchmark on this project's own
node_modules (10,881 third-party JS/TS/JSON/YAML/Markdown files, 59 MB): 9 Phase 1 findings, 0 Phase 2 findings, ~9 MB/s on a single core.
- Analysis is per file: values imported from another module, computed at runtime, base64/obfuscated, or split in non-static ways are not resolved. Aliasing is followed one level within a file.
- The JS/TS/Python analyzer is a tolerant tokenizer, not a full parser. Unusual syntax (decorators with string args, JSX edge cases, heredocs) may be missed, and the generic analyzer is regex-based.
- Minified files (very long lines) only get format-based rules.
- Binary key stores (
.p12, .pfx, .jks) are detected by name only.
- Workspace scans skip
excludePatterns; files over maxFileSizeKB are skipped everywhere.
- Hover and diagnostics show masked values; to see the real value open the file itself.
- See Limitations of client-side hooks.
Security and privacy guarantees
- Local only: no network requests, no telemetry, no LLM calls. The bundled code contains no HTTP client usage.
- No execution: scanned code is tokenised/parsed as text; nothing is imported, evaluated or run. YAML is parsed with alias limits and no custom tags.
- Redaction: findings carry only masked values (
ghp_****...****, -----BEGIN RSA PRIVATE KEY-----****...****, or ****...****). The output channel, hook output and reports additionally pass through a scrubber for known token formats, URL passwords and password= style pairs.
- Never modifies code while scanning. Every edit (move to env var, suppression comment,
.gitignore rule, .secretshield.json entry, git rm --cached) is an explicit quick fix with a confirmation dialog showing exactly what changes.
- Filesystem scope: only workspace folders are scanned; symlinks are skipped by default and never followed outside the folder.
- Hooks: never overwrite foreign hooks, detect tampering by checksum, fail closed by default, keep an audit log, and are honest about being bypassable.
Packaging a VSIX
npm install
npm run package # runs the production build, then vsce package
code --install-extension secretshield-0.3.0.vsix
The VSIX contains only dist/extension.js, dist/secretshield-cli.js, the icons, package.json, README, CHANGELOG and LICENSE (see .vscodeignore).
Manual testing checklist
Use a throwaway repository and the synthetic values below (never real credentials). To create a synthetic PEM key without real key material:
python -c "import base64,os;b=base64.b64encode(os.urandom(900)).decode();print('-----BEGIN RSA PRIVATE KEY-----');[print(b[i:i+64]) for i in range(0,len(b),64)];print('-----END RSA PRIVATE KEY-----')" > fake.key.txt
Setup
npm install && npm run build, press F5. In the new window, open an empty folder and run git init; add a bare remote: git init --bare ../remote.git && git remote add origin ../remote.git.
- Confirm the SecretShield icon appears in the activity bar and the status bar shows
$(shield) 0. If prompted, choose Install… for the hook; otherwise run SecretShield: Install Git Pre-Push Hook. Push Protection view should show installed.
Editor detection (Phases 1 and 2)
- Create
app.py with API_KEY = "example-hardcoded-key" and DATABASE_PASSWORD = "example-password" → two yellow squiggles (classification review). Hover: rule, phase 1, confidence, masked value.
- Add
real_pw = "Tr0ub4dor&3xq" → yellow, classification likely.
- Add
SERVICE_ACCOUNT_KEY = load_config()["private_key"] → information-level "Suspicious credential flow".
- Create
config.js with const config = { auth: { token: "k3J9sLq0Pz7Wm2Xr5Tv8" } }; → finding named config.auth.token.
- Paste the synthetic PEM into
notes.txt (innocent name) → red squiggle, Phase 2, pem.private-key.
- Create
blob.json with a service-account document ("type": "service_account", client_email, private_key containing the escaped synthetic PEM with \n) → red gcp.service-account-key.
- Type
password = "changeme" and TOKEN = "${TOKEN}" → no finding (placeholders). Enable secretshield.reportPlaceholders → faint hints.
- Confirm the Findings view groups by Phase 2 / Phase 1 / Phase 3 and severity; clicking opens the location. Status bar count matches and turns red.
- Ordinary code (
passwordLabel = "Password", tokenUrl = "https://…", interface X { password: string }) → no findings.
Quick fixes
- On
real_pw in app.py: Move "real_pw" to an environment variable… → name prompt, confirmation dialog describing the change; result os.environ["REAL_PW"] and import os added.
- On a Phase 1 finding: Suppress … on this line… → reason required, comment appended, finding disappears.
- On the PEM finding: inline suppression is not offered; Suppress this exact finding in .secretshield.json… writes an entry with fingerprint and reason; finding disappears. Remove the entry afterwards.
Phase 3 (Git ignore)
- Create
.env with DB_PASSWORD=S3cr3t-P@ss-w0rd → Phase 3 "Sensitive file not ignored" on line 1 plus Phase 1 content finding. Quick fix Add this file to .gitignore… → finding clears after save.
git add -f .env && git commit -m env → "Ignored file is still tracked" with the matching .gitignore rule. Content finding still shown.
- Add
!.env negation after .env in .gitignore → "not ignored" mentioning the negation rule.
Push gate
- Commit a clean file and
git push origin HEAD:main in the integrated terminal → passes; warnings for Phase 1 content are printed.
- Commit
notes.txt with the PEM, then a harmless commit, then git push → blocked, exit code non-zero, report shows notes.txt:1:1, commit, rule, masked value, remediation; no key material printed. A VS Code notification appears with Open Finding.
- Push again from the Source Control view (… → Push) → blocked; VS Code shows the Git error; SecretShield notification appears.
- Delete
notes.txt in a new commit and push → still blocked ("removed in a later commit"). Clean up with git reset --hard <clean commit>.
- SecretShield: Check Outgoing Commits (Dry-Run Push Gate) before pushing → same verdict without pushing.
- Overrides: try
SECRETSHIELD_OVERRIDE_REASON="testing override path" git push → refused ("disabled by policy"). Set secretshield.allowLocalOverrides: true, push again → allowed with "OVERRIDE applied"; check .git/secretshield/audit.log (no key material). Add .secretshield.json with {"allowLocalOverrides": false} → overrides refused again.
- One-time override: with overrides allowed, after a blocked push run SecretShield: Approve One-Time Push Override…, push → allowed once; next blocked push needs a new approval.
Hook management
- Remove the hook, create your own
.git/hooks/pre-push (#!/bin/sh\necho mine >&2), run Install → refused with an explanation; choose Chain Existing Hook → both run on push. Remove → your hook is restored unchanged.
- Edit
.git/hooks/pre-push by hand → Check status shows modified; Install/Update asks before replacing.
git config core.hooksPath .githooks → Install warns about hooksPath being inside the working tree and offers Install Scanner Only.
- Temporarily rename
.git/secretshield/secretshield-cli.js → push blocked by fail-closed policy; set secretshield.prePushHook.failClosed: false and update the hook → push continues with a warning.
- Run SecretShield: Explain Pre-Push Hook Limitations and confirm it mentions
--no-verify and server-side protection.
Robustness
- Workspace scan of a large folder: progress notification, Cancel works,
node_modules skipped.
- Set
git.path to an invalid path and reload → warning that Phase 3 is unavailable; content scanning still works.
- Repeat a subset on Windows (Git for Windows) and macOS/Linux.
| |