Protect access to your data

Cadrumo encrypts each profile under its own data key. The profile passphrase opens that key for normal login and daily work. An optional recovery code can open the same key for one purpose only: setting a new passphrase when the old one is forgotten. It never participates in login.

Use this guide to store the passphrase safely, decide whether to set up a recovery code, change or reset the passphrase, run commands without an interactive prompt, log out safely, and reset local state only as a last resort.

Before you start

You need:

Use --language en, es, ca, or hu when you need a specific output language.

Store your passphrase safely

Your passphrase opens the key to your encrypted data. Treat it accordingly:

  • Write the passphrase down and keep it offline, separate from your computer.

  • Keep a second copy somewhere you can still reach after a disk failure.

  • Never store it in a shared shell profile, a committed script, or a log.

If you lose the passphrase and have not set up a recovery code, the encrypted data is permanently unreadable. The only way forward is a reset, which deletes it.

Do not reset when only some records fail to open. Quarantine them first. Quarantine moves each unreadable record, still encrypted, into an archive inside the same storage and leaves every readable record untouched, so it deletes nothing and can be previewed before it runs. See diagnose and repair your local setup.

Set up a recovery code (optional)

A recovery code is a second way to open the profile data key. It exists so a forgotten passphrase can be replaced instead of costing the records. It is off by default: a profile is created with its passphrase as its only door, and you choose whether to add the code.

When you create a profile at a terminal, Cadrumo asks once whether to set up a recovery code and defaults to no. The full-screen registration asks the same question on its own screen with a Skip for now choice. Either way you can decide later.

To enable it on the active profile:

aeat config profile recovery enable

Cadrumo asks for the current passphrase, shows the code once on the terminal, and asks you to type it back before anything is installed. The code looks like XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX; case, hyphens and spaces do not matter when you type it. Write it down and keep it apart from your passphrase and apart from the computer holding the data. Cadrumo keeps no copy and cannot show it again.

Anyone holding the code can set a new passphrase for the profile, so treat it as you would the passphrase itself. To check or remove it:

aeat config profile recovery status
aeat config profile recovery disable

Disabling asks for the current passphrase. Enabling again issues a fresh code; the old one stops working.

The code stays valid across ordinary passphrase changes. It does not travel inside a backup archive: a restored profile always starts without recovery, and the restore command says so. Enable it again after a restore if you want it.

Reset a forgotten passphrase

If you have the recovery code and have forgotten the passphrase:

aeat config passphrase reset PROFILE

Enter the recovery code, then the new passphrase twice. Nothing is echoed. The existing key is re-wrapped under the new passphrase, so no record is re-encrypted and the recovery code keeps working afterwards. A wrong code, a profile without recovery, a new passphrase that is too short, or two copies that do not match all refuse and leave the profile untouched.

To reset without an interactive prompt, send {"recovery_code": "...", "new_passphrase": "...", "new_passphrase_confirmation": "..."} through --secrets-stdin or --secrets-fd FD.

Enable recovery without a terminal

A recovery code can only be shown on a terminal, so a headless caller must take delivery of it explicitly. Provide --secrets-stdin or --secrets-fd FD with {"passphrase": "..."}, plus both --recovery-handoff-fd WRITE_FD and --recovery-verification-fd READ_FD. Read exactly one object shaped as {"recovery_code":"..."} from the first pipe, store the code securely, then send the same strict object through the second pipe. Use distinct anonymous pipes and distinct descriptor numbers; neither descriptor may be 0, 1, or 2 or collide with --secrets-fd. The process closes each descriptor after its one bounded operation. A missing half, malformed proof, mismatch, oversized payload, descriptor collision, or I/O failure enrols nothing.

Change your passphrase

Change the passphrase whenever you suspect it has been seen, or on whatever schedule your own policy sets:

1Change the passphrase that protects the active profile.
aeat config passphrase change
aeat config passphrase change

