dumbtv
dumbtv가 ADB로 Fire TV에서 광고·추적·업데이트 알림을 제거, 루트·앱 변경 없이, 무료 오픈소스
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
This repository is a workspace of four packages, not one package with a
misleading name: @trinoris/securelib (the shared, dependency-free core
that actually holds keys), @trinoris/securegit (the Git integration most
users will actually run), and @trinoris/securelib-piv/-fido2 (hardware
key-provider transports, verified against real YubiKeys). See
Packages below for what each one does and its individual
coverage/audit status.README 전문 보기 ↗@trinoris/secure workspaceThis repository is a workspace of four packages, not one package with a
misleading name: @trinoris/securelib (the shared, dependency-free core
that actually holds keys), @trinoris/securegit (the Git integration most
users will actually run), and @trinoris/securelib-piv/-fido2 (hardware
key-provider transports, verified against real YubiKeys). See
Packages below for what each one does and its individual
coverage/audit status.
@trinoris/securegit is the flagship consumer, and what the rest of this
README mostly talks about: client-side Git encryption via a transparent
clean/smudge filter that encrypts selected files on your own
workstation, so the repository — every commit, every push, every mirror,
every backup — is AES-256-GCM ciphertext everywhere it goes afterward.
The boundary is the process, not the network. Plaintext exists in the working tree, on a machine that holds a key.
.git/objects, the remote, the mirror, the backup and the bundle hold ciphertext, and no cloud provider ever holds anything that can decrypt it.
worktree object database remote
──────── ─────────────── ──────
plaintext ──── clean ─────────▶ ciphertext ──── push ────▶ ciphertext
plaintext ◀─── smudge ──────── ciphertext ◀─── fetch ─── ciphertext
textconv ──────▶ plaintext, for display only
Day to day, nothing about the workflow changes:
git add . && git commit -m "hello" && git push
git add, git status, git diff, git log -p all behave normally — the
filter is invisible until you go looking for it.
Outside docs/, README.md, LICENSE, .github/workflows/,
.github/actions/, every AI-agent instruction file (.claude/,
.github/copilot-instructions.md, AGENTS.md, .cursor/,
.windsurfrules, .clinerules, GEMINI.md — an agent has to be able
to read one of these before it can decrypt anything else, same
bootstrap reasoning as the workflows; see
"Working on this repo with an AI coding agent"
below), and the package manifests (package.json,
package-lock.json — kept plaintext so Dependabot and a plain npm ci
still work), every file in this repo is real securegit ciphertext at
rest — the same clean/smudge filter, the same AEAD envelope, this tool
produces for anyone else's repo. git log -p on a source file shows
exactly the ciphertext diagram above.
The decrypt passphrase is published in secret-pass-phrase.txt,
in the clear, on purpose: this is the open-source tool itself, so hiding
its own source from the security researchers and users it asks to trust
would defeat the point of being open source. Publishing the key proves
the mechanism works end to end — real ciphertext at rest, real decrypt on
checkout — without costing anyone the ability to read or audit the
source. CI (.github/actions/decrypt-securegit) unlocks with that same
published passphrase before every build/test/scan/publish step.
These seven agent-instruction files are hand-authored for this repo
specifically — they are a separate, unrelated thing from
securegit agent install (spec 17),
the CLI feature that writes generic securegit guidance into someone
else's real repo. The two must never be conflated: this repo's own
files correctly say "the passphrase is published in the clear, on
purpose" (true only here, for open-source dogfooding reasons); agent install's generic output correctly says the opposite — "never expose
your passphrase" (true for every real consumer repo, including yours).
Where both want the same conventional path (AGENTS.md, GEMINI.md —
Codex CLI and Gemini CLI only ever look at the repo root, no subpath
option), agent install already refuses to touch this repo's own
hand-authored file (no managed by: securegit agent install marker on
it) unless --force is passed — never pass --force here, it would
silently replace this repo's own decrypt-critical instructions with
generic advice that doesn't mention this repo's passphrase at all.
A plain git clone gets you ciphertext for everything outside the
plaintext-excluded paths listed above — expected, not a bug. Five
commands turn that into the real source, using the same
@trinoris/securegit this repo builds:
npm install -g @trinoris/securegit # or use an existing checkout's own build
git clone https://github.com/trinoris/secure.git && cd secure
securegit install
SECUREGIT_RECOVERY_CODE="$(tail -1 recovery-code.txt)" SECUREGIT_PASSPHRASE="$(tail -1 secret-pass-phrase.txt)" \
securegit key import-recovery --in trinoris-secure.recovery.txt
SECUREGIT_PASSPHRASE="$(tail -1 secret-pass-phrase.txt)" securegit unlock
securegit unlock alone is not enough on a fresh machine — this was
a real bug, caught on a real CI run. unlock only ever decrypts a
local keyring at ~/.securegit/repos/<repoId>/keyring.json, and that
file is never committed to Git by design (the whole point of
client-side key management). It exists only on whichever machine
originally ran securegit init — anywhere else, unlock fails with
"no keyring found for this repository" no matter how correct the
passphrase is. securegit key import-recovery is what actually
rebuilds a keyring from nothing, using the committed
trinoris-secure.recovery.txt and the
published recovery-code.txt — safe to run even
on a machine that already has a keyring (it just adds an equivalent
copy), so it's the step to always run rather than something to guess
whether you need.
One more step actually re-materializes the plaintext: Git's own
stat-cache assumes a file already checked out as ciphertext "matches
the index" and skips re-running smudge even right after unlock.
git rm --cached -r -q . && git checkout HEAD -- .
Every agent below should follow its own file instead of improvising the
checkout+unlock sequence from memory — each names both real mistakes
caught the hard way on this exact repo (cat instead of tail -1 on
the passphrase file; securegit unlock alone silently failing on any
machine that isn't the one that ran init, since import-recovery
is what actually bootstraps a fresh keyring), and each verifies real
plaintext came back rather than trusting a clean exit code:
| Tool | File |
|---|---|
| Claude Code | .claude/skills/checkout-and-unlock |
| GitHub Copilot (Chat + coding agent, read automatically) | .github/copilot-instructions.md |
| Cursor | .cursor/rules/securegit-checkout.mdc |
| Windsurf | .windsurfrules |
| Cline | .clinerules |
| Gemini CLI | GEMINI.md |
| Everything else (Aider, OpenAI Codex, JetBrains Junie, ...) | AGENTS.md — the agents.md cross-tool convention |
From here it's an ordinary working tree — cat, open in an editor,
git diff, git log -p, all show real plaintext. This is exactly
what .github/actions/decrypt-securegit automates for CI.
trinoris-secure.recovery.txt and recovery-code.txt
are not a rare fallback — they're the mechanism every checkout above
actually depends on, since securegit unlock alone only works on a
machine that already has this repo's keyring. They also happen to
dogfood the real "every original keyring is gone" recovery scenario,
since rebuilding from nothing is exactly what import-recovery does
either way. scripts/recovery-scenario-demo.sh
proves it for real — a fresh keyring, rebuilt from only the committed
recovery file and the published code, decrypts real repo content
byte-for-byte identical to the original. Run it yourself:
./scripts/recovery-scenario-demo.sh
| Package | Purpose | Coverage | Audit |
|---|---|---|---|
@trinoris/securelib | The shared core: envelope format, key derivation, the KeyProvider port, multi-recipient sharing, rotation and recovery-code primitives, identity keypairs. Zero runtime dependencies — the package that actually holds keys. | ||
@trinoris/securegit | The Git integration: the clean/smudge/textconv/merge filter, .gitattributes handling, the CLI, verify's history-walking, the chaos sandbox. From a user's perspective this is "securegit". | ||
@trinoris/securelib-piv | A real KeyProvider transport for YubiKey/PIV smartcards — built and verified against physical hardware. Shells out to already-installed system tools (ykman, OpenSC's pkcs11-tool) rather than a native PC/SC addon. | ||
@trinoris/securelib-fido2 |
securelib-piv and securelib-fido2 are peer dependents of securelib,
loaded by it at runtime via a dynamic-import registry
(06-key-provider-port.md) —
neither is a build-time dependency of securegit itself. See
docs/securegit/01-architecture.md for
the full reasoning behind the split. Coverage and audit badges above are
per-package (each package has its own vitest.config.ts branch-coverage
threshold and is audited independently — see Development);
a single static analysis pass (CodeQL, badge above) covers every package's
TypeScript in one run, since CodeQL has no notion of workspace boundaries.
All badges refresh nightly (and on manual dispatch), same cadence as the
chaos sandbox site they're published alongside.
Leaked credentials in version control are a well-documented, recurring
cause of real breaches — not a hypothetical. A committed .env file, a
database connection string, an API key in a config file: the moment it's
pushed, it's in every clone, every fork, every CI runner's cache, and
every backup snapshot that's already run. Rewriting history afterward
rarely reaches all of them, and by then the secret itself has to be
treated as burned regardless.
What client-side encryption actually buys, concretely:
key rotate moves the repository onto a generation a departed
contractor's or employee's key cannot unwrap for anything written
afterward — see
specs/securegit/09-rotation-recovery.md.
It does not (and cannot) erase what they already read; nothing can.
But it stops the leak from continuing, which is the part every other
"revoke access" story in a plain Git repo can't actually deliver.git add, git commit,
git push, git diff, git log -p all behave exactly as before. A
security control that depends on people remembering an extra step
under deadline pressure is a control that eventually gets skipped —
this one doesn't ask for that.The adoption cost is securegit init and a few minutes; the incident
this prevents is measured in days of response, forced credential
rotation across every system the secret touched, and — depending on
what leaked — mandatory disclosure. And unlike almost every other
control on this list, it works before the first plaintext byte ever
leaves the workstation, not after something has already gone wrong.
None of this is only asserted — docs/securegit/03-chaos-sandbox.md is a live, adversarial simulation that measures it against real Git, real attacks, and three real-world code-review workflows compared side by side, with results published from an actual run every night.
securegit init
securegit install
securegit protect config/production.json
git add . && git commit -m "hello" && git push
A clone can be fetched without a key. Protected files remain ciphertext until you install SecureGit in that clone and obtain authorized key access. Git hosting never receives the plaintext.
git clone <repository-url>
cd <repository-directory>
securegit install
Choose the access path that applies to you:
For example, you are cloning on a machine where you previously set up SecureGit for this repository:
securegit unlock
Ask an existing repository member to grant recipient access. First create an identity on your machine and send its public key to that member:
securegit identity init
securegit identity show
The existing member adds that public key and pushes the recipient update:
securegit key add-recipient <your-public-key> --label <your-name>
git status --short
git add .securegit/recipients/<your-fingerprint>.json
git commit -m "Add SecureGit repository recipient"
git push
Fetch the update, then unlock with your identity:
git pull
securegit unlock
Obtain the recovery export and its code through separate private channels. Then import the export; SecureGit will prompt for the recovery code and a new local passphrase:
securegit key import-recovery --in /safe/location/repository.recovery.txt
securegit unlock
If this clone was checked out before SecureGit was installed and unlocked, Git may consider its ciphertext checkout current and skip the smudge filter. On a fresh clone with no local changes, refresh the checkout once:
git rm --cached -r -q . && git checkout HEAD -- .
Confirm the result before editing:
securegit verify
git status --short
Afterward, use normal Git commands. Keep the private keyring and recovery material out of the repository; share only your public identity key when requesting recipient access. For the complete setup and migration guide, see HOWTO.md and the CLI guide.
After the first npmjs.com release, install the SecureGit CLI with:
npm install --global @trinoris/securegit
Until then, or to build directly from this repository:
git clone [email protected]:trinoris/secure.git
cd secure
npm ci
npm run build
npm link --workspace=packages/securegit # puts `securegit` on your PATH,
# or run node
# packages/securegit/dist/bin/securegit.js
# directly
Once a v* tag is pushed, the release workflow runs the guarded
scripts/release.sh process. It validates package manifests and
the lockfile, installs with lifecycle scripts disabled, audits dependencies, builds and
type-checks every package, runs the full unit, coverage, and integration suites, and then
publishes only package versions not already present in the npm registry. The Git tag must
match SecureGit's version when one is supplied; direct runs can omit the tag and use the
versions in each package manifest. securelib and the two hardware-provider packages can
advance independently. The release publishes each package's manifest version only if that
exact version is not already present. npm run release:dry runs the same gates and
rehearses publication without publishing. Scoped packages are published with public
visibility. Public registry lookups need no credential. GitHub Actions publishes through
npm trusted publishing (OIDC), with no npm token secret; set up the trusted publisher for
each npm package using organization trinoris, repository secure, and workflow
release.yml, allowing direct npm publish. For a package's first npmjs.com release,
publish it manually, then add the trusted publisher in that package's npm settings; later
Actions releases use OIDC without an npm token. The release runner uses Node 24 with npm
11.5.1 or later for OIDC support. For local releases, authenticate with npm login --registry https://registry.npmjs.org or put NODE_AUTH_TOKEN in the ignored .env file. The script
uses a temporary npm config for that token and removes it on exit. GITHUB_TOKEN remains limited to
the GitHub Release and the CI source-decryption bootstrap. That bootstrap intentionally
installs the previously published SecureGit CLI from GitHub Packages: CI needs a prebuilt
CLI to decrypt the source before it can build the current checkout. This is separate from
the public npmjs.com releases.
git-crypt / SOPS / ageThe mechanism — a Git clean/smudge filter — isn't novel. What this bets on is what sits behind it: no single custodial party can ever be the sole decryption path, and key lifecycle (losing a machine, adding one, rotating a compromised generation) works without ever touching already-committed ciphertext. Zero runtime dependencies, on purpose — a package that holds encryption keys is an unusually attractive place for a supply-chain attack. The full comparison and reasoning: specs/securegit/README.md.
@trinoris/securelib core, @trinoris/securegit as a
consumer of it, and the hardware-verified @trinoris/securelib-piv/
-fido2 provider packages.A Docker Compose stack simulates real collaborators, a hostile pusher, a
file-corrupting "virus", and infrastructure faults (kills, disk pressure,
network drops) running concurrently against a shared remote — across
three different git workflows (direct push, a gated shared branch, or a
fully PR-gated master), each independently with and without commit
signing enforced (six modes total) — then audits three hard invariants:
no plaintext ever leaked, the repository's object graph stayed intact,
and zero confirmed-pushed data was lost. docs/securegit/03-chaos-sandbox.md has the
full story, including what real runs actually found.
npm run chaos:sandbox
See chaos/README.md for prerequisites and exact
commands. A nightly run of all six modes is published as a GitHub Pages
site (.github/workflows/build-ci.yml's chaos job) — the latest
comparison is viewable at https://trinoris.github.io/secure/.
npm ci
npm run build # tsc -p tsconfig.build.json, in dependency order
npm test # unit tests (vitest)
npm run test:coverage # unit tests + each package's branch-coverage gate
npm run test:integration # against a real git binary
npm run typecheck
build-ci.yml mirrors this repo's real
package dependency graph as a job DAG, rather than one job building and
testing all four packages in sequence: audit (npm audit --audit-level=high against the whole workspace — independent, needs
nothing built) and build-securelib run first; once securelib is green,
build-securelib-piv, build-securelib-fido2, and build-securegit build
and test in parallel, each gated on its own package's vitest branch-coverage
threshold (coverage.thresholds.branches in that package's
vitest.config.ts) failing the job the moment real coverage drops below
it. CodeQL static analysis and secret scanning run alongside it
(codeql.yml,
gitleaks.yml), and the chaos sandbox
plus the coverage/audit badges above (metrics-badges job) publish
nightly (and on manual dispatch) to the same GitHub Pages site.
See specs/securegit/01-threat-model.md for exactly what this protects against, and — just as important — what it explicitly does not.
The FIDO2/CTAP2 equivalent, via the hmac-secret extension — same hardware-verified standard, shells out to libfido2's CLI tools. |
.claude/skills/checkout-and-unlock