Evan JarrettandClaude Opus 5 a63f668de0 scanner: fix five crash and halt classes found by a pipeline audit
An audit of the scan pipeline and the hold side of scanning found several
ways scanning stops without saying so. Each fix here was written test-first:
a test expressing the wanted behaviour, confirmed failing for the right
reason, then the change.

A summary-less result crash-looped both processes. worker.go dereferenced
result.Summary unconditionally, but processJob only sets it when Grype runs,
and SendResult puts the nil on the wire before the scanner dies on it, so
handleResult's unguarded log killed the hold too. A nil Summary now means
"not scanned for vulnerabilities", deliberately distinct from "scanned, found
zero" — inventing a zeroed summary would report every image as clean when
Grype never ran. The hold writes a record rather than orphaning the uploaded
SBOM, and the appview renders an "SBOM only" state instead of a green Clean
badge.

The Grype database could wedge with no way back short of a restart. All three
throttles in loadVulnDatabase were guarded by vulnDB != nil, so a scanner
holding no provider retried a full download on every scan under the exclusive
lock. Two earlier attempts at this bug each added one more condition to the
same chain; this replaces the chain with a single decision function over a
state snapshot, consulted by both call sites so they cannot disagree. That
disagreement was itself a bug: the 50-scan reload had never once executed.

Two independent halts. An unparseable frame was dropped in silence, stranding
a row that held the hold's only dispatch slot forever; it is now answered
"skipped" on first delivery. The 10-minute sweep leaked the in-flight digest
and wrote no record, permanently retiring one image per timeout.

A digest went unvalidated into filepath.Join and os.Create, so a layer digest
of sha256:../../../x wrote outside the scan directory, and nothing verified
that downloaded bytes hashed to the digest naming them. Digests come from
records in a user's own PDS. Both are fixed together: verification is what
makes an escaping write self-defeating.

Concurrency did not work on either axis. The proactive capacity gate was
depth-one hold-wide, so neither extra workers nor extra scanner processes
received work. Depth is now the sum of the worker counts scanners advertise on
connect, the gate is scoped to proactive work, and dispatch prefers the
least-loaded scanner. Disconnects no longer hand a running scan to someone
else: a scanner keeps a stable per-process identity and reclaims its own rows
within a grace window, while a process that truly restarted returns with a new
identity and has its work reclaimed, which is correct because the restart did
lose it.

The hold's scanning deadline measured queueing rather than scanning, because
the scanner acks on receipt and handleAck never refreshed assigned_at. A new
"started" message, sent by the worker that dequeues the job, separates the two
budgets. An older scanner never sends it and falls under the queueing budget,
which is more forgiving than the deadline it gets today.

Adds an in-process mock hold and an e2e harness that runs the real client,
queue and worker pool, seeded with 84 real manifest records fetched from a
live PDS. Real image layouts and the Grype database are fetched by scripts and
gitignored; suites needing them skip cleanly, so the default run stays offline
and fast.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U1Km3N3uUmeGaj7VbaM8PF
2026-09-05 15:01:10 -05:00
2026-05-03 14:51:05 -05:00
2026-01-18 17:44:15 -06:00
2026-01-06 23:56:17 -06:00
2026-05-09 21:21:20 -05:00
2026-06-14 04:02:43 +03:00
2025-11-02 22:11:19 -06:00
2026-04-23 10:38:55 -05:00

ATCR - ATProto Container Registry

https://atcr.io

An OCI-compliant container registry that uses the AT Protocol for manifest storage and S3 for blob storage.

What is ATCR?

ATCR integrates container registries with the AT Protocol ecosystem. Container image manifests are stored as ATProto records in your Personal Data Server (PDS), while layers are stored in S3-compatible storage.

Image names use your ATProto identity:

atcr.io/alice.bsky.social/myapp:latest
atcr.io/did:plc:xyz123/myapp:latest

Architecture

Three components:

  1. AppView - Registry API + web UI

    • Serves OCI Distribution API (Docker push/pull)
    • Resolves handles/DIDs to PDS endpoints
    • Routes manifests to user's PDS, blobs to hold services
    • Web interface for browsing/search
  2. Hold Service - Storage service with embedded PDS (optional BYOS)

    • Each hold has a full ATProto PDS for access control (captain + crew records)
    • Identified by did:web (e.g., did:web:hold01.atcr.io)
    • Generates presigned URLs for S3/Storj/Minio/etc.
    • Users can deploy their own storage and control access via crew membership
  3. Credential Helper - Client authentication

    • ATProto OAuth (DPoP handled transparently)
    • Automatic authentication on first push/pull

Storage model:

  • Manifests → ATProto records in user's PDS (small JSON, includes holdDid reference)
  • Blobs → Hold services via XRPC multipart upload (large binaries, stored in S3/etc.)
  • AppView uses service tokens to communicate with holds on behalf of users

Features

  • OCI-compliant - Works with Docker, containerd, podman
  • Decentralized - You own your manifest data via your PDS
  • ATProto OAuth - Secure authentication (DPoP-compliant)
  • BYOS - Deploy your own storage service
  • Web UI - Browse, search, star repositories
  • Multi-backend - S3, Storj, Minio, Azure, GCS, filesystem

Quick Start

Using the Registry

1. Install credential helper:

curl -fsSL https://atcr.io/static/install.sh | bash

2. Configure Docker (add to ~/.docker/config.json):

{
  "credHelpers": {
    "atcr.io": "atcr"
  }
}

3. Push/pull images:

docker tag myapp:latest atcr.io/yourhandle/myapp:latest
docker push atcr.io/yourhandle/myapp:latest  # Authenticates automatically
docker pull atcr.io/yourhandle/myapp:latest

See INSTALLATION.md for detailed installation instructions.

Running Your Own AppView

# Build
go build -o bin/atcr-appview ./cmd/appview

# Generate a config file with all defaults
./bin/atcr-appview config init config-appview.yaml
# Edit config-appview.yaml — set server.default_hold_did at minimum

# Run
./bin/atcr-appview serve --config config-appview.yaml

Using Docker:

docker build -f Dockerfile.appview -t atcr-appview:latest .
docker run -d -p 5000:5000 \
  -v ./config-appview.yaml:/config.yaml:ro \
  -v atcr-data:/var/lib/atcr \
  atcr-appview:latest serve --config /config.yaml

See deploy/README.md for production deployment.

Running Your Own Hold (BYOS Storage)

See docs/hold.md for deploying your own storage backend.

Development

Building from Source

# Build all binaries
go build -o bin/atcr-appview ./cmd/appview
go build -o bin/atcr-hold ./cmd/hold
go build -o bin/docker-credential-atcr ./cmd/credential-helper

# Run tests
go test ./...
go test -race ./...

Project Structure

cmd/
├── appview/           # Registry server + web UI
├── hold/              # Storage service (BYOS)
├── credential-helper/ # Docker credential helper
├── oauth-helper/      # OAuth debug tool
├── healthcheck/       # HTTP health check (for Docker)
├── db-migrate/        # SQLite → libsql migration
├── usage-report/      # Hold storage usage report
├── record-query/      # Query ATProto relay by collection
└── s3-test/           # S3 connectivity test

pkg/
├── appview/
│   ├── db/            # SQLite database (migrations, queries, stores)
│   ├── handlers/      # HTTP handlers (home, repo, search, auth, settings)
│   ├── holdhealth/    # Hold service health checker
│   ├── jetstream/     # ATProto Jetstream consumer
│   ├── middleware/    # Auth & registry middleware
│   ├── ogcard/        # OpenGraph image generation
│   ├── readme/        # Repository README fetcher
│   ├── routes/        # HTTP route registration
│   ├── storage/       # Storage routing (blob proxy, manifest store)
│   ├── public/        # Static assets (JS, CSS, install scripts)
│   └── templates/     # HTML templates
├── atproto/           # ATProto client, records, manifest/tag stores
├── auth/
│   ├── oauth/         # OAuth client, refresher, storage
│   ├── token/         # JWT issuer, validator, claims
│   └── holdlocal/     # Local hold authorization
├── config/            # Config marshaling (commented YAML)
├── hold/
│   ├── admin/         # Admin web UI
│   ├── billing/       # Stripe billing integration
│   ├── db/            # Vendored carstore (go-libsql)
│   ├── gc/            # Garbage collection
│   ├── oci/           # OCI upload endpoints
│   ├── pds/           # Embedded PDS (DID, captain, crew, stats, scans)
│   └── quota/         # Storage quotas
├── logging/           # Structured logging + remote shipping
└── s3/                # S3 client utilities

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.

S
Description
No description provided
Readme
136 MiB
Languages
Go 88.6%
HTML 6.1%
JavaScript 3.1%
CSS 0.9%
Shell 0.7%
Other 0.6%