Enter the current passphrase, then the new one twice. Nothing is echoed.

The change re-wraps the existing key rather than replacing it, so:

  • No record is re-encrypted, and every existing record stays readable.

  • A recovery code you set up earlier stays correct.

The command reports both facts back to you. If the current passphrase is wrong, the new one is too short, or the two copies do not match, it refuses and leaves the existing passphrase working.

To change the passphrase without an interactive prompt, pass the three values as one JSON object through a leaf secret channel:

1Change the passphrase without an interactive prompt.
aeat config passphrase change --secrets-stdin
aeat config passphrase change --secrets-stdin

Use --secrets-stdin when standard input is free. Use --secrets-fd FD with the same object when another input owns standard input. Send {"current_passphrase": "...", "new_passphrase": "...", "new_passphrase_confirmation": "..."}. Never pass a passphrase as a command-line argument: arguments are visible in the process list and in shell history.

Run without a passphrase prompt

Automation cannot answer an interactive passphrase prompt. Supply a bounded UTF-8 JSON object through an explicit input channel. Do not put a profile passphrase, recovery code, or certificate passphrase in an environment variable or command-line argument. The CLI does not use CADRUMO_SECRET_PASSPHRASE as a secret-input fallback.

There are two separate option pairs:

  • Use leaf --secrets-stdin or --secrets-fd FD when the command itself owns the secret. The leaf commands and their exact objects are listed below.

  • Use root --profile-secrets-stdin or --profile-secrets-fd FD before config when a profile-bound command needs to authenticate a selected profile after its persisted session cannot resume. Its object is {"profile_passphrase": "..."}.

The root options do not replace leaf options. For example, certificate-secret storage may need a root profile passphrase and a certificate passphrase in the same invocation. Supply them through two non-colliding channels.

Supply a secret owned by a leaf command

Use the same two leaf flags on each scalar-secret command:

Command

Strict JSON object

aeat config login

{"passphrase": "..."}

aeat config profile create

{"passphrase": "...", "passphrase_confirmation": "..."}

aeat config passphrase change

{"current_passphrase": "...", "new_passphrase": "...", "new_passphrase_confirmation": "..."}

aeat config passphrase reset PROFILE

{"recovery_code": "...", "new_passphrase": "...", "new_passphrase_confirmation": "..."}

aeat config profile archive import

{"passphrase": "..."}

aeat config profile recovery enable

{"passphrase": "..."}

aeat config profile recovery disable

{"passphrase": "..."}

aeat config auth certificate secret set

{"certificate_passphrase": "..."}

Each object must contain exactly the fields shown. Profile creation through a machine channel never asks about recovery; enable it afterwards with the handoff pair described in enable recovery without a terminal. Duplicate, missing, extra, oversized, malformed, or non-UTF-8 input is refused. The former restore field password and certificate field secret are not accepted.

Inspect the current flags without supplying a secret:

1Inspect the root profile-authentication channels.
aeat --help
aeat --help
CADRUMO - local-first workflow with the Spanish Tax Agency (AEAT)

Cadrumo keeps taxpayer data local and exposes exactly two command roots.
The config root manages profiles, encrypted local data, recovery keys, profile sessions, AEAT authentication, and repair.
The app root manages profile overview, ledger, modelo, review, and authenticated AEAT reads.
A profile stores one taxpayer's local facts and settings. The active profile is the selected taxpayer context. A profile session provides resumable access to encrypted profile data.
The ledger contains imported money movements. A modelo is an official AEAT declaration form, distinct from its local work unit or export.
The review queue contains findings that need operator action.
Profile labels stay visible. Tax identities, credentials, storage identifiers, object keys, and sensitive web-address content stay protected.
For isolated state, set CADRUMO_LOCAL_STORAGE_ROOT and CADRUMO_SECRET_STORE_DIR. Pass profile secrets through --profile-secrets-stdin or --profile-secrets-fd, and command secrets through --secrets-stdin or --secrets-fd.

