Skip to content
| Marketplace
Sign in
Visual Studio Code>Linters>SecretShieldNew to Visual Studio Code? Get it now.
SecretShield

SecretShield

Mohammad Ramiz

|
1 install
| (0) | Free
Local secret detection for VS Code: three-phase credential scanning, Git ignore inspection and a pre-push gate that blocks restricted secrets.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

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

  1. Development setup
  2. Using SecretShield
  3. The three phases
  4. How detection works
  5. Git ignore inspection
  6. Pre-push hook
  7. Overrides and suppressions
  8. Configuration
  9. Custom rules
  10. Known false positives and limitations
  11. Security and privacy guarantees
  12. Packaging a VSIX
  13. 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

  1. Open this folder in VS Code.
  2. 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.
  3. 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:

  1. 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…).
  2. 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.
  3. 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.
  4. 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).
  5. 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.
  6. 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):

  1. Git passes <local ref> <local sha> <remote ref> <remote sha> lines to the hook.
  2. 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.
  3. 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.
  4. 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.
  5. 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").
  6. 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.
  7. 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".
  8. 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)

  • Install: SecretShield: Install Git Pre-Push Hook (also offered once per repository when secretshield.prePushHook.enabled is on). Writes .git/hooks/pre-push (POSIX sh, works with Git for Windows), plus .git/secretshield/secretshield-cli.js (self-contained scanner), policy.json (snapshot of your settings) and state.json. No administrator rights are needed. Hooks are only installed in trusted workspaces.
  • Existing hook: never overwritten. You can choose Chain Existing Hook: it is renamed to pre-push.secretshield-chained and run after the scan with the same arguments and stdin.
  • core.hooksPath: respected. SecretShield asks before writing into that directory and warns if it is inside the working tree (e.g. Husky), where the hook could be committed. Alternatively choose Install Scanner Only and add this line to your existing pre-push hook:
    node "$(git rev-parse --git-common-dir)/secretshield/secretshield-cli.js" pre-push "$@" || exit 1
    
    (the hook's stdin must reach this command).
  • Check: SecretShield: Check Git Pre-Push Hook Status → installed, outdated, modified (checksum mismatch: edited outside SecretShield), cli-missing, foreign (someone else's hook) or not-installed. The Push Protection view shows the same.
  • Update: SecretShield: Update Git Pre-Push Hook. The scanner copy and policy snapshot are also refreshed automatically when settings or the extension change. A modified hook is never replaced without confirmation.
  • Remove: SecretShield: Remove Git Pre-Push Hook removes only the SecretShield hook and its scanner/policy files, restores a chained hook, leaves other hooks alone and keeps the audit log.
  • Node.js: the hook runs node from PATH, falling back to the Node path detected at install time. VS Code's bundled runtime is not used. With prePushHook.failClosed (default) a missing Node or scanner error blocks the push with an explanation.

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:

  • Inline, same line or the line before:
    API_KEY = "…"  # secretshield-ignore ctx.hardcoded-api-key: public demo key used in docs
    # secretshield-ignore-next-line jwt.token: expired token used by parser tests
    
    Inline suppressions are not honoured for Phase 2 unless secretshield.suppressions.allowRestricted (or allowRestrictedSuppressions in .secretshield.json) is on. * wildcards never cover Phase 2. Malformed comments (no rule id or reason) are ignored and logged.
  • In .secretshield.json (reviewable in pull requests), by fingerprint (matches one rule + file + name + value hash, so it stops matching if the value changes), or by ruleId + path glob (+ optional line). Fingerprint suppressions work for Phase 2 findings too.
{
  "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

  1. 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.
  2. 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)

  1. 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.
  2. Add real_pw = "Tr0ub4dor&3xq" → yellow, classification likely.
  3. Add SERVICE_ACCOUNT_KEY = load_config()["private_key"] → information-level "Suspicious credential flow".
  4. Create config.js with const config = { auth: { token: "k3J9sLq0Pz7Wm2Xr5Tv8" } }; → finding named config.auth.token.
  5. Paste the synthetic PEM into notes.txt (innocent name) → red squiggle, Phase 2, pem.private-key.
  6. 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.
  7. Type password = "changeme" and TOKEN = "${TOKEN}" → no finding (placeholders). Enable secretshield.reportPlaceholders → faint hints.
  8. 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.
  9. Ordinary code (passwordLabel = "Password", tokenUrl = "https://…", interface X { password: string }) → no findings.

Quick fixes

  1. 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.
  2. On a Phase 1 finding: Suppress … on this line… → reason required, comment appended, finding disappears.
  3. 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)

  1. 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.
  2. git add -f .env && git commit -m env → "Ignored file is still tracked" with the matching .gitignore rule. Content finding still shown.
  3. Add !.env negation after .env in .gitignore → "not ignored" mentioning the negation rule.

Push gate

  1. Commit a clean file and git push origin HEAD:main in the integrated terminal → passes; warnings for Phase 1 content are printed.
  2. 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.
  3. Push again from the Source Control view (… → Push) → blocked; VS Code shows the Git error; SecretShield notification appears.
  4. Delete notes.txt in a new commit and push → still blocked ("removed in a later commit"). Clean up with git reset --hard <clean commit>.
  5. SecretShield: Check Outgoing Commits (Dry-Run Push Gate) before pushing → same verdict without pushing.
  6. 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.
  7. 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

  1. 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.
  2. Edit .git/hooks/pre-push by hand → Check status shows modified; Install/Update asks before replacing.
  3. git config core.hooksPath .githooks → Install warns about hooksPath being inside the working tree and offers Install Scanner Only.
  4. 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.
  5. Run SecretShield: Explain Pre-Push Hook Limitations and confirm it mentions --no-verify and server-side protection.

Robustness

  1. Workspace scan of a large folder: progress notification, Cancel works, node_modules skipped.
  2. Set git.path to an invalid path and reload → warning that Phase 3 is unavailable; content scanning still works.
  3. Repeat a subset on Windows (Git for Windows) and macOS/Linux.
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft