See what your confidential contract still reveals.
In the Arc Privacy Sector, contract storage is confidential by default — but a single Open getter, an event carrying an amount, a bridge to the public chain, or an over-broad trust grant can put private state back in the open. Concentric reads a Solidity contract and its APS access policy, then traces every path a confidential value can take across the boundary. It runs entirely in your browser, before you deploy.
The in-browser analysis tool is for exploring one contract interactively. For a real project, the same rules run at the command line and in CI: Concentric reads every .sol file under the paths you give it, resolves imports and flattens inheritance into one model, and analyzes the confidentiality boundary of each contract as it will actually deploy. Findings are reported at their true file and line — including a leak that only exists because of an inherited member — and it exits non-zero when a leak is present, so a pull request that would expose confidential state never merges.
Concentric 0.1.0 - 4 file(s), 5 contract(s) HIGH APS-1 contracts/Payroll.sol:12 Open function returns confidential state ConfidentialPayroll.balanceOf has access policy Open, so any caller may invoke it. It returns `balances`, which is confidential storage. ... fix: Set balanceOf's policy to Restricted and gate it with require(msg.sender == who) so a caller can only read their own entry. MEDIUM APS-2 contracts/Vault.sol:11 Confidential value emitted in an event TreasuryVault.withdraw emits Withdrawal(to, amount). An argument carries a confidential amount to every party authorized to view the transaction. high: 1 medium: 1 low: 0 info: 2 x Failing - a finding at or above high is present.
Flags let CI decide what matters: --fail-on medium tightens the gate, --min-severity hides advisory noise, and --disable APS-6,APS-9 turns off individual rules. Emit machine-readable output with --sarif or --json. To analyze exactly what solc compiled (e.g., dependencies, libraries, and remapped imports resolved) point --build-info at a Foundry out/build-info or Hardhat artifacts/build-info directory; dependencies are linked for resolution but only your own contracts are reported unless you add --include-deps.
See findings in the Security tab, on every pull request. Concentric emits SARIF 2.1.0, the format GitHub code scanning renders natively. Add the Action and each finding appears inline on the pull request and in the repository's Security tab, annotated on the exact line and accompanied by a link to the rule that raised it.
# .github/workflows/confidentiality.yml name: Confidentiality on: [pull_request] permissions: contents: read security-events: write # upload SARIF to code scanning jobs: concentric: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: choosek/concentric-ts@v0 with: path: contracts fail-on: high
The Action runs the analysis, uploads the SARIF report so results always reach code scanning, then enforces the threshold as a separate step (so a blocked merge still leaves a full report behind to read).
The below scenarios explore five contracts an institution might actually deploy to APS: four with a distinct, realistic confidentiality bug, and one done right. Load any of them in the analyzer above.
How is analysis performed? Static analysis builds a model of your contract's exposure surface (the reachability of each function and the flow of confidential state to observable sinks) and checks it against the rules the APS specification implies. It flags application-level leaks and misconfiguration; it does not analyze the enclaves or the cryptography themselves.
Who can call what
Every function is placed by its APS access policy: Open (any caller), Restricted (grant-holders), Locked (no one). An external function with no policy is unreachable under default-deny. It is safe, but flagged in case it was meant to be callable.
Where confidential state goes
Storage is confidential by default. The analyzer propagates it forward to the sinks that make it observable (e.g., a function's return value, an emitted event, a bridge to the public EVM, or an argument handed to an untrusted contract) and reports the exact path.
Who you let look in
Every addTrustee grant is examined. A trustee gets non-zero introspection and can reach your Restricted entrypoints. Therefore, trust is transitive through whatever that trustee re-exposes. Over-broad and third-party grants are surfaced.
Hashes are not leaks
A confidential value disclosed only through a cryptographic hash (i.e., a commitment) is recognized as such, not flagged as a leak. It notes the disclosure is a commitment and reminds you it is only as strong as the entropy of its pre-image.
It won't just cry "Open"
An Open function that moves value but never returns, emits, or bridges it reveals nothing. The tool distinguishes disclosure from exposure, so a clean contract gets a clean bill, not noise.
Its own assumptions, stated
It models the published specification, not the live protocol. Its assumptions are listed with every run, and where APS behavior needs confirming (for example, immutables), it flags for review rather than asserting.
Acknowledge what you've reviewed. Not every disclosure is a mistake, as some are deliberate and reviewed. Concentric honors an inline comment so an accepted finding stops failing CI while staying documented in the source, next to the code it concerns.
Suppress a single line
Put // concentric-disable-next-line APS-2 above a line to silence one rule there, or // concentric-disable-line at the end of a line to silence all findings on it. Naming the rule keeps every other check live.
Tune the rule set
Disable a rule everywhere with --disable APS-9, or raise the reporting floor with --min-severity medium. The severity a rule reports and its documentation are fixed, so a suppressed finding is always traceable to a rule.
Nothing new to trust
The CLI, the Action, and the browser analyzer all rely on the @choosek/concentric library, tested to full coverage and relying on one rule set. All three default to the AST front-end, parsing with a real Solidity grammar (the vetted @solidity-parser/parser) for precise dataflow and inheritance. Browser and CLI run the same engine and so agree on every finding.
Interpret the results. Every finding Concentric reports comes from one of the rules enumerated below. Each has a stable identifier and a default severity.
| ID | Rule | What it flags |
|---|---|---|
| HighA confidential value can be exposed on a reachable path. Fails CI by default. | ||
| APS-1 | Open function returns confidential state | A function any caller can reach returns a value read from confidential storage. |
| APS-2 | Confidential value emitted in an event | An emitted event carries a confidential value or amount to every authorized viewer of the transaction. |
| APS-3 | Confidential value bridged to the public EVM | A confidential value is moved across the boundary onto the public ledger. |
| APS-4 | Over-broad trust grant | Trust is granted to an arbitrary or third-party contract, exposing introspection and Restricted entrypoints. |
| APS-10 | Confidential value passed to an external contract | A confidential value is handed as an argument to a contract outside the boundary. |
| APS-14 | Confidential value copied into a public variable | A confidential value is assigned into a public or cleared variable, exposing it through that variable's getter. |
| MediumA likely disclosure that warrants review before deploying. | ||
| APS-5 | Public state variable auto-generates a getter | A public state variable exposes confidential storage through its implicit getter. |
| APS-12 | Confidential value indexed in an event | A confidential value is declared indexed, placing it in a queryable log topic. |
| APS-T | Transitive exposure through a trusted contract | An Open forwarder re-exposes the confidential state of a contract that trusts it. |
| LowA narrow or conditional disclosure worth confirming. | ||
| APS-7 | Confidential value in immutable or constant | A confidential value is placed in an immutable or constant, which may live in bytecode. |
| APS-8 | Open function echoes a calldata argument | A function any caller can reach returns a value derived from its arguments. |
| InfoAdvisory — a disclosure that is often intended, surfaced so it is reviewed. | ||
| APS-2b | Event reveals transaction metadata | An emitted event exposes a counterparty or timing, though no confidential amount. |
| APS-4ok | Trust granted to a compliance party | Trust is granted to a named auditor or regulator — a deliberate, policy-driven disclosure. |
| APS-6 | External function has no access policy | An externally visible function carries no APS access policy and is unreachable under default-deny. |
| APS-9 | Restricted function returns confidential state to grant-holders | A grant-holder-reachable function returns confidential state to every holder of a grant. |
| APS-H | Confidential value disclosed as a commitment | A confidential value is disclosed only through a cryptographic hash rather than in the clear. |
| PositiveA positive result: a correctly gated read or a clean bill. | ||
| APS-OK | Owner-gated confidential read | A confidential read is correctly gated so each caller reads only their own entry. |
| APS-CLEAN | No confidentiality findings | No reachable path exposes a confidential value beyond what is appropriately disclosed. |
These rules model the published APS specification at the application level. They reason about the paths a contract's own code opens, not the enclaves or cryptography beneath, and a clean result is an aid to review rather than a guarantee.