Start or resume
  aeat config profile create NAME  Create the first local taxpayer profile.
  aeat config profile list         List registered profiles and identify the active one.
  aeat config login NAME           Select a profile and start or renew its session.
  aeat config profile status       Check profile readiness and its authoritative next action.
  aeat config profile edit [NAME]  Complete or correct an incomplete profile.

Core workflow
  aeat config profile status                Show profile readiness.
  aeat app overview status                  Summarize profile and work state.
  aeat app ledger import                    Import bank or accounting transactions.
  aeat app ledger review                    Review imported rows that need attention.
  aeat app modelo work --help               Create or operate modelo work units.
  aeat app modelo verification-report list  List saved filing verification reports.
  aeat app review queue                     Show profile-wide items awaiting operator review.

Resume or recover
  aeat config login NAME      Start or renew an absent, closed, or expired profile session.
  aeat config repair          Before changing configuration or stored data, run this read-only health report.
  aeat config repair profile  Inspect a reported active-profile pointer problem.

Command families
  aeat config  Open profile, session, custody, storage, and repair commands.
  aeat app     Open profile-scoped tax-work commands.

Diagnostics
  aeat app overview status --verbose  Show general state with additional detail.
  aeat --format json config repair    Return the complete local health report as JSON.
  aeat config repair logs             Show the log path and latest lines.
  aeat --version --detail             Show the installed package identity.

Profile authentication options
  --profile-secrets-stdin  Read the exact target profile passphrase from one bounded JSON object on standard input after session resume fails.
  --profile-secrets-fd FD  Read the exact target profile passphrase from one bounded JSON object on an inherited readable descriptor after session resume fails.

Add --help for details. Remove sensitive log values, then report bugs at github.com/nevenincs/cadrumo/issues.
2Inspect the login secret channels.
aeat config login --help
aeat config login --help
Usage: aeat config login [OPTIONS] [name]

  Authenticate a profile and start a resumable session

Arguments:
  name  Profile UUID or exact label to log in; omit to use the selected
        profile

Options:
  --secrets-stdin                 Read the secrets as one bounded JSON object
                                  from standard input instead of prompting.
  --secrets-fd <int>              File descriptor to read machine secrets
                                  from.
  --output-language, --language <es|en|ca|hu>
                                  Output language for the response text (es,
                                  en, ca, hu).
  --install-completion            Install completion for the current shell.
  --show-completion               Show completion for the current shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.
3Inspect the profile-creation secret channels.
aeat config profile create --help
aeat config profile create --help
Usage: aeat config profile create [OPTIONS] [profile_name]

  Create a profile protected by a passphrase. At a terminal it then offers an
  optional recovery code; skip it, or add it later with 'aeat config profile
  recovery enable'.

Arguments:
  profile_name  Profile name to write the answers into

