Commit Graph
51 Commits
Author SHA1 Message Date
Sahilb315andCursor ffd0e7e759 fix: harden and simplify Linux system install
Tighten shim detection, profile repair, and install ordering while
trimming over-specific doctor/info hints from the system-install path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 02:19:59 +05:30
Sahilb315andCursor 7922606644 chore: keep local design documents untracked
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 02:19:59 +05:30
Sahilb315andCursor 8f6d0fcce0 feat: add Linux system-wide setup
Install shared shims, managed configuration, and login-shell PATH integration so golden images and multi-user hosts can protect package installs for every user.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 02:19:59 +05:30
Abhisek DattaandGitHub d3e656edcd feat(sandbox): enable network_via_proxy_only for the Go ecosystem profile (#375) 2026-07-10 17:40:35 +00:00
Sahil BansalandGitHub 2d938ea381 feat: advisory message appended to block output (#362)
* docs(specs): add custom block messages and package blocklist spec

* docs(specs): add custom block messages and package blocklist implementation plan

* feat(config): add blocked_packages list and custom block messages

* feat(audit): add package_blocklist_blocked event and blocklist model

* feat(proxy): block blocklisted packages in the policy gate before analysis

* feat(guard): block blocklisted packages before trust skip and analysis

* feat(ui): render blocklist blocks and custom messages, fix silent-mode block output

* feat(proxy): append custom messages to malware and go-cooldown block bodies

* test(proxye2e): cover blocklist enforcement and custom block messages

* docs(specs): remove spec and plan documents

* refactor: drop guard-flow blocklist enforcement and trim docs

Guard mode is being deprecated; the blocklist is enforced in proxy mode
only. Remove the trusted_packages mirroring references outside the docs.

* refactor(config): consolidate blocklist and block message under top-level block section

Replace dependency_cooldown.message, malware.message and blocked_packages
with a single block section: block.message is appended to every block
output regardless of which control blocked, and block.packages is the
package blocklist.

* fix(ui): render block.message as info note with clean spacing

* fix(ui): indent wrapped continuation lines in block reasons and messages

* update config template

* refactor(config): replace block section with top-level advisory_message

Remove the package blocklist (will be implemented as part of policies in
the future) and replace block.message with an optional top-level
advisory_message appended to every block output.

* chore(config): move advisory_message near top-level scalar configs in template
2026-07-09 17:49:00 +05:30
b19473945b Add experimental Go module proxy support (#358)
* feat: add experimental Go module support via pmg go

Adds Go modules as a proxy-guarded ecosystem, opt-in only: the command
runs solely when invoked explicitly as `pmg go ...` and is deliberately
excluded from setup aliases and PATH shims so existing users are
unaffected.

- packagemanager: goPackageManager with fail-safe command classification
  (vet/fix excluded from non-download since they can fetch on a cold
  cache) and pinned-version extraction where only canonical semver
  counts as explicit.
- GOPROXY normalization (fail-closed): effective GOPROXY read via
  `go env` (honors go env -w), rebuilt comma-joined with `direct`
  dropped so a 403 block is terminal and nothing silently falls back to
  unanalyzed VCS fetches. GOPRIVATE/GONOPROXY surface a warning;
  GOINSECURE is cleared. Contributed to the proxy flow through a new
  ProxyRoutingProvider hook (extra child env + dynamic MITM hosts).
- Go interceptor with dynamic host matching from the user's effective
  GOPROXY via InterceptorContext.GoProxyHosts. Malware analysis runs on
  .zip only (the sole endpoint that delivers code); .info/.mod/@latest/
  list pass through; /sumdb/ traffic and sum.golang.org are never
  touched so checksum-db verification stays intact; golang.org/toolchain
  is allowed on Go's own checksum verification.
- Dependency cooldown: publish time captured from .info responses
  (body unmodified), in-window .zip blocked with 403; fails open for
  cooldown only when the publish time was never observed.
- Cert gate: on macOS/Windows `pmg go` fails fast with actionable
  guidance unless the persisted PMG CA is OS-trusted (Go ignores
  SSL_CERT_FILE there); Linux works via the injected bundle.
- proxye2e: GOPROXY-protocol mock registry, Go driver and 10 hermetic
  cases (allow/block/confirm, case-escaped paths, cooldown block and
  fail-open, toolchain, sumdb passthrough).

Verified end-to-end on Linux: `pmg go get github.com/google/uuid@v1.6.0`
MITMs proxy.golang.org, analyzes the decoded module at the .zip fetch,
and go.sum verification succeeds through the tunneled checksum db.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xuhBeTVpfU4SdqVaarvuK

* fix(go): address review findings on experimental Go support

- Drop fmt/clean from NonDownloadCommands: both load packages via go
  list and can download modules on a cold cache, which would bypass the
  proxy under install_only.
- Support GOPROXY entries with a base path (e.g. corp Athens/JFrog at
  https://corp/goproxy): the interceptor now receives host -> base URL
  and strips the path prefix before parsing module URLs, so verdicts
  and cooldown key on the real module path.
- Default unschemed GOPROXY entries to https, matching go's own
  behavior, so corp mirrors configured as bare hosts are intercepted
  instead of silently unanalyzed.
- Memoize the final verdict per module zip: go re-requests a failed
  zip during go get's load phase, which double-recorded stats (the
  report showed the same blocked module twice) and would have
  re-prompted on Confirm verdicts.
- Fetch .info out-of-band on a cooldown cache miss: go serves .info
  from its local module cache on any machine that used go before PMG,
  which silently disabled cooldown. Failure of the side-fetch still
  fails open for cooldown only.
- Move the noop package resolver into packagemanager.

Verified live: cold-cache cooldown block now records once; warm-cache
rerun is blocked via the side-fetch instead of failing open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xuhBeTVpfU4SdqVaarvuK

* docs: collapse Go proxy-mode details by default

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xuhBeTVpfU4SdqVaarvuK

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-03 18:19:31 +05:30
648adcbda4 feat: add uvx (uv tool run) package executor (#357)
* feat(uvx): add uvx (uv tool run) package executor

Adds support for `uvx`, implemented as a PyPI Executor alongside pipx.
uvx is an alias for `uv tool run`: it installs a tool into an ephemeral
environment and runs it, so it has no install/list subcommand and the
first positional argument (or --from) is the package to audit.

Parsing highlights:
- --from overrides the positional command as the package to audit
- --with packages are audited as additional environment dependencies
- name@version shorthand (ruff@0.3.0, ruff@latest) is normalized
- flag parsing stops at the tool name so the tool's own flags are not
  misread as uvx options; uvx's value/boolean flags are registered so
  none greedily consume the package positional
- VCS/URL/local-path specs are skipped for registry auditing

Wires up command registration, analytics, shell alias/shim, cloud audit
mapping, a dedicated `uvx` sandbox profile (UV_*/PIP_* env, uv cache and
tool dirs), config policy, docs, unit tests and an E2E workflow step.

Closes #326

https://claude.ai/code/session_011hyLxq7oWJX5Dp4tCEfG19

* chore(uvx): align docs and base profile with uvx support

Incorporates the low-risk, non-parser improvements from the community
PR #345 (author non-responsive) into our implementation:

- list uvx (and the previously-missing pipx) as PyPI managers in the
  pypi-restrictive base profile package_managers and its README, so the
  base profile applies directly when selected via --sandbox-profile
- document uvx in docs/github-action.md and docs/proxy-mode.md
- add version / IsExplicitVersion assertions to the uvx parser tests

Our pflag-based parser is kept as-is: unlike #345 it audits --with
packages and handles all uvx short flags (e.g. -w), both of which the
community PR misses.

* fix(uvx): skip interpreter requests; use require in tests

Addresses review feedback on PR #357:

- uvx interpreter requests (`uvx python`, `uvx python@3.12`, `uvx pypy`,
  ...) launch an isolated interpreter rather than installing a PyPI tool.
  Treating the positional as a package made the guard flow resolve/analyze
  pkg:pypi/python (and python==3.12), which could wrongly block or fail a
  valid invocation. Skip these for the positional; --with packages on the
  same command are still audited.
- Use require.NoError / require.Len for fatal assertions in the uvx tests,
  matching the repo's testing convention, so a failure stops the subtest
  before a nil dereference instead of panicking.

* docs(uvx): document fail-open and --with-requirements trade-offs

Record the two deliberate parsing decisions raised in review as in-code
trade-off comments (no behavior change):

- unknown flags are tolerated (fail open), consistent with the other
  executors; the residual gap only affects non-proxy guard mode since the
  default proxy flow intercepts every registry download.
- --with-requirements / --with-editable values are consumed but not
  expanded into audit targets; expanding them needs manifest-extractor and
  guard changes, tracked as follow-up. Proxy mode still covers them.

* docs(uvx): drop --with-requirements limitation note

Per maintainer review: guard mode is being deprecated and auditing the
contents of an existing requirements file is a scanner's responsibility,
not PMG's. Remove the "known limitation / follow-up" note; the flags stay
registered only so their values are not mistaken for the tool positional.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-02 18:49:31 +05:30
c47776db27 feat(proxy): persistent proxy server mode (#351)
* refactor(flows): extract SetupCACertificate for reuse

Move the CA load/generate/merge logic out of proxyFlow into an exported
flows.SetupCACertificate so the persistent proxy server can reuse it.

* feat(proxy): add persistent proxy server with start/stop/env/status

Introduces 'pmg proxy' commands backed by internal/proxyserver: a long-lived
MITM proxy that intercepts package managers via env vars (no shims). Supports
--daemon (Unix), --state, --port; generic 'env' output that skips cert vars
when the CA is OS-trusted; opt-in 'stop --fail-on-violation' (fail-closed on
crash) with a synchronous cloud event flush; and the malysis analysis cache.

* feat(action): add server-mode for persistent proxy

When server-mode=true the action starts the proxy daemon and injects proxy
env vars into the job instead of installing shims.

* test(proxy): add persistent proxy server E2E workflow

* docs(readme): document persistent proxy server mode

* fix(proxy): create cache dir before writing state file and daemon log

On a fresh CI runner the cache directory does not exist yet; os.OpenFile and
os.WriteFile do not create parent dirs, so 'pmg proxy start --daemon' failed
with 'no such file or directory'. MkdirAll the parent before writing.

* docs: add persistent proxy server architecture doc

* refactor proxyserver

* fix(proxy): always emit cert env vars instead of skipping on OS-trust status

npm/pip/yarn/requests trust the MITM CA inconsistently across tools, versions,
and configs; many still use bundled CA stores. Always emitting the cert-path
env vars is the conservative choice that works regardless, and is harmless for
tools that read the OS store (they ignore the vars). Skipping them when a
system CA exists would silently break any tool still on a bundled store.

* refactor(proxy): drop redundant audit init in daemon; rely on main.go

main.go's PersistentPreRun already initializes the audit pipeline for every
command (including the daemon's re-exec'd child) and closes it at process exit.
Re-initializing in proxyserver.Run created a second auditor and a second
cloud-sync WAL connection, orphaning the first. Removing it makes the daemon
consistent with the normal proxy flow, which never self-initializes audit.

* fix(proxy): bypass proxy env when flushing events to cloud on stop

pmg proxy stop inherits HTTP(S)_PROXY (injected by 'pmg proxy env') pointing at
the PMG proxy it just shut down. The cloud sync gRPC client honored those vars
and routed api.safedep.io through the dead proxy, failing with 'connection
refused' so no events were delivered. Clear the proxy env vars before the sync
so PMG's own cloud traffic goes direct.

* chore(proxy): address review feedback

- configurable bind host via proxy.server.listen_host (default loopback)
- proxy commands use ui.ErrorExit instead of returning errors to cobra
- rename errcode to ProxyPolicyViolation (covers malware + cooldown)
- share cloud sync via audit.DrainToCloud (de-dup with cmd/cloud/sync)
- centralize proxy CA bundle path in certmanager
- docs: persistent proxy cert trust + bind address

* fix(proxy): show real message on fail-on-violation error

stopExitError set only WithMsg, but ui.ErrorExit renders HumanError, so the
framed error showed 'no human-readable message available'. Set both from one
string, and emit the framed error before the stdout summary so the blocked
count is stated once.

* fix(proxy): flush cloud events from the daemon, not stop

The stop process inherits HTTP_PROXY (from 'pmg proxy env'), so its cloud
client routed api.safedep.io through the already-stopped proxy and failed with
connection refused. Move the flush into the daemon's shutdown, which has no
proxy env (it started before env injection) and dials SafeDep directly.

- daemon flushes on shutdown via audit.DrainToCloud and records the result in
  the state file; stop surfaces it (on both success and fail-on-violation
  paths) since the daemon's own logs aren't visible to stop
- coordinate stop's wait with the daemon shutdown budget; on timeout, error
  out without reading stale state or deleting the file (fail-closed)
- persist blocked count before the flush so the gate stays correct if the
  flush hangs or the daemon is killed mid-flush
- remove now-redundant cloud_flush.go

* disable auto-sync for proxy cmds

* feat(proxy): periodic cloud sync + move proxy env vars to packagemanager

- daemon runs a periodic cloud-sync ticker so the shutdown flush stays small;
  the run total is reported by stop, and shutdown timeouts are coordinated
- move EnvVarForProxy from config to packagemanager (it is package-manager
  knowledge); the shared function now builds the proxy URL and NO_PROXY itself,
  removing the duplicated construction in the per-command and persistent paths
- relocate the #319 yarn and #339 IPv6 regression tests alongside the function
- enable cloud sync in the persistent-proxy E2E workflow and fix the stale
  internal/proxystate path filter

* refactor(proxy): rename cloudFlushLockTimeout to cloudFlushLockWait

Consistent timeout naming: *LockWait is the lock-acquire bound, *Timeout is
the sync-RPC bound. Previously the final-flush pair was cloudFlushLockTimeout
vs cloudFlushTimeout — two lookalike names for different operations.

* refactor(proxy): extract cloudFlush and trim duplicate shutdown comments

The shutdown's final-flush block is now a cloudFlush helper, symmetric with
startCloudSyncLoop (one-shot vs loop). Removed the triplicated ticker/lock
contention comments, keeping the contract on the function doc and one-line
pointers at the call sites.

* docs: update persistent proxy cloud sync to daemon-owned model

The daemon now owns cloud delivery (periodic sync while serving + final flush
on shutdown); stop signals it, waits, and reports the result. Rewrite the Cloud
event sync section, fix stop attributions, add the cloud_sync state field, and
update the sequence diagram.

* docs: move Usage section up below How it works

Put the copy-paste recipes near the top so users find them before the internals.

* refactor(proxy): address PR review feedback

- configurable bind host/port via --host/--port flags + config (listen_host,
  listen_port), bound directly to config fields per PMG's flag pattern
- daemon log path via --log-file and readiness timeout in ProxyDaemonConfig;
  Daemonize no longer owns path policy (caller validates, fails fast)
- gate periodic cloud sync on auto_sync; suppress detached background sync for
  proxy commands instead of flipping the flag
- pmg proxy env --export emits shell-quoted lines for eval (spaces survive)
- extract shared flows.BuildCachedMalysisAnalyzer, dropping the analyzer+cache
  duplication between proxy flow and proxy server
- add internal/proxyserver/doc.go documenting the package + boundary vs flows
- E2E: assert malicious installs are blocked (drop continue-on-error)
- docs: trim Commands/State-file to user contracts; refresh bind address

* refactor(proxy): proactive alignment fixes from whole-PR review

- gate the shutdown cloud flush on auto_sync too, matching the periodic ticker
  (auto_sync consistently controls all daemon-driven cloud delivery)
- ResolveStatePath takes cacheDir instead of *RuntimeConfig, keeping state.go
  free of config dependency
- drop the empty-host comment in listenAddr; keep the loopback guard so a blank
  host never silently binds all interfaces

* fix: Decouple localdb with malysis analyser construction

* fix: Persist global args before proxy server daemon exec

* fix: GitHub Action for cloud auto-sync in server mode

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2026-06-26 11:19:28 +05:30
Abhisek DattaandGitHub d360e75897 feat: Add malysis cache implementation with proxy flow integration (#346)
* feat: Add malysis cache implementation with proxy flow integration

* fix: Code review fixes
2026-06-22 10:12:02 +05:30
327c9c7068 feat(cooldown): respect trusted_packages in dependency cooldown (#342)
* feat(cooldown): respect trusted_packages in dependency cooldown

Trusted packages are now treated as a superset waiver that bypasses every
PMG control (malware analysis, cooldown, and any future controls). A
globally trusted package is automatically exempt from the cooldown window
and no longer needs a duplicate entry in dependency_cooldown.skip.

The skip list remains the narrower, cooldown-only waiver for packages
that must bypass the cooldown wait but still be malware-scanned.

* refactor(cooldown): tag skip reason and audit-log skipped packages

Address review feedback on #342:

- Restore cooldownSkip to a pure single-list function (SRP); the merge
  into trusted_packages now happens in a separate mergeCooldownSkip step,
  driven by the exported CooldownSkip wrapper.
- Extend CooldownSkipInfo with a CooldownSkipReason (TrustedPackage /
  CooldownSkipList) on both SkipAll and per-version entries, so callers
  can tell apart the broad waiver from the cooldown-only one. When both
  lists match the same package, trusted_packages wins.
- Add audit.LogCooldownSkipped and emit it from the npm and PyPI
  interceptors on the SkipAll path, alongside the existing info log,
  carrying the source list as the reason.

* refactor(cooldown): inline list merge, audit per-version exemptions

Address further review feedback:

- Drop the separate mergeCooldownSkip helper; cooldownSkip now writes
  into a shared *CooldownSkipInfo and is called twice from CooldownSkip
  (cooldown skip list first, trusted_packages on top so trusted entries
  override the reason on overlap).
- Audit log every exemption, not just SkipAll: a new auditCooldownSkip
  helper in proxy/interceptors/cooldown.go emits one event per match
  (package-wide or per-version), each tagged with its source list.
  LogCooldownSkipped gains a version argument for the per-version case.
- Cover the trusted_packages reason path in TestCooldownSkip.

* fix(cooldown): avoid double-auditing trusted package exemptions

auditCooldownSkip now only emits EventTypeCooldownSkipped for entries
that came from dependency_cooldown.skip. Trusted-package exemptions
already get an EventTypeInstallTrustedAllowed event at tarball-download
time (proxy/interceptors/base_registry.go), so emitting a cooldown event
for them too would double-count the same waiver.

* emit trusted and cooldown skip events to cloud

* fix tests

* refactor(cooldown): return value from collectCooldownSkip, short-circuit on trusted SkipAll

Address PR review feedback:
- Rename cooldownSkip to collectCooldownSkip and return CooldownSkipInfo
  instead of mutating an input pointer.
- Add mergeCooldownSkip to combine per-list results with trusted_packages
  taking precedence on overlap.
- CooldownSkip now consults trusted_packages first and returns immediately
  on a package-wide trusted exemption (DC skip list cannot add anything).
- Extend tests to cover disjoint pinned entries across both lists and the
  case where DC version-less subsumes a trusted pinned entry.

* fix(audit): address cooldown review feedback

* fix(cooldown): audit cooldown skips at download time with concrete version

Backend rejects PackageVersion messages without a version, and audit logs
should reflect the runtime fact (a specific version was skipped) rather
than the config rule. Move the audit emission from metadata-request
handling to download-request handling, where the concrete version is
known, and require version in LogCooldownSkipped.

* chore(audit): drop dead scope assignment in LogCooldownSkipped

* refactor(cooldown): move skip-list logic into cooldown handlers

Registry interceptors no longer compute CooldownSkip or branch on SkipAll;
they just call HandleMetadataRequest. The npm and pypi cooldown handlers
own the skip lookup, the package-wide exemption short-circuit, and (for
pypi) the canonical-name denormalization. Also align LogCooldownSkipped
with other LogXxx signatures by taking *packagev1.PackageVersion.

* fix: Simplify audit logging for dependency cooldown skip

* refactor: Simplify cooldown handling and maintain separation of concepts for trusted and DC skip packages

* fix: Code review fixes

* fix: Emit cooldown skipped audit event ONLY when an in-window version is skipped

---------

Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
2026-06-21 18:22:15 +05:30
dmdhrumilmistryandGitHub baf637be97 feat(analyzer): analysis cache contract (MalysisCache interface + config) (#334) 2026-06-17 15:07:47 +05:30
61230fbcd7 feat(cooldown): add dependency_cooldown.skip list (per-control exemption) (#328)
Let dependency cooldown respect an explicit skip list so first-party /
internal packages that must be installed the moment they are published
(e.g. to sanity-test a freshly released version) are not held back by the
cooldown window.

Per review, this is a per-control skip list — NOT a second definition of
"trusted package". There remains a single top-level `trusted_packages`
(which waives malware analysis); `dependency_cooldown.skip` waives ONLY
the cooldown wait, so a fast-tracked package is still malware-scanned.

Matching:
- a PURL without a version skips cooldown for all versions of the package
  (package-level) — the metadata passes through unmodified;
- a PURL with a version skips cooldown for that version only — that
  version is preserved during stripping while other recent versions are
  still held.

- config: DependencyCooldownConfig.Skip + CooldownSkip()/CooldownSkipInfo.
- npm/pypi interceptors: bypass on package-level skip; thread per-version
  exemptions into the cooldown stripper so pinned versions survive.
- docs + config template; unit tests for the matcher (package/version
  level, precedence, mismatches) and the skip-vs-trusted independence.

Signed-off-by: dmdhrumilmistry <56185972+dmdhrumilmistry@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:44:55 +05:30
Abhisek DattaandGitHub c7244f921a feat: Add support for environment protection (scrubbing) (#327)
* feat: Add support for environment variable protection for sandbox

* chore: Update dangerous env var list

* fix: Split profiles for improved environment protection

* fix: pipx sandbox profile separation

* chore: Show sandbox scrub info on error exit

* fix: Code review fixes

* test: Add e2e for sandbox environment scrubbing
2026-06-11 11:40:33 +05:30
Abhisek DattaandGitHub 4f0db15ede feat: Add opt-in support for proxy CA cert installation (#318)
* feat: Add opt-in support for proxy CA cert installation

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes

* docs: Add limitation for MacOS MDM script
2026-06-03 23:15:02 +05:30
a651761636 docs: Update README (#296)
* docs: Update README

* docs: Add diagram for how PMG works

* docs: Updated featured docs

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* Update README.md

Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-05-26 16:45:14 +00:00
Abhisek DattaandGitHub b03e82e3f2 feat: Add support for per-project sandbox overlays (#294)
* feat: Add support for per-project sandbox overlays

* chore: Maintain consistency with TUI experience

* fix: Code review fixes
2026-05-26 21:06:57 +05:30
Abhisek DattaandGitHub 0e3bb52f8c fix: Opt-in lockdown for global config (#278)
* fix: Reject overriding managed flags

* fix: Lockdown overrides when global config present

* fix: Opt-in lock-down enforcement for global config

* fix: Code review fixes

* fix: Code review fixes

* fix: Code review fixes
2026-05-21 16:37:26 +05:30
Abhisek DattaandGitHub b8588e3df4 feat: Add Sandbox Inspection and Debugging Commands (#261)
* feat: add sandbox DX commands

* fix: Linter errors

* fix: Sandbox deny log parsing

* fix: Sandbox docs

* refactor: Maintain SSOT across pkg dependencies

* fix: Linter errors
2026-05-19 14:40:54 +05:30
46cc70db53 feat: add GitHub Action for one-step PMG setup in CI (#263)
* feat: add GitHub Action for one-step PMG setup in CI

Composite action at repo root that downloads PMG (with SHA-256 verification
against the upstream checksums.txt), runs `pmg setup install`, and wires
shims onto $GITHUB_PATH so subsequent `npm install` / `pip install` calls
are transparently analyzed.

Defaults are conservative: malware blocking + dependency cooldown + proxy
mode (matching PMG's own defaults). Sandbox is opt-in because enabling
Landlock/Bubblewrap on ubuntu-latest requires relaxing AppArmor
user-namespace restrictions.

Cloud sync uses the documented SAFEDEP_API_KEY / SAFEDEP_TENANT_ID env-var
fallback so we skip the keychain codepath that has no usable backend in
headless CI. When cloud is enabled and no endpoint-id is supplied, the
action sets PMG_CLOUD_ENDPOINT_ID=github-actions/${GITHUB_REPOSITORY} so
events aggregate per repository instead of per ephemeral runner hostname.

Closes #248.

https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh

* fix(action): drop github.repository template from input description

Action manifest validation rejected the action.yml because the endpoint-id
input description contained ${{ github.repository }} — template expressions
aren't evaluated in input description text and trip the validator with
"Unrecognized named-value: 'github'". This caused every job using uses: ./
to fail before any step ran.

Also switch the config-file e2e job to verify the staged file directly
instead of calling `pmg config get`, which is not in the v0.13.0 release
that "latest" resolves to today.

https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh

* fix(action): address review comments on PR #263

- Drop opinionated defaults on PMG_* toggle inputs. All defaults are now
  empty strings, and the action only exports PMG_* env vars when the
  caller explicitly sets the input. Without this, defaults like
  PMG_PARANOID=false silently shadowed config-file overrides because env
  vars beat config.yml in Viper precedence.
- Verify cached PMG against upstream checksums.txt on every cache hit.
  The cached tarball is stored alongside the binary and re-hashed against
  the freshly-fetched checksums.txt; on drift, the cache entry is evicted
  and re-downloaded.
- Export PMG_* env vars BEFORE running `pmg setup install` so settings
  like disable-telemetry actually apply during setup, not just to
  subsequent package-manager calls.
- Add `|| true` to the grep that extracts the expected checksum so
  set -e doesn't kill the script before the friendly error message fires
  when no checksum entry is found.
- Pin third-party actions (actions/checkout, actions/setup-node) to
  commit SHAs to match the repo's supply-chain hardening convention.
- Fix the malicious-package E2E test capturing tee's exit code instead
  of npm's; redirect to a file and check the actual command exit code.
- Add an E2E job that asserts PMG_PARANOID is unset when only
  config-file is provided — regression guard for the precedence fix.

https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh

* ci(action-e2e): scope sandbox tests to action setup, not PMG runtime

The landlock job was running `npm install express` with no explicit
sandbox profile and the default profile blocks something npm needs
(PMG's own e2e uses `--sandbox-profile npm-restrictive` to make this
viable). Bubblewrap happened to pass, but verifying the default sandbox
profile is permissive enough for arbitrary package installs is PMG's
e2e responsibility — this workflow's job is to assert the action wires
sandbox config correctly.

Switch both drivers to a matrix and verify only what the action owns:
PMG_SANDBOX_* env vars propagated, pmg binary runs, bwrap is installed
when requested, AppArmor user-ns restriction relaxed.

https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh

* ci(action-e2e): bump setup-node to 24

Node 20 reached end-of-life and setup-node now warns on it. Match the
version pinned by publish-npm.yml (the repo's newest workflow). Updated
the README and docs/github-action.md quick-start examples to match.

https://claude.ai/code/session_01ARb8ZiBiJjvhWjchBXraAh

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-18 00:44:06 +05:30
Sahil BansalandGitHub c78287e5a0 feat: add pmg config get/set/edit CLI commands (#262) 2026-05-16 09:55:54 +05:30
Abhisek DattaandGitHub 4c42ceca0e feat: Add support for Landlock based Sandbox for Linux (#238)
* feat: Initial implementation of landlock based sandbox driver

* fix: Handle seccom probe failure

* fix: Remove unnecessary seccomp probe

* fix: Use file based policy load

* fix: Keep bpf filter in memory

* fix: Use TSYNC for seccom filter

* fix: Use TSYNC for seccom filter

* fix: Update landlock translator

* fix: Landlock sandbox implementation

* fix: Landlock + seccomp based sandboxing on Linux

* fix: Misc fixes

* fix: Cleanup sandbox files

* fix: Handle mandatory deny API change post merge

* fix: Landlock write access translation

* chore: Fix linter issues

* ci: Use /tmp for npm cache for landlock
2026-05-07 12:42:28 +05:30
Sahil BansalandGitHub d8abfb6c41 fix: flatten proxy skip_commands schema and move docs to proxy-mode.md (#241)
- Replace nested policies map with flat skip_commands map in ProxyConfig
- Make skip_commands dependent on install_only being enabled
- Move proxy configuration docs from proxy.md to proxy-mode.md
- Update config template and tests for new schema
2026-05-06 19:26:39 +05:30
Sahil BansalandGitHub d1dd2560a4 feat: consolidate proxy config into structured section and add support for custom commands to skip proxy (#240)
* feat: add ProxyConfig struct with per-PM skip_commands and legacy fallback

* feat: consolidate proxy config into structured section with backward compat

Replaces flat proxy_mode/proxy_install_only keys with a structured proxy
section supporting per-package-manager skip_commands. Legacy keys are
respected via fallback when user's config lacks the new proxy section.
Removes deprecated experimental_proxy_mode config and flag.

* fix: env var resolution for nested config keys and deduplicate skip command matching

- Add "." to "_" in Viper env key replacer so nested keys like
  sandbox.enabled resolve from PMG_SANDBOX_ENABLED (was silently broken)
- Export IsFirstNonFlagArgInList and remove duplicate from proxy_flow.go
- Add table-driven tests for skip command matching with real-world cases
- Remove redundant env var test

* docs: update proxy configuration and env var documentation

Update config.md env var table to reflect new proxy.enabled and
proxy.install_only keys. Add proxy configuration section to proxy.md
covering config structure, per-PM skip commands, CLI flags, and env vars.

* fix: legacy fallback precedence
2026-05-06 18:27:23 +05:30
d6755d3f44 feat/sandbox allow explicit dangerous pattern override (#239)
* feat(sandbox): allow opt-out of mandatory deny via explicit allow rules

Mandatory deny patterns (.env, .aws, .ssh, .gcloud, .kube, .gnupg,
.docker/config.json, .git/config) can now be opted out by listing the
exact literal post-expansion path in policy filesystem.allow_read /
allow_write, OR via --sandbox-allow read=... / write=... at runtime.
Both channels are treated at par.

Suppression is exact-match. Listing the CWD-absolute or HOME-absolute
form of a dangerous file additionally suppresses its **/<file> glob
sibling on the same direction so a single opt-out is sufficient.
Broad globs (${CWD}/**) and relative paths in user allow lists do not
suppress. The unnamed absolute form remains denied. .git/hooks is
unconditional and never suppressible (arbitrary code execution risk).

GetMandatoryDenyPatterns now returns split DenyRead / DenyWrite
slices and reports SuppressedRead / SuppressedWrite for audit. Both
translators emit per-direction deny rules and log.Warnf each
suppression. On Linux/bubblewrap, the tmpfs hide is restricted to the
intersection of DenyRead and DenyWrite; one-sided suppression falls
back to /dev/null (write) or the user's allow_read --ro-bind (read).
bwrap has no primitive that allows writes while denying reads, so
write-only opt-outs warn that the read-side mandatory deny is
unenforceable.

Updates docs/sandbox.md to document the opt-out, exact-match
semantics, and the Linux platform limitation. Updates pmg-e2e.yml to
create ./.env so the sandbox e2e test exercises the BLOCK case.

Closes #232

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: Code review fixes

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 12:45:36 +05:30
Sahil BansalandGitHub 544b38b278 feat: Add PyPI dependency cooldown support (#221)
* refactor: Extract shared cooldown helpers to package-level functions

* feat: Add PyPI cooldown handler with PEP 691 file parsing

* feat: Add PyPI cooldown file stripping logic

* feat: Implement PyPI cooldown HandleMetadataRequest with PEP 691 filtering

* feat: Wire PyPI cooldown into pypi_registry interceptor

* update headers for no cache

* fix: Strip conditional GET headers to prevent 304 bypass in cooldown handlers

pip and npm clients cache Simple API / registry responses with ETags. On
subsequent requests they send If-None-Match, which causes the server to
return 304 Not Modified with no body. The cooldown response modifier
received an empty body, failed to parse it, and failed-open — letting
the client use its stale cached (unfiltered) response.

Fix: delete If-None-Match and If-Modified-Since from the request before
forwarding, forcing a full 200 response so the modifier always has a
body to filter.

Also removes the Content-Type guard from the PyPI modifier (the empty
Content-Type on 304 responses was a symptom of the same root cause) and
replaces Cache-Control: no-cache with the more targeted header deletion.

* docs: Add PyPI cooldown limitation for pip < 22.3 to dependency-cooldown docs
2026-04-20 21:10:05 +05:30
365deb1897 feat: Add proxy_install_only config to restrict proxy to download commands (#222)
* feat: Add proxy_install_only config to restrict proxy to download commands

Introduces proxy_install_only (default: false) which, when enabled,
skips the proxy for package manager commands that do not download
packages (e.g. npm ls, pip list), avoiding unnecessary MITM overhead.

- Add ProxyInstallOnly to Config and config template
- Add IsKnownDownloadCommand / MayDownloadPackages to ParsedCommand
- Add DownloadCommands to npm and pypi PM configs covering update,
  ci, audit, dlx, exec, x, download, run and equivalents per PM
- Extract shared runner.Execute used by both proxy flow and guard
- Proxy flow short-circuits to runner.Execute for non-download commands
  when proxy_install_only=true

* refactor: Inject CommandExecutor into guard to fix dependency direction

guard depended on internal/runner, which inverted the intended layer
hierarchy. Now guard defines a CommandExecutor function type and accepts
it as a constructor argument. internal/flows (the composition root)
creates the executor closure wrapping runner.Execute and injects it,
keeping guard free of internal/ dependencies.

* refactor: Invert proxy_install_only logic to use known non-download commands

Replace the DownloadCommands allowlist (opt-in, fail-open) with a
NonDownloadCommands denylist (opt-out, fail-safe). The proxy now runs
for all commands except those explicitly known to not download packages.
Unknown or future package manager subcommands default to running with
the proxy.

Includes script runners (run, start, test, stop, restart) that can spin
up local servers — setting proxy env vars on these breaks them without
providing any security benefit. Also covers removal commands and local
operations that never contact the registry.

* fix: Support PMG_* env vars regardless of config file state

AutomaticEnv only resolves env vars for keys Viper already knows about
via AllKeys(). When a key is absent from the config file (commented out,
new key added after last setup, or no config file at all), Viper had no
knowledge of it and silently skipped the env var.

Fix by registering all Config struct fields as Viper defaults via
reflection (using mapstructure tags) before reading the config file.
This ensures PMG_* env vars work in all cases.

Precedence: cobra flags > env vars > config file > defaults.
SetDefault is used (not Set) so env vars and config file can still
override the Go defaults freely.

Tests added covering all precedence levels including the key-absent-
from-config-file case that was the original bug report.

* fix: Only check first non-flag arg against NonDownloadCommands

Scanning all args caused false proxy bypasses when package names or
script arguments matched a NonDownloadCommands entry. For example:
- npm exec test → "test" matched, proxy incorrectly skipped
- npm update config → "config" matched, proxy skipped
- npm publish --tag version → "version" matched, proxy skipped

Fix by checking only the first non-flag argument (the subcommand).
If it is not in NonDownloadCommands we break immediately, so trailing
args never influence the classification. Applied to all four parsers:
npm, pip/pip3, uv, and poetry.

Regression tests added for the false positive cases.

* refactor: Replace reflection-based Viper defaults with embedded template

Load the embedded config template as the Viper base so all keys are
registered upfront, enabling PMG_* env vars to work regardless of
whether a key exists in the user's config file.

* fix: Restore trusted_packages template entry and revert DefaultConfig change

* docs: Document environment variable overrides for config keys

* update npm test cmd

* refactor: extract shared non-download command detection helper

Replaces duplicated first-non-flag-arg detection loops in npm.go and
pypi.go (pip + poetry parsers) with a shared isFirstNonFlagArgInList
helper in packagemanager.go.

https://claude.ai/code/session_01AHaKF3vc2Haj9tK3jgUBAs

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-17 01:13:30 +05:30
a128a60982 docs: Add dependency cooldown docs (#209)
* docs: Add dependency cooldown docs

* fix: Remove unnecessary params

* Update docs/dependency-cooldown.md

Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* fix: Cooldown guarantees

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-04-08 17:19:30 +00:00
Abhisek DattaandGitHub 2bb5dd3778 fix: Allow explicit override over deny patterns (#177)
* fix: Allow explicit override over deny patterns

* test: Non-glob expansion for overrides is expected
2026-03-06 10:10:15 +05:30
Abhisek DattaandGitHub 8cba52201c feat: Add config support for UI verbosity level (#175)
* feat: Add config support for UI verbosity level

* docs: Add verbosity info in UI doc
2026-03-04 13:32:34 +05:30
Abhisek DattaandGitHub 6074080219 feat: Add support for sandbox allow override (#165)
* feat: Add support for sandbox allow override

* fix: Main should fail on arg processing error

* fix: Remove redundant policy conflict check
2026-02-16 13:58:08 +05:30
Sahil BansalandGitHub f1891271c1 Add proxy support for pypi package managers (#150)
* initial pypi registry implementation

* support proxy mode for pypi package managers

* support proxy mode for pypi package managers - 2

* rm default mode as proxy for pip3

* update goproxy version & fix pypi proxy failing on 304

* add PIP_RETRIES=0 env

* update pmg e2e & add proxy mode e2e for pypi

* rm safedep-test-pkg for pypi proxy e2e
2026-02-05 13:30:27 +05:30
Abhisek DattaandGitHub b5696f989f chore: Proxy mode without experimental tag (#147)
* chore: Proxy mode without experimental tag

* fix: Update proxy mode docs

* fix: Code review fixes
2026-02-02 12:58:28 +05:30
Sahil BansalandGitHub b332e1d6d4 update setup install cmd info (#143)
* update setup install cmd info

* update demo

* add doc comment
2026-01-27 21:24:08 +05:30
Abhisek DattaandGitHub be63fdd6ea fix: Remove Emoji from Setup (#142)
* fix: Remove emoji from setup

* fix: Update README demo
2026-01-27 20:08:23 +05:30
Sahil BansalandGitHub 224658e6d2 Update PMG banner (#138)
* update pmg banner

* rm width

* trial: rm lines

* trial: add line above demo

* trial: add thin line above demo

* trial: add thin line above demo

* trial: add thin line below demo

* trial: rm lines

* trial: add br

* trial: replace images with badges

* trial: replace h1 with h3

* trial: increase pmg height

* revert back to h1

* trial: change theme for demo

* trial: use lighter bg for demo

* trial: use lighter bg for demo

* trial: use lighter bg for demo & rm extra div

* revert demo back to original

* add private package limitation in non-proxy mode

* update demos
2026-01-27 19:39:36 +05:30
Sahil BansalandGitHub 0aa82033a5 fix proxy mode failing for GH private packages (#137)
* fix proxy mode failing for GH private packages

* skip analysis for private packages for proxy mode

* introduce npmRegistryConfig and support for handling multiple parsers in future

* refactor name and unexport npm config functions

* rm unused function

* rename & unexport npmRegistryURLParser

* add e2e for malicious pkg blocked using proxy mode
2026-01-23 18:38:54 +05:30
aa5c528a9d docs: Update README (#133)
* docs: Update README

* docs: Update README

* docs: Update README

* docs: Update README

* docs: Update README

* Apply suggestion from @Sahilb315

Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-01-21 20:29:15 +05:30
edfdd543e0 chore: README update demo and Error Fix (#126)
* docs: Update README with demo gif

* fix: Proxy remove dependency on interaction

* fix: Update demo gif width

* Update docs/demo/pmg-intro.tape

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* fix: PMG demo

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-18 16:00:37 +05:30
Abhisek DattaandGitHub 80a1747e3e feat: Add support for Linux Sandbox using Bubblewrap (#120)
* feat: Add support for bubblewrap sandbox

* fix: Glob pattern expansion limit for linux

* fix: Bug in glob pattern expansion for bwrap

* fix: README on trust

* fix: Multiple bubblewrap translator fix

* test: Add E2E for linux sandbox

* fix: Refactor bwrap sandbox to use common dangerous files

* fix: Path test case

* fix: Non-existent path handling bug

* refactor: Misc cleanup

* fix: Avoid bind mount for non-existentent deny protection

* fix: Off by one bug in path depth handling

* ci: Disable AppArmor on GHA runner

* fix: Disable apparmor userns restrictions
2026-01-15 20:12:12 +05:30
b97a4c2ee5 docs: Add trust doc (#118)
* docs: Add trust doc

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 15:02:08 +05:30
2e1f5b1a36 feat: Add support for policy inheritence (#113)
* feat: Add support for policy inheritence

* fix: Linter fixes

* Update docs/sandbox.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* fix: Handle boolean inheritence

* ci: Add linter

* Update sandbox/policy_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* fix: Linter fixes

* fix: Linter fixes

* fix: Sandbox rule regex format

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-14 10:50:39 +05:30
9693428171 feat: Experimental Sandbox Support (#101)
* feat: Sandbox implementation with seatbelt

* refactor: Remove concept of PM_CACHE

* fix: Misc fixes

* refactor: Sandbox for separation of boundaries

* fix: Apply API

* fix: Add support for sandbox cleanup

* test: Add variable interpolation test

* fix: Misc cleanup fixes

* chore: Cleanup sandbox registry

* chore: Cleanup sandbox policy

* chore: Cleanup sandbox

* fix: Misc cleanup fixes

* fix: Remove violation mode

* fix: Update config template

* chore: Go mod cleanup

* fix: Handle the case when package manager policy is explicitly disabled

* fix: Sandbox executor

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>

* test: Remove unused var

* test: Add test for seatbelt sandbox driver

* fix: Sandbox profile loader from file should use path for caching

* test: Add policy test

* feat: Add support for config templates

* fix: Seatbelt translator handle glob

* fix: Merge conflicts

* fix: Fix sandbox policy generator for MacOS min permissions

* fix: Sandbox path handling bugs

* fix: Deny read to dangerous directories

* fix: Deny read to dangerous directories

* add sandbox e2e (#112)

* fix: Sandbox E2E test

* fix: Code review fixes

* fix: Code review fixes

* doc: Add sandbox debugging guide

* doc: Update sandbox doc

* docs: Add sandbox usage doc

* fix: Use better error for sandbox without policy

* fix: Add sandbox for npx

* fix: Enable PTY for npm

---------

Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-01-13 14:52:02 +05:30
Sahil BansalandGitHub ca224f523c enable support for proxy mode for npm package managers (#104)
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-01-10 00:24:45 +05:30
31f23fd065 Add support for package executors and support for PTY handling (#100)
* define contract for package executors

* introduce npx executor

* add npx and pnpx cmd support

* fix typo

* rm PackageExecutor and depend on PackageManager interface

* add support for PTY to handle parent-child process interaction

* refactor PTY handling in proxy flow

* enforce interactiveSession interface check

* close reader explicitly and clean npm version for pkg executors

* rm interaction from interceptors

* add docs and wait for outputRouter before exit

* add support for non interactive TTY for proxy mode

* add support for CI env var check for non interactive tty proxy mode

* update readme to include npx, pnpx support

* Update internal/flows/proxy_flow.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* update ptyx lib

* fix docs typo

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-09 22:03:42 +05:30
Abhisek DattaandGitHub a373b5b243 docs: Add doc for trusted packages and proxy mode (#102) 2026-01-09 13:56:48 +00:00
Abhisek DattaandGitHub 21eb05373f feat: Add Support for Proxy with Interceptor (#77) 2025-12-10 08:34:28 +05:30
2be1e5f009 Update go version to 1.25 & Add steps for introducing new package manager (#78)
* upgrade go version to 1.25.1

* introduce doc for steps for creating a new pkg manager

* Update docs/package-manager.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* Update docs/package-manager.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* Update docs/package-manager.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 09:36:32 +05:30
4031219375 fix: Show error messages on fatal failures #32 (#34)
* fix: Show error messages on fatal failures #32

* test: Add E2E test

* test: fix E2E scripts

* test: fix E2E scripts

* fix: Race condition in concurrent analyzer

* fix: update formatting to ensure docs URL is clickable

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>

* fix: E2E test

---------

Signed-off-by: Sahil Bansal <bansalsahil315@gmail.com>
Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2025-05-17 21:54:47 +05:30
Sudhanshu DasguptaandGitHub 6c7e3b875a Add new intro image (#24)
* add new into image

Signed-off-by: Sudhanshu Dasgupta <dasguptashivam23@gmail.com>

* replace image

Signed-off-by: Sudhanshu Dasgupta <dasguptashivam23@gmail.com>

---------

Signed-off-by: Sudhanshu Dasgupta <dasguptashivam23@gmail.com>
2025-05-15 20:16:03 +05:30
Abhisek DattaandGitHub 5022d64ce4 chore: Improve UX (#22) 2025-05-15 18:03:46 +05:30