Options:
  --quiet                         Run non-interactively using only the
                                  supplied flag values; required flags left
                                  unset cause an error. Sufficient on its own;
                                  combine with --accept-defaults to fill the
                                  rest from descriptor defaults.
  --accept-defaults               Fill every unsupplied flag from the
                                  descriptor defaults, without prompting. Use
                                  alongside --quiet, or on its own for an all-
                                  defaults create.
  --output-language <es|en|ca|hu>
                                  CLI output language for this profile
  --entity-type <str>             Natural person, legal entity, or attribution
                                  entity
  --legal-entity-form <str>       Recognised legal form when the entity type
                                  is a legal entity
  --tax-id <str>                  Tax identifier (NIF/CIF/DNI/NIE) for the
                                  active profile
  --name <str>                    Display name shown in local reviews
  --surnames <str>                Surnames or company name for export headers
  --legal-name <str>              Legal or company name for legal-entity
                                  export headers
  --fiscal-residency <str>        Fiscal residency category: resident_irpf
                                  (habitual resident in Spain) or
                                  non_resident_irnr (non-resident, IRNR
                                  taxation RDLeg 5/2004).
  --country-of-fiscal-residence <str>
                                  ISO 3166-1 alpha-2 code of the country of
                                  fiscal residence (e.g. GB, DE, FR). Required
                                  when fiscal-residency is non_resident_irnr.
  --representante-fiscal-nif <str>
                                  NIF/NIE of the fiscal representative in
                                  Spain. Required for non-EU/EEA non-residents
                                  (Art. 47 LGT + Art. 10 TRLIRNR RDLeg
                                  5/2004).
  --representante-fiscal-nombre <str>
                                  Full name of the fiscal representative in
                                  Spain.
  --tax-residence-jurisdiction-scope <str>
                                  Declared territorial regime: common
                                  territory or the not-yet-supported foral
                                  territory.
  --tax-residence-ccaa <str>      Tax-residence autonomous community, under
                                  the common regime. If the value is not
                                  recognised, the error lists the accepted
                                  communities.
  --address-postcode <str>        Tax address postcode
  --irpf-income-categories <str>  IRPF income category; pass --irpf-income-
                                  categories once per category
  --activity <str>                Business activity as plain text, or the IAE
                                  heading if you know it
  --activity-start-date <str>     Censo registration / start-of-activity date
                                  (YYYY-MM-DD). Optional; when set,
                                  obligations for periods before this date are
                                  not shown.
  --incn-prior-12-months <str>    Net turnover (importe neto de la cifra de
                                  negocios) over the prior 12 months, in
                                  euros. Above 6,000,000 EUR Modelo 202
                                  mandates the Art. 40.3 LIS modality; below
                                  it both modalities are available. Optional.
  --new-entity-first-two-profit-periods / --no-new-entity-first-two-profit-periods
                                  Mark this when a newly-created legal entity
                                  is in one of its first two profit-making tax
                                  periods (LIS Art. 29) to opt into the 15
                                  percent reduced rate. Optional; unset leaves
                                  the entity on its otherwise-applicable rate.
  --ley-49-2002-option-declared / --no-ley-49-2002-option-declared
                                  Record whether Modelo 036 casilla 651
                                  declares the Ley 49/2002 Title II option.
  --ley-49-2002-option-date <str>
                                  Record the Modelo 036 casilla 653 date for
                                  the Ley 49/2002 Title II option.
  --ley-49-2002-renunciation-declared / --no-ley-49-2002-renunciation-declared
                                  Record whether Modelo 036 casilla 652
                                  declares renunciation of the Ley 49/2002
                                  Title II regime.
  --ley-49-2002-renunciation-date <str>
                                  Record the Modelo 036 casilla 654 date for
                                  the Ley 49/2002 Title II renunciation.
  --iva-regime <str>              IVA regime
  --iva-m303-regime-composition <str>
                                  Modelo 303 regime composition: general,
                                  simplified, or mixed.
  --iva-roi-enrolled / --no-iva-roi-enrolled
                                  Enrolled in ROI
  --iva-oss-enrolled / --no-iva-oss-enrolled
                                  Enrolled in OSS
  --iva-group-member-enrolled / --no-iva-group-member-enrolled
                                  Enrolled as a member entity of an IVA group
  --iva-group-dominant-entity-enrolled / --no-iva-group-dominant-entity-enrolled
                                  Enrolled as the dominant entity of an IVA
                                  group
  --iva-sii-enrolled / --no-iva-sii-enrolled
                                  Whether the taxpayer is enrolled in the SII
  --iva-redeme-enrolled / --no-iva-redeme-enrolled
                                  Whether the taxpayer is registered in REDEME
  --iva-intracommunity-operations-exceed-50000-eur / --no-iva-intracommunity-operations-exceed-50000-eur
                                  Intra-community operations exceed 50,000 EUR
  --iva-cash-accounting-regime-enrolled / --no-iva-cash-accounting-regime-enrolled
                                  Declare enrolment in the special IVA cash-
                                  accounting regime.
  --iva-voluntary-sii-enrolled / --no-iva-voluntary-sii-enrolled
                                  Declare voluntary SII enrolment separately
                                  from mandatory inclusion triggers.
  --iva-hydrocarbon-deposit-advance-payment-deduction-entitled / --no-iva-hydrocarbon-deposit-advance-payment-deduction-entitled
                                  Declare entitlement to deduct advance
                                  payments under the hydrocarbon tax-deposit
                                  regime.
  --enrollment-large-company / --no-enrollment-large-company
                                  Large-company enrollment
  --enrollment-public-administration-budget-gt-6000000 / --no-enrollment-public-administration-budget-gt-6000000
                                  Public administration budget over 6,000,000
  --taxation-type <str>           Income-tax return type: 1 = individual, 2 =
                                  joint (family unit).
  --taxpayer-sex <str>            First taxpayer sex: H = male, M = female.
  --taxpayer-marital-status <1|2|3|4|5>
                                  First taxpayer marital status: 1 = single, 2
                                  = married, 3 = widowed, 4 = separated or
                                  divorced, 5 = registered civil partnership.
  --situacion-familiar <casado|pareja_hecho_registrada|pareja_hecho_no_registrada|soltero|separado_divorciado>
                                  Family situation under Art. 82 LIRPF:
                                  determines whether joint taxation is
                                  available and which family unit variant
                                  applies.
  --taxpayer-marriage-date <str>  Start date of the current marriage (YYYY-MM-
                                  DD). Only when marital status = 2 (married).
                                  Derives casillas 0245/0246/0247 (matrimonio
                                  sobrevenido, Art. 82 LIRPF).
  --taxpayer-birth-date <str>     First taxpayer birth date
  --taxpayer-disability-grade <str>
                                  First taxpayer disability grade: 1 =
                                  33%-64%, 2 = 65% or higher, 3 = judicial
                                  incapacity, 4 = third-party assistance or
                                  reduced mobility.
  --taxpayer-death-date <str>     First taxpayer death date
  --spouse-tax-id <str>           Spouse NIF/NIE
  --spouse-name <str>             Spouse given name
  --spouse-surnames <str>         Spouse surnames
  --spouse-birth-date <str>       Spouse birth date
  --spouse-sex <str>              Spouse sex: H = male, M = female.
  --spouse-disability-grade <str>
                                  Spouse disability grade (if applicable): 1 =
                                  33%-64%, 2 = 65% or higher, 3 = judicial
                                  incapacity, 4 = third-party assistance or
                                  reduced mobility.
  --spouse-non-resident-irpf / --no-spouse-non-resident-irpf
                                  Spouse is non-resident IRPF
  --spouse-eu-eea-resident / --no-spouse-eu-eea-resident
                                  Spouse is EU/EEA resident
  --spouse-eu-eea-country <str>   Spouse EU/EEA country
  --family-descendants-eu-eea-deduction / --no-family-descendants-eu-eea-deduction
                                  EU/EEA descendants in family-unit deduction
  --family-minor-children-in-unit / --no-family-minor-children-in-unit
                                  Minor children in family unit
  --has-employees / --no-has-employees
                                  Has employees and pays salaries with
                                  retención
  --pays-professionals-with-retencion / --no-pays-professionals-with-retencion
                                  Pays professionals with retención
  --art109-activity-income-withholding-ge-70pct / --no-art109-activity-income-withholding-ge-70pct
                                  Art. 109 coverage - at least 70% of covered
                                  professional, agricultural, livestock, or
                                  forestry activity income subject to
                                  withholding or payment on account
  --pays-rent-with-retencion / --no-pays-rent-with-retencion
                                  Pays local rent with retención
  --pays-capital-income-with-retencion / --no-pays-capital-income-with-retencion
                                  Pays capital income with retención
  --modelo-111-no-retenciones-periods <str>
                                  Comma-separated Modelo 111 no-obligation
                                  periods, format YYYY:PERIOD (for example
                                  2025:2T,2025:3T). Use only when no subject
                                  payments were made.
  --irpf-estimation-regime <str>  IRPF estimation regime for economic-activity
                                  income
  --objective-estimation-modulos-iae-epigraph <str>
                                  IAE epigraph for objective-estimation
                                  modules, such as 972.
  --objective-estimation-modulos-module-1-units <str>
                                  Annual module 1 units for objective-
                                  estimation modules.
  --objective-estimation-modulos-module-2-units <str>
                                  Annual module 2 units for objective-
                                  estimation modules.
  --objective-estimation-modulos-module-3-units <str>
                                  Annual module 3 units for objective-
                                  estimation modules.
  --objective-estimation-modulos-module-4-units <str>
                                  Annual module 4 units for objective-
                                  estimation modules.
  --objective-estimation-modulos-module-5-units <str>
                                  Annual module 5 units for objective-
                                  estimation modules.
  --objective-estimation-modulos-module-6-units <str>
                                  Annual module 6 units for objective-
                                  estimation modules.
  --objective-estimation-modulos-module-7-units <str>
                                  Annual module 7 units for objective-
                                  estimation modules.
  --irpf-special-regime <str>     IRPF special regime. "general" for most
                                  taxpayers. "impatriado" for workers who
                                  relocated to Spain and elected the Beckham
                                  Law regime (Art. 93 LIRPF), available for
                                  the first 6 years after relocation.
  --irpf-special-regime-start-date <str>
                                  Date the worker elected the inpatriate
                                  regime (YYYY-MM-DD). Only when irpf-special-
                                  regime is "impatriado". RIRPF Art. 116. The
                                  regime expires at the end of the 6th
                                  calendar year.
  --does-intracomunitario / --no-does-intracomunitario
                                  Conducts intracomunitario operations
  --third-party-transactions-above-347-threshold / --no-third-party-transactions-above-347-threshold
                                  Third-party transactions exceed Modelo 347
                                  threshold
  --bienes-extranjero-above-threshold / --no-bienes-extranjero-above-threshold
                                  Foreign-held assets above legal threshold
  --monedas-virtuales-extranjero-above-threshold / --no-monedas-virtuales-extranjero-above-threshold
                                  Virtual currencies held abroad above Modelo
                                  721 threshold
  --llm-vision / --no-llm-vision  Enable on-host LLM vision for reading
                                  invoices.
  --google-export / --no-google-export
                                  Enable Google Sheets export.
  --notes <str>                   Notes for your own records (optional)
  --secrets-stdin                 Read the secrets as one bounded JSON object
                                  from standard input instead of prompting.
  --secrets-fd <int>              File descriptor to read machine secrets
                                  from.
  --install-completion            Install completion for the current shell.
  --show-completion               Show completion for the current shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.
4Inspect the passphrase-change secret channels.
aeat config passphrase change --help
aeat config passphrase change --help
Usage: aeat config passphrase change [OPTIONS]

  Change the passphrase without re-encrypting records or invalidating a
  recovery code.

Options:
  --secrets-stdin                 Read the secrets as one bounded JSON object
                                  from standard input instead of prompting.
  --secrets-fd <int>              File descriptor to read machine secrets
                                  from.
  --output-language, --language <es|en|ca|hu>
                                  Output language for the response text (es,
                                  en, ca, hu).
  --install-completion            Install completion for the current shell.
  --show-completion               Show completion for the current shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.
5Inspect the profile-restore secret channels.
aeat config profile archive import --help
aeat config profile archive import --help
Usage: aeat config profile archive import [OPTIONS] {label}

  Republish a profile capsule from a directory or a sealed archive, proving
  its passphrase.

Arguments:
  label  Display name for the restored profile.  [required]

Options:
  --file <path>                   Path to the sealed archive or capsule
                                  directory to restore.  [required]
  --secrets-stdin                 Read the secrets as one bounded JSON object
                                  from standard input instead of prompting.
  --secrets-fd <int>              File descriptor to read machine secrets
                                  from.
  --output-language, --language <es|en|ca|hu>
                                  Output language for the response text (es,
                                  en, ca, hu).
  --install-completion            Install completion for the current shell.
  --show-completion               Show completion for the current shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.
6Inspect the certificate-passphrase secret channels.
aeat config auth certificate secret set --help
aeat config auth certificate secret set --help
Usage: aeat config auth certificate secret set [OPTIONS]

  Set (or rotate) the passphrase for a registered certificate source

Options:
  --name <str>                    Registered certificate source the passphrase
                                  is bound to  [required]
  --secrets-stdin                 Read the secrets as one bounded JSON object
                                  from standard input instead of prompting.
  --secrets-fd <int>              File descriptor to read machine secrets
                                  from.
  --output-language, --language <es|en|ca|hu>
                                  Output language for the response text (es,
                                  en, ca, hu).
  --install-completion            Install completion for the current shell.
  --show-completion               Show completion for the current shell, to
                                  copy it or customize the installation.
  --help                          Show this message and exit.

Confirm every scalar-secret leaf exposes both explicit channels.

Authenticate a profile for one command

Place a root option before the command path. Name the exact profile on the target command when it accepts a profile target. For example:

aeat --profile-secrets-stdin config profile view PROFILE

This source is used only after the exact profile is known, the command requires a profile session, no matching live session exists, and persisted-session resume has failed. It is refused without being read when the command does not need profile authentication, when the exact target already has a live or resumed session, or when the command is self-authenticating. Login, create, restore, logout, passphrase change, passphrase reset, and recovery enable or disable do not accept this root proof.

On a host without usable keychain persistence, successful root authentication continues only the current process. The command emits a Notice that the passphrase must be supplied again in the next process. Each new process repeats the Argon2 password derivation; this deliberate cost is not cached in a weaker credential or transferable bearer token.

Keep two secret sources distinct

Choose sources before either is read:

  • Do not select both stdin and a descriptor for the same option pair.

  • Do not assign stdin to both the root and leaf scopes.

  • Do not assign the same descriptor number to both scopes.

  • Use root fd plus leaf stdin, root stdin plus leaf fd, or two different descriptors when one invocation needs both objects.

A selected source that does not apply to the resolved command or exact profile is refused as unused. This protects scripts from sending a credential to the wrong target after a command-line mistake.

Pass an inherited descriptor safely

Use an anonymous pipe when standard input already carries another value. The caller owns the descriptor’s backing object and lifetime. Cadrumo performs one bounded read and closes the descriptor locally after reading begins, including on refusal. Descriptor 0 is allowed as a stdin-equivalent; descriptors 1 and 2, negative descriptors, closed descriptors, and unreadable descriptors are refused.

On POSIX, start the process with the pipe’s read descriptor in the child process’s pass_fds allowlist, then pass that number to --secrets-fd or --profile-secrets-fd. On Windows, do not assume a numeric CRT descriptor is inherited directly. Allowlist inheritable Windows HANDLEs and use the supported bootstrap wrapper to convert them with msvcrt.open_osfhandle. Recovery enrolment uses one writable handoff HANDLE and one readable verification HANDLE:

python -m cadrumo.entrypoints.cli._windows_profile_secret_bootstrap --profile-handle ROOT_HANDLE --secrets-handle LEAF_HANDLE -- config auth certificate secret set --name SOURCE
python -m cadrumo.entrypoints.cli._windows_profile_secret_bootstrap --recovery-handoff-handle WRITE_HANDLE --recovery-verification-handle READ_HANDLE -- config profile recovery enable --quiet --secrets-stdin

Omit --profile-handle or --secrets-handle when the invocation needs only one scope. The wrapper inserts the matching canonical descriptor option; do not also add it to the command tail. Prefer the stdin channel for a directly launched portable command.

Cadrumo bounds the input, wipes mutable read buffers on a best-effort basis, and releases secret-bearing references promptly. Python may create immutable strings while decoding JSON and validating secret fields, so the application does not claim guaranteed memory erasure. Never reuse the pipe, redirect it to a regular file, or log the JSON object.

Interactive commands may prompt only on a verified terminal. A redirected or non-interactive invocation without the required explicit channel is refused.

Log out of the active profile

Run aeat config logout when you finish working with a profile. Logout closes the active storage session, discards in-memory key material, disposes the bucket engines, and keeps the profile selected for the next exact login:

1Confirm a profile is currently active.
aeat config profile status
aeat config profile status
profile	docs-sequence-sandbox
profile_id	<profile-id>
identity.tax_id	sha256:1c9f9632
activities.description	documentation examples
iva.regime	GENERAL
tax_residence.ccaa	<unset>
Next step: `aeat app overview status`
2Close the active session while keeping the profile selected.
aeat config logout
aeat config logout
logged_out_profile	docs-sequence-sandbox
3Confirm that the registered profile remains selected after its session closes.
aeat --format json config profile list
aeat --format json config profile list
{
  "active_profile": "docs-sequence-sandbox",
  "command": "config.profile.list",
  "notices": [],
  "result": {
    "active_profile": "docs-sequence-sandbox",
    "profiles": [
      {
        "active": true,
        "bucket_id": "<bucket-id>",
        "name": "docs-sequence-sandbox"
      }
    ]
  },
  "schema_version": "2",
  "status": "success"
}

Confirm logout closes the active session without deleting the profile.

Nothing in the profile is deleted. Log in again with aeat config login to use the selected profile, or name another exact profile when you return.

Reset local state (last resort)

Export every profile you want to keep before continuing. Reset permanently deletes every profile stored locally, related local authentication state, and the active profile selection. It doesn’t delete exports stored elsewhere. You can’t undo the reset. Deleting is also not a way to hide activity: any copies of your data that left the machine (exports, filings, backups) are outside Cadrumo’s control and nothing in Cadrumo can retract them.

1Start the reset.
aeat config reset start --yes
aeat config reset start --yes
2Check the latest reset operation.
aeat config reset status
aeat config reset status
3Check an exact reset operation.
aeat config reset status --operation-id \
  0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
aeat config reset status --operation-id `
  0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
4Resume the incomplete reset.
aeat config reset resume --yes
aeat config reset resume --yes
5Resume an exact paused operation after reviewing legal duties.
aeat config reset resume --operation-id \
  0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef --yes \
  --override-retention --reason Legal retention duties reviewed
aeat config reset resume --operation-id `
  0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef --yes `
  --override-retention --reason Legal retention duties reviewed
  1. Use start only when you intend to remove all local profiles. Confirm the reset explicitly. It checks retention requirements before deleting anything and refuses to start while another reset is incomplete.

  2. Use the read-only status command to inspect the latest operation without changing local data. Provide an operation ID to inspect an exact operation. Status reports an incomplete, paused, or complete operation. Its output includes the operation ID and any pause reason.

  3. Use resume after an interruption or pause. Confirm the reset again. It continues the same incomplete operation instead of creating another. Provide an operation ID to resume an exact operation. After deletion starts, resuming the reset doesn’t restore deleted data.

If legal retention requirements pause the reset, stop if you must keep the affected records. Override the pause only after reviewing and accepting the legal retention consequence, and always provide a non-empty reason. If you provide a reason without the override, the command refuses to continue.

See Set up your taxpayer profile for sealed archive backup instructions.

Next steps